mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-13 12:14:16 +01:00
Merge 4d9751ea5f into e8485dee25
This commit is contained in:
commit
e8c6e02693
35 changed files with 1019 additions and 1105 deletions
|
|
@ -11,7 +11,7 @@ namespace Dalamud.Configuration;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configuration to store settings for a dalamud plugin.
|
/// Configuration to store settings for a dalamud plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Api13ToDo("Make this a service. We need to be able to dispose it reliably to write configs asynchronously. Maybe also let people write files with vfs.")]
|
[Api14ToDo("Make this a service. We need to be able to dispose it reliably to write configs asynchronously. Maybe also let people write files with vfs.")]
|
||||||
public sealed class PluginConfigurations
|
public sealed class PluginConfigurations
|
||||||
{
|
{
|
||||||
private readonly DirectoryInfo configDirectory;
|
private readonly DirectoryInfo configDirectory;
|
||||||
|
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon;
|
|
||||||
|
|
||||||
/// <summary>Argument pool for Addon Lifecycle services.</summary>
|
|
||||||
[ServiceManager.EarlyLoadedService]
|
|
||||||
internal sealed class AddonLifecyclePooledArgs : IServiceType
|
|
||||||
{
|
|
||||||
private readonly AddonSetupArgs?[] addonSetupArgPool = new AddonSetupArgs?[64];
|
|
||||||
private readonly AddonFinalizeArgs?[] addonFinalizeArgPool = new AddonFinalizeArgs?[64];
|
|
||||||
private readonly AddonDrawArgs?[] addonDrawArgPool = new AddonDrawArgs?[64];
|
|
||||||
private readonly AddonUpdateArgs?[] addonUpdateArgPool = new AddonUpdateArgs?[64];
|
|
||||||
private readonly AddonRefreshArgs?[] addonRefreshArgPool = new AddonRefreshArgs?[64];
|
|
||||||
private readonly AddonRequestedUpdateArgs?[] addonRequestedUpdateArgPool = new AddonRequestedUpdateArgs?[64];
|
|
||||||
private readonly AddonReceiveEventArgs?[] addonReceiveEventArgPool = new AddonReceiveEventArgs?[64];
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
|
||||||
private AddonLifecyclePooledArgs()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Rents an instance of an argument.</summary>
|
|
||||||
/// <param name="arg">The rented instance.</param>
|
|
||||||
/// <returns>The returner.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public PooledEntry<AddonSetupArgs> Rent(out AddonSetupArgs arg) => new(out arg, this.addonSetupArgPool);
|
|
||||||
|
|
||||||
/// <summary>Rents an instance of an argument.</summary>
|
|
||||||
/// <param name="arg">The rented instance.</param>
|
|
||||||
/// <returns>The returner.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public PooledEntry<AddonFinalizeArgs> Rent(out AddonFinalizeArgs arg) => new(out arg, this.addonFinalizeArgPool);
|
|
||||||
|
|
||||||
/// <summary>Rents an instance of an argument.</summary>
|
|
||||||
/// <param name="arg">The rented instance.</param>
|
|
||||||
/// <returns>The returner.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public PooledEntry<AddonDrawArgs> Rent(out AddonDrawArgs arg) => new(out arg, this.addonDrawArgPool);
|
|
||||||
|
|
||||||
/// <summary>Rents an instance of an argument.</summary>
|
|
||||||
/// <param name="arg">The rented instance.</param>
|
|
||||||
/// <returns>The returner.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public PooledEntry<AddonUpdateArgs> Rent(out AddonUpdateArgs arg) => new(out arg, this.addonUpdateArgPool);
|
|
||||||
|
|
||||||
/// <summary>Rents an instance of an argument.</summary>
|
|
||||||
/// <param name="arg">The rented instance.</param>
|
|
||||||
/// <returns>The returner.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public PooledEntry<AddonRefreshArgs> Rent(out AddonRefreshArgs arg) => new(out arg, this.addonRefreshArgPool);
|
|
||||||
|
|
||||||
/// <summary>Rents an instance of an argument.</summary>
|
|
||||||
/// <param name="arg">The rented instance.</param>
|
|
||||||
/// <returns>The returner.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public PooledEntry<AddonRequestedUpdateArgs> Rent(out AddonRequestedUpdateArgs arg) =>
|
|
||||||
new(out arg, this.addonRequestedUpdateArgPool);
|
|
||||||
|
|
||||||
/// <summary>Rents an instance of an argument.</summary>
|
|
||||||
/// <param name="arg">The rented instance.</param>
|
|
||||||
/// <returns>The returner.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public PooledEntry<AddonReceiveEventArgs> Rent(out AddonReceiveEventArgs arg) =>
|
|
||||||
new(out arg, this.addonReceiveEventArgPool);
|
|
||||||
|
|
||||||
/// <summary>Returns the object to the pool on dispose.</summary>
|
|
||||||
/// <typeparam name="T">The type.</typeparam>
|
|
||||||
public readonly ref struct PooledEntry<T>
|
|
||||||
where T : AddonArgs, new()
|
|
||||||
{
|
|
||||||
private readonly Span<T> pool;
|
|
||||||
private readonly T obj;
|
|
||||||
|
|
||||||
/// <summary>Initializes a new instance of the <see cref="PooledEntry{T}"/> struct.</summary>
|
|
||||||
/// <param name="arg">An instance of the argument.</param>
|
|
||||||
/// <param name="pool">The pool to rent from and return to.</param>
|
|
||||||
public PooledEntry(out T arg, Span<T> pool)
|
|
||||||
{
|
|
||||||
this.pool = pool;
|
|
||||||
foreach (ref var item in pool)
|
|
||||||
{
|
|
||||||
if (Interlocked.Exchange(ref item, null) is { } v)
|
|
||||||
{
|
|
||||||
this.obj = arg = v;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.obj = arg = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Returns the item to the pool.</summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
var tmp = this.obj;
|
|
||||||
foreach (ref var item in this.pool)
|
|
||||||
{
|
|
||||||
if (Interlocked.Exchange(ref item, tmp) is not { } tmp2)
|
|
||||||
return;
|
|
||||||
tmp = tmp2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,19 +5,24 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for AddonLifecycle AddonArgTypes.
|
/// Base class for AddonLifecycle AddonArgTypes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract unsafe class AddonArgs
|
public class AddonArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constant string representing the name of an addon that is invalid.
|
/// Constant string representing the name of an addon that is invalid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string InvalidAddon = "NullAddon";
|
public const string InvalidAddon = "NullAddon";
|
||||||
|
|
||||||
private string? addonName;
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AddonArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name of the addon this args referrers to.
|
/// Gets the name of the addon this args referrers to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AddonName => this.GetAddonName();
|
public string AddonName { get; private set; } = InvalidAddon;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the pointer to the addons AtkUnitBase.
|
/// Gets the pointer to the addons AtkUnitBase.
|
||||||
|
|
@ -25,55 +30,17 @@ public abstract unsafe class AddonArgs
|
||||||
public AtkUnitBasePtr Addon
|
public AtkUnitBasePtr Addon
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
internal set;
|
internal set
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
|
||||||
|
if (!this.Addon.IsNull && !string.IsNullOrEmpty(value.Name))
|
||||||
|
this.AddonName = value.Name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the type of these args.
|
/// Gets the type of these args.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract AddonArgsType Type { get; }
|
public virtual AddonArgsType Type => AddonArgsType.Generic;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if addon name matches the given span of char.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The name to check.</param>
|
|
||||||
/// <returns>Whether it is the case.</returns>
|
|
||||||
internal bool IsAddon(string name)
|
|
||||||
{
|
|
||||||
if (this.Addon.IsNull)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (name.Length is 0 or > 32)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(this.Addon.Name))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return name == this.Addon.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears this AddonArgs values.
|
|
||||||
/// </summary>
|
|
||||||
internal virtual void Clear()
|
|
||||||
{
|
|
||||||
this.addonName = null;
|
|
||||||
this.Addon = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper method for ensuring the name of the addon is valid.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The name of the addon for this object. <see cref="InvalidAddon"/> when invalid.</returns>
|
|
||||||
private string GetAddonName()
|
|
||||||
{
|
|
||||||
if (this.Addon.IsNull) return InvalidAddon;
|
|
||||||
|
|
||||||
var name = this.Addon.Name;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(name))
|
|
||||||
return InvalidAddon;
|
|
||||||
|
|
||||||
return this.addonName ??= name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonCloseArgs.cs
Normal file
22
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonCloseArgs.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Addon argument data for Close events.
|
||||||
|
/// </summary>
|
||||||
|
public class AddonCloseArgs : AddonArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonCloseArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AddonCloseArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AddonArgsType Type => AddonArgsType.Close;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the window should fire the callback method on close.
|
||||||
|
/// </summary>
|
||||||
|
public bool FireCallback { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Addon argument data for Draw events.
|
|
||||||
/// </summary>
|
|
||||||
public class AddonDrawArgs : AddonArgs, ICloneable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AddonDrawArgs"/> class.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Not intended for public construction.", false)]
|
|
||||||
public AddonDrawArgs()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override AddonArgsType Type => AddonArgsType.Draw;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
|
||||||
public AddonDrawArgs Clone() => (AddonDrawArgs)this.MemberwiseClone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Clone"/>
|
|
||||||
object ICloneable.Clone() => this.Clone();
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Addon argument data for ReceiveEvent events.
|
|
||||||
/// </summary>
|
|
||||||
public class AddonFinalizeArgs : AddonArgs, ICloneable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AddonFinalizeArgs"/> class.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Not intended for public construction.", false)]
|
|
||||||
public AddonFinalizeArgs()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override AddonArgsType Type => AddonArgsType.Finalize;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
|
||||||
public AddonFinalizeArgs Clone() => (AddonFinalizeArgs)this.MemberwiseClone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Clone"/>
|
|
||||||
object ICloneable.Clone() => this.Clone();
|
|
||||||
}
|
|
||||||
32
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonHideArgs.cs
Normal file
32
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonHideArgs.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Addon argument data for Hide events.
|
||||||
|
/// </summary>
|
||||||
|
public class AddonHideArgs : AddonArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonHideArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AddonHideArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AddonArgsType Type => AddonArgsType.Hide;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to call the hide callback handler when this hides.
|
||||||
|
/// </summary>
|
||||||
|
public bool CallHideCallback { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the flags that the window will set when it Shows/Hides.
|
||||||
|
/// </summary>
|
||||||
|
public uint SetShowHideFlags { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether something for this event message.
|
||||||
|
/// </summary>
|
||||||
|
internal bool UnknownBool { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -3,13 +3,12 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Addon argument data for ReceiveEvent events.
|
/// Addon argument data for ReceiveEvent events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddonReceiveEventArgs : AddonArgs, ICloneable
|
public class AddonReceiveEventArgs : AddonArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AddonReceiveEventArgs"/> class.
|
/// Initializes a new instance of the <see cref="AddonReceiveEventArgs"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Not intended for public construction.", false)]
|
internal AddonReceiveEventArgs()
|
||||||
public AddonReceiveEventArgs()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,23 +31,7 @@ public class AddonReceiveEventArgs : AddonArgs, ICloneable
|
||||||
public nint AtkEvent { get; set; }
|
public nint AtkEvent { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the pointer to a block of data for this event message.
|
/// Gets or sets the pointer to an AtkEventData for this event message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public nint Data { get; set; }
|
public nint AtkEventData { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
|
||||||
public AddonReceiveEventArgs Clone() => (AddonReceiveEventArgs)this.MemberwiseClone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Clone"/>
|
|
||||||
object ICloneable.Clone() => this.Clone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
|
||||||
internal override void Clear()
|
|
||||||
{
|
|
||||||
base.Clear();
|
|
||||||
this.AtkEventType = default;
|
|
||||||
this.EventParam = default;
|
|
||||||
this.AtkEvent = default;
|
|
||||||
this.Data = default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
@ -5,13 +7,12 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Addon argument data for Refresh events.
|
/// Addon argument data for Refresh events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddonRefreshArgs : AddonArgs, ICloneable
|
public class AddonRefreshArgs : AddonArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AddonRefreshArgs"/> class.
|
/// Initializes a new instance of the <see cref="AddonRefreshArgs"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Not intended for public construction.", false)]
|
internal AddonRefreshArgs()
|
||||||
public AddonRefreshArgs()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,19 +32,7 @@ public class AddonRefreshArgs : AddonArgs, ICloneable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the AtkValues in the form of a span.
|
/// Gets the AtkValues in the form of a span.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Pending removal, unsafe to use when using custom ClientStructs")]
|
||||||
|
[Api15ToDo("Remove this")]
|
||||||
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
||||||
|
|
||||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
|
||||||
public AddonRefreshArgs Clone() => (AddonRefreshArgs)this.MemberwiseClone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Clone"/>
|
|
||||||
object ICloneable.Clone() => this.Clone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
|
||||||
internal override void Clear()
|
|
||||||
{
|
|
||||||
base.Clear();
|
|
||||||
this.AtkValueCount = default;
|
|
||||||
this.AtkValues = default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,12 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Addon argument data for OnRequestedUpdate events.
|
/// Addon argument data for OnRequestedUpdate events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
|
public class AddonRequestedUpdateArgs : AddonArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AddonRequestedUpdateArgs"/> class.
|
/// Initializes a new instance of the <see cref="AddonRequestedUpdateArgs"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Not intended for public construction.", false)]
|
internal AddonRequestedUpdateArgs()
|
||||||
public AddonRequestedUpdateArgs()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,18 +24,4 @@ public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
|
||||||
/// Gets or sets the StringArrayData** for this event.
|
/// Gets or sets the StringArrayData** for this event.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public nint StringArrayData { get; set; }
|
public nint StringArrayData { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
|
||||||
public AddonRequestedUpdateArgs Clone() => (AddonRequestedUpdateArgs)this.MemberwiseClone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Clone"/>
|
|
||||||
object ICloneable.Clone() => this.Clone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
|
||||||
internal override void Clear()
|
|
||||||
{
|
|
||||||
base.Clear();
|
|
||||||
this.NumberArrayData = default;
|
|
||||||
this.StringArrayData = default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
@ -5,13 +7,12 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Addon argument data for Setup events.
|
/// Addon argument data for Setup events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddonSetupArgs : AddonArgs, ICloneable
|
public class AddonSetupArgs : AddonArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AddonSetupArgs"/> class.
|
/// Initializes a new instance of the <see cref="AddonSetupArgs"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Not intended for public construction.", false)]
|
internal AddonSetupArgs()
|
||||||
public AddonSetupArgs()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,19 +32,7 @@ public class AddonSetupArgs : AddonArgs, ICloneable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the AtkValues in the form of a span.
|
/// Gets the AtkValues in the form of a span.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Pending removal, unsafe to use when using custom ClientStructs")]
|
||||||
|
[Api15ToDo("Remove this")]
|
||||||
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
||||||
|
|
||||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
|
||||||
public AddonSetupArgs Clone() => (AddonSetupArgs)this.MemberwiseClone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Clone"/>
|
|
||||||
object ICloneable.Clone() => this.Clone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
|
||||||
internal override void Clear()
|
|
||||||
{
|
|
||||||
base.Clear();
|
|
||||||
this.AtkValueCount = default;
|
|
||||||
this.AtkValues = default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
27
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonShowArgs.cs
Normal file
27
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonShowArgs.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Addon argument data for Show events.
|
||||||
|
/// </summary>
|
||||||
|
public class AddonShowArgs : AddonArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonShowArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AddonShowArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AddonArgsType Type => AddonArgsType.Show;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the window should play open sound effects.
|
||||||
|
/// </summary>
|
||||||
|
public bool SilenceOpenSoundEffect { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the flags that the window will unset when it Shows/Hides.
|
||||||
|
/// </summary>
|
||||||
|
public uint UnsetShowHideFlags { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Addon argument data for Update events.
|
|
||||||
/// </summary>
|
|
||||||
public class AddonUpdateArgs : AddonArgs, ICloneable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AddonUpdateArgs"/> class.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Not intended for public construction.", false)]
|
|
||||||
public AddonUpdateArgs()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override AddonArgsType Type => AddonArgsType.Update;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the time since the last update.
|
|
||||||
/// </summary>
|
|
||||||
public float TimeDelta
|
|
||||||
{
|
|
||||||
get => this.TimeDeltaInternal;
|
|
||||||
init => this.TimeDeltaInternal = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the time since the last update.
|
|
||||||
/// </summary>
|
|
||||||
internal float TimeDeltaInternal { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
|
||||||
public AddonUpdateArgs Clone() => (AddonUpdateArgs)this.MemberwiseClone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Clone"/>
|
|
||||||
object ICloneable.Clone() => this.Clone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
|
||||||
internal override void Clear()
|
|
||||||
{
|
|
||||||
base.Clear();
|
|
||||||
this.TimeDeltaInternal = default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,38 +5,43 @@
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum AddonArgsType
|
public enum AddonArgsType
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generic arg type that contains no meaningful data.
|
||||||
|
/// </summary>
|
||||||
|
Generic,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains argument data for Setup.
|
/// Contains argument data for Setup.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Setup,
|
Setup,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains argument data for Update.
|
|
||||||
/// </summary>
|
|
||||||
Update,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains argument data for Draw.
|
|
||||||
/// </summary>
|
|
||||||
Draw,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains argument data for Finalize.
|
|
||||||
/// </summary>
|
|
||||||
Finalize,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains argument data for RequestedUpdate.
|
/// Contains argument data for RequestedUpdate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RequestedUpdate,
|
RequestedUpdate,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains argument data for Refresh.
|
/// Contains argument data for Refresh.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Refresh,
|
Refresh,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains argument data for ReceiveEvent.
|
/// Contains argument data for ReceiveEvent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ReceiveEvent,
|
ReceiveEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for Show.
|
||||||
|
/// </summary>
|
||||||
|
Show,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for Hide.
|
||||||
|
/// </summary>
|
||||||
|
Hide,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for Close.
|
||||||
|
/// </summary>
|
||||||
|
Close,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ public enum AddonEvent
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <seealso cref="AddonSetupArgs"/>
|
/// <seealso cref="AddonSetupArgs"/>
|
||||||
PreSetup,
|
PreSetup,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that is fired after an addon has finished its initial setup. This event is particularly useful for
|
/// An event that is fired after an addon has finished its initial setup. This event is particularly useful for
|
||||||
/// developers seeking to add custom elements to now-initialized and populated node lists, as well as reading data
|
/// developers seeking to add custom elements to now-initialized and populated node lists, as well as reading data
|
||||||
|
|
@ -29,7 +29,6 @@ public enum AddonEvent
|
||||||
/// An event that is fired before an addon begins its update cycle via <see cref="AtkUnitBase.Update"/>. This event
|
/// An event that is fired before an addon begins its update cycle via <see cref="AtkUnitBase.Update"/>. This event
|
||||||
/// is fired every frame that an addon is loaded, regardless of visibility.
|
/// is fired every frame that an addon is loaded, regardless of visibility.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <seealso cref="AddonUpdateArgs"/>
|
|
||||||
PreUpdate,
|
PreUpdate,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -42,7 +41,6 @@ public enum AddonEvent
|
||||||
/// An event that is fired before an addon begins drawing to screen via <see cref="AtkUnitBase.Draw"/>. Unlike
|
/// An event that is fired before an addon begins drawing to screen via <see cref="AtkUnitBase.Draw"/>. Unlike
|
||||||
/// <see cref="PreUpdate"/>, this event is only fired if an addon is visible or otherwise drawing to screen.
|
/// <see cref="PreUpdate"/>, this event is only fired if an addon is visible or otherwise drawing to screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <seealso cref="AddonDrawArgs"/>
|
|
||||||
PreDraw,
|
PreDraw,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -62,9 +60,8 @@ public enum AddonEvent
|
||||||
/// <br />
|
/// <br />
|
||||||
/// As this is part of the destruction process for an addon, this event does not have an associated Post event.
|
/// As this is part of the destruction process for an addon, this event does not have an associated Post event.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <seealso cref="AddonFinalizeArgs"/>
|
|
||||||
PreFinalize,
|
PreFinalize,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that is fired before a call to <see cref="AtkUnitBase.OnRequestedUpdate"/> is made in response to a
|
/// An event that is fired before a call to <see cref="AtkUnitBase.OnRequestedUpdate"/> is made in response to a
|
||||||
/// change in the subscribed <see cref="AddonRequestedUpdateArgs.NumberArrayData"/> or
|
/// change in the subscribed <see cref="AddonRequestedUpdateArgs.NumberArrayData"/> or
|
||||||
|
|
@ -81,13 +78,13 @@ public enum AddonEvent
|
||||||
/// to the Free Company's overview.
|
/// to the Free Company's overview.
|
||||||
/// </example>
|
/// </example>
|
||||||
PreRequestedUpdate,
|
PreRequestedUpdate,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that is fired after an addon has finished processing an <c>ArrayData</c> update.
|
/// An event that is fired after an addon has finished processing an <c>ArrayData</c> update.
|
||||||
/// See <see cref="PreRequestedUpdate"/> for more information.
|
/// See <see cref="PreRequestedUpdate"/> for more information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PostRequestedUpdate,
|
PostRequestedUpdate,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that is fired before an addon calls its <see cref="AtkUnitManager.RefreshAddon"/> method. Refreshes are
|
/// An event that is fired before an addon calls its <see cref="AtkUnitManager.RefreshAddon"/> method. Refreshes are
|
||||||
/// generally triggered in response to certain user interactions such as changing tabs, and are primarily used to
|
/// generally triggered in response to certain user interactions such as changing tabs, and are primarily used to
|
||||||
|
|
@ -96,13 +93,13 @@ public enum AddonEvent
|
||||||
/// <seealso cref="AddonRefreshArgs"/>
|
/// <seealso cref="AddonRefreshArgs"/>
|
||||||
/// <seealso cref="PostRefresh"/>
|
/// <seealso cref="PostRefresh"/>
|
||||||
PreRefresh,
|
PreRefresh,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that is fired after an addon has finished its refresh.
|
/// An event that is fired after an addon has finished its refresh.
|
||||||
/// See <see cref="PreRefresh"/> for more information.
|
/// See <see cref="PreRefresh"/> for more information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PostRefresh,
|
PostRefresh,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that is fired before an addon begins processing a user-driven event via
|
/// An event that is fired before an addon begins processing a user-driven event via
|
||||||
/// <see cref="AtkEventListener.ReceiveEvent"/>, such as mousing over an element or clicking a button. This event
|
/// <see cref="AtkEventListener.ReceiveEvent"/>, such as mousing over an element or clicking a button. This event
|
||||||
|
|
@ -112,10 +109,98 @@ public enum AddonEvent
|
||||||
/// <seealso cref="AddonReceiveEventArgs"/>
|
/// <seealso cref="AddonReceiveEventArgs"/>
|
||||||
/// <seealso cref="PostReceiveEvent"/>
|
/// <seealso cref="PostReceiveEvent"/>
|
||||||
PreReceiveEvent,
|
PreReceiveEvent,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that is fired after an addon finishes calling its <see cref="AtkEventListener.ReceiveEvent"/> method.
|
/// An event that is fired after an addon finishes calling its <see cref="AtkEventListener.ReceiveEvent"/> method.
|
||||||
/// See <see cref="PreReceiveEvent"/> for more information.
|
/// See <see cref="PreReceiveEvent"/> for more information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PostReceiveEvent,
|
PostReceiveEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its open method.
|
||||||
|
/// </summary>
|
||||||
|
PreOpen,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its open method.
|
||||||
|
/// </summary>
|
||||||
|
PostOpen,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An even that is fired before an addon processes its Close method.
|
||||||
|
/// </summary>
|
||||||
|
PreClose,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its Close method.
|
||||||
|
/// </summary>
|
||||||
|
PostClose,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its Show method.
|
||||||
|
/// </summary>
|
||||||
|
PreShow,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its Show method.
|
||||||
|
/// </summary>
|
||||||
|
PostShow,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its Hide method.
|
||||||
|
/// </summary>
|
||||||
|
PreHide,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its Hide method.
|
||||||
|
/// </summary>
|
||||||
|
PostHide,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its OnMove method.
|
||||||
|
/// OnMove is triggered only when a move is completed.
|
||||||
|
/// </summary>
|
||||||
|
PreMove,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its OnMove method.
|
||||||
|
/// OnMove is triggered only when a move is completed.
|
||||||
|
/// </summary>
|
||||||
|
PostMove,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its MouseOver method.
|
||||||
|
/// </summary>
|
||||||
|
PreMouseOver,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its MouseOver method.
|
||||||
|
/// </summary>
|
||||||
|
PostMouseOver,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its MouseOut method.
|
||||||
|
/// </summary>
|
||||||
|
PreMouseOut,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its MouseOut method.
|
||||||
|
/// </summary>
|
||||||
|
PostMouseOut,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its Focus method.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows.
|
||||||
|
/// </remarks>
|
||||||
|
PreFocus,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its Focus method.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows.
|
||||||
|
/// </remarks>
|
||||||
|
PostFocus,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Hooking.Internal;
|
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Lifecycle;
|
namespace Dalamud.Game.Addon.Lifecycle;
|
||||||
|
|
@ -21,75 +19,36 @@ namespace Dalamud.Game.Addon.Lifecycle;
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal unsafe class AddonLifecycle : IInternalDisposableService
|
internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of all allocated addon virtual tables.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly List<AddonVirtualTable> AllocatedTables = [];
|
||||||
|
|
||||||
private static readonly ModuleLog Log = new("AddonLifecycle");
|
private static readonly ModuleLog Log = new("AddonLifecycle");
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
private Hook<AtkUnitBase.Delegates.Initialize>? onInitializeAddonHook;
|
||||||
private readonly Framework framework = Service<Framework>.Get();
|
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
|
||||||
private readonly AddonLifecyclePooledArgs argsPool = Service<AddonLifecyclePooledArgs>.Get();
|
|
||||||
|
|
||||||
private readonly nint disallowedReceiveEventAddress;
|
|
||||||
|
|
||||||
private readonly AddonLifecycleAddressResolver address;
|
|
||||||
private readonly AddonSetupHook<AtkUnitBase.Delegates.OnSetup> onAddonSetupHook;
|
|
||||||
private readonly Hook<AddonFinalizeDelegate> onAddonFinalizeHook;
|
|
||||||
private readonly CallHook<AtkUnitBase.Delegates.Draw> onAddonDrawHook;
|
|
||||||
private readonly CallHook<AtkUnitBase.Delegates.Update> onAddonUpdateHook;
|
|
||||||
private readonly Hook<AtkUnitManager.Delegates.RefreshAddon> onAddonRefreshHook;
|
|
||||||
private readonly CallHook<AtkUnitBase.Delegates.OnRequestedUpdate> onAddonRequestedUpdateHook;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private AddonLifecycle(TargetSigScanner sigScanner)
|
private AddonLifecycle()
|
||||||
{
|
{
|
||||||
this.address = new AddonLifecycleAddressResolver();
|
this.onInitializeAddonHook = Hook<AtkUnitBase.Delegates.Initialize>.FromAddress((nint)AtkUnitBase.StaticVirtualTablePointer->Initialize, this.OnAddonInitialize);
|
||||||
this.address.Setup(sigScanner);
|
this.onInitializeAddonHook.Enable();
|
||||||
|
|
||||||
this.disallowedReceiveEventAddress = (nint)AtkUnitBase.StaticVirtualTablePointer->ReceiveEvent;
|
|
||||||
|
|
||||||
var refreshAddonAddress = (nint)RaptureAtkUnitManager.StaticVirtualTablePointer->RefreshAddon;
|
|
||||||
|
|
||||||
this.onAddonSetupHook = new AddonSetupHook<AtkUnitBase.Delegates.OnSetup>(this.address.AddonSetup, this.OnAddonSetup);
|
|
||||||
this.onAddonFinalizeHook = Hook<AddonFinalizeDelegate>.FromAddress(this.address.AddonFinalize, this.OnAddonFinalize);
|
|
||||||
this.onAddonDrawHook = new CallHook<AtkUnitBase.Delegates.Draw>(this.address.AddonDraw, this.OnAddonDraw);
|
|
||||||
this.onAddonUpdateHook = new CallHook<AtkUnitBase.Delegates.Update>(this.address.AddonUpdate, this.OnAddonUpdate);
|
|
||||||
this.onAddonRefreshHook = Hook<AtkUnitManager.Delegates.RefreshAddon>.FromAddress(refreshAddonAddress, this.OnAddonRefresh);
|
|
||||||
this.onAddonRequestedUpdateHook = new CallHook<AtkUnitBase.Delegates.OnRequestedUpdate>(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate);
|
|
||||||
|
|
||||||
this.onAddonSetupHook.Enable();
|
|
||||||
this.onAddonFinalizeHook.Enable();
|
|
||||||
this.onAddonDrawHook.Enable();
|
|
||||||
this.onAddonUpdateHook.Enable();
|
|
||||||
this.onAddonRefreshHook.Enable();
|
|
||||||
this.onAddonRequestedUpdateHook.Enable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate void AddonFinalizeDelegate(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a list of all AddonLifecycle ReceiveEvent Listener Hooks.
|
|
||||||
/// </summary>
|
|
||||||
internal List<AddonLifecycleReceiveEventListener> ReceiveEventListeners { get; } = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of all AddonLifecycle Event Listeners.
|
/// Gets a list of all AddonLifecycle Event Listeners.
|
||||||
/// </summary>
|
/// </summary> <br/>
|
||||||
internal List<AddonLifecycleEventListener> EventListeners { get; } = new();
|
/// Mapping is: EventType -> AddonName -> ListenerList
|
||||||
|
internal Dictionary<AddonEvent, Dictionary<string, HashSet<AddonLifecycleEventListener>>> EventListeners { get; } = [];
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IInternalDisposableService.DisposeService()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
this.onAddonSetupHook.Dispose();
|
this.onInitializeAddonHook?.Dispose();
|
||||||
this.onAddonFinalizeHook.Dispose();
|
this.onInitializeAddonHook = null;
|
||||||
this.onAddonDrawHook.Dispose();
|
|
||||||
this.onAddonUpdateHook.Dispose();
|
|
||||||
this.onAddonRefreshHook.Dispose();
|
|
||||||
this.onAddonRequestedUpdateHook.Dispose();
|
|
||||||
|
|
||||||
foreach (var receiveEventListener in this.ReceiveEventListeners)
|
AllocatedTables.ForEach(entry => entry.Dispose());
|
||||||
{
|
AllocatedTables.Clear();
|
||||||
receiveEventListener.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -98,20 +57,20 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
/// <param name="listener">The listener to register.</param>
|
/// <param name="listener">The listener to register.</param>
|
||||||
internal void RegisterListener(AddonLifecycleEventListener listener)
|
internal void RegisterListener(AddonLifecycleEventListener listener)
|
||||||
{
|
{
|
||||||
this.framework.RunOnTick(() =>
|
if (!this.EventListeners.ContainsKey(listener.EventType))
|
||||||
{
|
{
|
||||||
this.EventListeners.Add(listener);
|
if (!this.EventListeners.TryAdd(listener.EventType, []))
|
||||||
|
return;
|
||||||
// If we want receive event messages have an already active addon, enable the receive event hook.
|
}
|
||||||
// If the addon isn't active yet, we'll grab the hook when it sets up.
|
|
||||||
if (listener is { EventType: AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent })
|
// Note: string.Empty is a valid addon name, as that will trigger on any addon for this event type
|
||||||
{
|
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AddonName))
|
||||||
if (this.ReceiveEventListeners.FirstOrDefault(listeners => listeners.AddonNames.Contains(listener.AddonName)) is { } receiveEventListener)
|
{
|
||||||
{
|
if (!this.EventListeners[listener.EventType].TryAdd(listener.AddonName, []))
|
||||||
receiveEventListener.TryEnable();
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
this.EventListeners[listener.EventType][listener.AddonName].Add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -120,27 +79,13 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
/// <param name="listener">The listener to unregister.</param>
|
/// <param name="listener">The listener to unregister.</param>
|
||||||
internal void UnregisterListener(AddonLifecycleEventListener listener)
|
internal void UnregisterListener(AddonLifecycleEventListener listener)
|
||||||
{
|
{
|
||||||
// Set removed state to true immediately, then lazily remove it from the EventListeners list on next Framework Update.
|
if (this.EventListeners.TryGetValue(listener.EventType, out var addonListeners))
|
||||||
listener.Removed = true;
|
|
||||||
|
|
||||||
this.framework.RunOnTick(() =>
|
|
||||||
{
|
{
|
||||||
this.EventListeners.Remove(listener);
|
if (addonListeners.TryGetValue(listener.AddonName, out var addonListener))
|
||||||
|
|
||||||
// If we are disabling an ReceiveEvent listener, check if we should disable the hook.
|
|
||||||
if (listener is { EventType: AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent })
|
|
||||||
{
|
{
|
||||||
// Get the ReceiveEvent Listener for this addon
|
addonListener.Remove(listener);
|
||||||
if (this.ReceiveEventListeners.FirstOrDefault(listeners => listeners.AddonNames.Contains(listener.AddonName)) is { } receiveEventListener)
|
|
||||||
{
|
|
||||||
// If there are no other listeners listening for this event, disable the hook.
|
|
||||||
if (!this.EventListeners.Any(listeners => listeners.AddonName.Contains(listener.AddonName) && listener.EventType is AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent))
|
|
||||||
{
|
|
||||||
receiveEventListener.Disable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -151,226 +96,63 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
/// <param name="blame">What to blame on errors.</param>
|
/// <param name="blame">What to blame on errors.</param>
|
||||||
internal void InvokeListenersSafely(AddonEvent eventType, AddonArgs args, [CallerMemberName] string blame = "")
|
internal void InvokeListenersSafely(AddonEvent eventType, AddonArgs args, [CallerMemberName] string blame = "")
|
||||||
{
|
{
|
||||||
// Do not use linq; this is a high-traffic function, and more heap allocations avoided, the better.
|
// Early return if we don't have any listeners of this type
|
||||||
foreach (var listener in this.EventListeners)
|
if (!this.EventListeners.TryGetValue(eventType, out var addonListeners)) return;
|
||||||
|
|
||||||
|
// Handle listeners for this event type that don't care which addon is triggering it
|
||||||
|
if (addonListeners.TryGetValue(string.Empty, out var globalListeners))
|
||||||
{
|
{
|
||||||
if (listener.EventType != eventType)
|
foreach (var listener in globalListeners)
|
||||||
continue;
|
|
||||||
|
|
||||||
// If the listener is pending removal, and is waiting until the next Framework Update, don't invoke listener.
|
|
||||||
if (listener.Removed)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Match on string.empty for listeners that want events for all addons.
|
|
||||||
if (!string.IsNullOrWhiteSpace(listener.AddonName) && !args.IsAddon(listener.AddonName))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
listener.FunctionDelegate.Invoke(eventType, args);
|
try
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, $"Exception in {blame} during {eventType} invoke.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RegisterReceiveEventHook(AtkUnitBase* addon)
|
|
||||||
{
|
|
||||||
// Hook the addon's ReceiveEvent function here, but only enable the hook if we have an active listener.
|
|
||||||
// Disallows hooking the core internal event handler.
|
|
||||||
var addonName = addon->NameString;
|
|
||||||
var receiveEventAddress = (nint)addon->VirtualTable->ReceiveEvent;
|
|
||||||
if (receiveEventAddress != this.disallowedReceiveEventAddress)
|
|
||||||
{
|
|
||||||
// If we have a ReceiveEvent listener already made for this hook address, add this addon's name to that handler.
|
|
||||||
if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.FunctionAddress == receiveEventAddress) is { } existingListener)
|
|
||||||
{
|
|
||||||
if (!existingListener.AddonNames.Contains(addonName))
|
|
||||||
{
|
{
|
||||||
existingListener.AddonNames.Add(addonName);
|
listener.FunctionDelegate.Invoke(eventType, args);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for global addon event listener.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Else, we have an addon that we don't have the ReceiveEvent for yet, make it.
|
// Handle listeners that are listening for this addon and event type specifically
|
||||||
else
|
if (addonListeners.TryGetValue(args.AddonName, out var addonListener))
|
||||||
|
{
|
||||||
|
foreach (var listener in addonListener)
|
||||||
{
|
{
|
||||||
this.ReceiveEventListeners.Add(new AddonLifecycleReceiveEventListener(this, addonName, receiveEventAddress));
|
try
|
||||||
}
|
|
||||||
|
|
||||||
// If we have an active listener for this addon already, we need to activate this hook.
|
|
||||||
if (this.EventListeners.Any(listener => (listener.EventType is AddonEvent.PostReceiveEvent or AddonEvent.PreReceiveEvent) && listener.AddonName == addonName))
|
|
||||||
{
|
|
||||||
if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.AddonNames.Contains(addonName)) is { } receiveEventListener)
|
|
||||||
{
|
{
|
||||||
receiveEventListener.TryEnable();
|
listener.FunctionDelegate.Invoke(eventType, args);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for specific addon {args.AddonName}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UnregisterReceiveEventHook(string addonName)
|
private void OnAddonInitialize(AtkUnitBase* addon)
|
||||||
{
|
{
|
||||||
// Remove this addons ReceiveEvent Registration
|
try
|
||||||
if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.AddonNames.Contains(addonName)) is { } eventListener)
|
|
||||||
{
|
{
|
||||||
eventListener.AddonNames.Remove(addonName);
|
this.LogInitialize(addon->NameString);
|
||||||
|
|
||||||
// If there are no more listeners let's remove and dispose.
|
// AddonVirtualTable class handles creating the virtual table, and overriding each of the tracked virtual functions
|
||||||
if (eventListener.AddonNames.Count is 0)
|
AllocatedTables.Add(new AddonVirtualTable(addon, this));
|
||||||
{
|
|
||||||
this.ReceiveEventListeners.Remove(eventListener);
|
|
||||||
eventListener.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Exception in AddonLifecycle during OnAddonInitialize.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onInitializeAddonHook!.Original(addon);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAddonSetup(AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
[Conditional("DEBUG")]
|
||||||
|
private void LogInitialize(string addonName)
|
||||||
{
|
{
|
||||||
try
|
Log.Debug($"Initializing {addonName}");
|
||||||
{
|
|
||||||
this.RegisterReceiveEventHook(addon);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Exception in OnAddonSetup ReceiveEvent Registration.");
|
|
||||||
}
|
|
||||||
|
|
||||||
using var returner = this.argsPool.Rent(out AddonSetupArgs arg);
|
|
||||||
arg.Clear();
|
|
||||||
arg.Addon = (nint)addon;
|
|
||||||
arg.AtkValueCount = valueCount;
|
|
||||||
arg.AtkValues = (nint)values;
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PreSetup, arg);
|
|
||||||
valueCount = arg.AtkValueCount;
|
|
||||||
values = (AtkValue*)arg.AtkValues;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
addon->OnSetup(valueCount, values);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Caught exception when calling original AddonSetup. This may be a bug in the game or another plugin hooking this method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PostSetup, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var addonName = atkUnitBase[0]->NameString;
|
|
||||||
this.UnregisterReceiveEventHook(addonName);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Exception in OnAddonFinalize ReceiveEvent Removal.");
|
|
||||||
}
|
|
||||||
|
|
||||||
using var returner = this.argsPool.Rent(out AddonFinalizeArgs arg);
|
|
||||||
arg.Clear();
|
|
||||||
arg.Addon = (nint)atkUnitBase[0];
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PreFinalize, arg);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.onAddonFinalizeHook.Original(unitManager, atkUnitBase);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Caught exception when calling original AddonFinalize. This may be a bug in the game or another plugin hooking this method.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAddonDraw(AtkUnitBase* addon)
|
|
||||||
{
|
|
||||||
using var returner = this.argsPool.Rent(out AddonDrawArgs arg);
|
|
||||||
arg.Clear();
|
|
||||||
arg.Addon = (nint)addon;
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PreDraw, arg);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
addon->Draw();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Caught exception when calling original AddonDraw. This may be a bug in the game or another plugin hooking this method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PostDraw, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAddonUpdate(AtkUnitBase* addon, float delta)
|
|
||||||
{
|
|
||||||
using var returner = this.argsPool.Rent(out AddonUpdateArgs arg);
|
|
||||||
arg.Clear();
|
|
||||||
arg.Addon = (nint)addon;
|
|
||||||
arg.TimeDeltaInternal = delta;
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PreUpdate, arg);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
addon->Update(delta);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Caught exception when calling original AddonUpdate. This may be a bug in the game or another plugin hooking this method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PostUpdate, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool OnAddonRefresh(AtkUnitManager* thisPtr, AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
|
||||||
{
|
|
||||||
var result = false;
|
|
||||||
|
|
||||||
using var returner = this.argsPool.Rent(out AddonRefreshArgs arg);
|
|
||||||
arg.Clear();
|
|
||||||
arg.Addon = (nint)addon;
|
|
||||||
arg.AtkValueCount = valueCount;
|
|
||||||
arg.AtkValues = (nint)values;
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PreRefresh, arg);
|
|
||||||
valueCount = arg.AtkValueCount;
|
|
||||||
values = (AtkValue*)arg.AtkValues;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result = this.onAddonRefreshHook.Original(thisPtr, addon, valueCount, values);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Caught exception when calling original AddonRefresh. This may be a bug in the game or another plugin hooking this method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PostRefresh, arg);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
|
||||||
{
|
|
||||||
using var returner = this.argsPool.Rent(out AddonRequestedUpdateArgs arg);
|
|
||||||
arg.Clear();
|
|
||||||
arg.Addon = (nint)addon;
|
|
||||||
arg.NumberArrayData = (nint)numberArrayData;
|
|
||||||
arg.StringArrayData = (nint)stringArrayData;
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, arg);
|
|
||||||
numberArrayData = (NumberArrayData**)arg.NumberArrayData;
|
|
||||||
stringArrayData = (StringArrayData**)arg.StringArrayData;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
addon->OnRequestedUpdate(numberArrayData, stringArrayData);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Caught exception when calling original AddonRequestedUpdate. This may be a bug in the game or another plugin hooking this method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PostRequestedUpdate, arg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -387,7 +169,7 @@ internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLi
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly AddonLifecycle addonLifecycleService = Service<AddonLifecycle>.Get();
|
private readonly AddonLifecycle addonLifecycleService = Service<AddonLifecycle>.Get();
|
||||||
|
|
||||||
private readonly List<AddonLifecycleEventListener> eventListeners = new();
|
private readonly List<AddonLifecycleEventListener> eventListeners = [];
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IInternalDisposableService.DisposeService()
|
void IInternalDisposableService.DisposeService()
|
||||||
|
|
@ -458,7 +240,7 @@ internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLi
|
||||||
this.eventListeners.RemoveAll(entry =>
|
this.eventListeners.RemoveAll(entry =>
|
||||||
{
|
{
|
||||||
if (entry.FunctionDelegate != handler) return false;
|
if (entry.FunctionDelegate != handler) return false;
|
||||||
|
|
||||||
this.addonLifecycleService.UnregisterListener(entry);
|
this.addonLifecycleService.UnregisterListener(entry);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Lifecycle;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// AddonLifecycleService memory address resolver.
|
|
||||||
/// </summary>
|
|
||||||
internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the addon setup hook invoked by the AtkUnitManager.
|
|
||||||
/// There are two callsites for this vFunc, we need to hook both of them to catch both normal UI and special UI cases like dialogue.
|
|
||||||
/// This is called for a majority of all addon OnSetup's.
|
|
||||||
/// </summary>
|
|
||||||
public nint AddonSetup { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the other addon setup hook invoked by the AtkUnitManager.
|
|
||||||
/// There are two callsites for this vFunc, we need to hook both of them to catch both normal UI and special UI cases like dialogue.
|
|
||||||
/// This seems to be called rarely for specific addons.
|
|
||||||
/// </summary>
|
|
||||||
public nint AddonSetup2 { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the addon finalize hook invoked by the AtkUnitManager.
|
|
||||||
/// </summary>
|
|
||||||
public nint AddonFinalize { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the addon draw hook invoked by virtual function call.
|
|
||||||
/// </summary>
|
|
||||||
public nint AddonDraw { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the addon update hook invoked by virtual function call.
|
|
||||||
/// </summary>
|
|
||||||
public nint AddonUpdate { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the addon onRequestedUpdate hook invoked by virtual function call.
|
|
||||||
/// </summary>
|
|
||||||
public nint AddonOnRequestedUpdate { 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(ISigScanner sig)
|
|
||||||
{
|
|
||||||
this.AddonSetup = sig.ScanText("4C 8B 88 ?? ?? ?? ?? 66 44 39 BB");
|
|
||||||
this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 83 EF 01 75 D5");
|
|
||||||
this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C4 48 81 EF ?? ?? ?? ?? 48 83 ED 01");
|
|
||||||
this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF ?? ?? ?? ?? 45 33 D2");
|
|
||||||
this.AddonOnRequestedUpdate = sig.ScanText("FF 90 A0 01 00 00 48 8B 5C 24 30");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -25,17 +25,12 @@ internal class AddonLifecycleEventListener
|
||||||
/// string.Empty if it wants to be called for any addon.
|
/// string.Empty if it wants to be called for any addon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AddonName { get; init; }
|
public string AddonName { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this event has been unregistered.
|
|
||||||
/// </summary>
|
|
||||||
public bool Removed { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the event type this listener is looking for.
|
/// Gets the event type this listener is looking for.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AddonEvent EventType { get; init; }
|
public AddonEvent EventType { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the delegate this listener invokes.
|
/// Gets the delegate this listener invokes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Dalamud.Logging.Internal;
|
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Lifecycle;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This class is a helper for tracking and invoking listener delegates for Addon_OnReceiveEvent.
|
|
||||||
/// Multiple addons may use the same ReceiveEvent function, this helper makes sure that those addon events are handled properly.
|
|
||||||
/// </summary>
|
|
||||||
internal unsafe class AddonLifecycleReceiveEventListener : IDisposable
|
|
||||||
{
|
|
||||||
private static readonly ModuleLog Log = new("AddonLifecycle");
|
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
|
||||||
private readonly AddonLifecyclePooledArgs argsPool = Service<AddonLifecyclePooledArgs>.Get();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AddonLifecycleReceiveEventListener"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="service">AddonLifecycle service instance.</param>
|
|
||||||
/// <param name="addonName">Initial Addon Requesting this listener.</param>
|
|
||||||
/// <param name="receiveEventAddress">Address of Addon's ReceiveEvent function.</param>
|
|
||||||
internal AddonLifecycleReceiveEventListener(AddonLifecycle service, string addonName, nint receiveEventAddress)
|
|
||||||
{
|
|
||||||
this.AddonLifecycle = service;
|
|
||||||
this.AddonNames = [addonName];
|
|
||||||
this.FunctionAddress = receiveEventAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the list of addons that use this receive event hook.
|
|
||||||
/// </summary>
|
|
||||||
public List<string> AddonNames { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the ReceiveEvent function as provided by the vtable on setup.
|
|
||||||
/// </summary>
|
|
||||||
public nint FunctionAddress { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the contained hook for these addons.
|
|
||||||
/// </summary>
|
|
||||||
public Hook<AtkUnitBase.Delegates.ReceiveEvent>? Hook { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Reference to AddonLifecycle service instance.
|
|
||||||
/// </summary>
|
|
||||||
private AddonLifecycle AddonLifecycle { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Try to hook and enable this receive event handler.
|
|
||||||
/// </summary>
|
|
||||||
public void TryEnable()
|
|
||||||
{
|
|
||||||
this.Hook ??= Hook<AtkUnitBase.Delegates.ReceiveEvent>.FromAddress(this.FunctionAddress, this.OnReceiveEvent);
|
|
||||||
this.Hook?.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disable the hook for this receive event handler.
|
|
||||||
/// </summary>
|
|
||||||
public void Disable()
|
|
||||||
{
|
|
||||||
this.Hook?.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
this.Hook?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnReceiveEvent(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
|
|
||||||
{
|
|
||||||
// Check that we didn't get here through a call to another addons handler.
|
|
||||||
var addonName = addon->NameString;
|
|
||||||
if (!this.AddonNames.Contains(addonName))
|
|
||||||
{
|
|
||||||
this.Hook!.Original(addon, eventType, eventParam, atkEvent, atkEventData);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var returner = this.argsPool.Rent(out AddonReceiveEventArgs arg);
|
|
||||||
arg.Clear();
|
|
||||||
arg.Addon = (nint)addon;
|
|
||||||
arg.AtkEventType = (byte)eventType;
|
|
||||||
arg.EventParam = eventParam;
|
|
||||||
arg.AtkEvent = (IntPtr)atkEvent;
|
|
||||||
arg.Data = (nint)atkEventData;
|
|
||||||
this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PreReceiveEvent, arg);
|
|
||||||
eventType = (AtkEventType)arg.AtkEventType;
|
|
||||||
eventParam = arg.EventParam;
|
|
||||||
atkEvent = (AtkEvent*)arg.AtkEvent;
|
|
||||||
atkEventData = (AtkEventData*)arg.Data;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.Hook!.Original(addon, eventType, eventParam, atkEvent, atkEventData);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Caught exception when calling original AddonReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PostReceiveEvent, arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using Reloaded.Hooks.Definitions;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Lifecycle;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents a callsite hook used to replace the address of the OnSetup function in r9.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Delegate signature for this hook.</typeparam>
|
|
||||||
internal class AddonSetupHook<T> : IDisposable where T : Delegate
|
|
||||||
{
|
|
||||||
private readonly Reloaded.Hooks.AsmHook asmHook;
|
|
||||||
|
|
||||||
private T? detour;
|
|
||||||
private bool activated;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AddonSetupHook{T}"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Address of the instruction to replace.</param>
|
|
||||||
/// <param name="detour">Delegate to invoke.</param>
|
|
||||||
internal AddonSetupHook(nint address, T detour)
|
|
||||||
{
|
|
||||||
this.detour = detour;
|
|
||||||
|
|
||||||
var detourPtr = Marshal.GetFunctionPointerForDelegate(this.detour);
|
|
||||||
var code = new[]
|
|
||||||
{
|
|
||||||
"use64",
|
|
||||||
$"mov r9, 0x{detourPtr:X8}",
|
|
||||||
};
|
|
||||||
|
|
||||||
var opt = new AsmHookOptions
|
|
||||||
{
|
|
||||||
PreferRelativeJump = true,
|
|
||||||
Behaviour = Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour.DoNotExecuteOriginal,
|
|
||||||
MaxOpcodeSize = 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.asmHook = new Reloaded.Hooks.AsmHook(code, (nuint)address, opt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the hook is enabled.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsEnabled => this.asmHook.IsEnabled;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts intercepting a call to the function.
|
|
||||||
/// </summary>
|
|
||||||
public void Enable()
|
|
||||||
{
|
|
||||||
if (!this.activated)
|
|
||||||
{
|
|
||||||
this.activated = true;
|
|
||||||
this.asmHook.Activate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.asmHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops intercepting a call to the function.
|
|
||||||
/// </summary>
|
|
||||||
public void Disable()
|
|
||||||
{
|
|
||||||
this.asmHook.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove a hook from the current process.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
this.asmHook.Disable();
|
|
||||||
this.detour = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
638
Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs
Normal file
638
Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs
Normal file
|
|
@ -0,0 +1,638 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a class that holds references to an addons original and modified virtual table entries.
|
||||||
|
/// </summary>
|
||||||
|
internal unsafe class AddonVirtualTable : IDisposable
|
||||||
|
{
|
||||||
|
// This need to be at minimum the largest virtual table size of all addons
|
||||||
|
// Copying extra entries is not problematic, and is considered safe.
|
||||||
|
private const int VirtualTableEntryCount = 200;
|
||||||
|
|
||||||
|
private const bool EnableLogging = false;
|
||||||
|
|
||||||
|
private static readonly ModuleLog Log = new("LifecycleVT");
|
||||||
|
|
||||||
|
private readonly AddonLifecycle lifecycleService;
|
||||||
|
|
||||||
|
// Each addon gets its own set of args that are used to mutate the original call when used in pre-calls
|
||||||
|
private readonly AddonSetupArgs setupArgs = new();
|
||||||
|
private readonly AddonArgs finalizeArgs = new();
|
||||||
|
private readonly AddonArgs drawArgs = new();
|
||||||
|
private readonly AddonArgs updateArgs = new();
|
||||||
|
private readonly AddonRefreshArgs refreshArgs = new();
|
||||||
|
private readonly AddonRequestedUpdateArgs requestedUpdateArgs = new();
|
||||||
|
private readonly AddonReceiveEventArgs receiveEventArgs = new();
|
||||||
|
private readonly AddonArgs openArgs = new();
|
||||||
|
private readonly AddonCloseArgs closeArgs = new();
|
||||||
|
private readonly AddonShowArgs showArgs = new();
|
||||||
|
private readonly AddonHideArgs hideArgs = new();
|
||||||
|
private readonly AddonArgs onMoveArgs = new();
|
||||||
|
private readonly AddonArgs onMouseOverArgs = new();
|
||||||
|
private readonly AddonArgs onMouseOutArgs = new();
|
||||||
|
private readonly AddonArgs focusArgs = new();
|
||||||
|
|
||||||
|
private readonly AtkUnitBase* atkUnitBase;
|
||||||
|
|
||||||
|
private readonly AtkUnitBase.AtkUnitBaseVirtualTable* originalVirtualTable;
|
||||||
|
private readonly AtkUnitBase.AtkUnitBaseVirtualTable* modifiedVirtualTable;
|
||||||
|
|
||||||
|
// Pinned Function Delegates, as these functions get assigned to an unmanaged virtual table,
|
||||||
|
// the CLR needs to know they are in use, or it will invalidate them causing random crashing.
|
||||||
|
private readonly AtkUnitBase.Delegates.Dtor destructorFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.OnSetup onSetupFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Finalizer finalizerFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Draw drawFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Update updateFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.OnRefresh onRefreshFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.OnRequestedUpdate onRequestedUpdateFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.ReceiveEvent onReceiveEventFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Open openFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Close closeFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Show showFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Hide hideFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.OnMove onMoveFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.OnMouseOver onMouseOverFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.OnMouseOut onMouseOutFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Focus focusFunction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonVirtualTable"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addon">AtkUnitBase* for the addon to replace the table of.</param>
|
||||||
|
/// <param name="lifecycleService">Reference to AddonLifecycle service to callback and invoke listeners.</param>
|
||||||
|
internal AddonVirtualTable(AtkUnitBase* addon, AddonLifecycle lifecycleService)
|
||||||
|
{
|
||||||
|
this.atkUnitBase = addon;
|
||||||
|
this.lifecycleService = lifecycleService;
|
||||||
|
|
||||||
|
// Save original virtual table
|
||||||
|
this.originalVirtualTable = addon->VirtualTable;
|
||||||
|
|
||||||
|
// Create copy of original table
|
||||||
|
// Note this will copy any derived/overriden functions that this specific addon has.
|
||||||
|
// Note: currently there are 73 virtual functions, but there's no harm in copying more for when they add new virtual functions to the game
|
||||||
|
this.modifiedVirtualTable = (AtkUnitBase.AtkUnitBaseVirtualTable*)IMemorySpace.GetUISpace()->Malloc(0x8 * VirtualTableEntryCount, 8);
|
||||||
|
NativeMemory.Copy(addon->VirtualTable, this.modifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||||
|
|
||||||
|
// Overwrite the addons existing virtual table with our own
|
||||||
|
addon->VirtualTable = this.modifiedVirtualTable;
|
||||||
|
|
||||||
|
// Pin each of our listener functions
|
||||||
|
this.destructorFunction = this.OnAddonDestructor;
|
||||||
|
this.onSetupFunction = this.OnAddonSetup;
|
||||||
|
this.finalizerFunction = this.OnAddonFinalize;
|
||||||
|
this.drawFunction = this.OnAddonDraw;
|
||||||
|
this.updateFunction = this.OnAddonUpdate;
|
||||||
|
this.onRefreshFunction = this.OnAddonRefresh;
|
||||||
|
this.onRequestedUpdateFunction = this.OnRequestedUpdate;
|
||||||
|
this.onReceiveEventFunction = this.OnAddonReceiveEvent;
|
||||||
|
this.openFunction = this.OnAddonOpen;
|
||||||
|
this.closeFunction = this.OnAddonClose;
|
||||||
|
this.showFunction = this.OnAddonShow;
|
||||||
|
this.hideFunction = this.OnAddonHide;
|
||||||
|
this.onMoveFunction = this.OnAddonMove;
|
||||||
|
this.onMouseOverFunction = this.OnAddonMouseOver;
|
||||||
|
this.onMouseOutFunction = this.OnAddonMouseOut;
|
||||||
|
this.focusFunction = this.OnAddonFocus;
|
||||||
|
|
||||||
|
// Overwrite specific virtual table entries
|
||||||
|
this.modifiedVirtualTable->Dtor = (delegate* unmanaged<AtkUnitBase*, byte, AtkEventListener*>)Marshal.GetFunctionPointerForDelegate(this.destructorFunction);
|
||||||
|
this.modifiedVirtualTable->OnSetup = (delegate* unmanaged<AtkUnitBase*, uint, AtkValue*, void>)Marshal.GetFunctionPointerForDelegate(this.onSetupFunction);
|
||||||
|
this.modifiedVirtualTable->Finalizer = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.finalizerFunction);
|
||||||
|
this.modifiedVirtualTable->Draw = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.drawFunction);
|
||||||
|
this.modifiedVirtualTable->Update = (delegate* unmanaged<AtkUnitBase*, float, void>)Marshal.GetFunctionPointerForDelegate(this.updateFunction);
|
||||||
|
this.modifiedVirtualTable->OnRefresh = (delegate* unmanaged<AtkUnitBase*, uint, AtkValue*, bool>)Marshal.GetFunctionPointerForDelegate(this.onRefreshFunction);
|
||||||
|
this.modifiedVirtualTable->OnRequestedUpdate = (delegate* unmanaged<AtkUnitBase*, NumberArrayData**, StringArrayData**, void>)Marshal.GetFunctionPointerForDelegate(this.onRequestedUpdateFunction);
|
||||||
|
this.modifiedVirtualTable->ReceiveEvent = (delegate* unmanaged<AtkUnitBase*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)Marshal.GetFunctionPointerForDelegate(this.onReceiveEventFunction);
|
||||||
|
this.modifiedVirtualTable->Open = (delegate* unmanaged<AtkUnitBase*, uint, bool>)Marshal.GetFunctionPointerForDelegate(this.openFunction);
|
||||||
|
this.modifiedVirtualTable->Close = (delegate* unmanaged<AtkUnitBase*, bool, bool>)Marshal.GetFunctionPointerForDelegate(this.closeFunction);
|
||||||
|
this.modifiedVirtualTable->Show = (delegate* unmanaged<AtkUnitBase*, bool, uint, void>)Marshal.GetFunctionPointerForDelegate(this.showFunction);
|
||||||
|
this.modifiedVirtualTable->Hide = (delegate* unmanaged<AtkUnitBase*, bool, bool, uint, void>)Marshal.GetFunctionPointerForDelegate(this.hideFunction);
|
||||||
|
this.modifiedVirtualTable->OnMove = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMoveFunction);
|
||||||
|
this.modifiedVirtualTable->OnMouseOver = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMouseOverFunction);
|
||||||
|
this.modifiedVirtualTable->OnMouseOut = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMouseOutFunction);
|
||||||
|
this.modifiedVirtualTable->Focus = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.focusFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Ensure restoration is done atomically.
|
||||||
|
Interlocked.Exchange(ref *(nint*)&this.atkUnitBase->VirtualTable, (nint)this.originalVirtualTable);
|
||||||
|
IMemorySpace.Free(this.modifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AtkEventListener* OnAddonDestructor(AtkUnitBase* thisPtr, byte freeFlags)
|
||||||
|
{
|
||||||
|
AtkEventListener* result = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = this.originalVirtualTable->Dtor(thisPtr, freeFlags);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Dtor. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((freeFlags & 1) == 1)
|
||||||
|
{
|
||||||
|
IMemorySpace.Free(this.modifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||||
|
AddonLifecycle.AllocatedTables.Remove(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonDestructor.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonSetup(AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.setupArgs.Addon = addon;
|
||||||
|
this.setupArgs.AtkValueCount = valueCount;
|
||||||
|
this.setupArgs.AtkValues = (nint)values;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreSetup, this.setupArgs);
|
||||||
|
|
||||||
|
valueCount = this.setupArgs.AtkValueCount;
|
||||||
|
values = (AtkValue*)this.setupArgs.AtkValues;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->OnSetup(addon, valueCount, values);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnSetup. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostSetup, this.setupArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonSetup.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonFinalize(AtkUnitBase* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.finalizeArgs.Addon = thisPtr;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreFinalize, this.finalizeArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->Finalizer(thisPtr);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Finalizer. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonFinalize.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonDraw(AtkUnitBase* addon)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.drawArgs.Addon = addon;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreDraw, this.drawArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->Draw(addon);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Draw. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostDraw, this.drawArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonDraw.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonUpdate(AtkUnitBase* addon, float delta)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.updateArgs.Addon = addon;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreUpdate, this.updateArgs);
|
||||||
|
|
||||||
|
// Note: Do not pass or allow manipulation of delta.
|
||||||
|
// It's realistically not something that should be needed.
|
||||||
|
// And even if someone does, they are encouraged to hook Update themselves.
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->Update(addon, delta);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Update. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostUpdate, this.updateArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonUpdate.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool OnAddonRefresh(AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
||||||
|
{
|
||||||
|
var result = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.refreshArgs.Addon = addon;
|
||||||
|
this.refreshArgs.AtkValueCount = valueCount;
|
||||||
|
this.refreshArgs.AtkValues = (nint)values;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreRefresh, this.refreshArgs);
|
||||||
|
|
||||||
|
valueCount = this.refreshArgs.AtkValueCount;
|
||||||
|
values = (AtkValue*)this.refreshArgs.AtkValues;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = this.originalVirtualTable->OnRefresh(addon, valueCount, values);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnRefresh. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostRefresh, this.refreshArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonRefresh.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.requestedUpdateArgs.Addon = addon;
|
||||||
|
this.requestedUpdateArgs.NumberArrayData = (nint)numberArrayData;
|
||||||
|
this.requestedUpdateArgs.StringArrayData = (nint)stringArrayData;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, this.requestedUpdateArgs);
|
||||||
|
|
||||||
|
numberArrayData = (NumberArrayData**)this.requestedUpdateArgs.NumberArrayData;
|
||||||
|
stringArrayData = (StringArrayData**)this.requestedUpdateArgs.StringArrayData;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->OnRequestedUpdate(addon, numberArrayData, stringArrayData);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnRequestedUpdate. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostRequestedUpdate, this.requestedUpdateArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnRequestedUpdate.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonReceiveEvent(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.receiveEventArgs.Addon = (nint)addon;
|
||||||
|
this.receiveEventArgs.AtkEventType = (byte)eventType;
|
||||||
|
this.receiveEventArgs.EventParam = eventParam;
|
||||||
|
this.receiveEventArgs.AtkEvent = (IntPtr)atkEvent;
|
||||||
|
this.receiveEventArgs.AtkEventData = (nint)atkEventData;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreReceiveEvent, this.receiveEventArgs);
|
||||||
|
|
||||||
|
eventType = (AtkEventType)this.receiveEventArgs.AtkEventType;
|
||||||
|
eventParam = this.receiveEventArgs.EventParam;
|
||||||
|
atkEvent = (AtkEvent*)this.receiveEventArgs.AtkEvent;
|
||||||
|
atkEventData = (AtkEventData*)this.receiveEventArgs.AtkEventData;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->ReceiveEvent(addon, eventType, eventParam, atkEvent, atkEventData);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon ReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostReceiveEvent, this.receiveEventArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonReceiveEvent.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool OnAddonOpen(AtkUnitBase* thisPtr, uint depthLayer)
|
||||||
|
{
|
||||||
|
var result = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.openArgs.Addon = thisPtr;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreOpen, this.openArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = this.originalVirtualTable->Open(thisPtr, depthLayer);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Open. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostOpen, this.openArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonOpen.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool OnAddonClose(AtkUnitBase* thisPtr, bool fireCallback)
|
||||||
|
{
|
||||||
|
var result = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.closeArgs.Addon = thisPtr;
|
||||||
|
this.closeArgs.FireCallback = fireCallback;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreClose, this.closeArgs);
|
||||||
|
|
||||||
|
fireCallback = this.closeArgs.FireCallback;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = this.originalVirtualTable->Close(thisPtr, fireCallback);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Close. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostClose, this.closeArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonClose.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonShow(AtkUnitBase* thisPtr, bool silenceOpenSoundEffect, uint unsetShowHideFlags)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.showArgs.Addon = thisPtr;
|
||||||
|
this.showArgs.SilenceOpenSoundEffect = silenceOpenSoundEffect;
|
||||||
|
this.showArgs.UnsetShowHideFlags = unsetShowHideFlags;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreShow, this.showArgs);
|
||||||
|
|
||||||
|
silenceOpenSoundEffect = this.showArgs.SilenceOpenSoundEffect;
|
||||||
|
unsetShowHideFlags = this.showArgs.UnsetShowHideFlags;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->Show(thisPtr, silenceOpenSoundEffect, unsetShowHideFlags);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Show. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostShow, this.showArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonShow.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonHide(AtkUnitBase* thisPtr, bool unkBool, bool callHideCallback, uint setShowHideFlags)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.hideArgs.Addon = thisPtr;
|
||||||
|
this.hideArgs.UnknownBool = unkBool;
|
||||||
|
this.hideArgs.CallHideCallback = callHideCallback;
|
||||||
|
this.hideArgs.SetShowHideFlags = setShowHideFlags;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreHide, this.hideArgs);
|
||||||
|
|
||||||
|
unkBool = this.hideArgs.UnknownBool;
|
||||||
|
callHideCallback = this.hideArgs.CallHideCallback;
|
||||||
|
setShowHideFlags = this.hideArgs.SetShowHideFlags;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->Hide(thisPtr, unkBool, callHideCallback, setShowHideFlags);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Hide. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostHide, this.hideArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonHide.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonMove(AtkUnitBase* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.onMoveArgs.Addon = thisPtr;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMove, this.onMoveArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->OnMove(thisPtr);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnMove. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMove, this.onMoveArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMove.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonMouseOver(AtkUnitBase* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.onMouseOverArgs.Addon = thisPtr;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMouseOver, this.onMouseOverArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->OnMouseOver(thisPtr);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnMouseOver. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMouseOver, this.onMouseOverArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMouseOver.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonMouseOut(AtkUnitBase* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.onMouseOutArgs.Addon = thisPtr;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMouseOut, this.onMouseOutArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->OnMouseOut(thisPtr);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnMouseOut. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMouseOut, this.onMouseOutArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMouseOut.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonFocus(AtkUnitBase* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.focusArgs.Addon = thisPtr;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreFocus, this.focusArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->Focus(thisPtr);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Focus. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostFocus, this.focusArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonFocus.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
private void LogEvent(bool loggingEnabled, [CallerMemberName] string caller = "")
|
||||||
|
{
|
||||||
|
if (loggingEnabled)
|
||||||
|
{
|
||||||
|
// Manually disable the really spammy log events, you can comment this out if you need to debug them.
|
||||||
|
if (caller is "OnAddonUpdate" or "OnAddonDraw" or "OnAddonReceiveEvent" or "OnRequestedUpdate")
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log.Debug($"[{caller}]: {this.atkUnitBase->NameString}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -150,7 +150,7 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[Api13ToDo("Maybe make this config scoped to internal name?")]
|
[Api14ToDo("Maybe make this config scoped to internal name?")]
|
||||||
public bool UserHidden => this.configuration.DtrIgnore?.Contains(this.Title) ?? false;
|
public bool UserHidden => this.configuration.DtrIgnore?.Contains(this.Title) ?? false;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using Reloaded.Hooks.Definitions;
|
|
||||||
|
|
||||||
namespace Dalamud.Hooking.Internal;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents a callsite hook. Only the specific address's instructions are replaced with this hook.
|
|
||||||
/// This is a destructive operation, no other callsite hooks can coexist at the same address.
|
|
||||||
///
|
|
||||||
/// There's no .Original for this hook type.
|
|
||||||
/// This is only intended for be for functions where the parameters provided allow you to invoke the original call.
|
|
||||||
///
|
|
||||||
/// This class was specifically added for hooking virtual function callsites.
|
|
||||||
/// Only the specific callsite hooked is modified, if the game calls the virtual function from other locations this hook will not be triggered.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Delegate signature for this hook.</typeparam>
|
|
||||||
internal class CallHook<T> : IDalamudHook where T : Delegate
|
|
||||||
{
|
|
||||||
private readonly Reloaded.Hooks.AsmHook asmHook;
|
|
||||||
|
|
||||||
private T? detour;
|
|
||||||
private bool activated;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="CallHook{T}"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Address of the instruction to replace.</param>
|
|
||||||
/// <param name="detour">Delegate to invoke.</param>
|
|
||||||
internal CallHook(nint address, T detour)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(detour);
|
|
||||||
|
|
||||||
this.detour = detour;
|
|
||||||
this.Address = address;
|
|
||||||
|
|
||||||
var detourPtr = Marshal.GetFunctionPointerForDelegate(this.detour);
|
|
||||||
var code = new[]
|
|
||||||
{
|
|
||||||
"use64",
|
|
||||||
$"mov rax, 0x{detourPtr:X8}",
|
|
||||||
"call rax",
|
|
||||||
};
|
|
||||||
|
|
||||||
var opt = new AsmHookOptions
|
|
||||||
{
|
|
||||||
PreferRelativeJump = true,
|
|
||||||
Behaviour = Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour.DoNotExecuteOriginal,
|
|
||||||
MaxOpcodeSize = 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.asmHook = new Reloaded.Hooks.AsmHook(code, (nuint)address, opt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the hook is enabled.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsEnabled => this.asmHook.IsEnabled;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public IntPtr Address { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public string BackendName => "Reloaded AsmHook";
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public bool IsDisposed => this.detour == null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts intercepting a call to the function.
|
|
||||||
/// </summary>
|
|
||||||
public void Enable()
|
|
||||||
{
|
|
||||||
if (!this.activated)
|
|
||||||
{
|
|
||||||
this.activated = true;
|
|
||||||
this.asmHook.Activate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.asmHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops intercepting a call to the function.
|
|
||||||
/// </summary>
|
|
||||||
public void Disable()
|
|
||||||
{
|
|
||||||
this.asmHook.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove a hook from the current process.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
this.asmHook.Disable();
|
|
||||||
this.detour = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -48,7 +48,7 @@ public abstract class Easing
|
||||||
/// Gets the current value of the animation, following unclamped logic.
|
/// Gets the current value of the animation, following unclamped logic.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete($"This field has been deprecated. Use either {nameof(ValueClamped)} or {nameof(ValueUnclamped)} instead.", true)]
|
[Obsolete($"This field has been deprecated. Use either {nameof(ValueClamped)} or {nameof(ValueUnclamped)} instead.", true)]
|
||||||
[Api13ToDo("Map this field to ValueClamped, probably.")]
|
[Api14ToDo("Map this field to ValueClamped, probably.")]
|
||||||
public double Value => this.ValueUnclamped;
|
public double Value => this.ValueUnclamped;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Game.Addon.Lifecycle;
|
using Dalamud.Game.Addon.Lifecycle;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||||
|
|
||||||
|
|
@ -48,97 +46,38 @@ public class AddonLifecycleWidget : IDataWindowWidget
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader("Listeners"u8))
|
foreach (var (eventType, addonListeners) in this.AddonLifecycle.EventListeners)
|
||||||
{
|
{
|
||||||
ImGui.Indent();
|
using var eventId = ImRaii.PushId(eventType.ToString());
|
||||||
this.DrawEventListeners();
|
|
||||||
ImGui.Unindent();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader("ReceiveEvent Hooks"u8))
|
|
||||||
{
|
|
||||||
ImGui.Indent();
|
|
||||||
this.DrawReceiveEventHooks();
|
|
||||||
ImGui.Unindent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawEventListeners()
|
|
||||||
{
|
|
||||||
if (!this.Ready) return;
|
|
||||||
|
|
||||||
foreach (var eventType in Enum.GetValues<AddonEvent>())
|
|
||||||
{
|
|
||||||
if (ImGui.CollapsingHeader(eventType.ToString()))
|
if (ImGui.CollapsingHeader(eventType.ToString()))
|
||||||
{
|
{
|
||||||
ImGui.Indent();
|
using var eventIndent = ImRaii.PushIndent();
|
||||||
var listeners = this.AddonLifecycle.EventListeners.Where(listener => listener.EventType == eventType).ToList();
|
|
||||||
|
|
||||||
if (listeners.Count == 0)
|
if (addonListeners.Count == 0)
|
||||||
{
|
{
|
||||||
ImGui.Text("No Listeners Registered for Event"u8);
|
ImGui.Text("No Addons Registered for Event"u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.BeginTable("AddonLifecycleListenersTable"u8, 2))
|
foreach (var (addonName, listeners) in addonListeners)
|
||||||
{
|
{
|
||||||
ImGui.TableSetupColumn("##AddonName"u8, ImGuiTableColumnFlags.WidthFixed, 100.0f * ImGuiHelpers.GlobalScale);
|
using var addonId = ImRaii.PushId(addonName);
|
||||||
ImGui.TableSetupColumn("##MethodInvoke"u8, ImGuiTableColumnFlags.WidthStretch);
|
|
||||||
|
|
||||||
foreach (var listener in listeners)
|
if (ImGui.CollapsingHeader(addonName.IsNullOrEmpty() ? "GLOBAL" : addonName))
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
using var addonIndent = ImRaii.PushIndent();
|
||||||
ImGui.Text(listener.AddonName is "" ? "GLOBAL" : listener.AddonName);
|
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
if (listeners.Count == 0)
|
||||||
ImGui.Text($"{listener.FunctionDelegate.Method.DeclaringType?.FullName ?? "Unknown Declaring Type"}::{listener.FunctionDelegate.Method.Name}");
|
{
|
||||||
|
ImGui.Text("No Listeners Registered for Event"u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var listener in listeners)
|
||||||
|
{
|
||||||
|
ImGui.Text($"{listener.FunctionDelegate.Method.DeclaringType?.FullName ?? "Unknown Declaring Type"}::{listener.FunctionDelegate.Method.Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Unindent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawReceiveEventHooks()
|
|
||||||
{
|
|
||||||
if (!this.Ready) return;
|
|
||||||
|
|
||||||
var listeners = this.AddonLifecycle.ReceiveEventListeners;
|
|
||||||
|
|
||||||
if (listeners.Count == 0)
|
|
||||||
{
|
|
||||||
ImGui.Text("No ReceiveEvent Hooks are Registered"u8);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var receiveEventListener in this.AddonLifecycle.ReceiveEventListeners)
|
|
||||||
{
|
|
||||||
if (ImGui.CollapsingHeader(string.Join(", ", receiveEventListener.AddonNames)))
|
|
||||||
{
|
|
||||||
ImGui.Columns(2);
|
|
||||||
|
|
||||||
var functionAddress = receiveEventListener.FunctionAddress;
|
|
||||||
|
|
||||||
ImGui.Text("Hook Address"u8);
|
|
||||||
ImGui.NextColumn();
|
|
||||||
ImGui.Text($"0x{functionAddress:X} (ffxiv_dx11.exe+{functionAddress - Process.GetCurrentProcess().MainModule!.BaseAddress:X})");
|
|
||||||
|
|
||||||
ImGui.NextColumn();
|
|
||||||
ImGui.Text("Hook Status"u8);
|
|
||||||
ImGui.NextColumn();
|
|
||||||
if (receiveEventListener.Hook is null)
|
|
||||||
{
|
|
||||||
ImGui.Text("Hook is null"u8);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var color = receiveEventListener.Hook.IsEnabled ? ImGuiColors.HealerGreen : ImGuiColors.DalamudRed;
|
|
||||||
var text = receiveEventListener.Hook.IsEnabled ? "Enabled"u8 : "Disabled"u8;
|
|
||||||
ImGui.TextColored(color, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Columns(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.Addon.Lifecycle;
|
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
@ -34,7 +33,7 @@ internal unsafe class HookWidget : IDataWindowWidget
|
||||||
private MessageBoxWDelegate? messageBoxWOriginal;
|
private MessageBoxWDelegate? messageBoxWOriginal;
|
||||||
private AddonFinalizeDelegate? addonFinalizeOriginal;
|
private AddonFinalizeDelegate? addonFinalizeOriginal;
|
||||||
|
|
||||||
private AddonLifecycleAddressResolver? address;
|
private nint address;
|
||||||
|
|
||||||
private delegate int MessageBoxWDelegate(
|
private delegate int MessageBoxWDelegate(
|
||||||
IntPtr hWnd,
|
IntPtr hWnd,
|
||||||
|
|
@ -55,7 +54,7 @@ internal unsafe class HookWidget : IDataWindowWidget
|
||||||
public string DisplayName { get; init; } = "Hook";
|
public string DisplayName { get; init; } = "Hook";
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string[]? CommandShortcuts { get; init; } = { "hook" };
|
public string[]? CommandShortcuts { get; init; } = ["hook"];
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool Ready { get; set; }
|
public bool Ready { get; set; }
|
||||||
|
|
@ -65,8 +64,8 @@ internal unsafe class HookWidget : IDataWindowWidget
|
||||||
{
|
{
|
||||||
this.Ready = true;
|
this.Ready = true;
|
||||||
|
|
||||||
this.address = new AddonLifecycleAddressResolver();
|
var sigScanner = Service<TargetSigScanner>.Get();
|
||||||
this.address.Setup(Service<TargetSigScanner>.Get());
|
this.address = sigScanner.ScanText("E8 ?? ?? ?? ?? 48 83 EF 01 75 D5");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -224,7 +223,7 @@ internal unsafe class HookWidget : IDataWindowWidget
|
||||||
|
|
||||||
private IDalamudHook HookAddonFinalize()
|
private IDalamudHook HookAddonFinalize()
|
||||||
{
|
{
|
||||||
var hook = Hook<AddonFinalizeDelegate>.FromAddress(this.address!.AddonFinalize, this.OnAddonFinalize);
|
var hook = Hook<AddonFinalizeDelegate>.FromAddress(this.address, this.OnAddonFinalize);
|
||||||
|
|
||||||
this.addonFinalizeOriginal = hook.Original;
|
this.addonFinalizeOriginal = hook.Original;
|
||||||
hook.Enable();
|
hook.Enable();
|
||||||
|
|
|
||||||
|
|
@ -472,9 +472,9 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
|
|
||||||
private unsafe void OnVersionStringDraw(AddonEvent ev, AddonArgs args)
|
private unsafe void OnVersionStringDraw(AddonEvent ev, AddonArgs args)
|
||||||
{
|
{
|
||||||
if (args is not AddonDrawArgs drawArgs) return;
|
if (ev is not (AddonEvent.PostDraw or AddonEvent.PreDraw)) return;
|
||||||
|
|
||||||
var addon = drawArgs.Addon.Struct;
|
var addon = args.Addon.Struct;
|
||||||
var textNode = addon->GetTextNodeById(3);
|
var textNode = addon->GetTextNodeById(3);
|
||||||
|
|
||||||
// look and feel init. should be harmless to set.
|
// look and feel init. should be harmless to set.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ namespace Dalamud.Utility;
|
||||||
/// Utility class for marking something to be changed for API 13, for ease of lookup.
|
/// Utility class for marking something to be changed for API 13, for ease of lookup.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.All, Inherited = false)]
|
[AttributeUsage(AttributeTargets.All, Inherited = false)]
|
||||||
internal sealed class Api13ToDoAttribute : Attribute
|
internal sealed class Api14ToDoAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marks that this should be made internal.
|
/// Marks that this should be made internal.
|
||||||
|
|
@ -12,11 +12,11 @@ internal sealed class Api13ToDoAttribute : Attribute
|
||||||
public const string MakeInternal = "Make internal.";
|
public const string MakeInternal = "Make internal.";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Api13ToDoAttribute"/> class.
|
/// Initializes a new instance of the <see cref="Api14ToDoAttribute"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="what">The explanation.</param>
|
/// <param name="what">The explanation.</param>
|
||||||
/// <param name="what2">The explanation 2.</param>
|
/// <param name="what2">The explanation 2.</param>
|
||||||
public Api13ToDoAttribute(string what, string what2 = "")
|
public Api14ToDoAttribute(string what, string what2 = "")
|
||||||
{
|
{
|
||||||
_ = what;
|
_ = what;
|
||||||
_ = what2;
|
_ = what2;
|
||||||
25
Dalamud/Utility/Api15ToDoAttribute.cs
Normal file
25
Dalamud/Utility/Api15ToDoAttribute.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
namespace Dalamud.Utility;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utility class for marking something to be changed for API 13, for ease of lookup.
|
||||||
|
/// Intended to represent not the upcoming API, but the one after it for more major changes.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.All, Inherited = false)]
|
||||||
|
internal sealed class Api15ToDoAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Marks that this should be made internal.
|
||||||
|
/// </summary>
|
||||||
|
public const string MakeInternal = "Make internal.";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Api15ToDoAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="what">The explanation.</param>
|
||||||
|
/// <param name="what2">The explanation 2.</param>
|
||||||
|
public Api15ToDoAttribute(string what, string what2 = "")
|
||||||
|
{
|
||||||
|
_ = what;
|
||||||
|
_ = what2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -79,7 +79,7 @@ public static partial class Util
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Dalamud version.
|
/// Gets the Dalamud version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Api13ToDo("Remove. Make both versions here internal. Add an API somewhere.")]
|
[Api14ToDo("Remove. Make both versions here internal. Add an API somewhere.")]
|
||||||
public static string AssemblyVersion { get; } =
|
public static string AssemblyVersion { get; } =
|
||||||
Assembly.GetAssembly(typeof(ChatHandlers))!.GetName().Version!.ToString();
|
Assembly.GetAssembly(typeof(ChatHandlers))!.GetName().Version!.ToString();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<TargetFramework>net10.0-windows</TargetFramework>
|
<TargetFramework>net10.0-windows</TargetFramework>
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
<Platforms>x64</Platforms>
|
<Platforms>x64</Platforms>
|
||||||
<LangVersion>13.0</LangVersion>
|
<LangVersion>14.0</LangVersion>
|
||||||
|
|
||||||
<!-- Disable Intel CET. Causes crashes on unpatched Windows 10 systems. -->
|
<!-- Disable Intel CET. Causes crashes on unpatched Windows 10 systems. -->
|
||||||
<!-- https://github.com/dotnet/runtime/issues/108589 -->
|
<!-- https://github.com/dotnet/runtime/issues/108589 -->
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@ public static unsafe partial class ImGui
|
||||||
ImGuiSliderFlags flags = ImGuiSliderFlags.None) => DragScalar(
|
ImGuiSliderFlags flags = ImGuiSliderFlags.None) => DragScalar(
|
||||||
label,
|
label,
|
||||||
ImGuiDataType.Float,
|
ImGuiDataType.Float,
|
||||||
MemoryMarshal.Cast<Vector2, float>(new(ref v)),
|
MemoryMarshal.Cast<Vector2, float>(new Span<Vector2>(ref v)),
|
||||||
vSpeed,
|
vSpeed,
|
||||||
vMin,
|
vMin,
|
||||||
vMax,
|
vMax,
|
||||||
|
|
@ -251,7 +251,7 @@ public static unsafe partial class ImGui
|
||||||
ImGuiSliderFlags flags = ImGuiSliderFlags.None) => DragScalar(
|
ImGuiSliderFlags flags = ImGuiSliderFlags.None) => DragScalar(
|
||||||
label,
|
label,
|
||||||
ImGuiDataType.Float,
|
ImGuiDataType.Float,
|
||||||
MemoryMarshal.Cast<Vector3, float>(new(ref v)),
|
MemoryMarshal.Cast<Vector3, float>(new Span<Vector3>(ref v)),
|
||||||
vSpeed,
|
vSpeed,
|
||||||
vMin,
|
vMin,
|
||||||
vMax,
|
vMax,
|
||||||
|
|
@ -264,7 +264,7 @@ public static unsafe partial class ImGui
|
||||||
ImGuiSliderFlags flags = ImGuiSliderFlags.None) => DragScalar(
|
ImGuiSliderFlags flags = ImGuiSliderFlags.None) => DragScalar(
|
||||||
label,
|
label,
|
||||||
ImGuiDataType.Float,
|
ImGuiDataType.Float,
|
||||||
MemoryMarshal.Cast<Vector4, float>(new(ref v)),
|
MemoryMarshal.Cast<Vector4, float>(new Span<Vector4>(ref v)),
|
||||||
vSpeed,
|
vSpeed,
|
||||||
vMin,
|
vMin,
|
||||||
vMax,
|
vMax,
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,7 @@ public static unsafe partial class ImGui
|
||||||
InputScalar(
|
InputScalar(
|
||||||
label,
|
label,
|
||||||
ImGuiDataType.Float,
|
ImGuiDataType.Float,
|
||||||
MemoryMarshal.Cast<Vector2, float>(new(ref data)),
|
MemoryMarshal.Cast<Vector2, float>(new Span<Vector2>(ref data)),
|
||||||
step,
|
step,
|
||||||
stepFast,
|
stepFast,
|
||||||
format.MoveOrDefault("%.3f"u8),
|
format.MoveOrDefault("%.3f"u8),
|
||||||
|
|
@ -219,7 +219,7 @@ public static unsafe partial class ImGui
|
||||||
InputScalar(
|
InputScalar(
|
||||||
label,
|
label,
|
||||||
ImGuiDataType.Float,
|
ImGuiDataType.Float,
|
||||||
MemoryMarshal.Cast<Vector3, float>(new(ref data)),
|
MemoryMarshal.Cast<Vector3, float>(new Span<Vector3>(ref data)),
|
||||||
step,
|
step,
|
||||||
stepFast,
|
stepFast,
|
||||||
format.MoveOrDefault("%.3f"u8),
|
format.MoveOrDefault("%.3f"u8),
|
||||||
|
|
@ -233,7 +233,7 @@ public static unsafe partial class ImGui
|
||||||
InputScalar(
|
InputScalar(
|
||||||
label,
|
label,
|
||||||
ImGuiDataType.Float,
|
ImGuiDataType.Float,
|
||||||
MemoryMarshal.Cast<Vector4, float>(new(ref data)),
|
MemoryMarshal.Cast<Vector4, float>(new Span<Vector4>(ref data)),
|
||||||
step,
|
step,
|
||||||
stepFast,
|
stepFast,
|
||||||
format.MoveOrDefault("%.3f"u8),
|
format.MoveOrDefault("%.3f"u8),
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ public static unsafe partial class ImGui
|
||||||
ImU8String format = default, ImGuiSliderFlags flags = ImGuiSliderFlags.None) => SliderScalar(
|
ImU8String format = default, ImGuiSliderFlags flags = ImGuiSliderFlags.None) => SliderScalar(
|
||||||
label,
|
label,
|
||||||
ImGuiDataType.Float,
|
ImGuiDataType.Float,
|
||||||
MemoryMarshal.Cast<Vector2, float>(new(ref v)),
|
MemoryMarshal.Cast<Vector2, float>(new Span<Vector2>(ref v)),
|
||||||
vMin,
|
vMin,
|
||||||
vMax,
|
vMax,
|
||||||
format.MoveOrDefault("%.3f"u8),
|
format.MoveOrDefault("%.3f"u8),
|
||||||
|
|
@ -222,7 +222,7 @@ public static unsafe partial class ImGui
|
||||||
SliderScalar(
|
SliderScalar(
|
||||||
label,
|
label,
|
||||||
ImGuiDataType.Float,
|
ImGuiDataType.Float,
|
||||||
MemoryMarshal.Cast<Vector3, float>(new(ref v)),
|
MemoryMarshal.Cast<Vector3, float>(new Span<Vector3>(ref v)),
|
||||||
vMin,
|
vMin,
|
||||||
vMax,
|
vMax,
|
||||||
format.MoveOrDefault("%.3f"u8),
|
format.MoveOrDefault("%.3f"u8),
|
||||||
|
|
@ -236,7 +236,7 @@ public static unsafe partial class ImGui
|
||||||
SliderScalar(
|
SliderScalar(
|
||||||
label,
|
label,
|
||||||
ImGuiDataType.Float,
|
ImGuiDataType.Float,
|
||||||
MemoryMarshal.Cast<Vector4, float>(new(ref v)),
|
MemoryMarshal.Cast<Vector4, float>(new Span<Vector4>(ref v)),
|
||||||
vMin,
|
vMin,
|
||||||
vMax,
|
vMax,
|
||||||
format.MoveOrDefault("%.3f"u8),
|
format.MoveOrDefault("%.3f"u8),
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ public ref struct ImU8String
|
||||||
|
|
||||||
return this.rentedBuffer is { } buf
|
return this.rentedBuffer is { } buf
|
||||||
? buf.AsSpan()
|
? buf.AsSpan()
|
||||||
: MemoryMarshal.Cast<FixedBufferContainer, byte>(new(ref Unsafe.AsRef(ref this.fixedBuffer)));
|
: MemoryMarshal.Cast<FixedBufferContainer, byte>(new Span<FixedBufferContainer>(ref Unsafe.AsRef(ref this.fixedBuffer)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,7 +165,7 @@ public ref struct ImU8String
|
||||||
private ref byte FixedBufferByteRef => ref this.FixedBufferSpan[0];
|
private ref byte FixedBufferByteRef => ref this.FixedBufferSpan[0];
|
||||||
|
|
||||||
private Span<byte> FixedBufferSpan =>
|
private Span<byte> FixedBufferSpan =>
|
||||||
MemoryMarshal.Cast<FixedBufferContainer, byte>(new(ref Unsafe.AsRef(ref this.fixedBuffer)));
|
MemoryMarshal.Cast<FixedBufferContainer, byte>(new Span<FixedBufferContainer>(ref Unsafe.AsRef(ref this.fixedBuffer)));
|
||||||
|
|
||||||
public static implicit operator ImU8String(ReadOnlySpan<byte> text) => new(text);
|
public static implicit operator ImU8String(ReadOnlySpan<byte> text) => new(text);
|
||||||
public static implicit operator ImU8String(ReadOnlyMemory<byte> text) => new(text);
|
public static implicit operator ImU8String(ReadOnlyMemory<byte> text) => new(text);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue