Fix buffer overflow in Discord URL patch - likely cause of crashes

The Discord URL patch was writing 28 bytes (.gg/MHkEjepMQ7, 14 chars)
where only 20 bytes existed (.gg/hytale, 10 chars), corrupting 8 bytes
of adjacent data in the binary.

Changes:
- Use same-length Discord URL: .gg/santop (10 chars)
- Add length check to UTF-16LE fallback path
- Add length check and zero-fill to findAndReplaceDomainSmart

This buffer overflow explains why the crash happened on some systems
(Steam Deck, Ubuntu LTS) but not others - depending on what data
was adjacent to the patched string.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
sanasol
2026-01-27 02:41:38 +01:00
parent c92c5bec3c
commit 50491abc69

View File

@@ -203,11 +203,19 @@ class ClientPatcher {
* .NET AOT stores some strings in various formats: * .NET AOT stores some strings in various formats:
* - Standard UTF-16LE (each char is 2 bytes with \x00 high byte) * - Standard UTF-16LE (each char is 2 bytes with \x00 high byte)
* - Length-prefixed where last char may have metadata byte instead of \x00 * - Length-prefixed where last char may have metadata byte instead of \x00
*
* IMPORTANT: newDomain must be same length or shorter than oldDomain to avoid buffer overflow
*/ */
findAndReplaceDomainSmart(data, oldDomain, newDomain) { findAndReplaceDomainSmart(data, oldDomain, newDomain) {
let count = 0; let count = 0;
const result = Buffer.from(data); const result = Buffer.from(data);
// Safety check: new domain must not be longer than old
if (newDomain.length > oldDomain.length) {
console.warn(` Warning: New domain (${newDomain.length} chars) longer than old (${oldDomain.length} chars), skipping smart replacement`);
return { buffer: result, count: 0 };
}
const oldUtf16NoLast = this.stringToUtf16LE(oldDomain.slice(0, -1)); const oldUtf16NoLast = this.stringToUtf16LE(oldDomain.slice(0, -1));
const newUtf16NoLast = this.stringToUtf16LE(newDomain.slice(0, -1)); const newUtf16NoLast = this.stringToUtf16LE(newDomain.slice(0, -1));
@@ -223,6 +231,11 @@ class ClientPatcher {
const lastCharFirstByte = result[lastCharPos]; const lastCharFirstByte = result[lastCharPos];
if (lastCharFirstByte === oldLastCharByte) { if (lastCharFirstByte === oldLastCharByte) {
// Zero-fill the old region first if new is shorter
if (newUtf16NoLast.length < oldUtf16NoLast.length) {
result.fill(0x00, pos, pos + oldUtf16NoLast.length);
}
newUtf16NoLast.copy(result, pos); newUtf16NoLast.copy(result, pos);
result[lastCharPos] = newLastCharByte; result[lastCharPos] = newLastCharByte;
@@ -316,14 +329,21 @@ class ClientPatcher {
} }
/** /**
* Patch Discord invite URLs from .gg/hytale to .gg/MHkEjepMQ7 * Patch Discord invite URLs from .gg/hytale to shorter URL
* IMPORTANT: New URL must be same length or shorter to avoid corrupting adjacent data
*/ */
patchDiscordUrl(data) { patchDiscordUrl(data) {
let count = 0; let count = 0;
const result = Buffer.from(data); const result = Buffer.from(data);
const oldUrl = '.gg/hytale'; const oldUrl = '.gg/hytale';
const newUrl = '.gg/MHkEjepMQ7'; // Use same-length URL to avoid buffer overflow
// Original: .gg/hytale (10 chars)
// New: .gg/gME8rUy3MB would be 14 chars - TOO LONG
// Using: .gg/sanasolf2p (13 chars) - still too long
// Using: .gg/hytalef2p (12 chars) - still too long
// Must be exactly 10 chars: .gg/XXXXXX (6 chars after .gg/)
const newUrl = '.gg/santop'; // 10 chars - same length, points to our server list
// Try length-prefixed format first // Try length-prefixed format first
const lpResult = this.replaceBytes( const lpResult = this.replaceBytes(
@@ -336,13 +356,22 @@ class ClientPatcher {
return { buffer: lpResult.buffer, count: lpResult.count }; return { buffer: lpResult.buffer, count: lpResult.count };
} }
// Fallback to UTF-16LE // Fallback to UTF-16LE - but ONLY if same length to avoid corruption
const oldUtf16 = this.stringToUtf16LE(oldUrl); const oldUtf16 = this.stringToUtf16LE(oldUrl);
const newUtf16 = this.stringToUtf16LE(newUrl); const newUtf16 = this.stringToUtf16LE(newUrl);
if (newUtf16.length > oldUtf16.length) {
console.warn(` Warning: Discord URL replacement skipped - new URL longer than old`);
return { buffer: result, count: 0 };
}
const positions = this.findAllOccurrences(result, oldUtf16); const positions = this.findAllOccurrences(result, oldUtf16);
for (const pos of positions) { for (const pos of positions) {
// Zero-fill first if new is shorter
if (newUtf16.length < oldUtf16.length) {
result.fill(0x00, pos, pos + oldUtf16.length);
}
newUtf16.copy(result, pos); newUtf16.copy(result, pos);
count++; count++;
} }