diff --git a/Penumbra.GameData/Structs/CharacterEquip.cs b/Penumbra.GameData/Structs/CharacterEquip.cs new file mode 100644 index 00000000..c16a69a5 --- /dev/null +++ b/Penumbra.GameData/Structs/CharacterEquip.cs @@ -0,0 +1,105 @@ +using System; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer; + +public readonly unsafe struct CharacterEquip +{ + public static readonly CharacterEquip Null = new(null); + + private readonly CharacterArmor* _armor; + + public IntPtr Address + => (IntPtr)_armor; + + public ref CharacterArmor this[int idx] + => ref _armor[idx]; + + public ref CharacterArmor this[uint idx] + => ref _armor[idx]; + + public ref CharacterArmor this[EquipSlot slot] + => ref _armor[IndexOf(slot)]; + + + public ref CharacterArmor Head + => ref _armor[0]; + + public ref CharacterArmor Body + => ref _armor[1]; + + public ref CharacterArmor Hands + => ref _armor[2]; + + public ref CharacterArmor Legs + => ref _armor[3]; + + public ref CharacterArmor Feet + => ref _armor[4]; + + public ref CharacterArmor Ears + => ref _armor[5]; + + public ref CharacterArmor Neck + => ref _armor[6]; + + public ref CharacterArmor Wrists + => ref _armor[7]; + + public ref CharacterArmor RFinger + => ref _armor[8]; + + public ref CharacterArmor LFinger + => ref _armor[9]; + + public CharacterEquip(CharacterArmor* val) + => _armor = val; + + public static implicit operator CharacterEquip(CharacterArmor* val) + => new(val); + + public static implicit operator CharacterEquip(IntPtr val) + => new((CharacterArmor*)val); + + public static implicit operator CharacterEquip(ReadOnlySpan val) + { + if (val.Length != 10) + throw new ArgumentException("Invalid number of equipment pieces in span."); + + fixed (CharacterArmor* ptr = val) + { + return new CharacterEquip(ptr); + } + } + + public static implicit operator bool(CharacterEquip equip) + => equip._armor != null; + + public static bool operator true(CharacterEquip equip) + => equip._armor != null; + + public static bool operator false(CharacterEquip equip) + => equip._armor == null; + + public static bool operator !(CharacterEquip equip) + => equip._armor == null; + + private static int IndexOf(EquipSlot slot) + { + return slot switch + { + EquipSlot.Head => 0, + EquipSlot.Body => 1, + EquipSlot.Hands => 2, + EquipSlot.Legs => 3, + EquipSlot.Feet => 4, + EquipSlot.Ears => 5, + EquipSlot.Neck => 6, + EquipSlot.Wrists => 7, + EquipSlot.RFinger => 8, + EquipSlot.LFinger => 9, + _ => throw new ArgumentOutOfRangeException(nameof(slot), slot, null), + }; + } +} diff --git a/Penumbra.GameData/Structs/CharacterEquipment.cs b/Penumbra.GameData/Structs/CharacterEquipment.cs deleted file mode 100644 index 357ce36b..00000000 --- a/Penumbra.GameData/Structs/CharacterEquipment.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Dalamud.Game.ClientState.Objects.Types; - -// Read the customization data regarding weapons and displayable equipment from an actor struct. -// Stores the data in a 56 bytes, i.e. 7 longs for easier comparison. -namespace Penumbra.GameData.Structs; - -[StructLayout( LayoutKind.Sequential, Pack = 1 )] -public class CharacterEquipment -{ - public const int MainWeaponOffset = 0x6D0; - public const int OffWeaponOffset = 0x738; - public const int EquipmentOffset = 0x808; - public const int EquipmentSlots = 10; - public const int WeaponSlots = 2; - - public CharacterWeapon MainHand; - public CharacterWeapon OffHand; - public CharacterArmor Head; - public CharacterArmor Body; - public CharacterArmor Hands; - public CharacterArmor Legs; - public CharacterArmor Feet; - public CharacterArmor Ears; - public CharacterArmor Neck; - public CharacterArmor Wrists; - public CharacterArmor RFinger; - public CharacterArmor LFinger; - public ushort IsSet; // Also fills struct size to 56, a multiple of 8. - - public CharacterEquipment() - => Clear(); - - public CharacterEquipment( Character actor ) - : this( actor.Address ) - { } - - public override string ToString() - => IsSet == 0 - ? "(Not Set)" - : $"({MainHand}) | ({OffHand}) | ({Head}) | ({Body}) | ({Hands}) | ({Legs}) | " - + $"({Feet}) | ({Ears}) | ({Neck}) | ({Wrists}) | ({LFinger}) | ({RFinger})"; - - public bool Equal( Character rhs ) - => CompareData( new CharacterEquipment( rhs ) ); - - public bool Equal( CharacterEquipment rhs ) - => CompareData( rhs ); - - public bool CompareAndUpdate( Character rhs ) - => CompareAndOverwrite( new CharacterEquipment( rhs ) ); - - public bool CompareAndUpdate( CharacterEquipment rhs ) - => CompareAndOverwrite( rhs ); - - private unsafe CharacterEquipment( IntPtr actorAddress ) - { - IsSet = 1; - var actorPtr = ( byte* )actorAddress.ToPointer(); - fixed( CharacterWeapon* main = &MainHand, off = &OffHand ) - { - Buffer.MemoryCopy( actorPtr + MainWeaponOffset, main, sizeof( CharacterWeapon ), sizeof( CharacterWeapon ) ); - Buffer.MemoryCopy( actorPtr + OffWeaponOffset, off, sizeof( CharacterWeapon ), sizeof( CharacterWeapon ) ); - } - - fixed( CharacterArmor* equipment = &Head ) - { - Buffer.MemoryCopy( actorPtr + EquipmentOffset, equipment, EquipmentSlots * sizeof( CharacterArmor ), - EquipmentSlots * sizeof( CharacterArmor ) ); - } - } - - public unsafe void Clear() - { - fixed( CharacterWeapon* main = &MainHand ) - { - var structSizeEights = ( 2 + EquipmentSlots * sizeof( CharacterArmor ) + WeaponSlots * sizeof( CharacterWeapon ) ) / 8; - for( ulong* ptr = ( ulong* )main, end = ptr + structSizeEights; ptr != end; ++ptr ) - { - *ptr = 0; - } - } - } - - private unsafe bool CompareAndOverwrite( CharacterEquipment rhs ) - { - var structSizeEights = ( 2 + EquipmentSlots * sizeof( CharacterArmor ) + WeaponSlots * sizeof( CharacterWeapon ) ) / 8; - var ret = true; - fixed( CharacterWeapon* data1 = &MainHand, data2 = &rhs.MainHand ) - { - var ptr1 = ( ulong* )data1; - var ptr2 = ( ulong* )data2; - for( var end = ptr1 + structSizeEights; ptr1 != end; ++ptr1, ++ptr2 ) - { - if( *ptr1 != *ptr2 ) - { - *ptr1 = *ptr2; - ret = false; - } - } - } - - return ret; - } - - private unsafe bool CompareData( CharacterEquipment rhs ) - { - var structSizeEights = ( 2 + EquipmentSlots * sizeof( CharacterArmor ) + WeaponSlots * sizeof( CharacterWeapon ) ) / 8; - fixed( CharacterWeapon* data1 = &MainHand, data2 = &rhs.MainHand ) - { - var ptr1 = ( ulong* )data1; - var ptr2 = ( ulong* )data2; - for( var end = ptr1 + structSizeEights; ptr1 != end; ++ptr1, ++ptr2 ) - { - if( *ptr1 != *ptr2 ) - { - return false; - } - } - } - - return true; - } - - public unsafe void WriteBytes( byte[] array, int offset = 0 ) - { - fixed( CharacterWeapon* data = &MainHand ) - { - Marshal.Copy( new IntPtr( data ), array, offset, 56 ); - } - } - - public byte[] ToBytes() - { - var ret = new byte[56]; - WriteBytes( ret ); - return ret; - } - - public unsafe void FromBytes( byte[] array, int offset = 0 ) - { - fixed( CharacterWeapon* data = &MainHand ) - { - Marshal.Copy( array, offset, new IntPtr( data ), 56 ); - } - } -} \ No newline at end of file diff --git a/Penumbra.GameData/Structs/CustomizeData.cs b/Penumbra.GameData/Structs/CustomizeData.cs new file mode 100644 index 00000000..0c7fcbf1 --- /dev/null +++ b/Penumbra.GameData/Structs/CustomizeData.cs @@ -0,0 +1,55 @@ +using System; +using Penumbra.GameData.Util; + +namespace Penumbra.GameData.Structs; + +public unsafe struct CustomizeData : IEquatable< CustomizeData > +{ + public const int Size = 26; + + public fixed byte Data[Size]; + + public void Read( void* source ) + { + fixed( byte* ptr = Data ) + { + Functions.MemCpyUnchecked( ptr, source, Size ); + } + } + + public void Write( void* target ) + { + fixed( byte* ptr = Data ) + { + Functions.MemCpyUnchecked( target, ptr, Size ); + } + } + + public CustomizeData Clone() + { + var ret = new CustomizeData(); + Write( ret.Data ); + return ret; + } + + public bool Equals( CustomizeData other ) + { + fixed( byte* ptr = Data ) + { + return Functions.MemCmpUnchecked( ptr, other.Data, Size ) == 0; + } + } + + public override bool Equals( object? obj ) + => obj is CustomizeData other && Equals( other ); + + public override int GetHashCode() + { + fixed( byte* ptr = Data ) + { + var p = ( int* )ptr; + var u = *( ushort* )( p + 6 ); + return HashCode.Combine( *p, p[ 1 ], p[ 2 ], p[ 3 ], p[ 4 ], p[ 5 ], u ); + } + } +} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Data.cs b/Penumbra/Interop/Resolver/PathResolver.Data.cs index 32bf5fbc..0a3d7fa2 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Data.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Data.cs @@ -11,11 +11,11 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Component.GUI; -using Lumina.Data.Parsing.Uld; using Penumbra.Api; using Penumbra.Collections; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; namespace Penumbra.Interop.Resolver; @@ -250,8 +250,9 @@ public unsafe partial class PathResolver return null; } - var pc = ( Character* )player.Address; - return pc->ClassJob == ( ( Character* )gameObject )->ClassJob ? player.Name.ToString() : null; + var customize1 = ( CustomizeData* )( ( Character* )gameObject )->CustomizeData; + var customize2 = ( CustomizeData* )( ( Character* )player.Address )->CustomizeData; + return customize1->Equals( *customize2 ) ? player.Name.ToString() : null; } // Identify the owner of a companion, mount or monster and apply the corresponding collection.