diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index cb7fe51..41f7650 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -14,7 +14,6 @@ using StateChanged = Glamourer.Events.StateChanged; using StateUpdated = Glamourer.Events.StateUpdated; namespace Glamourer.Api; - public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable { private readonly ApiHelpers _helpers; @@ -340,7 +339,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5) { - // Glamourer.Log.Verbose($"[OnStateChanged] Sending out OnStateChanged with type {type}."); + Glamourer.Log.Excessive($"[OnStateChanged] State Changed with Type {type} [Affecting {actors.ToLazyString("nothing")}.]"); if (StateChanged != null) foreach (var actor in actors.Objects) StateChanged.Invoke(actor.Address); @@ -352,7 +351,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnStateUpdated(StateUpdateType type, ActorData actors) { - // Glamourer.Log.Verbose($"[OnStateUpdated] Sending out OnStateUpdated with type {type}."); + Glamourer.Log.Verbose($"[OnStateUpdated] State Updated with Type {type}. [Affecting {actors.ToLazyString("nothing")}.]"); if (StateUpdated != null) foreach (var actor in actors.Objects) StateUpdated.Invoke(actor.Address, type); diff --git a/Glamourer/Events/GearsetDataLoaded.cs b/Glamourer/Events/GearsetDataLoaded.cs index dd12bc1..4750939 100644 --- a/Glamourer/Events/GearsetDataLoaded.cs +++ b/Glamourer/Events/GearsetDataLoaded.cs @@ -4,10 +4,10 @@ using Penumbra.GameData.Interop; namespace Glamourer.Events; /// -/// Triggers when the equipped gearset finished running all of its LoadEquipment, LoadWeapon, and crest calls. -/// This defines a universal endpoint of base game state application to monitor. +/// Triggers when the equipped gearset finished all LoadEquipment, LoadWeapon, and LoadCrest calls. (All Non-MetaData) +/// This defines an endpoint for when the gameState is updated. /// -/// The model drawobject associated with the finished load (Also fired by other players on render) +/// The model draw object associated with the finished load (Also fired by other players on render) /// /// public sealed class GearsetDataLoaded() @@ -18,4 +18,4 @@ public sealed class GearsetDataLoaded() /// StateListener = 0, } -} \ No newline at end of file +} diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index f0ed6b5..4b98d46 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -23,34 +23,26 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); - // This can be uncommented after ClientStructs Updates with EquipGearsetInternal after merged PR. (See comment below) - //_equipGearsetInternalHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetInternalDetour); - - // Can be removed after ClientStructs Update since this is only needed for current EquipGearsetInternal [Signature] - interop.InitializeFromAttributes(this); + _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetDetour); _moveItemHook.Enable(); - _equipGearsetInternalHook.Enable(); + _equipGearsetHook.Enable(); } public void Dispose() { _moveItemHook.Dispose(); - _equipGearsetInternalHook.Dispose(); + _equipGearsetHook.Dispose(); } - // This is the internal function processed by all sources of Equipping a gearset, such as hotbar gearset application and command gearset application. - // Currently is pending to ClientStructs for integration. See: https://github.com/aers/FFXIVClientStructs/pull/1277 - public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; private delegate nint EquipGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); - [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] - private readonly Hook _equipGearsetInternalHook = null!; + private readonly Hook _equipGearsetHook = null!; - private nint EquipGearSetInternalDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) + private nint EquipGearSetDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) { var prior = module->CurrentGearsetIndex; - var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId); + var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); var set = module->GetGearset((int)gearsetId); _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index e453c6e..466f1ae 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Network; using Glamourer.Events; using Penumbra.GameData; using Penumbra.GameData.DataContainers; @@ -13,22 +14,20 @@ namespace Glamourer.Interop; public unsafe class UpdateSlotService : IDisposable { - public readonly EquipSlotUpdating EquipSlotUpdatingEvent; - public readonly BonusSlotUpdating BonusSlotUpdatingEvent; - public readonly GearsetDataLoaded GearsetDataLoadedEvent; - private readonly DictBonusItems _bonusItems; + public readonly EquipSlotUpdating EquipSlotUpdatingEvent; + public readonly BonusSlotUpdating BonusSlotUpdatingEvent; + public readonly GearsetDataLoaded GearsetDataLoadedEvent; + private readonly DictBonusItems _bonusItems; public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, GearsetDataLoaded gearsetDataLoaded, IGameInteropProvider interop, DictBonusItems bonusItems) { EquipSlotUpdatingEvent = equipSlotUpdating; BonusSlotUpdatingEvent = bonusSlotUpdating; GearsetDataLoadedEvent = gearsetDataLoaded; - _bonusItems = bonusItems; + _bonusItems = bonusItems; - // Usable after the merge with client structs. - //_loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); + _loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); interop.InitializeFromAttributes(this); - _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); _loadGearsetDataHook.Enable(); @@ -87,25 +86,15 @@ public unsafe class UpdateSlotService : IDisposable [Signature(Sigs.FlagBonusSlotForUpdate, DetourName = nameof(FlagBonusSlotForUpdateDetour))] private readonly Hook _flagBonusSlotForUpdateHook = null!; - // This signature is what calls the weapon/equipment/crest load functions in the drawData container inherited from a human/characterBase. - // - // Contrary to assumption, this is not frequently fired when any slot changes, and is instead only called when another player - // initially loads, or when the client player changes gearsets. (Does not fire when another player or self is redrawn) - // - // This functions purpose is to iterate all Equipment/Weapon/Crest data on gearset change / initial player load, and determine which slots need to fire FlagSlotForUpdate. - // - // Because Glamourer processes GameState changes by detouring this method, this means by returning original after detour, any logic performed after will occur - // AFTER Glamourer finishes applying all changes to the game State, providing a gearset endpoint. (MetaData not included) - // Currently pending a merge to clientStructs, after which it can be removed, along with the explicit struct. See: https://github.com/aers/FFXIVClientStructs/pull/1277/files - public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; - private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetDataStruct* gearsetData); - - [Signature(LoadGearsetDataSig, DetourName = nameof(LoadGearsetDataDetour))] + /// Detours the func that makes all FlagSlotForUpdate calls on a gearset change or initial render of a given actor (Only Cases this is Called). + /// Logic done after returning the original hook executes After all equipment/weapon/crest data is loaded into the Actors BaseData. + /// + private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData); private readonly Hook _loadGearsetDataHook = null!; private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToEquipSlot(); + var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); @@ -114,7 +103,7 @@ public unsafe class UpdateSlotService : IDisposable private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToBonusSlot(); + var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); @@ -123,29 +112,27 @@ public unsafe class UpdateSlotService : IDisposable private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor) { - Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Invoked by Glamourer on 0x{drawObject.Address:X} on {slot} with itemdata {armor}."); + Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Glamourer-Invoked on 0x{drawObject.Address:X} on {slot} with item data {armor}."); return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } - private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetDataStruct* gearsetData) + private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData) { - // Let the gearset data process all of its loads and slot flag update calls first. var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); Model drawObject = drawDataContainer->OwnerObject->DrawObject; - Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{drawObject.Address:X} Finished Applying its GameState!"); GearsetDataLoadedEvent.Invoke(drawObject); - // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); + // Glamourer.Log.Excessive($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); return ret; } // If you ever care to debug this, here is a formatted string output of this new gearsetData struct. - private string FormatGearsetItemDataStruct(GearsetDataStruct gearsetData) + private string FormatGearsetItemDataStruct(PacketPlayerGearsetData gearsetData) { string ret = $"\nMainhandWeaponData: Id: {gearsetData.MainhandWeaponData.Id}, Type: {gearsetData.MainhandWeaponData.Type}, " + $"Variant: {gearsetData.MainhandWeaponData.Variant}, Stain0: {gearsetData.MainhandWeaponData.Stain0}, Stain1: {gearsetData.MainhandWeaponData.Stain1}" + $"\nOffhandWeaponData: Id: {gearsetData.OffhandWeaponData.Id}, Type: {gearsetData.OffhandWeaponData.Type}, " + $"Variant: {gearsetData.OffhandWeaponData.Variant}, Stain0: {gearsetData.OffhandWeaponData.Stain0}, Stain1: {gearsetData.OffhandWeaponData.Stain1}" + - $"\nCrestBitField: {gearsetData.CrestBitField} | JobId: {gearsetData.JobId} | UNK_18: {gearsetData.UNK_18} | UNK_19: {gearsetData.UNK_19}"; + $"\nCrestBitField: {gearsetData.CrestBitField} | JobId: {gearsetData.JobId}"; for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) { LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetData + offset); @@ -156,43 +143,3 @@ public unsafe class UpdateSlotService : IDisposable return ret; } } - -// Can be removed once merged with client structs and referenced directly. See: https://github.com/aers/FFXIVClientStructs/pull/1277/files -[StructLayout(LayoutKind.Explicit)] -public readonly struct GearsetDataStruct -{ - // Stores the weapon data. Includes both dyes in the data. - [FieldOffset(0)] public readonly WeaponModelId MainhandWeaponData; - [FieldOffset(8)] public readonly WeaponModelId OffhandWeaponData; - - [FieldOffset(16)] public readonly byte CrestBitField; // A Bitfield:: ShieldCrest == 1, HeadCrest == 2, Chest Crest == 4 - [FieldOffset(17)] public readonly byte JobId; // Job ID associated with the gearset change. - - // Flicks from 0 to 127 (anywhere inbetween), have yet to associate what it is linked to. Remains the same when flicking between gearsets of the same job. - [FieldOffset(18)] public readonly byte UNK_18; - [FieldOffset(19)] public readonly byte UNK_19; // I have never seen this be anything other than 0. - - // Legacy helmet equip slot armor for a character. - [FieldOffset(20)] public readonly LegacyCharacterArmor HeadSlotArmor; - [FieldOffset(24)] public readonly LegacyCharacterArmor TopSlotArmor; - [FieldOffset(28)] public readonly LegacyCharacterArmor ArmsSlotArmor; - [FieldOffset(32)] public readonly LegacyCharacterArmor LegsSlotArmor; - [FieldOffset(26)] public readonly LegacyCharacterArmor FeetSlotArmor; - [FieldOffset(40)] public readonly LegacyCharacterArmor EarSlotArmor; - [FieldOffset(44)] public readonly LegacyCharacterArmor NeckSlotArmor; - [FieldOffset(48)] public readonly LegacyCharacterArmor WristSlotArmor; - [FieldOffset(52)] public readonly LegacyCharacterArmor RFingerSlotArmor; - [FieldOffset(56)] public readonly LegacyCharacterArmor LFingerSlotArmor; - - // Byte array of all slot's secondary dyes. - [FieldOffset(60)] public readonly byte HeadSlotSecondaryDye; - [FieldOffset(61)] public readonly byte TopSlotSecondaryDye; - [FieldOffset(62)] public readonly byte ArmsSlotSecondaryDye; - [FieldOffset(63)] public readonly byte LegsSlotSecondaryDye; - [FieldOffset(64)] public readonly byte FeetSlotSecondaryDye; - [FieldOffset(65)] public readonly byte EarSlotSecondaryDye; - [FieldOffset(66)] public readonly byte NeckSlotSecondaryDye; - [FieldOffset(67)] public readonly byte WristSlotSecondaryDye; - [FieldOffset(68)] public readonly byte RFingerSlotSecondaryDye; - [FieldOffset(69)] public readonly byte LFingerSlotSecondaryDye; -} diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index b122352..13b0706 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -416,7 +416,7 @@ public class StateEditor( var actors = settings.Source.RequiresChange() ? Applier.ApplyAll(state, requiresRedraw, false) : ActorData.Invalid; - + Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index d570805..c4c16b5 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -225,7 +225,8 @@ public class StateListener : IDisposable // then we do not want to use our restricted gear protection // since we assume the player has that gear modded to availability. var locked = false; - if (actor.Identifier(_actors, out var identifier) && _manager.TryGetValue(identifier, out var state)) + if (actor.Identifier(_actors, out var identifier) + && _manager.TryGetValue(identifier, out var state)) { HandleEquipSlot(actor, state, slot, ref armor); locked = state.Sources[slot, false] is StateSource.IpcFixed;