Add debug options and jemalloc support for Linux memory issues

Debug env vars for clientPatcher:
- HYTALE_SKIP_SENTRY_PATCH=1 - Skip sentry URL patch (60->26 chars)
- HYTALE_SKIP_SUBDOMAIN_PATCH=1 - Skip subdomain prefix patches

Game launcher Linux options:
- HYTALE_USE_JEMALLOC=1 - Use jemalloc allocator instead of glibc

This helps isolate which patch causes "free(): invalid pointer"
on Steam Deck and Ubuntu LTS.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
sanasol
2026-01-27 02:31:38 +01:00
parent 73f67f2fec
commit c92c5bec3c
2 changed files with 61 additions and 31 deletions

View File

@@ -285,12 +285,30 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
const gpuEnv = setupGpuEnvironment(gpuPreference); const gpuEnv = setupGpuEnvironment(gpuPreference);
Object.assign(env, gpuEnv); Object.assign(env, gpuEnv);
// Disable glibc heap validation on Linux to prevent false positive // Linux memory allocator fixes for "free(): invalid pointer" crashes
// "free(): invalid pointer" crashes on Steam Deck and Ubuntu LTS // on Steam Deck (glibc 2.41) and Ubuntu LTS
// This doesn't hide real bugs - the same binary works on other systems
if (process.platform === 'linux') { if (process.platform === 'linux') {
// Option 1: Disable glibc heap validation
env.MALLOC_CHECK_ = '0'; env.MALLOC_CHECK_ = '0';
console.log('Linux detected: Setting MALLOC_CHECK_=0 to disable glibc heap validation');
// Option 2: Try to use jemalloc if available (more robust allocator)
// User can set HYTALE_USE_JEMALLOC=1 to enable
if (process.env.HYTALE_USE_JEMALLOC === '1') {
const jemalloc = require('fs').existsSync('/usr/lib/libjemalloc.so.2')
? '/usr/lib/libjemalloc.so.2'
: require('fs').existsSync('/usr/lib/x86_64-linux-gnu/libjemalloc.so.2')
? '/usr/lib/x86_64-linux-gnu/libjemalloc.so.2'
: null;
if (jemalloc) {
env.LD_PRELOAD = jemalloc + (env.LD_PRELOAD ? ':' + env.LD_PRELOAD : '');
console.log(`Linux: Using jemalloc allocator (${jemalloc})`);
} else {
console.log('Linux: jemalloc not found, using glibc with MALLOC_CHECK_=0');
}
} else {
console.log('Linux: Using glibc with MALLOC_CHECK_=0 (set HYTALE_USE_JEMALLOC=1 to try jemalloc)');
}
} }
try { try {

View File

@@ -245,6 +245,10 @@ class ClientPatcher {
/** /**
* Apply all domain patches using length-prefixed format * Apply all domain patches using length-prefixed format
* This is the main patching method for variable-length domains * This is the main patching method for variable-length domains
*
* Debug env vars:
* HYTALE_SKIP_SENTRY_PATCH=1 - Skip sentry URL patch (biggest size change)
* HYTALE_SKIP_SUBDOMAIN_PATCH=1 - Skip subdomain prefix patches
*/ */
applyDomainPatches(data, domain, protocol = 'https://') { applyDomainPatches(data, domain, protocol = 'https://') {
let result = Buffer.from(data); let result = Buffer.from(data);
@@ -253,20 +257,24 @@ class ClientPatcher {
console.log(` Patching strategy: ${strategy.description}`); console.log(` Patching strategy: ${strategy.description}`);
// 1. Patch telemetry/sentry URL // 1. Patch telemetry/sentry URL (skip if debugging)
const oldSentry = 'https://ca900df42fcf57d4dd8401a86ddd7da2@sentry.hytale.com/2'; if (process.env.HYTALE_SKIP_SENTRY_PATCH === '1') {
const newSentry = `${protocol}t@${domain}/2`; console.log(` Skipping sentry patch (HYTALE_SKIP_SENTRY_PATCH=1)`);
} else {
const oldSentry = 'https://ca900df42fcf57d4dd8401a86ddd7da2@sentry.hytale.com/2';
const newSentry = `${protocol}t@${domain}/2`;
console.log(` Patching sentry: ${oldSentry.slice(0, 30)}... -> ${newSentry}`); console.log(` Patching sentry: ${oldSentry.slice(0, 30)}... -> ${newSentry}`);
const sentryResult = this.replaceBytes( const sentryResult = this.replaceBytes(
result, result,
this.stringToLengthPrefixed(oldSentry), this.stringToLengthPrefixed(oldSentry),
this.stringToLengthPrefixed(newSentry) this.stringToLengthPrefixed(newSentry)
); );
result = sentryResult.buffer; result = sentryResult.buffer;
if (sentryResult.count > 0) { if (sentryResult.count > 0) {
console.log(` Replaced ${sentryResult.count} sentry occurrence(s)`); console.log(` Replaced ${sentryResult.count} sentry occurrence(s)`);
totalCount += sentryResult.count; totalCount += sentryResult.count;
}
} }
// 2. Patch main domain (hytale.com -> mainDomain) // 2. Patch main domain (hytale.com -> mainDomain)
@@ -282,21 +290,25 @@ class ClientPatcher {
totalCount += domainResult.count; totalCount += domainResult.count;
} }
// 3. Patch subdomain prefixes // 3. Patch subdomain prefixes (skip if debugging)
const subdomains = ['https://tools.', 'https://sessions.', 'https://account-data.', 'https://telemetry.']; if (process.env.HYTALE_SKIP_SUBDOMAIN_PATCH === '1') {
const newSubdomainPrefix = protocol + strategy.subdomainPrefix; console.log(` Skipping subdomain patches (HYTALE_SKIP_SUBDOMAIN_PATCH=1)`);
} else {
const subdomains = ['https://tools.', 'https://sessions.', 'https://account-data.', 'https://telemetry.'];
const newSubdomainPrefix = protocol + strategy.subdomainPrefix;
for (const sub of subdomains) { for (const sub of subdomains) {
console.log(` Patching subdomain: ${sub} -> ${newSubdomainPrefix}`); console.log(` Patching subdomain: ${sub} -> ${newSubdomainPrefix}`);
const subResult = this.replaceBytes( const subResult = this.replaceBytes(
result, result,
this.stringToLengthPrefixed(sub), this.stringToLengthPrefixed(sub),
this.stringToLengthPrefixed(newSubdomainPrefix) this.stringToLengthPrefixed(newSubdomainPrefix)
); );
result = subResult.buffer; result = subResult.buffer;
if (subResult.count > 0) { if (subResult.count > 0) {
console.log(` Replaced ${subResult.count} occurrence(s)`); console.log(` Replaced ${subResult.count} occurrence(s)`);
totalCount += subResult.count; totalCount += subResult.count;
}
} }
} }