diff --git a/Glamourer/CharacterSave.cs b/Glamourer/CharacterSave.cs index ea35669..08f1bd7 100644 --- a/Glamourer/CharacterSave.cs +++ b/Glamourer/CharacterSave.cs @@ -39,11 +39,13 @@ public class CharacterSaveConverter : JsonConverter [JsonConverter(typeof(CharacterSaveConverter))] public class CharacterSave { - public const byte CurrentVersion = 2; + public const byte CurrentVersion = 4; public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes; public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 1; + // Version 4 is part of the rework. + public const byte TotalSizeVersion4 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 1 + 4; - public const byte TotalSize = TotalSizeVersion2; + public const byte TotalSize = TotalSizeVersion4; private readonly byte[] _bytes = new byte[TotalSize]; @@ -63,6 +65,21 @@ public class CharacterSave public byte Version => _bytes[0]; + public uint ModelId + { + get => (uint)_bytes[1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 1] + | ((uint)_bytes[1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 1 + 1] << 8) + | ((uint)_bytes[1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 1 + 2] << 16) + | ((uint)_bytes[1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 1 + 3] << 24); + set + { + _bytes[1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 1] = (byte)value; + _bytes[1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 2] = (byte)(value >> 8); + _bytes[1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 3] = (byte)(value >> 16); + _bytes[1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 4] = (byte)(value >> 24); + } + } + public bool WriteCustomizations { get => (_bytes[1] & 0x01) != 0; @@ -295,8 +312,9 @@ public class CharacterSave SetWeaponState = true; StateFlags = (byte)((a.IsHatVisible() ? 0x00 : 0x01) | (a.IsVisorToggled() ? 0x10 : 0x00) | (a.IsWeaponHidden() ? 0x02 : 0x00)); - IsWet = a.IsWet(); - Alpha = a.Alpha(); + IsWet = a.IsWet(); + Alpha = a.Alpha(); + ModelId = a.ModelType(); } @@ -304,6 +322,7 @@ public class CharacterSave { Glamourer.RevertableDesigns.Add(a); + a.SetModelType(ModelId); if (WriteCustomizations) Customizations.Write(a.Address); if (WriteEquipment != CharacterEquipMask.None) @@ -357,8 +376,28 @@ public class CharacterSave case 2: CheckSize(bytes.Length, TotalSizeVersion2); CheckRange(2, bytes[1], 0, 0x3F); + oldVersion = true; + bytes[0] = CurrentVersion; + ModelId = 0; + break; + case 3: + throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tVersion 3 is only supported by the rework."); + case CurrentVersion: + CheckSize(bytes.Length, TotalSizeVersion4); + CheckRange(2, bytes[1], 0, 0x3F); oldVersion = false; break; + // This is a compatibility version between old glamourer and new glamourer, + // where new glamourer sends the byte array for old in front of its own. + case 5: + if (bytes.Length < TotalSizeVersion4) + throw new Exception( + $"Can not parse Base64 string into CharacterSave:\n\tInvalid size {bytes.Length} instead of at least {TotalSizeVersion4}."); + CheckRange(2, bytes[1], 0, 0x3F); + oldVersion = false; + CheckCharacterMask(bytes[2], bytes[3]); + bytes.AsSpan(0, TotalSizeVersion4).CopyTo(_bytes.AsSpan()); + return; default: throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid Version {bytes[0]}."); } diff --git a/Glamourer/Gui/InterfaceActorPanel.cs b/Glamourer/Gui/InterfaceActorPanel.cs index 701fc5a..dc58d87 100644 --- a/Glamourer/Gui/InterfaceActorPanel.cs +++ b/Glamourer/Gui/InterfaceActorPanel.cs @@ -232,7 +232,7 @@ namespace Glamourer.Gui if (!ImGui.Selectable($"{id:D6}##models", id == currentModel) || id == currentModel) continue; - _player!.SetModelType((int) id); + _player!.SetModelType(id); Glamourer.Penumbra.UpdateCharacters(_player!); } } diff --git a/Penumbra.PlayerWatch/CharacterFactory.cs b/Penumbra.PlayerWatch/CharacterFactory.cs index b88cb28..b30c77c 100644 --- a/Penumbra.PlayerWatch/CharacterFactory.cs +++ b/Penumbra.PlayerWatch/CharacterFactory.cs @@ -4,60 +4,55 @@ using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; -namespace Penumbra.PlayerWatch +namespace Penumbra.PlayerWatch; + +public static class CharacterFactory { - public static class CharacterFactory + private static ConstructorInfo? _characterConstructor; + + private static void Initialize() { - private static ConstructorInfo? _characterConstructor; - - private static void Initialize() + _characterConstructor ??= typeof(Character).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { - _characterConstructor ??= typeof( Character ).GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, new[] - { - typeof( IntPtr ), - }, null )!; - } - - private static Character Character( IntPtr address ) - { - Initialize(); - return ( Character )_characterConstructor?.Invoke( new object[] - { - address, - } )!; - } - - public static Character? Convert( GameObject? actor ) - { - if( actor == null ) - { - return null; - } - - return actor switch - { - PlayerCharacter p => p, - BattleChara b => b, - _ => actor.ObjectKind switch - { - ObjectKind.BattleNpc => Character( actor.Address ), - ObjectKind.Companion => Character( actor.Address ), - ObjectKind.Retainer => Character( actor.Address ), - ObjectKind.EventNpc => Character( actor.Address ), - _ => null, - }, - }; - } + typeof(IntPtr), + }, null)!; } - public static class GameObjectExtensions + private static Character Character(IntPtr address) { - private const int ModelTypeOffset = 0x01B4; - - public static unsafe int ModelType( this GameObject actor ) - => *( int* )( actor.Address + ModelTypeOffset ); - - public static unsafe void SetModelType( this GameObject actor, int value ) - => *( int* )( actor.Address + ModelTypeOffset ) = value; + Initialize(); + return (Character)_characterConstructor?.Invoke(new object[] + { + address, + })!; } -} \ No newline at end of file + + public static Character? Convert(GameObject? actor) + { + if (actor == null) + return null; + + return actor switch + { + PlayerCharacter p => p, + BattleChara b => b, + _ => actor.ObjectKind switch + { + ObjectKind.BattleNpc => Character(actor.Address), + ObjectKind.Companion => Character(actor.Address), + ObjectKind.Retainer => Character(actor.Address), + ObjectKind.EventNpc => Character(actor.Address), + _ => null, + }, + }; + } +} + +public static class GameObjectExtensions +{ + public static unsafe uint ModelType(this Character actor) + => (uint) ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address)->CharacterData.ModelCharaId; + + public static unsafe void SetModelType(this Character actor, uint value) + => ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address)->CharacterData.ModelCharaId = (int) value; +}