mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-15 21:24:18 +01:00
208 lines
9.2 KiB
C#
208 lines
9.2 KiB
C#
using System;
|
|
using Glamourer.Customization;
|
|
using Glamourer.Services;
|
|
using Glamourer.Structs;
|
|
using OtterGui;
|
|
using Penumbra.GameData.DataContainers;
|
|
using Penumbra.GameData.Enums;
|
|
using Penumbra.GameData.Structs;
|
|
|
|
namespace Glamourer.Designs;
|
|
|
|
public class DesignBase64Migration
|
|
{
|
|
public const int Base64SizeV1 = 86;
|
|
public const int Base64SizeV2 = 91;
|
|
public const int Base64SizeV4 = 95;
|
|
|
|
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)
|
|
{
|
|
if (length != requiredLength)
|
|
throw new Exception(
|
|
$"Can not parse Base64 string into CharacterSave:\n\tInvalid size {length} instead of {requiredLength}.");
|
|
}
|
|
|
|
byte applicationFlags;
|
|
ushort equipFlagsS;
|
|
var bytes = Convert.FromBase64String(base64);
|
|
applyHat = false;
|
|
applyVisor = false;
|
|
applyWeapon = false;
|
|
var data = new DesignData();
|
|
switch (bytes[0])
|
|
{
|
|
case 1:
|
|
{
|
|
CheckSize(bytes.Length, Base64SizeV1);
|
|
applicationFlags = bytes[1];
|
|
equipFlagsS = BitConverter.ToUInt16(bytes, 2);
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
CheckSize(bytes.Length, Base64SizeV2);
|
|
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);
|
|
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]}.");
|
|
}
|
|
|
|
customizeFlags = (applicationFlags & 0x01) != 0 ? CustomizeFlagExtensions.All : 0;
|
|
data.SetIsWet((applicationFlags & 0x02) != 0);
|
|
applyHat = (applicationFlags & 0x04) != 0;
|
|
applyWeapon = (applicationFlags & 0x08) != 0;
|
|
applyVisor = (applicationFlags & 0x10) != 0;
|
|
writeProtected = (applicationFlags & 0x20) != 0;
|
|
|
|
equipFlags = 0;
|
|
equipFlags |= (equipFlagsS & 0x0001) != 0 ? EquipFlag.Mainhand | EquipFlag.MainhandStain : 0;
|
|
equipFlags |= (equipFlagsS & 0x0002) != 0 ? EquipFlag.Offhand | EquipFlag.OffhandStain : 0;
|
|
var flag = 0x0002u;
|
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
|
{
|
|
flag <<= 1;
|
|
equipFlags |= (equipFlagsS & flag) != 0 ? slot.ToFlag() | slot.ToStainFlag() : 0;
|
|
}
|
|
|
|
fixed (byte* ptr = bytes)
|
|
{
|
|
var cur = (CharacterWeapon*)(ptr + 30);
|
|
var eq = (CharacterArmor*)(cur + 2);
|
|
|
|
if (!humans.IsHuman(data.ModelId))
|
|
{
|
|
data.LoadNonHuman(data.ModelId, *(Customize*)(ptr + 4), (nint)eq);
|
|
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)
|
|
{
|
|
Glamourer.Log.Warning("Base64 string invalid, item could not be identified.");
|
|
item = ItemManager.NothingItem(slot);
|
|
}
|
|
|
|
data.SetItem(slot, item);
|
|
data.SetStain(slot, mdl.Stain);
|
|
}
|
|
|
|
var main = cur[0].Skeleton.Id == 0
|
|
? items.DefaultSword
|
|
: items.Identify(EquipSlot.MainHand, cur[0].Skeleton, cur[0].Weapon, cur[0].Variant);
|
|
if (!main.Valid)
|
|
{
|
|
Glamourer.Log.Warning("Base64 string invalid, weapon could not be identified.");
|
|
main = items.DefaultSword;
|
|
}
|
|
|
|
data.SetItem(EquipSlot.MainHand, main);
|
|
data.SetStain(EquipSlot.MainHand, cur[0].Stain);
|
|
|
|
EquipItem off;
|
|
// Fist weapon hack
|
|
if (main.PrimaryId.Id is > 1600 and < 1651 && cur[1].Variant == 0)
|
|
{
|
|
off = items.Identify(EquipSlot.OffHand, (PrimaryId)(main.PrimaryId.Id + 50), main.SecondaryId, main.Variant, main.Type);
|
|
var gauntlet = items.Identify(EquipSlot.Hands, cur[1].Skeleton, (Variant)cur[1].Weapon.Id);
|
|
if (gauntlet.Valid)
|
|
{
|
|
data.SetItem(EquipSlot.Hands, gauntlet);
|
|
data.SetStain(EquipSlot.Hands, cur[0].Stain);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
off = cur[0].Skeleton.Id == 0
|
|
? ItemManager.NothingItem(FullEquipType.Shield)
|
|
: items.Identify(EquipSlot.OffHand, cur[1].Skeleton, cur[1].Weapon, cur[1].Variant, main.Type);
|
|
}
|
|
|
|
if (main.Type.ValidOffhand() != FullEquipType.Unknown && !off.Valid)
|
|
{
|
|
Glamourer.Log.Warning("Base64 string invalid, weapon could not be identified.");
|
|
off = items.GetDefaultOffhand(main);
|
|
}
|
|
|
|
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[Base64SizeV4];
|
|
data[0] = 5;
|
|
data[1] = (byte)((customizeFlags == CustomizeFlagExtensions.All ? 0x01 : 0)
|
|
| (save.IsWet() ? 0x02 : 0)
|
|
| (setHat ? 0x04 : 0)
|
|
| (setWeapon ? 0x08 : 0)
|
|
| (setVisor ? 0x10 : 0)
|
|
| (writeProtected ? 0x20 : 0));
|
|
data[2] = (byte)((equipFlags.HasFlag(EquipFlag.Mainhand) ? 0x01 : 0)
|
|
| (equipFlags.HasFlag(EquipFlag.Offhand) ? 0x02 : 0)
|
|
| (equipFlags.HasFlag(EquipFlag.Head) ? 0x04 : 0)
|
|
| (equipFlags.HasFlag(EquipFlag.Body) ? 0x08 : 0)
|
|
| (equipFlags.HasFlag(EquipFlag.Hands) ? 0x10 : 0)
|
|
| (equipFlags.HasFlag(EquipFlag.Legs) ? 0x20 : 0)
|
|
| (equipFlags.HasFlag(EquipFlag.Feet) ? 0x40 : 0)
|
|
| (equipFlags.HasFlag(EquipFlag.Ears) ? 0x80 : 0));
|
|
data[3] = (byte)((equipFlags.HasFlag(EquipFlag.Neck) ? 0x01 : 0)
|
|
| (equipFlags.HasFlag(EquipFlag.Wrist) ? 0x02 : 0)
|
|
| (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0)
|
|
| (equipFlags.HasFlag(EquipFlag.LFinger) ? 0x08 : 0));
|
|
save.Customize.Write((nint)data + 4);
|
|
((CharacterWeapon*)(data + 30))[0] = save.Item(EquipSlot.MainHand).Weapon(save.Stain(EquipSlot.MainHand));
|
|
((CharacterWeapon*)(data + 30))[1] = save.Item(EquipSlot.OffHand).Weapon(save.Stain(EquipSlot.OffHand));
|
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
|
((CharacterArmor*)(data + 44))[slot.ToIndex()] = save.Item(slot).Armor(save.Stain(slot));
|
|
*(ushort*)(data + 84) = 1; // IsSet.
|
|
*(float*)(data + 86) = 1f;
|
|
data[90] = (byte)((save.IsHatVisible() ? 0x00 : 0x01)
|
|
| (save.IsVisorToggled() ? 0x10 : 0)
|
|
| (save.IsWeaponVisible() ? 0x00 : 0x02));
|
|
|
|
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));
|
|
}
|
|
}
|