diff --git a/Dalamud/Game/AddonEventManager/AddonEventListener.cs b/Dalamud/Game/AddonEventManager/AddonEventListener.cs
new file mode 100644
index 000000000..cb0aa1502
--- /dev/null
+++ b/Dalamud/Game/AddonEventManager/AddonEventListener.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Runtime.InteropServices;
+
+using FFXIVClientStructs.FFXIV.Component.GUI;
+
+namespace Dalamud.Game.AddonEventManager;
+
+///
+/// Event listener class for managing custom events.
+///
+// Custom event handler tech provided by Pohky, implemented by MidoriKami
+internal unsafe class AddonEventListener : IDisposable
+{
+ private ReceiveEventDelegate? receiveEventDelegate;
+
+ private AtkEventListener* eventListener;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The managed handler to send events to.
+ public AddonEventListener(ReceiveEventDelegate eventHandler)
+ {
+ this.receiveEventDelegate = eventHandler;
+
+ this.eventListener = (AtkEventListener*)Marshal.AllocHGlobal(sizeof(AtkEventListener));
+ this.eventListener->vtbl = (void*)Marshal.AllocHGlobal(sizeof(void*) * 3);
+ this.eventListener->vfunc[0] = (delegate* unmanaged)&NullSub;
+ this.eventListener->vfunc[1] = (delegate* unmanaged)&NullSub;
+ this.eventListener->vfunc[2] = (void*)Marshal.GetFunctionPointerForDelegate(this.receiveEventDelegate);
+ }
+
+ ///
+ /// Delegate for receiving custom events.
+ ///
+ /// Pointer to the event listener.
+ /// Event type.
+ /// Unique Id for this event.
+ /// Event Data.
+ /// Unknown Parameter.
+ public delegate void ReceiveEventDelegate(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventData, nint unknown);
+
+ ///
+ public void Dispose()
+ {
+ if (this.eventListener is null) return;
+
+ Marshal.FreeHGlobal((nint)this.eventListener->vtbl);
+ Marshal.FreeHGlobal((nint)this.eventListener);
+
+ this.eventListener = null;
+ this.receiveEventDelegate = null;
+ }
+
+ ///
+ /// Register an event to this event handler.
+ ///
+ /// Addon that triggers this event.
+ /// Node to attach event to.
+ /// Event type to trigger this event.
+ /// Unique id for this event.
+ public void RegisterEvent(AtkUnitBase* addon, AtkResNode* node, AtkEventType eventType, uint param)
+ {
+ if (node is null) return;
+
+ node->AddEvent(eventType, param, this.eventListener, (AtkResNode*)addon, false);
+ }
+
+ ///
+ /// Unregister an event from this event handler.
+ ///
+ /// Node to remove the event from.
+ /// Event type that this event is for.
+ /// Unique id for this event.
+ public void UnregisterEvent(AtkResNode* node, AtkEventType eventType, uint param)
+ {
+ if (node is null) return;
+
+ node->RemoveEvent(eventType, param, this.eventListener, false);
+ }
+
+ [UnmanagedCallersOnly]
+ private static void NullSub()
+ {
+ /* do nothing */
+ }
+}
diff --git a/Dalamud/Game/AddonEventManager/AddonEventManager.cs b/Dalamud/Game/AddonEventManager/AddonEventManager.cs
index ede59a9a9..c6e61fe5a 100644
--- a/Dalamud/Game/AddonEventManager/AddonEventManager.cs
+++ b/Dalamud/Game/AddonEventManager/AddonEventManager.cs
@@ -19,23 +19,22 @@ namespace Dalamud.Game.AddonEventManager;
internal unsafe class AddonEventManager : IDisposable, IServiceType
{
// The starting value for param key ranges.
- // ascii `DD` 0x4444 chosen for the key start, this just has to be larger than anything vanilla makes.
- private const uint ParamKeyStart = 0x44440000;
+ private const uint ParamKeyStart = 0x0000_0000;
// The range each plugin is allowed to use.
- // 65,536 per plugin should be reasonable.
- private const uint ParamKeyPluginRange = 0x10000;
+ // 1,048,576 per plugin should be reasonable.
+ private const uint ParamKeyPluginRange = 0x10_0000;
// The maximum range allowed to be given to a plugin.
- // 20,560 maximum plugins should be reasonable.
- // 202,113,024 maximum event handlers should be reasonable.
- private const uint ParamKeyMax = 0x50500000;
+ // 1,048,576 maximum plugins should be reasonable.
+ private const uint ParamKeyMax = 0xFFF0_0000;
private static readonly ModuleLog Log = new("AddonEventManager");
+
private readonly AddonEventManagerAddressResolver address;
- private readonly Hook onGlobalEventHook;
private readonly Hook onUpdateCursor;
private readonly Dictionary eventHandlers;
+ private readonly AddonEventListener eventListener;
private AddonCursorType currentCursor;
private bool cursorSet;
@@ -48,21 +47,20 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
this.address = new AddonEventManagerAddressResolver();
this.address.Setup(sigScanner);
+ this.eventListener = new AddonEventListener(this.OnCustomEvent);
+
this.eventHandlers = new Dictionary();
this.currentCursor = AddonCursorType.Arrow;
- this.onGlobalEventHook = Hook.FromAddress(this.address.GlobalEventHandler, this.GlobalEventHandler);
this.onUpdateCursor = Hook.FromAddress(this.address.UpdateCursor, this.UpdateCursorDetour);
}
- private delegate nint GlobalEventHandlerDelegate(AtkUnitBase* atkUnitBase, AtkEventType eventType, uint eventParam, AtkResNode** eventData, nint unknown);
-
private delegate nint UpdateCursorDelegate(RaptureAtkModule* module);
///
public void Dispose()
{
- this.onGlobalEventHook.Dispose();
+ this.eventListener.Dispose();
this.onUpdateCursor.Dispose();
}
@@ -85,17 +83,39 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
}
///
- /// Adds a event handler to be triggered when the specified id is received.
+ /// Attaches an event to a node.
///
- /// Unique id for this event handler.
+ /// Addon that contains the node.
+ /// The node that will trigger the event.
+ /// The event type to trigger on.
+ /// The unique id for this event.
/// The event handler to be called.
- public void AddEvent(uint eventId, IAddonEventManager.AddonEventHandler handler) => this.eventHandlers.Add(eventId, handler);
+ public void AddEvent(AtkUnitBase* addon, AtkResNode* node, AtkEventType eventType, uint param, IAddonEventManager.AddonEventHandler handler)
+ {
+ this.eventListener.RegisterEvent(addon, node, eventType, param);
+ this.eventHandlers.TryAdd(param, handler);
+ }
///
- /// Removes the event handler with the specified id.
+ /// Detaches an event from a node.
///
- /// Event id to unregister.
- public void RemoveEvent(uint eventId) => this.eventHandlers.Remove(eventId);
+ /// The node to remove the event from.
+ /// The event type to remove.
+ /// The unique id of the event to remove.
+ public void RemoveEvent(AtkResNode* node, AtkEventType eventType, uint param)
+ {
+ this.eventListener.UnregisterEvent(node, eventType, param);
+ this.eventHandlers.Remove(param);
+ }
+
+ ///
+ /// Removes a delegate from the managed event handlers.
+ ///
+ /// Unique id of the delegate to remove.
+ public void RemoveHandler(uint param)
+ {
+ this.eventHandlers.Remove(param);
+ }
///
/// Sets the game cursor.
@@ -119,33 +139,23 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction()
{
- this.onGlobalEventHook.Enable();
this.onUpdateCursor.Enable();
}
- private nint GlobalEventHandler(AtkUnitBase* atkUnitBase, AtkEventType eventType, uint eventParam, AtkResNode** eventData, nint unknown)
+ private void OnCustomEvent(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventData, nint unknown)
{
- try
+ if (this.eventHandlers.TryGetValue(eventParam, out var handler) && eventData is not null)
{
- if (this.eventHandlers.TryGetValue(eventParam, out var handler) && eventData is not null)
+ try
{
- try
- {
- handler?.Invoke((AddonEventType)eventType, (nint)atkUnitBase, (nint)eventData[0]);
- return nint.Zero;
- }
- catch (Exception exception)
- {
- Log.Error(exception, "Exception in GlobalEventHandler custom event invoke.");
- }
+ // We passed the AtkUnitBase into the EventData.Node field from our AddonEventHandler
+ handler?.Invoke((AddonEventType)eventType, (nint)eventData->Node, (nint)eventData->Target);
+ }
+ catch (Exception exception)
+ {
+ Log.Error(exception, "Exception in OnCustomEvent custom event invoke.");
}
}
- catch (Exception e)
- {
- Log.Error(e, "Exception in GlobalEventHandler attempting to retrieve event handler.");
- }
-
- return this.onGlobalEventHook!.Original(atkUnitBase, eventType, eventParam, eventData, unknown);
}
private nint UpdateCursorDetour(RaptureAtkModule* module)
@@ -193,7 +203,7 @@ internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType,
private readonly uint paramKeyStartRange;
private readonly List activeParamKeys;
private bool isForcingCursor;
-
+
///
/// Initializes a new instance of the class.
///
@@ -208,7 +218,7 @@ internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType,
{
foreach (var activeKey in this.activeParamKeys)
{
- this.baseEventManager.RemoveEvent(activeKey);
+ this.baseEventManager.RemoveHandler(activeKey);
}
// if multiple plugins force cursors and dispose without un-forcing them then all forces will be cleared.
@@ -221,18 +231,16 @@ internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType,
///
public void AddEvent(uint eventId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
{
- if (eventId < 0x10000)
+ if (eventId < 0x10_000)
{
var type = (AtkEventType)eventType;
var node = (AtkResNode*)atkResNode;
- var eventListener = (AtkEventListener*)atkUnitBase;
+ var addon = (AtkUnitBase*)atkUnitBase;
var uniqueId = eventId + this.paramKeyStartRange;
if (!this.activeParamKeys.Contains(uniqueId))
{
- node->AddEvent(type, uniqueId, eventListener, node, true);
- this.baseEventManager.AddEvent(uniqueId, eventHandler);
-
+ this.baseEventManager.AddEvent(addon, node, type, uniqueId, eventHandler);
this.activeParamKeys.Add(uniqueId);
}
else
@@ -242,7 +250,7 @@ internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType,
}
else
{
- Log.Warning($"Attempted to register eventId out of range: {eventId}\nMaximum value: {0x10000}");
+ Log.Warning($"Attempted to register eventId out of range: {eventId}\nMaximum value: {0x10_000}");
}
}
@@ -251,14 +259,11 @@ internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType,
{
var type = (AtkEventType)eventType;
var node = (AtkResNode*)atkResNode;
- var eventListener = (AtkEventListener*)atkUnitBase;
var uniqueId = eventId + this.paramKeyStartRange;
if (this.activeParamKeys.Contains(uniqueId))
{
- node->RemoveEvent(type, uniqueId, eventListener, true);
- this.baseEventManager.RemoveEvent(uniqueId);
-
+ this.baseEventManager.RemoveEvent(node, type, uniqueId);
this.activeParamKeys.Remove(uniqueId);
}
else
diff --git a/Dalamud/Game/AddonEventManager/AddonEventManagerAddressResolver.cs b/Dalamud/Game/AddonEventManager/AddonEventManagerAddressResolver.cs
index 5cfa51149..ba1c07db8 100644
--- a/Dalamud/Game/AddonEventManager/AddonEventManagerAddressResolver.cs
+++ b/Dalamud/Game/AddonEventManager/AddonEventManagerAddressResolver.cs
@@ -5,11 +5,6 @@
///
internal class AddonEventManagerAddressResolver : BaseAddressResolver
{
- ///
- /// Gets the address of the global AtkEvent handler.
- ///
- public nint GlobalEventHandler { get; private set; }
-
///
/// Gets the address of the AtkModule UpdateCursor method.
///
@@ -21,7 +16,6 @@ internal class AddonEventManagerAddressResolver : BaseAddressResolver
/// The signature scanner to facilitate setup.
protected override void Setup64Bit(SigScanner scanner)
{
- this.GlobalEventHandler = scanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 50 44 0F B7 F2");
this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE");
}
}