diff --git a/Dalamud/Game/AddonLifecycle/AddonEvent.cs b/Dalamud/Game/AddonLifecycle/AddonEvent.cs index bf5ee75cf..0125d1337 100644 --- a/Dalamud/Game/AddonLifecycle/AddonEvent.cs +++ b/Dalamud/Game/AddonLifecycle/AddonEvent.cs @@ -1,7 +1,7 @@ namespace Dalamud.Game.AddonLifecycle; /// -/// Enumeration for available AddonLifecycle events +/// Enumeration for available AddonLifecycle events. /// public enum AddonEvent { @@ -9,53 +9,32 @@ public enum AddonEvent /// Event that is fired before an addon begins it's setup process. /// PreSetup, - + /// /// Event that is fired after an addon has completed it's setup process. /// PostSetup, - - // // Events not implemented yet. - // /// - // /// Event that is fired right before an addon is set to shown. - // /// - // PreShow, - // - // /// - // /// Event that is fired after an addon has been shown. - // /// - // PostShow, - // - // /// - // /// Event that is fired right before an addon is set to hidden. - // /// - // PreHide, - // - // /// - // /// Event that is fired after an addon has been hidden. - // /// - // PostHide, - // - // /// - // /// Event that is fired before an addon begins update. - // /// - // PreUpdate, - // - // /// - // /// Event that is fired after an addon has completed update. - // /// - // PostUpdate, - // - // /// - // /// Event that is fired before an addon begins draw. - // /// - // PreDraw, - // - // /// - // /// Event that is fired after an addon has completed draw. - // /// - // PostDraw, - + + /// + /// Event that is fired before an addon begins update. + /// + PreUpdate, + + /// + /// Event that is fired after an addon has completed update. + /// + PostUpdate, + + /// + /// Event that is fired before an addon begins draw. + /// + PreDraw, + + /// + /// Event that is fired after an addon has completed draw. + /// + PostDraw, + /// /// Event that is fired before an addon is finalized. /// diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs index d915bbd00..3a8644c4c 100644 --- a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using Dalamud.Hooking; +using Dalamud.Hooking.Internal; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; @@ -27,6 +28,8 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType private readonly AddonLifecycleAddressResolver address; private readonly Hook onAddonSetupHook; private readonly Hook onAddonFinalizeHook; + private readonly CallHook onAddonDrawHook; + private readonly CallHook onAddonUpdateHook; private readonly ConcurrentBag newEventListeners = new(); private readonly ConcurrentBag removeEventListeners = new(); @@ -42,12 +45,18 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType this.onAddonSetupHook = Hook.FromAddress(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); } - + private delegate nint AddonSetupDelegate(AtkUnitBase* addon); private delegate void AddonFinalizeDelegate(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase); + private delegate void AddonDrawDelegate(AtkUnitBase* addon); + + private delegate void AddonUpdateDelegate(AtkUnitBase* addon); + /// public void Dispose() { @@ -55,6 +64,8 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType this.onAddonSetupHook.Dispose(); this.onAddonFinalizeHook.Dispose(); + this.onAddonDrawHook.Dispose(); + this.onAddonUpdateHook.Dispose(); } /// @@ -100,6 +111,8 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType { this.onAddonSetupHook.Enable(); this.onAddonFinalizeHook.Enable(); + this.onAddonDrawHook.Enable(); + this.onAddonUpdateHook.Enable(); } private void InvokeListeners(AddonEvent eventType, IAddonLifecycle.AddonArgs args) @@ -158,6 +171,56 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType this.onAddonFinalizeHook.Original(unitManager, atkUnitBase); } + + private void OnAddonDraw(AtkUnitBase* addon) + { + if (addon is null) return; + + try + { + this.InvokeListeners(AddonEvent.PreDraw, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + } + catch (Exception e) + { + Log.Error(e, "Exception in OnAddonDraw pre-draw invoke."); + } + + ((delegate* unmanaged)addon->AtkEventListener.vfunc[42])(addon); + + try + { + this.InvokeListeners(AddonEvent.PostDraw, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + } + catch (Exception e) + { + Log.Error(e, "Exception in OnAddonDraw post-draw invoke."); + } + } + + private void OnAddonUpdate(AtkUnitBase* addon) + { + if (addon is null) return; + + try + { + this.InvokeListeners(AddonEvent.PreUpdate, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + } + catch (Exception e) + { + Log.Error(e, "Exception in OnAddonUpdate pre-update invoke."); + } + + ((delegate* unmanaged)addon->AtkEventListener.vfunc[41])(addon); + + try + { + this.InvokeListeners(AddonEvent.PostUpdate, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + } + catch (Exception e) + { + Log.Error(e, "Exception in OnAddonUpdate post-update invoke."); + } + } } /// @@ -175,7 +238,7 @@ internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLif [ServiceManager.ServiceDependency] private readonly AddonLifecycle addonLifecycleService = Service.Get(); - + private readonly List eventListeners = new(); /// @@ -227,7 +290,8 @@ internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLif var targetListeners = this.eventListeners .Where(entry => entry.EventType == eventType) .Where(entry => entry.AddonName == addonName) - .Where(entry => handler is null || entry.FunctionDelegate == handler); + .Where(entry => handler is null || entry.FunctionDelegate == handler) + .ToArray(); // Make a copy so we don't mutate this list while removing entries. foreach (var listener in targetListeners) { @@ -245,7 +309,7 @@ internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLif /// public void UnregisterListener(IAddonLifecycle.AddonEventDelegate handler, params IAddonLifecycle.AddonEventDelegate[] handlers) { - foreach (var listener in this.eventListeners.Where(entry => entry.FunctionDelegate == handler)) + foreach (var listener in this.eventListeners.Where(entry => entry.FunctionDelegate == handler).ToArray()) { this.addonLifecycleService.UnregisterListener(listener); this.eventListeners.Remove(listener); @@ -253,7 +317,7 @@ internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLif foreach (var handlerParma in handlers) { - foreach (var listener in this.eventListeners.Where(entry => entry.FunctionDelegate == handlerParma)) + foreach (var listener in this.eventListeners.Where(entry => entry.FunctionDelegate == handlerParma).ToArray()) { this.addonLifecycleService.UnregisterListener(listener); this.eventListeners.Remove(listener); diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs index ba7b723ec..0300667a9 100644 --- a/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs @@ -6,14 +6,24 @@ internal class AddonLifecycleAddressResolver : BaseAddressResolver { /// - /// Gets the address of the addon setup hook invoked by the atkunitmanager. + /// Gets the address of the addon setup hook invoked by the AtkUnitManager. /// public nint AddonSetup { get; private set; } /// - /// Gets the address of the addon finalize hook invoked by the atkunitmanager. + /// Gets the address of the addon finalize hook invoked by the AtkUnitManager. /// public nint AddonFinalize { get; private set; } + + /// + /// Gets the address of the addon draw hook invoked by virtual function call. + /// + public nint AddonDraw { get; private set; } + + /// + /// Gets the address of the addon update hook invoked by virtual function call. + /// + public nint AddonUpdate { get; private set; } /// /// Scan for and setup any configured address pointers. @@ -23,5 +33,7 @@ internal class AddonLifecycleAddressResolver : BaseAddressResolver { this.AddonSetup = sig.ScanText("E8 ?? ?? ?? ?? 8B 83 ?? ?? ?? ?? C1 E8 14"); this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 7C 24 ?? 41 8B C6"); + this.AddonDraw = sig.ScanText("48 8B 01 FF 90 ?? ?? ?? ?? 83 EB 01 79 C1"); + this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF"); } }