qstbak/QuestionableCompanion/QuestionableCompanion.Services/ErrorRecoveryService.cs
2025-12-07 10:54:53 +10:00

197 lines
4.8 KiB
C#

using System;
using System.Runtime.InteropServices;
using Dalamud.Game.NativeWrapper;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace QuestionableCompanion.Services;
public class ErrorRecoveryService : IDisposable
{
private delegate char LobbyErrorHandlerDelegate(long a1, long a2, long a3);
private readonly IPluginLog log;
private readonly IGameInteropProvider hookProvider;
private readonly IClientState clientState;
private readonly IFramework framework;
private readonly IGameGui gameGui;
private readonly AutoRetainerIPC? autoRetainerIPC;
private Hook<LobbyErrorHandlerDelegate>? lobbyErrorHandlerHook;
private DateTime lastDialogClickTime = DateTime.MinValue;
public bool IsErrorDisconnect { get; private set; }
public string? LastDisconnectedCharacter { get; private set; }
public ErrorRecoveryService(IPluginLog log, IGameInteropProvider hookProvider, IClientState clientState, IFramework framework, IGameGui gameGui, AutoRetainerIPC? autoRetainerIPC = null)
{
this.log = log;
this.hookProvider = hookProvider;
this.clientState = clientState;
this.framework = framework;
this.gameGui = gameGui;
this.autoRetainerIPC = autoRetainerIPC;
framework.Update += OnFrameworkUpdate;
InitializeHook();
}
private void InitializeHook()
{
try
{
lobbyErrorHandlerHook = hookProvider.HookFromSignature<LobbyErrorHandlerDelegate>("40 53 48 83 EC 30 48 8B D9 49 8B C8 E8 ?? ?? ?? ?? 8B D0", LobbyErrorHandlerDetour);
if (lobbyErrorHandlerHook != null && lobbyErrorHandlerHook.Address != IntPtr.Zero)
{
lobbyErrorHandlerHook.Enable();
}
}
catch (Exception)
{
}
}
private char LobbyErrorHandlerDetour(long a1, long a2, long a3)
{
try
{
nint p3 = new IntPtr(a3);
byte t1 = Marshal.ReadByte(p3);
int num = (((t1 & 0xF) > 0) ? Marshal.ReadInt32(p3 + 8) : 0);
_ = 0;
if (num != 0)
{
try
{
if (autoRetainerIPC != null)
{
string currentChar = autoRetainerIPC.GetCurrentCharacter();
if (!string.IsNullOrEmpty(currentChar))
{
LastDisconnectedCharacter = currentChar;
}
}
}
catch (Exception)
{
}
Marshal.WriteInt64(p3 + 8, 16000L);
IsErrorDisconnect = true;
if ((t1 & 0xF) > 0)
{
Marshal.ReadInt32(p3 + 8);
}
else
_ = 0;
}
}
catch (Exception)
{
}
return lobbyErrorHandlerHook.Original(a1, a2, a3);
}
private unsafe void OnFrameworkUpdate(IFramework framework)
{
try
{
AtkUnitBasePtr dialoguePtr = gameGui.GetAddonByName("Dialogue");
if (dialoguePtr == IntPtr.Zero)
{
return;
}
AtkUnitBase* dialogueAddon = (AtkUnitBase*)(nint)dialoguePtr;
if (dialogueAddon == null || !dialogueAddon->IsVisible || (DateTime.Now - lastDialogClickTime).TotalMilliseconds < 1000.0)
{
return;
}
AtkTextNode* textNode = dialogueAddon->GetTextNodeById(3u);
if (textNode == null)
{
return;
}
string text = textNode->NodeText.ToString();
if (string.IsNullOrEmpty(text) || (!text.Contains("server", StringComparison.OrdinalIgnoreCase) && !text.Contains("connection", StringComparison.OrdinalIgnoreCase) && !text.Contains("error", StringComparison.OrdinalIgnoreCase) && !text.Contains("lost", StringComparison.OrdinalIgnoreCase)))
{
return;
}
IsErrorDisconnect = true;
try
{
if (autoRetainerIPC != null)
{
string currentChar = autoRetainerIPC.GetCurrentCharacter();
if (!string.IsNullOrEmpty(currentChar))
{
LastDisconnectedCharacter = currentChar;
}
}
}
catch
{
}
try
{
AtkComponentButton* button = dialogueAddon->GetComponentButtonById(4u);
if (button != null)
{
AtkResNode btnRes = button->AtkComponentBase.OwnerNode->AtkResNode;
AtkEvent* evt = btnRes.AtkEventManager.Event;
dialogueAddon->ReceiveEvent(evt->State.EventType, (int)evt->Param, btnRes.AtkEventManager.Event, null);
lastDialogClickTime = DateTime.Now;
}
}
catch (Exception)
{
}
}
catch (Exception)
{
}
}
public void Reset()
{
IsErrorDisconnect = false;
LastDisconnectedCharacter = null;
}
public bool RequestRelog()
{
if (autoRetainerIPC == null || !autoRetainerIPC.IsAvailable)
{
log.Warning("[ErrorRecovery] AutoRetainer IPC not available - cannot relog");
return false;
}
if (string.IsNullOrEmpty(LastDisconnectedCharacter))
{
log.Warning("[ErrorRecovery] No character to relog to");
return false;
}
log.Information("[ErrorRecovery] Requesting AutoRetainer relog to: " + LastDisconnectedCharacter);
return autoRetainerIPC.SwitchCharacter(LastDisconnectedCharacter);
}
public void Dispose()
{
try
{
framework.Update -= OnFrameworkUpdate;
if (lobbyErrorHandlerHook != null)
{
lobbyErrorHandlerHook.Disable();
lobbyErrorHandlerHook.Dispose();
}
}
catch (Exception)
{
}
}
}