From 19263a2ff9011d9bafb1daed9e4a3edb9db2f74b Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Fri, 12 Sep 2025 20:36:47 +0200 Subject: [PATCH] Add PublicInstanceId with event to ClientState --- Dalamud/Game/ClientState/ClientState.cs | 37 ++++++++++++++++++- .../ClientState/ClientStateAddressResolver.cs | 10 ++--- Dalamud/Plugin/Services/IClientState.cs | 10 +++++ 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 05b194f82..726f54f11 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -17,6 +17,7 @@ using FFXIVClientStructs.FFXIV.Application.Network; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Event; using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Network; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; @@ -38,7 +39,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState private readonly ClientStateAddressResolver address; private readonly Hook setupTerritoryTypeHook; private readonly Hook uiModuleHandlePacketHook; - private Hook onLogoutHook; + private readonly Hook setCurrentInstanceHook; [ServiceManager.ServiceDependency] private readonly Framework framework = Service.Get(); @@ -46,6 +47,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState [ServiceManager.ServiceDependency] private readonly NetworkHandlers networkHandlers = Service.Get(); + private Hook onLogoutHook; private bool lastConditionNone = true; [ServiceManager.ServiceConstructor] @@ -64,24 +66,31 @@ internal sealed class ClientState : IInternalDisposableService, IClientState this.setupTerritoryTypeHook = Hook.FromAddress(setTerritoryTypeAddr, this.SetupTerritoryTypeDetour); this.uiModuleHandlePacketHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour); + this.setCurrentInstanceHook = Hook.FromAddress(this.AddressResolver.SetCurrentInstance, this.SetCurrentInstanceDetour); this.framework.Update += this.OnFrameworkUpdate; this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop; this.setupTerritoryTypeHook.Enable(); this.uiModuleHandlePacketHook.Enable(); + this.setCurrentInstanceHook.Enable(); this.framework.RunOnTick(this.Setup); } private unsafe delegate void ProcessPacketPlayerSetupDelegate(nint a1, nint packet); + private unsafe delegate void SetCurrentInstanceDelegate(NetworkModuleProxy* thisPtr, short instanceId); + /// public event Action? TerritoryChanged; /// public event Action? MapChanged; + /// + public event Action? PublicInstanceChanged; + /// public event IClientState.ClassJobChangeDelegate? ClassJobChanged; @@ -112,6 +121,9 @@ internal sealed class ClientState : IInternalDisposableService, IClientState /// public uint MapId { get; private set; } + /// + public uint PublicInstanceId { get; private set; } + /// public IPlayerCharacter? LocalPlayer => Service.GetNullable()?[0] as IPlayerCharacter; @@ -171,6 +183,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState this.setupTerritoryTypeHook.Dispose(); this.uiModuleHandlePacketHook.Dispose(); this.onLogoutHook.Dispose(); + this.setCurrentInstanceHook.Dispose(); this.framework.Update -= this.OnFrameworkUpdate; this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop; @@ -270,6 +283,18 @@ internal sealed class ClientState : IInternalDisposableService, IClientState } } + private unsafe void SetCurrentInstanceDetour(NetworkModuleProxy* thisPtr, short instanceId) + { + this.setCurrentInstanceHook.Original(thisPtr, instanceId); + + if (this.PublicInstanceId == instanceId || instanceId < 0) + return; + + Log.Debug("Instance changed: {0}", instanceId); + this.PublicInstanceId = (uint)instanceId; + this.PublicInstanceChanged?.InvokeSafely((uint)instanceId); + } + private void OnFrameworkUpdate(IFramework framework) { this.UpdateLogin(); @@ -381,6 +406,7 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat { this.clientStateService.TerritoryChanged += this.TerritoryChangedForward; this.clientStateService.MapChanged += this.MapChangedForward; + this.clientStateService.PublicInstanceChanged += this.PublicInstanceChangedForward; this.clientStateService.ClassJobChanged += this.ClassJobChangedForward; this.clientStateService.LevelChanged += this.LevelChangedForward; this.clientStateService.Login += this.LoginForward; @@ -396,6 +422,9 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat /// public event Action? MapChanged; + /// + public event Action? PublicInstanceChanged; + /// public event IClientState.ClassJobChangeDelegate? ClassJobChanged; @@ -426,6 +455,9 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat /// public uint MapId => this.clientStateService.MapId; + /// + public uint PublicInstanceId => this.clientStateService.PublicInstanceId; + /// public IPlayerCharacter? LocalPlayer => this.clientStateService.LocalPlayer; @@ -455,6 +487,7 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat { this.clientStateService.TerritoryChanged -= this.TerritoryChangedForward; this.clientStateService.MapChanged -= this.MapChangedForward; + this.clientStateService.PublicInstanceChanged -= this.PublicInstanceChangedForward; this.clientStateService.ClassJobChanged -= this.ClassJobChangedForward; this.clientStateService.LevelChanged -= this.LevelChangedForward; this.clientStateService.Login -= this.LoginForward; @@ -475,6 +508,8 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat private void MapChangedForward(uint mapId) => this.MapChanged?.Invoke(mapId); + private void PublicInstanceChangedForward(uint instanceId) => this.PublicInstanceChanged?.Invoke(instanceId); + private void ClassJobChangedForward(uint classJobId) => this.ClassJobChanged?.Invoke(classJobId); private void LevelChangedForward(uint classJobId, uint level) => this.LevelChanged?.Invoke(classJobId, level); diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index 97bc5dae1..14c26428c 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -10,19 +10,19 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver /// /// Gets the address of the keyboard state. /// - public IntPtr KeyboardState { get; private set; } + public nint KeyboardState { get; private set; } /// /// Gets the address of the keyboard state index array which translates the VK enumeration to the key state. /// - public IntPtr KeyboardStateIndexArray { get; private set; } + public nint KeyboardStateIndexArray { get; private set; } // Functions /// - /// Gets the address of the method which sets up the player. + /// Gets the address of the method that sets the current public instance. /// - public IntPtr ProcessPacketPlayerSetup { get; private set; } + public nint SetCurrentInstance { get; private set; } /// /// Scan for and setup any configured address pointers. @@ -30,7 +30,7 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver /// The signature scanner to facilitate setup. protected override void Setup64Bit(ISigScanner sig) { - this.ProcessPacketPlayerSetup = sig.ScanText("40 53 48 83 EC 20 48 8D 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 8B D3"); // not in cs struct + this.SetCurrentInstance = sig.ScanText("E8 ?? ?? ?? ?? 0F B6 55 ?? 48 8D 0D ?? ?? ?? ?? C0 EA"); // NetworkModuleProxy.SetCurrentInstance // These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used. // lea rcx, ds:1DB9F74h[rax*4] KeyboardState diff --git a/Dalamud/Plugin/Services/IClientState.cs b/Dalamud/Plugin/Services/IClientState.cs index 06ec0a6f0..a5ed64770 100644 --- a/Dalamud/Plugin/Services/IClientState.cs +++ b/Dalamud/Plugin/Services/IClientState.cs @@ -39,6 +39,11 @@ public interface IClientState /// public event Action MapChanged; + /// + /// Event that gets fired when the current zone Instance changes. + /// + public event Action PublicInstanceChanged; + /// /// Event that fires when a characters ClassJob changed. /// @@ -90,6 +95,11 @@ public interface IClientState /// public uint MapId { get; } + /// + /// Gets the instance number of the current zone, used when multiple copies of an area are active. + /// + public uint PublicInstanceId { get; } + /// /// Gets the local player character, if one is present. ///