From 4dabd0713171613d1fef4ece7015dd2879decc76 Mon Sep 17 00:00:00 2001 From: MidoriKami <9083275+MidoriKami@users.noreply.github.com> Date: Wed, 6 Sep 2023 13:08:04 -0700 Subject: [PATCH 01/12] Prototype, untested --- Dalamud/Game/AddonLifecycle/AddonEvent.cs | 63 +++++++ Dalamud/Game/AddonLifecycle/AddonLifecycle.cs | 177 ++++++++++++++---- .../AddonLifecycleEventListener.cs | 38 ++++ Dalamud/Hooking/Internal/CallHook.cs | 83 ++++++++ Dalamud/Plugin/Services/IAddonLifecycle.cs | 70 ++++++- 5 files changed, 389 insertions(+), 42 deletions(-) create mode 100644 Dalamud/Game/AddonLifecycle/AddonEvent.cs create mode 100644 Dalamud/Game/AddonLifecycle/AddonLifecycleEventListener.cs create mode 100644 Dalamud/Hooking/Internal/CallHook.cs diff --git a/Dalamud/Game/AddonLifecycle/AddonEvent.cs b/Dalamud/Game/AddonLifecycle/AddonEvent.cs new file mode 100644 index 000000000..bf5ee75cf --- /dev/null +++ b/Dalamud/Game/AddonLifecycle/AddonEvent.cs @@ -0,0 +1,63 @@ +namespace Dalamud.Game.AddonLifecycle; + +/// +/// Enumeration for available AddonLifecycle events +/// +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 is finalized. + /// + PreFinalize, +} diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs index 72d1c25ff..d915bbd00 100644 --- a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; using Dalamud.Hooking; using Dalamud.IoC; @@ -14,49 +17,99 @@ namespace Dalamud.Game.AddonLifecycle; /// [InterfaceVersion("1.0")] [ServiceManager.EarlyLoadedService] -internal unsafe class AddonLifecycle : IDisposable, IServiceType, IAddonLifecycle +internal unsafe class AddonLifecycle : IDisposable, IServiceType { private static readonly ModuleLog Log = new("AddonLifecycle"); + + [ServiceManager.ServiceDependency] + private readonly Framework framework = Service.Get(); + private readonly AddonLifecycleAddressResolver address; private readonly Hook onAddonSetupHook; private readonly Hook onAddonFinalizeHook; + + private readonly ConcurrentBag newEventListeners = new(); + private readonly ConcurrentBag removeEventListeners = new(); + private readonly List eventListeners = new(); [ServiceManager.ServiceConstructor] private AddonLifecycle(SigScanner sigScanner) { this.address = new AddonLifecycleAddressResolver(); this.address.Setup(sigScanner); + + this.framework.Update += this.OnFrameworkUpdate; this.onAddonSetupHook = Hook.FromAddress(this.address.AddonSetup, this.OnAddonSetup); this.onAddonFinalizeHook = Hook.FromAddress(this.address.AddonFinalize, this.OnAddonFinalize); } - + private delegate nint AddonSetupDelegate(AtkUnitBase* addon); private delegate void AddonFinalizeDelegate(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase); - - /// - public event Action? AddonPreSetup; - - /// - public event Action? AddonPostSetup; - - /// - public event Action? AddonPreFinalize; - + /// public void Dispose() { + this.framework.Update -= this.OnFrameworkUpdate; + this.onAddonSetupHook.Dispose(); this.onAddonFinalizeHook.Dispose(); } + /// + /// Register a listener for the target event and addon. + /// + /// The listener to register. + internal void RegisterListener(AddonLifecycleEventListener listener) + { + this.newEventListeners.Add(listener); + } + + /// + /// Unregisters the listener from events. + /// + /// The listener to unregister. + internal void UnregisterListener(AddonLifecycleEventListener listener) + { + this.removeEventListeners.Add(listener); + } + + // Used to prevent concurrency issues if plugins try to register during iteration of listeners. + private void OnFrameworkUpdate(Framework unused) + { + if (this.newEventListeners.Any()) + { + this.eventListeners.AddRange(this.newEventListeners); + this.newEventListeners.Clear(); + } + + if (this.removeEventListeners.Any()) + { + foreach (var toRemoveListener in this.removeEventListeners) + { + this.eventListeners.Remove(toRemoveListener); + } + + this.removeEventListeners.Clear(); + } + } + [ServiceManager.CallWhenServicesReady] private void ContinueConstruction() { this.onAddonSetupHook.Enable(); this.onAddonFinalizeHook.Enable(); } + + private void InvokeListeners(AddonEvent eventType, IAddonLifecycle.AddonArgs args) + { + // Match on string.empty for listeners that want events for all addons. + foreach (var listener in this.eventListeners.Where(listener => listener.EventType == eventType && (listener.AddonName == args.AddonName || listener.AddonName == string.Empty))) + { + listener.FunctionDelegate.Invoke(eventType, args); + } + } private nint OnAddonSetup(AtkUnitBase* addon) { @@ -65,7 +118,7 @@ private nint OnAddonSetup(AtkUnitBase* addon) try { - this.AddonPreSetup?.Invoke(new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + this.InvokeListeners(AddonEvent.PreSetup, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); } catch (Exception e) { @@ -76,7 +129,7 @@ private nint OnAddonSetup(AtkUnitBase* addon) try { - this.AddonPostSetup?.Invoke(new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + this.InvokeListeners(AddonEvent.PostSetup, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); } catch (Exception e) { @@ -96,7 +149,7 @@ private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitB try { - this.AddonPreFinalize?.Invoke(new IAddonLifecycle.AddonArgs { Addon = (nint)atkUnitBase[0] }); + this.InvokeListeners(AddonEvent.PreFinalize, new IAddonLifecycle.AddonArgs { Addon = (nint)atkUnitBase[0] }); } catch (Exception e) { @@ -118,39 +171,93 @@ private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitB #pragma warning restore SA1015 internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLifecycle { + private static readonly ModuleLog Log = new("AddonLifecycle:PluginScoped"); + [ServiceManager.ServiceDependency] private readonly AddonLifecycle addonLifecycleService = Service.Get(); + + private readonly List eventListeners = new(); + + /// + public void Dispose() + { + foreach (var listener in this.eventListeners) + { + this.addonLifecycleService.UnregisterListener(listener); + } + } - /// - /// Initializes a new instance of the class. - /// - public AddonLifecyclePluginScoped() + /// + public void RegisterListener(AddonEvent eventType, IEnumerable addonNames, IAddonLifecycle.AddonEventDelegate handler) { - this.addonLifecycleService.AddonPreSetup += this.AddonPreSetupForward; - this.addonLifecycleService.AddonPostSetup += this.AddonPostSetupForward; - this.addonLifecycleService.AddonPreFinalize += this.AddonPreFinalizeForward; + foreach (var addonName in addonNames) + { + this.RegisterListener(eventType, addonName, handler); + } } /// - public event Action? AddonPreSetup; - + public void RegisterListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate handler) + { + var listener = new AddonLifecycleEventListener(eventType, addonName, handler); + this.eventListeners.Add(listener); + this.addonLifecycleService.RegisterListener(listener); + } + /// - public event Action? AddonPostSetup; - + public void RegisterListener(AddonEvent eventType, IAddonLifecycle.AddonEventDelegate handler) + { + this.RegisterListener(eventType, string.Empty, handler); + } + /// - public event Action? AddonPreFinalize; + public void UnregisterListener(AddonEvent eventType, IEnumerable addonNames, IAddonLifecycle.AddonEventDelegate? handler = null) + { + foreach (var addonName in addonNames) + { + this.UnregisterListener(eventType, addonName, handler); + } + } /// - public void Dispose() + public void UnregisterListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate? handler = null) { - this.addonLifecycleService.AddonPreSetup -= this.AddonPreSetupForward; - this.addonLifecycleService.AddonPostSetup -= this.AddonPostSetupForward; - this.addonLifecycleService.AddonPreFinalize -= this.AddonPreFinalizeForward; - } + // This style is simpler to read imo. If the handler is null we want all entries, + // if they specified a handler then only the specific entries with that handler. + var targetListeners = this.eventListeners + .Where(entry => entry.EventType == eventType) + .Where(entry => entry.AddonName == addonName) + .Where(entry => handler is null || entry.FunctionDelegate == handler); - private void AddonPreSetupForward(IAddonLifecycle.AddonArgs args) => this.AddonPreSetup?.Invoke(args); + foreach (var listener in targetListeners) + { + this.addonLifecycleService.UnregisterListener(listener); + this.eventListeners.Remove(listener); + } + } - private void AddonPostSetupForward(IAddonLifecycle.AddonArgs args) => this.AddonPostSetup?.Invoke(args); + /// + public void UnregisterListener(AddonEvent eventType, IAddonLifecycle.AddonEventDelegate? handler = null) + { + this.UnregisterListener(eventType, string.Empty, handler); + } - private void AddonPreFinalizeForward(IAddonLifecycle.AddonArgs args) => this.AddonPreFinalize?.Invoke(args); + /// + public void UnregisterListener(IAddonLifecycle.AddonEventDelegate handler, params IAddonLifecycle.AddonEventDelegate[] handlers) + { + foreach (var listener in this.eventListeners.Where(entry => entry.FunctionDelegate == handler)) + { + this.addonLifecycleService.UnregisterListener(listener); + this.eventListeners.Remove(listener); + } + + foreach (var handlerParma in handlers) + { + foreach (var listener in this.eventListeners.Where(entry => entry.FunctionDelegate == handlerParma)) + { + this.addonLifecycleService.UnregisterListener(listener); + this.eventListeners.Remove(listener); + } + } + } } diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycleEventListener.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycleEventListener.cs new file mode 100644 index 000000000..0f088362d --- /dev/null +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycleEventListener.cs @@ -0,0 +1,38 @@ +using Dalamud.Plugin.Services; + +namespace Dalamud.Game.AddonLifecycle; + +/// +/// This class is a helper for tracking and invoking listener delegates. +/// +internal class AddonLifecycleEventListener +{ + /// + /// Initializes a new instance of the class. + /// + /// Event type to listen for. + /// Addon name to listen for. + /// Delegate to invoke. + internal AddonLifecycleEventListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate functionDelegate) + { + this.EventType = eventType; + this.AddonName = addonName; + this.FunctionDelegate = functionDelegate; + } + + /// + /// Gets the name of the addon this listener is looking for. + /// string.Empty if it wants to be called for any addon. + /// + public string AddonName { get; init; } + + /// + /// Gets the event type this listener is looking for. + /// + public AddonEvent EventType { get; init; } + + /// + /// Gets the delegate this listener invokes. + /// + public IAddonLifecycle.AddonEventDelegate FunctionDelegate { get; init; } +} diff --git a/Dalamud/Hooking/Internal/CallHook.cs b/Dalamud/Hooking/Internal/CallHook.cs new file mode 100644 index 000000000..0f8c681c2 --- /dev/null +++ b/Dalamud/Hooking/Internal/CallHook.cs @@ -0,0 +1,83 @@ +using System; +using System.Runtime.InteropServices; + +using Reloaded.Hooks.Definitions; + +namespace Dalamud.Hooking.Internal; + +/// +/// Hooking class for callsite hooking. This hook does not have capabilities of calling the original function. +/// The intended use is replacing virtual function calls where you are able to manually invoke the original call using the delegate arguments. +/// +/// Delegate signature for this hook. +internal class CallHook : 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 CallHook(nint address, T detour) + { + this.detour = detour; + + var detourPtr = Marshal.GetFunctionPointerForDelegate(this.detour); + var code = new[] + { + "use64", + $"mov rax, 0x{detourPtr:X8}", + "call rax", + }; + + 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; + } +} diff --git a/Dalamud/Plugin/Services/IAddonLifecycle.cs b/Dalamud/Plugin/Services/IAddonLifecycle.cs index 43b9fef0a..1e318ae79 100644 --- a/Dalamud/Plugin/Services/IAddonLifecycle.cs +++ b/Dalamud/Plugin/Services/IAddonLifecycle.cs @@ -1,5 +1,7 @@ -using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Dalamud.Game.AddonLifecycle; using Dalamud.Memory; using FFXIVClientStructs.FFXIV.Component.GUI; @@ -11,19 +13,73 @@ namespace Dalamud.Plugin.Services; public interface IAddonLifecycle { /// - /// Event that fires before an addon is being setup. + /// Delegate for receiving addon lifecycle event messages. /// - public event Action AddonPreSetup; + /// The event type that triggered the message. + /// Information about what addon triggered the message. + public delegate void AddonEventDelegate(AddonEvent eventType, AddonArgs addonInfo); /// - /// Event that fires after an addon is done being setup. + /// Register a listener that will trigger on the specified event and any of the specified addons. /// - public event Action AddonPostSetup; + /// Event type to trigger on. + /// Addon names that will trigger the handler to be invoked. + /// The handler to invoke. + void RegisterListener(AddonEvent eventType, IEnumerable addonNames, AddonEventDelegate handler); /// - /// Event that fires before an addon is being finalized. + /// Register a listener that will trigger on the specified event only for the specified addon. /// - public event Action AddonPreFinalize; + /// Event type to trigger on. + /// The addon name that will trigger the handler to be invoked. + /// The handler to invoke. + void RegisterListener(AddonEvent eventType, string addonName, AddonEventDelegate handler); + + /// + /// Register a listener that will trigger on the specified event for any addon. + /// + /// Event type to trigger on. + /// The handler to invoke. + void RegisterListener(AddonEvent eventType, AddonEventDelegate handler); + + /// + /// Unregister listener from specified event type and specified addon names. + /// + /// + /// If a specific handler is not provided, all handlers for the event type and addon names will be unregistered. + /// + /// Event type to deregister. + /// Addon names to deregister. + /// Optional specific handler to remove. + void UnregisterListener(AddonEvent eventType, IEnumerable addonNames, [Optional] AddonEventDelegate handler); + + /// + /// Unregister all listeners for the specified event type and addon name. + /// + /// + /// If a specific handler is not provided, all handlers for the event type and addons will be unregistered. + /// + /// Event type to deregister. + /// Addon name to deregister. + /// Optional specific handler to remove. + void UnregisterListener(AddonEvent eventType, string addonName, [Optional] AddonEventDelegate handler); + + /// + /// Unregister an event type handler.
This will only remove a handler that is added via . + ///
+ /// + /// If a specific handler is not provided, all handlers for the event type and addons will be unregistered. + /// + /// Event type to deregister. + /// Optional specific handler to remove. + void UnregisterListener(AddonEvent eventType, [Optional] AddonEventDelegate handler); + + /// + /// Unregister all events that use the specified handlers. + /// + /// Event handler to remove. + /// Additional handlers to remove. + void UnregisterListener(AddonEventDelegate handler, params AddonEventDelegate[] handlers); /// /// Addon argument data for use in event subscribers. From 9176342ad5b4978b1f3849758d8093ee3768cbd9 Mon Sep 17 00:00:00 2001 From: MidoriKami <9083275+MidoriKami@users.noreply.github.com> Date: Wed, 6 Sep 2023 16:00:37 -0700 Subject: [PATCH 02/12] Add AddonDraw, AddonUpdate --- Dalamud/Game/AddonLifecycle/AddonEvent.cs | 67 ++++++----------- Dalamud/Game/AddonLifecycle/AddonLifecycle.cs | 74 +++++++++++++++++-- .../AddonLifecycleAddressResolver.cs | 16 +++- 3 files changed, 106 insertions(+), 51 deletions(-) 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.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 @@ private AddonLifecycle(SigScanner sigScanner) 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 @@ public void Dispose() this.onAddonSetupHook.Dispose(); this.onAddonFinalizeHook.Dispose(); + this.onAddonDrawHook.Dispose(); + this.onAddonUpdateHook.Dispose(); } /// @@ -100,6 +111,8 @@ private void ContinueConstruction() { this.onAddonSetupHook.Enable(); this.onAddonFinalizeHook.Enable(); + this.onAddonDrawHook.Enable(); + this.onAddonUpdateHook.Enable(); } private void InvokeListeners(AddonEvent eventType, IAddonLifecycle.AddonArgs args) @@ -158,6 +171,56 @@ private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitB 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 @@ public void UnregisterListener(AddonEvent eventType, string addonName, 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 @@ public void UnregisterListener(AddonEvent eventType, IAddonLifecycle.AddonEventD /// 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 @@ public void UnregisterListener(IAddonLifecycle.AddonEventDelegate handler, param 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 @@ protected override void Setup64Bit(SigScanner sig) { 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"); } } From 8cb76a7438ee668a413f1cbb604f0c2c045379c0 Mon Sep 17 00:00:00 2001 From: MidoriKami <9083275+MidoriKami@users.noreply.github.com> Date: Wed, 6 Sep 2023 20:40:46 -0700 Subject: [PATCH 03/12] Fix incorrect AtkUnitBase.Update function delegate definition --- Dalamud/Game/AddonLifecycle/AddonLifecycle.cs | 14 +++++--------- .../AddonLifecycleAddressResolver.cs | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs index 3a8644c4c..a3e9f4be8 100644 --- a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs @@ -55,7 +55,7 @@ private AddonLifecycle(SigScanner sigScanner) private delegate void AddonDrawDelegate(AtkUnitBase* addon); - private delegate void AddonUpdateDelegate(AtkUnitBase* addon); + private delegate void AddonUpdateDelegate(AtkUnitBase* addon, float delta); /// public void Dispose() @@ -174,8 +174,6 @@ private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitB private void OnAddonDraw(AtkUnitBase* addon) { - if (addon is null) return; - try { this.InvokeListeners(AddonEvent.PreDraw, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); @@ -185,7 +183,7 @@ private void OnAddonDraw(AtkUnitBase* addon) Log.Error(e, "Exception in OnAddonDraw pre-draw invoke."); } - ((delegate* unmanaged)addon->AtkEventListener.vfunc[42])(addon); + addon->Draw(); try { @@ -197,10 +195,8 @@ private void OnAddonDraw(AtkUnitBase* addon) } } - private void OnAddonUpdate(AtkUnitBase* addon) + private void OnAddonUpdate(AtkUnitBase* addon, float delta) { - if (addon is null) return; - try { this.InvokeListeners(AddonEvent.PreUpdate, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); @@ -209,8 +205,8 @@ private void OnAddonUpdate(AtkUnitBase* addon) { Log.Error(e, "Exception in OnAddonUpdate pre-update invoke."); } - - ((delegate* unmanaged)addon->AtkEventListener.vfunc[41])(addon); + + addon->Update(delta); try { diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs index 0300667a9..688476d82 100644 --- a/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs @@ -33,7 +33,7 @@ protected override void Setup64Bit(SigScanner sig) { 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.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C1"); this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF"); } } From 6a0401646ff99ea845fb16e7f48a7e3e1c7314dd Mon Sep 17 00:00:00 2001 From: MidoriKami <9083275+MidoriKami@users.noreply.github.com> Date: Thu, 7 Sep 2023 11:47:35 -0700 Subject: [PATCH 04/12] Add AddonOnRequestedUpdate --- Dalamud/Game/AddonLifecycle/AddonEvent.cs | 10 ++++++ Dalamud/Game/AddonLifecycle/AddonLifecycle.cs | 31 +++++++++++++++++++ .../AddonLifecycleAddressResolver.cs | 6 ++++ 3 files changed, 47 insertions(+) diff --git a/Dalamud/Game/AddonLifecycle/AddonEvent.cs b/Dalamud/Game/AddonLifecycle/AddonEvent.cs index 0125d1337..30121d162 100644 --- a/Dalamud/Game/AddonLifecycle/AddonEvent.cs +++ b/Dalamud/Game/AddonLifecycle/AddonEvent.cs @@ -39,4 +39,14 @@ public enum AddonEvent /// Event that is fired before an addon is finalized. /// PreFinalize, + + /// + /// Event that is fired before an addon begins a requested update. + /// + PreRequestedUpdate, + + /// + /// Event that is fired after an addon finishes a requested update. + /// + PostRequestedUpdate, } diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs index a3e9f4be8..9bc792f46 100644 --- a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs @@ -30,6 +30,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType private readonly Hook onAddonFinalizeHook; private readonly CallHook onAddonDrawHook; private readonly CallHook onAddonUpdateHook; + // private readonly CallHook onAddonRequestedUpdateHook; // See Note in Ctor private readonly ConcurrentBag newEventListeners = new(); private readonly ConcurrentBag removeEventListeners = new(); @@ -47,6 +48,9 @@ private AddonLifecycle(SigScanner sigScanner) 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); + + // todo: reenable this. WARNING: This hook overwrites a system that SimpleTweaks uses, causing SimpleTweaks to report exceptions. + // this.onAddonRequestedUpdateHook = new CallHook(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate); } private delegate nint AddonSetupDelegate(AtkUnitBase* addon); @@ -57,6 +61,8 @@ private AddonLifecycle(SigScanner sigScanner) private delegate void AddonUpdateDelegate(AtkUnitBase* addon, float delta); + private delegate void AddonOnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData); + /// public void Dispose() { @@ -66,6 +72,7 @@ public void Dispose() this.onAddonFinalizeHook.Dispose(); this.onAddonDrawHook.Dispose(); this.onAddonUpdateHook.Dispose(); + // this.onAddonRequestedUpdateHook.Dispose(); // See Note in Ctor } /// @@ -113,6 +120,7 @@ private void ContinueConstruction() this.onAddonFinalizeHook.Enable(); this.onAddonDrawHook.Enable(); this.onAddonUpdateHook.Enable(); + // this.onAddonRequestedUpdateHook.Enable(); // See Note in Ctor } private void InvokeListeners(AddonEvent eventType, IAddonLifecycle.AddonArgs args) @@ -217,6 +225,29 @@ private void OnAddonUpdate(AtkUnitBase* addon, float delta) Log.Error(e, "Exception in OnAddonUpdate post-update invoke."); } } + + private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) + { + try + { + this.InvokeListeners(AddonEvent.PreRequestedUpdate, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + } + catch (Exception e) + { + Log.Error(e, "Exception in OnRequestedUpdate pre-requestedUpdate invoke."); + } + + addon->OnUpdate(numberArrayData, stringArrayData); + + try + { + this.InvokeListeners(AddonEvent.PostRequestedUpdate, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + } + catch (Exception e) + { + Log.Error(e, "Exception in OnRequestedUpdate post-requestedUpdate invoke."); + } + } } /// diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs index 688476d82..557cedc34 100644 --- a/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs @@ -24,6 +24,11 @@ internal class AddonLifecycleAddressResolver : BaseAddressResolver /// Gets the address of the addon update hook invoked by virtual function call. /// public nint AddonUpdate { get; private set; } + + /// + /// Gets the address of the addon onRequestedUpdate hook invoked by virtual function call. + /// + public nint AddonOnRequestedUpdate { get; private set; } /// /// Scan for and setup any configured address pointers. @@ -35,5 +40,6 @@ protected override void Setup64Bit(SigScanner sig) this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 7C 24 ?? 41 8B C6"); this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C1"); this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF"); + this.AddonOnRequestedUpdate = sig.ScanText("FF 90 90 01 00 00 48 8B 5C 24 30 48 83 C4 20"); } } From e54b09a3c5c132ac2989cbf682bf4a35b82d0064 Mon Sep 17 00:00:00 2001 From: MidoriKami <9083275+MidoriKami@users.noreply.github.com> Date: Thu, 7 Sep 2023 12:24:45 -0700 Subject: [PATCH 05/12] Add Addon.OnRefresh --- Dalamud/Game/AddonLifecycle/AddonEvent.cs | 10 ++++++ Dalamud/Game/AddonLifecycle/AddonLifecycle.cs | 31 ++++++++++++++++++- .../AddonLifecycleAddressResolver.cs | 6 ++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/Dalamud/Game/AddonLifecycle/AddonEvent.cs b/Dalamud/Game/AddonLifecycle/AddonEvent.cs index 30121d162..faef30c88 100644 --- a/Dalamud/Game/AddonLifecycle/AddonEvent.cs +++ b/Dalamud/Game/AddonLifecycle/AddonEvent.cs @@ -49,4 +49,14 @@ public enum AddonEvent /// Event that is fired after an addon finishes a requested update. /// PostRequestedUpdate, + + /// + /// Event that is fired before an addon begins a refresh. + /// + PreRefresh, + + /// + /// Event that is fired after an addon has finished a refresh. + /// + PostRefresh, } diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs index 9bc792f46..d6da18dd5 100644 --- a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs @@ -30,6 +30,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType private readonly Hook onAddonFinalizeHook; private readonly CallHook onAddonDrawHook; private readonly CallHook onAddonUpdateHook; + private readonly Hook onAddonRefreshHook; // private readonly CallHook onAddonRequestedUpdateHook; // See Note in Ctor private readonly ConcurrentBag newEventListeners = new(); @@ -48,6 +49,7 @@ private AddonLifecycle(SigScanner sigScanner) 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); + this.onAddonRefreshHook = Hook.FromAddress(this.address.AddonOnRefresh, this.OnAddonRefresh); // todo: reenable this. WARNING: This hook overwrites a system that SimpleTweaks uses, causing SimpleTweaks to report exceptions. // this.onAddonRequestedUpdateHook = new CallHook(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate); @@ -61,7 +63,9 @@ private AddonLifecycle(SigScanner sigScanner) private delegate void AddonUpdateDelegate(AtkUnitBase* addon, float delta); - private delegate void AddonOnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData); + private delegate void AddonOnRequestedUpdateDelegate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData); + + private delegate void AddonOnRefreshDelegate(AtkUnitManager* unitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values); /// public void Dispose() @@ -72,6 +76,7 @@ public void Dispose() this.onAddonFinalizeHook.Dispose(); this.onAddonDrawHook.Dispose(); this.onAddonUpdateHook.Dispose(); + this.onAddonRefreshHook.Dispose(); // this.onAddonRequestedUpdateHook.Dispose(); // See Note in Ctor } @@ -120,6 +125,7 @@ private void ContinueConstruction() this.onAddonFinalizeHook.Enable(); this.onAddonDrawHook.Enable(); this.onAddonUpdateHook.Enable(); + this.onAddonRefreshHook.Enable(); // this.onAddonRequestedUpdateHook.Enable(); // See Note in Ctor } @@ -226,6 +232,29 @@ private void OnAddonUpdate(AtkUnitBase* addon, float delta) } } + private void OnAddonRefresh(AtkUnitManager* atkUnitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values) + { + try + { + this.InvokeListeners(AddonEvent.PreRefresh, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + } + catch (Exception e) + { + Log.Error(e, "Exception in OnAddonRefresh pre-refresh invoke."); + } + + this.onAddonRefreshHook.Original(atkUnitManager, addon, valueCount, values); + + try + { + this.InvokeListeners(AddonEvent.PostRefresh, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + } + catch (Exception e) + { + Log.Error(e, "Exception in OnAddonRefresh post-refresh invoke."); + } + } + private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) { try diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs index 557cedc34..079e09c80 100644 --- a/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs @@ -29,6 +29,11 @@ internal class AddonLifecycleAddressResolver : BaseAddressResolver /// Gets the address of the addon onRequestedUpdate hook invoked by virtual function call. /// public nint AddonOnRequestedUpdate { get; private set; } + + /// + /// Gets the address of AtkUnitManager_vf10 which triggers addon onRefresh. + /// + public nint AddonOnRefresh { get; private set; } /// /// Scan for and setup any configured address pointers. @@ -41,5 +46,6 @@ protected override void Setup64Bit(SigScanner sig) this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C1"); this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF"); this.AddonOnRequestedUpdate = sig.ScanText("FF 90 90 01 00 00 48 8B 5C 24 30 48 83 C4 20"); + this.AddonOnRefresh = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 41 8B F8 48 8B DA"); } } From 371b8a9dccb6324114c763d83f11e70ea60fb1df Mon Sep 17 00:00:00 2001 From: MidoriKami <9083275+MidoriKami@users.noreply.github.com> Date: Fri, 8 Sep 2023 00:16:25 -0700 Subject: [PATCH 06/12] Restore AddonOnRequestedUpdate --- Dalamud/Game/AddonLifecycle/AddonLifecycle.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs index d6da18dd5..321c60a15 100644 --- a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs @@ -31,7 +31,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType private readonly CallHook onAddonDrawHook; private readonly CallHook onAddonUpdateHook; private readonly Hook onAddonRefreshHook; - // private readonly CallHook onAddonRequestedUpdateHook; // See Note in Ctor + private readonly CallHook onAddonRequestedUpdateHook; private readonly ConcurrentBag newEventListeners = new(); private readonly ConcurrentBag removeEventListeners = new(); @@ -50,9 +50,7 @@ private AddonLifecycle(SigScanner sigScanner) this.onAddonDrawHook = new CallHook(this.address.AddonDraw, this.OnAddonDraw); this.onAddonUpdateHook = new CallHook(this.address.AddonUpdate, this.OnAddonUpdate); this.onAddonRefreshHook = Hook.FromAddress(this.address.AddonOnRefresh, this.OnAddonRefresh); - - // todo: reenable this. WARNING: This hook overwrites a system that SimpleTweaks uses, causing SimpleTweaks to report exceptions. - // this.onAddonRequestedUpdateHook = new CallHook(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate); + this.onAddonRequestedUpdateHook = new CallHook(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate); } private delegate nint AddonSetupDelegate(AtkUnitBase* addon); @@ -77,7 +75,7 @@ public void Dispose() this.onAddonDrawHook.Dispose(); this.onAddonUpdateHook.Dispose(); this.onAddonRefreshHook.Dispose(); - // this.onAddonRequestedUpdateHook.Dispose(); // See Note in Ctor + this.onAddonRequestedUpdateHook.Dispose(); } /// @@ -126,7 +124,7 @@ private void ContinueConstruction() this.onAddonDrawHook.Enable(); this.onAddonUpdateHook.Enable(); this.onAddonRefreshHook.Enable(); - // this.onAddonRequestedUpdateHook.Enable(); // See Note in Ctor + this.onAddonRequestedUpdateHook.Enable(); } private void InvokeListeners(AddonEvent eventType, IAddonLifecycle.AddonArgs args) From e914f339906b8656211d7efd4da678e416e93d35 Mon Sep 17 00:00:00 2001 From: MidoriKami <9083275+MidoriKami@users.noreply.github.com> Date: Fri, 8 Sep 2023 01:20:29 -0700 Subject: [PATCH 07/12] Remove redundant null checking, and unused module log. There's no reasonable way the args passed in will be null. There's tons of null checking that square does before passing the pointers to these functions. If they are null, then the game has likely already crashed. --- Dalamud/Game/AddonLifecycle/AddonLifecycle.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs index 321c60a15..014033312 100644 --- a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs @@ -138,9 +138,6 @@ private void InvokeListeners(AddonEvent eventType, IAddonLifecycle.AddonArgs arg private nint OnAddonSetup(AtkUnitBase* addon) { - if (addon is null) - return this.onAddonSetupHook.Original(addon); - try { this.InvokeListeners(AddonEvent.PreSetup, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); @@ -166,12 +163,6 @@ private nint OnAddonSetup(AtkUnitBase* addon) private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase) { - if (atkUnitBase is null || atkUnitBase[0] is null) - { - this.onAddonFinalizeHook.Original(unitManager, atkUnitBase); - return; - } - try { this.InvokeListeners(AddonEvent.PreFinalize, new IAddonLifecycle.AddonArgs { Addon = (nint)atkUnitBase[0] }); @@ -288,8 +279,6 @@ private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArray #pragma warning restore SA1015 internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLifecycle { - private static readonly ModuleLog Log = new("AddonLifecycle:PluginScoped"); - [ServiceManager.ServiceDependency] private readonly AddonLifecycle addonLifecycleService = Service.Get(); From 967c79fdd318acba110772fea546e820b9c0fe2c Mon Sep 17 00:00:00 2001 From: MidoriKami <9083275+MidoriKami@users.noreply.github.com> Date: Fri, 8 Sep 2023 02:33:27 -0700 Subject: [PATCH 08/12] Improve logic for Unregistering Listeners --- Dalamud/Game/AddonLifecycle/AddonLifecycle.cs | 60 +++++++++---------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs index 014033312..c0094d11a 100644 --- a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs @@ -21,10 +21,10 @@ namespace Dalamud.Game.AddonLifecycle; internal unsafe class AddonLifecycle : IDisposable, IServiceType { private static readonly ModuleLog Log = new("AddonLifecycle"); - + [ServiceManager.ServiceDependency] private readonly Framework framework = Service.Get(); - + private readonly AddonLifecycleAddressResolver address; private readonly Hook onAddonSetupHook; private readonly Hook onAddonFinalizeHook; @@ -36,13 +36,13 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType private readonly ConcurrentBag newEventListeners = new(); private readonly ConcurrentBag removeEventListeners = new(); private readonly List eventListeners = new(); - + [ServiceManager.ServiceConstructor] private AddonLifecycle(SigScanner sigScanner) { this.address = new AddonLifecycleAddressResolver(); this.address.Setup(sigScanner); - + this.framework.Update += this.OnFrameworkUpdate; this.onAddonSetupHook = Hook.FromAddress(this.address.AddonSetup, this.OnAddonSetup); @@ -69,7 +69,7 @@ private AddonLifecycle(SigScanner sigScanner) public void Dispose() { this.framework.Update -= this.OnFrameworkUpdate; - + this.onAddonSetupHook.Dispose(); this.onAddonFinalizeHook.Dispose(); this.onAddonDrawHook.Dispose(); @@ -77,7 +77,7 @@ public void Dispose() this.onAddonRefreshHook.Dispose(); this.onAddonRequestedUpdateHook.Dispose(); } - + /// /// Register a listener for the target event and addon. /// @@ -95,7 +95,7 @@ internal void UnregisterListener(AddonLifecycleEventListener listener) { this.removeEventListeners.Add(listener); } - + // Used to prevent concurrency issues if plugins try to register during iteration of listeners. private void OnFrameworkUpdate(Framework unused) { @@ -111,11 +111,11 @@ private void OnFrameworkUpdate(Framework unused) { this.eventListeners.Remove(toRemoveListener); } - + this.removeEventListeners.Clear(); } } - + [ServiceManager.CallWhenServicesReady] private void ContinueConstruction() { @@ -126,7 +126,7 @@ private void ContinueConstruction() this.onAddonRefreshHook.Enable(); this.onAddonRequestedUpdateHook.Enable(); } - + private void InvokeListeners(AddonEvent eventType, IAddonLifecycle.AddonArgs args) { // Match on string.empty for listeners that want events for all addons. @@ -174,7 +174,7 @@ private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitB this.onAddonFinalizeHook.Original(unitManager, atkUnitBase); } - + private void OnAddonDraw(AtkUnitBase* addon) { try @@ -185,7 +185,7 @@ private void OnAddonDraw(AtkUnitBase* addon) { Log.Error(e, "Exception in OnAddonDraw pre-draw invoke."); } - + addon->Draw(); try @@ -197,7 +197,7 @@ private void OnAddonDraw(AtkUnitBase* addon) Log.Error(e, "Exception in OnAddonDraw post-draw invoke."); } } - + private void OnAddonUpdate(AtkUnitBase* addon, float delta) { try @@ -220,7 +220,7 @@ private void OnAddonUpdate(AtkUnitBase* addon, float delta) Log.Error(e, "Exception in OnAddonUpdate post-update invoke."); } } - + private void OnAddonRefresh(AtkUnitManager* atkUnitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values) { try @@ -243,7 +243,7 @@ private void OnAddonRefresh(AtkUnitManager* atkUnitManager, AtkUnitBase* addon, Log.Error(e, "Exception in OnAddonRefresh post-refresh invoke."); } } - + private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) { try @@ -283,7 +283,7 @@ internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLif private readonly AddonLifecycle addonLifecycleService = Service.Get(); private readonly List eventListeners = new(); - + /// public void Dispose() { @@ -301,7 +301,7 @@ public void RegisterListener(AddonEvent eventType, IEnumerable addonName this.RegisterListener(eventType, addonName, handler); } } - + /// public void RegisterListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate handler) { @@ -324,31 +324,27 @@ public void UnregisterListener(AddonEvent eventType, IEnumerable addonNa this.UnregisterListener(eventType, addonName, handler); } } - + /// public void UnregisterListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate? handler = null) { - // This style is simpler to read imo. If the handler is null we want all entries, - // if they specified a handler then only the specific entries with that handler. - var targetListeners = this.eventListeners - .Where(entry => entry.EventType == eventType) - .Where(entry => entry.AddonName == addonName) - .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) + this.eventListeners.RemoveAll(entry => { - this.addonLifecycleService.UnregisterListener(listener); - this.eventListeners.Remove(listener); - } + if (entry.EventType != eventType) return false; + if (entry.AddonName != addonName) return false; + if (handler is not null && entry.FunctionDelegate != handler) return false; + + this.addonLifecycleService.UnregisterListener(entry); + return true; + }); } - + /// public void UnregisterListener(AddonEvent eventType, IAddonLifecycle.AddonEventDelegate? handler = null) { this.UnregisterListener(eventType, string.Empty, handler); } - + /// public void UnregisterListener(IAddonLifecycle.AddonEventDelegate handler, params IAddonLifecycle.AddonEventDelegate[] handlers) { From 1b4bee3d1302a4a779dd6efa396f0ad1cf8008ae Mon Sep 17 00:00:00 2001 From: MidoriKami <9083275+MidoriKami@users.noreply.github.com> Date: Fri, 8 Sep 2023 21:07:03 -0700 Subject: [PATCH 09/12] Remove array copy of handlers. --- Dalamud/Game/AddonLifecycle/AddonLifecycle.cs | 20 ++++++++----------- Dalamud/Plugin/Services/IAddonLifecycle.cs | 5 ++--- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs index c0094d11a..c0e817f0d 100644 --- a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs @@ -346,21 +346,17 @@ public void UnregisterListener(AddonEvent eventType, IAddonLifecycle.AddonEventD } /// - public void UnregisterListener(IAddonLifecycle.AddonEventDelegate handler, params IAddonLifecycle.AddonEventDelegate[] handlers) + public void UnregisterListener(params IAddonLifecycle.AddonEventDelegate[] handlers) { - foreach (var listener in this.eventListeners.Where(entry => entry.FunctionDelegate == handler).ToArray()) + foreach (var handler in handlers) { - this.addonLifecycleService.UnregisterListener(listener); - this.eventListeners.Remove(listener); - } - - foreach (var handlerParma in handlers) - { - foreach (var listener in this.eventListeners.Where(entry => entry.FunctionDelegate == handlerParma).ToArray()) + this.eventListeners.RemoveAll(entry => { - this.addonLifecycleService.UnregisterListener(listener); - this.eventListeners.Remove(listener); - } + if (entry.FunctionDelegate != handler) return false; + + this.addonLifecycleService.UnregisterListener(entry); + return true; + }); } } } diff --git a/Dalamud/Plugin/Services/IAddonLifecycle.cs b/Dalamud/Plugin/Services/IAddonLifecycle.cs index 1e318ae79..cbb3d7c24 100644 --- a/Dalamud/Plugin/Services/IAddonLifecycle.cs +++ b/Dalamud/Plugin/Services/IAddonLifecycle.cs @@ -77,9 +77,8 @@ public interface IAddonLifecycle /// /// Unregister all events that use the specified handlers. /// - /// Event handler to remove. - /// Additional handlers to remove. - void UnregisterListener(AddonEventDelegate handler, params AddonEventDelegate[] handlers); + /// Handlers to remove. + void UnregisterListener(params AddonEventDelegate[] handlers); /// /// Addon argument data for use in event subscribers. From c9a5c7c4c53d3af8280b5ff809f18c1ba3f3e82c Mon Sep 17 00:00:00 2001 From: MidoriKami <9083275+MidoriKami@users.noreply.github.com> Date: Sat, 9 Sep 2023 20:21:26 -0700 Subject: [PATCH 10/12] Add AddonLifecycle to Self-Test --- Dalamud/Hooking/Internal/CallHook.cs | 10 +- .../AgingSteps/AddonLifecycleAgingStep.cs | 133 ++++++++++++++++++ .../Windows/SelfTest/SelfTestWindow.cs | 1 + 3 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs diff --git a/Dalamud/Hooking/Internal/CallHook.cs b/Dalamud/Hooking/Internal/CallHook.cs index 0f8c681c2..2bef59c86 100644 --- a/Dalamud/Hooking/Internal/CallHook.cs +++ b/Dalamud/Hooking/Internal/CallHook.cs @@ -6,8 +6,14 @@ namespace Dalamud.Hooking.Internal; /// -/// Hooking class for callsite hooking. This hook does not have capabilities of calling the original function. -/// The intended use is replacing virtual function calls where you are able to manually invoke the original call using the delegate arguments. +/// This class represents a callsite hook. Only the specific address's instructions are replaced with this hook. +/// This is a destructive operation, no other callsite hooks can coexist at the same address. +/// +/// There's no .Original for this hook type. +/// This is only intended for be for functions where the parameters provided allow you to invoke the original call. +/// +/// This class was specifically added for hooking virtual function callsites. +/// Only the specific callsite hooked is modified, if the game calls the virtual function from other locations this hook will not be triggered. /// /// Delegate signature for this hook. internal class CallHook : IDisposable where T : Delegate diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs new file mode 100644 index 000000000..9dcaec558 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs @@ -0,0 +1,133 @@ +using System.Collections.Generic; + +using Dalamud.Game.AddonLifecycle; +using Dalamud.Plugin.Services; +using ImGuiNET; + +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup AddonLifecycle Service. +/// +internal class AddonLifecycleAgingStep : IAgingStep +{ + private readonly List listeners; + + private AddonLifecycle? service; + private TestStep currentStep = TestStep.CharacterRefresh; + private bool listenersRegistered; + + /// + /// Initializes a new instance of the class. + /// + public AddonLifecycleAgingStep() + { + this.listeners = new List + { + new(AddonEvent.PostSetup, "Character", this.PostSetup), + new(AddonEvent.PostUpdate, "Character", this.PostUpdate), + new(AddonEvent.PostDraw, "Character", this.PostDraw), + new(AddonEvent.PostRefresh, "Character", this.PostRefresh), + new(AddonEvent.PostRequestedUpdate, "Character", this.PostRequestedUpdate), + new(AddonEvent.PreFinalize, "Character", this.PreFinalize), + }; + } + + private enum TestStep + { + CharacterRefresh, + CharacterSetup, + CharacterRequestedUpdate, + CharacterUpdate, + CharacterDraw, + CharacterFinalize, + Complete, + } + + /// + public string Name => "Test AddonLifecycle"; + + /// + public SelfTestStepResult RunStep() + { + this.service ??= Service.Get(); + if (this.service is null) return SelfTestStepResult.Fail; + + if (!this.listenersRegistered) + { + foreach (var listener in this.listeners) + { + this.service.RegisterListener(listener); + } + + this.listenersRegistered = true; + } + + switch (this.currentStep) + { + case TestStep.CharacterRefresh: + ImGui.Text("Open Character Window."); + break; + + case TestStep.CharacterSetup: + ImGui.Text("Open Character Window."); + break; + + case TestStep.CharacterRequestedUpdate: + ImGui.Text("Change tabs, or un-equip/equip gear."); + break; + + case TestStep.CharacterFinalize: + ImGui.Text("Close Character Window."); + break; + + case TestStep.CharacterUpdate: + case TestStep.CharacterDraw: + case TestStep.Complete: + default: + // Nothing to report to tester. + break; + } + + return this.currentStep is TestStep.Complete ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + foreach (var listener in this.listeners) + { + this.service?.UnregisterListener(listener); + } + } + + private void PostSetup(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo) + { + if (this.currentStep is TestStep.CharacterSetup) this.currentStep++; + } + + private void PostUpdate(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo) + { + if (this.currentStep is TestStep.CharacterUpdate) this.currentStep++; + } + + private void PostDraw(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo) + { + if (this.currentStep is TestStep.CharacterDraw) this.currentStep++; + } + + private void PostRefresh(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo) + { + if (this.currentStep is TestStep.CharacterRefresh) this.currentStep++; + } + + private void PostRequestedUpdate(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo) + { + if (this.currentStep is TestStep.CharacterRequestedUpdate) this.currentStep++; + } + + private void PreFinalize(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo) + { + if (this.currentStep is TestStep.CharacterFinalize) this.currentStep++; + } +} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs index 3e25b6f5a..68d197208 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs @@ -39,6 +39,7 @@ internal class SelfTestWindow : Window new ChatAgingStep(), new HoverAgingStep(), new LuminaAgingStep(), + new AddonLifecycleAgingStep(), new PartyFinderAgingStep(), new HandledExceptionAgingStep(), new DutyStateAgingStep(), From ca58a1bf4fa4852b6ea77a1f09088e57439c9842 Mon Sep 17 00:00:00 2001 From: MidoriKami <9083275+MidoriKami@users.noreply.github.com> Date: Sun, 10 Sep 2023 19:26:28 -0700 Subject: [PATCH 11/12] Move AddonArgs to it's own file --- Dalamud/Game/AddonLifecycle/AddonArgs.cs | 22 +++++++++++++++++ Dalamud/Game/AddonLifecycle/AddonLifecycle.cs | 24 +++++++++---------- Dalamud/Plugin/Services/IAddonLifecycle.cs | 20 ---------------- 3 files changed, 34 insertions(+), 32 deletions(-) create mode 100644 Dalamud/Game/AddonLifecycle/AddonArgs.cs diff --git a/Dalamud/Game/AddonLifecycle/AddonArgs.cs b/Dalamud/Game/AddonLifecycle/AddonArgs.cs new file mode 100644 index 000000000..50c995abb --- /dev/null +++ b/Dalamud/Game/AddonLifecycle/AddonArgs.cs @@ -0,0 +1,22 @@ +using Dalamud.Memory; +using FFXIVClientStructs.FFXIV.Component.GUI; + +namespace Dalamud.Game.AddonLifecycle; + +/// +/// Addon argument data for use in event subscribers. +/// +public unsafe class AddonArgs +{ + private string? addonName; + + /// + /// Gets the name of the addon this args referrers to. + /// + public string AddonName => this.Addon == nint.Zero ? "NullAddon" : this.addonName ??= MemoryHelper.ReadString((nint)((AtkUnitBase*)this.Addon)->Name, 0x20); + + /// + /// Gets the pointer to the addons AtkUnitBase. + /// + required public nint Addon { get; init; } +} diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs index c0e817f0d..5fc1c7d2b 100644 --- a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs @@ -127,7 +127,7 @@ private void ContinueConstruction() this.onAddonRequestedUpdateHook.Enable(); } - private void InvokeListeners(AddonEvent eventType, IAddonLifecycle.AddonArgs args) + private void InvokeListeners(AddonEvent eventType, AddonArgs args) { // Match on string.empty for listeners that want events for all addons. foreach (var listener in this.eventListeners.Where(listener => listener.EventType == eventType && (listener.AddonName == args.AddonName || listener.AddonName == string.Empty))) @@ -140,7 +140,7 @@ private nint OnAddonSetup(AtkUnitBase* addon) { try { - this.InvokeListeners(AddonEvent.PreSetup, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + this.InvokeListeners(AddonEvent.PreSetup, new AddonArgs { Addon = (nint)addon }); } catch (Exception e) { @@ -151,7 +151,7 @@ private nint OnAddonSetup(AtkUnitBase* addon) try { - this.InvokeListeners(AddonEvent.PostSetup, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + this.InvokeListeners(AddonEvent.PostSetup, new AddonArgs { Addon = (nint)addon }); } catch (Exception e) { @@ -165,7 +165,7 @@ private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitB { try { - this.InvokeListeners(AddonEvent.PreFinalize, new IAddonLifecycle.AddonArgs { Addon = (nint)atkUnitBase[0] }); + this.InvokeListeners(AddonEvent.PreFinalize, new AddonArgs { Addon = (nint)atkUnitBase[0] }); } catch (Exception e) { @@ -179,7 +179,7 @@ private void OnAddonDraw(AtkUnitBase* addon) { try { - this.InvokeListeners(AddonEvent.PreDraw, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + this.InvokeListeners(AddonEvent.PreDraw, new AddonArgs { Addon = (nint)addon }); } catch (Exception e) { @@ -190,7 +190,7 @@ private void OnAddonDraw(AtkUnitBase* addon) try { - this.InvokeListeners(AddonEvent.PostDraw, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + this.InvokeListeners(AddonEvent.PostDraw, new AddonArgs { Addon = (nint)addon }); } catch (Exception e) { @@ -202,7 +202,7 @@ private void OnAddonUpdate(AtkUnitBase* addon, float delta) { try { - this.InvokeListeners(AddonEvent.PreUpdate, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + this.InvokeListeners(AddonEvent.PreUpdate, new AddonArgs { Addon = (nint)addon }); } catch (Exception e) { @@ -213,7 +213,7 @@ private void OnAddonUpdate(AtkUnitBase* addon, float delta) try { - this.InvokeListeners(AddonEvent.PostUpdate, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + this.InvokeListeners(AddonEvent.PostUpdate, new AddonArgs { Addon = (nint)addon }); } catch (Exception e) { @@ -225,7 +225,7 @@ private void OnAddonRefresh(AtkUnitManager* atkUnitManager, AtkUnitBase* addon, { try { - this.InvokeListeners(AddonEvent.PreRefresh, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + this.InvokeListeners(AddonEvent.PreRefresh, new AddonArgs { Addon = (nint)addon }); } catch (Exception e) { @@ -236,7 +236,7 @@ private void OnAddonRefresh(AtkUnitManager* atkUnitManager, AtkUnitBase* addon, try { - this.InvokeListeners(AddonEvent.PostRefresh, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + this.InvokeListeners(AddonEvent.PostRefresh, new AddonArgs { Addon = (nint)addon }); } catch (Exception e) { @@ -248,7 +248,7 @@ private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArray { try { - this.InvokeListeners(AddonEvent.PreRequestedUpdate, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + this.InvokeListeners(AddonEvent.PreRequestedUpdate, new AddonArgs { Addon = (nint)addon }); } catch (Exception e) { @@ -259,7 +259,7 @@ private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArray try { - this.InvokeListeners(AddonEvent.PostRequestedUpdate, new IAddonLifecycle.AddonArgs { Addon = (nint)addon }); + this.InvokeListeners(AddonEvent.PostRequestedUpdate, new AddonArgs { Addon = (nint)addon }); } catch (Exception e) { diff --git a/Dalamud/Plugin/Services/IAddonLifecycle.cs b/Dalamud/Plugin/Services/IAddonLifecycle.cs index cbb3d7c24..1dc792660 100644 --- a/Dalamud/Plugin/Services/IAddonLifecycle.cs +++ b/Dalamud/Plugin/Services/IAddonLifecycle.cs @@ -2,8 +2,6 @@ using System.Runtime.InteropServices; using Dalamud.Game.AddonLifecycle; -using Dalamud.Memory; -using FFXIVClientStructs.FFXIV.Component.GUI; namespace Dalamud.Plugin.Services; @@ -79,22 +77,4 @@ public interface IAddonLifecycle /// /// Handlers to remove. void UnregisterListener(params AddonEventDelegate[] handlers); - - /// - /// Addon argument data for use in event subscribers. - /// - public unsafe class AddonArgs - { - private string? addonName; - - /// - /// Gets the name of the addon this args referrers to. - /// - public string AddonName => this.Addon == nint.Zero ? "NullAddon" : this.addonName ??= MemoryHelper.ReadString((nint)((AtkUnitBase*)this.Addon)->Name, 0x20); - - /// - /// Gets the pointer to the addons AtkUnitBase. - /// - required public nint Addon { get; init; } - } } From 9c0ca2769c774c02a0d510ba36d953e609d93888 Mon Sep 17 00:00:00 2001 From: MidoriKami <9083275+MidoriKami@users.noreply.github.com> Date: Sun, 10 Sep 2023 20:01:49 -0700 Subject: [PATCH 12/12] Fix Aging Steps --- .../SelfTest/AgingSteps/AddonLifecycleAgingStep.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs index 9dcaec558..3a1cb0e77 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using Dalamud.Game.AddonLifecycle; -using Dalamud.Plugin.Services; using ImGuiNET; namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; @@ -101,32 +100,32 @@ public void CleanUp() } } - private void PostSetup(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo) + private void PostSetup(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterSetup) this.currentStep++; } - private void PostUpdate(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo) + private void PostUpdate(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterUpdate) this.currentStep++; } - private void PostDraw(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo) + private void PostDraw(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterDraw) this.currentStep++; } - private void PostRefresh(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo) + private void PostRefresh(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterRefresh) this.currentStep++; } - private void PostRequestedUpdate(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo) + private void PostRequestedUpdate(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterRequestedUpdate) this.currentStep++; } - private void PreFinalize(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo) + private void PreFinalize(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterFinalize) this.currentStep++; }