Skip to content

Commit

Permalink
Config change event (#1301)
Browse files Browse the repository at this point in the history
* Add events for config changes

* Update ConfigChangeEvent.cs

* change event names

---------

Co-authored-by: goat <[email protected]>
  • Loading branch information
Caraxi and goaaats committed Jul 6, 2023
1 parent e52f769 commit 7109f21
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 5 deletions.
7 changes: 7 additions & 0 deletions Dalamud/Game/Config/ConfigChangeEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using System;

namespace Dalamud.Game.Config;

public abstract record ConfigChangeEvent(Enum Option);

public record ConfigChangeEvent<T>(T ConfigOption) : ConfigChangeEvent(ConfigOption) where T : Enum;
61 changes: 58 additions & 3 deletions Dalamud/Game/Config/GameConfig.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using Dalamud.IoC;
using System;
using Dalamud.Hooking;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Common.Configuration;
using Serilog;

namespace Dalamud.Game.Config;
Expand All @@ -14,10 +18,13 @@ namespace Dalamud.Game.Config;
#pragma warning disable SA1015
[ResolveVia<IGameConfig>]
#pragma warning restore SA1015
public sealed class GameConfig : IServiceType, IGameConfig
public sealed class GameConfig : IServiceType, IGameConfig, IDisposable
{
private readonly GameConfigAddressResolver address = new();
private Hook<ConfigChangeDelegate>? configChangeHook;

[ServiceManager.ServiceConstructor]
private unsafe GameConfig(Framework framework)
private unsafe GameConfig(Framework framework, SigScanner sigScanner)
{
framework.RunOnTick(() =>
{
Expand All @@ -27,9 +34,18 @@ private unsafe GameConfig(Framework framework)
this.System = new GameConfigSection("System", framework, &commonConfig->ConfigBase);
this.UiConfig = new GameConfigSection("UiConfig", framework, &commonConfig->UiConfig);
this.UiControl = new GameConfigSection("UiControl", framework, () => this.UiConfig.TryGetBool("PadMode", out var padMode) && padMode ? &commonConfig->UiControlGamepadConfig : &commonConfig->UiControlConfig);
this.address.Setup(sigScanner);
this.configChangeHook = Hook<ConfigChangeDelegate>.FromAddress(this.address.ConfigChangeAddress, this.OnConfigChanged);
this.configChangeHook?.Enable();
});
}

private unsafe delegate nint ConfigChangeDelegate(ConfigBase* configBase, ConfigEntry* configEntry);

/// <inheritdoc/>
public event EventHandler<ConfigChangeEvent> Changed;

/// <inheritdoc/>
public GameConfigSection System { get; private set; }

Expand Down Expand Up @@ -110,4 +126,43 @@ private unsafe GameConfig(Framework framework)

/// <inheritdoc/>
public void Set(UiControlOption option, string value) => this.UiControl.Set(option.GetName(), value);

/// <inheritdoc/>
void IDisposable.Dispose()
{
this.configChangeHook?.Disable();
this.configChangeHook?.Dispose();
}

private unsafe nint OnConfigChanged(ConfigBase* configBase, ConfigEntry* configEntry)
{
var returnValue = this.configChangeHook!.Original(configBase, configEntry);
try
{
ConfigChangeEvent? eventArgs = null;

if (configBase == this.System.GetConfigBase())
{
eventArgs = this.System.InvokeChange<SystemConfigOption>(configEntry);
}
else if (configBase == this.UiConfig.GetConfigBase())
{
eventArgs = this.UiConfig.InvokeChange<UiConfigOption>(configEntry);
}
else if (configBase == this.UiControl.GetConfigBase())
{
eventArgs = this.UiControl.InvokeChange<UiControlOption>(configEntry);
}

if (eventArgs == null) return returnValue;

this.Changed?.InvokeSafely(this, eventArgs);
}
catch (Exception ex)
{
Log.Error(ex, $"Exception thrown handing {nameof(this.OnConfigChanged)} events.");
}

return returnValue;
}
}
18 changes: 18 additions & 0 deletions Dalamud/Game/Config/GameConfigAddressResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Dalamud.Game.Config;

/// <summary>
/// Game config system address resolver.
/// </summary>
public sealed class GameConfigAddressResolver : BaseAddressResolver
{
/// <summary>
/// Gets the address of the method called when any config option is changed.
/// </summary>
public nint ConfigChangeAddress { get; private set; }

/// <inheritdoc/>
protected override void Setup64Bit(SigScanner scanner)
{
this.ConfigChangeAddress = scanner.ScanText("E8 ?? ?? ?? ?? 48 8B 3F 49 3B 3E");
}
}
41 changes: 40 additions & 1 deletion Dalamud/Game/Config/GameConfigSection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics;

using Dalamud.Memory;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Common.Configuration;
using Serilog;

Expand All @@ -16,6 +17,12 @@ public class GameConfigSection
private readonly Framework framework;
private readonly Dictionary<string, uint> indexMap = new();
private readonly Dictionary<uint, string> nameMap = new();
private readonly Dictionary<uint, object> enumMap = new();

/// <summary>
/// Event which is fired when a game config option is changed within the section.
/// </summary>
public event EventHandler<ConfigChangeEvent> Changed;

/// <summary>
/// Initializes a new instance of the <see cref="GameConfigSection"/> class.
Expand Down Expand Up @@ -59,7 +66,10 @@ internal GameConfigSection(string sectionName, Framework framework, GetConfigBas
/// </summary>
public string SectionName { get; }

private GetConfigBaseDelegate GetConfigBase { get; }
/// <summary>
/// Gets the pointer to the config section container.
/// </summary>
internal GetConfigBaseDelegate GetConfigBase { get; }

/// <summary>
/// Attempts to get a boolean config option.
Expand Down Expand Up @@ -380,6 +390,35 @@ public unsafe void Set(string name, string value)
});
}

/// <summary>
/// Invokes a change event within the config section.
/// </summary>
/// <param name="entry">The config entry that was changed.</param>
/// <typeparam name="TEnum">SystemConfigOption, UiConfigOption, or UiControlOption.</typeparam>
/// <returns>The ConfigChangeEvent record.</returns>
internal unsafe ConfigChangeEvent? InvokeChange<TEnum>(ConfigEntry* entry) where TEnum : Enum
{
if (!this.enumMap.TryGetValue(entry->Index, out var enumObject))
{
if (entry->Name == null) return null;
var name = MemoryHelper.ReadStringNullTerminated(new IntPtr(entry->Name));
if (Enum.TryParse(typeof(TEnum), name, out enumObject))
{
this.enumMap.Add(entry->Index, enumObject);
}
else
{
enumObject = null;
this.enumMap.Add(entry->Index, null);
}
}

if (enumObject == null) return null;
var eventArgs = new ConfigChangeEvent<TEnum>((TEnum)enumObject);
this.Changed?.InvokeSafely(this, eventArgs);
return eventArgs;
}

private unsafe bool TryGetIndex(string name, out uint index)
{
if (this.indexMap.TryGetValue(name, out index))
Expand Down
9 changes: 8 additions & 1 deletion Dalamud/Plugin/Services/IGameConfig.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Diagnostics;
using System;
using System.Diagnostics;

using Dalamud.Game.Config;
using FFXIVClientStructs.FFXIV.Common.Configuration;

namespace Dalamud.Plugin.Services;

Expand All @@ -9,6 +11,11 @@ namespace Dalamud.Plugin.Services;
/// </summary>
public interface IGameConfig
{
/// <summary>
/// Event which is fired when a game config option is changed.
/// </summary>
public event EventHandler<ConfigChangeEvent> Changed;

/// <summary>
/// Gets the collection of config options that persist between characters.
/// </summary>
Expand Down

0 comments on commit 7109f21

Please sign in to comment.