diff --git a/Glamourer.GameData/Customization/ActorCustomization.cs b/Glamourer.GameData/Customization/ActorCustomization.cs index 7408e72..8d54bda 100644 --- a/Glamourer.GameData/Customization/ActorCustomization.cs +++ b/Glamourer.GameData/Customization/ActorCustomization.cs @@ -250,14 +250,18 @@ namespace Glamourer.Customization } } - public unsafe byte[] ToBytes() + public unsafe void WriteBytes(byte[] array, int offset = 0) { - var ret = new byte[CustomizationBytes]; fixed (byte* ptr = &_race) { - Marshal.Copy(new IntPtr(ptr), ret, 0, CustomizationBytes); + Marshal.Copy(new IntPtr(ptr), array, offset, CustomizationBytes); } + } + public byte[] ToBytes() + { + var ret = new byte[CustomizationBytes]; + WriteBytes(ret); return ret; } } diff --git a/Glamourer/Gui/Interface.cs b/Glamourer/Gui/Interface.cs index a4d461c..848e906 100644 --- a/Glamourer/Gui/Interface.cs +++ b/Glamourer/Gui/Interface.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Reflection; +using System.Windows.Forms; using Dalamud.Game.ClientState.Actors; using Dalamud.Game.ClientState.Actors.Types; using Dalamud.Interface; @@ -735,9 +736,8 @@ namespace Glamourer.Gui private static readonly CustomizationId[] AllCustomizations = (CustomizationId[]) Enum.GetValues(typeof(CustomizationId)); - private bool DrawStuff() + private bool DrawStuff(LazyCustomization x) { - var x = new LazyCustomization(_player!.Address); _currentSubRace = x.Value.Clan; _currentGender = x.Value.Gender; var ret = DrawGenderSelector(x); @@ -885,8 +885,37 @@ namespace Glamourer.Gui changes |= DrawEquip(EquipSlot.LFinger, equip.LFinger); } + var x = new LazyCustomization(_player!.Address); if (ImGui.CollapsingHeader("Character Customization")) - changes |= DrawStuff(); + changes |= DrawStuff(x); + + if (ImGui.Button("Copy to Clipboard")) + { + var save = new CharacterSave(); + save.Load(x.Value); + save.Load(equip); + Clipboard.SetText(save.ToBase64()); + } + + ImGui.SameLine(); + if (ImGui.Button("Apply from Clipboard")) + { + var text = Clipboard.GetText(); + if (text.Any()) + { + try + { + var save = CharacterSave.FromString(text); + save.Customizations.Write(_player.Address); + save.Equipment.Write(_player.Address); + changes = true; + } + catch (Exception e) + { + PluginLog.Information($"{e}"); + } + } + } if (changes) UpdateActors(_player); diff --git a/Glamourer/Main.cs b/Glamourer/Main.cs index de7ca13..ad4e953 100644 --- a/Glamourer/Main.cs +++ b/Glamourer/Main.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; using Dalamud.Game.Command; using Dalamud.Plugin; using Glamourer.Customization; @@ -15,12 +17,103 @@ namespace Glamourer { public class CharacterSave { - public string Name { get; set; } = string.Empty; - public ActorCustomization Customizations { get; private set; } - public ActorEquipment Equipment { get; private set; } = null!; + public const byte CurrentVersion = 1; + public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + ActorCustomization.CustomizationBytes; - public ActorEquipMask WriteEquipment { get; set; } = ActorEquipMask.None; - public bool WriteCustomizations { get; set; } = false; + public const byte TotalSize = TotalSizeVersion1; + + private readonly byte[] _bytes = new byte[TotalSize]; + + public CharacterSave() + => _bytes[0] = CurrentVersion; + + public byte Version + => _bytes[0]; + + public bool WriteCustomizations + { + get => _bytes[1] != 0; + set => _bytes[1] = (byte) (value ? 1 : 0); + } + + public ActorEquipMask WriteEquipment + { + get => (ActorEquipMask) ((ushort) _bytes[2] | ((ushort) _bytes[3] << 8)); + set + { + _bytes[2] = (byte) (((ushort) value) & 0xFF); + _bytes[3] = (byte) (((ushort) value) >> 8); + } + } + + public void Load(ActorCustomization customization) + { + WriteCustomizations = true; + customization.WriteBytes(_bytes, 4); + } + + public void Load(ActorEquipment equipment, ActorEquipMask mask = ActorEquipMask.All) + { + WriteEquipment = mask; + equipment.WriteBytes(_bytes, 4 + ActorCustomization.CustomizationBytes); + } + + public string ToBase64() + => System.Convert.ToBase64String(_bytes); + + public void Load(string base64) + { + var bytes = System.Convert.FromBase64String(base64); + switch (bytes[0]) + { + case 1: + if (bytes.Length != TotalSizeVersion1) + throw new Exception( + $"Can not parse Base64 string into CharacterSave:\n\tInvalid size {bytes.Length} instead of {TotalSizeVersion1}."); + if (bytes[1] != 0 && bytes[1] != 1) + throw new Exception( + $"Can not parse Base64 string into CharacterSave:\n\tInvalid value {bytes[1]} in byte 2, should be either 0 or 1."); + + var mask = (ActorEquipMask) ((ushort) bytes[2] | ((ushort) bytes[3] << 8)); + if (!Enum.IsDefined(typeof(ActorEquipMask), mask)) + throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid value {mask} in byte 3 and 4."); + bytes.CopyTo(_bytes, 0); + break; + default: + throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid Version {bytes[0]}."); + } + } + + public static CharacterSave FromString(string base64) + { + var ret = new CharacterSave(); + ret.Load(base64); + return ret; + } + + public unsafe ActorCustomization Customizations + { + get + { + var ret = new ActorCustomization(); + fixed (byte* ptr = _bytes) + { + ret.Read(new IntPtr(ptr) + 4); + } + + return ret; + } + } + + public ActorEquipment Equipment + { + get + { + var ret = new ActorEquipment(); + ret.FromBytes(_bytes, 4 + ActorCustomization.CustomizationBytes); + return ret; + } + } } internal class Glamourer