Updated new functionality and internal gearset to use FFXIVClientStructs member functions and implemented structs, corrected remaining spacing issues.

This commit is contained in:
Cordelia Mist 2025-01-21 10:47:12 -08:00
parent 2e11481276
commit c43ce9d978
6 changed files with 33 additions and 94 deletions

View file

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

View file

@ -4,10 +4,10 @@ using Penumbra.GameData.Interop;
namespace Glamourer.Events;
/// <summary>
/// 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.
/// <list type="number">
/// <item>The model drawobject associated with the finished load (Also fired by other players on render) </item>
/// <item>The model draw object associated with the finished load (Also fired by other players on render) </item>
/// </list>
/// </summary>
public sealed class GearsetDataLoaded()
@ -18,4 +18,4 @@ public sealed class GearsetDataLoaded()
/// <seealso cref="State.StateListener.OnGearsetDataLoaded"/>
StateListener = 0,
}
}
}

View file

@ -23,34 +23,26 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService
_gearsetEvent = gearsetEvent;
_moveItemHook = interop.HookFromAddress<MoveItemDelegate>((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour);
// 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);
_equipGearsetHook = interop.HookFromAddress<EquipGearsetInternalDelegate>((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<EquipGearsetInternalDelegate> _equipGearsetInternalHook = null!;
private readonly Hook<EquipGearsetInternalDelegate> _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})");

View file

@ -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<LoadGearsetDataDelegate>((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour);
_loadGearsetDataHook = interop.HookFromAddress<LoadGearsetDataDelegate>((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<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))]
/// <summary> Detours the func that makes all FlagSlotForUpdate calls on a gearset change or initial render of a given actor (Only Cases this is Called).
/// <para> Logic done after returning the original hook executes <b>After</b> all equipment/weapon/crest data is loaded into the Actors BaseData. </para>
/// </summary>
private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData);
private readonly Hook<LoadGearsetDataDelegate> _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. </summary>
[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;
}

View file

@ -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

View file

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