From ad3c73563dd166da9c52330c1e85541872c83e40 Mon Sep 17 00:00:00 2001 From: AMIAY Date: Sun, 25 Jan 2026 17:02:02 +0100 Subject: [PATCH] temp jar patcher --- backend/managers/gameLauncher.js | 15 ++- backend/utils/clientPatcher.js | 168 ++++++++++++++++++------------- backend/utils/fileManager.js | 19 ++-- 3 files changed, 123 insertions(+), 79 deletions(-) diff --git a/backend/managers/gameLauncher.js b/backend/managers/gameLauncher.js index 9ca0e18..d1274b8 100644 --- a/backend/managers/gameLauncher.js +++ b/backend/managers/gameLauncher.js @@ -331,6 +331,7 @@ exec "$REAL_JAVA" "\${ARGS[@]}" } }); + // Monitor game process status in background setTimeout(() => { if (!hasExited) { console.log('Game appears to be running successfully'); @@ -343,6 +344,7 @@ exec "$REAL_JAVA" "\${ARGS[@]}" } }, 3000); + // Return immediately, don't wait for setTimeout return { success: true, installed: true, launched: true, pid: child.pid }; } catch (spawnError) { console.error(`Error spawning game process: ${spawnError.message}`); @@ -404,13 +406,22 @@ async function launchGameWithVersionCheck(playerName = 'Player', progressCallbac progressCallback('Launching game...', 80, null, null, null); } - return await launchGame(playerName, progressCallback, javaPathOverride, installPathOverride, gpuPreference, branch); + const launchResult = await launchGame(playerName, progressCallback, javaPathOverride, installPathOverride, gpuPreference, branch); + + // Ensure we always return a result + if (!launchResult) { + console.error('launchGame returned null/undefined, creating fallback response'); + return { success: false, error: 'Game launch failed - no response from launcher' }; + } + + return launchResult; } catch (error) { console.error('Error in version check and launch:', error); if (progressCallback) { progressCallback(`Error: ${error.message}`, -1, null, null, null); } - throw error; + // Always return an error response instead of throwing + return { success: false, error: error.message || 'Unknown launch error' }; } } diff --git a/backend/utils/clientPatcher.js b/backend/utils/clientPatcher.js index 09c08df..735e1dd 100644 --- a/backend/utils/clientPatcher.js +++ b/backend/utils/clientPatcher.js @@ -525,17 +525,16 @@ class ClientPatcher { } /** - * Patch the server JAR using DualAuthPatcher for full dual auth support - * This uses the same patcher as the Docker server for consistency + * Patch the server JAR by downloading pre-patched version * @param {string} serverPath - Path to the HytaleServer.jar * @param {function} progressCallback - Optional callback for progress updates - * @param {string} javaPath - Path to Java executable + * @param {string} javaPath - Path to Java executable (unused, kept for compatibility) * @returns {object} Result object with success status and details */ async patchServer(serverPath, progressCallback, javaPath = null) { const newDomain = this.getNewDomain(); - console.log('=== Server Patcher v3.0 (DualAuth) ==='); + console.log('=== Server Patcher TEMP SYSTEM NEED TO BE FIXED ==='); console.log(`Target: ${serverPath}`); console.log(`Domain: ${newDomain}`); @@ -545,13 +544,13 @@ class ClientPatcher { return { success: false, error }; } - // Check if already patched with DualAuth + // Check if already patched const patchFlagFile = serverPath + '.dualauth_patched'; if (fs.existsSync(patchFlagFile)) { try { const flagData = JSON.parse(fs.readFileSync(patchFlagFile, 'utf8')); if (flagData.domain === newDomain) { - console.log(`Server already patched with DualAuth for ${newDomain}, skipping`); + console.log(`Server already patched for ${newDomain}, skipping`); if (progressCallback) progressCallback('Server already patched', 100); return { success: true, alreadyPatched: true }; } @@ -560,80 +559,99 @@ class ClientPatcher { } } - if (progressCallback) progressCallback('Preparing DualAuth patcher...', 10); - - // Find Java executable - use bundled JRE first (same as game uses) - const java = javaPath || this.findJava(); - if (!java) { - const error = 'Java not found. Please install the game first (it includes Java) or install Java 25 from: https://adoptium.net/'; - console.error(error); - return { success: false, error }; - } - console.log(`Using Java: ${java}`); - - // Setup patcher directory - const patcherDir = path.join(__dirname, '..', 'patcher'); - const patcherJava = path.join(patcherDir, 'DualAuthPatcher.java'); - const libDir = path.join(patcherDir, 'lib'); - - // Download patcher from hytale-auth-server if not present - if (progressCallback) progressCallback('Checking patcher...', 15); - try { - await this.ensurePatcherDownloaded(patcherDir); - } catch (e) { - const error = `Failed to download DualAuthPatcher: ${e.message}`; - console.error(error); - return { success: false, error }; - } - - if (!fs.existsSync(patcherJava)) { - const error = `DualAuthPatcher.java not found at ${patcherJava}`; - console.error(error); - return { success: false, error }; - } - - // Download ASM libraries if not present - if (progressCallback) progressCallback('Checking ASM libraries...', 20); - await this.ensureAsmLibraries(libDir); - - // Compile patcher if needed - if (progressCallback) progressCallback('Compiling patcher...', 30); - const compileResult = await this.compileDualAuthPatcher(java, patcherDir, libDir); - if (!compileResult.success) { - return { success: false, error: compileResult.error }; - } - // Create backup - if (progressCallback) progressCallback('Creating backup...', 40); + if (progressCallback) progressCallback('Creating backup...', 10); console.log('Creating backup...'); this.backupClient(serverPath); - // Run the patcher - if (progressCallback) progressCallback('Patching server JAR...', 50); - console.log('Running DualAuthPatcher...'); + // Download pre-patched JAR + if (progressCallback) progressCallback('Downloading patched server JAR...', 30); + console.log('Downloading pre-patched HytaleServer.jar from https://files.hytalef2p.com/jar'); - const classpath = [ - patcherDir, - path.join(libDir, 'asm-9.6.jar'), - path.join(libDir, 'asm-tree-9.6.jar'), - path.join(libDir, 'asm-util-9.6.jar') - ].join(process.platform === 'win32' ? ';' : ':'); + try { + const https = require('https'); + const url = 'https://files.hytalef2p.com/jar'; - const patchResult = await this.runDualAuthPatcher(java, classpath, serverPath, newDomain); + await new Promise((resolve, reject) => { + https.get(url, (response) => { + if (response.statusCode === 302 || response.statusCode === 301) { + // Follow redirect + https.get(response.headers.location, (redirectResponse) => { + if (redirectResponse.statusCode !== 200) { + reject(new Error(`Failed to download: HTTP ${redirectResponse.statusCode}`)); + return; + } + + const file = fs.createWriteStream(serverPath); + const totalSize = parseInt(redirectResponse.headers['content-length'], 10); + let downloaded = 0; + + redirectResponse.on('data', (chunk) => { + downloaded += chunk.length; + if (progressCallback && totalSize) { + const percent = 30 + Math.floor((downloaded / totalSize) * 60); + progressCallback(`Downloading... ${(downloaded / 1024 / 1024).toFixed(2)} MB`, percent); + } + }); + + redirectResponse.pipe(file); + file.on('finish', () => { + file.close(); + resolve(); + }); + }).on('error', reject); + } else if (response.statusCode === 200) { + const file = fs.createWriteStream(serverPath); + const totalSize = parseInt(response.headers['content-length'], 10); + let downloaded = 0; + + response.on('data', (chunk) => { + downloaded += chunk.length; + if (progressCallback && totalSize) { + const percent = 30 + Math.floor((downloaded / totalSize) * 60); + progressCallback(`Downloading... ${(downloaded / 1024 / 1024).toFixed(2)} MB`, percent); + } + }); + + response.pipe(file); + file.on('finish', () => { + file.close(); + resolve(); + }); + } else { + reject(new Error(`Failed to download: HTTP ${response.statusCode}`)); + } + }).on('error', (err) => { + fs.unlink(serverPath, () => {}); + reject(err); + }); + }); + + console.log(' Download successful'); - if (patchResult.success) { // Mark as patched fs.writeFileSync(patchFlagFile, JSON.stringify({ domain: newDomain, patchedAt: new Date().toISOString(), - patcher: 'DualAuthPatcher' + patcher: 'PrePatchedDownload', + source: 'https://download.sanasol.ws/download/HytaleServer.jar' })); if (progressCallback) progressCallback('Server patching complete', 100); console.log('=== Server Patching Complete ==='); - return { success: true, patchCount: patchResult.patchCount || 1 }; - } else { - return { success: false, error: patchResult.error }; + return { success: true, patchCount: 1 }; + + } catch (downloadError) { + console.error(`Failed to download patched JAR: ${downloadError.message}`); + + // Restore backup on failure + const backupPath = serverPath + '.original'; + if (fs.existsSync(backupPath)) { + fs.copyFileSync(backupPath, serverPath); + console.log('Restored backup after download failure'); + } + + return { success: false, error: `Failed to download patched server: ${downloadError.message}` }; } } @@ -802,10 +820,24 @@ class ClientPatcher { ].join(process.platform === 'win32' ? ';' : ':'); try { - execSync(`"${javac}" -cp "${classpath}" -d "${patcherDir}" "${patcherJava}"`, { + // Fix PATH for packaged Electron apps on Windows + const execOptions = { stdio: 'pipe', - cwd: patcherDir - }); + cwd: patcherDir, + env: { ...process.env } + }; + + // Add system32 to PATH for Windows to find cmd.exe + if (process.platform === 'win32') { + const systemRoot = process.env.SystemRoot || 'C:\\WINDOWS'; + const systemPath = `${systemRoot}\\system32;${systemRoot};${systemRoot}\\System32\\Wbem`; + execOptions.env.PATH = execOptions.env.PATH + ? `${systemPath};${execOptions.env.PATH}` + : systemPath; + execOptions.shell = true; + } + + execSync(`"${javac}" -cp "${classpath}" -d "${patcherDir}" "${patcherJava}"`, execOptions); console.log(' Compilation successful'); return { success: true }; } catch (e) { diff --git a/backend/utils/fileManager.js b/backend/utils/fileManager.js index bad80b3..a2a96b0 100644 --- a/backend/utils/fileManager.js +++ b/backend/utils/fileManager.js @@ -58,11 +58,11 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) { console.log(`Download attempt ${attempt + 1}/${maxRetries} for ${url}`); if (attempt > 0 && progressCallback) { - // Exponential backoff with jitter - const baseDelay = 2000; + // Exponential backoff with jitter - longer delays for unstable connections + const baseDelay = 3000; const exponentialDelay = baseDelay * Math.pow(2, attempt - 1); - const jitter = Math.random() * 1000; - const delay = Math.min(exponentialDelay + jitter, 30000); + const jitter = Math.random() * 2000; + const delay = Math.min(exponentialDelay + jitter, 60000); progressCallback(`Retry ${attempt}/${maxRetries - 1}...`, null, null, null, null, retryState); await new Promise(resolve => setTimeout(resolve, delay)); @@ -78,9 +78,9 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) { const now = Date.now(); const timeSinceLastProgress = now - lastProgressTime; - // Only timeout if no data received for 5 minutes (300 seconds) - if (timeSinceLastProgress > 300000 && hasReceivedData) { - console.log('Download stalled for 5 minutes, aborting...'); + // Only timeout if no data received for 10 minutes (600 seconds) - for very slow connections + if (timeSinceLastProgress > 600000 && hasReceivedData) { + console.log('Download stalled for 10 minutes, aborting...'); console.log(`Download had progress before stall: ${(downloaded / 1024 / 1024).toFixed(2)} MB`); controller.abort(); } @@ -119,7 +119,7 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) { method: 'GET', url: url, responseType: 'stream', - timeout: 60000, + timeout: 120000, // 120 seconds for slow connections signal: controller.signal, headers: headers, validateStatus: function (status) { @@ -403,8 +403,9 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) { const retryableErrors = [ 'ECONNRESET', 'ENOTFOUND', 'ECONNREFUSED', 'ETIMEDOUT', 'ESOCKETTIMEDOUT', 'EPROTO', 'ENETDOWN', 'EHOSTUNREACH', + 'ECONNABORTED', 'EPIPE', 'ENETRESET', 'EADDRNOTAVAIL', 'ERR_NETWORK', 'ERR_INTERNET_DISCONNECTED', 'ERR_CONNECTION_RESET', - 'ERR_CONNECTION_TIMED_OUT', 'ERR_NAME_NOT_RESOLVED' + 'ERR_CONNECTION_TIMED_OUT', 'ERR_NAME_NOT_RESOLVED', 'ERR_CONNECTION_CLOSED' ]; const isRetryable = retryableErrors.includes(error.code) ||