mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Add AddonEventManager
This commit is contained in:
parent
fc2042ea4f
commit
c095f99cd1
4 changed files with 396 additions and 0 deletions
207
Dalamud/Game/AddonEventManager/AddonEventManager.cs
Normal file
207
Dalamud/Game/AddonEventManager/AddonEventManager.cs
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.AddonEventManager;
|
||||
|
||||
/// <summary>
|
||||
/// Service provider for addon event management.
|
||||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
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;
|
||||
|
||||
// The range each plugin is allowed to use.
|
||||
// 65,536 per plugin should be reasonable.
|
||||
private const uint ParamKeyPluginRange = 0x10000;
|
||||
|
||||
// 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;
|
||||
|
||||
private static readonly ModuleLog Log = new("AddonEventManager");
|
||||
private readonly AddonEventManagerAddressResolver address;
|
||||
private readonly Hook<GlobalEventHandlerDetour> onGlobalEventHook;
|
||||
private readonly Dictionary<uint, IAddonEventManager.AddonEventHandler> eventHandlers;
|
||||
|
||||
private uint currentPluginParamStart = ParamKeyStart;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AddonEventManager(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new AddonEventManagerAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.eventHandlers = new Dictionary<uint, IAddonEventManager.AddonEventHandler>();
|
||||
|
||||
this.onGlobalEventHook = Hook<GlobalEventHandlerDetour>.FromAddress(this.address.GlobalEventHandler, this.GlobalEventHandler);
|
||||
}
|
||||
|
||||
private delegate nint GlobalEventHandlerDetour(AtkUnitBase* atkUnitBase, AtkEventType eventType, uint eventParam, AtkResNode** eventData, nint unknown);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.onGlobalEventHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the start value for a new plugin register.
|
||||
/// </summary>
|
||||
/// <returns>A unique starting range for event handlers.</returns>
|
||||
/// <exception cref="Exception">Throws when attempting to register too many event handlers.</exception>
|
||||
public uint GetPluginParamStart()
|
||||
{
|
||||
if (this.currentPluginParamStart >= ParamKeyMax)
|
||||
{
|
||||
throw new Exception("Maximum number of event handlers reached.");
|
||||
}
|
||||
|
||||
var result = this.currentPluginParamStart;
|
||||
|
||||
this.currentPluginParamStart += ParamKeyPluginRange;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a event handler to be triggered when the specified id is received.
|
||||
/// </summary>
|
||||
/// <param name="eventId">Unique id for this event handler.</param>
|
||||
/// <param name="handler">The event handler to be called.</param>
|
||||
public void AddEvent(uint eventId, IAddonEventManager.AddonEventHandler handler) => this.eventHandlers.Add(eventId, handler);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the event handler with the specified id.
|
||||
/// </summary>
|
||||
/// <param name="eventId">Event id to unregister.</param>
|
||||
public void RemoveEvent(uint eventId) => this.eventHandlers.Remove(eventId);
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction()
|
||||
{
|
||||
this.onGlobalEventHook.Enable();
|
||||
}
|
||||
|
||||
private nint GlobalEventHandler(AtkUnitBase* atkUnitBase, AtkEventType eventType, uint eventParam, AtkResNode** eventData, nint unknown)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.eventHandlers.TryGetValue(eventParam, out var handler))
|
||||
{
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in GlobalEventHandler attempting to retrieve event handler.");
|
||||
}
|
||||
|
||||
return this.onGlobalEventHook!.Original(atkUnitBase, eventType, eventParam, eventData, unknown);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a AddonEventManager service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IAddonEventManager>]
|
||||
#pragma warning restore SA1015
|
||||
internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType, IAddonEventManager
|
||||
{
|
||||
private static readonly ModuleLog Log = new("AddonEventManager");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonEventManager baseEventManager = Service<AddonEventManager>.Get();
|
||||
|
||||
private readonly uint paramKeyStartRange;
|
||||
private readonly List<uint> activeParamKeys;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonEventManagerPluginScoped"/> class.
|
||||
/// </summary>
|
||||
public AddonEventManagerPluginScoped()
|
||||
{
|
||||
this.paramKeyStartRange = this.baseEventManager.GetPluginParamStart();
|
||||
this.activeParamKeys = new List<uint>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var activeKey in this.activeParamKeys)
|
||||
{
|
||||
this.baseEventManager.RemoveEvent(activeKey);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddEvent(uint eventId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
||||
{
|
||||
if (eventId < 0x10000)
|
||||
{
|
||||
var type = (AtkEventType)eventType;
|
||||
var node = (AtkResNode*)atkResNode;
|
||||
var eventListener = (AtkEventListener*)atkUnitBase;
|
||||
var uniqueId = eventId + this.paramKeyStartRange;
|
||||
|
||||
if (!this.activeParamKeys.Contains(uniqueId))
|
||||
{
|
||||
node->AddEvent(type, uniqueId, eventListener, node, true);
|
||||
this.baseEventManager.AddEvent(uniqueId, eventHandler);
|
||||
|
||||
this.activeParamKeys.Add(uniqueId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"Attempted to register already registered eventId: {eventId}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"Attempted to register eventId out of range: {eventId}\nMaximum value: {0x10000}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveEvent(uint eventId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType)
|
||||
{
|
||||
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.activeParamKeys.Remove(uniqueId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"Attempted to unregister already unregistered eventId: {eventId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
namespace Dalamud.Game.AddonEventManager;
|
||||
|
||||
/// <summary>
|
||||
/// AddonEventManager memory address resolver.
|
||||
/// </summary>
|
||||
internal class AddonEventManagerAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the global atkevent handler
|
||||
/// </summary>
|
||||
public nint GlobalEventHandler { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scan for and setup any configured address pointers.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The signature scanner to facilitate setup.</param>
|
||||
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");
|
||||
}
|
||||
}
|
||||
132
Dalamud/Game/AddonEventManager/AddonEventType.cs
Normal file
132
Dalamud/Game/AddonEventManager/AddonEventType.cs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
namespace Dalamud.Game.AddonEventManager;
|
||||
|
||||
/// <summary>
|
||||
/// Reimplementation of AtkEventType.
|
||||
/// </summary>
|
||||
public enum AddonEventType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Mouse Down.
|
||||
/// </summary>
|
||||
MouseDown = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Up.
|
||||
/// </summary>
|
||||
MouseUp = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Move.
|
||||
/// </summary>
|
||||
MouseMove = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Over.
|
||||
/// </summary>
|
||||
MouseOver = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Out.
|
||||
/// </summary>
|
||||
MouseOut = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Click.
|
||||
/// </summary>
|
||||
MouseClick = 9,
|
||||
|
||||
/// <summary>
|
||||
/// Input Received.
|
||||
/// </summary>
|
||||
InputReceived = 12,
|
||||
|
||||
/// <summary>
|
||||
/// Focus Start.
|
||||
/// </summary>
|
||||
FocusStart = 18,
|
||||
|
||||
/// <summary>
|
||||
/// Focus Stop.
|
||||
/// </summary>
|
||||
FocusStop = 19,
|
||||
|
||||
/// <summary>
|
||||
/// Button Press, sent on MouseDown on Button.
|
||||
/// </summary>
|
||||
ButtonPress = 23,
|
||||
|
||||
/// <summary>
|
||||
/// Button Release, sent on MouseUp and MouseOut.
|
||||
/// </summary>
|
||||
ButtonRelease = 24,
|
||||
|
||||
/// <summary>
|
||||
/// Button Click, sent on MouseUp and MouseClick on button.
|
||||
/// </summary>
|
||||
ButtonClick = 25,
|
||||
|
||||
/// <summary>
|
||||
/// List Item RollOver.
|
||||
/// </summary>
|
||||
ListItemRollOver = 33,
|
||||
|
||||
/// <summary>
|
||||
/// List Item Roll Out.
|
||||
/// </summary>
|
||||
ListItemRollOut = 34,
|
||||
|
||||
/// <summary>
|
||||
/// List Item Toggle.
|
||||
/// </summary>
|
||||
ListItemToggle = 35,
|
||||
|
||||
/// <summary>
|
||||
/// Drag Drop Roll Over.
|
||||
/// </summary>
|
||||
DragDropRollOver = 52,
|
||||
|
||||
/// <summary>
|
||||
/// Drag Drop Roll Out.
|
||||
/// </summary>
|
||||
DragDropRollOut = 53,
|
||||
|
||||
/// <summary>
|
||||
/// Drag Drop Unknown.
|
||||
/// </summary>
|
||||
DragDropUnk54 = 54,
|
||||
|
||||
/// <summary>
|
||||
/// Drag Drop Unknown.
|
||||
/// </summary>
|
||||
DragDropUnk55 = 55,
|
||||
|
||||
/// <summary>
|
||||
/// Icon Text Roll Over.
|
||||
/// </summary>
|
||||
IconTextRollOver = 56,
|
||||
|
||||
/// <summary>
|
||||
/// Icon Text Roll Out.
|
||||
/// </summary>
|
||||
IconTextRollOut = 57,
|
||||
|
||||
/// <summary>
|
||||
/// Icon Text Click.
|
||||
/// </summary>
|
||||
IconTextClick = 58,
|
||||
|
||||
/// <summary>
|
||||
/// Window Roll Over.
|
||||
/// </summary>
|
||||
WindowRollOver = 67,
|
||||
|
||||
/// <summary>
|
||||
/// Window Roll Out.
|
||||
/// </summary>
|
||||
WindowRollOut = 68,
|
||||
|
||||
/// <summary>
|
||||
/// Window Change Scale.
|
||||
/// </summary>
|
||||
WindowChangeScale = 69,
|
||||
}
|
||||
36
Dalamud/Plugin/Services/IAddonEventManager.cs
Normal file
36
Dalamud/Plugin/Services/IAddonEventManager.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using Dalamud.Game.AddonEventManager;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service provider for addon event management.
|
||||
/// </summary>
|
||||
public interface IAddonEventManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate to be called when an event is received.
|
||||
/// </summary>
|
||||
/// <param name="atkEventType">Event type for this event handler.</param>
|
||||
/// <param name="atkUnitBase">The parent addon for this event handler.</param>
|
||||
/// <param name="atkResNode">The specific node that will trigger this event handler.</param>
|
||||
public delegate void AddonEventHandler(AddonEventType atkEventType, nint atkUnitBase, nint atkResNode);
|
||||
|
||||
/// <summary>
|
||||
/// Registers an event handler for the specified addon, node, and type.
|
||||
/// </summary>
|
||||
/// <param name="eventId">Unique Id for this event, maximum 0x10000.</param>
|
||||
/// <param name="atkUnitBase">The parent addon for this event.</param>
|
||||
/// <param name="atkResNode">The node that will trigger this event.</param>
|
||||
/// <param name="eventType">The event type for this event.</param>
|
||||
/// <param name="eventHandler">The handler to call when event is triggered.</param>
|
||||
void AddEvent(uint eventId, nint atkUnitBase, nint atkResNode, AddonEventType eventType, AddonEventHandler eventHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters an event handler with the specified event id and event type.
|
||||
/// </summary>
|
||||
/// <param name="eventId">The Unique Id for this event.</param>
|
||||
/// <param name="atkUnitBase">The parent addon for this event.</param>
|
||||
/// <param name="atkResNode">The node for this event.</param>
|
||||
/// <param name="eventType">The event type for this event.</param>
|
||||
void RemoveEvent(uint eventId, nint atkUnitBase, nint atkResNode, AddonEventType eventType);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue