From a40b710d254d6de5411545b1fab77600969f2360 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 25 Jul 2023 17:23:14 +0200 Subject: [PATCH] Fix some NPC item stuff and add new version of design base64, backward and forward compatible. --- Glamourer/Designs/DesignBase.cs | 5 +- Glamourer/Designs/DesignBase64Migration.cs | 159 +++++++++++++-------- Glamourer/Designs/DesignConverter.cs | 49 +++++-- Glamourer/Designs/DesignManager.cs | 7 +- Glamourer/Gui/Tabs/DebugTab.cs | 14 +- Glamourer/Services/ItemManager.cs | 6 +- Penumbra.GameData | 2 +- 7 files changed, 162 insertions(+), 80 deletions(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index cdf488b..73546ab 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -6,6 +6,7 @@ using Glamourer.Services; using Glamourer.Structs; using Newtonsoft.Json.Linq; using OtterGui.Classes; +using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -398,11 +399,11 @@ public class DesignBase } } - public void MigrateBase64(CustomizationService customizations, ItemManager items, string base64) + public void MigrateBase64(ItemManager items, HumanModelList humans, string base64) { try { - DesignData = DesignBase64Migration.MigrateBase64(items, base64, out var equipFlags, out var customizeFlags, + DesignData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags, out var writeProtected, out var applyHat, out var applyVisor, out var applyWeapon); ApplyEquip = equipFlags; diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index 85e42f6..852c7a6 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -3,16 +3,20 @@ using Glamourer.Customization; using Glamourer.Services; using Glamourer.Structs; using OtterGui; +using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.Designs; -public static class DesignBase64Migration +public class DesignBase64Migration { - public const int Base64Size = 91; + public const int Base64SizeV1 = 86; + public const int Base64SizeV2 = 91; + public const int Base64SizeV4 = 95; - public static DesignData MigrateBase64(ItemManager items, string base64, out EquipFlag equipFlags, out CustomizeFlag customizeFlags, + public static unsafe DesignData MigrateBase64(ItemManager items, HumanModelList humans, string base64, out EquipFlag equipFlags, + out CustomizeFlag customizeFlags, out bool writeProtected, out bool applyHat, out bool applyVisor, out bool applyWeapon) { static void CheckSize(int length, int requiredLength) @@ -25,22 +29,22 @@ public static class DesignBase64Migration byte applicationFlags; ushort equipFlagsS; var bytes = Convert.FromBase64String(base64); - applyHat = false; - applyVisor = false; - applyWeapon = false; + applyHat = false; + applyVisor = false; + applyWeapon = false; var data = new DesignData(); switch (bytes[0]) { case 1: { - CheckSize(bytes.Length, 86); + CheckSize(bytes.Length, Base64SizeV1); applicationFlags = bytes[1]; equipFlagsS = BitConverter.ToUInt16(bytes, 2); break; } case 2: { - CheckSize(bytes.Length, Base64Size); + CheckSize(bytes.Length, Base64SizeV2); applicationFlags = bytes[1]; equipFlagsS = BitConverter.ToUInt16(bytes, 2); data.SetHatVisible((bytes[90] & 0x01) == 0); @@ -48,6 +52,30 @@ public static class DesignBase64Migration data.SetWeaponVisible((bytes[90] & 0x02) == 0); break; } + case 3: // does not exist as old base64. + throw new Exception( + $"Can not parse Base64 string into design for migration:\n\tInvalid Version {bytes[0]} can not be migrated."); + case 4: + { + CheckSize(bytes.Length, Base64SizeV4); // contains model id + applicationFlags = bytes[1]; + equipFlagsS = BitConverter.ToUInt16(bytes, 2); + data.SetHatVisible((bytes[90] & 0x01) == 0); + data.SetVisor((bytes[90] & 0x10) != 0); + data.SetWeaponVisible((bytes[90] & 0x02) == 0); + data.ModelId = (uint)bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24); + break; + } + case 5: + bytes = bytes[..Base64SizeV4]; + CheckSize(bytes.Length, Base64SizeV4); // contains model id + applicationFlags = bytes[1]; + equipFlagsS = BitConverter.ToUInt16(bytes, 2); + data.SetHatVisible((bytes[90] & 0x01) == 0); + data.SetVisor((bytes[90] & 0x10) != 0); + data.SetWeaponVisible((bytes[90] & 0x02) == 0); + data.ModelId = (uint)bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24); + break; default: throw new Exception($"Can not parse Base64 string into design for migration:\n\tInvalid Version {bytes[0]}."); } @@ -68,67 +96,71 @@ public static class DesignBase64Migration equipFlags |= (equipFlagsS & flag) != 0 ? slot.ToFlag() | slot.ToStainFlag() : 0; } - unsafe + fixed (byte* ptr = bytes) { - fixed (byte* ptr = bytes) + var cur = (CharacterWeapon*)(ptr + 30); + var eq = (CharacterArmor*)(cur + 2); + + if (!humans.IsHuman(data.ModelId)) { - data.Customize.Load(*(Customize*)(ptr + 4)); - var cur = (CharacterWeapon*)(ptr + 30); - - var eq = (CharacterArmor*)(cur + 2); - foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) - { - var mdl = eq[idx]; - var item = items.Identify(slot, mdl.Set, mdl.Variant); - if (!item.Valid) - throw new Exception($"Base64 string invalid, item could not be identified."); - - data.SetItem(slot, item); - data.SetStain(slot, mdl.Stain); - } - - var main = cur[0].Set.Value == 0 ? items.DefaultSword : items.Identify(EquipSlot.MainHand, cur[0].Set, cur[0].Type, (byte)cur[0].Variant); - if (!main.Valid) - throw new Exception($"Base64 string invalid, weapon could not be identified."); - - data.SetItem(EquipSlot.MainHand, main); - data.SetStain(EquipSlot.MainHand, cur[0].Stain); - - EquipItem off; - // Fist weapon hack - if (main.ModelId.Value is > 1600 and < 1651 && cur[1].Variant == 0) - { - off = items.Identify(EquipSlot.OffHand, (SetId)(main.ModelId.Value + 50), main.WeaponType, main.Variant, main.Type); - var gauntlet = items.Identify(EquipSlot.Hands, cur[1].Set, (byte)cur[1].Type); - if (!gauntlet.Valid) - throw new Exception($"Base64 string invalid, item could not be identified."); - - data.SetItem(EquipSlot.Hands, gauntlet); - data.SetStain(EquipSlot.Hands, cur[0].Stain); - } - else - { - off = cur[0].Set.Value == 0 - ? ItemManager.NothingItem(FullEquipType.Shield) - : items.Identify(EquipSlot.OffHand, cur[1].Set, cur[1].Type, (byte)cur[1].Variant, main.Type); - } - - if (main.Type.ValidOffhand() != FullEquipType.Unknown && !off.Valid) - throw new Exception($"Base64 string invalid, weapon could not be identified."); - - data.SetItem(EquipSlot.OffHand, off); - data.SetStain(EquipSlot.OffHand, cur[1].Stain); + data.LoadNonHuman(data.ModelId, *(Customize*)(ptr + 4), (nint)eq); + return data; } - } - return data; + data.Customize.Load(*(Customize*)(ptr + 4)); + foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) + { + var mdl = eq[idx]; + var item = items.Identify(slot, mdl.Set, mdl.Variant); + if (!item.Valid) + throw new Exception($"Base64 string invalid, item could not be identified."); + + data.SetItem(slot, item); + data.SetStain(slot, mdl.Stain); + } + + var main = cur[0].Set.Value == 0 + ? items.DefaultSword + : items.Identify(EquipSlot.MainHand, cur[0].Set, cur[0].Type, (byte)cur[0].Variant); + if (!main.Valid) + throw new Exception($"Base64 string invalid, weapon could not be identified."); + + data.SetItem(EquipSlot.MainHand, main); + data.SetStain(EquipSlot.MainHand, cur[0].Stain); + + EquipItem off; + // Fist weapon hack + if (main.ModelId.Value is > 1600 and < 1651 && cur[1].Variant == 0) + { + off = items.Identify(EquipSlot.OffHand, (SetId)(main.ModelId.Value + 50), main.WeaponType, main.Variant, main.Type); + var gauntlet = items.Identify(EquipSlot.Hands, cur[1].Set, (byte)cur[1].Type); + if (!gauntlet.Valid) + throw new Exception($"Base64 string invalid, item could not be identified."); + + data.SetItem(EquipSlot.Hands, gauntlet); + data.SetStain(EquipSlot.Hands, cur[0].Stain); + } + else + { + off = cur[0].Set.Value == 0 + ? ItemManager.NothingItem(FullEquipType.Shield) + : items.Identify(EquipSlot.OffHand, cur[1].Set, cur[1].Type, (byte)cur[1].Variant, main.Type); + } + + if (main.Type.ValidOffhand() != FullEquipType.Unknown && !off.Valid) + throw new Exception($"Base64 string invalid, weapon could not be identified."); + + data.SetItem(EquipSlot.OffHand, off); + data.SetStain(EquipSlot.OffHand, cur[1].Stain); + return data; + } } public static unsafe string CreateOldBase64(in DesignData save, EquipFlag equipFlags, CustomizeFlag customizeFlags, bool setHat, bool setVisor, bool setWeapon, bool writeProtected, float alpha = 1.0f) { - var data = stackalloc byte[Base64Size]; - data[0] = 2; + var data = stackalloc byte[Base64SizeV4]; + data[0] = 5; data[1] = (byte)((customizeFlags == CustomizeFlagExtensions.All ? 0x01 : 0) | (save.IsWet() ? 0x02 : 0) | (setHat ? 0x04 : 0) @@ -158,6 +190,11 @@ public static class DesignBase64Migration | (save.IsVisorToggled() ? 0x10 : 0) | (save.IsWeaponVisible() ? 0x00 : 0x02)); - return Convert.ToBase64String(new Span(data, Base64Size)); + data[91] = (byte)save.ModelId; + data[92] = (byte)(save.ModelId >> 8); + data[93] = (byte)(save.ModelId >> 16); + data[94] = (byte)(save.ModelId >> 24); + + return Convert.ToBase64String(new Span(data, Base64SizeV4)); } } diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index df2a201..98a0d3d 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -8,23 +8,26 @@ using Glamourer.Structs; using Glamourer.Utility; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Penumbra.GameData.Data; using Penumbra.GameData.Enums; namespace Glamourer.Designs; public class DesignConverter { - public const byte Version = 3; + public const byte Version = 5; private readonly ItemManager _items; private readonly DesignManager _designs; private readonly CustomizationService _customize; + private readonly HumanModelList _humans; - public DesignConverter(ItemManager items, DesignManager designs, CustomizationService customize) + public DesignConverter(ItemManager items, DesignManager designs, CustomizationService customize, HumanModelList humans) { _items = items; _designs = designs; _customize = customize; + _humans = humans; } public JObject ShareJObject(DesignBase design) @@ -40,13 +43,16 @@ public class DesignConverter } public string ShareBase64(Design design) - => ShareBase64(ShareJObject(design)); + => ShareBackwardCompatible(ShareJObject(design), design); public string ShareBase64(DesignBase design) - => ShareBase64(ShareJObject(design)); + => ShareBackwardCompatible(ShareJObject(design), design); public string ShareBase64(ActorState state) - => ShareBase64(ShareJObject(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All)); + { + var design = Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All); + return ShareBackwardCompatible(ShareJObject(design), design); + } public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags) { @@ -79,17 +85,31 @@ public class DesignConverter break; case 1: case 2: + case 4: ret = _designs.CreateTemporary(); - ret.MigrateBase64(_customize, _items, base64); + ret.MigrateBase64(_items, _humans, base64); break; - case Version: + case 3: + { version = bytes.DecompressToString(out var decompressed); - var jObj2 = JObject.Parse(decompressed); + var jObj2 = JObject.Parse(decompressed); + Debug.Assert(version == 3); + ret = jObj2["Identifier"] != null + ? Design.LoadDesign(_customize, _items, jObj2) + : DesignBase.LoadDesignBase(_customize, _items, jObj2); + break; + } + case Version: + { + bytes = bytes[DesignBase64Migration.Base64SizeV4..]; + version = bytes.DecompressToString(out var decompressed); + var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == Version); ret = jObj2["Identifier"] != null ? Design.LoadDesign(_customize, _items, jObj2) : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; + } default: throw new Exception($"Unknown Version {bytes[0]}."); } } @@ -122,4 +142,17 @@ public class DesignConverter var compressed = json.Compress(Version); return System.Convert.ToBase64String(compressed); } + + private static string ShareBackwardCompatible(JObject jObject, DesignBase design) + { + var oldBase64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomize, + design.DoApplyHatVisible(), design.DoApplyVisorToggle(), design.DoApplyWeaponVisible(), design.WriteProtected(), 1f); + var oldBytes = System.Convert.FromBase64String(oldBase64); + var json = jObject.ToString(Formatting.None); + var compressed = json.Compress(Version); + var bytes = new byte[oldBytes.Length + compressed.Length]; + oldBytes.CopyTo(bytes, 0); + compressed.CopyTo(bytes, oldBytes.Length); + return System.Convert.ToBase64String(bytes); + } } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 9274f42..61ba08a 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -11,6 +11,7 @@ using Glamourer.State; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; +using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -20,6 +21,7 @@ public class DesignManager { private readonly CustomizationService _customizations; private readonly ItemManager _items; + private readonly HumanModelList _humans; private readonly SaveService _saveService; private readonly DesignChanged _event; private readonly List _designs = new(); @@ -28,12 +30,13 @@ public class DesignManager => _designs; public DesignManager(SaveService saveService, ItemManager items, CustomizationService customizations, - DesignChanged @event) + DesignChanged @event, HumanModelList humans) { _saveService = saveService; _items = items; _customizations = customizations; _event = @event; + _humans = humans; CreateDesignFolder(saveService); LoadDesigns(); MigrateOldDesigns(); @@ -519,7 +522,7 @@ public class DesignManager Identifier = CreateNewGuid(), Name = actualName, }; - design.MigrateBase64(_customizations, _items, base64); + design.MigrateBase64(_items, _humans, base64); if (!_designs.Any(d => d.Name == design.Name && d.CreationDate == design.CreationDate)) { Add(design, $"Migrated old design to {design.Identifier}."); diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs index f4b966c..fba97a2 100644 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab.cs @@ -29,6 +29,7 @@ using OtterGui; using OtterGui.Raii; using OtterGui.Widgets; using Penumbra.Api.Enums; +using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -62,6 +63,7 @@ public unsafe class DebugTab : ITab private readonly DesignFileSystem _designFileSystem; private readonly AutoDesignManager _autoDesignManager; private readonly DesignConverter _designConverter; + private readonly HumanModelList _humans; private readonly PenumbraChangedItemTooltip _penumbraTooltip; @@ -78,7 +80,8 @@ public unsafe class DebugTab : ITab DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config, PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface, AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks, - ItemUnlockManager itemUnlocks, DesignConverter designConverter, DatFileService datFileService, InventoryService inventoryService) + ItemUnlockManager itemUnlocks, DesignConverter designConverter, DatFileService datFileService, InventoryService inventoryService, + HumanModelList humans) { _changeCustomizeService = changeCustomizeService; _visorService = visorService; @@ -106,6 +109,7 @@ public unsafe class DebugTab : ITab _designConverter = designConverter; _datFileService = datFileService; _inventoryService = inventoryService; + _humans = humans; } public ReadOnlySpan Label @@ -639,7 +643,7 @@ public unsafe class DebugTab : ITab var identified = _items.Identify(slot, (SetId)_setId, (byte)_variant); Text(identified.Name); ImGuiUtil.HoverTooltip(string.Join("\n", - _items.IdentifierService.AwaitedService.Identify((SetId)_setId, (ushort)_variant, slot).Select(i => i.Name))); + _items.IdentifierService.AwaitedService.Identify((SetId)_setId, (ushort)_variant, slot).Select(i => $"{i.Name} {i.Id} {i.ItemId} {i.IconId}"))); } var weapon = _items.Identify(EquipSlot.MainHand, (SetId)_setId, (WeaponType)_secondaryId, (byte)_variant); @@ -939,7 +943,8 @@ public unsafe class DebugTab : ITab if (_parse64Failure == null) try { - _parse64 = DesignBase64Migration.MigrateBase64(_items, _base64, out var ef, out var cf, out var wp, out var ah, out var av, + _parse64 = DesignBase64Migration.MigrateBase64(_items, _humans, _base64, out var ef, out var cf, out var wp, out var ah, + out var av, out var aw); _restore = DesignBase64Migration.CreateOldBase64(in _parse64, ef, cf, ah, av, aw, wp); _restoreBytes = Convert.FromBase64String(_restore); @@ -1035,6 +1040,9 @@ public unsafe class DebugTab : ITab { _clipboardText = ImGui.GetClipboardText(); _clipboardData = Convert.FromBase64String(_clipboardText); + _version = _clipboardData[0]; + if (_version == 5) + _clipboardData = _clipboardData[DesignBase64Migration.Base64SizeV4..]; _version = _clipboardData.Decompress(out _dataUncompressed); _textUncompressed = Encoding.UTF8.GetString(_dataUncompressed); _json = JObject.Parse(_textUncompressed); diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 0f8c47d..f691c35 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -75,7 +75,7 @@ public class ItemManager : IDisposable return SmallClothesItem(slot); if (!ItemService.AwaitedService.TryGetValue(itemId, slot, out var item)) - return new EquipItem(string.Intern($"Unknown #{itemId}"), itemId, 0, 0, 0, 0, 0); + return EquipItem.FromId(itemId); if (item.Type.ToSlot() != slot) return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.ModelId, item.WeaponType, item.Variant, 0); @@ -89,7 +89,7 @@ public class ItemManager : IDisposable return NothingItem(type); if (!ItemService.AwaitedService.TryGetValue(itemId, type is FullEquipType.Shield ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) - return new EquipItem(string.Intern($"Unknown #{itemId}"), itemId, 0, 0, 0, 0, 0); + return EquipItem.FromId(itemId); if (item.Type != type) return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.ModelId, item.WeaponType, item.Variant, 0); @@ -111,7 +111,7 @@ public class ItemManager : IDisposable var item = IdentifierService.AwaitedService.Identify(id, variant, slot).FirstOrDefault(); return item.Valid ? item - : new EquipItem($"Unknown ({id.Value}-{variant})", 0, 0, id, 0, variant, slot.ToEquipType()); + : EquipItem.FromIds(0, 0, id, 0, variant, slot.ToEquipType()); } } diff --git a/Penumbra.GameData b/Penumbra.GameData index eeb5591..98bd4e9 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit eeb55916372432aa0b5936cc8648657fd4b3447a +Subproject commit 98bd4e9946ded20cb5d54182883e73f344fe2d26