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.Collections.Generic;
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 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
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 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
this.onAddonSetupHook.Dispose();
this.onAddonFinalizeHook.Dispose();
+ this.onAddonDrawHook.Dispose();
+ this.onAddonUpdateHook.Dispose();
}
///
@@ -100,6 +111,8 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
{
this.onAddonSetupHook.Enable();
this.onAddonFinalizeHook.Enable();
+ this.onAddonDrawHook.Enable();
+ this.onAddonUpdateHook.Enable();
}
private void InvokeListeners(AddonEvent eventType, IAddonLifecycle.AddonArgs args)
@@ -158,6 +171,56 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
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 @@ internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, 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 @@ internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLif
///
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 @@ internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLif
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 @@ internal class AddonLifecycleAddressResolver : BaseAddressResolver
{
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");
}
}