From e6e033bb8b30e626581b5d8317309ff3aa7f3fa4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 14 Apr 2022 21:59:30 +0200 Subject: [PATCH] Simple changes for 6.1 --- .../Customization/CharaMakeParams.cs | 211 ++--- .../Customization/CharacterCustomization.cs | 2 +- .../Customization/CustomizationOptions.cs | 2 +- Glamourer/CharacterExtensions.cs | 90 ++- Glamourer/CharacterSave.cs | 756 +++++++++--------- Glamourer/Gui/InterfaceCustomization.cs | 2 +- Glamourer/Gui/InterfaceDesigns.cs | 3 +- Glamourer/Gui/InterfaceMiscellaneous.cs | 2 +- 8 files changed, 539 insertions(+), 529 deletions(-) diff --git a/Glamourer.GameData/Customization/CharaMakeParams.cs b/Glamourer.GameData/Customization/CharaMakeParams.cs index 497cdb9..4ac224f 100644 --- a/Glamourer.GameData/Customization/CharaMakeParams.cs +++ b/Glamourer.GameData/Customization/CharaMakeParams.cs @@ -2,123 +2,124 @@ using Lumina.Excel; using Lumina.Excel.GeneratedSheets; -namespace Glamourer.Customization +namespace Glamourer.Customization; + +[Sheet("CharaMakeParams")] +public class CharaMakeParams : ExcelRow { - [Sheet("CharaMakeParams")] - public class CharaMakeParams : ExcelRow + public const int NumMenus = 28; + public const int NumVoices = 12; + public const int NumGraphics = 10; + public const int MaxNumValues = 100; + public const int NumFaces = 8; + public const int NumFeatures = 7; + public const int NumEquip = 3; + + public enum MenuType { - public const int NumMenus = 28; - public const int NumVoices = 12; - public const int NumGraphics = 10; - public const int MaxNumValues = 100; - public const int NumFaces = 8; - public const int NumFeatures = 7; - public const int NumEquip = 3; + ListSelector = 0, + IconSelector = 1, + ColorPicker = 2, + DoubleColorPicker = 3, + MultiIconSelector = 4, + Percentage = 5, + } - public enum MenuType + public struct Menu + { + public uint Id; + public byte InitVal; + public MenuType Type; + public byte Size; + public byte LookAt; + public uint Mask; + public CustomizationId Customization; + public uint[] Values; + public byte[] Graphic; + } + + public struct FacialFeatures + { + public uint[] Icons; + } + + public LazyRow Race { get; set; } = null!; + public LazyRow Tribe { get; set; } = null!; + + public sbyte Gender { get; set; } + + public Menu[] Menus { get; set; } = new Menu[NumMenus]; + public byte[] Voices { get; set; } = new byte[NumVoices]; + public FacialFeatures[] FacialFeatureByFace { get; set; } = new FacialFeatures[NumFaces]; + public CharaMakeType.UnkData3347Obj[] Equip { get; set; } = new CharaMakeType.UnkData3347Obj[NumEquip]; + + public override void PopulateData(RowParser parser, Lumina.GameData gameData, Language language) + { + RowId = parser.RowId; + SubRowId = parser.SubRowId; + Race = new LazyRow(gameData, parser.ReadColumn(0), language); + Tribe = new LazyRow(gameData, parser.ReadColumn(1), language); + Gender = parser.ReadColumn(2); + for (var i = 0; i < NumMenus; ++i) { - ListSelector = 0, - IconSelector = 1, - ColorPicker = 2, - DoubleColorPicker = 3, - MultiIconSelector = 4, - Percentage = 5, - } + Menus[i].Id = parser.ReadColumn(3 + 0 * NumMenus + i); + Menus[i].InitVal = parser.ReadColumn(3 + 1 * NumMenus + i); + Menus[i].Type = (MenuType)parser.ReadColumn(3 + 2 * NumMenus + i); + Menus[i].Size = parser.ReadColumn(3 + 3 * NumMenus + i); + Menus[i].LookAt = parser.ReadColumn(3 + 4 * NumMenus + i); + Menus[i].Mask = parser.ReadColumn(3 + 5 * NumMenus + i); + Menus[i].Customization = (CustomizationId)parser.ReadColumn(3 + 6 * NumMenus + i); + Menus[i].Values = new uint[Menus[i].Size]; - public struct Menu - { - public uint Id; - public byte InitVal; - public MenuType Type; - public byte Size; - public byte LookAt; - public uint Mask; - public CustomizationId Customization; - public uint[] Values; - public byte[] Graphic; - } - - public struct FacialFeatures - { - public uint[] Icons; - } - - public LazyRow Race { get; set; } = null!; - public LazyRow Tribe { get; set; } = null!; - - public sbyte Gender { get; set; } - - public Menu[] Menus { get; set; } = new Menu[NumMenus]; - public byte[] Voices { get; set; } = new byte[NumVoices]; - public FacialFeatures[] FacialFeatureByFace { get; set; } = new FacialFeatures[NumFaces]; - public CharaMakeType.CharaMakeTypeUnkData3347Obj[] Equip { get; set; } = new CharaMakeType.CharaMakeTypeUnkData3347Obj[NumEquip]; - - public override void PopulateData(RowParser parser, Lumina.GameData gameData, Language language) - { - RowId = parser.RowId; - SubRowId = parser.SubRowId; - Race = new LazyRow(gameData, parser.ReadColumn(0), language); - Tribe = new LazyRow(gameData, parser.ReadColumn(1), language); - Gender = parser.ReadColumn(2); - for (var i = 0; i < NumMenus; ++i) + switch (Menus[i].Type) { - Menus[i].Id = parser.ReadColumn(3 + 0 * NumMenus + i); - Menus[i].InitVal = parser.ReadColumn(3 + 1 * NumMenus + i); - Menus[i].Type = (MenuType) parser.ReadColumn(3 + 2 * NumMenus + i); - Menus[i].Size = parser.ReadColumn(3 + 3 * NumMenus + i); - Menus[i].LookAt = parser.ReadColumn(3 + 4 * NumMenus + i); - Menus[i].Mask = parser.ReadColumn(3 + 5 * NumMenus + i); - Menus[i].Customization = (CustomizationId) parser.ReadColumn(3 + 6 * NumMenus + i); - Menus[i].Values = new uint[Menus[i].Size]; - - switch (Menus[i].Type) - { - case MenuType.ColorPicker: - case MenuType.DoubleColorPicker: - case MenuType.Percentage: - break; - default: - for (var j = 0; j < Menus[i].Size; ++j) - Menus[i].Values[j] = parser.ReadColumn(3 + (7 + j) * NumMenus + i); - break; - } - - Menus[i].Graphic = new byte[NumGraphics]; - for (var j = 0; j < NumGraphics; ++j) - Menus[i].Graphic[j] = parser.ReadColumn(3 + (MaxNumValues + 7 + j) * NumMenus + i); + case MenuType.ColorPicker: + case MenuType.DoubleColorPicker: + case MenuType.Percentage: + break; + default: + for (var j = 0; j < Menus[i].Size; ++j) + Menus[i].Values[j] = parser.ReadColumn(3 + (7 + j) * NumMenus + i); + break; } - for (var i = 0; i < NumVoices; ++i) - Voices[i] = parser.ReadColumn(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + i); + Menus[i].Graphic = new byte[NumGraphics]; + for (var j = 0; j < NumGraphics; ++j) + Menus[i].Graphic[j] = parser.ReadColumn(3 + (MaxNumValues + 7 + j) * NumMenus + i); + } - for (var i = 0; i < NumFaces; ++i) - { - FacialFeatureByFace[i].Icons = new uint[NumFeatures]; - for (var j = 0; j < NumFeatures; ++j) - FacialFeatureByFace[i].Icons[j] = - (uint) parser.ReadColumn(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + j * NumFaces + i); - } + for (var i = 0; i < NumVoices; ++i) + Voices[i] = parser.ReadColumn(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + i); - for (var i = 0; i < NumEquip; ++i) + for (var i = 0; i < NumFaces; ++i) + { + FacialFeatureByFace[i].Icons = new uint[NumFeatures]; + for (var j = 0; j < NumFeatures; ++j) { - Equip[i] = new CharaMakeType.CharaMakeTypeUnkData3347Obj() - { - Helmet = parser.ReadColumn( - 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 0), - Top = parser.ReadColumn( - 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 1), - Gloves = parser.ReadColumn( - 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 2), - Legs = parser.ReadColumn( - 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 3), - Shoes = parser.ReadColumn( - 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 4), - Weapon = parser.ReadColumn( - 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 5), - SubWeapon = parser.ReadColumn( - 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 6), - }; + FacialFeatureByFace[i].Icons[j] = + (uint)parser.ReadColumn(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + j * NumFaces + i); } } + + for (var i = 0; i < NumEquip; ++i) + { + Equip[i] = new CharaMakeType.UnkData3347Obj() + { + Helmet = parser.ReadColumn( + 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 0), + Top = parser.ReadColumn( + 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 1), + Gloves = parser.ReadColumn( + 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 2), + Legs = parser.ReadColumn( + 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 3), + Shoes = parser.ReadColumn( + 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 4), + Weapon = parser.ReadColumn( + 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 5), + SubWeapon = parser.ReadColumn( + 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 6), + }; + } } } diff --git a/Glamourer.GameData/Customization/CharacterCustomization.cs b/Glamourer.GameData/Customization/CharacterCustomization.cs index 1cd8ae6..b518538 100644 --- a/Glamourer.GameData/Customization/CharacterCustomization.cs +++ b/Glamourer.GameData/Customization/CharacterCustomization.cs @@ -23,7 +23,7 @@ namespace Glamourer.Customization [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct CharacterCustomization { - public const int CustomizationOffset = 0xDD8; + public const int CustomizationOffset = 0x830; public const int CustomizationBytes = 26; public static CharacterCustomization Default = new() diff --git a/Glamourer.GameData/Customization/CustomizationOptions.cs b/Glamourer.GameData/Customization/CustomizationOptions.cs index c6fbee5..6ee80d3 100644 --- a/Glamourer.GameData/Customization/CustomizationOptions.cs +++ b/Glamourer.GameData/Customization/CustomizationOptions.cs @@ -196,7 +196,7 @@ namespace Glamourer.Customization if (set.Faces.Count > 0) set.SetAvailable(CustomizationId.Face); - var count = race.ToRace() == Race.Hrothgar ? set.HairStyles.Count : set.Faces.Count; + var count = set.Faces.Count; var featureDict = new List>(count); for (var i = 0; i < count; ++i) { diff --git a/Glamourer/CharacterExtensions.cs b/Glamourer/CharacterExtensions.cs index 0dd87dd..4a38366 100644 --- a/Glamourer/CharacterExtensions.cs +++ b/Glamourer/CharacterExtensions.cs @@ -4,14 +4,17 @@ namespace Glamourer; public static class CharacterExtensions { - public const int WetnessOffset = 0x19E4; - public const byte WetnessFlag = 0x08; - public const int StateFlagsOffset = 0xDF6; - public const byte HatHiddenFlag = 0x01; - public const byte VisorToggledFlag = 0x10; - public const int AlphaOffset = 0x18B8; - public const int WeaponHiddenOffset = 0xCD4; - public const byte WeaponHiddenFlag = 0x02; + public const int WetnessOffset = 0x1ADA; + public const byte WetnessFlag = 0x80; + public const int HatVisibleOffset = 0x84E; + public const int VisorToggledOffset = 0x84F; + public const byte HatHiddenFlag = 0x01; + public const byte VisorToggledFlag = 0x08; + public const int AlphaOffset = 0x19E0; + public const int WeaponHiddenOffset1 = 0x84F; + public const int WeaponHiddenOffset2 = 0x72C; // maybe + public const byte WeaponHiddenFlag1 = 0x01; + public const byte WeaponHiddenFlag2 = 0x02; public static unsafe bool IsWet(this Character a) => (*((byte*)a.Address + WetnessOffset) & WetnessFlag) != 0; @@ -29,49 +32,62 @@ public static class CharacterExtensions return true; } - public static unsafe ref byte StateFlags(this Character a) - => ref *((byte*)a.Address + StateFlagsOffset); + public static unsafe bool IsHatVisible(this Character a) + => (*((byte*)a.Address + HatVisibleOffset) & HatHiddenFlag) == 0; - public static bool SetStateFlag(this Character a, bool value, byte flag) + public static unsafe bool SetHatVisible(this Character a, bool visible) { - var current = a.StateFlags(); - var previousValue = (current & flag) != 0; - if (previousValue == value) + var current = IsHatVisible(a); + if (current == visible) return false; - if (value) - a.StateFlags() = (byte)(current | flag); + if (visible) + *((byte*)a.Address + HatVisibleOffset) = (byte)(*((byte*)a.Address + HatVisibleOffset) & ~HatHiddenFlag); else - a.StateFlags() = (byte)(current & ~flag); + *((byte*)a.Address + HatVisibleOffset) = (byte)(*((byte*)a.Address + HatVisibleOffset) | HatHiddenFlag); return true; } - public static bool IsHatHidden(this Character a) - => (a.StateFlags() & HatHiddenFlag) != 0; + public static unsafe bool IsVisorToggled(this Character a) + => (*((byte*)a.Address + VisorToggledOffset) & VisorToggledFlag) == VisorToggledFlag; + + public static unsafe bool SetVisorToggled(this Character a, bool toggled) + { + var current = IsVisorToggled(a); + if (current == toggled) + return false; + + if (toggled) + *((byte*)a.Address + VisorToggledOffset) = (byte)(*((byte*)a.Address + VisorToggledOffset) | VisorToggledFlag); + else + *((byte*)a.Address + VisorToggledOffset) = (byte)(*((byte*)a.Address + VisorToggledOffset) & ~VisorToggledFlag); + return true; + } public static unsafe bool IsWeaponHidden(this Character a) - => (a.StateFlags() & WeaponHiddenFlag) != 0 - && (*((byte*)a.Address + WeaponHiddenOffset) & WeaponHiddenFlag) != 0; - - public static bool IsVisorToggled(this Character a) - => (a.StateFlags() & VisorToggledFlag) != 0; - - public static bool SetHatHidden(this Character a, bool value) - => SetStateFlag(a, value, HatHiddenFlag); + => (*((byte*)a.Address + WeaponHiddenOffset1) & WeaponHiddenFlag1) == WeaponHiddenFlag1 + && (*((byte*)a.Address + WeaponHiddenOffset2) & WeaponHiddenFlag2) == WeaponHiddenFlag2; public static unsafe bool SetWeaponHidden(this Character a, bool value) { - var ret = SetStateFlag(a, value, WeaponHiddenFlag); - var val = *((byte*)a.Address + WeaponHiddenOffset); - if (value) - *((byte*)a.Address + WeaponHiddenOffset) = (byte)(val | WeaponHiddenFlag); - else - *((byte*)a.Address + WeaponHiddenOffset) = (byte)(val & ~WeaponHiddenFlag); - return ret || (val & WeaponHiddenFlag) != 0 != value; - } + var hidden = IsWeaponHidden(a); + if (hidden == value) + return false; - public static bool SetVisorToggled(this Character a, bool value) - => SetStateFlag(a, value, VisorToggledFlag); + var val1 = *((byte*)a.Address + WeaponHiddenOffset1); + var val2 = *((byte*)a.Address + WeaponHiddenOffset2); + if (value) + { + *((byte*)a.Address + WeaponHiddenOffset1) = (byte)(val1 | WeaponHiddenFlag1); + *((byte*)a.Address + WeaponHiddenOffset2) = (byte)(val2 | WeaponHiddenFlag2); + } + else + { + *((byte*)a.Address + WeaponHiddenOffset1) = (byte)(val1 & ~WeaponHiddenFlag1); + *((byte*)a.Address + WeaponHiddenOffset2) = (byte)(val2 & ~WeaponHiddenFlag2); + } + return true; + } public static unsafe ref float Alpha(this Character a) => ref *(float*)((byte*)a.Address + AlphaOffset); diff --git a/Glamourer/CharacterSave.cs b/Glamourer/CharacterSave.cs index 83dec78..eeeca88 100644 --- a/Glamourer/CharacterSave.cs +++ b/Glamourer/CharacterSave.cs @@ -8,394 +8,386 @@ using Newtonsoft.Json.Linq; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -namespace Glamourer +namespace Glamourer; + +public class CharacterSaveConverter : JsonConverter { - public class CharacterSaveConverter : JsonConverter + public override bool CanConvert(Type objectType) + => objectType == typeof(CharacterSave); + + public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { - public override bool CanConvert(Type objectType) - => objectType == typeof(CharacterSave); - - public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - var token = JToken.Load(reader); - var s = token.ToObject(); - return CharacterSave.FromString(s!); - } - - public override bool CanWrite - => true; - - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value != null) - { - var s = ((CharacterSave) value).ToBase64(); - serializer.Serialize(writer, s); - } - } + var token = JToken.Load(reader); + var s = token.ToObject(); + return CharacterSave.FromString(s!); } - [JsonConverter(typeof(CharacterSaveConverter))] - public class CharacterSave + public override bool CanWrite + => true; + + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { - public const byte CurrentVersion = 2; - public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes; - public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 1; - - public const byte TotalSize = TotalSizeVersion2; - - private readonly byte[] _bytes = new byte[TotalSize]; - - public CharacterSave() + if (value != null) { - _bytes[0] = CurrentVersion; - Alpha = 1.0f; - } - - public CharacterSave Copy() - { - var ret = new CharacterSave(); - _bytes.CopyTo((Span) ret._bytes); - return ret; - } - - public byte Version - => _bytes[0]; - - public bool WriteCustomizations - { - get => (_bytes[1] & 0x01) != 0; - set => _bytes[1] = (byte) (value ? _bytes[1] | 0x01 : _bytes[1] & ~0x01); - } - - public bool IsWet - { - get => (_bytes[1] & 0x02) != 0; - set => _bytes[1] = (byte) (value ? _bytes[1] | 0x02 : _bytes[1] & ~0x02); - } - - public bool SetHatState - { - get => (_bytes[1] & 0x04) != 0; - set => _bytes[1] = (byte) (value ? _bytes[1] | 0x04 : _bytes[1] & ~0x04); - } - - public bool SetWeaponState - { - get => (_bytes[1] & 0x08) != 0; - set => _bytes[1] = (byte) (value ? _bytes[1] | 0x08 : _bytes[1] & ~0x08); - } - - public bool SetVisorState - { - get => (_bytes[1] & 0x10) != 0; - set => _bytes[1] = (byte) (value ? _bytes[1] | 0x10 : _bytes[1] & ~0x10); - } - - public bool WriteProtected - { - get => (_bytes[1] & 0x20) != 0; - set => _bytes[1] = (byte) (value ? _bytes[1] | 0x20 : _bytes[1] & ~0x20); - } - - public byte StateFlags - { - get => _bytes[64 + CharacterCustomization.CustomizationBytes]; - set => _bytes[64 + CharacterCustomization.CustomizationBytes] = value; - } - - public bool HatState - { - get => (StateFlags & 0x01) == 0; - set => StateFlags = (byte) (value ? StateFlags & ~0x01 : StateFlags | 0x01); - } - - public bool VisorState - { - get => (StateFlags & 0x10) != 0; - set => StateFlags = (byte) (value ? StateFlags | 0x10 : StateFlags & ~0x10); - } - - public bool WeaponState - { - get => (StateFlags & 0x02) == 0; - set => StateFlags = (byte) (value ? StateFlags & ~0x02 : StateFlags | 0x02); - } - - public CharacterEquipMask WriteEquipment - { - get => (CharacterEquipMask) (_bytes[2] | (_bytes[3] << 8)); - set - { - _bytes[2] = (byte) ((ushort) value & 0xFF); - _bytes[3] = (byte) ((ushort) value >> 8); - } - } - - private static Dictionary Offsets() - { - var stainOffsetWeapon = (int) Marshal.OffsetOf("Stain"); - var stainOffsetEquip = (int) Marshal.OffsetOf("Stain"); - - (int, int, bool) ToOffsets(IntPtr offset, bool weapon) - { - var off = 4 + CharacterCustomization.CustomizationBytes + (int) offset; - return weapon ? (off, off + stainOffsetWeapon, weapon) : (off, off + stainOffsetEquip, weapon); - } - - return new Dictionary(12) - { - [EquipSlot.MainHand] = ToOffsets(Marshal.OffsetOf("MainHand"), true), - [EquipSlot.OffHand] = ToOffsets(Marshal.OffsetOf("OffHand"), true), - [EquipSlot.Head] = ToOffsets(Marshal.OffsetOf("Head"), false), - [EquipSlot.Body] = ToOffsets(Marshal.OffsetOf("Body"), false), - [EquipSlot.Hands] = ToOffsets(Marshal.OffsetOf("Hands"), false), - [EquipSlot.Legs] = ToOffsets(Marshal.OffsetOf("Legs"), false), - [EquipSlot.Feet] = ToOffsets(Marshal.OffsetOf("Feet"), false), - [EquipSlot.Ears] = ToOffsets(Marshal.OffsetOf("Ears"), false), - [EquipSlot.Neck] = ToOffsets(Marshal.OffsetOf("Neck"), false), - [EquipSlot.Wrists] = ToOffsets(Marshal.OffsetOf("Wrists"), false), - [EquipSlot.RFinger] = ToOffsets(Marshal.OffsetOf("RFinger"), false), - [EquipSlot.LFinger] = ToOffsets(Marshal.OffsetOf("LFinger"), false), - }; - } - - private static readonly IReadOnlyDictionary FieldOffsets = Offsets(); - - public bool WriteStain(EquipSlot slot, StainId stainId) - { - if (WriteProtected) - return false; - - var (_, stainOffset, _) = FieldOffsets[slot]; - if (_bytes[stainOffset] == (byte) stainId) - return false; - - _bytes[stainOffset] = stainId.Value; - return true; - } - - private bool WriteItem(int offset, SetId id, WeaponType type, ushort variant, bool weapon) - { - var idBytes = BitConverter.GetBytes(id.Value); - - static bool WriteIfDifferent(ref byte x, byte y) - { - if (x == y) - return false; - - x = y; - return true; - } - - var ret = WriteIfDifferent(ref _bytes[offset], idBytes[0]); - ret |= WriteIfDifferent(ref _bytes[offset + 1], idBytes[1]); - if (weapon) - { - var typeBytes = BitConverter.GetBytes(type.Value); - var variantBytes = BitConverter.GetBytes(variant); - ret |= WriteIfDifferent(ref _bytes[offset + 2], typeBytes[0]); - ret |= WriteIfDifferent(ref _bytes[offset + 3], typeBytes[1]); - ret |= WriteIfDifferent(ref _bytes[offset + 4], variantBytes[0]); - ret |= WriteIfDifferent(ref _bytes[offset + 5], variantBytes[1]); - } - else - { - ret |= WriteIfDifferent(ref _bytes[offset + 2], (byte) variant); - } - - return ret; - } - - public bool WriteItem(Item item) - { - if (WriteProtected) - return false; - - var (itemOffset, _, isWeapon) = FieldOffsets[item.EquippableTo]; - var (id, type, variant) = item.MainModel; - var ret = WriteItem(itemOffset, id, type, variant, isWeapon); - if (item.EquippableTo == EquipSlot.MainHand && item.HasSubModel) - { - var (subOffset, _, _) = FieldOffsets[EquipSlot.OffHand]; - var (subId, subType, subVariant) = item.SubModel; - ret |= WriteItem(subOffset, subId, subType, subVariant, true); - } - - return ret; - } - - public unsafe float Alpha - { - get - { - fixed (byte* ptr = &_bytes[60 + CharacterCustomization.CustomizationBytes]) - { - return *(float*) ptr; - } - } - set - { - fixed (byte* ptr = _bytes) - { - *(ptr + 60 + CharacterCustomization.CustomizationBytes + 0) = *((byte*) &value + 0); - *(ptr + 60 + CharacterCustomization.CustomizationBytes + 1) = *((byte*) &value + 1); - *(ptr + 60 + CharacterCustomization.CustomizationBytes + 2) = *((byte*) &value + 2); - *(ptr + 60 + CharacterCustomization.CustomizationBytes + 3) = *((byte*) &value + 3); - } - } - } - - public void Load(CharacterCustomization customization) - { - WriteCustomizations = true; - customization.WriteBytes(_bytes, 4); - } - - public void Load(CharacterEquipment equipment, CharacterEquipMask mask = CharacterEquipMask.All) - { - WriteEquipment = mask; - equipment.WriteBytes(_bytes, 4 + CharacterCustomization.CustomizationBytes); - } - - public string ToBase64() - => Convert.ToBase64String(_bytes); - - private static void CheckSize(int length, int requiredLength) - { - if (length != requiredLength) - throw new Exception( - $"Can not parse Base64 string into CharacterSave:\n\tInvalid size {length} instead of {requiredLength}."); - } - - private static void CheckRange(int idx, byte value, byte min, byte max) - { - if (value < min || value > max) - throw new Exception( - $"Can not parse Base64 string into CharacterSave:\n\tInvalid value {value} in byte {idx}, should be in [{min},{max}]."); - } - - private static void CheckCharacterMask(byte val1, byte val2) - { - var mask = (CharacterEquipMask) (val1 | (val2 << 8)); - if (mask > CharacterEquipMask.All) - throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid value {mask} in byte 3 and 4."); - } - - public void LoadCharacter(Character a) - { - WriteCustomizations = true; - Load(new CharacterCustomization(a)); - - Load(new CharacterEquipment(a)); - - SetHatState = true; - SetVisorState = true; - SetWeaponState = true; - StateFlags = a.StateFlags(); - - IsWet = a.IsWet(); - Alpha = a.Alpha(); - } - - - public void Apply(Character a) - { - Glamourer.RevertableDesigns.Add(a); - - if (WriteCustomizations) - Customizations.Write(a.Address); - if (WriteEquipment != CharacterEquipMask.None) - Equipment.Write(a.Address, WriteEquipment, WriteEquipment); - a.SetWetness(IsWet); - a.Alpha() = Alpha; - if ((_bytes[1] & 0b11100) == 0b11100) - { - a.StateFlags() = StateFlags; - } - else - { - if (SetHatState) - a.SetHatHidden(HatState); - if (SetVisorState) - a.SetVisorToggled(VisorState); - if (SetWeaponState) - a.SetWeaponHidden(WeaponState); - } - } - - public void ApplyOnlyEquipment(Character a) - { - var oldState = _bytes[1]; - WriteCustomizations = false; - SetHatState = false; - SetVisorState = false; - SetWeaponState = false; - Apply(a); - _bytes[1] = oldState; - } - - public void ApplyOnlyCustomizations(Character a) - { - var oldState = _bytes[1]; - SetHatState = false; - SetVisorState = false; - SetWeaponState = false; - var oldEquip = WriteEquipment; - WriteEquipment = CharacterEquipMask.None; - Apply(a); - _bytes[1] = oldState; - WriteEquipment = oldEquip; - } - - public void Load(string base64) - { - var bytes = Convert.FromBase64String(base64); - switch (bytes[0]) - { - case 1: - CheckSize(bytes.Length, TotalSizeVersion1); - CheckRange(2, bytes[1], 0, 1); - Alpha = 1.0f; - bytes[0] = CurrentVersion; - break; - case 2: - CheckSize(bytes.Length, TotalSizeVersion2); - CheckRange(2, bytes[1], 0, 0x3F); - break; - default: throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid Version {bytes[0]}."); - } - - CheckCharacterMask(bytes[2], bytes[3]); - bytes.CopyTo(_bytes, 0); - } - - public static CharacterSave FromString(string base64) - { - var ret = new CharacterSave(); - ret.Load(base64); - return ret; - } - - public unsafe ref CharacterCustomization Customizations - { - get - { - fixed (byte* ptr = _bytes) - { - return ref *(CharacterCustomization*) (ptr + 4); - } - } - } - - public CharacterEquipment Equipment - { - get - { - var ret = new CharacterEquipment(); - ret.FromBytes(_bytes, 4 + CharacterCustomization.CustomizationBytes); - return ret; - } + var s = ((CharacterSave)value).ToBase64(); + serializer.Serialize(writer, s); + } + } +} + +[JsonConverter(typeof(CharacterSaveConverter))] +public class CharacterSave +{ + public const byte CurrentVersion = 2; + public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes; + public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 1; + + public const byte TotalSize = TotalSizeVersion2; + + private readonly byte[] _bytes = new byte[TotalSize]; + + public CharacterSave() + { + _bytes[0] = CurrentVersion; + Alpha = 1.0f; + } + + public CharacterSave Copy() + { + var ret = new CharacterSave(); + _bytes.CopyTo((Span)ret._bytes); + return ret; + } + + public byte Version + => _bytes[0]; + + public bool WriteCustomizations + { + get => (_bytes[1] & 0x01) != 0; + set => _bytes[1] = (byte)(value ? _bytes[1] | 0x01 : _bytes[1] & ~0x01); + } + + public bool IsWet + { + get => (_bytes[1] & 0x02) != 0; + set => _bytes[1] = (byte)(value ? _bytes[1] | 0x02 : _bytes[1] & ~0x02); + } + + public bool SetHatState + { + get => (_bytes[1] & 0x04) != 0; + set => _bytes[1] = (byte)(value ? _bytes[1] | 0x04 : _bytes[1] & ~0x04); + } + + public bool SetWeaponState + { + get => (_bytes[1] & 0x08) != 0; + set => _bytes[1] = (byte)(value ? _bytes[1] | 0x08 : _bytes[1] & ~0x08); + } + + public bool SetVisorState + { + get => (_bytes[1] & 0x10) != 0; + set => _bytes[1] = (byte)(value ? _bytes[1] | 0x10 : _bytes[1] & ~0x10); + } + + public bool WriteProtected + { + get => (_bytes[1] & 0x20) != 0; + set => _bytes[1] = (byte)(value ? _bytes[1] | 0x20 : _bytes[1] & ~0x20); + } + + public byte StateFlags + { + get => _bytes[64 + CharacterCustomization.CustomizationBytes]; + set => _bytes[64 + CharacterCustomization.CustomizationBytes] = value; + } + + public bool HatState + { + get => (StateFlags & 0x01) == 0; + set => StateFlags = (byte)(value ? StateFlags & ~0x01 : StateFlags | 0x01); + } + + public bool VisorState + { + get => (StateFlags & 0x10) != 0; + set => StateFlags = (byte)(value ? StateFlags | 0x10 : StateFlags & ~0x10); + } + + public bool WeaponState + { + get => (StateFlags & 0x02) == 0; + set => StateFlags = (byte)(value ? StateFlags & ~0x02 : StateFlags | 0x02); + } + + public CharacterEquipMask WriteEquipment + { + get => (CharacterEquipMask)(_bytes[2] | (_bytes[3] << 8)); + set + { + _bytes[2] = (byte)((ushort)value & 0xFF); + _bytes[3] = (byte)((ushort)value >> 8); + } + } + + private static Dictionary Offsets() + { + var stainOffsetWeapon = (int)Marshal.OffsetOf("Stain"); + var stainOffsetEquip = (int)Marshal.OffsetOf("Stain"); + + (int, int, bool) ToOffsets(IntPtr offset, bool weapon) + { + var off = 4 + CharacterCustomization.CustomizationBytes + (int)offset; + return weapon ? (off, off + stainOffsetWeapon, weapon) : (off, off + stainOffsetEquip, weapon); + } + + return new Dictionary(12) + { + [EquipSlot.MainHand] = ToOffsets(Marshal.OffsetOf("MainHand"), true), + [EquipSlot.OffHand] = ToOffsets(Marshal.OffsetOf("OffHand"), true), + [EquipSlot.Head] = ToOffsets(Marshal.OffsetOf("Head"), false), + [EquipSlot.Body] = ToOffsets(Marshal.OffsetOf("Body"), false), + [EquipSlot.Hands] = ToOffsets(Marshal.OffsetOf("Hands"), false), + [EquipSlot.Legs] = ToOffsets(Marshal.OffsetOf("Legs"), false), + [EquipSlot.Feet] = ToOffsets(Marshal.OffsetOf("Feet"), false), + [EquipSlot.Ears] = ToOffsets(Marshal.OffsetOf("Ears"), false), + [EquipSlot.Neck] = ToOffsets(Marshal.OffsetOf("Neck"), false), + [EquipSlot.Wrists] = ToOffsets(Marshal.OffsetOf("Wrists"), false), + [EquipSlot.RFinger] = ToOffsets(Marshal.OffsetOf("RFinger"), false), + [EquipSlot.LFinger] = ToOffsets(Marshal.OffsetOf("LFinger"), false), + }; + } + + private static readonly IReadOnlyDictionary FieldOffsets = Offsets(); + + public bool WriteStain(EquipSlot slot, StainId stainId) + { + if (WriteProtected) + return false; + + var (_, stainOffset, _) = FieldOffsets[slot]; + if (_bytes[stainOffset] == (byte)stainId) + return false; + + _bytes[stainOffset] = stainId.Value; + return true; + } + + private bool WriteItem(int offset, SetId id, WeaponType type, ushort variant, bool weapon) + { + var idBytes = BitConverter.GetBytes(id.Value); + + static bool WriteIfDifferent(ref byte x, byte y) + { + if (x == y) + return false; + + x = y; + return true; + } + + var ret = WriteIfDifferent(ref _bytes[offset], idBytes[0]); + ret |= WriteIfDifferent(ref _bytes[offset + 1], idBytes[1]); + if (weapon) + { + var typeBytes = BitConverter.GetBytes(type.Value); + var variantBytes = BitConverter.GetBytes(variant); + ret |= WriteIfDifferent(ref _bytes[offset + 2], typeBytes[0]); + ret |= WriteIfDifferent(ref _bytes[offset + 3], typeBytes[1]); + ret |= WriteIfDifferent(ref _bytes[offset + 4], variantBytes[0]); + ret |= WriteIfDifferent(ref _bytes[offset + 5], variantBytes[1]); + } + else + { + ret |= WriteIfDifferent(ref _bytes[offset + 2], (byte)variant); + } + + return ret; + } + + public bool WriteItem(Item item) + { + if (WriteProtected) + return false; + + var (itemOffset, _, isWeapon) = FieldOffsets[item.EquippableTo]; + var (id, type, variant) = item.MainModel; + var ret = WriteItem(itemOffset, id, type, variant, isWeapon); + if (item.EquippableTo == EquipSlot.MainHand && item.HasSubModel) + { + var (subOffset, _, _) = FieldOffsets[EquipSlot.OffHand]; + var (subId, subType, subVariant) = item.SubModel; + ret |= WriteItem(subOffset, subId, subType, subVariant, true); + } + + return ret; + } + + public unsafe float Alpha + { + get + { + fixed (byte* ptr = &_bytes[60 + CharacterCustomization.CustomizationBytes]) + { + return *(float*)ptr; + } + } + set + { + fixed (byte* ptr = _bytes) + { + *(ptr + 60 + CharacterCustomization.CustomizationBytes + 0) = *((byte*)&value + 0); + *(ptr + 60 + CharacterCustomization.CustomizationBytes + 1) = *((byte*)&value + 1); + *(ptr + 60 + CharacterCustomization.CustomizationBytes + 2) = *((byte*)&value + 2); + *(ptr + 60 + CharacterCustomization.CustomizationBytes + 3) = *((byte*)&value + 3); + } + } + } + + public void Load(CharacterCustomization customization) + { + WriteCustomizations = true; + customization.WriteBytes(_bytes, 4); + } + + public void Load(CharacterEquipment equipment, CharacterEquipMask mask = CharacterEquipMask.All) + { + WriteEquipment = mask; + equipment.WriteBytes(_bytes, 4 + CharacterCustomization.CustomizationBytes); + } + + public string ToBase64() + => Convert.ToBase64String(_bytes); + + private static void CheckSize(int length, int requiredLength) + { + if (length != requiredLength) + throw new Exception( + $"Can not parse Base64 string into CharacterSave:\n\tInvalid size {length} instead of {requiredLength}."); + } + + private static void CheckRange(int idx, byte value, byte min, byte max) + { + if (value < min || value > max) + throw new Exception( + $"Can not parse Base64 string into CharacterSave:\n\tInvalid value {value} in byte {idx}, should be in [{min},{max}]."); + } + + private static void CheckCharacterMask(byte val1, byte val2) + { + var mask = (CharacterEquipMask)(val1 | (val2 << 8)); + if (mask > CharacterEquipMask.All) + throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid value {mask} in byte 3 and 4."); + } + + public void LoadCharacter(Character a) + { + WriteCustomizations = true; + Load(new CharacterCustomization(a)); + + Load(new CharacterEquipment(a)); + + SetHatState = true; + SetVisorState = true; + SetWeaponState = true; + StateFlags = (byte)((a.IsHatVisible() ? 0x00 : 0x01) | (a.IsVisorToggled() ? 0x10 : 0x00) | (a.IsWeaponHidden() ? 0x02 : 0x00)); + + IsWet = a.IsWet(); + Alpha = a.Alpha(); + } + + + public void Apply(Character a) + { + Glamourer.RevertableDesigns.Add(a); + + if (WriteCustomizations) + Customizations.Write(a.Address); + if (WriteEquipment != CharacterEquipMask.None) + Equipment.Write(a.Address, WriteEquipment, WriteEquipment); + a.SetWetness(IsWet); + a.Alpha() = Alpha; + if (SetHatState) + a.SetHatVisible(!HatState); + if (SetVisorState) + a.SetVisorToggled(VisorState); + if (SetWeaponState) + a.SetWeaponHidden(WeaponState); + } + + public void ApplyOnlyEquipment(Character a) + { + var oldState = _bytes[1]; + WriteCustomizations = false; + SetHatState = false; + SetVisorState = false; + SetWeaponState = false; + Apply(a); + _bytes[1] = oldState; + } + + public void ApplyOnlyCustomizations(Character a) + { + var oldState = _bytes[1]; + SetHatState = false; + SetVisorState = false; + SetWeaponState = false; + var oldEquip = WriteEquipment; + WriteEquipment = CharacterEquipMask.None; + Apply(a); + _bytes[1] = oldState; + WriteEquipment = oldEquip; + } + + public void Load(string base64) + { + var bytes = Convert.FromBase64String(base64); + switch (bytes[0]) + { + case 1: + CheckSize(bytes.Length, TotalSizeVersion1); + CheckRange(2, bytes[1], 0, 1); + Alpha = 1.0f; + bytes[0] = CurrentVersion; + break; + case 2: + CheckSize(bytes.Length, TotalSizeVersion2); + CheckRange(2, bytes[1], 0, 0x3F); + break; + default: throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid Version {bytes[0]}."); + } + + CheckCharacterMask(bytes[2], bytes[3]); + bytes.CopyTo(_bytes, 0); + } + + public static CharacterSave FromString(string base64) + { + var ret = new CharacterSave(); + ret.Load(base64); + return ret; + } + + public unsafe ref CharacterCustomization Customizations + { + get + { + fixed (byte* ptr = _bytes) + { + return ref *(CharacterCustomization*)(ptr + 4); + } + } + } + + public CharacterEquipment Equipment + { + get + { + var ret = new CharacterEquipment(); + ret.FromBytes(_bytes, 4 + CharacterCustomization.CustomizationBytes); + return ret; } } } diff --git a/Glamourer/Gui/InterfaceCustomization.cs b/Glamourer/Gui/InterfaceCustomization.cs index 02a14c2..a797bdf 100644 --- a/Glamourer/Gui/InterfaceCustomization.cs +++ b/Glamourer/Gui/InterfaceCustomization.cs @@ -140,7 +140,7 @@ namespace Glamourer.Gui ImGui.SameLine(); if (InputInt($"##text_{id}", ref current, 1, count)) { - customization[id] = set.Data(id, current).Value; + customization[id] = (byte) current; ret = true; } diff --git a/Glamourer/Gui/InterfaceDesigns.cs b/Glamourer/Gui/InterfaceDesigns.cs index 371fd4b..c317b31 100644 --- a/Glamourer/Gui/InterfaceDesigns.cs +++ b/Glamourer/Gui/InterfaceDesigns.cs @@ -277,8 +277,9 @@ namespace Glamourer.Gui var label = $"##fsPopup{child.FullName()}"; if (ImGui.BeginPopup(label)) { - if (ImGui.MenuItem("Delete")) + if (ImGui.MenuItem("Delete") && ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyShift) _designs.DeleteAllChildren(child, false); + ImGuiCustom.HoverTooltip("Hold Control and Shift to delete."); RenameChildInput(child); diff --git a/Glamourer/Gui/InterfaceMiscellaneous.cs b/Glamourer/Gui/InterfaceMiscellaneous.cs index c589139..3867dbf 100644 --- a/Glamourer/Gui/InterfaceMiscellaneous.cs +++ b/Glamourer/Gui/InterfaceMiscellaneous.cs @@ -38,7 +38,7 @@ namespace Glamourer.Gui ret |= DrawCheckMark("Hat Visible", save.HatState, v => { save.HatState = v; - player?.SetHatHidden(!v); + player?.SetHatVisible(v); }); ret |= DrawCheckMark("Weapon Visible", save.WeaponState, v =>