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.");
+ }
+ }
}
///