diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs index 36083337e..1d0087c5b 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs @@ -1,8 +1,4 @@ -using System.Runtime.CompilerServices; - -using Dalamud.Memory; - -using FFXIVClientStructs.FFXIV.Component.GUI; +using Dalamud.Game.Gui.NativeWrapper; namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; @@ -17,7 +13,6 @@ public abstract unsafe class AddonArgs public const string InvalidAddon = "NullAddon"; private string? addonName; - private IntPtr addon; /// /// Gets the name of the addon this args referrers to. @@ -27,10 +22,10 @@ public abstract unsafe class AddonArgs /// /// Gets the pointer to the addons AtkUnitBase. /// - public nint Addon + public AtkUnitBasePtr Addon { - get => this.AddonInternal; - init => this.AddonInternal = value; + get; + internal set; } /// @@ -38,22 +33,6 @@ public abstract unsafe class AddonArgs /// public abstract AddonArgsType Type { get; } - /// - /// Gets or sets the pointer to the addons AtkUnitBase. - /// - internal nint AddonInternal - { - get => this.addon; - set - { - this.addon = value; - - // Note: always clear addonName on updating the addon being pointed. - // Same address may point to a different addon. - this.addonName = null; - } - } - /// /// Checks if addon name matches the given span of char. /// @@ -61,19 +40,18 @@ public abstract unsafe class AddonArgs /// Whether it is the case. internal bool IsAddon(ReadOnlySpan name) { - if (this.Addon == nint.Zero) return false; - if (name.Length is 0 or > 0x20) + if (this.Addon.IsNull) return false; - var addonPointer = (AtkUnitBase*)this.Addon; - if (addonPointer->Name[0] == 0) return false; + if (name.Length is 0 or > 32) + return false; - // note: might want to rewrite this to just compare to NameString - return MemoryHelper.EqualsZeroTerminatedString( - name, - (nint)Unsafe.AsPointer(ref addonPointer->Name[0]), - null, - 0x20); + var addonName = this.Addon.Name; + + if (string.IsNullOrEmpty(addonName)) + return false; + + return name == addonName; } /// @@ -82,11 +60,13 @@ public abstract unsafe class AddonArgs /// The name of the addon for this object. when invalid. private string GetAddonName() { - if (this.Addon == nint.Zero) return InvalidAddon; + if (this.Addon.IsNull) return InvalidAddon; - var addonPointer = (AtkUnitBase*)this.Addon; - if (addonPointer->Name[0] == 0) return InvalidAddon; + var name = this.Addon.Name; - return this.addonName ??= addonPointer->NameString; + if (string.IsNullOrEmpty(name)) + return InvalidAddon; + + return this.addonName ??= name; } } diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs index b9179edde..ee9f3a44c 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Runtime.CompilerServices; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; +using Dalamud.Game.Gui.NativeWrapper; using Dalamud.Hooking; using Dalamud.Hooking.Internal; using Dalamud.IoC; @@ -238,7 +239,7 @@ internal unsafe class AddonLifecycle : IInternalDisposableService } using var returner = this.argsPool.Rent(out AddonSetupArgs arg); - arg.AddonInternal = (nint)addon; + arg.Addon = (nint)addon; arg.AtkValueCount = valueCount; arg.AtkValues = (nint)values; this.InvokeListenersSafely(AddonEvent.PreSetup, arg); @@ -270,7 +271,7 @@ internal unsafe class AddonLifecycle : IInternalDisposableService } using var returner = this.argsPool.Rent(out AddonFinalizeArgs arg); - arg.AddonInternal = (nint)atkUnitBase[0]; + arg.Addon = (nint)atkUnitBase[0]; this.InvokeListenersSafely(AddonEvent.PreFinalize, arg); try @@ -286,7 +287,7 @@ internal unsafe class AddonLifecycle : IInternalDisposableService private void OnAddonDraw(AtkUnitBase* addon) { using var returner = this.argsPool.Rent(out AddonDrawArgs arg); - arg.AddonInternal = (nint)addon; + arg.Addon = (nint)addon; this.InvokeListenersSafely(AddonEvent.PreDraw, arg); try @@ -304,7 +305,7 @@ internal unsafe class AddonLifecycle : IInternalDisposableService private void OnAddonUpdate(AtkUnitBase* addon, float delta) { using var returner = this.argsPool.Rent(out AddonUpdateArgs arg); - arg.AddonInternal = (nint)addon; + arg.Addon = (nint)addon; arg.TimeDeltaInternal = delta; this.InvokeListenersSafely(AddonEvent.PreUpdate, arg); @@ -325,7 +326,7 @@ internal unsafe class AddonLifecycle : IInternalDisposableService var result = false; using var returner = this.argsPool.Rent(out AddonRefreshArgs arg); - arg.AddonInternal = (nint)addon; + arg.Addon = (nint)addon; arg.AtkValueCount = valueCount; arg.AtkValues = (nint)values; this.InvokeListenersSafely(AddonEvent.PreRefresh, arg); @@ -348,7 +349,7 @@ internal unsafe class AddonLifecycle : IInternalDisposableService private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) { using var returner = this.argsPool.Rent(out AddonRequestedUpdateArgs arg); - arg.AddonInternal = (nint)addon; + arg.Addon = (nint)addon; arg.NumberArrayData = (nint)numberArrayData; arg.StringArrayData = (nint)stringArrayData; this.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, arg); diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs index a66440b25..110c67491 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Hooking; @@ -86,7 +86,7 @@ internal unsafe class AddonLifecycleReceiveEventListener : IDisposable } using var returner = this.argsPool.Rent(out AddonReceiveEventArgs arg); - arg.AddonInternal = (nint)addon; + arg.Addon = (nint)addon; arg.AtkEventType = (byte)eventType; arg.EventParam = eventParam; arg.AtkEvent = (IntPtr)atkEvent; diff --git a/Dalamud/Game/Gui/Dtr/DtrBar.cs b/Dalamud/Game/Gui/Dtr/DtrBar.cs index 8339838fd..dc814232e 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBar.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBar.cs @@ -427,7 +427,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar private void FixCollision(AddonEvent eventType, AddonArgs addonInfo) { - var addon = (AtkUnitBase*)addonInfo.Addon; + var addon = addonInfo.Addon.Struct; if (addon->RootNode is null || addon->UldManager.NodeList is null) return; float minX = addon->RootNode->Width; diff --git a/Dalamud/Game/Gui/NativeWrapper/AtkUnitBasePtr.cs b/Dalamud/Game/Gui/NativeWrapper/AtkUnitBasePtr.cs new file mode 100644 index 000000000..a35c4a743 --- /dev/null +++ b/Dalamud/Game/Gui/NativeWrapper/AtkUnitBasePtr.cs @@ -0,0 +1,141 @@ +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.InteropServices; + +using FFXIVClientStructs.FFXIV.Component.GUI; +using FFXIVClientStructs.Interop; + +namespace Dalamud.Game.Gui.NativeWrapper; + +/// +/// A wrapper for AtkUnitBase. +/// +/// The address to the AtkUnitBase. +[StructLayout(LayoutKind.Explicit, Size = 0x08)] +public readonly unsafe struct AtkUnitBasePtr(nint address) : IEquatable +{ + /// + /// The address to the AtkUnitBase. + /// + [FieldOffset(0x00)] + public readonly nint Address = address; + + /// + /// Gets a value indicating whether the underlying pointer is a nullptr. + /// + public readonly bool IsNull => this.Address == 0; + + /// + /// Gets a value indicating whether the OnSetup function of the AtkUnitBase has been called. + /// + public readonly bool IsReady => !this.IsNull && this.Struct->IsReady; + + /// + /// Gets a value indicating whether the AtkUnitBase is visible. + /// + public readonly bool IsVisible => !this.IsNull && this.Struct->IsVisible; + + /// + /// Gets the name of the AtkUnitBase. + /// + public readonly string Name => this.IsNull ? string.Empty : this.Struct->NameString; + + /// + /// Gets the id of the AtkUnitBase. + /// + public readonly ushort Id => this.IsNull ? (ushort)0 : this.Struct->Id; + + /// + /// Gets the parent id of the AtkUnitBase. + /// + public readonly ushort ParentId => this.IsNull ? (ushort)0 : this.Struct->ParentId; + + /// + /// Gets the host id of the AtkUnitBase. + /// + public readonly ushort HostId => this.IsNull ? (ushort)0 : this.Struct->HostId; + + /// + /// Gets the scale of the AtkUnitBase. + /// + public readonly float Scale => this.IsNull ? 0f : this.Struct->Scale; + + /// + /// Gets the x-position of the AtkUnitBase. + /// + public readonly short X => this.IsNull ? (short)0 : this.Struct->X; + + /// + /// Gets the y-position of the AtkUnitBase. + /// + public readonly short Y => this.IsNull ? (short)0 : this.Struct->Y; + + /// + /// Gets the width of the AtkUnitBase. + /// + public readonly float Width => this.IsNull ? 0f : this.Struct->GetScaledWidth(false); + + /// + /// Gets the height of the AtkUnitBase. + /// + public readonly float Height => this.IsNull ? 0f : this.Struct->GetScaledHeight(false); + + /// + /// Gets the scaled width of the AtkUnitBase. + /// + public readonly float ScaledWidth => this.IsNull ? 0f : this.Struct->GetScaledWidth(true); + + /// + /// Gets the scaled height of the AtkUnitBase. + /// + public readonly float ScaledHeight => this.IsNull ? 0f : this.Struct->GetScaledHeight(true); + + /// + /// Gets the position of the AtkUnitBase. + /// + public readonly Vector2 Position => new(this.X, this.Y); + + /// + /// Gets the size of the AtkUnitBase. + /// + public readonly Vector2 Size => new(this.Width, this.Height); + + /// + /// Gets the scaled size of the AtkUnitBase. + /// + public readonly Vector2 ScaledSize => new(this.ScaledWidth, this.ScaledHeight); + + /// + /// Gets the AtkUnitBase*. + /// + /// Internal use only. + internal readonly AtkUnitBase* Struct => (AtkUnitBase*)this.Address; + + public static implicit operator nint(AtkUnitBasePtr wrapper) => wrapper.Address; + + public static implicit operator AtkUnitBasePtr(nint address) => new(address); + + public static bool operator ==(AtkUnitBasePtr left, AtkUnitBasePtr right) => left.Address == right.Address; + + public static bool operator !=(AtkUnitBasePtr left, AtkUnitBasePtr right) => left.Address != right.Address; + + /// + /// Focuses the AtkUnitBase. + /// + public readonly void Focus() + { + if (!this.IsNull) + this.Struct->Focus(); + } + + /// Determines whether the specified AtkUnitBasePtr is equal to the current AtkUnitBasePtr. + /// The AtkUnitBasePtr to compare with the current AtkUnitBasePtr. + /// true if the specified AtkUnitBasePtr is equal to the current AtkUnitBasePtr; otherwise, false. + public readonly bool Equals(AtkUnitBasePtr other) => this.Address == other.Address; + + /// + public override readonly bool Equals(object obj) => obj is AtkUnitBasePtr wrapper && this.Equals(wrapper); + + /// + public override readonly int GetHashCode() => ((nuint)this.Address).GetHashCode(); +} diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index 2b8973dc5..9d476e10b 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -477,7 +477,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable { if (args is not AddonDrawArgs drawArgs) return; - var addon = (AtkUnitBase*)drawArgs.Addon; + var addon = drawArgs.Addon.Struct; var textNode = addon->GetTextNodeById(3); // look and feel init. should be harmless to set.