diff --git a/Dalamud/Configuration/PluginConfigurations.cs b/Dalamud/Configuration/PluginConfigurations.cs
index fa2969d31..c01ab2af0 100644
--- a/Dalamud/Configuration/PluginConfigurations.cs
+++ b/Dalamud/Configuration/PluginConfigurations.cs
@@ -11,7 +11,7 @@ namespace Dalamud.Configuration;
///
/// Configuration to store settings for a dalamud plugin.
///
-[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
{
private readonly DirectoryInfo configDirectory;
diff --git a/Dalamud/Game/Addon/AddonLifecyclePooledArgs.cs b/Dalamud/Game/Addon/AddonLifecyclePooledArgs.cs
deleted file mode 100644
index 14def2036..000000000
--- a/Dalamud/Game/Addon/AddonLifecyclePooledArgs.cs
+++ /dev/null
@@ -1,107 +0,0 @@
-using System.Runtime.CompilerServices;
-using System.Threading;
-
-using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
-
-namespace Dalamud.Game.Addon;
-
-/// Argument pool for Addon Lifecycle services.
-[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()
- {
- }
-
- /// Rents an instance of an argument.
- /// The rented instance.
- /// The returner.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public PooledEntry Rent(out AddonSetupArgs arg) => new(out arg, this.addonSetupArgPool);
-
- /// Rents an instance of an argument.
- /// The rented instance.
- /// The returner.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public PooledEntry Rent(out AddonFinalizeArgs arg) => new(out arg, this.addonFinalizeArgPool);
-
- /// Rents an instance of an argument.
- /// The rented instance.
- /// The returner.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public PooledEntry Rent(out AddonDrawArgs arg) => new(out arg, this.addonDrawArgPool);
-
- /// Rents an instance of an argument.
- /// The rented instance.
- /// The returner.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public PooledEntry Rent(out AddonUpdateArgs arg) => new(out arg, this.addonUpdateArgPool);
-
- /// Rents an instance of an argument.
- /// The rented instance.
- /// The returner.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public PooledEntry Rent(out AddonRefreshArgs arg) => new(out arg, this.addonRefreshArgPool);
-
- /// Rents an instance of an argument.
- /// The rented instance.
- /// The returner.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public PooledEntry Rent(out AddonRequestedUpdateArgs arg) =>
- new(out arg, this.addonRequestedUpdateArgPool);
-
- /// Rents an instance of an argument.
- /// The rented instance.
- /// The returner.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public PooledEntry Rent(out AddonReceiveEventArgs arg) =>
- new(out arg, this.addonReceiveEventArgPool);
-
- /// Returns the object to the pool on dispose.
- /// The type.
- public readonly ref struct PooledEntry
- where T : AddonArgs, new()
- {
- private readonly Span pool;
- private readonly T obj;
-
- /// Initializes a new instance of the struct.
- /// An instance of the argument.
- /// The pool to rent from and return to.
- public PooledEntry(out T arg, Span 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();
- }
-
- /// Returns the item to the pool.
- 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;
- }
- }
- }
-}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs
index c008db08f..c4a7e8f53 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs
@@ -5,19 +5,24 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
///
/// Base class for AddonLifecycle AddonArgTypes.
///
-public abstract unsafe class AddonArgs
+public class AddonArgs
{
///
/// Constant string representing the name of an addon that is invalid.
///
public const string InvalidAddon = "NullAddon";
- private string? addonName;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal AddonArgs()
+ {
+ }
///
/// Gets the name of the addon this args referrers to.
///
- public string AddonName => this.GetAddonName();
+ public string AddonName { get; private set; } = InvalidAddon;
///
/// Gets the pointer to the addons AtkUnitBase.
@@ -25,55 +30,17 @@ public abstract unsafe class AddonArgs
public AtkUnitBasePtr Addon
{
get;
- internal set;
+ internal set
+ {
+ field = value;
+
+ if (!this.Addon.IsNull && !string.IsNullOrEmpty(value.Name))
+ this.AddonName = value.Name;
+ }
}
///
/// Gets the type of these args.
///
- public abstract AddonArgsType Type { get; }
-
- ///
- /// Checks if addon name matches the given span of char.
- ///
- /// The name to check.
- /// Whether it is the case.
- 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;
- }
-
- ///
- /// Clears this AddonArgs values.
- ///
- internal virtual void Clear()
- {
- this.addonName = null;
- this.Addon = 0;
- }
-
- ///
- /// Helper method for ensuring the name of the addon is valid.
- ///
- /// The name of the addon for this object. when invalid.
- private string GetAddonName()
- {
- if (this.Addon.IsNull) return InvalidAddon;
-
- var name = this.Addon.Name;
-
- if (string.IsNullOrEmpty(name))
- return InvalidAddon;
-
- return this.addonName ??= name;
- }
+ public virtual AddonArgsType Type => AddonArgsType.Generic;
}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonCloseArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonCloseArgs.cs
new file mode 100644
index 000000000..db3e442f8
--- /dev/null
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonCloseArgs.cs
@@ -0,0 +1,22 @@
+namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
+
+///
+/// Addon argument data for Close events.
+///
+public class AddonCloseArgs : AddonArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal AddonCloseArgs()
+ {
+ }
+
+ ///
+ public override AddonArgsType Type => AddonArgsType.Close;
+
+ ///
+ /// Gets or sets a value indicating whether the window should fire the callback method on close.
+ ///
+ public bool FireCallback { get; set; }
+}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonDrawArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonDrawArgs.cs
deleted file mode 100644
index 989e11912..000000000
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonDrawArgs.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
-
-///
-/// Addon argument data for Draw events.
-///
-public class AddonDrawArgs : AddonArgs, ICloneable
-{
- ///
- /// Initializes a new instance of the class.
- ///
- [Obsolete("Not intended for public construction.", false)]
- public AddonDrawArgs()
- {
- }
-
- ///
- public override AddonArgsType Type => AddonArgsType.Draw;
-
- ///
- public AddonDrawArgs Clone() => (AddonDrawArgs)this.MemberwiseClone();
-
- ///
- object ICloneable.Clone() => this.Clone();
-}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonFinalizeArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonFinalizeArgs.cs
deleted file mode 100644
index d9401b414..000000000
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonFinalizeArgs.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
-
-///
-/// Addon argument data for ReceiveEvent events.
-///
-public class AddonFinalizeArgs : AddonArgs, ICloneable
-{
- ///
- /// Initializes a new instance of the class.
- ///
- [Obsolete("Not intended for public construction.", false)]
- public AddonFinalizeArgs()
- {
- }
-
- ///
- public override AddonArgsType Type => AddonArgsType.Finalize;
-
- ///
- public AddonFinalizeArgs Clone() => (AddonFinalizeArgs)this.MemberwiseClone();
-
- ///
- object ICloneable.Clone() => this.Clone();
-}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonHideArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonHideArgs.cs
new file mode 100644
index 000000000..3e3521bd0
--- /dev/null
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonHideArgs.cs
@@ -0,0 +1,32 @@
+namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
+
+///
+/// Addon argument data for Hide events.
+///
+public class AddonHideArgs : AddonArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal AddonHideArgs()
+ {
+ }
+
+ ///
+ public override AddonArgsType Type => AddonArgsType.Hide;
+
+ ///
+ /// Gets or sets a value indicating whether to call the hide callback handler when this hides.
+ ///
+ public bool CallHideCallback { get; set; }
+
+ ///
+ /// Gets or sets the flags that the window will set when it Shows/Hides.
+ ///
+ public uint SetShowHideFlags { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether something for this event message.
+ ///
+ internal bool UnknownBool { get; set; }
+}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs
index 980fe4f2f..785cd199f 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs
@@ -3,13 +3,12 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
///
/// Addon argument data for ReceiveEvent events.
///
-public class AddonReceiveEventArgs : AddonArgs, ICloneable
+public class AddonReceiveEventArgs : AddonArgs
{
///
/// Initializes a new instance of the class.
///
- [Obsolete("Not intended for public construction.", false)]
- public AddonReceiveEventArgs()
+ internal AddonReceiveEventArgs()
{
}
@@ -32,23 +31,7 @@ public class AddonReceiveEventArgs : AddonArgs, ICloneable
public nint AtkEvent { get; set; }
///
- /// 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.
///
- public nint Data { get; set; }
-
- ///
- public AddonReceiveEventArgs Clone() => (AddonReceiveEventArgs)this.MemberwiseClone();
-
- ///
- object ICloneable.Clone() => this.Clone();
-
- ///
- internal override void Clear()
- {
- base.Clear();
- this.AtkEventType = default;
- this.EventParam = default;
- this.AtkEvent = default;
- this.Data = default;
- }
+ public nint AtkEventData { get; set; }
}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs
index d28631c3c..8af017318 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs
@@ -1,3 +1,5 @@
+using Dalamud.Utility;
+
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
@@ -5,13 +7,12 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
///
/// Addon argument data for Refresh events.
///
-public class AddonRefreshArgs : AddonArgs, ICloneable
+public class AddonRefreshArgs : AddonArgs
{
///
/// Initializes a new instance of the class.
///
- [Obsolete("Not intended for public construction.", false)]
- public AddonRefreshArgs()
+ internal AddonRefreshArgs()
{
}
@@ -31,19 +32,7 @@ public class AddonRefreshArgs : AddonArgs, ICloneable
///
/// Gets the AtkValues in the form of a span.
///
+ [Obsolete("Pending removal, unsafe to use when using custom ClientStructs")]
+ [Api15ToDo("Remove this")]
public unsafe Span AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
-
- ///
- public AddonRefreshArgs Clone() => (AddonRefreshArgs)this.MemberwiseClone();
-
- ///
- object ICloneable.Clone() => this.Clone();
-
- ///
- internal override void Clear()
- {
- base.Clear();
- this.AtkValueCount = default;
- this.AtkValues = default;
- }
}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs
index e87a980fd..7005b77c2 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs
@@ -3,13 +3,12 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
///
/// Addon argument data for OnRequestedUpdate events.
///
-public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
+public class AddonRequestedUpdateArgs : AddonArgs
{
///
/// Initializes a new instance of the class.
///
- [Obsolete("Not intended for public construction.", false)]
- public AddonRequestedUpdateArgs()
+ internal AddonRequestedUpdateArgs()
{
}
@@ -25,18 +24,4 @@ public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
/// Gets or sets the StringArrayData** for this event.
///
public nint StringArrayData { get; set; }
-
- ///
- public AddonRequestedUpdateArgs Clone() => (AddonRequestedUpdateArgs)this.MemberwiseClone();
-
- ///
- object ICloneable.Clone() => this.Clone();
-
- ///
- internal override void Clear()
- {
- base.Clear();
- this.NumberArrayData = default;
- this.StringArrayData = default;
- }
}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs
index 0dd9ecee2..9fd7b6dd0 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs
@@ -1,3 +1,5 @@
+using Dalamud.Utility;
+
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
@@ -5,13 +7,12 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
///
/// Addon argument data for Setup events.
///
-public class AddonSetupArgs : AddonArgs, ICloneable
+public class AddonSetupArgs : AddonArgs
{
///
/// Initializes a new instance of the class.
///
- [Obsolete("Not intended for public construction.", false)]
- public AddonSetupArgs()
+ internal AddonSetupArgs()
{
}
@@ -31,19 +32,7 @@ public class AddonSetupArgs : AddonArgs, ICloneable
///
/// Gets the AtkValues in the form of a span.
///
+ [Obsolete("Pending removal, unsafe to use when using custom ClientStructs")]
+ [Api15ToDo("Remove this")]
public unsafe Span AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
-
- ///
- public AddonSetupArgs Clone() => (AddonSetupArgs)this.MemberwiseClone();
-
- ///
- object ICloneable.Clone() => this.Clone();
-
- ///
- internal override void Clear()
- {
- base.Clear();
- this.AtkValueCount = default;
- this.AtkValues = default;
- }
}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonShowArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonShowArgs.cs
new file mode 100644
index 000000000..3153d1208
--- /dev/null
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonShowArgs.cs
@@ -0,0 +1,27 @@
+namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
+
+///
+/// Addon argument data for Show events.
+///
+public class AddonShowArgs : AddonArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal AddonShowArgs()
+ {
+ }
+
+ ///
+ public override AddonArgsType Type => AddonArgsType.Show;
+
+ ///
+ /// Gets or sets a value indicating whether the window should play open sound effects.
+ ///
+ public bool SilenceOpenSoundEffect { get; set; }
+
+ ///
+ /// Gets or sets the flags that the window will unset when it Shows/Hides.
+ ///
+ public uint UnsetShowHideFlags { get; set; }
+}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs
deleted file mode 100644
index a263f6ae4..000000000
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
-
-///
-/// Addon argument data for Update events.
-///
-public class AddonUpdateArgs : AddonArgs, ICloneable
-{
- ///
- /// Initializes a new instance of the class.
- ///
- [Obsolete("Not intended for public construction.", false)]
- public AddonUpdateArgs()
- {
- }
-
- ///
- public override AddonArgsType Type => AddonArgsType.Update;
-
- ///
- /// Gets the time since the last update.
- ///
- public float TimeDelta
- {
- get => this.TimeDeltaInternal;
- init => this.TimeDeltaInternal = value;
- }
-
- ///
- /// Gets or sets the time since the last update.
- ///
- internal float TimeDeltaInternal { get; set; }
-
- ///
- public AddonUpdateArgs Clone() => (AddonUpdateArgs)this.MemberwiseClone();
-
- ///
- object ICloneable.Clone() => this.Clone();
-
- ///
- internal override void Clear()
- {
- base.Clear();
- this.TimeDeltaInternal = default;
- }
-}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs
index b58b5f4c7..46ee479ac 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs
@@ -5,38 +5,43 @@
///
public enum AddonArgsType
{
+ ///
+ /// Generic arg type that contains no meaningful data.
+ ///
+ Generic,
+
///
/// Contains argument data for Setup.
///
Setup,
-
- ///
- /// Contains argument data for Update.
- ///
- Update,
-
- ///
- /// Contains argument data for Draw.
- ///
- Draw,
-
- ///
- /// Contains argument data for Finalize.
- ///
- Finalize,
-
+
///
/// Contains argument data for RequestedUpdate.
- ///
+ ///
RequestedUpdate,
-
+
///
/// Contains argument data for Refresh.
- ///
+ ///
Refresh,
-
+
///
/// Contains argument data for ReceiveEvent.
///
ReceiveEvent,
+
+ ///
+ /// Contains argument data for Show.
+ ///
+ Show,
+
+ ///
+ /// Contains argument data for Hide.
+ ///
+ Hide,
+
+ ///
+ /// Contains argument data for Close.
+ ///
+ Close,
}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs b/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs
index 5fd0ac964..3b9c6e867 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs
@@ -16,7 +16,7 @@ public enum AddonEvent
///
///
PreSetup,
-
+
///
/// 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
@@ -29,7 +29,6 @@ public enum AddonEvent
/// An event that is fired before an addon begins its update cycle via . This event
/// is fired every frame that an addon is loaded, regardless of visibility.
///
- ///
PreUpdate,
///
@@ -42,7 +41,6 @@ public enum AddonEvent
/// An event that is fired before an addon begins drawing to screen via . Unlike
/// , this event is only fired if an addon is visible or otherwise drawing to screen.
///
- ///
PreDraw,
///
@@ -62,9 +60,8 @@ public enum AddonEvent
///
/// As this is part of the destruction process for an addon, this event does not have an associated Post event.
///
- ///
PreFinalize,
-
+
///
/// An event that is fired before a call to is made in response to a
/// change in the subscribed or
@@ -81,13 +78,13 @@ public enum AddonEvent
/// to the Free Company's overview.
///
PreRequestedUpdate,
-
+
///
/// An event that is fired after an addon has finished processing an ArrayData update.
/// See for more information.
///
PostRequestedUpdate,
-
+
///
/// An event that is fired before an addon calls its method. Refreshes are
/// generally triggered in response to certain user interactions such as changing tabs, and are primarily used to
@@ -96,13 +93,13 @@ public enum AddonEvent
///
///
PreRefresh,
-
+
///
/// An event that is fired after an addon has finished its refresh.
/// See for more information.
///
PostRefresh,
-
+
///
/// An event that is fired before an addon begins processing a user-driven event via
/// , such as mousing over an element or clicking a button. This event
@@ -112,10 +109,98 @@ public enum AddonEvent
///
///
PreReceiveEvent,
-
+
///
/// An event that is fired after an addon finishes calling its method.
/// See for more information.
///
PostReceiveEvent,
+
+ ///
+ /// An event that is fired before an addon processes its open method.
+ ///
+ PreOpen,
+
+ ///
+ /// An event that is fired after an addon has processed its open method.
+ ///
+ PostOpen,
+
+ ///
+ /// An even that is fired before an addon processes its Close method.
+ ///
+ PreClose,
+
+ ///
+ /// An event that is fired after an addon has processed its Close method.
+ ///
+ PostClose,
+
+ ///
+ /// An event that is fired before an addon processes its Show method.
+ ///
+ PreShow,
+
+ ///
+ /// An event that is fired after an addon has processed its Show method.
+ ///
+ PostShow,
+
+ ///
+ /// An event that is fired before an addon processes its Hide method.
+ ///
+ PreHide,
+
+ ///
+ /// An event that is fired after an addon has processed its Hide method.
+ ///
+ PostHide,
+
+ ///
+ /// An event that is fired before an addon processes its OnMove method.
+ /// OnMove is triggered only when a move is completed.
+ ///
+ PreMove,
+
+ ///
+ /// An event that is fired after an addon has processed its OnMove method.
+ /// OnMove is triggered only when a move is completed.
+ ///
+ PostMove,
+
+ ///
+ /// An event that is fired before an addon processes its MouseOver method.
+ ///
+ PreMouseOver,
+
+ ///
+ /// An event that is fired after an addon has processed its MouseOver method.
+ ///
+ PostMouseOver,
+
+ ///
+ /// An event that is fired before an addon processes its MouseOut method.
+ ///
+ PreMouseOut,
+
+ ///
+ /// An event that is fired after an addon has processed its MouseOut method.
+ ///
+ PostMouseOut,
+
+ ///
+ /// An event that is fired before an addon processes its Focus method.
+ ///
+ ///
+ /// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows.
+ ///
+ PreFocus,
+
+ ///
+ /// An event that is fired after an addon has processed its Focus method.
+ ///
+ ///
+ /// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows.
+ ///
+ PostFocus,
}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
index b44ab8764..716ce1bfb 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
@@ -1,16 +1,14 @@
using System.Collections.Generic;
-using System.Linq;
+using System.Diagnostics;
using System.Runtime.CompilerServices;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Hooking;
-using Dalamud.Hooking.Internal;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace Dalamud.Game.Addon.Lifecycle;
@@ -21,75 +19,36 @@ namespace Dalamud.Game.Addon.Lifecycle;
[ServiceManager.EarlyLoadedService]
internal unsafe class AddonLifecycle : IInternalDisposableService
{
+ ///
+ /// Gets a list of all allocated addon virtual tables.
+ ///
+ public static readonly List AllocatedTables = [];
+
private static readonly ModuleLog Log = new("AddonLifecycle");
- [ServiceManager.ServiceDependency]
- private readonly Framework framework = Service.Get();
-
- [ServiceManager.ServiceDependency]
- private readonly AddonLifecyclePooledArgs argsPool = Service.Get();
-
- private readonly nint disallowedReceiveEventAddress;
-
- private readonly AddonLifecycleAddressResolver address;
- private readonly AddonSetupHook onAddonSetupHook;
- private readonly Hook onAddonFinalizeHook;
- private readonly CallHook onAddonDrawHook;
- private readonly CallHook onAddonUpdateHook;
- private readonly Hook onAddonRefreshHook;
- private readonly CallHook onAddonRequestedUpdateHook;
+ private Hook? onInitializeAddonHook;
[ServiceManager.ServiceConstructor]
- private AddonLifecycle(TargetSigScanner sigScanner)
+ private AddonLifecycle()
{
- this.address = new AddonLifecycleAddressResolver();
- this.address.Setup(sigScanner);
-
- this.disallowedReceiveEventAddress = (nint)AtkUnitBase.StaticVirtualTablePointer->ReceiveEvent;
-
- var refreshAddonAddress = (nint)RaptureAtkUnitManager.StaticVirtualTablePointer->RefreshAddon;
-
- this.onAddonSetupHook = new AddonSetupHook(this.address.AddonSetup, this.OnAddonSetup);
- this.onAddonFinalizeHook = Hook.FromAddress(this.address.AddonFinalize, this.OnAddonFinalize);
- this.onAddonDrawHook = new CallHook(this.address.AddonDraw, this.OnAddonDraw);
- this.onAddonUpdateHook = new CallHook(this.address.AddonUpdate, this.OnAddonUpdate);
- this.onAddonRefreshHook = Hook.FromAddress(refreshAddonAddress, this.OnAddonRefresh);
- this.onAddonRequestedUpdateHook = new CallHook(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate);
-
- this.onAddonSetupHook.Enable();
- this.onAddonFinalizeHook.Enable();
- this.onAddonDrawHook.Enable();
- this.onAddonUpdateHook.Enable();
- this.onAddonRefreshHook.Enable();
- this.onAddonRequestedUpdateHook.Enable();
+ this.onInitializeAddonHook = Hook.FromAddress((nint)AtkUnitBase.StaticVirtualTablePointer->Initialize, this.OnAddonInitialize);
+ this.onInitializeAddonHook.Enable();
}
- private delegate void AddonFinalizeDelegate(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase);
-
- ///
- /// Gets a list of all AddonLifecycle ReceiveEvent Listener Hooks.
- ///
- internal List ReceiveEventListeners { get; } = new();
-
///
/// Gets a list of all AddonLifecycle Event Listeners.
- ///
- internal List EventListeners { get; } = new();
+ ///
+ /// Mapping is: EventType -> AddonName -> ListenerList
+ internal Dictionary>> EventListeners { get; } = [];
///
void IInternalDisposableService.DisposeService()
{
- this.onAddonSetupHook.Dispose();
- this.onAddonFinalizeHook.Dispose();
- this.onAddonDrawHook.Dispose();
- this.onAddonUpdateHook.Dispose();
- this.onAddonRefreshHook.Dispose();
- this.onAddonRequestedUpdateHook.Dispose();
+ this.onInitializeAddonHook?.Dispose();
+ this.onInitializeAddonHook = null;
- foreach (var receiveEventListener in this.ReceiveEventListeners)
- {
- receiveEventListener.Dispose();
- }
+ AllocatedTables.ForEach(entry => entry.Dispose());
+ AllocatedTables.Clear();
}
///
@@ -98,20 +57,20 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
/// The listener to register.
internal void RegisterListener(AddonLifecycleEventListener listener)
{
- this.framework.RunOnTick(() =>
+ if (!this.EventListeners.ContainsKey(listener.EventType))
{
- this.EventListeners.Add(listener);
-
- // 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 })
- {
- if (this.ReceiveEventListeners.FirstOrDefault(listeners => listeners.AddonNames.Contains(listener.AddonName)) is { } receiveEventListener)
- {
- receiveEventListener.TryEnable();
- }
- }
- });
+ if (!this.EventListeners.TryAdd(listener.EventType, []))
+ return;
+ }
+
+ // 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.EventListeners[listener.EventType].TryAdd(listener.AddonName, []))
+ return;
+ }
+
+ this.EventListeners[listener.EventType][listener.AddonName].Add(listener);
}
///
@@ -120,27 +79,13 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
/// The listener to unregister.
internal void UnregisterListener(AddonLifecycleEventListener listener)
{
- // Set removed state to true immediately, then lazily remove it from the EventListeners list on next Framework Update.
- listener.Removed = true;
-
- this.framework.RunOnTick(() =>
+ if (this.EventListeners.TryGetValue(listener.EventType, out var addonListeners))
{
- this.EventListeners.Remove(listener);
-
- // If we are disabling an ReceiveEvent listener, check if we should disable the hook.
- if (listener is { EventType: AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent })
+ if (addonListeners.TryGetValue(listener.AddonName, out var addonListener))
{
- // Get the ReceiveEvent Listener for this addon
- 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();
- }
- }
+ addonListener.Remove(listener);
}
- });
+ }
}
///
@@ -151,226 +96,63 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
/// What to blame on errors.
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.
- foreach (var listener in this.EventListeners)
+ // Early return if we don't have any listeners of this type
+ 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)
- 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
+ foreach (var listener in globalListeners)
{
- listener.FunctionDelegate.Invoke(eventType, args);
- }
- 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))
+ try
{
- 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.
- else
+ // Handle listeners that are listening for this addon and event type specifically
+ if (addonListeners.TryGetValue(args.AddonName, out var addonListener))
+ {
+ foreach (var listener in addonListener)
{
- this.ReceiveEventListeners.Add(new AddonLifecycleReceiveEventListener(this, addonName, receiveEventAddress));
- }
-
- // 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)
+ try
{
- 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
- if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.AddonNames.Contains(addonName)) is { } eventListener)
+ try
{
- eventListener.AddonNames.Remove(addonName);
+ this.LogInitialize(addon->NameString);
- // If there are no more listeners let's remove and dispose.
- if (eventListener.AddonNames.Count is 0)
- {
- this.ReceiveEventListeners.Remove(eventListener);
- eventListener.Dispose();
- }
+ // AddonVirtualTable class handles creating the virtual table, and overriding each of the tracked virtual functions
+ AllocatedTables.Add(new AddonVirtualTable(addon, this));
}
+ 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
- {
- 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);
+ Log.Debug($"Initializing {addonName}");
}
}
@@ -387,7 +169,7 @@ internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLi
[ServiceManager.ServiceDependency]
private readonly AddonLifecycle addonLifecycleService = Service.Get();
- private readonly List eventListeners = new();
+ private readonly List eventListeners = [];
///
void IInternalDisposableService.DisposeService()
@@ -458,7 +240,7 @@ internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLi
this.eventListeners.RemoveAll(entry =>
{
if (entry.FunctionDelegate != handler) return false;
-
+
this.addonLifecycleService.UnregisterListener(entry);
return true;
});
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs
deleted file mode 100644
index bc9e4b639..000000000
--- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using Dalamud.Plugin.Services;
-
-namespace Dalamud.Game.Addon.Lifecycle;
-
-///
-/// AddonLifecycleService memory address resolver.
-///
-internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver
-{
- ///
- /// 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.
- ///
- public nint AddonSetup { get; private set; }
-
- ///
- /// 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.
- ///
- public nint AddonSetup2 { get; private set; }
-
- ///
- /// Gets the address of the addon finalize hook invoked by the AtkUnitManager.
- ///
- public nint AddonFinalize { get; private set; }
-
- ///
- /// Gets the address of the addon draw hook invoked by virtual function call.
- ///
- public nint AddonDraw { get; private set; }
-
- ///
- /// Gets the address of the addon update hook invoked by virtual function call.
- ///
- public nint AddonUpdate { get; private set; }
-
- ///
- /// Gets the address of the addon onRequestedUpdate hook invoked by virtual function call.
- ///
- public nint AddonOnRequestedUpdate { get; private set; }
-
- ///
- /// Scan for and setup any configured address pointers.
- ///
- /// The signature scanner to facilitate setup.
- 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");
- }
-}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleEventListener.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleEventListener.cs
index 9d411cdbc..fc82e0582 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleEventListener.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleEventListener.cs
@@ -25,17 +25,12 @@ internal class AddonLifecycleEventListener
/// string.Empty if it wants to be called for any addon.
///
public string AddonName { get; init; }
-
- ///
- /// Gets or sets a value indicating whether this event has been unregistered.
- ///
- public bool Removed { get; set; }
-
+
///
/// Gets the event type this listener is looking for.
///
public AddonEvent EventType { get; init; }
-
+
///
/// Gets the delegate this listener invokes.
///
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs
deleted file mode 100644
index 0d2bcc7f2..000000000
--- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs
+++ /dev/null
@@ -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;
-
-///
-/// 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.
-///
-internal unsafe class AddonLifecycleReceiveEventListener : IDisposable
-{
- private static readonly ModuleLog Log = new("AddonLifecycle");
-
- [ServiceManager.ServiceDependency]
- private readonly AddonLifecyclePooledArgs argsPool = Service.Get();
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// AddonLifecycle service instance.
- /// Initial Addon Requesting this listener.
- /// Address of Addon's ReceiveEvent function.
- internal AddonLifecycleReceiveEventListener(AddonLifecycle service, string addonName, nint receiveEventAddress)
- {
- this.AddonLifecycle = service;
- this.AddonNames = [addonName];
- this.FunctionAddress = receiveEventAddress;
- }
-
- ///
- /// Gets the list of addons that use this receive event hook.
- ///
- public List AddonNames { get; init; }
-
- ///
- /// Gets the address of the ReceiveEvent function as provided by the vtable on setup.
- ///
- public nint FunctionAddress { get; init; }
-
- ///
- /// Gets the contained hook for these addons.
- ///
- public Hook? Hook { get; private set; }
-
- ///
- /// Gets or sets the Reference to AddonLifecycle service instance.
- ///
- private AddonLifecycle AddonLifecycle { get; set; }
-
- ///
- /// Try to hook and enable this receive event handler.
- ///
- public void TryEnable()
- {
- this.Hook ??= Hook.FromAddress(this.FunctionAddress, this.OnReceiveEvent);
- this.Hook?.Enable();
- }
-
- ///
- /// Disable the hook for this receive event handler.
- ///
- public void Disable()
- {
- this.Hook?.Disable();
- }
-
- ///
- 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);
- }
-}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonSetupHook.cs b/Dalamud/Game/Addon/Lifecycle/AddonSetupHook.cs
deleted file mode 100644
index 297323b8f..000000000
--- a/Dalamud/Game/Addon/Lifecycle/AddonSetupHook.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System.Runtime.InteropServices;
-
-using Reloaded.Hooks.Definitions;
-
-namespace Dalamud.Game.Addon.Lifecycle;
-
-///
-/// This class represents a callsite hook used to replace the address of the OnSetup function in r9.
-///
-/// Delegate signature for this hook.
-internal class AddonSetupHook : IDisposable where T : Delegate
-{
- private readonly Reloaded.Hooks.AsmHook asmHook;
-
- private T? detour;
- private bool activated;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Address of the instruction to replace.
- /// Delegate to invoke.
- 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);
- }
-
- ///
- /// Gets a value indicating whether the hook is enabled.
- ///
- public bool IsEnabled => this.asmHook.IsEnabled;
-
- ///
- /// Starts intercepting a call to the function.
- ///
- public void Enable()
- {
- if (!this.activated)
- {
- this.activated = true;
- this.asmHook.Activate();
- return;
- }
-
- this.asmHook.Enable();
- }
-
- ///
- /// Stops intercepting a call to the function.
- ///
- public void Disable()
- {
- this.asmHook.Disable();
- }
-
- ///
- /// Remove a hook from the current process.
- ///
- public void Dispose()
- {
- this.asmHook.Disable();
- this.detour = null;
- }
-}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs b/Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs
new file mode 100644
index 000000000..47ff92c3d
--- /dev/null
+++ b/Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs
@@ -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;
+
+///
+/// Represents a class that holds references to an addons original and modified virtual table entries.
+///
+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;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// AtkUnitBase* for the addon to replace the table of.
+ /// Reference to AddonLifecycle service to callback and invoke listeners.
+ 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)Marshal.GetFunctionPointerForDelegate(this.destructorFunction);
+ this.modifiedVirtualTable->OnSetup = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.onSetupFunction);
+ this.modifiedVirtualTable->Finalizer = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.finalizerFunction);
+ this.modifiedVirtualTable->Draw = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.drawFunction);
+ this.modifiedVirtualTable->Update = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.updateFunction);
+ this.modifiedVirtualTable->OnRefresh = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.onRefreshFunction);
+ this.modifiedVirtualTable->OnRequestedUpdate = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.onRequestedUpdateFunction);
+ this.modifiedVirtualTable->ReceiveEvent = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.onReceiveEventFunction);
+ this.modifiedVirtualTable->Open = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.openFunction);
+ this.modifiedVirtualTable->Close = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.closeFunction);
+ this.modifiedVirtualTable->Show = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.showFunction);
+ this.modifiedVirtualTable->Hide = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.hideFunction);
+ this.modifiedVirtualTable->OnMove = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.onMoveFunction);
+ this.modifiedVirtualTable->OnMouseOver = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.onMouseOverFunction);
+ this.modifiedVirtualTable->OnMouseOut = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.onMouseOutFunction);
+ this.modifiedVirtualTable->Focus = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.focusFunction);
+ }
+
+ ///
+ 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}");
+ }
+ }
+}
diff --git a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs
index f5b7011fe..af85f9228 100644
--- a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs
+++ b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs
@@ -150,7 +150,7 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry
}
///
- [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;
///
diff --git a/Dalamud/Hooking/Internal/CallHook.cs b/Dalamud/Hooking/Internal/CallHook.cs
deleted file mode 100644
index 92bc6e31a..000000000
--- a/Dalamud/Hooking/Internal/CallHook.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-using System.Runtime.InteropServices;
-
-using Reloaded.Hooks.Definitions;
-
-namespace Dalamud.Hooking.Internal;
-
-///
-/// 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.
-///
-/// Delegate signature for this hook.
-internal class CallHook : IDalamudHook where T : Delegate
-{
- private readonly Reloaded.Hooks.AsmHook asmHook;
-
- private T? detour;
- private bool activated;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Address of the instruction to replace.
- /// Delegate to invoke.
- 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);
- }
-
- ///
- /// Gets a value indicating whether the hook is enabled.
- ///
- public bool IsEnabled => this.asmHook.IsEnabled;
-
- ///
- public IntPtr Address { get; }
-
- ///
- public string BackendName => "Reloaded AsmHook";
-
- ///
- public bool IsDisposed => this.detour == null;
-
- ///
- /// Starts intercepting a call to the function.
- ///
- public void Enable()
- {
- if (!this.activated)
- {
- this.activated = true;
- this.asmHook.Activate();
- return;
- }
-
- this.asmHook.Enable();
- }
-
- ///
- /// Stops intercepting a call to the function.
- ///
- public void Disable()
- {
- this.asmHook.Disable();
- }
-
- ///
- /// Remove a hook from the current process.
- ///
- public void Dispose()
- {
- this.asmHook.Disable();
- this.detour = null;
- }
-}
diff --git a/Dalamud/Interface/Animation/Easing.cs b/Dalamud/Interface/Animation/Easing.cs
index 0d2057b3b..cc1f48ce7 100644
--- a/Dalamud/Interface/Animation/Easing.cs
+++ b/Dalamud/Interface/Animation/Easing.cs
@@ -48,7 +48,7 @@ public abstract class Easing
/// Gets the current value of the animation, following unclamped logic.
///
[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;
///
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs
index b58166e89..4fb13b81a 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs
@@ -1,11 +1,9 @@
-using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.Addon.Lifecycle;
-using Dalamud.Interface.Colors;
-using Dalamud.Interface.Utility;
+using Dalamud.Interface.Utility.Raii;
+using Dalamud.Utility;
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
@@ -48,97 +46,38 @@ public class AddonLifecycleWidget : IDataWindowWidget
return;
}
- if (ImGui.CollapsingHeader("Listeners"u8))
+ foreach (var (eventType, addonListeners) in this.AddonLifecycle.EventListeners)
{
- ImGui.Indent();
- this.DrawEventListeners();
- ImGui.Unindent();
- }
+ using var eventId = ImRaii.PushId(eventType.ToString());
- 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())
- {
if (ImGui.CollapsingHeader(eventType.ToString()))
{
- ImGui.Indent();
- var listeners = this.AddonLifecycle.EventListeners.Where(listener => listener.EventType == eventType).ToList();
+ using var eventIndent = ImRaii.PushIndent();
- 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);
- ImGui.TableSetupColumn("##MethodInvoke"u8, ImGuiTableColumnFlags.WidthStretch);
+ using var addonId = ImRaii.PushId(addonName);
- foreach (var listener in listeners)
+ if (ImGui.CollapsingHeader(addonName.IsNullOrEmpty() ? "GLOBAL" : addonName))
{
- ImGui.TableNextColumn();
- ImGui.Text(listener.AddonName is "" ? "GLOBAL" : listener.AddonName);
+ using var addonIndent = ImRaii.PushIndent();
- ImGui.TableNextColumn();
- ImGui.Text($"{listener.FunctionDelegate.Method.DeclaringType?.FullName ?? "Unknown Declaring Type"}::{listener.FunctionDelegate.Method.Name}");
+ if (listeners.Count == 0)
+ {
+ 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);
}
}
}
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/HookWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/HookWidget.cs
index 3ad8f86c2..f3e25caf8 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/HookWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/HookWidget.cs
@@ -5,7 +5,6 @@ using System.Threading.Tasks;
using Dalamud.Bindings.ImGui;
using Dalamud.Game;
-using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Serilog;
@@ -34,7 +33,7 @@ internal unsafe class HookWidget : IDataWindowWidget
private MessageBoxWDelegate? messageBoxWOriginal;
private AddonFinalizeDelegate? addonFinalizeOriginal;
- private AddonLifecycleAddressResolver? address;
+ private nint address;
private delegate int MessageBoxWDelegate(
IntPtr hWnd,
@@ -55,7 +54,7 @@ internal unsafe class HookWidget : IDataWindowWidget
public string DisplayName { get; init; } = "Hook";
///
- public string[]? CommandShortcuts { get; init; } = { "hook" };
+ public string[]? CommandShortcuts { get; init; } = ["hook"];
///
public bool Ready { get; set; }
@@ -65,8 +64,8 @@ internal unsafe class HookWidget : IDataWindowWidget
{
this.Ready = true;
- this.address = new AddonLifecycleAddressResolver();
- this.address.Setup(Service.Get());
+ var sigScanner = Service.Get();
+ this.address = sigScanner.ScanText("E8 ?? ?? ?? ?? 48 83 EF 01 75 D5");
}
///
@@ -224,7 +223,7 @@ internal unsafe class HookWidget : IDataWindowWidget
private IDalamudHook HookAddonFinalize()
{
- var hook = Hook.FromAddress(this.address!.AddonFinalize, this.OnAddonFinalize);
+ var hook = Hook.FromAddress(this.address, this.OnAddonFinalize);
this.addonFinalizeOriginal = hook.Original;
hook.Enable();
diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs
index 69cdc4d28..482a00153 100644
--- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs
@@ -472,9 +472,9 @@ internal class TitleScreenMenuWindow : Window, IDisposable
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);
// look and feel init. should be harmless to set.
diff --git a/Dalamud/Utility/Api13ToDoAttribute.cs b/Dalamud/Utility/Api14ToDoAttribute.cs
similarity index 75%
rename from Dalamud/Utility/Api13ToDoAttribute.cs
rename to Dalamud/Utility/Api14ToDoAttribute.cs
index 576401cda..945b6e4db 100644
--- a/Dalamud/Utility/Api13ToDoAttribute.cs
+++ b/Dalamud/Utility/Api14ToDoAttribute.cs
@@ -4,7 +4,7 @@ namespace Dalamud.Utility;
/// Utility class for marking something to be changed for API 13, for ease of lookup.
///
[AttributeUsage(AttributeTargets.All, Inherited = false)]
-internal sealed class Api13ToDoAttribute : Attribute
+internal sealed class Api14ToDoAttribute : Attribute
{
///
/// Marks that this should be made internal.
@@ -12,11 +12,11 @@ internal sealed class Api13ToDoAttribute : Attribute
public const string MakeInternal = "Make internal.";
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The explanation.
/// The explanation 2.
- public Api13ToDoAttribute(string what, string what2 = "")
+ public Api14ToDoAttribute(string what, string what2 = "")
{
_ = what;
_ = what2;
diff --git a/Dalamud/Utility/Api15ToDoAttribute.cs b/Dalamud/Utility/Api15ToDoAttribute.cs
new file mode 100644
index 000000000..646c260e8
--- /dev/null
+++ b/Dalamud/Utility/Api15ToDoAttribute.cs
@@ -0,0 +1,25 @@
+namespace Dalamud.Utility;
+
+///
+/// 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.
+///
+[AttributeUsage(AttributeTargets.All, Inherited = false)]
+internal sealed class Api15ToDoAttribute : Attribute
+{
+ ///
+ /// Marks that this should be made internal.
+ ///
+ public const string MakeInternal = "Make internal.";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The explanation.
+ /// The explanation 2.
+ public Api15ToDoAttribute(string what, string what2 = "")
+ {
+ _ = what;
+ _ = what2;
+ }
+}
diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs
index bde113904..e273decac 100644
--- a/Dalamud/Utility/Util.cs
+++ b/Dalamud/Utility/Util.cs
@@ -79,7 +79,7 @@ public static partial class Util
///
/// Gets the Dalamud version.
///
- [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; } =
Assembly.GetAssembly(typeof(ChatHandlers))!.GetName().Version!.ToString();
diff --git a/Directory.Build.props b/Directory.Build.props
index eabb727e8..3897256bf 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -5,7 +5,7 @@
net10.0-windows
x64
x64
- 13.0
+ 14.0
diff --git a/imgui/Dalamud.Bindings.ImGui/Custom/ImGui.DragScalar.cs b/imgui/Dalamud.Bindings.ImGui/Custom/ImGui.DragScalar.cs
index 665fa434f..3cf20bb30 100644
--- a/imgui/Dalamud.Bindings.ImGui/Custom/ImGui.DragScalar.cs
+++ b/imgui/Dalamud.Bindings.ImGui/Custom/ImGui.DragScalar.cs
@@ -238,7 +238,7 @@ public static unsafe partial class ImGui
ImGuiSliderFlags flags = ImGuiSliderFlags.None) => DragScalar(
label,
ImGuiDataType.Float,
- MemoryMarshal.Cast(new(ref v)),
+ MemoryMarshal.Cast(new Span(ref v)),
vSpeed,
vMin,
vMax,
@@ -251,7 +251,7 @@ public static unsafe partial class ImGui
ImGuiSliderFlags flags = ImGuiSliderFlags.None) => DragScalar(
label,
ImGuiDataType.Float,
- MemoryMarshal.Cast(new(ref v)),
+ MemoryMarshal.Cast(new Span(ref v)),
vSpeed,
vMin,
vMax,
@@ -264,7 +264,7 @@ public static unsafe partial class ImGui
ImGuiSliderFlags flags = ImGuiSliderFlags.None) => DragScalar(
label,
ImGuiDataType.Float,
- MemoryMarshal.Cast(new(ref v)),
+ MemoryMarshal.Cast(new Span(ref v)),
vSpeed,
vMin,
vMax,
diff --git a/imgui/Dalamud.Bindings.ImGui/Custom/ImGui.InputScalar.cs b/imgui/Dalamud.Bindings.ImGui/Custom/ImGui.InputScalar.cs
index fb86096ff..5881ac462 100644
--- a/imgui/Dalamud.Bindings.ImGui/Custom/ImGui.InputScalar.cs
+++ b/imgui/Dalamud.Bindings.ImGui/Custom/ImGui.InputScalar.cs
@@ -205,7 +205,7 @@ public static unsafe partial class ImGui
InputScalar(
label,
ImGuiDataType.Float,
- MemoryMarshal.Cast(new(ref data)),
+ MemoryMarshal.Cast(new Span(ref data)),
step,
stepFast,
format.MoveOrDefault("%.3f"u8),
@@ -219,7 +219,7 @@ public static unsafe partial class ImGui
InputScalar(
label,
ImGuiDataType.Float,
- MemoryMarshal.Cast(new(ref data)),
+ MemoryMarshal.Cast(new Span(ref data)),
step,
stepFast,
format.MoveOrDefault("%.3f"u8),
@@ -233,7 +233,7 @@ public static unsafe partial class ImGui
InputScalar(
label,
ImGuiDataType.Float,
- MemoryMarshal.Cast(new(ref data)),
+ MemoryMarshal.Cast(new Span(ref data)),
step,
stepFast,
format.MoveOrDefault("%.3f"u8),
diff --git a/imgui/Dalamud.Bindings.ImGui/Custom/ImGui.SliderScalar.cs b/imgui/Dalamud.Bindings.ImGui/Custom/ImGui.SliderScalar.cs
index 20ee78ab6..b0c4b7c79 100644
--- a/imgui/Dalamud.Bindings.ImGui/Custom/ImGui.SliderScalar.cs
+++ b/imgui/Dalamud.Bindings.ImGui/Custom/ImGui.SliderScalar.cs
@@ -210,7 +210,7 @@ public static unsafe partial class ImGui
ImU8String format = default, ImGuiSliderFlags flags = ImGuiSliderFlags.None) => SliderScalar(
label,
ImGuiDataType.Float,
- MemoryMarshal.Cast(new(ref v)),
+ MemoryMarshal.Cast(new Span(ref v)),
vMin,
vMax,
format.MoveOrDefault("%.3f"u8),
@@ -222,7 +222,7 @@ public static unsafe partial class ImGui
SliderScalar(
label,
ImGuiDataType.Float,
- MemoryMarshal.Cast(new(ref v)),
+ MemoryMarshal.Cast(new Span(ref v)),
vMin,
vMax,
format.MoveOrDefault("%.3f"u8),
@@ -236,7 +236,7 @@ public static unsafe partial class ImGui
SliderScalar(
label,
ImGuiDataType.Float,
- MemoryMarshal.Cast(new(ref v)),
+ MemoryMarshal.Cast(new Span(ref v)),
vMin,
vMax,
format.MoveOrDefault("%.3f"u8),
diff --git a/imgui/Dalamud.Bindings.ImGui/ImU8String.cs b/imgui/Dalamud.Bindings.ImGui/ImU8String.cs
index a62152c39..f2b635764 100644
--- a/imgui/Dalamud.Bindings.ImGui/ImU8String.cs
+++ b/imgui/Dalamud.Bindings.ImGui/ImU8String.cs
@@ -156,7 +156,7 @@ public ref struct ImU8String
return this.rentedBuffer is { } buf
? buf.AsSpan()
- : MemoryMarshal.Cast(new(ref Unsafe.AsRef(ref this.fixedBuffer)));
+ : MemoryMarshal.Cast(new Span(ref Unsafe.AsRef(ref this.fixedBuffer)));
}
}
@@ -165,7 +165,7 @@ public ref struct ImU8String
private ref byte FixedBufferByteRef => ref this.FixedBufferSpan[0];
private Span FixedBufferSpan =>
- MemoryMarshal.Cast(new(ref Unsafe.AsRef(ref this.fixedBuffer)));
+ MemoryMarshal.Cast(new Span(ref Unsafe.AsRef(ref this.fixedBuffer)));
public static implicit operator ImU8String(ReadOnlySpan text) => new(text);
public static implicit operator ImU8String(ReadOnlyMemory text) => new(text);