From d15e35f3f68382b4a44967e7b2af7243c61d2e79 Mon Sep 17 00:00:00 2001 From: MidoriKami <9083275+MidoriKami@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:10:49 -0700 Subject: [PATCH] AddonLifecycle Add AddonReceiveEvent (#1473) --- .../Lifecycle/AddonArgTypes/AddonDrawArgs.cs | 2 +- .../AddonArgTypes/AddonFinalizeArgs.cs | 2 +- .../AddonArgTypes/AddonReceiveEventArgs.cs | 30 ++++++ .../AddonArgTypes/AddonRefreshArgs.cs | 2 +- .../AddonArgTypes/AddonRequestedUpdateArgs.cs | 2 +- .../AddonArgTypes/AddonUpdateArgs.cs | 2 +- Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs | 5 + Dalamud/Game/Addon/Lifecycle/AddonEvent.cs | 10 ++ .../Game/Addon/Lifecycle/AddonLifecycle.cs | 96 ++++++++++++++++++- 9 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonDrawArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonDrawArgs.cs index 6bb72f567..10d46a573 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonDrawArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonDrawArgs.cs @@ -1,7 +1,7 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; /// -/// Addon argument data for Finalize events. +/// Addon argument data for Draw events. /// public class AddonDrawArgs : AddonArgs { diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonFinalizeArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonFinalizeArgs.cs index 782943955..caf422927 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonFinalizeArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonFinalizeArgs.cs @@ -1,7 +1,7 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; /// -/// Addon argument data for Finalize events. +/// Addon argument data for ReceiveEvent events. /// public class AddonFinalizeArgs : AddonArgs { diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs new file mode 100644 index 000000000..df75307f1 --- /dev/null +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs @@ -0,0 +1,30 @@ +namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; + +/// +/// Addon argument data for ReceiveEvent events. +/// +public class AddonReceiveEventArgs : AddonArgs +{ + /// + public override AddonArgsType Type => AddonArgsType.ReceiveEvent; + + /// + /// Gets the AtkEventType for this event message. + /// + public byte AtkEventType { get; init; } + + /// + /// Gets the event id for this event message. + /// + public int EventParam { get; init; } + + /// + /// Gets the pointer to an AtkEvent for this event message. + /// + public nint AtkEvent { get; init; } + + /// + /// Gets the pointer to a block of data for this event message. + /// + public nint Data { get; init; } +} diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs index a50dc68f6..b6ac6d8b6 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs @@ -3,7 +3,7 @@ using FFXIVClientStructs.FFXIV.Component.GUI; namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; /// -/// Addon argument data for Finalize events. +/// Addon argument data for Refresh events. /// public class AddonRefreshArgs : AddonArgs { diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs index e73d11e23..1b743b31a 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs @@ -1,7 +1,7 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; /// -/// Addon argument data for Finalize events. +/// Addon argument data for OnRequestedUpdate events. /// public class AddonRequestedUpdateArgs : AddonArgs { diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs index 6870746db..651fbcafb 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs @@ -1,7 +1,7 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; /// -/// Addon argument data for Finalize events. +/// Addon argument data for Update events. /// public class AddonUpdateArgs : AddonArgs { diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs index 11f73a4de..b58b5f4c7 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs @@ -34,4 +34,9 @@ public enum AddonArgsType /// Contains argument data for Refresh. /// Refresh, + + /// + /// Contains argument data for ReceiveEvent. + /// + ReceiveEvent, } diff --git a/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs b/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs index 75a77482d..7cbc93eb2 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs @@ -59,4 +59,14 @@ public enum AddonEvent /// Event that is fired after an addon has finished a refresh. /// PostRefresh, + + /// + /// Event that is fired before an addon begins processing an event. + /// + PreReceiveEvent, + + /// + /// Event that is fired after an addon has processed an event. + /// + PostReceiveEvent, } diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs index b188095d0..a12290c10 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs @@ -8,6 +8,7 @@ using Dalamud.Hooking.Internal; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; +using Dalamud.Memory; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Component.GUI; @@ -38,6 +39,8 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType private readonly ConcurrentBag removeEventListeners = new(); private readonly List eventListeners = new(); + private readonly Dictionary> receiveEventHooks = new(); + [ServiceManager.ServiceConstructor] private AddonLifecycle(TargetSigScanner sigScanner) { @@ -67,6 +70,8 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType private delegate byte AddonOnRefreshDelegate(AtkUnitManager* unitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values); + private delegate void AddonReceiveEventDelegate(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, nint a5); + /// public void Dispose() { @@ -79,6 +84,11 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType this.onAddonUpdateHook.Dispose(); this.onAddonRefreshHook.Dispose(); this.onAddonRequestedUpdateHook.Dispose(); + + foreach (var (_, hook) in this.receiveEventHooks) + { + hook.Dispose(); + } } /// @@ -104,7 +114,21 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType { if (this.newEventListeners.Any()) { - this.eventListeners.AddRange(this.newEventListeners); + foreach (var toAddListener in this.newEventListeners) + { + this.eventListeners.Add(toAddListener); + + // If we want receive event messages have an already active addon, enable the receive event hook. + // If the addon isn't active yet, we'll grab the hook when it sets up. + if (toAddListener is { EventType: AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent }) + { + if (this.receiveEventHooks.TryGetValue(toAddListener.AddonName, out var hook)) + { + hook.Enable(); + } + } + } + this.newEventListeners.Clear(); } @@ -142,6 +166,23 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType private void OnAddonSetup(AtkUnitBase* addon, uint valueCount, AtkValue* values) { + try + { + // Hook the addon's ReceiveEvent function here, but only enable the hook if we have an active listener. + var addonName = MemoryHelper.ReadStringNullTerminated((nint)addon->Name); + var receiveEventHook = Hook.FromAddress((nint)addon->VTable->ReceiveEvent, this.OnReceiveEvent); + this.receiveEventHooks.TryAdd(addonName, receiveEventHook); + + if (this.eventListeners.Any(listener => listener.EventType is AddonEvent.PostReceiveEvent or AddonEvent.PreReceiveEvent)) + { + receiveEventHook.Enable(); + } + } + catch (Exception e) + { + Log.Error(e, "Exception in OnAddonSetup ReceiveEvent Registration."); + } + try { this.InvokeListeners(AddonEvent.PreSetup, new AddonSetupArgs @@ -175,6 +216,21 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase) { + try + { + // Remove this addons ReceiveEvent Registration + var addonName = MemoryHelper.ReadStringNullTerminated((nint)atkUnitBase[0]->Name); + if (this.receiveEventHooks.TryGetValue(addonName, out var hook)) + { + hook.Dispose(); + this.receiveEventHooks.Remove(addonName); + } + } + catch (Exception e) + { + Log.Error(e, "Exception in OnAddonFinalize ReceiveEvent Removal."); + } + try { this.InvokeListeners(AddonEvent.PreFinalize, new AddonFinalizeArgs { Addon = (nint)atkUnitBase[0] }); @@ -300,6 +356,44 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType Log.Error(e, "Exception in OnRequestedUpdate post-requestedUpdate invoke."); } } + + private void OnReceiveEvent(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, nint data) + { + try + { + this.InvokeListeners(AddonEvent.PreReceiveEvent, new AddonReceiveEventArgs + { + Addon = (nint)addon, + AtkEventType = (byte)eventType, + EventParam = eventParam, + AtkEvent = (nint)atkEvent, + Data = data, + }); + } + catch (Exception e) + { + Log.Error(e, "Exception in OnReceiveEvent pre-receiveEvent invoke."); + } + + var addonName = MemoryHelper.ReadStringNullTerminated((nint)addon->Name); + this.receiveEventHooks[addonName].Original(addon, eventType, eventParam, atkEvent, data); + + try + { + this.InvokeListeners(AddonEvent.PostReceiveEvent, new AddonReceiveEventArgs + { + Addon = (nint)addon, + AtkEventType = (byte)eventType, + EventParam = eventParam, + AtkEvent = (nint)atkEvent, + Data = data, + }); + } + catch (Exception e) + { + Log.Error(e, "Exception in OnAddonRefresh post-receiveEvent invoke."); + } + } } ///