Add support for modelchara id and extended arrays.

This commit is contained in:
Ottermandias 2023-07-25 15:36:08 +02:00
parent 1f4685f505
commit afc987432a
3 changed files with 89 additions and 55 deletions

View file

@ -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]}.");
}

View file

@ -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!);
}
}

View file

@ -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,
})!;
}
}
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;
}