diff --git a/Library/RSBot.Core/Components/ClientManager.cs b/Library/RSBot.Core/Components/ClientManager.cs
index df4055ae..3f87181a 100644
--- a/Library/RSBot.Core/Components/ClientManager.cs
+++ b/Library/RSBot.Core/Components/ClientManager.cs
@@ -91,6 +91,10 @@ out var pi
true
);
}
+ catch (UnauthorizedAccessException)
+ {
+ Log.Error("Run as administrator!");
+ }
catch (IOException)
{
Log.Debug("DLL is using, can't replace");
@@ -98,18 +102,21 @@ out var pi
Process sroProcess = Process.GetProcessById((int)pi.dwProcessId);
- ResumeThread(pi.hThread);
- Thread.Sleep(150);
- SuspendThread(pi.hThread);
-
if (
Game.ClientType == GameClientType.VTC_Game
|| Game.ClientType == GameClientType.Turkey
|| Game.ClientType == GameClientType.Taiwan
)
{
+ WaitForXigncodeUnpack(sroProcess, pi.hThread);
ApplyXigncodePatch(sroProcess, pi);
}
+ else
+ {
+ ResumeThread(pi.hThread);
+ Thread.Sleep(150);
+ SuspendThread(pi.hThread);
+ }
BypassLauncherCheck(sroProcess, pi);
@@ -200,6 +207,81 @@ out _
VirtualProtectEx(pi.hProcess, address, (UIntPtr)patch.Length, oldProtect, out oldProtect);
}
+ ///
+ /// Runs the suspended client in small time slices and stops the instant the packed
+ /// executable has unpacked far enough for the "XIGNCODE" string to appear in the main
+ /// module. This replaces a fixed Thread.Sleep so the patch is applied just before
+ /// XIGNCODE initializes, independent of disk/CPU speed. On return the primary thread is
+ /// left suspended (suspend count 1) so the caller can patch memory and resume normally.
+ ///
+ private static void WaitForXigncodeUnpack(Process process, IntPtr hThread)
+ {
+ const int sliceMs = 8;
+ const int maxWaitMs = 5000;
+
+ byte[] needle = Encoding.Unicode.GetBytes("XIGNCODE\0");
+ var sw = Stopwatch.StartNew();
+
+ while (true)
+ {
+ ResumeThread(hThread);
+ Thread.Sleep(sliceMs);
+ SuspendThread(hThread);
+
+ if (process.HasExited)
+ return;
+
+ if (ModuleContains(process, needle))
+ return;
+
+ if (sw.ElapsedMilliseconds >= maxWaitMs)
+ {
+ Log.Warn("XIGNCODE: unpack timeout");
+ return;
+ }
+ }
+ }
+
+ ///
+ /// Reads the process main module and reports whether the given byte sequence is present.
+ /// Returns false (instead of throwing) while the module is not yet readable.
+ ///
+ private static bool ModuleContains(Process process, byte[] needle)
+ {
+ try
+ {
+ process.Refresh();
+ int size = process.MainModule.ModuleMemorySize;
+ var buffer = new byte[size];
+
+ if (
+ !ReadProcessMemory(process.Handle, process.MainModule.BaseAddress, buffer, size, out var read)
+ || read == IntPtr.Zero
+ )
+ return false;
+
+ int limit = (int)read - needle.Length;
+ for (int i = 0; i <= limit; i++)
+ {
+ int j = 0;
+ for (; j < needle.Length; j++)
+ {
+ if (buffer[i + j] != needle[j])
+ break;
+ }
+
+ if (j == needle.Length)
+ return true;
+ }
+
+ return false;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
///
/// Applies a universal in-memory patch to bypass XIGNCODE in the specified process.
/// Algorithm: