diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs index effba80d7..b9179edde 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs @@ -32,8 +32,7 @@ internal unsafe class AddonLifecycle : IInternalDisposableService private readonly nint disallowedReceiveEventAddress; private readonly AddonLifecycleAddressResolver address; - private readonly CallHook onAddonSetupHook; - private readonly CallHook onAddonSetup2Hook; + private readonly AddonSetupHook onAddonSetupHook; private readonly Hook onAddonFinalizeHook; private readonly CallHook onAddonDrawHook; private readonly CallHook onAddonUpdateHook; @@ -43,9 +42,6 @@ internal unsafe class AddonLifecycle : IInternalDisposableService [ServiceManager.ServiceConstructor] private AddonLifecycle(TargetSigScanner sigScanner) { - // TODO: Service is currently non-functional pending DT changes. NOP'd. - return; - this.address = new AddonLifecycleAddressResolver(); this.address.Setup(sigScanner); @@ -53,8 +49,7 @@ internal unsafe class AddonLifecycle : IInternalDisposableService var refreshAddonAddress = (nint)RaptureAtkUnitManager.StaticVirtualTablePointer->RefreshAddon; - this.onAddonSetupHook = new CallHook(this.address.AddonSetup, this.OnAddonSetup); - this.onAddonSetup2Hook = new CallHook(this.address.AddonSetup2, this.OnAddonSetup); + this.onAddonSetupHook = new AddonSetupHook(this.address.AddonSetup, this.OnAddonSetup); this.onAddonFinalizeHook = Hook.FromAddress(this.address.AddonFinalize, this.OnAddonFinalize); this.onAddonDrawHook = new CallHook(this.address.AddonDraw, this.OnAddonDraw); this.onAddonUpdateHook = new CallHook(this.address.AddonUpdate, this.OnAddonUpdate); @@ -62,7 +57,6 @@ internal unsafe class AddonLifecycle : IInternalDisposableService this.onAddonRequestedUpdateHook = new CallHook(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate); this.onAddonSetupHook.Enable(); - this.onAddonSetup2Hook.Enable(); this.onAddonFinalizeHook.Enable(); this.onAddonDrawHook.Enable(); this.onAddonUpdateHook.Enable(); @@ -85,14 +79,12 @@ internal unsafe class AddonLifecycle : IInternalDisposableService /// void IInternalDisposableService.DisposeService() { - // TODO: Service is currently non-functional pending DT changes. - // this.onAddonSetupHook.Dispose(); - // this.onAddonSetup2Hook.Dispose(); - // this.onAddonFinalizeHook.Dispose(); - // this.onAddonDrawHook.Dispose(); - // this.onAddonUpdateHook.Dispose(); - // this.onAddonRefreshHook.Dispose(); - // this.onAddonRequestedUpdateHook.Dispose(); + this.onAddonSetupHook.Dispose(); + this.onAddonFinalizeHook.Dispose(); + this.onAddonDrawHook.Dispose(); + this.onAddonUpdateHook.Dispose(); + this.onAddonRefreshHook.Dispose(); + this.onAddonRequestedUpdateHook.Dispose(); foreach (var receiveEventListener in this.ReceiveEventListeners) { @@ -106,9 +98,6 @@ internal unsafe class AddonLifecycle : IInternalDisposableService /// The listener to register. internal void RegisterListener(AddonLifecycleEventListener listener) { - // TODO: Service is currently non-functional pending DT changes. NOP'd. - return; - this.framework.RunOnTick(() => { this.EventListeners.Add(listener); @@ -131,9 +120,6 @@ internal unsafe class AddonLifecycle : IInternalDisposableService /// The listener to unregister. internal void UnregisterListener(AddonLifecycleEventListener listener) { - // TODO: Service is currently non-functional pending DT changes. NOP'd. - return; - // Set removed state to true immediately, then lazily remove it from the EventListeners list on next Framework Update. listener.Removed = true; diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs index 90a883816..baf8bb86c 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs @@ -1,4 +1,4 @@ -using FFXIVClientStructs.FFXIV.Component.GUI; +using FFXIVClientStructs.FFXIV.Component.GUI; namespace Dalamud.Game.Addon.Lifecycle; @@ -47,8 +47,7 @@ internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver /// The signature scanner to facilitate setup. protected override void Setup64Bit(ISigScanner sig) { - this.AddonSetup = sig.ScanText("41 FF D1 48 8B 93 ?? ?? ?? ?? 80 8B"); // TODO: verify - this.AddonSetup2 = sig.ScanText("41 FF D1 48 8B 03 48 8B CB"); // TODO: verify + this.AddonSetup = sig.ScanText("4C 8B 88 ?? ?? ?? ?? 66 44 39 BB"); this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 83 EF 01 75 D5"); this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C4 48 81 EF ?? ?? ?? ?? 48 83 ED 01"); this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF ?? ?? ?? ?? 45 33 D2"); diff --git a/Dalamud/Game/Addon/Lifecycle/AddonSetupHook.cs b/Dalamud/Game/Addon/Lifecycle/AddonSetupHook.cs new file mode 100644 index 000000000..aa684a644 --- /dev/null +++ b/Dalamud/Game/Addon/Lifecycle/AddonSetupHook.cs @@ -0,0 +1,80 @@ +using System.Runtime.InteropServices; + +using Reloaded.Hooks.Definitions; + +namespace Dalamud.Game.Addon.Lifecycle; + +/// +/// This class represents a callsite hook used to replace the address of the OnSetup function in r9. +/// +/// Delegate signature for this hook. +internal class AddonSetupHook : IDisposable where T : Delegate +{ + private readonly Reloaded.Hooks.AsmHook asmHook; + + private T? detour; + private bool activated; + + /// + /// Initializes a new instance of the class. + /// + /// Address of the instruction to replace. + /// Delegate to invoke. + internal AddonSetupHook(nint address, T detour) + { + this.detour = detour; + + var detourPtr = Marshal.GetFunctionPointerForDelegate(this.detour); + var code = new[] + { + "use64", + $"mov r9, 0x{detourPtr:X8}", + }; + + var opt = new AsmHookOptions + { + PreferRelativeJump = true, + Behaviour = Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour.DoNotExecuteOriginal, + MaxOpcodeSize = 5, + }; + + this.asmHook = new Reloaded.Hooks.AsmHook(code, (nuint)address, opt); + } + + /// + /// Gets a value indicating whether or not the hook is enabled. + /// + public bool IsEnabled => this.asmHook.IsEnabled; + + /// + /// Starts intercepting a call to the function. + /// + public void Enable() + { + if (!this.activated) + { + this.activated = true; + this.asmHook.Activate(); + return; + } + + this.asmHook.Enable(); + } + + /// + /// Stops intercepting a call to the function. + /// + public void Disable() + { + this.asmHook.Disable(); + } + + /// + /// Remove a hook from the current process. + /// + public void Dispose() + { + this.asmHook.Disable(); + this.detour = null; + } +}