mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-20 14:57:45 +01:00
Compare commits
44 commits
fecba89710
...
2e7c48316f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e7c48316f | ||
|
|
8a742e7e59 | ||
|
|
56325afa7f | ||
|
|
1bff6abae9 | ||
|
|
d7935d6dd4 | ||
|
|
a715725a9d | ||
|
|
bc8e986c11 | ||
|
|
ffd99d5791 | ||
|
|
20af5b40c7 | ||
|
|
a1409096fd | ||
|
|
4d9751ea5f | ||
|
|
2dbae05522 | ||
|
|
45366efd9f | ||
|
|
0480693f92 | ||
|
|
85a7c60dae | ||
|
|
c923884626 | ||
|
|
78781c8988 | ||
|
|
b81cb9c74c | ||
|
|
8e8d0246bc | ||
|
|
d47a41b295 | ||
|
|
c9276b1771 | ||
|
|
386828005b | ||
|
|
08c1768286 | ||
|
|
eb9555ee22 | ||
|
|
be3f71dc73 | ||
|
|
e01acb4a80 | ||
|
|
f8725e5f37 | ||
|
|
c3e3e4aa85 | ||
|
|
b82b4f40ce | ||
|
|
4f59e09513 | ||
|
|
0533872a73 | ||
|
|
27a7adfdb9 | ||
|
|
54bac7f32a | ||
|
|
26f119096b | ||
|
|
c51e65e0bd | ||
|
|
b8724f7a59 | ||
|
|
170f6e0859 | ||
|
|
325d28ee32 | ||
|
|
29c154f9b5 | ||
|
|
166f249e13 | ||
|
|
c525655be6 | ||
|
|
ab0500ca6f | ||
|
|
2c1bb76643 | ||
|
|
9a1fae8246 |
60 changed files with 1260 additions and 1275 deletions
|
|
@ -11,7 +11,7 @@ namespace Dalamud.Configuration;
|
|||
/// <summary>
|
||||
/// Configuration to store settings for a dalamud plugin.
|
||||
/// </summary>
|
||||
[Api13ToDo("Make this a service. We need to be able to dispose it reliably to write configs asynchronously. Maybe also let people write files with vfs.")]
|
||||
[Api14ToDo("Make this a service. We need to be able to dispose it reliably to write configs asynchronously. Maybe also let people write files with vfs.")]
|
||||
public sealed class PluginConfigurations
|
||||
{
|
||||
private readonly DirectoryInfo configDirectory;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -1,107 +0,0 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>Argument pool for Addon Lifecycle services.</summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed class AddonLifecyclePooledArgs : IServiceType
|
||||
{
|
||||
private readonly AddonSetupArgs?[] addonSetupArgPool = new AddonSetupArgs?[64];
|
||||
private readonly AddonFinalizeArgs?[] addonFinalizeArgPool = new AddonFinalizeArgs?[64];
|
||||
private readonly AddonDrawArgs?[] addonDrawArgPool = new AddonDrawArgs?[64];
|
||||
private readonly AddonUpdateArgs?[] addonUpdateArgPool = new AddonUpdateArgs?[64];
|
||||
private readonly AddonRefreshArgs?[] addonRefreshArgPool = new AddonRefreshArgs?[64];
|
||||
private readonly AddonRequestedUpdateArgs?[] addonRequestedUpdateArgPool = new AddonRequestedUpdateArgs?[64];
|
||||
private readonly AddonReceiveEventArgs?[] addonReceiveEventArgPool = new AddonReceiveEventArgs?[64];
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AddonLifecyclePooledArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Rents an instance of an argument.</summary>
|
||||
/// <param name="arg">The rented instance.</param>
|
||||
/// <returns>The returner.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledEntry<AddonSetupArgs> Rent(out AddonSetupArgs arg) => new(out arg, this.addonSetupArgPool);
|
||||
|
||||
/// <summary>Rents an instance of an argument.</summary>
|
||||
/// <param name="arg">The rented instance.</param>
|
||||
/// <returns>The returner.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledEntry<AddonFinalizeArgs> Rent(out AddonFinalizeArgs arg) => new(out arg, this.addonFinalizeArgPool);
|
||||
|
||||
/// <summary>Rents an instance of an argument.</summary>
|
||||
/// <param name="arg">The rented instance.</param>
|
||||
/// <returns>The returner.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledEntry<AddonDrawArgs> Rent(out AddonDrawArgs arg) => new(out arg, this.addonDrawArgPool);
|
||||
|
||||
/// <summary>Rents an instance of an argument.</summary>
|
||||
/// <param name="arg">The rented instance.</param>
|
||||
/// <returns>The returner.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledEntry<AddonUpdateArgs> Rent(out AddonUpdateArgs arg) => new(out arg, this.addonUpdateArgPool);
|
||||
|
||||
/// <summary>Rents an instance of an argument.</summary>
|
||||
/// <param name="arg">The rented instance.</param>
|
||||
/// <returns>The returner.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledEntry<AddonRefreshArgs> Rent(out AddonRefreshArgs arg) => new(out arg, this.addonRefreshArgPool);
|
||||
|
||||
/// <summary>Rents an instance of an argument.</summary>
|
||||
/// <param name="arg">The rented instance.</param>
|
||||
/// <returns>The returner.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledEntry<AddonRequestedUpdateArgs> Rent(out AddonRequestedUpdateArgs arg) =>
|
||||
new(out arg, this.addonRequestedUpdateArgPool);
|
||||
|
||||
/// <summary>Rents an instance of an argument.</summary>
|
||||
/// <param name="arg">The rented instance.</param>
|
||||
/// <returns>The returner.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledEntry<AddonReceiveEventArgs> Rent(out AddonReceiveEventArgs arg) =>
|
||||
new(out arg, this.addonReceiveEventArgPool);
|
||||
|
||||
/// <summary>Returns the object to the pool on dispose.</summary>
|
||||
/// <typeparam name="T">The type.</typeparam>
|
||||
public readonly ref struct PooledEntry<T>
|
||||
where T : AddonArgs, new()
|
||||
{
|
||||
private readonly Span<T> pool;
|
||||
private readonly T obj;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="PooledEntry{T}"/> struct.</summary>
|
||||
/// <param name="arg">An instance of the argument.</param>
|
||||
/// <param name="pool">The pool to rent from and return to.</param>
|
||||
public PooledEntry(out T arg, Span<T> pool)
|
||||
{
|
||||
this.pool = pool;
|
||||
foreach (ref var item in pool)
|
||||
{
|
||||
if (Interlocked.Exchange(ref item, null) is { } v)
|
||||
{
|
||||
this.obj = arg = v;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.obj = arg = new();
|
||||
}
|
||||
|
||||
/// <summary>Returns the item to the pool.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
var tmp = this.obj;
|
||||
foreach (ref var item in this.pool)
|
||||
{
|
||||
if (Interlocked.Exchange(ref item, tmp) is not { } tmp2)
|
||||
return;
|
||||
tmp = tmp2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,19 +5,24 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|||
/// <summary>
|
||||
/// Base class for AddonLifecycle AddonArgTypes.
|
||||
/// </summary>
|
||||
public abstract unsafe class AddonArgs
|
||||
public class AddonArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Constant string representing the name of an addon that is invalid.
|
||||
/// </summary>
|
||||
public const string InvalidAddon = "NullAddon";
|
||||
|
||||
private string? addonName;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonArgs"/> class.
|
||||
/// </summary>
|
||||
internal AddonArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the addon this args referrers to.
|
||||
/// </summary>
|
||||
public string AddonName => this.GetAddonName();
|
||||
public string AddonName { get; private set; } = InvalidAddon;
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of these args.
|
||||
/// </summary>
|
||||
public abstract AddonArgsType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if addon name matches the given span of char.
|
||||
/// </summary>
|
||||
/// <param name="name">The name to check.</param>
|
||||
/// <returns>Whether it is the case.</returns>
|
||||
internal bool IsAddon(string name)
|
||||
{
|
||||
if (this.Addon.IsNull)
|
||||
return false;
|
||||
|
||||
if (name.Length is 0 or > 32)
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrEmpty(this.Addon.Name))
|
||||
return false;
|
||||
|
||||
return name == this.Addon.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears this AddonArgs values.
|
||||
/// </summary>
|
||||
internal virtual void Clear()
|
||||
{
|
||||
this.addonName = null;
|
||||
this.Addon = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for ensuring the name of the addon is valid.
|
||||
/// </summary>
|
||||
/// <returns>The name of the addon for this object. <see cref="InvalidAddon"/> when invalid.</returns>
|
||||
private string GetAddonName()
|
||||
{
|
||||
if (this.Addon.IsNull) return InvalidAddon;
|
||||
|
||||
var name = this.Addon.Name;
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return InvalidAddon;
|
||||
|
||||
return this.addonName ??= name;
|
||||
}
|
||||
public virtual AddonArgsType Type => AddonArgsType.Generic;
|
||||
}
|
||||
|
|
|
|||
22
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonCloseArgs.cs
Normal file
22
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonCloseArgs.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Close events.
|
||||
/// </summary>
|
||||
public class AddonCloseArgs : AddonArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonCloseArgs"/> class.
|
||||
/// </summary>
|
||||
internal AddonCloseArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Close;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the window should fire the callback method on close.
|
||||
/// </summary>
|
||||
public bool FireCallback { get; set; }
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Draw events.
|
||||
/// </summary>
|
||||
public class AddonDrawArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonDrawArgs"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Not intended for public construction.", false)]
|
||||
public AddonDrawArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Draw;
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonDrawArgs Clone() => (AddonDrawArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for ReceiveEvent events.
|
||||
/// </summary>
|
||||
public class AddonFinalizeArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonFinalizeArgs"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Not intended for public construction.", false)]
|
||||
public AddonFinalizeArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Finalize;
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonFinalizeArgs Clone() => (AddonFinalizeArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
32
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonHideArgs.cs
Normal file
32
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonHideArgs.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Hide events.
|
||||
/// </summary>
|
||||
public class AddonHideArgs : AddonArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonHideArgs"/> class.
|
||||
/// </summary>
|
||||
internal AddonHideArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Hide;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to call the hide callback handler when this hides.
|
||||
/// </summary>
|
||||
public bool CallHideCallback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flags that the window will set when it Shows/Hides.
|
||||
/// </summary>
|
||||
public uint SetShowHideFlags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether something for this event message.
|
||||
/// </summary>
|
||||
internal bool UnknownBool { get; set; }
|
||||
}
|
||||
|
|
@ -3,13 +3,12 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|||
/// <summary>
|
||||
/// Addon argument data for ReceiveEvent events.
|
||||
/// </summary>
|
||||
public class AddonReceiveEventArgs : AddonArgs, ICloneable
|
||||
public class AddonReceiveEventArgs : AddonArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonReceiveEventArgs"/> class.
|
||||
/// </summary>
|
||||
[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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pointer to a block of data for this event message.
|
||||
/// Gets or sets the pointer to an AtkEventData for this event message.
|
||||
/// </summary>
|
||||
public nint Data { get; set; }
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonReceiveEventArgs Clone() => (AddonReceiveEventArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
|
||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
||||
internal override void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
this.AtkEventType = default;
|
||||
this.EventParam = default;
|
||||
this.AtkEvent = default;
|
||||
this.Data = default;
|
||||
}
|
||||
public nint AtkEventData { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Refresh events.
|
||||
/// </summary>
|
||||
public class AddonRefreshArgs : AddonArgs, ICloneable
|
||||
public class AddonRefreshArgs : AddonArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonRefreshArgs"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Not intended for public construction.", false)]
|
||||
public AddonRefreshArgs()
|
||||
internal AddonRefreshArgs()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -31,19 +36,30 @@ public class AddonRefreshArgs : AddonArgs, ICloneable
|
|||
/// <summary>
|
||||
/// Gets the AtkValues in the form of a span.
|
||||
/// </summary>
|
||||
[Obsolete("Pending removal, Use AtkValueEnumerable instead.")]
|
||||
[Api15ToDo("Make this internal, remove obsolete")]
|
||||
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonRefreshArgs Clone() => (AddonRefreshArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
|
||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
||||
internal override void Clear()
|
||||
/// <summary>
|
||||
/// Gets an enumerable collection of <see cref="AtkValuePtr"/> of the event's AtkValues.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An <see cref="IEnumerable{T}"/> of <see cref="AtkValuePtr"/> corresponding to the event's AtkValues.
|
||||
/// </returns>
|
||||
public IEnumerable<AtkValuePtr> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,12 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|||
/// <summary>
|
||||
/// Addon argument data for OnRequestedUpdate events.
|
||||
/// </summary>
|
||||
public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
|
||||
public class AddonRequestedUpdateArgs : AddonArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonRequestedUpdateArgs"/> class.
|
||||
/// </summary>
|
||||
[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.
|
||||
/// </summary>
|
||||
public nint StringArrayData { get; set; }
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonRequestedUpdateArgs Clone() => (AddonRequestedUpdateArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
|
||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
||||
internal override void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
this.NumberArrayData = default;
|
||||
this.StringArrayData = default;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,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;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Setup events.
|
||||
/// </summary>
|
||||
public class AddonSetupArgs : AddonArgs, ICloneable
|
||||
public class AddonSetupArgs : AddonArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonSetupArgs"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Not intended for public construction.", false)]
|
||||
public AddonSetupArgs()
|
||||
internal AddonSetupArgs()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -31,19 +36,30 @@ public class AddonSetupArgs : AddonArgs, ICloneable
|
|||
/// <summary>
|
||||
/// Gets the AtkValues in the form of a span.
|
||||
/// </summary>
|
||||
[Obsolete("Pending removal, Use AtkValueEnumerable instead.")]
|
||||
[Api15ToDo("Make this internal, remove obsolete")]
|
||||
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonSetupArgs Clone() => (AddonSetupArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
|
||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
||||
internal override void Clear()
|
||||
/// <summary>
|
||||
/// Gets an enumerable collection of <see cref="AtkValuePtr"/> of the event's AtkValues.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An <see cref="IEnumerable{T}"/> of <see cref="AtkValuePtr"/> corresponding to the event's AtkValues.
|
||||
/// </returns>
|
||||
public IEnumerable<AtkValuePtr> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
27
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonShowArgs.cs
Normal file
27
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonShowArgs.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Show events.
|
||||
/// </summary>
|
||||
public class AddonShowArgs : AddonArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonShowArgs"/> class.
|
||||
/// </summary>
|
||||
internal AddonShowArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Show;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the window should play open sound effects.
|
||||
/// </summary>
|
||||
public bool SilenceOpenSoundEffect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the flags that the window will unset when it Shows/Hides.
|
||||
/// </summary>
|
||||
public uint UnsetShowHideFlags { get; set; }
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Update events.
|
||||
/// </summary>
|
||||
public class AddonUpdateArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonUpdateArgs"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Not intended for public construction.", false)]
|
||||
public AddonUpdateArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Update;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time since the last update.
|
||||
/// </summary>
|
||||
public float TimeDelta
|
||||
{
|
||||
get => this.TimeDeltaInternal;
|
||||
init => this.TimeDeltaInternal = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time since the last update.
|
||||
/// </summary>
|
||||
internal float TimeDeltaInternal { get; set; }
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonUpdateArgs Clone() => (AddonUpdateArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
|
||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
||||
internal override void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
this.TimeDeltaInternal = default;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,38 +5,43 @@
|
|||
/// </summary>
|
||||
public enum AddonArgsType
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic arg type that contains no meaningful data.
|
||||
/// </summary>
|
||||
Generic,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Setup.
|
||||
/// </summary>
|
||||
Setup,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Update.
|
||||
/// </summary>
|
||||
Update,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Draw.
|
||||
/// </summary>
|
||||
Draw,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Finalize.
|
||||
/// </summary>
|
||||
Finalize,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for RequestedUpdate.
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
RequestedUpdate,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Refresh.
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
Refresh,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for ReceiveEvent.
|
||||
/// </summary>
|
||||
ReceiveEvent,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Show.
|
||||
/// </summary>
|
||||
Show,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Hide.
|
||||
/// </summary>
|
||||
Hide,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Close.
|
||||
/// </summary>
|
||||
Close,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public enum AddonEvent
|
|||
/// </summary>
|
||||
/// <seealso cref="AddonSetupArgs"/>
|
||||
PreSetup,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="AtkUnitBase.Update"/>. This event
|
||||
/// is fired every frame that an addon is loaded, regardless of visibility.
|
||||
/// </summary>
|
||||
/// <seealso cref="AddonUpdateArgs"/>
|
||||
PreUpdate,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -42,7 +41,6 @@ public enum AddonEvent
|
|||
/// An event that is fired before an addon begins drawing to screen via <see cref="AtkUnitBase.Draw"/>. Unlike
|
||||
/// <see cref="PreUpdate"/>, this event is only fired if an addon is visible or otherwise drawing to screen.
|
||||
/// </summary>
|
||||
/// <seealso cref="AddonDrawArgs"/>
|
||||
PreDraw,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -62,9 +60,8 @@ public enum AddonEvent
|
|||
/// <br />
|
||||
/// As this is part of the destruction process for an addon, this event does not have an associated Post event.
|
||||
/// </remarks>
|
||||
/// <seealso cref="AddonFinalizeArgs"/>
|
||||
PreFinalize,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before a call to <see cref="AtkUnitBase.OnRequestedUpdate"/> is made in response to a
|
||||
/// change in the subscribed <see cref="AddonRequestedUpdateArgs.NumberArrayData"/> or
|
||||
|
|
@ -81,13 +78,13 @@ public enum AddonEvent
|
|||
/// to the Free Company's overview.
|
||||
/// </example>
|
||||
PreRequestedUpdate,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has finished processing an <c>ArrayData</c> update.
|
||||
/// See <see cref="PreRequestedUpdate"/> for more information.
|
||||
/// </summary>
|
||||
PostRequestedUpdate,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon calls its <see cref="AtkUnitManager.RefreshAddon"/> method. Refreshes are
|
||||
/// generally triggered in response to certain user interactions such as changing tabs, and are primarily used to
|
||||
|
|
@ -96,13 +93,13 @@ public enum AddonEvent
|
|||
/// <seealso cref="AddonRefreshArgs"/>
|
||||
/// <seealso cref="PostRefresh"/>
|
||||
PreRefresh,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has finished its refresh.
|
||||
/// See <see cref="PreRefresh"/> for more information.
|
||||
/// </summary>
|
||||
PostRefresh,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon begins processing a user-driven event via
|
||||
/// <see cref="AtkEventListener.ReceiveEvent"/>, such as mousing over an element or clicking a button. This event
|
||||
|
|
@ -112,10 +109,98 @@ public enum AddonEvent
|
|||
/// <seealso cref="AddonReceiveEventArgs"/>
|
||||
/// <seealso cref="PostReceiveEvent"/>
|
||||
PreReceiveEvent,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon finishes calling its <see cref="AtkEventListener.ReceiveEvent"/> method.
|
||||
/// See <see cref="PreReceiveEvent"/> for more information.
|
||||
/// </summary>
|
||||
PostReceiveEvent,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its open method.
|
||||
/// </summary>
|
||||
PreOpen,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its open method.
|
||||
/// </summary>
|
||||
PostOpen,
|
||||
|
||||
/// <summary>
|
||||
/// An even that is fired before an addon processes its Close method.
|
||||
/// </summary>
|
||||
PreClose,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its Close method.
|
||||
/// </summary>
|
||||
PostClose,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its Show method.
|
||||
/// </summary>
|
||||
PreShow,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its Show method.
|
||||
/// </summary>
|
||||
PostShow,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its Hide method.
|
||||
/// </summary>
|
||||
PreHide,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its Hide method.
|
||||
/// </summary>
|
||||
PostHide,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its OnMove method.
|
||||
/// OnMove is triggered only when a move is completed.
|
||||
/// </summary>
|
||||
PreMove,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its OnMove method.
|
||||
/// OnMove is triggered only when a move is completed.
|
||||
/// </summary>
|
||||
PostMove,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its MouseOver method.
|
||||
/// </summary>
|
||||
PreMouseOver,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its MouseOver method.
|
||||
/// </summary>
|
||||
PostMouseOver,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its MouseOut method.
|
||||
/// </summary>
|
||||
PreMouseOut,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its MouseOut method.
|
||||
/// </summary>
|
||||
PostMouseOut,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before an addon processes its Focus method.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows.
|
||||
/// </remarks>
|
||||
PreFocus,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after an addon has processed its Focus method.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows.
|
||||
/// </remarks>
|
||||
PostFocus,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
using System.Collections.Generic;
|
||||
using System.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
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of all allocated addon virtual tables.
|
||||
/// </summary>
|
||||
public static readonly List<AddonVirtualTable> AllocatedTables = [];
|
||||
|
||||
private static readonly ModuleLog Log = new("AddonLifecycle");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonLifecyclePooledArgs argsPool = Service<AddonLifecyclePooledArgs>.Get();
|
||||
|
||||
private readonly nint disallowedReceiveEventAddress;
|
||||
|
||||
private readonly AddonLifecycleAddressResolver address;
|
||||
private readonly AddonSetupHook<AtkUnitBase.Delegates.OnSetup> onAddonSetupHook;
|
||||
private readonly Hook<AddonFinalizeDelegate> onAddonFinalizeHook;
|
||||
private readonly CallHook<AtkUnitBase.Delegates.Draw> onAddonDrawHook;
|
||||
private readonly CallHook<AtkUnitBase.Delegates.Update> onAddonUpdateHook;
|
||||
private readonly Hook<AtkUnitManager.Delegates.RefreshAddon> onAddonRefreshHook;
|
||||
private readonly CallHook<AtkUnitBase.Delegates.OnRequestedUpdate> onAddonRequestedUpdateHook;
|
||||
private Hook<AtkUnitBase.Delegates.Initialize>? 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<AtkUnitBase.Delegates.OnSetup>(this.address.AddonSetup, this.OnAddonSetup);
|
||||
this.onAddonFinalizeHook = Hook<AddonFinalizeDelegate>.FromAddress(this.address.AddonFinalize, this.OnAddonFinalize);
|
||||
this.onAddonDrawHook = new CallHook<AtkUnitBase.Delegates.Draw>(this.address.AddonDraw, this.OnAddonDraw);
|
||||
this.onAddonUpdateHook = new CallHook<AtkUnitBase.Delegates.Update>(this.address.AddonUpdate, this.OnAddonUpdate);
|
||||
this.onAddonRefreshHook = Hook<AtkUnitManager.Delegates.RefreshAddon>.FromAddress(refreshAddonAddress, this.OnAddonRefresh);
|
||||
this.onAddonRequestedUpdateHook = new CallHook<AtkUnitBase.Delegates.OnRequestedUpdate>(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate);
|
||||
|
||||
this.onAddonSetupHook.Enable();
|
||||
this.onAddonFinalizeHook.Enable();
|
||||
this.onAddonDrawHook.Enable();
|
||||
this.onAddonUpdateHook.Enable();
|
||||
this.onAddonRefreshHook.Enable();
|
||||
this.onAddonRequestedUpdateHook.Enable();
|
||||
this.onInitializeAddonHook = Hook<AtkUnitBase.Delegates.Initialize>.FromAddress((nint)AtkUnitBase.StaticVirtualTablePointer->Initialize, this.OnAddonInitialize);
|
||||
this.onInitializeAddonHook.Enable();
|
||||
}
|
||||
|
||||
private delegate void AddonFinalizeDelegate(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all AddonLifecycle ReceiveEvent Listener Hooks.
|
||||
/// </summary>
|
||||
internal List<AddonLifecycleReceiveEventListener> ReceiveEventListeners { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all AddonLifecycle Event Listeners.
|
||||
/// </summary>
|
||||
internal List<AddonLifecycleEventListener> EventListeners { get; } = new();
|
||||
/// </summary> <br/>
|
||||
/// Mapping is: EventType -> AddonName -> ListenerList
|
||||
internal Dictionary<AddonEvent, Dictionary<string, HashSet<AddonLifecycleEventListener>>> EventListeners { get; } = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -98,20 +57,20 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
|||
/// <param name="listener">The listener to register.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -120,27 +79,13 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
|||
/// <param name="listener">The listener to unregister.</param>
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -151,226 +96,63 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
|||
/// <param name="blame">What to blame on errors.</param>
|
||||
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<AddonLifecycle>.Get();
|
||||
|
||||
private readonly List<AddonLifecycleEventListener> eventListeners = new();
|
||||
private readonly List<AddonLifecycleEventListener> eventListeners = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Dalamud.Game.Addon.Lifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// AddonLifecycleService memory address resolver.
|
||||
/// </summary>
|
||||
internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the addon setup hook invoked by the AtkUnitManager.
|
||||
/// There are two callsites for this vFunc, we need to hook both of them to catch both normal UI and special UI cases like dialogue.
|
||||
/// This is called for a majority of all addon OnSetup's.
|
||||
/// </summary>
|
||||
public nint AddonSetup { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the other addon setup hook invoked by the AtkUnitManager.
|
||||
/// There are two callsites for this vFunc, we need to hook both of them to catch both normal UI and special UI cases like dialogue.
|
||||
/// This seems to be called rarely for specific addons.
|
||||
/// </summary>
|
||||
public nint AddonSetup2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the addon finalize hook invoked by the AtkUnitManager.
|
||||
/// </summary>
|
||||
public nint AddonFinalize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the addon draw hook invoked by virtual function call.
|
||||
/// </summary>
|
||||
public nint AddonDraw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the addon update hook invoked by virtual function call.
|
||||
/// </summary>
|
||||
public nint AddonUpdate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the addon onRequestedUpdate hook invoked by virtual function call.
|
||||
/// </summary>
|
||||
public nint AddonOnRequestedUpdate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scan for and setup any configured address pointers.
|
||||
/// </summary>
|
||||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.AddonSetup = sig.ScanText("4C 8B 88 ?? ?? ?? ?? 66 44 39 BB");
|
||||
this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 83 EF 01 75 D5");
|
||||
this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C4 48 81 EF ?? ?? ?? ?? 48 83 ED 01");
|
||||
this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF ?? ?? ?? ?? 45 33 D2");
|
||||
this.AddonOnRequestedUpdate = sig.ScanText("FF 90 A0 01 00 00 48 8B 5C 24 30");
|
||||
}
|
||||
}
|
||||
|
|
@ -25,17 +25,12 @@ internal class AddonLifecycleEventListener
|
|||
/// string.Empty if it wants to be called for any addon.
|
||||
/// </summary>
|
||||
public string AddonName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this event has been unregistered.
|
||||
/// </summary>
|
||||
public bool Removed { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event type this listener is looking for.
|
||||
/// </summary>
|
||||
public AddonEvent EventType { get; init; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delegate this listener invokes.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,112 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon.Lifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// This class is a helper for tracking and invoking listener delegates for Addon_OnReceiveEvent.
|
||||
/// Multiple addons may use the same ReceiveEvent function, this helper makes sure that those addon events are handled properly.
|
||||
/// </summary>
|
||||
internal unsafe class AddonLifecycleReceiveEventListener : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("AddonLifecycle");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonLifecyclePooledArgs argsPool = Service<AddonLifecyclePooledArgs>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonLifecycleReceiveEventListener"/> class.
|
||||
/// </summary>
|
||||
/// <param name="service">AddonLifecycle service instance.</param>
|
||||
/// <param name="addonName">Initial Addon Requesting this listener.</param>
|
||||
/// <param name="receiveEventAddress">Address of Addon's ReceiveEvent function.</param>
|
||||
internal AddonLifecycleReceiveEventListener(AddonLifecycle service, string addonName, nint receiveEventAddress)
|
||||
{
|
||||
this.AddonLifecycle = service;
|
||||
this.AddonNames = [addonName];
|
||||
this.FunctionAddress = receiveEventAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of addons that use this receive event hook.
|
||||
/// </summary>
|
||||
public List<string> AddonNames { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the ReceiveEvent function as provided by the vtable on setup.
|
||||
/// </summary>
|
||||
public nint FunctionAddress { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the contained hook for these addons.
|
||||
/// </summary>
|
||||
public Hook<AtkUnitBase.Delegates.ReceiveEvent>? Hook { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Reference to AddonLifecycle service instance.
|
||||
/// </summary>
|
||||
private AddonLifecycle AddonLifecycle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Try to hook and enable this receive event handler.
|
||||
/// </summary>
|
||||
public void TryEnable()
|
||||
{
|
||||
this.Hook ??= Hook<AtkUnitBase.Delegates.ReceiveEvent>.FromAddress(this.FunctionAddress, this.OnReceiveEvent);
|
||||
this.Hook?.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable the hook for this receive event handler.
|
||||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
this.Hook?.Disable();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Hook?.Dispose();
|
||||
}
|
||||
|
||||
private void OnReceiveEvent(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
|
||||
{
|
||||
// Check that we didn't get here through a call to another addons handler.
|
||||
var addonName = addon->NameString;
|
||||
if (!this.AddonNames.Contains(addonName))
|
||||
{
|
||||
this.Hook!.Original(addon, eventType, eventParam, atkEvent, atkEventData);
|
||||
return;
|
||||
}
|
||||
|
||||
using var returner = this.argsPool.Rent(out AddonReceiveEventArgs arg);
|
||||
arg.Clear();
|
||||
arg.Addon = (nint)addon;
|
||||
arg.AtkEventType = (byte)eventType;
|
||||
arg.EventParam = eventParam;
|
||||
arg.AtkEvent = (IntPtr)atkEvent;
|
||||
arg.Data = (nint)atkEventData;
|
||||
this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PreReceiveEvent, arg);
|
||||
eventType = (AtkEventType)arg.AtkEventType;
|
||||
eventParam = arg.EventParam;
|
||||
atkEvent = (AtkEvent*)arg.AtkEvent;
|
||||
atkEventData = (AtkEventData*)arg.Data;
|
||||
|
||||
try
|
||||
{
|
||||
this.Hook!.Original(addon, eventType, eventParam, atkEvent, atkEventData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original AddonReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PostReceiveEvent, arg);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
using Reloaded.Hooks.Definitions;
|
||||
|
||||
namespace Dalamud.Game.Addon.Lifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a callsite hook used to replace the address of the OnSetup function in r9.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Delegate signature for this hook.</typeparam>
|
||||
internal class AddonSetupHook<T> : IDisposable where T : Delegate
|
||||
{
|
||||
private readonly Reloaded.Hooks.AsmHook asmHook;
|
||||
|
||||
private T? detour;
|
||||
private bool activated;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonSetupHook{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the instruction to replace.</param>
|
||||
/// <param name="detour">Delegate to invoke.</param>
|
||||
internal AddonSetupHook(nint address, T detour)
|
||||
{
|
||||
this.detour = detour;
|
||||
|
||||
var detourPtr = Marshal.GetFunctionPointerForDelegate(this.detour);
|
||||
var code = new[]
|
||||
{
|
||||
"use64",
|
||||
$"mov r9, 0x{detourPtr:X8}",
|
||||
};
|
||||
|
||||
var opt = new AsmHookOptions
|
||||
{
|
||||
PreferRelativeJump = true,
|
||||
Behaviour = Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour.DoNotExecuteOriginal,
|
||||
MaxOpcodeSize = 5,
|
||||
};
|
||||
|
||||
this.asmHook = new Reloaded.Hooks.AsmHook(code, (nuint)address, opt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the hook is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled => this.asmHook.IsEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Starts intercepting a call to the function.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
if (!this.activated)
|
||||
{
|
||||
this.activated = true;
|
||||
this.asmHook.Activate();
|
||||
return;
|
||||
}
|
||||
|
||||
this.asmHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops intercepting a call to the function.
|
||||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
this.asmHook.Disable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a hook from the current process.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.asmHook.Disable();
|
||||
this.detour = null;
|
||||
}
|
||||
}
|
||||
638
Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs
Normal file
638
Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs
Normal file
|
|
@ -0,0 +1,638 @@
|
|||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon.Lifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a class that holds references to an addons original and modified virtual table entries.
|
||||
/// </summary>
|
||||
internal unsafe class AddonVirtualTable : IDisposable
|
||||
{
|
||||
// This need to be at minimum the largest virtual table size of all addons
|
||||
// Copying extra entries is not problematic, and is considered safe.
|
||||
private const int VirtualTableEntryCount = 200;
|
||||
|
||||
private const bool EnableLogging = false;
|
||||
|
||||
private static readonly ModuleLog Log = new("LifecycleVT");
|
||||
|
||||
private readonly AddonLifecycle lifecycleService;
|
||||
|
||||
// Each addon gets its own set of args that are used to mutate the original call when used in pre-calls
|
||||
private readonly AddonSetupArgs setupArgs = new();
|
||||
private readonly AddonArgs finalizeArgs = new();
|
||||
private readonly AddonArgs drawArgs = new();
|
||||
private readonly AddonArgs updateArgs = new();
|
||||
private readonly AddonRefreshArgs refreshArgs = new();
|
||||
private readonly AddonRequestedUpdateArgs requestedUpdateArgs = new();
|
||||
private readonly AddonReceiveEventArgs receiveEventArgs = new();
|
||||
private readonly AddonArgs openArgs = new();
|
||||
private readonly AddonCloseArgs closeArgs = new();
|
||||
private readonly AddonShowArgs showArgs = new();
|
||||
private readonly AddonHideArgs hideArgs = new();
|
||||
private readonly AddonArgs onMoveArgs = new();
|
||||
private readonly AddonArgs onMouseOverArgs = new();
|
||||
private readonly AddonArgs onMouseOutArgs = new();
|
||||
private readonly AddonArgs focusArgs = new();
|
||||
|
||||
private readonly AtkUnitBase* atkUnitBase;
|
||||
|
||||
private readonly AtkUnitBase.AtkUnitBaseVirtualTable* originalVirtualTable;
|
||||
private readonly AtkUnitBase.AtkUnitBaseVirtualTable* modifiedVirtualTable;
|
||||
|
||||
// Pinned Function Delegates, as these functions get assigned to an unmanaged virtual table,
|
||||
// the CLR needs to know they are in use, or it will invalidate them causing random crashing.
|
||||
private readonly AtkUnitBase.Delegates.Dtor destructorFunction;
|
||||
private readonly AtkUnitBase.Delegates.OnSetup onSetupFunction;
|
||||
private readonly AtkUnitBase.Delegates.Finalizer finalizerFunction;
|
||||
private readonly AtkUnitBase.Delegates.Draw drawFunction;
|
||||
private readonly AtkUnitBase.Delegates.Update updateFunction;
|
||||
private readonly AtkUnitBase.Delegates.OnRefresh onRefreshFunction;
|
||||
private readonly AtkUnitBase.Delegates.OnRequestedUpdate onRequestedUpdateFunction;
|
||||
private readonly AtkUnitBase.Delegates.ReceiveEvent onReceiveEventFunction;
|
||||
private readonly AtkUnitBase.Delegates.Open openFunction;
|
||||
private readonly AtkUnitBase.Delegates.Close closeFunction;
|
||||
private readonly AtkUnitBase.Delegates.Show showFunction;
|
||||
private readonly AtkUnitBase.Delegates.Hide hideFunction;
|
||||
private readonly AtkUnitBase.Delegates.OnMove onMoveFunction;
|
||||
private readonly AtkUnitBase.Delegates.OnMouseOver onMouseOverFunction;
|
||||
private readonly AtkUnitBase.Delegates.OnMouseOut onMouseOutFunction;
|
||||
private readonly AtkUnitBase.Delegates.Focus focusFunction;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonVirtualTable"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addon">AtkUnitBase* for the addon to replace the table of.</param>
|
||||
/// <param name="lifecycleService">Reference to AddonLifecycle service to callback and invoke listeners.</param>
|
||||
internal AddonVirtualTable(AtkUnitBase* addon, AddonLifecycle lifecycleService)
|
||||
{
|
||||
this.atkUnitBase = addon;
|
||||
this.lifecycleService = lifecycleService;
|
||||
|
||||
// Save original virtual table
|
||||
this.originalVirtualTable = addon->VirtualTable;
|
||||
|
||||
// Create copy of original table
|
||||
// Note this will copy any derived/overriden functions that this specific addon has.
|
||||
// Note: currently there are 73 virtual functions, but there's no harm in copying more for when they add new virtual functions to the game
|
||||
this.modifiedVirtualTable = (AtkUnitBase.AtkUnitBaseVirtualTable*)IMemorySpace.GetUISpace()->Malloc(0x8 * VirtualTableEntryCount, 8);
|
||||
NativeMemory.Copy(addon->VirtualTable, this.modifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||
|
||||
// Overwrite the addons existing virtual table with our own
|
||||
addon->VirtualTable = this.modifiedVirtualTable;
|
||||
|
||||
// Pin each of our listener functions
|
||||
this.destructorFunction = this.OnAddonDestructor;
|
||||
this.onSetupFunction = this.OnAddonSetup;
|
||||
this.finalizerFunction = this.OnAddonFinalize;
|
||||
this.drawFunction = this.OnAddonDraw;
|
||||
this.updateFunction = this.OnAddonUpdate;
|
||||
this.onRefreshFunction = this.OnAddonRefresh;
|
||||
this.onRequestedUpdateFunction = this.OnRequestedUpdate;
|
||||
this.onReceiveEventFunction = this.OnAddonReceiveEvent;
|
||||
this.openFunction = this.OnAddonOpen;
|
||||
this.closeFunction = this.OnAddonClose;
|
||||
this.showFunction = this.OnAddonShow;
|
||||
this.hideFunction = this.OnAddonHide;
|
||||
this.onMoveFunction = this.OnAddonMove;
|
||||
this.onMouseOverFunction = this.OnAddonMouseOver;
|
||||
this.onMouseOutFunction = this.OnAddonMouseOut;
|
||||
this.focusFunction = this.OnAddonFocus;
|
||||
|
||||
// Overwrite specific virtual table entries
|
||||
this.modifiedVirtualTable->Dtor = (delegate* unmanaged<AtkUnitBase*, byte, AtkEventListener*>)Marshal.GetFunctionPointerForDelegate(this.destructorFunction);
|
||||
this.modifiedVirtualTable->OnSetup = (delegate* unmanaged<AtkUnitBase*, uint, AtkValue*, void>)Marshal.GetFunctionPointerForDelegate(this.onSetupFunction);
|
||||
this.modifiedVirtualTable->Finalizer = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.finalizerFunction);
|
||||
this.modifiedVirtualTable->Draw = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.drawFunction);
|
||||
this.modifiedVirtualTable->Update = (delegate* unmanaged<AtkUnitBase*, float, void>)Marshal.GetFunctionPointerForDelegate(this.updateFunction);
|
||||
this.modifiedVirtualTable->OnRefresh = (delegate* unmanaged<AtkUnitBase*, uint, AtkValue*, bool>)Marshal.GetFunctionPointerForDelegate(this.onRefreshFunction);
|
||||
this.modifiedVirtualTable->OnRequestedUpdate = (delegate* unmanaged<AtkUnitBase*, NumberArrayData**, StringArrayData**, void>)Marshal.GetFunctionPointerForDelegate(this.onRequestedUpdateFunction);
|
||||
this.modifiedVirtualTable->ReceiveEvent = (delegate* unmanaged<AtkUnitBase*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)Marshal.GetFunctionPointerForDelegate(this.onReceiveEventFunction);
|
||||
this.modifiedVirtualTable->Open = (delegate* unmanaged<AtkUnitBase*, uint, bool>)Marshal.GetFunctionPointerForDelegate(this.openFunction);
|
||||
this.modifiedVirtualTable->Close = (delegate* unmanaged<AtkUnitBase*, bool, bool>)Marshal.GetFunctionPointerForDelegate(this.closeFunction);
|
||||
this.modifiedVirtualTable->Show = (delegate* unmanaged<AtkUnitBase*, bool, uint, void>)Marshal.GetFunctionPointerForDelegate(this.showFunction);
|
||||
this.modifiedVirtualTable->Hide = (delegate* unmanaged<AtkUnitBase*, bool, bool, uint, void>)Marshal.GetFunctionPointerForDelegate(this.hideFunction);
|
||||
this.modifiedVirtualTable->OnMove = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMoveFunction);
|
||||
this.modifiedVirtualTable->OnMouseOver = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMouseOverFunction);
|
||||
this.modifiedVirtualTable->OnMouseOut = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMouseOutFunction);
|
||||
this.modifiedVirtualTable->Focus = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.focusFunction);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
// Ensure restoration is done atomically.
|
||||
Interlocked.Exchange(ref *(nint*)&this.atkUnitBase->VirtualTable, (nint)this.originalVirtualTable);
|
||||
IMemorySpace.Free(this.modifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||
}
|
||||
|
||||
private AtkEventListener* OnAddonDestructor(AtkUnitBase* thisPtr, byte freeFlags)
|
||||
{
|
||||
AtkEventListener* result = null;
|
||||
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
try
|
||||
{
|
||||
result = this.originalVirtualTable->Dtor(thisPtr, freeFlags);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Dtor. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
if ((freeFlags & 1) == 1)
|
||||
{
|
||||
IMemorySpace.Free(this.modifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||
AddonLifecycle.AllocatedTables.Remove(this);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonDestructor.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnAddonSetup(AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.setupArgs.Addon = addon;
|
||||
this.setupArgs.AtkValueCount = valueCount;
|
||||
this.setupArgs.AtkValues = (nint)values;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreSetup, this.setupArgs);
|
||||
|
||||
valueCount = this.setupArgs.AtkValueCount;
|
||||
values = (AtkValue*)this.setupArgs.AtkValues;
|
||||
|
||||
try
|
||||
{
|
||||
this.originalVirtualTable->OnSetup(addon, valueCount, values);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnSetup. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostSetup, this.setupArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonSetup.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonFinalize(AtkUnitBase* thisPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.finalizeArgs.Addon = thisPtr;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreFinalize, this.finalizeArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.originalVirtualTable->Finalizer(thisPtr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Finalizer. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonFinalize.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonDraw(AtkUnitBase* addon)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.drawArgs.Addon = addon;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreDraw, this.drawArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.originalVirtualTable->Draw(addon);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Draw. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostDraw, this.drawArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonDraw.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonUpdate(AtkUnitBase* addon, float delta)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.updateArgs.Addon = addon;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreUpdate, this.updateArgs);
|
||||
|
||||
// Note: Do not pass or allow manipulation of delta.
|
||||
// It's realistically not something that should be needed.
|
||||
// And even if someone does, they are encouraged to hook Update themselves.
|
||||
|
||||
try
|
||||
{
|
||||
this.originalVirtualTable->Update(addon, delta);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Update. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostUpdate, this.updateArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonUpdate.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool OnAddonRefresh(AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.refreshArgs.Addon = addon;
|
||||
this.refreshArgs.AtkValueCount = valueCount;
|
||||
this.refreshArgs.AtkValues = (nint)values;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreRefresh, this.refreshArgs);
|
||||
|
||||
valueCount = this.refreshArgs.AtkValueCount;
|
||||
values = (AtkValue*)this.refreshArgs.AtkValues;
|
||||
|
||||
try
|
||||
{
|
||||
result = this.originalVirtualTable->OnRefresh(addon, valueCount, values);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnRefresh. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostRefresh, this.refreshArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonRefresh.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.requestedUpdateArgs.Addon = addon;
|
||||
this.requestedUpdateArgs.NumberArrayData = (nint)numberArrayData;
|
||||
this.requestedUpdateArgs.StringArrayData = (nint)stringArrayData;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, this.requestedUpdateArgs);
|
||||
|
||||
numberArrayData = (NumberArrayData**)this.requestedUpdateArgs.NumberArrayData;
|
||||
stringArrayData = (StringArrayData**)this.requestedUpdateArgs.StringArrayData;
|
||||
|
||||
try
|
||||
{
|
||||
this.originalVirtualTable->OnRequestedUpdate(addon, numberArrayData, stringArrayData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnRequestedUpdate. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostRequestedUpdate, this.requestedUpdateArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnRequestedUpdate.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonReceiveEvent(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.receiveEventArgs.Addon = (nint)addon;
|
||||
this.receiveEventArgs.AtkEventType = (byte)eventType;
|
||||
this.receiveEventArgs.EventParam = eventParam;
|
||||
this.receiveEventArgs.AtkEvent = (IntPtr)atkEvent;
|
||||
this.receiveEventArgs.AtkEventData = (nint)atkEventData;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreReceiveEvent, this.receiveEventArgs);
|
||||
|
||||
eventType = (AtkEventType)this.receiveEventArgs.AtkEventType;
|
||||
eventParam = this.receiveEventArgs.EventParam;
|
||||
atkEvent = (AtkEvent*)this.receiveEventArgs.AtkEvent;
|
||||
atkEventData = (AtkEventData*)this.receiveEventArgs.AtkEventData;
|
||||
|
||||
try
|
||||
{
|
||||
this.originalVirtualTable->ReceiveEvent(addon, eventType, eventParam, atkEvent, atkEventData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon ReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostReceiveEvent, this.receiveEventArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonReceiveEvent.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool OnAddonOpen(AtkUnitBase* thisPtr, uint depthLayer)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.openArgs.Addon = thisPtr;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreOpen, this.openArgs);
|
||||
|
||||
try
|
||||
{
|
||||
result = this.originalVirtualTable->Open(thisPtr, depthLayer);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Open. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostOpen, this.openArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonOpen.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool OnAddonClose(AtkUnitBase* thisPtr, bool fireCallback)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.closeArgs.Addon = thisPtr;
|
||||
this.closeArgs.FireCallback = fireCallback;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreClose, this.closeArgs);
|
||||
|
||||
fireCallback = this.closeArgs.FireCallback;
|
||||
|
||||
try
|
||||
{
|
||||
result = this.originalVirtualTable->Close(thisPtr, fireCallback);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Close. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostClose, this.closeArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonClose.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnAddonShow(AtkUnitBase* thisPtr, bool silenceOpenSoundEffect, uint unsetShowHideFlags)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.showArgs.Addon = thisPtr;
|
||||
this.showArgs.SilenceOpenSoundEffect = silenceOpenSoundEffect;
|
||||
this.showArgs.UnsetShowHideFlags = unsetShowHideFlags;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreShow, this.showArgs);
|
||||
|
||||
silenceOpenSoundEffect = this.showArgs.SilenceOpenSoundEffect;
|
||||
unsetShowHideFlags = this.showArgs.UnsetShowHideFlags;
|
||||
|
||||
try
|
||||
{
|
||||
this.originalVirtualTable->Show(thisPtr, silenceOpenSoundEffect, unsetShowHideFlags);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Show. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostShow, this.showArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonShow.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonHide(AtkUnitBase* thisPtr, bool unkBool, bool callHideCallback, uint setShowHideFlags)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.hideArgs.Addon = thisPtr;
|
||||
this.hideArgs.UnknownBool = unkBool;
|
||||
this.hideArgs.CallHideCallback = callHideCallback;
|
||||
this.hideArgs.SetShowHideFlags = setShowHideFlags;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreHide, this.hideArgs);
|
||||
|
||||
unkBool = this.hideArgs.UnknownBool;
|
||||
callHideCallback = this.hideArgs.CallHideCallback;
|
||||
setShowHideFlags = this.hideArgs.SetShowHideFlags;
|
||||
|
||||
try
|
||||
{
|
||||
this.originalVirtualTable->Hide(thisPtr, unkBool, callHideCallback, setShowHideFlags);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Hide. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostHide, this.hideArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonHide.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonMove(AtkUnitBase* thisPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.onMoveArgs.Addon = thisPtr;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMove, this.onMoveArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.originalVirtualTable->OnMove(thisPtr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnMove. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMove, this.onMoveArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMove.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonMouseOver(AtkUnitBase* thisPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.onMouseOverArgs.Addon = thisPtr;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMouseOver, this.onMouseOverArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.originalVirtualTable->OnMouseOver(thisPtr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnMouseOver. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMouseOver, this.onMouseOverArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMouseOver.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonMouseOut(AtkUnitBase* thisPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.onMouseOutArgs.Addon = thisPtr;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMouseOut, this.onMouseOutArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.originalVirtualTable->OnMouseOut(thisPtr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnMouseOut. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMouseOut, this.onMouseOutArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMouseOut.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonFocus(AtkUnitBase* thisPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.focusArgs.Addon = thisPtr;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreFocus, this.focusArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.originalVirtualTable->Focus(thisPtr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Focus. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostFocus, this.focusArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonFocus.");
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void LogEvent(bool loggingEnabled, [CallerMemberName] string caller = "")
|
||||
{
|
||||
if (loggingEnabled)
|
||||
{
|
||||
// Manually disable the really spammy log events, you can comment this out if you need to debug them.
|
||||
if (caller is "OnAddonUpdate" or "OnAddonDraw" or "OnAddonReceiveEvent" or "OnRequestedUpdate")
|
||||
return;
|
||||
|
||||
Log.Debug($"[{caller}]: {this.atkUnitBase->NameString}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Api13ToDo("Maybe make this config scoped to internal name?")]
|
||||
[Api14ToDo("Maybe make this config scoped to internal name?")]
|
||||
public bool UserHidden => this.configuration.DtrIgnore?.Contains(this.Title) ?? false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
|||
|
|
@ -92,34 +92,16 @@ public enum FlyTextKind : int
|
|||
/// </summary>
|
||||
IslandExp = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font next to all caps condensed font Text1 with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
[Obsolete("Use Dataset instead", true)]
|
||||
Unknown16 = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font next to all caps condensed font Text1 with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
Dataset = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
[Obsolete("Use Knowledge instead", true)]
|
||||
Unknown17 = 17,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
Knowledge = 17,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
[Obsolete("Use PhantomExp instead", true)]
|
||||
Unknown18 = 18,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,100 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
using Reloaded.Hooks.Definitions;
|
||||
|
||||
namespace Dalamud.Hooking.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a callsite hook. Only the specific address's instructions are replaced with this hook.
|
||||
/// This is a destructive operation, no other callsite hooks can coexist at the same address.
|
||||
///
|
||||
/// There's no .Original for this hook type.
|
||||
/// This is only intended for be for functions where the parameters provided allow you to invoke the original call.
|
||||
///
|
||||
/// This class was specifically added for hooking virtual function callsites.
|
||||
/// Only the specific callsite hooked is modified, if the game calls the virtual function from other locations this hook will not be triggered.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Delegate signature for this hook.</typeparam>
|
||||
internal class CallHook<T> : IDalamudHook where T : Delegate
|
||||
{
|
||||
private readonly Reloaded.Hooks.AsmHook asmHook;
|
||||
|
||||
private T? detour;
|
||||
private bool activated;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CallHook{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the instruction to replace.</param>
|
||||
/// <param name="detour">Delegate to invoke.</param>
|
||||
internal CallHook(nint address, T detour)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(detour);
|
||||
|
||||
this.detour = detour;
|
||||
this.Address = address;
|
||||
|
||||
var detourPtr = Marshal.GetFunctionPointerForDelegate(this.detour);
|
||||
var code = new[]
|
||||
{
|
||||
"use64",
|
||||
$"mov rax, 0x{detourPtr:X8}",
|
||||
"call rax",
|
||||
};
|
||||
|
||||
var opt = new AsmHookOptions
|
||||
{
|
||||
PreferRelativeJump = true,
|
||||
Behaviour = Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour.DoNotExecuteOriginal,
|
||||
MaxOpcodeSize = 5,
|
||||
};
|
||||
|
||||
this.asmHook = new Reloaded.Hooks.AsmHook(code, (nuint)address, opt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the hook is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled => this.asmHook.IsEnabled;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr Address { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string BackendName => "Reloaded AsmHook";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsDisposed => this.detour == null;
|
||||
|
||||
/// <summary>
|
||||
/// Starts intercepting a call to the function.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
if (!this.activated)
|
||||
{
|
||||
this.activated = true;
|
||||
this.asmHook.Activate();
|
||||
return;
|
||||
}
|
||||
|
||||
this.asmHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops intercepting a call to the function.
|
||||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
this.asmHook.Disable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a hook from the current process.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.asmHook.Disable();
|
||||
this.detour = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ public abstract class Easing
|
|||
/// Gets the current value of the animation, following unclamped logic.
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ internal class DalamudInterface : IInternalDisposableService
|
|||
() => Service<DalamudInterface>.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);
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ public class BranchSwitcherWindow : Window
|
|||
this.branches = await client.GetFromJsonAsync<Dictionary<string, VersionEntry>>(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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<AddonEvent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string[]? CommandShortcuts { get; init; } = { "hook" };
|
||||
public string[]? CommandShortcuts { get; init; } = ["hook"];
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<TargetSigScanner>.Get());
|
||||
var sigScanner = Service<TargetSigScanner>.Get();
|
||||
this.address = sigScanner.ScanText("E8 ?? ?? ?? ?? 48 83 EF 01 75 D5");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -224,7 +223,7 @@ internal unsafe class HookWidget : IDataWindowWidget
|
|||
|
||||
private IDalamudHook HookAddonFinalize()
|
||||
{
|
||||
var hook = Hook<AddonFinalizeDelegate>.FromAddress(this.address!.AddonFinalize, this.OnAddonFinalize);
|
||||
var hook = Hook<AddonFinalizeDelegate>.FromAddress(this.address, this.OnAddonFinalize);
|
||||
|
||||
this.addonFinalizeOriginal = hook.Original;
|
||||
hook.Enable();
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ internal class SeStringCreatorWidget : IDataWindowWidget
|
|||
new TextEntry(TextEntryType.Macro, " <string(lstr1)>"),
|
||||
];
|
||||
|
||||
private SeStringParameter[]? localParameters = [Util.GetScmVersion()];
|
||||
private SeStringParameter[]? localParameters = [Versioning.GetScmVersion()];
|
||||
private ReadOnlySeString input;
|
||||
private ClientLanguage? language;
|
||||
private Task? validImportSheetNamesTask;
|
||||
|
|
|
|||
|
|
@ -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<DalamudReleases>.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 &&
|
||||
|
|
|
|||
|
|
@ -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<GameGui>.Get();
|
||||
var playerState = PlayerState.Instance();
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ internal class HappyHttpClient : IInternalDisposableService
|
|||
{
|
||||
UserAgent =
|
||||
{
|
||||
new ProductInfoHeaderValue("Dalamud", Util.AssemblyVersion),
|
||||
new ProductInfoHeaderValue("Dalamud", Versioning.GetAssemblyVersion()),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin the given assembly is part of.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly to check.</param>
|
||||
/// <returns>The plugin the given assembly is part of, or null if this is a shared assembly or if this information cannot be determined.</returns>
|
||||
/// <inheritdoc/>
|
||||
public IExposedPlugin? GetPlugin(Assembly assembly)
|
||||
=> AssemblyLoadContext.GetLoadContext(assembly) switch
|
||||
{
|
||||
|
|
@ -216,11 +209,7 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa
|
|||
var context => this.GetPlugin(context),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin that loads in the given context.
|
||||
/// </summary>
|
||||
/// <param name="context">The context to check.</param>
|
||||
/// <returns>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.</returns>
|
||||
/// <inheritdoc/>
|
||||
public IExposedPlugin? GetPlugin(AssemblyLoadContext context)
|
||||
=> Service<PluginManager>.Get().InstalledPlugins.FirstOrDefault(p => p.LoadsIn(context)) switch
|
||||
{
|
||||
|
|
@ -228,6 +217,12 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa
|
|||
var p => new ExposedPlugin(p),
|
||||
};
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudVersionInfo GetDalamudVersion()
|
||||
{
|
||||
return new DalamudVersionInfo(Versioning.GetAssemblyVersionParsed(), Versioning.GetActiveTrack());
|
||||
}
|
||||
|
||||
#region IPC
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
|||
|
|
@ -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
|
|||
/// <returns>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.</returns>
|
||||
IExposedPlugin? GetPlugin(AssemblyLoadContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the version of Dalamud this plugin is loaded into.
|
||||
/// </summary>
|
||||
/// <returns>Class containing version information.</returns>
|
||||
IDalamudVersionInfo GetDalamudVersion();
|
||||
|
||||
/// <inheritdoc cref="DataShare.GetOrCreateData{T}"/>
|
||||
T GetOrCreateData<T>(string tag, Func<T> dataGenerator) where T : class;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
Dalamud/Plugin/VersionInfo/DalamudVersionInfo.cs
Normal file
11
Dalamud/Plugin/VersionInfo/DalamudVersionInfo.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
namespace Dalamud.Plugin.VersionInfo;
|
||||
|
||||
/// <inheritdoc />
|
||||
internal class DalamudVersionInfo(Version version, string? track) : IDalamudVersionInfo
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public Version Version { get; } = version;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? BetaTrack { get; } = track;
|
||||
}
|
||||
19
Dalamud/Plugin/VersionInfo/IDalamudVersionInfo.cs
Normal file
19
Dalamud/Plugin/VersionInfo/IDalamudVersionInfo.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
namespace Dalamud.Plugin.VersionInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Interface exposing various information related to Dalamud versioning.
|
||||
/// </summary>
|
||||
public interface IDalamudVersionInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Dalamud version.
|
||||
/// </summary>
|
||||
Version Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
string? BetaTrack { get; }
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ internal class DalamudReleases : IServiceType
|
|||
/// <returns>The version info for the current track.</returns>
|
||||
public async Task<DalamudVersionInfo?> GetVersionForCurrentTrack()
|
||||
{
|
||||
var currentTrack = Util.GetActiveTrack();
|
||||
var currentTrack = Versioning.GetActiveTrack();
|
||||
if (currentTrack.IsNullOrEmpty())
|
||||
return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ namespace Dalamud.Utility;
|
|||
/// Utility class for marking something to be changed for API 13, for ease of lookup.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.All, Inherited = false)]
|
||||
internal sealed class Api13ToDoAttribute : Attribute
|
||||
internal sealed class Api14ToDoAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks that this should be made internal.
|
||||
|
|
@ -12,11 +12,11 @@ internal sealed class Api13ToDoAttribute : Attribute
|
|||
public const string MakeInternal = "Make internal.";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Api13ToDoAttribute"/> class.
|
||||
/// Initializes a new instance of the <see cref="Api14ToDoAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="what">The explanation.</param>
|
||||
/// <param name="what2">The explanation 2.</param>
|
||||
public Api13ToDoAttribute(string what, string what2 = "")
|
||||
public Api14ToDoAttribute(string what, string what2 = "")
|
||||
{
|
||||
_ = what;
|
||||
_ = what2;
|
||||
25
Dalamud/Utility/Api15ToDoAttribute.cs
Normal file
25
Dalamud/Utility/Api15ToDoAttribute.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
namespace Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Utility class for marking something to be changed for API 13, for ease of lookup.
|
||||
/// Intended to represent not the upcoming API, but the one after it for more major changes.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.All, Inherited = false)]
|
||||
internal sealed class Api15ToDoAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks that this should be made internal.
|
||||
/// </summary>
|
||||
public const string MakeInternal = "Make internal.";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Api15ToDoAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="what">The explanation.</param>
|
||||
/// <param name="what2">The explanation 2.</param>
|
||||
public Api15ToDoAttribute(string what, string what2 = "")
|
||||
{
|
||||
_ = what;
|
||||
_ = what2;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud version.
|
||||
/// </summary>
|
||||
[Api13ToDo("Remove. Make both versions here internal. Add an API somewhere.")]
|
||||
public static string AssemblyVersion { get; } =
|
||||
Assembly.GetAssembly(typeof(ChatHandlers))!.GetName().Version!.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud version.
|
||||
/// </summary>
|
||||
internal static Version AssemblyVersionParsed { get; } =
|
||||
Assembly.GetAssembly(typeof(ChatHandlers))!.GetName().Version!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SCM Version from the assembly, or null if it cannot be found. This method will generally return
|
||||
/// the <c>git describe</c> 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.
|
||||
/// </summary>
|
||||
/// <returns>The SCM version of the assembly.</returns>
|
||||
public static string GetScmVersion()
|
||||
{
|
||||
if (scmVersionInternal != null) return scmVersionInternal;
|
||||
|
||||
var asm = typeof(Util).Assembly;
|
||||
var attrs = asm.GetCustomAttributes<AssemblyMetadataAttribute>();
|
||||
|
||||
return scmVersionInternal = attrs.First(a => a.Key == "SCMVersion").Value
|
||||
?? asm.GetName().Version!.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <returns>The git hash of the assembly.</returns>
|
||||
public static string? GetGitHash()
|
||||
{
|
||||
if (gitHashInternal != null)
|
||||
return gitHashInternal;
|
||||
|
||||
var asm = typeof(Util).Assembly;
|
||||
var attrs = asm.GetCustomAttributes<AssemblyMetadataAttribute>();
|
||||
|
||||
return gitHashInternal = attrs.FirstOrDefault(a => a.Key == "GitHash")?.Value ?? "N/A";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the git hash value from the assembly or null if it cannot be found.
|
||||
/// </summary>
|
||||
/// <returns>The git hash of the assembly.</returns>
|
||||
public static string? GetGitHashClientStructs()
|
||||
{
|
||||
if (gitHashClientStructsInternal != null)
|
||||
return gitHashClientStructsInternal;
|
||||
|
||||
var asm = typeof(Util).Assembly;
|
||||
var attrs = asm.GetCustomAttributes<AssemblyMetadataAttribute>();
|
||||
|
||||
gitHashClientStructsInternal = attrs.First(a => a.Key == "GitHashClientStructs").Value;
|
||||
|
||||
return gitHashClientStructsInternal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Git branch name this version of Dalamud was built from, or null, if this is a Debug build.
|
||||
/// </summary>
|
||||
/// <returns>The branch name.</returns>
|
||||
public static string? GetGitBranch()
|
||||
{
|
||||
if (branchInternal != null)
|
||||
return branchInternal;
|
||||
|
||||
var asm = typeof(Util).Assembly;
|
||||
var attrs = asm.GetCustomAttributes<AssemblyMetadataAttribute>();
|
||||
|
||||
var gitBranch = attrs.FirstOrDefault(a => a.Key == "GitBranch")?.Value;
|
||||
if (gitBranch == null)
|
||||
return null;
|
||||
|
||||
return branchInternal = gitBranch;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DescribeAddress(nint)"/>
|
||||
public static unsafe string DescribeAddress(void* p) => DescribeAddress((nint)p);
|
||||
|
||||
|
|
@ -693,16 +607,6 @@ public static partial class Util
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active Dalamud track, if this instance was launched through XIVLauncher and used a version
|
||||
/// downloaded from webservices.
|
||||
/// </summary>
|
||||
/// <returns>The name of the track, or null.</returns>
|
||||
internal static string? GetActiveTrack()
|
||||
{
|
||||
return Environment.GetEnvironmentVariable("DALAMUD_BRANCH");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a random, inoffensive, human-friendly string.
|
||||
/// </summary>
|
||||
|
|
|
|||
108
Dalamud/Utility/Versioning.cs
Normal file
108
Dalamud/Utility/Versioning.cs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers to access Dalamud versioning information.
|
||||
/// </summary>
|
||||
internal static class Versioning
|
||||
{
|
||||
private static string? scmVersionInternal;
|
||||
private static string? gitHashInternal;
|
||||
private static string? gitHashClientStructsInternal;
|
||||
private static string? branchInternal;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud version.
|
||||
/// </summary>
|
||||
/// <returns>The raw Dalamud assembly version.</returns>
|
||||
internal static string GetAssemblyVersion() =>
|
||||
Assembly.GetAssembly(typeof(Versioning))!.GetName().Version!.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Dalamud version.
|
||||
/// </summary>
|
||||
/// <returns>The parsed Dalamud assembly version.</returns>
|
||||
internal static Version GetAssemblyVersionParsed() =>
|
||||
Assembly.GetAssembly(typeof(Versioning))!.GetName().Version!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SCM Version from the assembly, or null if it cannot be found. This method will generally return
|
||||
/// the <c>git describe</c> 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.
|
||||
/// </summary>
|
||||
/// <returns>The SCM version of the assembly.</returns>
|
||||
internal static string GetScmVersion()
|
||||
{
|
||||
if (scmVersionInternal != null) return scmVersionInternal;
|
||||
|
||||
var asm = typeof(Util).Assembly;
|
||||
var attrs = asm.GetCustomAttributes<AssemblyMetadataAttribute>();
|
||||
|
||||
return scmVersionInternal = attrs.First(a => a.Key == "SCMVersion").Value
|
||||
?? asm.GetName().Version!.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <returns>The git hash of the assembly.</returns>
|
||||
internal static string? GetGitHash()
|
||||
{
|
||||
if (gitHashInternal != null)
|
||||
return gitHashInternal;
|
||||
|
||||
var asm = typeof(Util).Assembly;
|
||||
var attrs = asm.GetCustomAttributes<AssemblyMetadataAttribute>();
|
||||
|
||||
return gitHashInternal = attrs.FirstOrDefault(a => a.Key == "GitHash")?.Value ?? "N/A";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the git hash value from the assembly or null if it cannot be found.
|
||||
/// </summary>
|
||||
/// <returns>The git hash of the assembly.</returns>
|
||||
internal static string? GetGitHashClientStructs()
|
||||
{
|
||||
if (gitHashClientStructsInternal != null)
|
||||
return gitHashClientStructsInternal;
|
||||
|
||||
var asm = typeof(Util).Assembly;
|
||||
var attrs = asm.GetCustomAttributes<AssemblyMetadataAttribute>();
|
||||
|
||||
gitHashClientStructsInternal = attrs.First(a => a.Key == "GitHashClientStructs").Value;
|
||||
|
||||
return gitHashClientStructsInternal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Git branch name this version of Dalamud was built from, or null, if this is a Debug build.
|
||||
/// </summary>
|
||||
/// <returns>The branch name.</returns>
|
||||
internal static string? GetGitBranch()
|
||||
{
|
||||
if (branchInternal != null)
|
||||
return branchInternal;
|
||||
|
||||
var asm = typeof(Util).Assembly;
|
||||
var attrs = asm.GetCustomAttributes<AssemblyMetadataAttribute>();
|
||||
|
||||
var gitBranch = attrs.FirstOrDefault(a => a.Key == "GitBranch")?.Value;
|
||||
if (gitBranch == null)
|
||||
return null;
|
||||
|
||||
return branchInternal = gitBranch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active Dalamud track, if this instance was launched through XIVLauncher and used a version
|
||||
/// downloaded from webservices.
|
||||
/// </summary>
|
||||
/// <returns>The name of the track, or null.</returns>
|
||||
internal static string? GetActiveTrack()
|
||||
{
|
||||
return Environment.GetEnvironmentVariable("DALAMUD_BRANCH");
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>x64</Platforms>
|
||||
<LangVersion>13.0</LangVersion>
|
||||
<LangVersion>14.0</LangVersion>
|
||||
|
||||
<!-- Disable Intel CET. Causes crashes on unpatched Windows 10 systems. -->
|
||||
<!-- https://github.com/dotnet/runtime/issues/108589 -->
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ public static unsafe partial class ImGui
|
|||
ImGuiSliderFlags flags = ImGuiSliderFlags.None) => DragScalar(
|
||||
label,
|
||||
ImGuiDataType.Float,
|
||||
MemoryMarshal.Cast<Vector2, float>(new(ref v)),
|
||||
MemoryMarshal.Cast<Vector2, float>(new Span<Vector2>(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<Vector3, float>(new(ref v)),
|
||||
MemoryMarshal.Cast<Vector3, float>(new Span<Vector3>(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<Vector4, float>(new(ref v)),
|
||||
MemoryMarshal.Cast<Vector4, float>(new Span<Vector4>(ref v)),
|
||||
vSpeed,
|
||||
vMin,
|
||||
vMax,
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ public static unsafe partial class ImGui
|
|||
InputScalar(
|
||||
label,
|
||||
ImGuiDataType.Float,
|
||||
MemoryMarshal.Cast<Vector2, float>(new(ref data)),
|
||||
MemoryMarshal.Cast<Vector2, float>(new Span<Vector2>(ref data)),
|
||||
step,
|
||||
stepFast,
|
||||
format.MoveOrDefault("%.3f"u8),
|
||||
|
|
@ -219,7 +219,7 @@ public static unsafe partial class ImGui
|
|||
InputScalar(
|
||||
label,
|
||||
ImGuiDataType.Float,
|
||||
MemoryMarshal.Cast<Vector3, float>(new(ref data)),
|
||||
MemoryMarshal.Cast<Vector3, float>(new Span<Vector3>(ref data)),
|
||||
step,
|
||||
stepFast,
|
||||
format.MoveOrDefault("%.3f"u8),
|
||||
|
|
@ -233,7 +233,7 @@ public static unsafe partial class ImGui
|
|||
InputScalar(
|
||||
label,
|
||||
ImGuiDataType.Float,
|
||||
MemoryMarshal.Cast<Vector4, float>(new(ref data)),
|
||||
MemoryMarshal.Cast<Vector4, float>(new Span<Vector4>(ref data)),
|
||||
step,
|
||||
stepFast,
|
||||
format.MoveOrDefault("%.3f"u8),
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ public static unsafe partial class ImGui
|
|||
ImU8String format = default, ImGuiSliderFlags flags = ImGuiSliderFlags.None) => SliderScalar(
|
||||
label,
|
||||
ImGuiDataType.Float,
|
||||
MemoryMarshal.Cast<Vector2, float>(new(ref v)),
|
||||
MemoryMarshal.Cast<Vector2, float>(new Span<Vector2>(ref v)),
|
||||
vMin,
|
||||
vMax,
|
||||
format.MoveOrDefault("%.3f"u8),
|
||||
|
|
@ -222,7 +222,7 @@ public static unsafe partial class ImGui
|
|||
SliderScalar(
|
||||
label,
|
||||
ImGuiDataType.Float,
|
||||
MemoryMarshal.Cast<Vector3, float>(new(ref v)),
|
||||
MemoryMarshal.Cast<Vector3, float>(new Span<Vector3>(ref v)),
|
||||
vMin,
|
||||
vMax,
|
||||
format.MoveOrDefault("%.3f"u8),
|
||||
|
|
@ -236,7 +236,7 @@ public static unsafe partial class ImGui
|
|||
SliderScalar(
|
||||
label,
|
||||
ImGuiDataType.Float,
|
||||
MemoryMarshal.Cast<Vector4, float>(new(ref v)),
|
||||
MemoryMarshal.Cast<Vector4, float>(new Span<Vector4>(ref v)),
|
||||
vMin,
|
||||
vMax,
|
||||
format.MoveOrDefault("%.3f"u8),
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ public ref struct ImU8String
|
|||
|
||||
return this.rentedBuffer is { } buf
|
||||
? buf.AsSpan()
|
||||
: MemoryMarshal.Cast<FixedBufferContainer, byte>(new(ref Unsafe.AsRef(ref this.fixedBuffer)));
|
||||
: MemoryMarshal.Cast<FixedBufferContainer, byte>(new Span<FixedBufferContainer>(ref Unsafe.AsRef(ref this.fixedBuffer)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ public ref struct ImU8String
|
|||
private ref byte FixedBufferByteRef => ref this.FixedBufferSpan[0];
|
||||
|
||||
private Span<byte> FixedBufferSpan =>
|
||||
MemoryMarshal.Cast<FixedBufferContainer, byte>(new(ref Unsafe.AsRef(ref this.fixedBuffer)));
|
||||
MemoryMarshal.Cast<FixedBufferContainer, byte>(new Span<FixedBufferContainer>(ref Unsafe.AsRef(ref this.fixedBuffer)));
|
||||
|
||||
public static implicit operator ImU8String(ReadOnlySpan<byte> text) => new(text);
|
||||
public static implicit operator ImU8String(ReadOnlyMemory<byte> text) => new(text);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue