197 lines
4.8 KiB
C#
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)
|
|
{
|
|
}
|
|
}
|
|
}
|