diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 0fad33e15..51f25fb5c 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -14,6 +14,7 @@ using Dalamud.Logging.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Application.Network; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Event; using FFXIVClientStructs.FFXIV.Client.UI; @@ -37,16 +38,12 @@ internal sealed class ClientState : IInternalDisposableService, IClientState private readonly ClientStateAddressResolver address; private readonly Hook setupTerritoryTypeHook; private readonly Hook uiModuleHandlePacketHook; - - [ServiceManager.ServiceDependency] - private readonly Framework framework = Service.Get(); + private readonly Hook processPacketPlayerSetupHook; + private readonly Hook onLogoutHook; [ServiceManager.ServiceDependency] private readonly NetworkHandlers networkHandlers = Service.Get(); - private bool lastConditionNone = true; - private bool lastFramePvP; - [ServiceManager.ServiceConstructor] private unsafe ClientState(TargetSigScanner sigScanner, Dalamud dalamud, GameLifecycle lifecycle) { @@ -62,18 +59,22 @@ internal sealed class ClientState : IInternalDisposableService, IClientState this.setupTerritoryTypeHook = Hook.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); this.uiModuleHandlePacketHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour); - - this.framework.Update += this.FrameworkOnOnUpdateEvent; + this.processPacketPlayerSetupHook = Hook.FromAddress(this.address.ProcessPacketPlayerSetup, this.ProcessPacketPlayerSetupDetour); + this.onLogoutHook = Hook.FromAddress((nint)LogoutCallbackInterface.StaticVirtualTablePointer->OnLogout, this.OnLogoutDetour); this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop; this.setupTerritoryTypeHook.Enable(); this.uiModuleHandlePacketHook.Enable(); + this.processPacketPlayerSetupHook.Enable(); + this.onLogoutHook.Enable(); } [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private unsafe delegate void SetupTerritoryTypeDelegate(EventFramework* eventFramework, ushort terriType); + private unsafe delegate void ProcessPacketPlayerSetupDelegate(nint a1, nint packet); + /// public event Action? TerritoryChanged; @@ -87,7 +88,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState public event Action? Login; /// - public event Action? Logout; + public event IClientState.LogoutDelegate? Logout; /// public event Action? EnterPvP; @@ -165,23 +166,46 @@ internal sealed class ClientState : IInternalDisposableService, IClientState { this.setupTerritoryTypeHook.Dispose(); this.uiModuleHandlePacketHook.Dispose(); - this.framework.Update -= this.FrameworkOnOnUpdateEvent; + this.processPacketPlayerSetupHook.Dispose(); + this.onLogoutHook.Dispose(); this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop; } private unsafe void SetupTerritoryTypeDetour(EventFramework* eventFramework, ushort territoryType) { + Log.Debug("TerritoryType changed: {0}", territoryType); + this.TerritoryType = territoryType; this.TerritoryChanged?.InvokeSafely(territoryType); - Log.Debug("TerritoryType changed: {0}", territoryType); + var rowRef = LuminaUtils.CreateRef(territoryType); + if (rowRef.IsValid) + { + var isPvP = rowRef.Value.IsPvpZone; + if (isPvP != this.IsPvP) + { + this.IsPvP = isPvP; + this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250; + + if (this.IsPvP) + { + Log.Debug("EnterPvP"); + this.EnterPvP?.InvokeSafely(); + } + else + { + Log.Debug("LeavePvP"); + this.LeavePvP?.InvokeSafely(); + } + } + } this.setupTerritoryTypeHook.Original(eventFramework, territoryType); } private unsafe void UIModuleHandlePacketDetour(UIModule* thisPtr, UIModulePacketType type, uint uintParam, void* packet) { - this.uiModuleHandlePacketHook!.Original(thisPtr, type, uintParam, packet); + this.uiModuleHandlePacketHook.Original(thisPtr, type, uintParam, packet); switch (type) { @@ -226,59 +250,74 @@ internal sealed class ClientState : IInternalDisposableService, IClientState } } + private unsafe void ProcessPacketPlayerSetupDetour(nint a1, nint packet) + { + // Call original first, so everything is set up. + this.processPacketPlayerSetupHook.Original(a1, packet); + + var gameGui = Service.GetNullable(); + + try + { + Log.Debug("Login"); + this.IsLoggedIn = true; + this.Login?.InvokeSafely(); + gameGui?.ResetUiHideState(); + this.lifecycle.ResetLogout(); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during ProcessPacketPlayerSetupDetour"); + } + } + + private unsafe void OnLogoutDetour(LogoutCallbackInterface* thisPtr, LogoutCallbackInterface.LogoutParams* logoutParams) + { + var gameGui = Service.GetNullable(); + + if (logoutParams != null) + { + try + { + var type = logoutParams->Type; + var code = logoutParams->Code; + + Log.Debug("Logout: Type {type}, Code {code}", type, code); + + this.IsLoggedIn = false; + + if (this.Logout is { } callback) + { + foreach (var action in callback.GetInvocationList().Cast()) + { + try + { + action(type, code); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during raise of {handler}", action.Method); + } + } + } + + gameGui?.ResetUiHideState(); + + this.lifecycle.SetLogout(); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during OnLogoutDetour"); + } + } + + this.onLogoutHook.Original(thisPtr, logoutParams); + } + private void NetworkHandlersOnCfPop(ContentFinderCondition e) { this.CfPop?.InvokeSafely(e); } - - private void FrameworkOnOnUpdateEvent(IFramework framework1) - { - var condition = Service.GetNullable(); - var gameGui = Service.GetNullable(); - var data = Service.GetNullable(); - - if (condition == null || gameGui == null || data == null) - return; - - if (condition.Any() && this.lastConditionNone && this.LocalPlayer != null) - { - Log.Debug("Is login"); - this.lastConditionNone = false; - this.IsLoggedIn = true; - this.Login?.InvokeSafely(); - gameGui.ResetUiHideState(); - - this.lifecycle.ResetLogout(); - } - - if (!condition.Any() && this.lastConditionNone == false) - { - Log.Debug("Is logout"); - this.lastConditionNone = true; - this.IsLoggedIn = false; - this.Logout?.InvokeSafely(); - gameGui.ResetUiHideState(); - - this.lifecycle.SetLogout(); - } - - this.IsPvP = GameMain.IsInPvPArea(); - this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250; - - if (this.IsPvP != this.lastFramePvP) - { - this.lastFramePvP = this.IsPvP; - - if (this.IsPvP) - { - this.EnterPvP?.InvokeSafely(); - } - else - { - this.LeavePvP?.InvokeSafely(); - } - } - } } /// @@ -322,7 +361,7 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat public event Action? Login; /// - public event Action? Logout; + public event IClientState.LogoutDelegate? Logout; /// public event Action? EnterPvP; @@ -394,7 +433,7 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat private void LoginForward() => this.Login?.Invoke(); - private void LogoutForward() => this.Logout?.Invoke(); + private void LogoutForward(int type, int code) => this.Logout?.Invoke(type, code); private void EnterPvPForward() => this.EnterPvP?.Invoke(); diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index 625271d2a..6b46ffc0d 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -57,6 +57,11 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver /// public IntPtr SetupTerritoryType { get; private set; } + /// + /// Gets the address of the method which sets up the player. + /// + public IntPtr ProcessPacketPlayerSetup { get; private set; } + /// /// Gets the address of the method which polls the gamepads for data. /// Called every frame, even when `Enable Gamepad` is off in the settings. @@ -82,6 +87,8 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 57 48 83 EC 20 0F B7 DA"); + this.ProcessPacketPlayerSetup = sig.ScanText("40 53 48 83 EC 20 48 8D 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 8B D3"); + // 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 // movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs index 1869dd108..723182111 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs @@ -50,7 +50,7 @@ internal class LogoutEventAgingStep : IAgingStep } } - private void ClientStateOnOnLogout() + private void ClientStateOnOnLogout(int type, int code) { this.hasPassed = true; } diff --git a/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs b/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs index 6404846f7..c25ec4ee4 100644 --- a/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs +++ b/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -89,7 +89,7 @@ internal class AutoUpdateManager : IServiceType t => { t.Result.Login += this.OnLogin; - t.Result.Logout += this.OnLogout; + t.Result.Logout += (int type, int code) => this.OnLogout(); }); Service.GetAsync().ContinueWith(t => { t.Result.Update += this.OnUpdate; }); diff --git a/Dalamud/Plugin/Services/IClientState.cs b/Dalamud/Plugin/Services/IClientState.cs index f7c462d09..bac2b3e3f 100644 --- a/Dalamud/Plugin/Services/IClientState.cs +++ b/Dalamud/Plugin/Services/IClientState.cs @@ -22,6 +22,13 @@ public interface IClientState /// The level of the corresponding ClassJob. public delegate void LevelChangeDelegate(uint classJobId, uint level); + /// + /// A delegate type used for the event. + /// + /// The type of logout. + /// The success/failure code. + public delegate void LogoutDelegate(int type, int code); + /// /// Event that gets fired when the current Territory changes. /// @@ -46,7 +53,7 @@ public interface IClientState /// /// Event that fires when a character is logging out. /// - public event Action Logout; + public event LogoutDelegate Logout; /// /// Event that fires when a character is entering PvP.