From a160276cc5722b27da598536075806f776f003be Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 18 Jul 2022 18:29:01 +0200 Subject: [PATCH] Shit's fucked, yo --- Glamourer.GameData/CharacterCustomization.cs | 3 + Glamourer.GameData/GameData.cs | 3 - Glamourer.GameData/RestrictedGear.cs | 18 +- Glamourer/Actor.cs | 87 ++++ Glamourer/Api/PenumbraAttach.cs | 4 +- Glamourer/CharacterSave.cs | 143 +++++-- Glamourer/CustomizeExtensions.cs | 67 --- Glamourer/Glamourer.cs | 4 - Glamourer/Gui/Interface.Actors.cs | 27 +- Glamourer/Gui/Interface.Customization.cs | 4 +- Glamourer/ObjectManager.cs | 164 +++----- Glamourer/RedrawManager.cs | 412 +++++++++++-------- 12 files changed, 507 insertions(+), 429 deletions(-) create mode 100644 Glamourer/Actor.cs diff --git a/Glamourer.GameData/CharacterCustomization.cs b/Glamourer.GameData/CharacterCustomization.cs index c031a03..7176f8f 100644 --- a/Glamourer.GameData/CharacterCustomization.cs +++ b/Glamourer.GameData/CharacterCustomization.cs @@ -16,6 +16,9 @@ public readonly unsafe struct CharacterCustomization public CharacterCustomization(CustomizationData* data) => _data = data; + public void Load(CharacterCustomization other) + => *_data = *other._data; + public ref Race Race => ref _data->Race; diff --git a/Glamourer.GameData/GameData.cs b/Glamourer.GameData/GameData.cs index dfa5bea..b74f5a1 100644 --- a/Glamourer.GameData/GameData.cs +++ b/Glamourer.GameData/GameData.cs @@ -1,11 +1,8 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; using System.Linq; using Dalamud; using Dalamud.Data; -using Dalamud.Game.ClientState.Objects.SubKinds; using Glamourer.Structs; using Lumina.Excel.GeneratedSheets; using Penumbra.GameData.Enums; diff --git a/Glamourer.GameData/RestrictedGear.cs b/Glamourer.GameData/RestrictedGear.cs index e842e4f..b53411e 100644 --- a/Glamourer.GameData/RestrictedGear.cs +++ b/Glamourer.GameData/RestrictedGear.cs @@ -5,6 +5,7 @@ using Dalamud.Logging; using Dalamud.Utility; using Lumina.Excel; using Lumina.Excel.GeneratedSheets; +using Newtonsoft.Json.Linq; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Race = Penumbra.GameData.Enums.Race; @@ -20,9 +21,9 @@ public class RestrictedGear private readonly ExcelSheet _items; private readonly ExcelSheet _categories; - private readonly HashSet _raceGenderSet = RaceGenderGroup.Where(c => c != 0).ToHashSet(); - private readonly Dictionary _maleToFemale = new(); - private readonly Dictionary _femaleToMale = new(); + private readonly HashSet _raceGenderSet = RaceGenderGroup.Where(c => c != 0).ToHashSet(); + private readonly Dictionary _maleToFemale = new(); + private readonly Dictionary _femaleToMale = new(); internal RestrictedGear(DataManager gameData) { @@ -33,16 +34,15 @@ public class RestrictedGear } // Resolve a model given by its model id, variant and slot for your current race and gender. - public (bool Replaced, SetId ModelId, byte Variant) ResolveRestricted(SetId modelId, byte variant, EquipSlot slot, Race race, - Gender gender) + public (bool Replaced, CharacterArmor Armor) ResolveRestricted(CharacterArmor armor, EquipSlot slot, Race race, Gender gender) { - var quad = modelId.Value | ((uint)variant << 16); + var quad = armor.Set.Value | ((uint)armor.Variant << 16); // Check racial gear, this does not need slots. if (RaceGenderGroup.Contains(quad)) { var idx = ((int)race - 1) * 2 + (gender is Gender.Female or Gender.FemaleNpc ? 1 : 0); var value = RaceGenderGroup[idx]; - return (value != quad, (ushort)value, (byte)(value >> 16)); + return (value != quad, new CharacterArmor((ushort)value, (byte)(value >> 16), armor.Stain)); } // Check gender slots. If current gender is female, check if anything needs to be changed from male to female, @@ -52,10 +52,10 @@ public class RestrictedGear var needle = quad | ((uint)slot.ToSlot() << 24); if (gender is Gender.Female or Gender.FemaleNpc && _maleToFemale.TryGetValue(needle, out var newValue) || gender is Gender.Male or Gender.MaleNpc && _femaleToMale.TryGetValue(needle, out newValue)) - return (quad != newValue, (ushort)newValue, (byte)(newValue >> 16)); + return (quad != newValue, new CharacterArmor((ushort)newValue, (byte)(newValue >> 16), armor.Stain)); // The gear is not restricted. - return (false, modelId, variant); + return (false, armor); } // Add all unknown restricted gear and pair it with emperor's new gear on start up. diff --git a/Glamourer/Actor.cs b/Glamourer/Actor.cs new file mode 100644 index 0000000..d13037c --- /dev/null +++ b/Glamourer/Actor.cs @@ -0,0 +1,87 @@ +using System; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Types; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Penumbra.GameData.ByteString; + +namespace Glamourer; + +public unsafe struct Actor : IEquatable +{ + public record struct Identifier(Utf8String Name, uint Id, ushort HomeWorld, ushort Index); + + public static readonly Actor Null = new() { Pointer = null }; + + public FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Pointer; + + public IntPtr Address + => (IntPtr)Pointer; + + public static implicit operator Actor(IntPtr? pointer) + => new() { Pointer = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)pointer.GetValueOrDefault(IntPtr.Zero) }; + + public static implicit operator IntPtr(Actor actor) + => actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer; + + public Identifier GetIdentifier() + { + if (Pointer == null) + return new Identifier(Utf8String.Empty, 0, 0, 0); + + return new Identifier(Utf8Name, Pointer->GameObject.ObjectID, Pointer->HomeWorld, Pointer->GameObject.ObjectIndex); + } + + public Character? Character + => Pointer == null ? null : Dalamud.Objects[Pointer->GameObject.ObjectIndex] as Character; + + public bool IsAvailable + => Pointer->GameObject.GetIsTargetable(); + + public bool IsHuman + => Pointer != null && Pointer->ModelCharaId == 0; + + public int ModelId + => Pointer->ModelCharaId; + + public ObjectKind ObjectKind + => (ObjectKind)Pointer->GameObject.ObjectKind; + + public Utf8String Utf8Name + => new(Pointer->GameObject.Name); + + public Human* DrawObject + => (Human*)Pointer->GameObject.DrawObject; + + public void SetModelId(int value) + { + if (Pointer != null) + Pointer->ModelCharaId = value; + } + + public static implicit operator bool(Actor actor) + => actor.Pointer != null; + + public static bool operator true(Actor actor) + => actor.Pointer != null; + + public static bool operator false(Actor actor) + => actor.Pointer == null; + + public static bool operator !(Actor actor) + => actor.Pointer == null; + + public bool Equals(Actor other) + => Pointer == other.Pointer; + + public override bool Equals(object? obj) + => obj is Actor other && Equals(other); + + public override int GetHashCode() + => ((ulong)Pointer).GetHashCode(); + + public static bool operator ==(Actor lhs, Actor rhs) + => lhs.Pointer == rhs.Pointer; + + public static bool operator !=(Actor lhs, Actor rhs) + => lhs.Pointer != rhs.Pointer; +} diff --git a/Glamourer/Api/PenumbraAttach.cs b/Glamourer/Api/PenumbraAttach.cs index bc8c61c..b9051e0 100644 --- a/Glamourer/Api/PenumbraAttach.cs +++ b/Glamourer/Api/PenumbraAttach.cs @@ -126,8 +126,8 @@ public class PenumbraAttach : IDisposable //} } - public unsafe FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* GameObjectFromDrawObject(IntPtr drawObject) - => (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)(_drawObjectInfo?.InvokeFunc(drawObject).Item1 ?? IntPtr.Zero); + public Actor GameObjectFromDrawObject(IntPtr drawObject) + => _drawObjectInfo?.InvokeFunc(drawObject).Item1 ?? IntPtr.Zero; public void RedrawObject(GameObject? actor, RedrawType settings, bool repeat) { diff --git a/Glamourer/CharacterSave.cs b/Glamourer/CharacterSave.cs index 83c9b7d..1bf29cc 100644 --- a/Glamourer/CharacterSave.cs +++ b/Glamourer/CharacterSave.cs @@ -13,13 +13,13 @@ namespace Glamourer; public class CharacterSaveConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, CharacterSave value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, CharacterSave? value, JsonSerializer serializer) { - var s = value.ToBase64(); + var s = value?.ToBase64() ?? string.Empty; serializer.Serialize(writer, s); } - public override CharacterSave ReadJson(JsonReader reader, Type objectType, CharacterSave existingValue, bool hasExistingValue, + public override CharacterSave ReadJson(JsonReader reader, Type objectType, CharacterSave? existingValue, bool hasExistingValue, JsonSerializer serializer) { var token = JToken.Load(reader); @@ -28,9 +28,8 @@ public class CharacterSaveConverter : JsonConverter } } -[JsonConverter(typeof(CharacterSaveConverter))] [StructLayout(LayoutKind.Sequential, Pack = 1)] -public unsafe struct CharacterSave +public unsafe struct CharacterData { [Flags] public enum SaveFlags : byte @@ -47,42 +46,27 @@ public unsafe struct CharacterSave public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + CustomizationData.CustomizationBytes; public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + CustomizationData.CustomizationBytes + 4 + 1; + public const byte TotalSizeVersion3 = 1 + 1 + 2 + 7 + 7 + 2 + 40 + CustomizationData.CustomizationBytes + 4; + public const byte CurrentVersion = 3; - public const byte CurrentVersion = 3; - public byte Version = CurrentVersion; - public SaveFlags Flags = 0; - public CharacterEquipMask Equip = 0; - public CharacterWeapon MainHand = default; - public CharacterWeapon OffHand = default; - public ushort Padding = 0; - public CharacterArmor Head = default; - public CharacterArmor Body = default; - public CharacterArmor Hands = default; - public CharacterArmor Legs = default; - public CharacterArmor Feet = default; - public CharacterArmor Ears = default; - public CharacterArmor Neck = default; - public CharacterArmor Wrist = default; - public CharacterArmor RFinger = default; - public CharacterArmor LFinger = default; - private CustomizationData CustomizationData = CustomizationData.Default; - public float Alpha = 1f; - - public CharacterSave() - { } - - public void Load(Actor actor) - { - if (!actor.IsHuman || actor.Pointer->GameObject.DrawObject == null) - return; - - var human = (Human*)actor.Pointer->GameObject.DrawObject; - CustomizationData = *(CustomizationData*)human->CustomizeData; - fixed (void* equip = &Head) - { - Functions.MemCpyUnchecked(equip, human->EquipSlotData, sizeof(CharacterArmor) * 10); - } - } + public byte Version; + public SaveFlags Flags; + public CharacterEquipMask Equip; + public CharacterWeapon MainHand; + public CharacterWeapon OffHand; + public ushort Padding; + public CharacterArmor Head; + public CharacterArmor Body; + public CharacterArmor Hands; + public CharacterArmor Legs; + public CharacterArmor Feet; + public CharacterArmor Ears; + public CharacterArmor Neck; + public CharacterArmor Wrist; + public CharacterArmor RFinger; + public CharacterArmor LFinger; + private CustomizationData CustomizationData; + public float Alpha; public CharacterCustomization Customize { @@ -106,11 +90,47 @@ public unsafe struct CharacterSave } } + public static readonly CharacterData Default + = new() + { + Version = CurrentVersion, + Flags = SaveFlags.WriteCustomizations, + Equip = CharacterEquipMask.All, + MainHand = CharacterWeapon.Empty, + OffHand = CharacterWeapon.Empty, + Padding = 0, + Head = CharacterArmor.Empty, + Body = CharacterArmor.Empty, + Hands = CharacterArmor.Empty, + Legs = CharacterArmor.Empty, + Feet = CharacterArmor.Empty, + Ears = CharacterArmor.Empty, + Neck = CharacterArmor.Empty, + Wrist = CharacterArmor.Empty, + RFinger = CharacterArmor.Empty, + LFinger = CharacterArmor.Empty, + CustomizationData = CustomizationData.Default, + Alpha = 1f, + }; + + public void Load(Actor actor) + { + if (!actor.IsHuman || actor.Pointer->GameObject.DrawObject == null) + return; + + var human = (Human*)actor.Pointer->GameObject.DrawObject; + CustomizationData = *(CustomizationData*)human->CustomizeData; + fixed (void* equip = &Head) + { + Functions.MemCpyUnchecked(equip, human->EquipSlotData, sizeof(CharacterArmor) * 10); + } + } + public string ToBase64() { fixed (void* ptr = &this) { - return Convert.ToBase64String(new ReadOnlySpan(ptr, sizeof(CharacterSave))); + return Convert.ToBase64String(new ReadOnlySpan(ptr, sizeof(CharacterData))); } } @@ -128,10 +148,10 @@ public unsafe struct CharacterSave $"Can not parse Base64 string into CharacterSave:\n\tInvalid value {value} in byte {idx}, should be in [{min},{max}]."); } - public static CharacterSave FromString(string data) + public static CharacterData FromString(string data) { var bytes = Convert.FromBase64String(data); - var ret = new CharacterSave(); + var ret = new CharacterData(); fixed (byte* ptr = bytes) { switch (bytes[0]) @@ -156,8 +176,8 @@ public unsafe struct CharacterSave ret.Flags |= SaveFlags.VisorState; break; case 3: - CheckSize(bytes.Length, sizeof(CharacterSave)); - Functions.MemCpyUnchecked(&ret, ptr, sizeof(CharacterSave)); + CheckSize(bytes.Length, TotalSizeVersion3); + Functions.MemCpyUnchecked(&ret, ptr, TotalSizeVersion3); break; default: throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid Version {bytes[0]}."); } @@ -166,3 +186,36 @@ public unsafe struct CharacterSave return ret; } } + +[JsonConverter(typeof(CharacterSaveConverter))] +public class CharacterSave +{ + private CharacterData _data; + + public CharacterSave() + => _data = CharacterData.Default; + + public CharacterSave(Actor actor) + => _data.Load(actor); + + public void Load(Actor actor) + => _data.Load(actor); + + public string ToBase64() + => _data.ToBase64(); + + public CharacterCustomization Customization + => _data.Customize; + + public CharacterEquip Equipment + => _data.Equipment; + + public ref CharacterWeapon MainHand + => ref _data.MainHand; + + public ref CharacterWeapon OffHand + => ref _data.OffHand; + + public static CharacterSave FromString(string data) + => new() { _data = CharacterData.FromString(data) }; +} diff --git a/Glamourer/CustomizeExtensions.cs b/Glamourer/CustomizeExtensions.cs index 0c467df..d3d7ba3 100644 --- a/Glamourer/CustomizeExtensions.cs +++ b/Glamourer/CustomizeExtensions.cs @@ -54,71 +54,4 @@ public static unsafe class CustomizeExtensions public static string ClanName(this CharacterCustomization customize) => ClanName(customize.Clan, customize.Gender); - - // Change a gender and fix up all required customizations afterwards. - public static bool ChangeGender(this CharacterCustomization customize, Gender gender, CharacterEquip equip) - { - if (customize.Gender == gender) - return false; - - customize.Gender = gender; - FixUpAttributes(customize, equip); - - return true; - } - - // Change a race and fix up all required customizations afterwards. - public static bool ChangeRace(this CharacterCustomization customize, SubRace clan, CharacterEquip equip) - { - if (customize.Clan == clan) - return false; - - var race = clan.ToRace(); - customize.Race = race; - customize.Clan = clan; - - // TODO: Female Hrothgar - if (race == Race.Hrothgar) - customize.Gender = Gender.Male; - - FixUpAttributes(customize, equip); - - return true; - } - - // Go through a whole customization struct and fix up all settings that need fixing. - private static void FixUpAttributes(CharacterCustomization customize, CharacterEquip equip) - { - var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender); - foreach (CustomizationId id in Enum.GetValues(typeof(CustomizationId))) - { - switch (id) - { - case CustomizationId.Race: break; - case CustomizationId.Clan: break; - case CustomizationId.BodyType: break; - case CustomizationId.Gender: break; - case CustomizationId.FacialFeaturesTattoos: break; - case CustomizationId.HighlightsOnFlag: break; - case CustomizationId.Face: break; - default: - var count = set.Count(id); - if (set.DataByValue(id, customize[id], out _) < 0) - customize[id] = count == 0 ? (byte)0 : set.Data(id, 0).Value; - break; - } - } - - if (!equip) - return; - - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - var item = equip[slot]; - var (replaced, newSet, newVariant) = - Glamourer.RestrictedGear.ResolveRestricted(item.Set, item.Variant, slot, customize.Race, customize.Gender); - if (replaced) - equip[slot] = new CharacterArmor(newSet, newVariant, item.Stain); - } - } } diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index e67c812..9b6fd60 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -48,7 +48,6 @@ public class Glamourer : IDalamudPlugin public static ICustomizationManager Customization = null!; public static RedrawManager RedrawManager = null!; - public static RestrictedGear RestrictedGear = null!; private readonly WindowSystem _windowSystem = new("Glamourer"); private readonly Interface _interface; @@ -61,8 +60,6 @@ public class Glamourer : IDalamudPlugin { Dalamud.Initialize(pluginInterface); Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.ClientState.ClientLanguage); - RestrictedGear = GameData.RestrictedGear(Dalamud.GameData); - var m = global::Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData, Dalamud.ClientState.ClientLanguage); Config = GlamourerConfig.Load(); Penumbra = new PenumbraAttach(Config.AttachToPenumbra); @@ -83,7 +80,6 @@ public class Glamourer : IDalamudPlugin _interface = new Interface(this); _windowSystem.AddWindow(_interface); Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw; - var x = 0x00011000u; //FixedDesignManager.Flag((Human*)((Actor)Dalamud.ClientState.LocalPlayer?.Address).Pointer->GameObject.DrawObject, 0, &x); } diff --git a/Glamourer/Gui/Interface.Actors.cs b/Glamourer/Gui/Interface.Actors.cs index c606fb0..2441861 100644 --- a/Glamourer/Gui/Interface.Actors.cs +++ b/Glamourer/Gui/Interface.Actors.cs @@ -1,16 +1,6 @@ using System; -using System.Linq; using System.Numerics; -using System.Runtime.CompilerServices; -using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Game.ClientState.Objects.SubKinds; -using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface; -using Dalamud.Logging; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using Glamourer.Customization; -using Glamourer.Designs; -using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -24,8 +14,7 @@ internal partial class Interface { private class ActorTab { - private ObjectManager.ActorData _data = new(string.Empty, string.Empty, Actor.Null, false, Actor.Null); - private CharacterSave _character = new(); + private ObjectManager.ActorData _data = new(string.Empty, new Actor.Identifier(), Actor.Null, false, Actor.Null); private Actor _nextSelect = Actor.Null; public void Draw() @@ -49,9 +38,12 @@ internal partial class Interface private void DrawActorPanel() { using var group = ImRaii.Group(); - if (DrawCustomization(_character.Customize, _character.Equipment, !_data.Modifiable)) + if (!Glamourer.RedrawManager.CurrentManipulations.GetSave(_data.Actor, out var save)) + return; + + if (DrawCustomization(save.Customization, save.Equipment, !_data.Modifiable)) { - Glamourer.RedrawManager.Set(_data.Actor.Address, _character); + //Glamourer.RedrawManager.Set(_data.Actor.Address, _character); Glamourer.Penumbra.RedrawObject(_data.Actor.Character, RedrawType.Redraw, true); } @@ -59,6 +51,11 @@ internal partial class Interface { Glamourer.RedrawManager.ChangeEquip(_data.Actor.Address, EquipSlot.Head, new CharacterArmor(265, 1, 0)); } + + if (ImGui.Button("Set Weapon")) + { + Glamourer.RedrawManager.LoadWeapon(_data.Actor.Address, new CharacterWeapon(0x00C9, 0x004E, 0x0001, 0x00), new CharacterWeapon(0x0065, 0x003D, 0x0001, 0x00)); + } } private void DrawMonsterPanel() @@ -115,7 +112,7 @@ internal partial class Interface private void UpdateSelection(ObjectManager.ActorData data) { _data = data; - _character.Load(_data.Actor); + //_character.Load(_data.Actor); } private bool CheckFilter(ObjectManager.ActorData data) diff --git a/Glamourer/Gui/Interface.Customization.cs b/Glamourer/Gui/Interface.Customization.cs index 1e3ceb0..7260d7d 100644 --- a/Glamourer/Gui/Interface.Customization.cs +++ b/Glamourer/Gui/Interface.Customization.cs @@ -80,7 +80,7 @@ internal partial class Interface return false; var gender = customize.Gender == Gender.Male ? Gender.Female : Gender.Male; - return customize.ChangeGender(gender, locked ? CharacterEquip.Null : equip); + return false; //customize.ChangeGender(gender, locked ? CharacterEquip.Null : equip); } private static bool DrawRaceCombo(CharacterCustomization customize, CharacterEquip equip, bool locked) @@ -98,7 +98,7 @@ internal partial class Interface foreach (var subRace in Enum.GetValues().Skip(1)) // Skip Unknown { if (ImGui.Selectable(CustomizeExtensions.ClanName(subRace, customize.Gender), subRace == customize.Clan)) - ret |= customize.ChangeRace(subRace, equip); + ret |= false; //customize.ChangeRace(subRace, equip); } return ret; diff --git a/Glamourer/ObjectManager.cs b/Glamourer/ObjectManager.cs index 3f47829..39ac988 100644 --- a/Glamourer/ObjectManager.cs +++ b/Glamourer/ObjectManager.cs @@ -1,80 +1,20 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Types; +using Penumbra.GameData.ByteString; namespace Glamourer; -public unsafe struct Actor : IEquatable -{ - public static readonly Actor Null = new() { Pointer = null }; - - public FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Pointer; - - public IntPtr Address - => (IntPtr)Pointer; - - public static implicit operator Actor(IntPtr? pointer) - => new() { Pointer = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)pointer.GetValueOrDefault(IntPtr.Zero) }; - - public static implicit operator IntPtr(Actor actor) - => actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer; - - public Character? Character - => Pointer == null ? null : Dalamud.Objects[Pointer->GameObject.ObjectIndex] as Character; - - public bool IsAvailable - => Pointer->GameObject.GetIsTargetable(); - - public bool IsHuman - => Pointer != null && Pointer->ModelCharaId == 0; - - public int ModelId - => Pointer != null ? Pointer->ModelCharaId : 0; - - public void SetModelId(int value) - { - if (Pointer != null) - Pointer->ModelCharaId = value; - } - - public static implicit operator bool(Actor actor) - => actor.Pointer != null; - - public static bool operator true(Actor actor) - => actor.Pointer != null; - - public static bool operator false(Actor actor) - => actor.Pointer == null; - - public static bool operator !(Actor actor) - => actor.Pointer == null; - - public bool Equals(Actor other) - => Pointer == other.Pointer; - - public override bool Equals(object? obj) - => obj is Actor other && Equals(other); - - public override int GetHashCode() - => ((ulong)Pointer).GetHashCode(); - - public static bool operator ==(Actor lhs, Actor rhs) - => lhs.Pointer == rhs.Pointer; - - public static bool operator !=(Actor lhs, Actor rhs) - => lhs.Pointer != rhs.Pointer; -} - public static class ObjectManager { - private const int GPosePlayerIndex = 201; - private const int CharacterScreenIndex = 240; - private const int ExamineScreenIndex = 241; - private const int FittingRoomIndex = 242; - private const int DyePreviewIndex = 243; - private static readonly Dictionary _nameCounters = new(); - private static readonly Dictionary _gPoseActors = new(CharacterScreenIndex - GPosePlayerIndex); + private const int GPosePlayerIndex = 201; + private const int CharacterScreenIndex = 240; + private const int ExamineScreenIndex = 241; + private const int FittingRoomIndex = 242; + private const int DyePreviewIndex = 243; + + private static readonly Dictionary NameCounters = new(); + private static readonly Dictionary GPoseActors = new(CharacterScreenIndex - GPosePlayerIndex); public static bool IsInGPose() => Dalamud.Objects[GPosePlayerIndex] != null; @@ -85,88 +25,88 @@ public static class ObjectManager public static Actor Player => Dalamud.ClientState.LocalPlayer?.Address; - public record struct ActorData(string Label, string Name, Actor Actor, bool Modifiable, Actor GPose); + public record struct ActorData(string Label, Actor.Identifier Identifier, Actor Actor, bool Modifiable, Actor GPose); public static IEnumerable GetEnumerator() { - _nameCounters.Clear(); - _gPoseActors.Clear(); + NameCounters.Clear(); + GPoseActors.Clear(); for (var i = GPosePlayerIndex; i < CharacterScreenIndex; ++i) { - var character = Dalamud.Objects[i]; - if (character == null) + Actor character = Dalamud.Objects[i]?.Address; + if (!character) break; - var name = character.Name.TextValue; - _gPoseActors[name] = character.Address; - yield return new ActorData(GetLabel(character, name, 0, true), name, character.Address, true, Actor.Null); + var identifier = character.GetIdentifier(); + GPoseActors[identifier] = character.Address; + yield return new ActorData(GetLabel(character, identifier.Name.ToString(), 0, true), identifier, character.Address, true, + Actor.Null); } - var actor = Dalamud.Objects[CharacterScreenIndex]; - if (actor != null) - yield return new ActorData("Character Screen Actor", string.Empty, actor.Address, false, Actor.Null); + Actor actor = Dalamud.Objects[CharacterScreenIndex]?.Address; + if (actor) + yield return new ActorData("Character Screen Actor", actor.GetIdentifier(), actor.Address, false, Actor.Null); - actor = Dalamud.Objects[ExamineScreenIndex]; - if (actor != null) - yield return new ActorData("Examine Screen Actor", string.Empty, actor.Address, false, Actor.Null); + actor = Dalamud.Objects[ExamineScreenIndex]?.Address; + if (actor) + yield return new ActorData("Examine Screen Actor", actor.GetIdentifier(), actor.Address, false, Actor.Null); - actor = Dalamud.Objects[FittingRoomIndex]; - if (actor != null) - yield return new ActorData("Fitting Room Actor", string.Empty, actor.Address, false, Actor.Null); + actor = Dalamud.Objects[FittingRoomIndex]?.Address; + if (actor) + yield return new ActorData("Fitting Room Actor", actor.GetIdentifier(), actor.Address, false, Actor.Null); - actor = Dalamud.Objects[DyePreviewIndex]; - if (actor != null) - yield return new ActorData("Dye Preview Actor", string.Empty, actor.Address, false, Actor.Null); + actor = Dalamud.Objects[DyePreviewIndex]?.Address; + if (actor) + yield return new ActorData("Dye Preview Actor", actor.GetIdentifier(), actor.Address, false, Actor.Null); for (var i = 0; i < GPosePlayerIndex; ++i) { - var character = Dalamud.Objects[i]; - if (character == null - || character.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion + actor = Dalamud.Objects[i]?.Address; + if (!actor + || actor.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion or ObjectKind.Retainer)) continue; - var name = character.Name.TextValue; - if (name.Length == 0) + var identifier = actor.GetIdentifier(); + if (identifier.Name.Length == 0) continue; - if (_nameCounters.TryGetValue(name, out var num)) - _nameCounters[name] = ++num; + if (NameCounters.TryGetValue(identifier.Name, out var num)) + NameCounters[identifier.Name] = ++num; else - _nameCounters[name] = num = 1; + NameCounters[identifier.Name] = num = 1; - if (!_gPoseActors.TryGetValue(name, out var gPose)) + if (!GPoseActors.TryGetValue(identifier, out var gPose)) gPose = Actor.Null; - yield return new ActorData(GetLabel(character, name, num, false), name, character.Address, true, gPose); + yield return new ActorData(GetLabel(actor, identifier.Name.ToString(), num, false), identifier, actor.Address, true, gPose); } for (var i = DyePreviewIndex + 1; i < Dalamud.Objects.Length; ++i) { - var character = Dalamud.Objects[i]; - if (character == null - || !((Actor)character.Address).IsAvailable - || character.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion + actor = Dalamud.Objects[i]?.Address; + if (!actor + || actor.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion or ObjectKind.Retainer)) continue; - var name = character.Name.TextValue; - if (name.Length == 0) + var identifier = actor.GetIdentifier(); + if (identifier.Name.Length == 0) continue; - if (_nameCounters.TryGetValue(name, out var num)) - _nameCounters[name] = ++num; + if (NameCounters.TryGetValue(identifier.Name, out var num)) + NameCounters[identifier.Name] = ++num; else - _nameCounters[name] = num = 1; + NameCounters[identifier.Name] = num = 1; - if (!_gPoseActors.TryGetValue(name, out var gPose)) + if (!GPoseActors.TryGetValue(identifier, out var gPose)) gPose = Actor.Null; - yield return new ActorData(GetLabel(character, name, num, false), name, character.Address, true, gPose); + yield return new ActorData(GetLabel(actor, identifier.Name.ToString(), num, false), identifier, actor.Address, true, gPose); } } - private static unsafe string GetLabel(GameObject player, string playerName, int num, bool gPose) + private static unsafe string GetLabel(Actor player, string playerName, int num, bool gPose) { if (player.ObjectKind == ObjectKind.Player) return gPose ? $"{playerName} (GPose)" : num == 1 ? playerName : $"{playerName} #{num}"; diff --git a/Glamourer/RedrawManager.cs b/Glamourer/RedrawManager.cs index 00112b3..a591e04 100644 --- a/Glamourer/RedrawManager.cs +++ b/Glamourer/RedrawManager.cs @@ -1,211 +1,283 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using Dalamud.Hooking; -using Dalamud.Logging; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; -using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Customization; -using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer; -public unsafe class RedrawManager : IDisposable +public class CurrentManipulations +{ + private readonly RestrictedGear _restrictedGear = GameData.RestrictedGear(Dalamud.GameData); + private readonly Dictionary _characterSaves = new(); + + public CharacterSave CreateSave(Actor actor) + { + var id = actor.GetIdentifier(); + if (_characterSaves.TryGetValue(actor.GetIdentifier(), out var save)) + return save; + + save = new CharacterSave(actor); + _characterSaves.Add(id with { Name = id.Name.Clone() }, save); + return save; + } + + public bool GetSave(Actor actor, [NotNullWhen(true)] out CharacterSave? save) + { + save = null; + return actor && _characterSaves.TryGetValue(actor.GetIdentifier(), out save); + } + + public bool GetSave(Actor.Identifier identifier, [NotNullWhen(true)] out CharacterSave? save) + => _characterSaves.TryGetValue(identifier, out save); + + public CharacterArmor? ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data) + { + var save = CreateSave(actor); + (_, data) = _restrictedGear.ResolveRestricted(data, slot, save.Customization.Race, save.Customization.Gender); + if (save.Equipment[slot] == data) + return null; + + save.Equipment[slot] = data; + return data; + } + + public bool ChangeWeapon(Actor actor, CharacterWeapon main) + { + var save = CreateSave(actor); + if (save.MainHand == main) + return false; + + save.MainHand = main; + return true; + } + + public bool ChangeWeapon(Actor actor, CharacterWeapon main, CharacterWeapon off) + { + var save = CreateSave(actor); + if (main == save.MainHand && off == save.OffHand) + return false; + + save.MainHand = main; + save.OffHand = off; + return true; + } + + public void ChangeCustomization(Actor actor, CharacterCustomization customize) + { + var save = CreateSave(actor); + FixRestrictedGear(save, customize.Gender, customize.Race); + save.Customization.Load(customize); + } + + public bool ChangeCustomization(Actor actor, CustomizationId id, byte value) + { + if (id == CustomizationId.Race) + return ChangeRace(actor, (SubRace)value); + if (id == CustomizationId.Gender) + return ChangeGender(actor, (Gender)value); + + var save = CreateSave(actor); + var customize = save.Customization; + if (customize[id] != value) + return false; + + customize[id] = value; + return true; + } + + // Change a gender and fix up all required customizations afterwards. + public bool ChangeGender(Actor actor, Gender gender) + { + var save = CreateSave(actor); + if (save.Customization.Gender == gender) + return false; + + var customize = save.Customization; + FixRestrictedGear(save, gender, customize.Race); + FixUpAttributes(customize); + return true; + } + + // Change a race and fix up all required customizations afterwards. + public bool ChangeRace(Actor actor, SubRace clan) + { + var save = CreateSave(actor); + if (save.Customization.Clan == clan) + return false; + + var customize = save.Customization; + var race = clan.ToRace(); + var gender = race == Race.Hrothgar ? Gender.Male : customize.Gender; // TODO Female Hrothgar + FixRestrictedGear(save, gender, race); + customize.Gender = gender; + customize.Race = race; + customize.Clan = clan; + + FixUpAttributes(customize); + return true; + } + + // Go through a whole customization struct and fix up all settings that need fixing. + private void FixUpAttributes(CharacterCustomization customize) + { + var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender); + foreach (CustomizationId id in Enum.GetValues(typeof(CustomizationId))) + { + switch (id) + { + case CustomizationId.Race: break; + case CustomizationId.Clan: break; + case CustomizationId.BodyType: break; + case CustomizationId.Gender: break; + case CustomizationId.FacialFeaturesTattoos: break; + case CustomizationId.HighlightsOnFlag: break; + case CustomizationId.Face: break; + default: + var count = set.Count(id); + if (set.DataByValue(id, customize[id], out _) < 0) + customize[id] = count == 0 ? (byte)0 : set.Data(id, 0).Value; + break; + } + } + } + + private void FixRestrictedGear(CharacterSave save, Gender gender, Race race) + { + if (race == save.Customization.Race && gender == save.Customization.Gender) + return; + + var equip = save.Equipment; + foreach (var slot in EquipSlotExtensions.EqdpSlots) + (_, equip[slot]) = _restrictedGear.ResolveRestricted(equip[slot], slot, race, gender); + } +} + +public unsafe partial class RedrawManager { public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, CharacterArmor* data); - [Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A")] - public Hook FlagSlotForUpdateHook = null!; + // This gets called when one of the ten equip items of an existing draw object gets changed. + [Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A", DetourName = nameof(FlagSlotForUpdateDetour))] + private readonly Hook? _flagSlotForUpdateHook; private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slot, CharacterArmor* data) { - return FlagSlotForUpdateHook.Original(drawObject, slot, data); + //try + //{ + // var actor = Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject); + // if (actor && CurrentManipulations.GetSave(actor, out _)) + // // TODO fixed design + // + // *data = CurrentManipulations.ChangeEquip(actor, slot.ToEquipSlot(), *data) ?? *data; + //} + //catch + //{ + // // ignored + //} + + return _flagSlotForUpdateHook!.Original(drawObject, slot, data); } - public delegate void LoadWeaponDelegate(IntPtr characterOffset, uint slot, ulong data, byte unk); - - [Signature("E8 ?? ?? ?? ?? 44 8B 9F")] - public Hook LoadWeaponHook = null!; - - private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong data, byte unk) + public bool ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data) { - const int offset = 0xD8 * 8; - PluginLog.Information($"0x{characterOffset:X}, 0x{characterOffset - offset:X}, {slot}, {data:16X}, {unk}"); - LoadWeaponHook.Original(characterOffset, slot, data, unk); + if (actor && CurrentManipulations.ChangeEquip(actor, slot, data).HasValue && actor.DrawObject != null) + return _flagSlotForUpdateHook?.Original(actor.DrawObject, slot.ToIndex(), &data) != 0; + + return false; + } +} + +public unsafe partial class RedrawManager +{ + // The character weapon object manipulated is inside the actual character. + public const int CharacterWeaponOffset = 0xD8 * 8; + + public delegate void LoadWeaponDelegate(IntPtr offsetCharacter, uint slot, CharacterWeapon weapon, byte unk1, byte unk2, byte unk3, + byte unk4); + + // Weapons for a specific character are reloaded with this function. + // The first argument is a pointer to the game object but shifted a bit inside. + // slot is 0 for main hand, 1 for offhand, 2 for unknown (always called with empty data. + // weapon argument is the new weapon data. + // unk1 seems to be 0 when re-equipping and 1 when redrawing the entire actor. + // unk2 seemed to always be 1. + // unk3 seemed to always be 0. + // unk4 seemed to be the same as unk1. + [Signature("E8 ?? ?? ?? ?? 44 8B 9F", DetourName = nameof(LoadWeaponDetour))] + private readonly Hook? _loadWeaponHook; + + private void LoadWeaponDetour(IntPtr characterOffset, uint slot, CharacterWeapon weapon, byte unk1, byte unk2, byte unk3, byte unk4) + { + _loadWeaponHook!.Original(characterOffset, slot, weapon, unk1, unk2, unk3, unk4); } - //[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A", - // DetourName = nameof(FlagSlotForUpdateDetour))] - //public Hook? FlagSlotForUpdateHook; - // - // public readonly FixedDesigns FixedDesigns; - // + // Load a specific weapon for a character by its data and slot. + public void LoadWeapon(IntPtr character, EquipSlot slot, CharacterWeapon weapon) + { + switch (slot) + { + case EquipSlot.MainHand: + LoadWeaponDetour(character + CharacterWeaponOffset, 0, weapon, 1, 1, 0, 1); + return; + case EquipSlot.OffHand: + LoadWeaponDetour(character + CharacterWeaponOffset, 1, weapon, 1, 1, 0, 1); + return; + case EquipSlot.BothHand: + LoadWeaponDetour(character + CharacterWeaponOffset, 0, weapon, 1, 1, 0, 1); + LoadWeaponDetour(character + CharacterWeaponOffset, 1, CharacterWeapon.Empty, 1, 1, 0, 1); + return; + // function can also be called with '2', but does not seem to ever be. + } + } - private readonly Dictionary _currentRedraws = new(32); + public void LoadWeapon(Character* character, EquipSlot slot, CharacterWeapon weapon) + => LoadWeapon((IntPtr)character, slot, weapon); + + // Load specific Main- and Offhand weapons. + public void LoadWeapon(IntPtr character, CharacterWeapon main, CharacterWeapon off) + { + LoadWeaponDetour(character + CharacterWeaponOffset, 0, main, 1, 1, 0, 1); + LoadWeaponDetour(character + CharacterWeaponOffset, 1, off, 1, 1, 0, 1); + } + + public void LoadWeapon(Character* character, CharacterWeapon main, CharacterWeapon off) + => LoadWeapon((IntPtr)character, main, off); +} + +public unsafe partial class RedrawManager : IDisposable +{ + internal readonly CurrentManipulations CurrentManipulations = new(); public RedrawManager() { SignatureHelper.Initialise(this); -// FixedDesigns = new FixedDesigns(designs); Glamourer.Penumbra.CreatingCharacterBase += OnCharacterRedraw; - FlagSlotForUpdateHook.Enable(); - LoadWeaponHook.Enable(); -// -// if (Glamourer.Config.ApplyFixedDesigns) -// Enable(); + //_flagSlotForUpdateHook?.Enable(); + //_loadWeaponHook?.Enable(); } public void Dispose() { - FlagSlotForUpdateHook.Dispose(); - LoadWeaponHook.Dispose(); + _flagSlotForUpdateHook?.Dispose(); + _loadWeaponHook?.Dispose(); Glamourer.Penumbra.CreatingCharacterBase -= OnCharacterRedraw; - //FlagSlotForUpdateHook?.Dispose(); } - public void Set(Character* actor, CharacterSave save) - { - var name = GetName(actor); - if (name.Length == 0) - return; - - _currentRedraws[name] = save; - } - - public void Set(IntPtr actor, CharacterSave save) - => Set((Character*)actor, save); - - public void Revert(Character* actor) - => _currentRedraws.Remove(GetName(actor)); - - public void Revert(IntPtr actor) - => Revert((Character*)actor); - - private static string GetName(Character* actor) - { - return string.Concat(new Utf8String(actor->GameObject.Name) - .Select(c => (char)c) - .Append(actor->GameObject.ObjectKind == (byte)ObjectKind.Pc ? (char)actor->HomeWorld : (char)actor->GameObject.ObjectIndex)); - } - - private void Cleanup(object? _, ushort _1) - => _currentRedraws.Clear(); - - public void ChangeEquip(Human* actor, EquipSlot slot, CharacterArmor item) - => Flag(actor, slot.ToIndex(), &item); - - public void ChangeEquip(Character* character, EquipSlot slot, CharacterArmor item) - => ChangeEquip((Human*)character->GameObject.DrawObject, slot, item); - - public void ChangeEquip(IntPtr character, EquipSlot slot, CharacterArmor item) - => ChangeEquip((Character*)character, slot, item); - private void OnCharacterRedraw(IntPtr addr, IntPtr modelId, IntPtr customize, IntPtr equipData) { - var name = GetName((Character*)addr); - if (_currentRedraws.TryGetValue(name, out var save)) - { - *(CustomizationData*)customize = *(CustomizationData*)save.Customize.Address; - var equip = (CharacterEquip)equipData; - var newEquip = save.Equipment; - for (var i = 0; i < 10; ++i) - equip[i] = newEquip[i]; - } - - - //*(uint*)modelId = 0; - - //var human = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)addr; - //if (human->GameObject.ObjectKind is (byte)ObjectKind.EventNpc or (byte)ObjectKind.BattleNpc or (byte)ObjectKind.Player - // && human->ModelCharaId == 0) + //if (CurrentManipulations.GetSave(addr, out var save)) //{ - // var name = new Utf8String(human->GameObject.Name).ToString(); - // if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs)) - // { - // var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(human->ClassJob)); - // if (design != null) - // { - // if (design.Design.Data.WriteCustomizations) - // *(CharacterCustomization*)customize = design.Design.Data.Customizations; - // - // var data = (uint*)equipData; - // for (var i = 0u; i < 10; ++i) - // { - // var slot = i.ToEquipSlot(); - // if (design.Design.Data.WriteEquipment.Fits(slot)) - // data[i] = slot switch - // { - // EquipSlot.Head => design.Design.Data.Equipment.Head.Value, - // EquipSlot.Body => design.Design.Data.Equipment.Body.Value, - // EquipSlot.Hands => design.Design.Data.Equipment.Hands.Value, - // EquipSlot.Legs => design.Design.Data.Equipment.Legs.Value, - // EquipSlot.Feet => design.Design.Data.Equipment.Feet.Value, - // EquipSlot.Ears => design.Design.Data.Equipment.Ears.Value, - // EquipSlot.Neck => design.Design.Data.Equipment.Neck.Value, - // EquipSlot.Wrists => design.Design.Data.Equipment.Wrists.Value, - // EquipSlot.RFinger => design.Design.Data.Equipment.RFinger.Value, - // EquipSlot.LFinger => design.Design.Data.Equipment.LFinger.Value, - // _ => 0, - // }; - // } - // } - // } + // *(CustomizationData*)customize = *(CustomizationData*)save.Customization.Address; + // var equip = (CharacterEquip)equipData; + // var newEquip = save.Equipment; + // for (var i = 0; i < 10; ++i) + // equip[i] = newEquip[i]; //} } -// -// private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, uint* data) -// { -// ulong ret; -// var slot = slotIdx.ToEquipSlot(); -// try -// { -// if (slot != EquipSlot.Unknown) -// { -// var gameObject = -// (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject); -// if (gameObject != null) -// { -// var name = new Utf8String(gameObject->GameObject.Name).ToString(); -// if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs)) -// { -// var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(gameObject->ClassJob)); -// if (design != null && design.Design.Data.WriteEquipment.Fits(slot)) -// *data = slot switch -// { -// EquipSlot.Head => design.Design.Data.Equipment.Head.Value, -// EquipSlot.Body => design.Design.Data.Equipment.Body.Value, -// EquipSlot.Hands => design.Design.Data.Equipment.Hands.Value, -// EquipSlot.Legs => design.Design.Data.Equipment.Legs.Value, -// EquipSlot.Feet => design.Design.Data.Equipment.Feet.Value, -// EquipSlot.Ears => design.Design.Data.Equipment.Ears.Value, -// EquipSlot.Neck => design.Design.Data.Equipment.Neck.Value, -// EquipSlot.Wrists => design.Design.Data.Equipment.Wrists.Value, -// EquipSlot.RFinger => design.Design.Data.Equipment.RFinger.Value, -// EquipSlot.LFinger => design.Design.Data.Equipment.LFinger.Value, -// _ => 0, -// }; -// } -// } -// } -// } -// finally -// { -// ret = FlagSlotForUpdateHook!.Original(drawObject, slotIdx, data); -// } -// -// return ret; -// } -// -// public void UpdateSlot(Human* drawObject, EquipSlot slot, CharacterArmor data) -// { -// var idx = slot.ToIndex(); -// if (idx >= 10) -// return; -// -// FlagSlotForUpdateDetour(drawObject, idx, (uint*)&data); -// } -} +} \ No newline at end of file