Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 86 additions & 4 deletions Library/RSBot.Core/Components/ClientManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,25 +91,32 @@ out var pi
true
);
}
catch (UnauthorizedAccessException)
{
Log.Error("Run as administrator!");
}
catch (IOException)
{
Log.Debug("DLL is using, can't replace");
}

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);
Comment on lines +111 to 112

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Differentiate unpack success from timeout/exit.

Line 111 treats every return from WaitForXigncodeUnpack() as success, but Lines 231-240 also return on HasExited and timeout. That means Start() can still call ApplyXigncodePatch() on a dead process or fall through the success path even though the XIGNCODE wait failed, which bypasses the false-return contract that GeneralManager.StartClientProcess() is relying on. Return a status from the helper and abort/cleanup on failure instead of patching unconditionally.

Also applies to: 231-240

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Library/RSBot.Core/Components/ClientManager.cs` around lines 111 - 112,
Change the logic so WaitForXigncodeUnpack returns an explicit status (bool or
enum) and Start() checks that status before calling ApplyXigncodePatch; if the
unpack wait returned failure (timeout or process exit) then perform the same
abort/cleanup path and return false instead of proceeding to ApplyXigncodePatch.
Update any checks around HasExited/timeout in the block currently around lines
231-240 to use the new status semantics so
StartClientProcess/GeneralManager.StartClientProcess observes false on failure.
Ensure the unique symbols WaitForXigncodeUnpack, ApplyXigncodePatch, and Start
(or GeneralManager.StartClientProcess caller) are updated to propagate and
handle the returned status consistently.

}
else
{
ResumeThread(pi.hThread);
Thread.Sleep(150);
SuspendThread(pi.hThread);
}

BypassLauncherCheck(sroProcess, pi);

Expand Down Expand Up @@ -200,6 +207,81 @@ out _
VirtualProtectEx(pi.hProcess, address, (UIntPtr)patch.Length, oldProtect, out oldProtect);
}

/// <summary>
/// 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.
/// </summary>
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;
}
}
}

/// <summary>
/// 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.
/// </summary>
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;
}
}

/// <summary>
/// Applies a universal in-memory patch to bypass XIGNCODE in the specified process.
/// Algorithm:
Expand Down
Loading