mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Add AddonLifecycle Service (#1361)
Adds new service to manage addon lifecycle events, such as setup/teardown.
This commit is contained in:
parent
49bffccf82
commit
efefcd70cf
3 changed files with 246 additions and 0 deletions
169
Dalamud/Game/AddonLifecycle/AddonLifecycle.cs
Normal file
169
Dalamud/Game/AddonLifecycle/AddonLifecycle.cs
Normal file
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides events for in-game addon lifecycles.
|
||||
/// </summary>
|
||||
[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<AddonSetupDelegate> onAddonSetupHook;
|
||||
private readonly Hook<AddonFinalizeDelegate> onAddonFinalizeHook;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AddonLifecycle(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new AddonLifecycleAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.onAddonSetupHook = Hook<AddonSetupDelegate>.FromAddress(this.address.AddonSetup, this.OnAddonSetup);
|
||||
this.onAddonFinalizeHook = Hook<AddonFinalizeDelegate>.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);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<IAddonLifecycle.AddonArgs>? AddonPreSetup;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<IAddonLifecycle.AddonArgs>? AddonPostSetup;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<IAddonLifecycle.AddonArgs>? AddonPreFinalize;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<IAddonLifecycle.AddonArgs>? AddonPostFinalize;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a AddonLifecycle service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IAddonLifecycle>]
|
||||
#pragma warning restore SA1015
|
||||
internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLifecycle
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonLifecycle addonLifecycleService = Service<AddonLifecycle>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonLifecyclePluginScoped"/> class.
|
||||
/// </summary>
|
||||
public AddonLifecyclePluginScoped()
|
||||
{
|
||||
this.addonLifecycleService.AddonPreSetup += this.AddonPreSetupForward;
|
||||
this.addonLifecycleService.AddonPostSetup += this.AddonPostSetupForward;
|
||||
this.addonLifecycleService.AddonPreFinalize += this.AddonPreFinalizeForward;
|
||||
this.addonLifecycleService.AddonPostFinalize += this.AddonPostFinalizeForward;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<IAddonLifecycle.AddonArgs>? AddonPreSetup;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<IAddonLifecycle.AddonArgs>? AddonPostSetup;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<IAddonLifecycle.AddonArgs>? AddonPreFinalize;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<IAddonLifecycle.AddonArgs>? AddonPostFinalize;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
27
Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs
Normal file
27
Dalamud/Game/AddonLifecycle/AddonLifecycleAddressResolver.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace Dalamud.Game.AddonLifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// AddonLifecycleService memory address resolver.
|
||||
/// </summary>
|
||||
internal class AddonLifecycleAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the addon setup hook invoked by the atkunitmanager.
|
||||
/// </summary>
|
||||
public nint AddonSetup { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the addon finalize hook invoked by the atkunitmanager.
|
||||
/// </summary>
|
||||
public nint AddonFinalize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scan for and setup any configured address pointers.
|
||||
/// </summary>
|
||||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||
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");
|
||||
}
|
||||
}
|
||||
50
Dalamud/Plugin/Services/IAddonLifecycle.cs
Normal file
50
Dalamud/Plugin/Services/IAddonLifecycle.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides events for in-game addon lifecycles.
|
||||
/// </summary>
|
||||
public interface IAddonLifecycle
|
||||
{
|
||||
/// <summary>
|
||||
/// Event that fires before an addon is being setup.
|
||||
/// </summary>
|
||||
public event Action<AddonArgs> AddonPreSetup;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires after an addon is done being setup.
|
||||
/// </summary>
|
||||
public event Action<AddonArgs> AddonPostSetup;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires before an addon is being finalized.
|
||||
/// </summary>
|
||||
public event Action<AddonArgs> AddonPreFinalize;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires after an addon is done being finalized.
|
||||
/// </summary>
|
||||
public event Action<AddonArgs> AddonPostFinalize;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for use in event subscribers.
|
||||
/// </summary>
|
||||
public unsafe class AddonArgs
|
||||
{
|
||||
private string? addonName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the addon this args referrers to.
|
||||
/// </summary>
|
||||
public string AddonName => this.addonName ??= MemoryHelper.ReadString((nint)((AtkUnitBase*)this.Addon)->Name, 0x20);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the addons AtkUnitBase.
|
||||
/// </summary>
|
||||
required public nint Addon { get; init; }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue