Simple changes for 6.1

This commit is contained in:
Ottermandias 2022-04-14 21:59:30 +02:00
parent b141da40b0
commit e6e033bb8b
8 changed files with 539 additions and 529 deletions

View file

@ -2,123 +2,124 @@
using Lumina.Excel; using Lumina.Excel;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
namespace Glamourer.Customization namespace Glamourer.Customization;
[Sheet("CharaMakeParams")]
public class CharaMakeParams : ExcelRow
{ {
[Sheet("CharaMakeParams")] public const int NumMenus = 28;
public class CharaMakeParams : ExcelRow 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; ListSelector = 0,
public const int NumVoices = 12; IconSelector = 1,
public const int NumGraphics = 10; ColorPicker = 2,
public const int MaxNumValues = 100; DoubleColorPicker = 3,
public const int NumFaces = 8; MultiIconSelector = 4,
public const int NumFeatures = 7; Percentage = 5,
public const int NumEquip = 3; }
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> Race { get; set; } = null!;
public LazyRow<Tribe> 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<Race>(gameData, parser.ReadColumn<uint>(0), language);
Tribe = new LazyRow<Tribe>(gameData, parser.ReadColumn<uint>(1), language);
Gender = parser.ReadColumn<sbyte>(2);
for (var i = 0; i < NumMenus; ++i)
{ {
ListSelector = 0, Menus[i].Id = parser.ReadColumn<uint>(3 + 0 * NumMenus + i);
IconSelector = 1, Menus[i].InitVal = parser.ReadColumn<byte>(3 + 1 * NumMenus + i);
ColorPicker = 2, Menus[i].Type = (MenuType)parser.ReadColumn<byte>(3 + 2 * NumMenus + i);
DoubleColorPicker = 3, Menus[i].Size = parser.ReadColumn<byte>(3 + 3 * NumMenus + i);
MultiIconSelector = 4, Menus[i].LookAt = parser.ReadColumn<byte>(3 + 4 * NumMenus + i);
Percentage = 5, Menus[i].Mask = parser.ReadColumn<uint>(3 + 5 * NumMenus + i);
} Menus[i].Customization = (CustomizationId)parser.ReadColumn<uint>(3 + 6 * NumMenus + i);
Menus[i].Values = new uint[Menus[i].Size];
public struct Menu switch (Menus[i].Type)
{
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> Race { get; set; } = null!;
public LazyRow<Tribe> 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<Race>(gameData, parser.ReadColumn<uint>(0), language);
Tribe = new LazyRow<Tribe>(gameData, parser.ReadColumn<uint>(1), language);
Gender = parser.ReadColumn<sbyte>(2);
for (var i = 0; i < NumMenus; ++i)
{ {
Menus[i].Id = parser.ReadColumn<uint>(3 + 0 * NumMenus + i); case MenuType.ColorPicker:
Menus[i].InitVal = parser.ReadColumn<byte>(3 + 1 * NumMenus + i); case MenuType.DoubleColorPicker:
Menus[i].Type = (MenuType) parser.ReadColumn<byte>(3 + 2 * NumMenus + i); case MenuType.Percentage:
Menus[i].Size = parser.ReadColumn<byte>(3 + 3 * NumMenus + i); break;
Menus[i].LookAt = parser.ReadColumn<byte>(3 + 4 * NumMenus + i); default:
Menus[i].Mask = parser.ReadColumn<uint>(3 + 5 * NumMenus + i); for (var j = 0; j < Menus[i].Size; ++j)
Menus[i].Customization = (CustomizationId) parser.ReadColumn<uint>(3 + 6 * NumMenus + i); Menus[i].Values[j] = parser.ReadColumn<uint>(3 + (7 + j) * NumMenus + i);
Menus[i].Values = new uint[Menus[i].Size]; break;
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<uint>(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<byte>(3 + (MaxNumValues + 7 + j) * NumMenus + i);
} }
for (var i = 0; i < NumVoices; ++i) Menus[i].Graphic = new byte[NumGraphics];
Voices[i] = parser.ReadColumn<byte>(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + i); for (var j = 0; j < NumGraphics; ++j)
Menus[i].Graphic[j] = parser.ReadColumn<byte>(3 + (MaxNumValues + 7 + j) * NumMenus + i);
}
for (var i = 0; i < NumFaces; ++i) for (var i = 0; i < NumVoices; ++i)
{ Voices[i] = parser.ReadColumn<byte>(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + i);
FacialFeatureByFace[i].Icons = new uint[NumFeatures];
for (var j = 0; j < NumFeatures; ++j)
FacialFeatureByFace[i].Icons[j] =
(uint) parser.ReadColumn<int>(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + j * NumFaces + 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() FacialFeatureByFace[i].Icons[j] =
{ (uint)parser.ReadColumn<int>(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + j * NumFaces + i);
Helmet = parser.ReadColumn<ulong>(
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 0),
Top = parser.ReadColumn<ulong>(
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 1),
Gloves = parser.ReadColumn<ulong>(
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 2),
Legs = parser.ReadColumn<ulong>(
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 3),
Shoes = parser.ReadColumn<ulong>(
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 4),
Weapon = parser.ReadColumn<ulong>(
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 5),
SubWeapon = parser.ReadColumn<ulong>(
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 6),
};
} }
} }
for (var i = 0; i < NumEquip; ++i)
{
Equip[i] = new CharaMakeType.UnkData3347Obj()
{
Helmet = parser.ReadColumn<ulong>(
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 0),
Top = parser.ReadColumn<ulong>(
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 1),
Gloves = parser.ReadColumn<ulong>(
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 2),
Legs = parser.ReadColumn<ulong>(
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 3),
Shoes = parser.ReadColumn<ulong>(
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 4),
Weapon = parser.ReadColumn<ulong>(
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 5),
SubWeapon = parser.ReadColumn<ulong>(
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 6),
};
}
} }
} }

View file

@ -23,7 +23,7 @@ namespace Glamourer.Customization
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CharacterCustomization public struct CharacterCustomization
{ {
public const int CustomizationOffset = 0xDD8; public const int CustomizationOffset = 0x830;
public const int CustomizationBytes = 26; public const int CustomizationBytes = 26;
public static CharacterCustomization Default = new() public static CharacterCustomization Default = new()

View file

@ -196,7 +196,7 @@ namespace Glamourer.Customization
if (set.Faces.Count > 0) if (set.Faces.Count > 0)
set.SetAvailable(CustomizationId.Face); 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<IReadOnlyList<Customization>>(count); var featureDict = new List<IReadOnlyList<Customization>>(count);
for (var i = 0; i < count; ++i) for (var i = 0; i < count; ++i)
{ {

View file

@ -4,14 +4,17 @@ namespace Glamourer;
public static class CharacterExtensions public static class CharacterExtensions
{ {
public const int WetnessOffset = 0x19E4; public const int WetnessOffset = 0x1ADA;
public const byte WetnessFlag = 0x08; public const byte WetnessFlag = 0x80;
public const int StateFlagsOffset = 0xDF6; public const int HatVisibleOffset = 0x84E;
public const byte HatHiddenFlag = 0x01; public const int VisorToggledOffset = 0x84F;
public const byte VisorToggledFlag = 0x10; public const byte HatHiddenFlag = 0x01;
public const int AlphaOffset = 0x18B8; public const byte VisorToggledFlag = 0x08;
public const int WeaponHiddenOffset = 0xCD4; public const int AlphaOffset = 0x19E0;
public const byte WeaponHiddenFlag = 0x02; 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) public static unsafe bool IsWet(this Character a)
=> (*((byte*)a.Address + WetnessOffset) & WetnessFlag) != 0; => (*((byte*)a.Address + WetnessOffset) & WetnessFlag) != 0;
@ -29,49 +32,62 @@ public static class CharacterExtensions
return true; return true;
} }
public static unsafe ref byte StateFlags(this Character a) public static unsafe bool IsHatVisible(this Character a)
=> ref *((byte*)a.Address + StateFlagsOffset); => (*((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 current = IsHatVisible(a);
var previousValue = (current & flag) != 0; if (current == visible)
if (previousValue == value)
return false; return false;
if (value) if (visible)
a.StateFlags() = (byte)(current | flag); *((byte*)a.Address + HatVisibleOffset) = (byte)(*((byte*)a.Address + HatVisibleOffset) & ~HatHiddenFlag);
else else
a.StateFlags() = (byte)(current & ~flag); *((byte*)a.Address + HatVisibleOffset) = (byte)(*((byte*)a.Address + HatVisibleOffset) | HatHiddenFlag);
return true; return true;
} }
public static bool IsHatHidden(this Character a) public static unsafe bool IsVisorToggled(this Character a)
=> (a.StateFlags() & HatHiddenFlag) != 0; => (*((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) public static unsafe bool IsWeaponHidden(this Character a)
=> (a.StateFlags() & WeaponHiddenFlag) != 0 => (*((byte*)a.Address + WeaponHiddenOffset1) & WeaponHiddenFlag1) == WeaponHiddenFlag1
&& (*((byte*)a.Address + WeaponHiddenOffset) & WeaponHiddenFlag) != 0; && (*((byte*)a.Address + WeaponHiddenOffset2) & WeaponHiddenFlag2) == WeaponHiddenFlag2;
public static bool IsVisorToggled(this Character a)
=> (a.StateFlags() & VisorToggledFlag) != 0;
public static bool SetHatHidden(this Character a, bool value)
=> SetStateFlag(a, value, HatHiddenFlag);
public static unsafe bool SetWeaponHidden(this Character a, bool value) public static unsafe bool SetWeaponHidden(this Character a, bool value)
{ {
var ret = SetStateFlag(a, value, WeaponHiddenFlag); var hidden = IsWeaponHidden(a);
var val = *((byte*)a.Address + WeaponHiddenOffset); if (hidden == value)
if (value) return false;
*((byte*)a.Address + WeaponHiddenOffset) = (byte)(val | WeaponHiddenFlag);
else
*((byte*)a.Address + WeaponHiddenOffset) = (byte)(val & ~WeaponHiddenFlag);
return ret || (val & WeaponHiddenFlag) != 0 != value;
}
public static bool SetVisorToggled(this Character a, bool value) var val1 = *((byte*)a.Address + WeaponHiddenOffset1);
=> SetStateFlag(a, value, VisorToggledFlag); 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) public static unsafe ref float Alpha(this Character a)
=> ref *(float*)((byte*)a.Address + AlphaOffset); => ref *(float*)((byte*)a.Address + AlphaOffset);

View file

@ -8,394 +8,386 @@ using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; 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) var token = JToken.Load(reader);
=> objectType == typeof(CharacterSave); var s = token.ToObject<string>();
return CharacterSave.FromString(s!);
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
var s = token.ToObject<string>();
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);
}
}
} }
[JsonConverter(typeof(CharacterSaveConverter))] public override bool CanWrite
public class CharacterSave => true;
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{ {
public const byte CurrentVersion = 2; if (value != null)
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; var s = ((CharacterSave)value).ToBase64();
Alpha = 1.0f; serializer.Serialize(writer, s);
} }
}
public CharacterSave Copy() }
{
var ret = new CharacterSave(); [JsonConverter(typeof(CharacterSaveConverter))]
_bytes.CopyTo((Span<byte>) ret._bytes); public class CharacterSave
return ret; {
} public const byte CurrentVersion = 2;
public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes;
public byte Version public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 1;
=> _bytes[0];
public const byte TotalSize = TotalSizeVersion2;
public bool WriteCustomizations
{ private readonly byte[] _bytes = new byte[TotalSize];
get => (_bytes[1] & 0x01) != 0;
set => _bytes[1] = (byte) (value ? _bytes[1] | 0x01 : _bytes[1] & ~0x01); public CharacterSave()
} {
_bytes[0] = CurrentVersion;
public bool IsWet Alpha = 1.0f;
{ }
get => (_bytes[1] & 0x02) != 0;
set => _bytes[1] = (byte) (value ? _bytes[1] | 0x02 : _bytes[1] & ~0x02); public CharacterSave Copy()
} {
var ret = new CharacterSave();
public bool SetHatState _bytes.CopyTo((Span<byte>)ret._bytes);
{ return ret;
get => (_bytes[1] & 0x04) != 0; }
set => _bytes[1] = (byte) (value ? _bytes[1] | 0x04 : _bytes[1] & ~0x04);
} public byte Version
=> _bytes[0];
public bool SetWeaponState
{ public bool WriteCustomizations
get => (_bytes[1] & 0x08) != 0; {
set => _bytes[1] = (byte) (value ? _bytes[1] | 0x08 : _bytes[1] & ~0x08); get => (_bytes[1] & 0x01) != 0;
} set => _bytes[1] = (byte)(value ? _bytes[1] | 0x01 : _bytes[1] & ~0x01);
}
public bool SetVisorState
{ public bool IsWet
get => (_bytes[1] & 0x10) != 0; {
set => _bytes[1] = (byte) (value ? _bytes[1] | 0x10 : _bytes[1] & ~0x10); get => (_bytes[1] & 0x02) != 0;
} set => _bytes[1] = (byte)(value ? _bytes[1] | 0x02 : _bytes[1] & ~0x02);
}
public bool WriteProtected
{ public bool SetHatState
get => (_bytes[1] & 0x20) != 0; {
set => _bytes[1] = (byte) (value ? _bytes[1] | 0x20 : _bytes[1] & ~0x20); get => (_bytes[1] & 0x04) != 0;
} set => _bytes[1] = (byte)(value ? _bytes[1] | 0x04 : _bytes[1] & ~0x04);
}
public byte StateFlags
{ public bool SetWeaponState
get => _bytes[64 + CharacterCustomization.CustomizationBytes]; {
set => _bytes[64 + CharacterCustomization.CustomizationBytes] = value; get => (_bytes[1] & 0x08) != 0;
} set => _bytes[1] = (byte)(value ? _bytes[1] | 0x08 : _bytes[1] & ~0x08);
}
public bool HatState
{ public bool SetVisorState
get => (StateFlags & 0x01) == 0; {
set => StateFlags = (byte) (value ? StateFlags & ~0x01 : StateFlags | 0x01); get => (_bytes[1] & 0x10) != 0;
} set => _bytes[1] = (byte)(value ? _bytes[1] | 0x10 : _bytes[1] & ~0x10);
}
public bool VisorState
{ public bool WriteProtected
get => (StateFlags & 0x10) != 0; {
set => StateFlags = (byte) (value ? StateFlags | 0x10 : StateFlags & ~0x10); get => (_bytes[1] & 0x20) != 0;
} set => _bytes[1] = (byte)(value ? _bytes[1] | 0x20 : _bytes[1] & ~0x20);
}
public bool WeaponState
{ public byte StateFlags
get => (StateFlags & 0x02) == 0; {
set => StateFlags = (byte) (value ? StateFlags & ~0x02 : StateFlags | 0x02); get => _bytes[64 + CharacterCustomization.CustomizationBytes];
} set => _bytes[64 + CharacterCustomization.CustomizationBytes] = value;
}
public CharacterEquipMask WriteEquipment
{ public bool HatState
get => (CharacterEquipMask) (_bytes[2] | (_bytes[3] << 8)); {
set get => (StateFlags & 0x01) == 0;
{ set => StateFlags = (byte)(value ? StateFlags & ~0x01 : StateFlags | 0x01);
_bytes[2] = (byte) ((ushort) value & 0xFF); }
_bytes[3] = (byte) ((ushort) value >> 8);
} public bool VisorState
} {
get => (StateFlags & 0x10) != 0;
private static Dictionary<EquipSlot, (int, int, bool)> Offsets() set => StateFlags = (byte)(value ? StateFlags | 0x10 : StateFlags & ~0x10);
{ }
var stainOffsetWeapon = (int) Marshal.OffsetOf<CharacterWeapon>("Stain");
var stainOffsetEquip = (int) Marshal.OffsetOf<CharacterArmor>("Stain"); public bool WeaponState
{
(int, int, bool) ToOffsets(IntPtr offset, bool weapon) get => (StateFlags & 0x02) == 0;
{ set => StateFlags = (byte)(value ? StateFlags & ~0x02 : StateFlags | 0x02);
var off = 4 + CharacterCustomization.CustomizationBytes + (int) offset; }
return weapon ? (off, off + stainOffsetWeapon, weapon) : (off, off + stainOffsetEquip, weapon);
} public CharacterEquipMask WriteEquipment
{
return new Dictionary<EquipSlot, (int, int, bool)>(12) get => (CharacterEquipMask)(_bytes[2] | (_bytes[3] << 8));
{ set
[EquipSlot.MainHand] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("MainHand"), true), {
[EquipSlot.OffHand] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("OffHand"), true), _bytes[2] = (byte)((ushort)value & 0xFF);
[EquipSlot.Head] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Head"), false), _bytes[3] = (byte)((ushort)value >> 8);
[EquipSlot.Body] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Body"), false), }
[EquipSlot.Hands] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Hands"), false), }
[EquipSlot.Legs] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Legs"), false),
[EquipSlot.Feet] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Feet"), false), private static Dictionary<EquipSlot, (int, int, bool)> Offsets()
[EquipSlot.Ears] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Ears"), false), {
[EquipSlot.Neck] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Neck"), false), var stainOffsetWeapon = (int)Marshal.OffsetOf<CharacterWeapon>("Stain");
[EquipSlot.Wrists] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Wrists"), false), var stainOffsetEquip = (int)Marshal.OffsetOf<CharacterArmor>("Stain");
[EquipSlot.RFinger] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("RFinger"), false),
[EquipSlot.LFinger] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("LFinger"), false), (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);
private static readonly IReadOnlyDictionary<EquipSlot, (int, int, bool)> FieldOffsets = Offsets(); }
public bool WriteStain(EquipSlot slot, StainId stainId) return new Dictionary<EquipSlot, (int, int, bool)>(12)
{ {
if (WriteProtected) [EquipSlot.MainHand] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("MainHand"), true),
return false; [EquipSlot.OffHand] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("OffHand"), true),
[EquipSlot.Head] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Head"), false),
var (_, stainOffset, _) = FieldOffsets[slot]; [EquipSlot.Body] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Body"), false),
if (_bytes[stainOffset] == (byte) stainId) [EquipSlot.Hands] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Hands"), false),
return false; [EquipSlot.Legs] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Legs"), false),
[EquipSlot.Feet] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Feet"), false),
_bytes[stainOffset] = stainId.Value; [EquipSlot.Ears] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Ears"), false),
return true; [EquipSlot.Neck] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Neck"), false),
} [EquipSlot.Wrists] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Wrists"), false),
[EquipSlot.RFinger] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("RFinger"), false),
private bool WriteItem(int offset, SetId id, WeaponType type, ushort variant, bool weapon) [EquipSlot.LFinger] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("LFinger"), false),
{ };
var idBytes = BitConverter.GetBytes(id.Value); }
static bool WriteIfDifferent(ref byte x, byte y) private static readonly IReadOnlyDictionary<EquipSlot, (int, int, bool)> FieldOffsets = Offsets();
{
if (x == y) public bool WriteStain(EquipSlot slot, StainId stainId)
return false; {
if (WriteProtected)
x = y; return false;
return true;
} var (_, stainOffset, _) = FieldOffsets[slot];
if (_bytes[stainOffset] == (byte)stainId)
var ret = WriteIfDifferent(ref _bytes[offset], idBytes[0]); return false;
ret |= WriteIfDifferent(ref _bytes[offset + 1], idBytes[1]);
if (weapon) _bytes[stainOffset] = stainId.Value;
{ return true;
var typeBytes = BitConverter.GetBytes(type.Value); }
var variantBytes = BitConverter.GetBytes(variant);
ret |= WriteIfDifferent(ref _bytes[offset + 2], typeBytes[0]); private bool WriteItem(int offset, SetId id, WeaponType type, ushort variant, bool weapon)
ret |= WriteIfDifferent(ref _bytes[offset + 3], typeBytes[1]); {
ret |= WriteIfDifferent(ref _bytes[offset + 4], variantBytes[0]); var idBytes = BitConverter.GetBytes(id.Value);
ret |= WriteIfDifferent(ref _bytes[offset + 5], variantBytes[1]);
} static bool WriteIfDifferent(ref byte x, byte y)
else {
{ if (x == y)
ret |= WriteIfDifferent(ref _bytes[offset + 2], (byte) variant); return false;
}
x = y;
return ret; return true;
} }
public bool WriteItem(Item item) var ret = WriteIfDifferent(ref _bytes[offset], idBytes[0]);
{ ret |= WriteIfDifferent(ref _bytes[offset + 1], idBytes[1]);
if (WriteProtected) if (weapon)
return false; {
var typeBytes = BitConverter.GetBytes(type.Value);
var (itemOffset, _, isWeapon) = FieldOffsets[item.EquippableTo]; var variantBytes = BitConverter.GetBytes(variant);
var (id, type, variant) = item.MainModel; ret |= WriteIfDifferent(ref _bytes[offset + 2], typeBytes[0]);
var ret = WriteItem(itemOffset, id, type, variant, isWeapon); ret |= WriteIfDifferent(ref _bytes[offset + 3], typeBytes[1]);
if (item.EquippableTo == EquipSlot.MainHand && item.HasSubModel) ret |= WriteIfDifferent(ref _bytes[offset + 4], variantBytes[0]);
{ ret |= WriteIfDifferent(ref _bytes[offset + 5], variantBytes[1]);
var (subOffset, _, _) = FieldOffsets[EquipSlot.OffHand]; }
var (subId, subType, subVariant) = item.SubModel; else
ret |= WriteItem(subOffset, subId, subType, subVariant, true); {
} ret |= WriteIfDifferent(ref _bytes[offset + 2], (byte)variant);
}
return ret;
} return ret;
}
public unsafe float Alpha
{ public bool WriteItem(Item item)
get {
{ if (WriteProtected)
fixed (byte* ptr = &_bytes[60 + CharacterCustomization.CustomizationBytes]) return false;
{
return *(float*) ptr; var (itemOffset, _, isWeapon) = FieldOffsets[item.EquippableTo];
} var (id, type, variant) = item.MainModel;
} var ret = WriteItem(itemOffset, id, type, variant, isWeapon);
set if (item.EquippableTo == EquipSlot.MainHand && item.HasSubModel)
{ {
fixed (byte* ptr = _bytes) var (subOffset, _, _) = FieldOffsets[EquipSlot.OffHand];
{ var (subId, subType, subVariant) = item.SubModel;
*(ptr + 60 + CharacterCustomization.CustomizationBytes + 0) = *((byte*) &value + 0); ret |= WriteItem(subOffset, subId, subType, subVariant, true);
*(ptr + 60 + CharacterCustomization.CustomizationBytes + 1) = *((byte*) &value + 1); }
*(ptr + 60 + CharacterCustomization.CustomizationBytes + 2) = *((byte*) &value + 2);
*(ptr + 60 + CharacterCustomization.CustomizationBytes + 3) = *((byte*) &value + 3); return ret;
} }
}
} public unsafe float Alpha
{
public void Load(CharacterCustomization customization) get
{ {
WriteCustomizations = true; fixed (byte* ptr = &_bytes[60 + CharacterCustomization.CustomizationBytes])
customization.WriteBytes(_bytes, 4); {
} return *(float*)ptr;
}
public void Load(CharacterEquipment equipment, CharacterEquipMask mask = CharacterEquipMask.All) }
{ set
WriteEquipment = mask; {
equipment.WriteBytes(_bytes, 4 + CharacterCustomization.CustomizationBytes); fixed (byte* ptr = _bytes)
} {
*(ptr + 60 + CharacterCustomization.CustomizationBytes + 0) = *((byte*)&value + 0);
public string ToBase64() *(ptr + 60 + CharacterCustomization.CustomizationBytes + 1) = *((byte*)&value + 1);
=> Convert.ToBase64String(_bytes); *(ptr + 60 + CharacterCustomization.CustomizationBytes + 2) = *((byte*)&value + 2);
*(ptr + 60 + CharacterCustomization.CustomizationBytes + 3) = *((byte*)&value + 3);
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}."); public void Load(CharacterCustomization customization)
} {
WriteCustomizations = true;
private static void CheckRange(int idx, byte value, byte min, byte max) customization.WriteBytes(_bytes, 4);
{ }
if (value < min || value > max)
throw new Exception( public void Load(CharacterEquipment equipment, CharacterEquipMask mask = CharacterEquipMask.All)
$"Can not parse Base64 string into CharacterSave:\n\tInvalid value {value} in byte {idx}, should be in [{min},{max}]."); {
} WriteEquipment = mask;
equipment.WriteBytes(_bytes, 4 + CharacterCustomization.CustomizationBytes);
private static void CheckCharacterMask(byte val1, byte val2) }
{
var mask = (CharacterEquipMask) (val1 | (val2 << 8)); public string ToBase64()
if (mask > CharacterEquipMask.All) => Convert.ToBase64String(_bytes);
throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid value {mask} in byte 3 and 4.");
} private static void CheckSize(int length, int requiredLength)
{
public void LoadCharacter(Character a) if (length != requiredLength)
{ throw new Exception(
WriteCustomizations = true; $"Can not parse Base64 string into CharacterSave:\n\tInvalid size {length} instead of {requiredLength}.");
Load(new CharacterCustomization(a)); }
Load(new CharacterEquipment(a)); private static void CheckRange(int idx, byte value, byte min, byte max)
{
SetHatState = true; if (value < min || value > max)
SetVisorState = true; throw new Exception(
SetWeaponState = true; $"Can not parse Base64 string into CharacterSave:\n\tInvalid value {value} in byte {idx}, should be in [{min},{max}].");
StateFlags = a.StateFlags(); }
IsWet = a.IsWet(); private static void CheckCharacterMask(byte val1, byte val2)
Alpha = a.Alpha(); {
} 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 Apply(Character a) }
{
Glamourer.RevertableDesigns.Add(a); public void LoadCharacter(Character a)
{
if (WriteCustomizations) WriteCustomizations = true;
Customizations.Write(a.Address); Load(new CharacterCustomization(a));
if (WriteEquipment != CharacterEquipMask.None)
Equipment.Write(a.Address, WriteEquipment, WriteEquipment); Load(new CharacterEquipment(a));
a.SetWetness(IsWet);
a.Alpha() = Alpha; SetHatState = true;
if ((_bytes[1] & 0b11100) == 0b11100) SetVisorState = true;
{ SetWeaponState = true;
a.StateFlags() = StateFlags; StateFlags = (byte)((a.IsHatVisible() ? 0x00 : 0x01) | (a.IsVisorToggled() ? 0x10 : 0x00) | (a.IsWeaponHidden() ? 0x02 : 0x00));
}
else IsWet = a.IsWet();
{ Alpha = a.Alpha();
if (SetHatState) }
a.SetHatHidden(HatState);
if (SetVisorState)
a.SetVisorToggled(VisorState); public void Apply(Character a)
if (SetWeaponState) {
a.SetWeaponHidden(WeaponState); Glamourer.RevertableDesigns.Add(a);
}
} if (WriteCustomizations)
Customizations.Write(a.Address);
public void ApplyOnlyEquipment(Character a) if (WriteEquipment != CharacterEquipMask.None)
{ Equipment.Write(a.Address, WriteEquipment, WriteEquipment);
var oldState = _bytes[1]; a.SetWetness(IsWet);
WriteCustomizations = false; a.Alpha() = Alpha;
SetHatState = false; if (SetHatState)
SetVisorState = false; a.SetHatVisible(!HatState);
SetWeaponState = false; if (SetVisorState)
Apply(a); a.SetVisorToggled(VisorState);
_bytes[1] = oldState; if (SetWeaponState)
} a.SetWeaponHidden(WeaponState);
}
public void ApplyOnlyCustomizations(Character a)
{ public void ApplyOnlyEquipment(Character a)
var oldState = _bytes[1]; {
SetHatState = false; var oldState = _bytes[1];
SetVisorState = false; WriteCustomizations = false;
SetWeaponState = false; SetHatState = false;
var oldEquip = WriteEquipment; SetVisorState = false;
WriteEquipment = CharacterEquipMask.None; SetWeaponState = false;
Apply(a); Apply(a);
_bytes[1] = oldState; _bytes[1] = oldState;
WriteEquipment = oldEquip; }
}
public void ApplyOnlyCustomizations(Character a)
public void Load(string base64) {
{ var oldState = _bytes[1];
var bytes = Convert.FromBase64String(base64); SetHatState = false;
switch (bytes[0]) SetVisorState = false;
{ SetWeaponState = false;
case 1: var oldEquip = WriteEquipment;
CheckSize(bytes.Length, TotalSizeVersion1); WriteEquipment = CharacterEquipMask.None;
CheckRange(2, bytes[1], 0, 1); Apply(a);
Alpha = 1.0f; _bytes[1] = oldState;
bytes[0] = CurrentVersion; WriteEquipment = oldEquip;
break; }
case 2:
CheckSize(bytes.Length, TotalSizeVersion2); public void Load(string base64)
CheckRange(2, bytes[1], 0, 0x3F); {
break; var bytes = Convert.FromBase64String(base64);
default: throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid Version {bytes[0]}."); switch (bytes[0])
} {
case 1:
CheckCharacterMask(bytes[2], bytes[3]); CheckSize(bytes.Length, TotalSizeVersion1);
bytes.CopyTo(_bytes, 0); CheckRange(2, bytes[1], 0, 1);
} Alpha = 1.0f;
bytes[0] = CurrentVersion;
public static CharacterSave FromString(string base64) break;
{ case 2:
var ret = new CharacterSave(); CheckSize(bytes.Length, TotalSizeVersion2);
ret.Load(base64); CheckRange(2, bytes[1], 0, 0x3F);
return ret; break;
} default: throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid Version {bytes[0]}.");
}
public unsafe ref CharacterCustomization Customizations
{ CheckCharacterMask(bytes[2], bytes[3]);
get bytes.CopyTo(_bytes, 0);
{ }
fixed (byte* ptr = _bytes)
{ public static CharacterSave FromString(string base64)
return ref *(CharacterCustomization*) (ptr + 4); {
} var ret = new CharacterSave();
} ret.Load(base64);
} return ret;
}
public CharacterEquipment Equipment
{ public unsafe ref CharacterCustomization Customizations
get {
{ get
var ret = new CharacterEquipment(); {
ret.FromBytes(_bytes, 4 + CharacterCustomization.CustomizationBytes); fixed (byte* ptr = _bytes)
return ret; {
} return ref *(CharacterCustomization*)(ptr + 4);
}
}
}
public CharacterEquipment Equipment
{
get
{
var ret = new CharacterEquipment();
ret.FromBytes(_bytes, 4 + CharacterCustomization.CustomizationBytes);
return ret;
} }
} }
} }

View file

@ -140,7 +140,7 @@ namespace Glamourer.Gui
ImGui.SameLine(); ImGui.SameLine();
if (InputInt($"##text_{id}", ref current, 1, count)) if (InputInt($"##text_{id}", ref current, 1, count))
{ {
customization[id] = set.Data(id, current).Value; customization[id] = (byte) current;
ret = true; ret = true;
} }

View file

@ -277,8 +277,9 @@ namespace Glamourer.Gui
var label = $"##fsPopup{child.FullName()}"; var label = $"##fsPopup{child.FullName()}";
if (ImGui.BeginPopup(label)) if (ImGui.BeginPopup(label))
{ {
if (ImGui.MenuItem("Delete")) if (ImGui.MenuItem("Delete") && ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyShift)
_designs.DeleteAllChildren(child, false); _designs.DeleteAllChildren(child, false);
ImGuiCustom.HoverTooltip("Hold Control and Shift to delete.");
RenameChildInput(child); RenameChildInput(child);

View file

@ -38,7 +38,7 @@ namespace Glamourer.Gui
ret |= DrawCheckMark("Hat Visible", save.HatState, v => ret |= DrawCheckMark("Hat Visible", save.HatState, v =>
{ {
save.HatState = v; save.HatState = v;
player?.SetHatHidden(!v); player?.SetHatVisible(v);
}); });
ret |= DrawCheckMark("Weapon Visible", save.WeaponState, v => ret |= DrawCheckMark("Weapon Visible", save.WeaponState, v =>