Fix some NPC item stuff and add new version of design base64, backward and forward compatible.

This commit is contained in:
Ottermandias 2023-07-25 17:23:14 +02:00
parent 795e3b6845
commit a40b710d25
7 changed files with 162 additions and 80 deletions

View file

@ -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;

View file

@ -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<byte>(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<byte>(data, Base64SizeV4));
}
}

View file

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

View file

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

View file

@ -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<byte> 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);

View file

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

@ -1 +1 @@
Subproject commit eeb55916372432aa0b5936cc8648657fd4b3447a
Subproject commit 98bd4e9946ded20cb5d54182883e73f344fe2d26