Added Proper Unsubsribe from OnStateUpdated.

Ensures that ReapplyAutomation is called from OnAutomationChange when change occurs.
Reformatted temporary Signature locations and comments to align with the structure of the respective classes.
This commit is contained in:
Cordelia Mist 2025-01-19 09:07:43 -08:00
parent 9c57935a87
commit 1d185e9bfe
4 changed files with 39 additions and 45 deletions

View file

@ -54,6 +54,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
public void Dispose()
{
_stateChanged.Unsubscribe(OnStateChanged);
_stateUpdated.Unsubscribe(OnStateUpdated);
_gPose.Unsubscribe(OnGPoseChange);
}

View file

@ -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);
}
}
}

View file

@ -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<ChangeGearsetInternalDelegate> _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<MoveItemDelegate>((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour);
_equipGearsetHook = interop.HookFromAddress<EquipGearsetDelegate>((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour);
// This can be uncommented after ClientStructs Updates with EquipGearsetInternal after merged PR. (See comment below)
//_equipGearsetInternalHook = interop.HookFromAddress<EquipGearsetDelegate>((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<EquipGearsetDelegate> _equipGearsetHook;
[Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))]
private readonly Hook<EquipGearsetInternalDelegate> _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;

View file

@ -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. </summary>
[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<LoadGearsetDataDelegate>((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<FlagSlotForUpdateDelegateIntern> _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<LoadGearsetDataDelegate> _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}, " +