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);
Object.assign(env, gpuEnv);
// Disable glibc heap validation on Linux to prevent false positive
// "free(): invalid pointer" crashes on Steam Deck and Ubuntu LTS
// This doesn't hide real bugs - the same binary works on other systems
// Linux memory allocator fixes for "free(): invalid pointer" crashes
// on Steam Deck (glibc 2.41) and Ubuntu LTS
if (process.platform === 'linux') {
// Option 1: Disable glibc heap validation
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 {

View File

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