diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 248e294..34f4ad9 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -54,6 +54,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public void Dispose() { _stateChanged.Unsubscribe(OnStateChanged); + _stateUpdated.Unsubscribe(OnStateUpdated); _gPose.Unsubscribe(OnGPoseChange); } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 660acf4..1655c15 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -163,7 +163,7 @@ public sealed class AutoDesignApplier : IDisposable { Reduce(data.Objects[0], state, newSet, _config.RespectManualOnAutomationUpdate, false, true, out var forcedRedraw); foreach (var actor in data.Objects) - _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); + _state.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Fixed); } } else if (_objects.TryGetValueAllWorld(id, out data) || _objects.TryGetValueNonOwned(id, out data)) @@ -174,7 +174,7 @@ public sealed class AutoDesignApplier : IDisposable if (_state.GetOrCreate(specificId, actor, out var state)) { Reduce(actor, state, newSet, _config.RespectManualOnAutomationUpdate, false, true, out var forcedRedraw); - _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); + _state.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Fixed); } } } diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 33ba5cf..6c4a223 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -13,18 +13,6 @@ namespace Glamourer.Interop; public sealed unsafe class InventoryService : IDisposable, IRequiredService { - // Called by EquipGearset, but returns a pointer instead of an int. - // This is the internal function processed by all sources of Equipping a gearset, - // such as hotbar gearset application and command gearset application - 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 ChangeGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); - - [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] - private readonly Hook _equipGearsetInternalHook = null!; - - // The following above is currently pending for an accepted PR in FFXIVCLientStructs. - // Once accepted, remove everything above this comment and replace EquipGearset with EquipGearsetInternal. - private readonly MovedEquipment _movedItemsEvent; private readonly EquippedGearset _gearsetEvent; private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); @@ -34,24 +22,29 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); - _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour); + // 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); _moveItemHook.Enable(); - _equipGearsetHook.Enable(); _equipGearsetInternalHook.Enable(); } public void Dispose() { _moveItemHook.Dispose(); - _equipGearsetHook.Dispose(); _equipGearsetInternalHook.Dispose(); } - private delegate int EquipGearsetDelegate(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId); + // 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); - private readonly Hook _equipGearsetHook; + [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] + private readonly Hook _equipGearsetInternalHook = null!; private nint EquipGearSetInternalDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) { @@ -132,14 +125,6 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService return ret; } - // Remove once internal is added. This no longer serves any purpose. - private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) - { - var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); - Glamourer.Log.Excessive($"[InventoryService] (old) Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); - return ret; - } - private static uint FixId(uint itemId) => itemId % 50000; diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 9ee8d8f..5f6e9cb 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -11,9 +11,9 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop; -// This struct is implemented into a PR for FFXIVClientStructs. Once merged, remove this struct and reference the data in ClientStructs instead. +// 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 GearsetItemDataStruct +public readonly struct GearsetDataStruct { // Stores the weapon data. Includes both dyes in the data. [FieldOffset(0)] public readonly WeaponModelId MainhandWeaponData; @@ -53,11 +53,6 @@ public readonly struct GearsetItemDataStruct public unsafe class UpdateSlotService : IDisposable { - // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. (MetaData not included) - 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, GearsetItemDataStruct* gearsetData); - // The above can be removed after the FFXIVClientStruct Merge is made! - public readonly EquipSlotUpdating EquipSlotUpdatingEvent; public readonly BonusSlotUpdating BonusSlotUpdatingEvent; public readonly GearsetDataLoaded GearsetDataLoadedEvent; @@ -68,9 +63,12 @@ public unsafe class UpdateSlotService : IDisposable EquipSlotUpdatingEvent = equipSlotUpdating; BonusSlotUpdatingEvent = bonusSlotUpdating; GearsetDataLoadedEvent = gearsetDataLoaded; - _bonusItems = bonusItems; + + // Usable after the merge with client structs. + //_loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); interop.InitializeFromAttributes(this); + _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); _loadGearsetDataHook.Enable(); @@ -129,6 +127,19 @@ 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))] private readonly Hook _loadGearsetDataHook = null!; @@ -157,23 +168,20 @@ public unsafe class UpdateSlotService : IDisposable Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Invoked by Glamourer on 0x{drawObject.Address:X} on {slot} with itemdata {armor}."); return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } - private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) + private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetDataStruct* gearsetData) { // Let the gearset data process all of its loads and slot flag update calls first. var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); - Model ownerDrawObject = drawDataContainer->OwnerObject->DrawObject; - if (!ownerDrawObject.IsCharacterBase) - return ret; - - // invoke the changed event for the state listener and return. - Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{ownerDrawObject.Address:X} Finished Applying its GameState!"); + Model drawObject = drawDataContainer->OwnerObject->DrawObject; + Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{drawObject.Address:X} Finished Applying its GameState!"); + GearsetDataLoadedEvent.Invoke(drawObject); + // Can use for debugging, if desired. // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); - GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject->DrawObject); return ret; } - // If you ever care to debug this, here is a formatted string output of this new gearsetDataPacket struct. - private string FormatGearsetItemDataStruct(GearsetItemDataStruct gearsetData) + // If you ever care to debug this, here is a formatted string output of this new gearsetData struct. + private string FormatGearsetItemDataStruct(GearsetDataStruct gearsetData) { string ret = $"\nMainhandWeaponData: Id: {gearsetData.MainhandWeaponData.Id}, Type: {gearsetData.MainhandWeaponData.Type}, " +