diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs
new file mode 100644
index 000000000..95cb2539c
--- /dev/null
+++ b/Dalamud/Game/AddonLifecycle/AddonLifecycle.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Runtime.InteropServices;
+
+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.AddonLifecycle;
+
+///
+/// This class provides events for in-game addon lifecycles.
+///
+[InterfaceVersion("1.0")]
+[ServiceManager.EarlyLoadedService]
+internal unsafe class AddonLifecycle : IDisposable, IServiceType, IAddonLifecycle
+{
+ private static readonly ModuleLog Log = new("AddonLifecycle");
+ private readonly AddonLifecycleAddressResolver address;
+ private readonly Hook onAddonSetupHook;
+ private readonly Hook onAddonFinalizeHook;
+
+ [ServiceManager.ServiceConstructor]
+ private AddonLifecycle(SigScanner sigScanner)
+ {
+ this.address = new AddonLifecycleAddressResolver();
+ this.address.Setup(sigScanner);
+
+ this.onAddonSetupHook = Hook.FromAddress(this.address.AddonSetup, this.OnAddonSetup);
+ this.onAddonFinalizeHook = Hook.FromAddress(this.address.AddonSetup, this.OnAddonFinalize);
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate nint AddonSetupDelegate(AtkUnitBase* addon);
+
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate void AddonFinalizeDelegate(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase);
+
+ ///
+ public event Action? AddonPreSetup;
+
+ ///
+ public event Action? AddonPostSetup;
+
+ ///
+ public event Action? AddonPreFinalize;
+
+ ///
+ public event Action? AddonPostFinalize;
+
+ ///
+ public void Dispose()
+ {
+ this.onAddonSetupHook.Dispose();
+ this.onAddonFinalizeHook.Dispose();
+ }
+
+ [ServiceManager.CallWhenServicesReady]
+ private void ContinueConstruction()
+ {
+ this.onAddonSetupHook.Enable();
+ this.onAddonFinalizeHook.Enable();
+ }
+
+ private nint OnAddonSetup(AtkUnitBase* addon)
+ {
+ try
+ {
+ this.AddonPreSetup?.Invoke(new IAddonLifecycle.AddonArgs { Addon = (nint)addon });
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Exception in OnAddonSetup pre-setup invoke.");
+ }
+
+ var result = this.onAddonSetupHook.Original(addon);
+
+ try
+ {
+ this.AddonPostSetup?.Invoke(new IAddonLifecycle.AddonArgs { Addon = (nint)addon });
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Exception in OnAddonSetup post-setup invoke.");
+ }
+
+ return result;
+ }
+
+ private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase)
+ {
+ try
+ {
+ this.AddonPreFinalize?.Invoke(new IAddonLifecycle.AddonArgs { Addon = (nint)atkUnitBase[0] });
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Exception in OnAddonFinalize pre-finalize invoke.");
+ }
+
+ this.onAddonFinalizeHook.Original(unitManager, atkUnitBase);
+
+ try
+ {
+ this.AddonPostFinalize?.Invoke(new IAddonLifecycle.AddonArgs { Addon = (nint)atkUnitBase[0] });
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Exception in OnAddonFinalize post-finalize invoke.");
+ }
+ }
+}
+
+///
+/// Plugin-scoped version of a AddonLifecycle service.
+///
+[PluginInterface]
+[InterfaceVersion("1.0")]
+[ServiceManager.ScopedService]
+#pragma warning disable SA1015
+[ResolveVia]
+#pragma warning restore SA1015
+internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLifecycle
+{
+ [ServiceManager.ServiceDependency]
+ private readonly AddonLifecycle addonLifecycleService = Service.Get();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public AddonLifecyclePluginScoped()
+ {
+ this.addonLifecycleService.AddonPreSetup += this.AddonPreSetupForward;
+ this.addonLifecycleService.AddonPostSetup += this.AddonPostSetupForward;
+ this.addonLifecycleService.AddonPreFinalize += this.AddonPreFinalizeForward;
+ this.addonLifecycleService.AddonPostFinalize += this.AddonPostFinalizeForward;
+ }
+
+ ///
+ public event Action? AddonPreSetup;
+
+ ///
+ public event Action? AddonPostSetup;
+
+ ///
+ public event Action? AddonPreFinalize;
+
+ ///
+ public event Action? AddonPostFinalize;
+
+ ///
+ public void Dispose()
+ {
+ this.addonLifecycleService.AddonPreSetup -= this.AddonPreSetupForward;
+ this.addonLifecycleService.AddonPostSetup -= this.AddonPostSetupForward;
+ this.addonLifecycleService.AddonPreFinalize -= this.AddonPreFinalizeForward;
+ this.addonLifecycleService.AddonPostFinalize -= this.AddonPostFinalizeForward;
+ }
+
+ private void AddonPreSetupForward(IAddonLifecycle.AddonArgs args) => this.AddonPreSetup?.Invoke(args);
+
+ private void AddonPostSetupForward(IAddonLifecycle.AddonArgs args) => this.AddonPostSetup?.Invoke(args);
+
+ private void AddonPreFinalizeForward(IAddonLifecycle.AddonArgs args) => this.AddonPreFinalize?.Invoke(args);
+
+ private void AddonPostFinalizeForward(IAddonLifecycle.AddonArgs args) => this.AddonPostFinalize?.Invoke(args);
+}
diff --git a/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs b/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs
new file mode 100644
index 000000000..ba7b723ec
--- /dev/null
+++ b/Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs
@@ -0,0 +1,27 @@
+namespace Dalamud.Game.AddonLifecycle;
+
+///
+/// AddonLifecycleService memory address resolver.
+///
+internal class AddonLifecycleAddressResolver : BaseAddressResolver
+{
+ ///
+ /// 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.
+ ///
+ public nint AddonFinalize { get; private set; }
+
+ ///
+ /// Scan for and setup any configured address pointers.
+ ///
+ /// The signature scanner to facilitate setup.
+ protected override void Setup64Bit(SigScanner sig)
+ {
+ this.AddonSetup = sig.ScanText("E8 ?? ?? ?? ?? 8B 83 ?? ?? ?? ?? C1 E8 14");
+ this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 7C 24 ?? 41 8B C6");
+ }
+}
diff --git a/Dalamud/Plugin/Services/IAddonLifecycle.cs b/Dalamud/Plugin/Services/IAddonLifecycle.cs
new file mode 100644
index 000000000..7b90cf0cd
--- /dev/null
+++ b/Dalamud/Plugin/Services/IAddonLifecycle.cs
@@ -0,0 +1,50 @@
+using System;
+
+using Dalamud.Memory;
+using FFXIVClientStructs.FFXIV.Component.GUI;
+
+namespace Dalamud.Plugin.Services;
+
+///
+/// This class provides events for in-game addon lifecycles.
+///
+public interface IAddonLifecycle
+{
+ ///
+ /// Event that fires before an addon is being setup.
+ ///
+ public event Action AddonPreSetup;
+
+ ///
+ /// Event that fires after an addon is done being setup.
+ ///
+ public event Action AddonPostSetup;
+
+ ///
+ /// Event that fires before an addon is being finalized.
+ ///
+ public event Action AddonPreFinalize;
+
+ ///
+ /// Event that fires after an addon is done being finalized.
+ ///
+ public event Action AddonPostFinalize;
+
+ ///
+ /// Addon argument data for use in event subscribers.
+ ///
+ public unsafe class AddonArgs
+ {
+ private string? addonName;
+
+ ///
+ /// Gets the name of the addon this args referrers to.
+ ///
+ public string AddonName => this.addonName ??= MemoryHelper.ReadString((nint)((AtkUnitBase*)this.Addon)->Name, 0x20);
+
+ ///
+ /// Gets the pointer to the addons AtkUnitBase.
+ ///
+ required public nint Addon { get; init; }
+ }
+}