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/EntryPoint.cs b/Dalamud/EntryPoint.cs
index 54e25b6f2..d9f6ef172 100644
--- a/Dalamud/EntryPoint.cs
+++ b/Dalamud/EntryPoint.cs
@@ -192,8 +192,8 @@ public sealed class EntryPoint
var dalamud = new Dalamud(info, fs, configuration, mainThreadContinueEvent);
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]",
- Util.GetScmVersion(),
- Util.GetGitHashClientStructs(),
+ Versioning.GetScmVersion(),
+ Versioning.GetGitHashClientStructs(),
FFXIVClientStructs.ThisAssembly.Git.Commits);
dalamud.WaitForUnload();
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..4fc81632a 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs
@@ -1,17 +1,22 @@
+using System.Collections.Generic;
+
+using Dalamud.Game.NativeWrapper;
+using Dalamud.Utility;
+
using FFXIVClientStructs.FFXIV.Component.GUI;
+using FFXIVClientStructs.Interop;
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 +36,30 @@ public class AddonRefreshArgs : AddonArgs, ICloneable
///
/// Gets the AtkValues in the form of a span.
///
+ [Obsolete("Pending removal, Use AtkValueEnumerable instead.")]
+ [Api15ToDo("Make this internal, remove obsolete")]
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()
+ ///
+ /// Gets an enumerable collection of of the event's AtkValues.
+ ///
+ ///
+ /// An of corresponding to the event's AtkValues.
+ ///
+ public IEnumerable AtkValueEnumerable
{
- base.Clear();
- this.AtkValueCount = default;
- this.AtkValues = default;
+ get
+ {
+ for (var i = 0; i < this.AtkValueCount; i++)
+ {
+ AtkValuePtr ptr;
+ unsafe
+ {
+ ptr = new AtkValuePtr((nint)this.AtkValueSpan.GetPointer(i));
+ }
+
+ yield return ptr;
+ }
+ }
}
}
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..e0b2defbf 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs
@@ -1,17 +1,22 @@
+using System.Collections.Generic;
+
+using Dalamud.Game.NativeWrapper;
+using Dalamud.Utility;
+
using FFXIVClientStructs.FFXIV.Component.GUI;
+using FFXIVClientStructs.Interop;
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 +36,30 @@ public class AddonSetupArgs : AddonArgs, ICloneable
///
/// Gets the AtkValues in the form of a span.
///
+ [Obsolete("Pending removal, Use AtkValueEnumerable instead.")]
+ [Api15ToDo("Make this internal, remove obsolete")]
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()
+ ///
+ /// Gets an enumerable collection of of the event's AtkValues.
+ ///
+ ///
+ /// An of corresponding to the event's AtkValues.
+ ///
+ public IEnumerable AtkValueEnumerable
{
- base.Clear();
- this.AtkValueCount = default;
- this.AtkValues = default;
+ get
+ {
+ for (var i = 0; i < this.AtkValueCount; i++)
+ {
+ AtkValuePtr ptr;
+ unsafe
+ {
+ ptr = new AtkValuePtr((nint)this.AtkValueSpan.GetPointer(i));
+ }
+
+ yield return ptr;
+ }
+ }
}
}
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/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs
index b1b798a8a..279bf46e5 100644
--- a/Dalamud/Game/ChatHandlers.cs
+++ b/Dalamud/Game/ChatHandlers.cs
@@ -104,7 +104,7 @@ internal partial class ChatHandlers : IServiceType
if (this.configuration.PrintDalamudWelcomeMsg)
{
- chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud {0} loaded."), Util.GetScmVersion())
+ chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud {0} loaded."), Versioning.GetScmVersion())
+ string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count(x => x.IsLoaded)));
}
@@ -116,7 +116,7 @@ internal partial class ChatHandlers : IServiceType
}
}
- if (string.IsNullOrEmpty(this.configuration.LastVersion) || !Util.AssemblyVersion.StartsWith(this.configuration.LastVersion))
+ if (string.IsNullOrEmpty(this.configuration.LastVersion) || !Versioning.GetAssemblyVersion().StartsWith(this.configuration.LastVersion))
{
var linkPayload = chatGui.AddChatLinkHandler(
(_, _) => dalamudInterface.OpenPluginInstallerTo(PluginInstallerOpenKind.Changelogs));
@@ -137,7 +137,7 @@ internal partial class ChatHandlers : IServiceType
Type = XivChatType.Notice,
});
- this.configuration.LastVersion = Util.AssemblyVersion;
+ this.configuration.LastVersion = Versioning.GetAssemblyVersion();
this.configuration.QueueSave();
}
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/Game/Gui/FlyText/FlyTextKind.cs b/Dalamud/Game/Gui/FlyText/FlyTextKind.cs
index 2b8325927..da448c683 100644
--- a/Dalamud/Game/Gui/FlyText/FlyTextKind.cs
+++ b/Dalamud/Game/Gui/FlyText/FlyTextKind.cs
@@ -92,34 +92,16 @@ public enum FlyTextKind : int
///
IslandExp = 15,
- ///
- /// Val1 in serif font next to all caps condensed font Text1 with Text2 in sans-serif as subtitle.
- ///
- [Obsolete("Use Dataset instead", true)]
- Unknown16 = 16,
-
///
/// Val1 in serif font next to all caps condensed font Text1 with Text2 in sans-serif as subtitle.
///
Dataset = 16,
- ///
- /// Val1 in serif font, Text2 in sans-serif as subtitle.
- ///
- [Obsolete("Use Knowledge instead", true)]
- Unknown17 = 17,
-
///
/// Val1 in serif font, Text2 in sans-serif as subtitle.
///
Knowledge = 17,
- ///
- /// Val1 in serif font, Text2 in sans-serif as subtitle.
- ///
- [Obsolete("Use PhantomExp instead", true)]
- Unknown18 = 18,
-
///
/// Val1 in serif font, Text2 in sans-serif as subtitle.
///
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/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs
index 4a8e6517e..f161c1868 100644
--- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs
+++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs
@@ -162,8 +162,7 @@ internal class SeStringRenderer : IServiceType
if (drawParams.Font.HasValue)
font = drawParams.Font.Value;
- // API14: Remove commented out code
- if (ThreadSafety.IsMainThread /* && drawParams.TargetDrawList is null */ && font is null)
+ if (ThreadSafety.IsMainThread && drawParams.TargetDrawList is null && font is null)
font = ImGui.GetFont();
if (font is null)
throw new ArgumentException("Specified font is empty.");
diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs
index 5edf60e9d..dcbe123e7 100644
--- a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs
+++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs
@@ -66,17 +66,10 @@ public unsafe ref struct SeStringDrawState : IDisposable
this.drawList = ssdp.TargetDrawList.Value;
this.ScreenOffset = ssdp.ScreenOffset ?? Vector2.Zero;
- // API14: Remove, always throw
- if (ThreadSafety.IsMainThread)
- {
- this.ScreenOffset = ssdp.ScreenOffset ?? ImGui.GetCursorScreenPos();
- this.FontSize = ssdp.FontSize ?? ImGui.GetFontSize();
- }
- else
- {
- throw new ArgumentException(
- $"{nameof(ssdp.FontSize)} must be set when specifying a target draw list, as it cannot be fetched from the ImGui state.");
- }
+ this.ScreenOffset = ssdp.ScreenOffset ?? throw new ArgumentException(
+ $"{nameof(ssdp.ScreenOffset)} must be set when specifying a target draw list, as it cannot be fetched from the ImGui state. (GetCursorScreenPos?)");
+ this.FontSize = ssdp.FontSize ?? throw new ArgumentException(
+ $"{nameof(ssdp.FontSize)} must be set when specifying a target draw list, as it cannot be fetched from the ImGui state.");
// this.FontSize = ssdp.FontSize ?? throw new ArgumentException(
// $"{nameof(ssdp.FontSize)} must be set when specifying a target draw list, as it cannot be fetched from the ImGui state.");
diff --git a/Dalamud/Interface/Internal/DalamudCommands.cs b/Dalamud/Interface/Internal/DalamudCommands.cs
index b1fdb5232..3e4a5cec6 100644
--- a/Dalamud/Interface/Internal/DalamudCommands.cs
+++ b/Dalamud/Interface/Internal/DalamudCommands.cs
@@ -305,12 +305,12 @@ internal class DalamudCommands : IServiceType
chatGui.Print(new SeStringBuilder()
.AddItalics("Dalamud:")
- .AddText($" {Util.GetScmVersion()}")
+ .AddText($" {Versioning.GetScmVersion()}")
.Build());
chatGui.Print(new SeStringBuilder()
.AddItalics("FFXIVCS:")
- .AddText($" {Util.GetGitHashClientStructs()}")
+ .AddText($" {Versioning.GetGitHashClientStructs()}")
.Build());
}
diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs
index b0fbeb6c5..be4228a81 100644
--- a/Dalamud/Interface/Internal/DalamudInterface.cs
+++ b/Dalamud/Interface/Internal/DalamudInterface.cs
@@ -182,7 +182,7 @@ internal class DalamudInterface : IInternalDisposableService
() => Service.GetNullable()?.ToggleDevMenu(),
VirtualKey.SHIFT);
- if (Util.GetActiveTrack() != "release")
+ if (Versioning.GetActiveTrack() != "release")
{
titleScreenMenu.AddEntryCore(
Loc.Localize("TSMDalamudDevMenu", "Developer Menu"),
@@ -865,7 +865,7 @@ internal class DalamudInterface : IInternalDisposableService
}
ImGui.MenuItem(this.dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown version", false, false);
- ImGui.MenuItem($"D: {Util.GetScmVersion()} CS: {Util.GetGitHashClientStructs()}[{FFXIVClientStructs.ThisAssembly.Git.Commits}]", false, false);
+ ImGui.MenuItem($"D: {Versioning.GetScmVersion()} CS: {Versioning.GetGitHashClientStructs()}[{FFXIVClientStructs.ThisAssembly.Git.Commits}]", false, false);
ImGui.MenuItem($"CLR: {Environment.Version}", false, false);
ImGui.EndMenu();
@@ -1076,8 +1076,8 @@ internal class DalamudInterface : IInternalDisposableService
{
ImGui.PushFont(InterfaceManager.MonoFont);
- ImGui.BeginMenu($"{Util.GetActiveTrack() ?? "???"} on {Util.GetGitBranch() ?? "???"}", false);
- ImGui.BeginMenu($"{Util.GetScmVersion()}", false);
+ ImGui.BeginMenu($"{Versioning.GetActiveTrack() ?? "???"} on {Versioning.GetGitBranch() ?? "???"}", false);
+ ImGui.BeginMenu($"{Versioning.GetScmVersion()}", false);
ImGui.BeginMenu(this.FrameCount.ToString("000000"), false);
ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("000"), false);
ImGui.BeginMenu($"W:{Util.FormatBytes(GC.GetTotalMemory(false))}", false);
diff --git a/Dalamud/Interface/Internal/Windows/BranchSwitcherWindow.cs b/Dalamud/Interface/Internal/Windows/BranchSwitcherWindow.cs
index 9cc14ea14..51a9c48a6 100644
--- a/Dalamud/Interface/Internal/Windows/BranchSwitcherWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/BranchSwitcherWindow.cs
@@ -47,7 +47,7 @@ public class BranchSwitcherWindow : Window
this.branches = await client.GetFromJsonAsync>(BranchInfoUrl);
Debug.Assert(this.branches != null, "this.branches != null");
- var trackName = Util.GetActiveTrack();
+ var trackName = Versioning.GetActiveTrack();
this.selectedBranchIndex = this.branches.IndexOf(x => x.Value.Track == trackName);
if (this.selectedBranchIndex == -1)
{
diff --git a/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs b/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs
index b0a910ead..44626ba31 100644
--- a/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs
@@ -147,7 +147,7 @@ internal sealed class ChangelogWindow : Window, IDisposable
var pmWantsChangelog = pm?.InstalledPlugins.Any() ?? true;
return (string.IsNullOrEmpty(configuration.LastChangelogMajorMinor) ||
(!WarrantsChangelogForMajorMinor.StartsWith(configuration.LastChangelogMajorMinor) &&
- Util.AssemblyVersion.StartsWith(WarrantsChangelogForMajorMinor))) && pmWantsChangelog;
+ Versioning.GetAssemblyVersion().StartsWith(WarrantsChangelogForMajorMinor))) && pmWantsChangelog;
}
///
@@ -357,7 +357,7 @@ internal sealed class ChangelogWindow : Window, IDisposable
{
case State.WindowFadeIn:
case State.ExplainerIntro:
- ImGui.TextWrapped($"Welcome to Dalamud v{Util.GetScmVersion()}!");
+ ImGui.TextWrapped($"Welcome to Dalamud v{Versioning.GetScmVersion()}!");
ImGuiHelpers.ScaledDummy(5);
ImGui.TextWrapped(ChangeLog);
ImGuiHelpers.ScaledDummy(5);
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/Data/Widgets/SeStringCreatorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs
index a88f576f9..e9b4022e4 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs
@@ -144,7 +144,7 @@ internal class SeStringCreatorWidget : IDataWindowWidget
new TextEntry(TextEntryType.Macro, " "),
];
- private SeStringParameter[]? localParameters = [Util.GetScmVersion()];
+ private SeStringParameter[]? localParameters = [Versioning.GetScmVersion()];
private ReadOnlySeString input;
private ClientLanguage? language;
private Task? validImportSheetNamesTask;
diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
index ac092bd25..3241015fc 100644
--- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
@@ -302,7 +302,7 @@ internal class PluginInstallerWindow : Window, IDisposable
this.profileManagerWidget.Reset();
- if (this.staleDalamudNewVersion == null && !Util.GetActiveTrack().IsNullOrEmpty())
+ if (this.staleDalamudNewVersion == null && !Versioning.GetActiveTrack().IsNullOrEmpty())
{
Service.Get().GetVersionForCurrentTrack().ContinueWith(t =>
{
@@ -310,7 +310,7 @@ internal class PluginInstallerWindow : Window, IDisposable
return;
var versionInfo = t.Result;
- if (versionInfo.AssemblyVersion != Util.GetScmVersion())
+ if (versionInfo.AssemblyVersion != Versioning.GetScmVersion())
{
this.staleDalamudNewVersion = versionInfo.AssemblyVersion;
}
@@ -1670,7 +1670,7 @@ internal class PluginInstallerWindow : Window, IDisposable
DrawWarningIcon();
DrawLinesCentered("A new version of Dalamud is available.\n" +
"Please restart the game to ensure compatibility with updated plugins.\n" +
- $"old: {Util.GetScmVersion()} new: {this.staleDalamudNewVersion}");
+ $"old: {Versioning.GetScmVersion()} new: {this.staleDalamudNewVersion}");
ImGuiHelpers.ScaledDummy(10);
}
@@ -2461,7 +2461,7 @@ internal class PluginInstallerWindow : Window, IDisposable
var isOutdated = effectiveApiLevel < PluginManager.DalamudApiLevel;
var isIncompatible = manifest.MinimumDalamudVersion != null &&
- manifest.MinimumDalamudVersion > Util.AssemblyVersionParsed;
+ manifest.MinimumDalamudVersion > Versioning.GetAssemblyVersionParsed();
var enableInstallButton = this.updateStatus != OperationStatus.InProgress &&
this.installStatus != OperationStatus.InProgress &&
diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs
index 74b9b0fd7..4785ceb3c 100644
--- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs
+++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs
@@ -223,7 +223,7 @@ Contribute at: https://github.com/goatcorp/Dalamud
.Select(plugin => $"{plugin.Manifest.Name} by {plugin.Manifest.Author}\n")
.Aggregate(string.Empty, (current, next) => $"{current}{next}");
- this.creditsText = string.Format(CreditsTextTempl, typeof(Dalamud).Assembly.GetName().Version, pluginCredits, Util.GetGitHashClientStructs());
+ this.creditsText = string.Format(CreditsTextTempl, typeof(Dalamud).Assembly.GetName().Version, pluginCredits, Versioning.GetGitHashClientStructs());
var gameGui = Service.Get();
var playerState = PlayerState.Instance();
diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs
index 69cdc4d28..f826da622 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.
@@ -503,7 +503,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
lssb.PushEdgeColorType(701).PushColorType(539)
.Append(SeIconChar.BoxedLetterD.ToIconChar())
.PopColorType().PopEdgeColorType();
- lssb.Append($" Dalamud: {Util.GetScmVersion()}");
+ lssb.Append($" Dalamud: {Versioning.GetScmVersion()}");
lssb.Append($" - {count} {(count != 1 ? "plugins" : "plugin")} loaded");
diff --git a/Dalamud/Networking/Http/HappyHttpClient.cs b/Dalamud/Networking/Http/HappyHttpClient.cs
index aeed98695..c6a476fff 100644
--- a/Dalamud/Networking/Http/HappyHttpClient.cs
+++ b/Dalamud/Networking/Http/HappyHttpClient.cs
@@ -36,7 +36,7 @@ internal class HappyHttpClient : IInternalDisposableService
{
UserAgent =
{
- new ProductInfoHeaderValue("Dalamud", Util.AssemblyVersion),
+ new ProductInfoHeaderValue("Dalamud", Versioning.GetAssemblyVersion()),
},
},
};
diff --git a/Dalamud/Networking/Rpc/Service/ClientHelloService.cs b/Dalamud/Networking/Rpc/Service/ClientHelloService.cs
index c5a4c851a..ae8319f21 100644
--- a/Dalamud/Networking/Rpc/Service/ClientHelloService.cs
+++ b/Dalamud/Networking/Rpc/Service/ClientHelloService.cs
@@ -38,7 +38,7 @@ internal sealed class ClientHelloService : IInternalDisposableService
return new ClientHelloResponse
{
ApiVersion = "1.0",
- DalamudVersion = Util.GetScmVersion(),
+ DalamudVersion = Versioning.GetScmVersion(),
GameVersion = dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown",
ProcessId = Environment.ProcessId,
ProcessStartTime = new DateTimeOffset(Process.GetCurrentProcess().StartTime).ToUnixTimeSeconds(),
diff --git a/Dalamud/Plugin/DalamudPluginInterface.cs b/Dalamud/Plugin/DalamudPluginInterface.cs
index 6fd9064b6..1051f908c 100644
--- a/Dalamud/Plugin/DalamudPluginInterface.cs
+++ b/Dalamud/Plugin/DalamudPluginInterface.cs
@@ -16,18 +16,15 @@ using Dalamud.Game.Text;
using Dalamud.Game.Text.Sanitizer;
using Dalamud.Interface;
using Dalamud.Interface.Internal;
-using Dalamud.Interface.Internal.Windows.PluginInstaller;
-using Dalamud.Interface.Internal.Windows.SelfTest;
-using Dalamud.Interface.Internal.Windows.Settings;
using Dalamud.IoC.Internal;
using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Internal.AutoUpdate;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Internal.Types.Manifest;
using Dalamud.Plugin.Ipc;
-using Dalamud.Plugin.Ipc.Exceptions;
using Dalamud.Plugin.Ipc.Internal;
-using Dalamud.Plugin.Services;
+using Dalamud.Plugin.VersionInfo;
+using Dalamud.Utility;
using Serilog;
@@ -204,11 +201,7 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa
return true;
}
- ///
- /// Gets the plugin the given assembly is part of.
- ///
- /// The assembly to check.
- /// The plugin the given assembly is part of, or null if this is a shared assembly or if this information cannot be determined.
+ ///
public IExposedPlugin? GetPlugin(Assembly assembly)
=> AssemblyLoadContext.GetLoadContext(assembly) switch
{
@@ -216,11 +209,7 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa
var context => this.GetPlugin(context),
};
- ///
- /// Gets the plugin that loads in the given context.
- ///
- /// The context to check.
- /// The plugin that loads in the given context, or null if this isn't a plugin's context or if this information cannot be determined.
+ ///
public IExposedPlugin? GetPlugin(AssemblyLoadContext context)
=> Service.Get().InstalledPlugins.FirstOrDefault(p => p.LoadsIn(context)) switch
{
@@ -228,6 +217,12 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa
var p => new ExposedPlugin(p),
};
+ ///
+ public IDalamudVersionInfo GetDalamudVersion()
+ {
+ return new DalamudVersionInfo(Versioning.GetAssemblyVersionParsed(), Versioning.GetActiveTrack());
+ }
+
#region IPC
///
diff --git a/Dalamud/Plugin/IDalamudPluginInterface.cs b/Dalamud/Plugin/IDalamudPluginInterface.cs
index d1b6977d4..92ecab006 100644
--- a/Dalamud/Plugin/IDalamudPluginInterface.cs
+++ b/Dalamud/Plugin/IDalamudPluginInterface.cs
@@ -15,7 +15,7 @@ using Dalamud.Plugin.Internal.Types.Manifest;
using Dalamud.Plugin.Ipc;
using Dalamud.Plugin.Ipc.Exceptions;
using Dalamud.Plugin.Ipc.Internal;
-using Dalamud.Plugin.Services;
+using Dalamud.Plugin.VersionInfo;
namespace Dalamud.Plugin;
@@ -194,6 +194,12 @@ public interface IDalamudPluginInterface : IServiceProvider
/// The plugin that loads in the given context, or null if this isn't a plugin's context or if this information cannot be determined.
IExposedPlugin? GetPlugin(AssemblyLoadContext context);
+ ///
+ /// Gets information about the version of Dalamud this plugin is loaded into.
+ ///
+ /// Class containing version information.
+ IDalamudVersionInfo GetDalamudVersion();
+
///
T GetOrCreateData(string tag, Func dataGenerator) where T : class;
diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs
index e2eded57c..193a2d45f 100644
--- a/Dalamud/Plugin/Internal/PluginManager.cs
+++ b/Dalamud/Plugin/Internal/PluginManager.cs
@@ -1790,7 +1790,7 @@ internal class PluginManager : IInternalDisposableService
var updates = this.AvailablePlugins
.Where(remoteManifest => plugin.Manifest.InternalName == remoteManifest.InternalName)
.Where(remoteManifest => plugin.Manifest.InstalledFromUrl == remoteManifest.SourceRepo.PluginMasterUrl || !remoteManifest.SourceRepo.IsThirdParty)
- .Where(remoteManifest => remoteManifest.MinimumDalamudVersion == null || Util.AssemblyVersionParsed >= remoteManifest.MinimumDalamudVersion)
+ .Where(remoteManifest => remoteManifest.MinimumDalamudVersion == null || Versioning.GetAssemblyVersionParsed() >= remoteManifest.MinimumDalamudVersion)
.Where(remoteManifest =>
{
var useTesting = this.UseTesting(remoteManifest);
diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs
index 0197683ef..1fe18b95b 100644
--- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs
+++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs
@@ -315,7 +315,7 @@ internal class LocalPlugin : IAsyncDisposable
if (!this.CheckPolicy())
throw new PluginPreconditionFailedException($"Unable to load {this.Name} as a load policy forbids it");
- if (this.Manifest.MinimumDalamudVersion != null && this.Manifest.MinimumDalamudVersion > Util.AssemblyVersionParsed)
+ if (this.Manifest.MinimumDalamudVersion != null && this.Manifest.MinimumDalamudVersion > Versioning.GetAssemblyVersionParsed())
throw new PluginPreconditionFailedException($"Unable to load {this.Name}, Dalamud version is lower than minimum required version {this.Manifest.MinimumDalamudVersion}");
this.State = PluginState.Loading;
diff --git a/Dalamud/Plugin/Internal/Types/PluginRepository.cs b/Dalamud/Plugin/Internal/Types/PluginRepository.cs
index c5e703e4b..d5c1131af 100644
--- a/Dalamud/Plugin/Internal/Types/PluginRepository.cs
+++ b/Dalamud/Plugin/Internal/Types/PluginRepository.cs
@@ -59,7 +59,7 @@ internal class PluginRepository
},
UserAgent =
{
- new ProductInfoHeaderValue("Dalamud", Util.AssemblyVersion),
+ new ProductInfoHeaderValue("Dalamud", Versioning.GetAssemblyVersion()),
},
},
};
@@ -164,7 +164,7 @@ internal class PluginRepository
}
this.PluginMaster = pluginMaster.Where(this.IsValidManifest).ToList().AsReadOnly();
-
+
// API9 HACK: Force IsHide to false, we should remove that
if (!this.IsThirdParty)
{
@@ -197,7 +197,7 @@ internal class PluginRepository
Log.Error("Plugin {PluginName} in {RepoLink} has an invalid Name.", manifest.InternalName, this.PluginMasterUrl);
return false;
}
-
+
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (manifest.AssemblyVersion == null)
{
@@ -224,7 +224,7 @@ internal class PluginRepository
request.Headers.CacheControl = new CacheControlHeaderValue { NoCache = true };
using var requestCts = new CancellationTokenSource(TimeSpan.FromSeconds(timeout));
-
+
return await httpClient.SendAsync(request, requestCts.Token);
}
}
diff --git a/Dalamud/Plugin/VersionInfo/DalamudVersionInfo.cs b/Dalamud/Plugin/VersionInfo/DalamudVersionInfo.cs
new file mode 100644
index 000000000..c87c012af
--- /dev/null
+++ b/Dalamud/Plugin/VersionInfo/DalamudVersionInfo.cs
@@ -0,0 +1,11 @@
+namespace Dalamud.Plugin.VersionInfo;
+
+///
+internal class DalamudVersionInfo(Version version, string? track) : IDalamudVersionInfo
+{
+ ///
+ public Version Version { get; } = version;
+
+ ///
+ public string? BetaTrack { get; } = track;
+}
diff --git a/Dalamud/Plugin/VersionInfo/IDalamudVersionInfo.cs b/Dalamud/Plugin/VersionInfo/IDalamudVersionInfo.cs
new file mode 100644
index 000000000..e6b6a9601
--- /dev/null
+++ b/Dalamud/Plugin/VersionInfo/IDalamudVersionInfo.cs
@@ -0,0 +1,19 @@
+namespace Dalamud.Plugin.VersionInfo;
+
+///
+/// Interface exposing various information related to Dalamud versioning.
+///
+public interface IDalamudVersionInfo
+{
+ ///
+ /// Gets the Dalamud version.
+ ///
+ Version Version { get; }
+
+ ///
+ /// Gets the currently used beta track.
+ /// Please don't tell users to switch branches. They have it bad enough, fix your things instead.
+ /// Null if this build wasn't launched from XIVLauncher.
+ ///
+ string? BetaTrack { get; }
+}
diff --git a/Dalamud/Support/BugBait.cs b/Dalamud/Support/BugBait.cs
index 7ce96208c..f0a98ca98 100644
--- a/Dalamud/Support/BugBait.cs
+++ b/Dalamud/Support/BugBait.cs
@@ -37,7 +37,7 @@ internal static class BugBait
Name = plugin.InternalName,
Version = isTesting ? plugin.TestingAssemblyVersion?.ToString() : plugin.AssemblyVersion.ToString(),
Platform = Util.GetHostPlatform().ToString(),
- DalamudHash = Util.GetScmVersion(),
+ DalamudHash = Versioning.GetScmVersion(),
};
if (includeException)
diff --git a/Dalamud/Support/DalamudReleases.cs b/Dalamud/Support/DalamudReleases.cs
index 603c77487..949ebf94a 100644
--- a/Dalamud/Support/DalamudReleases.cs
+++ b/Dalamud/Support/DalamudReleases.cs
@@ -38,7 +38,7 @@ internal class DalamudReleases : IServiceType
/// The version info for the current track.
public async Task GetVersionForCurrentTrack()
{
- var currentTrack = Util.GetActiveTrack();
+ var currentTrack = Versioning.GetActiveTrack();
if (currentTrack.IsNullOrEmpty())
return null;
diff --git a/Dalamud/Support/Troubleshooting.cs b/Dalamud/Support/Troubleshooting.cs
index 88048c462..de529a29b 100644
--- a/Dalamud/Support/Troubleshooting.cs
+++ b/Dalamud/Support/Troubleshooting.cs
@@ -69,11 +69,11 @@ public static class Troubleshooting
LoadedPlugins = pluginManager?.InstalledPlugins?.Select(x => x.Manifest as LocalPluginManifest)?.OrderByDescending(x => x.InternalName).ToArray(),
PluginStates = pluginManager?.InstalledPlugins?.Where(x => !x.IsDev).ToDictionary(x => x.Manifest.InternalName, x => x.IsBanned ? "Banned" : x.State.ToString()),
EverStartedLoadingPlugins = pluginManager?.InstalledPlugins.Where(x => x.HasEverStartedLoad).Select(x => x.InternalName).ToList(),
- DalamudVersion = Util.GetScmVersion(),
- DalamudGitHash = Util.GetGitHash() ?? "Unknown",
+ DalamudVersion = Versioning.GetScmVersion(),
+ DalamudGitHash = Versioning.GetGitHash() ?? "Unknown",
GameVersion = startInfo.GameVersion?.ToString() ?? "Unknown",
Language = startInfo.Language.ToString(),
- BetaKey = Util.GetActiveTrack(),
+ BetaKey = Versioning.GetActiveTrack(),
DoPluginTest = configuration.DoPluginTest,
LoadAllApiLevels = pluginManager?.LoadAllApiLevels == true,
InterfaceLoaded = interfaceManager?.IsReady ?? false,
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..0ea5bbcbf 100644
--- a/Dalamud/Utility/Util.cs
+++ b/Dalamud/Utility/Util.cs
@@ -68,96 +68,10 @@ public static partial class Util
];
private static readonly Type GenericSpanType = typeof(Span<>);
- private static string? scmVersionInternal;
- private static string? gitHashInternal;
- private static string? gitHashClientStructsInternal;
- private static string? branchInternal;
private static ulong moduleStartAddr;
private static ulong moduleEndAddr;
- ///
- /// Gets the Dalamud version.
- ///
- [Api13ToDo("Remove. Make both versions here internal. Add an API somewhere.")]
- public static string AssemblyVersion { get; } =
- Assembly.GetAssembly(typeof(ChatHandlers))!.GetName().Version!.ToString();
-
- ///
- /// Gets the Dalamud version.
- ///
- internal static Version AssemblyVersionParsed { get; } =
- Assembly.GetAssembly(typeof(ChatHandlers))!.GetName().Version!;
-
- ///
- /// Gets the SCM Version from the assembly, or null if it cannot be found. This method will generally return
- /// the git describe output for this build, which will be a raw version if this is a stable build or an
- /// appropriately-annotated version if this is *not* stable. Local builds will return a `Local Build` text string.
- ///
- /// The SCM version of the assembly.
- public static string GetScmVersion()
- {
- if (scmVersionInternal != null) return scmVersionInternal;
-
- var asm = typeof(Util).Assembly;
- var attrs = asm.GetCustomAttributes();
-
- return scmVersionInternal = attrs.First(a => a.Key == "SCMVersion").Value
- ?? asm.GetName().Version!.ToString();
- }
-
- ///
- /// Gets the git commit hash value from the assembly or null if it cannot be found. Will be null for Debug builds,
- /// and will be suffixed with `-dirty` if in release with pending changes.
- ///
- /// The git hash of the assembly.
- public static string? GetGitHash()
- {
- if (gitHashInternal != null)
- return gitHashInternal;
-
- var asm = typeof(Util).Assembly;
- var attrs = asm.GetCustomAttributes();
-
- return gitHashInternal = attrs.FirstOrDefault(a => a.Key == "GitHash")?.Value ?? "N/A";
- }
-
- ///
- /// Gets the git hash value from the assembly or null if it cannot be found.
- ///
- /// The git hash of the assembly.
- public static string? GetGitHashClientStructs()
- {
- if (gitHashClientStructsInternal != null)
- return gitHashClientStructsInternal;
-
- var asm = typeof(Util).Assembly;
- var attrs = asm.GetCustomAttributes();
-
- gitHashClientStructsInternal = attrs.First(a => a.Key == "GitHashClientStructs").Value;
-
- return gitHashClientStructsInternal;
- }
-
- ///
- /// Gets the Git branch name this version of Dalamud was built from, or null, if this is a Debug build.
- ///
- /// The branch name.
- public static string? GetGitBranch()
- {
- if (branchInternal != null)
- return branchInternal;
-
- var asm = typeof(Util).Assembly;
- var attrs = asm.GetCustomAttributes();
-
- var gitBranch = attrs.FirstOrDefault(a => a.Key == "GitBranch")?.Value;
- if (gitBranch == null)
- return null;
-
- return branchInternal = gitBranch;
- }
-
///
public static unsafe string DescribeAddress(void* p) => DescribeAddress((nint)p);
@@ -693,16 +607,6 @@ public static partial class Util
}
}
- ///
- /// Gets the active Dalamud track, if this instance was launched through XIVLauncher and used a version
- /// downloaded from webservices.
- ///
- /// The name of the track, or null.
- internal static string? GetActiveTrack()
- {
- return Environment.GetEnvironmentVariable("DALAMUD_BRANCH");
- }
-
///
/// Gets a random, inoffensive, human-friendly string.
///
diff --git a/Dalamud/Utility/Versioning.cs b/Dalamud/Utility/Versioning.cs
new file mode 100644
index 000000000..d3b30b834
--- /dev/null
+++ b/Dalamud/Utility/Versioning.cs
@@ -0,0 +1,108 @@
+using System.Linq;
+using System.Reflection;
+
+namespace Dalamud.Utility;
+
+///
+/// Helpers to access Dalamud versioning information.
+///
+internal static class Versioning
+{
+ private static string? scmVersionInternal;
+ private static string? gitHashInternal;
+ private static string? gitHashClientStructsInternal;
+ private static string? branchInternal;
+
+ ///
+ /// Gets the Dalamud version.
+ ///
+ /// The raw Dalamud assembly version.
+ internal static string GetAssemblyVersion() =>
+ Assembly.GetAssembly(typeof(Versioning))!.GetName().Version!.ToString();
+
+ ///
+ /// Gets the Dalamud version.
+ ///
+ /// The parsed Dalamud assembly version.
+ internal static Version GetAssemblyVersionParsed() =>
+ Assembly.GetAssembly(typeof(Versioning))!.GetName().Version!;
+
+ ///
+ /// Gets the SCM Version from the assembly, or null if it cannot be found. This method will generally return
+ /// the git describe output for this build, which will be a raw version if this is a stable build or an
+ /// appropriately-annotated version if this is *not* stable. Local builds will return a `Local Build` text string.
+ ///
+ /// The SCM version of the assembly.
+ internal static string GetScmVersion()
+ {
+ if (scmVersionInternal != null) return scmVersionInternal;
+
+ var asm = typeof(Util).Assembly;
+ var attrs = asm.GetCustomAttributes();
+
+ return scmVersionInternal = attrs.First(a => a.Key == "SCMVersion").Value
+ ?? asm.GetName().Version!.ToString();
+ }
+
+ ///
+ /// Gets the git commit hash value from the assembly or null if it cannot be found. Will be null for Debug builds,
+ /// and will be suffixed with `-dirty` if in release with pending changes.
+ ///
+ /// The git hash of the assembly.
+ internal static string? GetGitHash()
+ {
+ if (gitHashInternal != null)
+ return gitHashInternal;
+
+ var asm = typeof(Util).Assembly;
+ var attrs = asm.GetCustomAttributes();
+
+ return gitHashInternal = attrs.FirstOrDefault(a => a.Key == "GitHash")?.Value ?? "N/A";
+ }
+
+ ///
+ /// Gets the git hash value from the assembly or null if it cannot be found.
+ ///
+ /// The git hash of the assembly.
+ internal static string? GetGitHashClientStructs()
+ {
+ if (gitHashClientStructsInternal != null)
+ return gitHashClientStructsInternal;
+
+ var asm = typeof(Util).Assembly;
+ var attrs = asm.GetCustomAttributes();
+
+ gitHashClientStructsInternal = attrs.First(a => a.Key == "GitHashClientStructs").Value;
+
+ return gitHashClientStructsInternal;
+ }
+
+ ///
+ /// Gets the Git branch name this version of Dalamud was built from, or null, if this is a Debug build.
+ ///
+ /// The branch name.
+ internal static string? GetGitBranch()
+ {
+ if (branchInternal != null)
+ return branchInternal;
+
+ var asm = typeof(Util).Assembly;
+ var attrs = asm.GetCustomAttributes();
+
+ var gitBranch = attrs.FirstOrDefault(a => a.Key == "GitBranch")?.Value;
+ if (gitBranch == null)
+ return null;
+
+ return branchInternal = gitBranch;
+ }
+
+ ///
+ /// Gets the active Dalamud track, if this instance was launched through XIVLauncher and used a version
+ /// downloaded from webservices.
+ ///
+ /// The name of the track, or null.
+ internal static string? GetActiveTrack()
+ {
+ return Environment.GetEnvironmentVariable("DALAMUD_BRANCH");
+ }
+}
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);