From 0b6f3b8bcfaf9f4ab8fb1713097b9930319bd27f Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Wed, 1 Oct 2025 15:30:29 +0200 Subject: [PATCH 1/3] Add events based on AgentUpdateFlag --- Dalamud/Game/Gui/GameGui.cs | 75 +++++++++++++++++++++++++ Dalamud/Game/Inventory/GameInventory.cs | 25 +++------ Dalamud/Plugin/Services/IGameGui.cs | 21 +++++++ 3 files changed, 103 insertions(+), 18 deletions(-) diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 4da7b6857..51ab6b1d2 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -43,6 +43,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui private readonly Hook handleImmHook; private readonly Hook setUiVisibilityHook; private readonly Hook utf8StringFromSequenceHook; + private readonly Hook raptureAtkModuleUpdateHook; [ServiceManager.ServiceConstructor] private GameGui(TargetSigScanner sigScanner) @@ -65,6 +66,10 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui this.utf8StringFromSequenceHook = Hook.FromAddress(Utf8String.Addresses.Ctor_FromSequence.Value, this.Utf8StringFromSequenceDetour); + this.raptureAtkModuleUpdateHook = Hook.FromFunctionPointerVariable( + new(&RaptureAtkModule.StaticVirtualTablePointer->Update), + this.RaptureAtkModuleUpdateDetour); + this.handleItemHoverHook.Enable(); this.handleItemOutHook.Enable(); this.handleImmHook.Enable(); @@ -72,6 +77,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui this.handleActionHoverHook.Enable(); this.handleActionOutHook.Enable(); this.utf8StringFromSequenceHook.Enable(); + this.raptureAtkModuleUpdateHook.Enable(); } // Hooked delegates @@ -88,6 +94,18 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui /// public event EventHandler? HoveredActionChanged; + /// + public event Action InventoryUpdate; + + /// + public event Action ActionBarUpdate; + + /// + public event Action UnlocksUpdate; + + /// + public event Action MainCommandEnabledStateUpdate; + /// public bool GameUiHidden { get; private set; } @@ -238,6 +256,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui this.handleActionHoverHook.Dispose(); this.handleActionOutHook.Dispose(); this.utf8StringFromSequenceHook.Dispose(); + this.raptureAtkModuleUpdateHook.Dispose(); } /// @@ -362,6 +381,34 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui return thisPtr; // this function shouldn't need to return but the original asm moves this into rax before returning so be safe? } + + private void RaptureAtkModuleUpdateDetour(RaptureAtkModule* thisPtr, float delta) + { + // The game clears the AgentUpdateFlag in the original function, but it also updates agents in it too. + // We'll make a copy of the flags, so that we can fire events after the agents have been updated. + + var agentUpdateFlag = thisPtr->AgentUpdateFlag; + + this.raptureAtkModuleUpdateHook.Original(thisPtr, delta); + + if (agentUpdateFlag != RaptureAtkModule.AgentUpdateFlags.None) + { + if (agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.InventoryUpdate) || + agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.RetainerMarketInventoryUpdate) || + agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.HousingInventoryUpdate) || + agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.ContentInventoryUpdate)) + this.InventoryUpdate.InvokeSafely(); + + if (agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.ActionBarUpdate)) + this.ActionBarUpdate.InvokeSafely(); + + if (agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.UnlocksUpdate)) + this.UnlocksUpdate.InvokeSafely(); + + if (agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.MainCommandEnabledStateUpdate)) + this.MainCommandEnabledStateUpdate.InvokeSafely(); + } + } } /// @@ -385,6 +432,10 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui this.gameGuiService.UiHideToggled += this.UiHideToggledForward; this.gameGuiService.HoveredItemChanged += this.HoveredItemForward; this.gameGuiService.HoveredActionChanged += this.HoveredActionForward; + this.gameGuiService.InventoryUpdate += this.InventoryUpdateForward; + this.gameGuiService.ActionBarUpdate += this.ActionBarUpdateForward; + this.gameGuiService.UnlocksUpdate += this.UnlocksUpdateForward; + this.gameGuiService.MainCommandEnabledStateUpdate += this.MainCommandEnabledStateUpdateForward; } /// @@ -396,6 +447,18 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui /// public event EventHandler? HoveredActionChanged; + /// + public event Action InventoryUpdate; + + /// + public event Action ActionBarUpdate; + + /// + public event Action UnlocksUpdate; + + /// + public event Action MainCommandEnabledStateUpdate; + /// public bool GameUiHidden => this.gameGuiService.GameUiHidden; @@ -415,6 +478,10 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui this.gameGuiService.UiHideToggled -= this.UiHideToggledForward; this.gameGuiService.HoveredItemChanged -= this.HoveredItemForward; this.gameGuiService.HoveredActionChanged -= this.HoveredActionForward; + this.gameGuiService.InventoryUpdate -= this.InventoryUpdateForward; + this.gameGuiService.ActionBarUpdate -= this.ActionBarUpdateForward; + this.gameGuiService.UnlocksUpdate -= this.UnlocksUpdateForward; + this.gameGuiService.MainCommandEnabledStateUpdate -= this.MainCommandEnabledStateUpdateForward; this.UiHideToggled = null; this.HoveredItemChanged = null; @@ -466,4 +533,12 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui private void HoveredItemForward(object sender, ulong itemId) => this.HoveredItemChanged?.Invoke(sender, itemId); private void HoveredActionForward(object sender, HoveredAction hoverAction) => this.HoveredActionChanged?.Invoke(sender, hoverAction); + + private void InventoryUpdateForward() => this.InventoryUpdate.InvokeSafely(); + + private void ActionBarUpdateForward() => this.ActionBarUpdate.InvokeSafely(); + + private void UnlocksUpdateForward() => this.UnlocksUpdate.InvokeSafely(); + + private void MainCommandEnabledStateUpdateForward() => this.MainCommandEnabledStateUpdate.InvokeSafely(); } diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs index 5c8ed27b0..e2e3c1ec2 100644 --- a/Dalamud/Game/Inventory/GameInventory.cs +++ b/Dalamud/Game/Inventory/GameInventory.cs @@ -2,16 +2,14 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using Dalamud.Game.Gui; using Dalamud.Game.Inventory.InventoryEventArgTypes; -using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal; using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Client.UI; - namespace Dalamud.Game.Inventory; /// @@ -33,7 +31,8 @@ internal class GameInventory : IInternalDisposableService [ServiceManager.ServiceDependency] private readonly Framework framework = Service.Get(); - private readonly Hook raptureAtkModuleUpdateHook; + [ServiceManager.ServiceDependency] + private readonly GameGui gameGui = Service.Get(); private readonly GameInventoryType[] inventoryTypes; private readonly GameInventoryItem[]?[] inventoryItems; @@ -47,18 +46,9 @@ internal class GameInventory : IInternalDisposableService this.inventoryTypes = Enum.GetValues(); this.inventoryItems = new GameInventoryItem[this.inventoryTypes.Length][]; - unsafe - { - this.raptureAtkModuleUpdateHook = Hook.FromFunctionPointerVariable( - new(&RaptureAtkModule.StaticVirtualTablePointer->Update), - this.RaptureAtkModuleUpdateDetour); - } - - this.raptureAtkModuleUpdateHook.Enable(); + this.gameGui.InventoryUpdate += this.OnInventoryUpdate; } - private unsafe delegate void RaptureAtkModuleUpdateDelegate(RaptureAtkModule* ram, float f1); - /// void IInternalDisposableService.DisposeService() { @@ -68,7 +58,7 @@ internal class GameInventory : IInternalDisposableService this.subscribersPendingChange.Clear(); this.subscribersChanged = false; this.framework.Update -= this.OnFrameworkUpdate; - this.raptureAtkModuleUpdateHook.Dispose(); + this.gameGui.InventoryUpdate -= this.OnInventoryUpdate; } } @@ -319,10 +309,9 @@ internal class GameInventory : IInternalDisposableService return items; } - private unsafe void RaptureAtkModuleUpdateDetour(RaptureAtkModule* ram, float f1) + private void OnInventoryUpdate() { - this.inventoriesMightBeChanged |= ram->AgentUpdateFlag != 0; - this.raptureAtkModuleUpdateHook.Original(ram, f1); + this.inventoriesMightBeChanged |= true; } /// diff --git a/Dalamud/Plugin/Services/IGameGui.cs b/Dalamud/Plugin/Services/IGameGui.cs index e2326328a..fc37c14b7 100644 --- a/Dalamud/Plugin/Services/IGameGui.cs +++ b/Dalamud/Plugin/Services/IGameGui.cs @@ -26,6 +26,27 @@ public unsafe interface IGameGui /// public event EventHandler HoveredActionChanged; + /// + /// Event that is fired when the inventory has been updated. + /// + event Action InventoryUpdate; + + /// + /// Fired when the action bar needs to be updated, e.g. after changing Class/Job, + /// updating Gear Sets, modifying Macros, or executing a hotbar slot. + /// + event Action ActionBarUpdate; + + /// + /// Event that is fired when collectibles, content or systems were unlocked. + /// + event Action UnlocksUpdate; + + /// + /// Event that is fired when the enable state of MainCommands has been updated. + /// + event Action MainCommandEnabledStateUpdate; + /// /// Gets a value indicating whether the game UI is hidden. /// From 8fd49f261ade0e811e02bb8002cbfe17d3f68ff9 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sat, 8 Nov 2025 11:31:10 +0100 Subject: [PATCH 2/3] Unify agent update events into AgentUpdate --- Dalamud/Game/Gui/AgentUpdateFlag.cs | 34 +++++++++++++++ Dalamud/Game/Gui/GameGui.cs | 55 +++---------------------- Dalamud/Game/Inventory/GameInventory.cs | 6 +-- Dalamud/Plugin/Services/IGameGui.cs | 21 ++-------- 4 files changed, 46 insertions(+), 70 deletions(-) create mode 100644 Dalamud/Game/Gui/AgentUpdateFlag.cs diff --git a/Dalamud/Game/Gui/AgentUpdateFlag.cs b/Dalamud/Game/Gui/AgentUpdateFlag.cs new file mode 100644 index 000000000..1c99e104c --- /dev/null +++ b/Dalamud/Game/Gui/AgentUpdateFlag.cs @@ -0,0 +1,34 @@ +using FFXIVClientStructs.FFXIV.Client.UI.Agent; + +namespace Dalamud.Game.Gui; + +/// +/// Represents a flag set by the game used by agents to conditionally update their addons. +/// +[Flags] +public enum AgentUpdateFlag : byte +{ + /// Set when an inventory has been updated. + InventoryUpdate = 1 << 0, + + /// Set when a hotbar slot has been executed, or a Gearset or Macro has been changed. + ActionBarUpdate = 1 << 1, + + /// Set when the RetainerMarket inventory has been updated. + RetainerMarketInventoryUpdate = 1 << 2, + + /// Unknown use case. + NameplateUpdate = 1 << 3, + + /// Set when the player unlocked collectibles, contents or systems. + UnlocksUpdate = 1 << 4, + + /// Set when was called. + MainCommandEnabledStateUpdate = 1 << 5, + + /// Set when any housing inventory has been updated. + HousingInventoryUpdate = 1 << 6, + + /// Set when any content inventory has been updated. + ContentInventoryUpdate = 1 << 7, +} diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 51ab6b1d2..1d887055e 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -95,16 +95,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui public event EventHandler? HoveredActionChanged; /// - public event Action InventoryUpdate; - - /// - public event Action ActionBarUpdate; - - /// - public event Action UnlocksUpdate; - - /// - public event Action MainCommandEnabledStateUpdate; + public event Action AgentUpdate; /// public bool GameUiHidden { get; private set; } @@ -393,20 +384,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui if (agentUpdateFlag != RaptureAtkModule.AgentUpdateFlags.None) { - if (agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.InventoryUpdate) || - agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.RetainerMarketInventoryUpdate) || - agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.HousingInventoryUpdate) || - agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.ContentInventoryUpdate)) - this.InventoryUpdate.InvokeSafely(); - - if (agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.ActionBarUpdate)) - this.ActionBarUpdate.InvokeSafely(); - - if (agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.UnlocksUpdate)) - this.UnlocksUpdate.InvokeSafely(); - - if (agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.MainCommandEnabledStateUpdate)) - this.MainCommandEnabledStateUpdate.InvokeSafely(); + this.AgentUpdate.InvokeSafely((AgentUpdateFlag)agentUpdateFlag); } } } @@ -432,10 +410,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui this.gameGuiService.UiHideToggled += this.UiHideToggledForward; this.gameGuiService.HoveredItemChanged += this.HoveredItemForward; this.gameGuiService.HoveredActionChanged += this.HoveredActionForward; - this.gameGuiService.InventoryUpdate += this.InventoryUpdateForward; - this.gameGuiService.ActionBarUpdate += this.ActionBarUpdateForward; - this.gameGuiService.UnlocksUpdate += this.UnlocksUpdateForward; - this.gameGuiService.MainCommandEnabledStateUpdate += this.MainCommandEnabledStateUpdateForward; + this.gameGuiService.AgentUpdate += this.AgentUpdateForward; } /// @@ -448,16 +423,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui public event EventHandler? HoveredActionChanged; /// - public event Action InventoryUpdate; - - /// - public event Action ActionBarUpdate; - - /// - public event Action UnlocksUpdate; - - /// - public event Action MainCommandEnabledStateUpdate; + public event Action AgentUpdate; /// public bool GameUiHidden => this.gameGuiService.GameUiHidden; @@ -478,10 +444,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui this.gameGuiService.UiHideToggled -= this.UiHideToggledForward; this.gameGuiService.HoveredItemChanged -= this.HoveredItemForward; this.gameGuiService.HoveredActionChanged -= this.HoveredActionForward; - this.gameGuiService.InventoryUpdate -= this.InventoryUpdateForward; - this.gameGuiService.ActionBarUpdate -= this.ActionBarUpdateForward; - this.gameGuiService.UnlocksUpdate -= this.UnlocksUpdateForward; - this.gameGuiService.MainCommandEnabledStateUpdate -= this.MainCommandEnabledStateUpdateForward; + this.gameGuiService.AgentUpdate -= this.AgentUpdateForward; this.UiHideToggled = null; this.HoveredItemChanged = null; @@ -534,11 +497,5 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui private void HoveredActionForward(object sender, HoveredAction hoverAction) => this.HoveredActionChanged?.Invoke(sender, hoverAction); - private void InventoryUpdateForward() => this.InventoryUpdate.InvokeSafely(); - - private void ActionBarUpdateForward() => this.ActionBarUpdate.InvokeSafely(); - - private void UnlocksUpdateForward() => this.UnlocksUpdate.InvokeSafely(); - - private void MainCommandEnabledStateUpdateForward() => this.MainCommandEnabledStateUpdate.InvokeSafely(); + private void AgentUpdateForward(AgentUpdateFlag agentUpdateFlag) => this.AgentUpdate.InvokeSafely(agentUpdateFlag); } diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs index e2e3c1ec2..535b84372 100644 --- a/Dalamud/Game/Inventory/GameInventory.cs +++ b/Dalamud/Game/Inventory/GameInventory.cs @@ -46,7 +46,7 @@ internal class GameInventory : IInternalDisposableService this.inventoryTypes = Enum.GetValues(); this.inventoryItems = new GameInventoryItem[this.inventoryTypes.Length][]; - this.gameGui.InventoryUpdate += this.OnInventoryUpdate; + this.gameGui.AgentUpdate += this.OnAgentUpdate; } /// @@ -58,7 +58,7 @@ internal class GameInventory : IInternalDisposableService this.subscribersPendingChange.Clear(); this.subscribersChanged = false; this.framework.Update -= this.OnFrameworkUpdate; - this.gameGui.InventoryUpdate -= this.OnInventoryUpdate; + this.gameGui.AgentUpdate -= this.OnAgentUpdate; } } @@ -309,7 +309,7 @@ internal class GameInventory : IInternalDisposableService return items; } - private void OnInventoryUpdate() + private void OnAgentUpdate(AgentUpdateFlag agentUpdateFlag) { this.inventoriesMightBeChanged |= true; } diff --git a/Dalamud/Plugin/Services/IGameGui.cs b/Dalamud/Plugin/Services/IGameGui.cs index fc37c14b7..6c2e0083e 100644 --- a/Dalamud/Plugin/Services/IGameGui.cs +++ b/Dalamud/Plugin/Services/IGameGui.cs @@ -27,25 +27,10 @@ public unsafe interface IGameGui public event EventHandler HoveredActionChanged; /// - /// Event that is fired when the inventory has been updated. + /// Fired when the game sets one or more values, + /// used by agents to conditionally update their addons. /// - event Action InventoryUpdate; - - /// - /// Fired when the action bar needs to be updated, e.g. after changing Class/Job, - /// updating Gear Sets, modifying Macros, or executing a hotbar slot. - /// - event Action ActionBarUpdate; - - /// - /// Event that is fired when collectibles, content or systems were unlocked. - /// - event Action UnlocksUpdate; - - /// - /// Event that is fired when the enable state of MainCommands has been updated. - /// - event Action MainCommandEnabledStateUpdate; + event Action AgentUpdate; /// /// Gets a value indicating whether the game UI is hidden. From 494d9a04fa548340d0de7af29c965e167d59a4e2 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sat, 8 Nov 2025 11:32:34 +0100 Subject: [PATCH 3/3] Remove unknown NameplateUpdate flag for now --- Dalamud/Game/Gui/AgentUpdateFlag.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dalamud/Game/Gui/AgentUpdateFlag.cs b/Dalamud/Game/Gui/AgentUpdateFlag.cs index 1c99e104c..3e2808adb 100644 --- a/Dalamud/Game/Gui/AgentUpdateFlag.cs +++ b/Dalamud/Game/Gui/AgentUpdateFlag.cs @@ -17,8 +17,8 @@ public enum AgentUpdateFlag : byte /// Set when the RetainerMarket inventory has been updated. RetainerMarketInventoryUpdate = 1 << 2, - /// Unknown use case. - NameplateUpdate = 1 << 3, + // /// Unknown use case. + // NameplateUpdate = 1 << 3, /// Set when the player unlocked collectibles, contents or systems. UnlocksUpdate = 1 << 4,