diff --git a/GUI/index.html b/GUI/index.html index 2cb204b..fa94706 100644 --- a/GUI/index.html +++ b/GUI/index.html @@ -619,9 +619,17 @@
- +
+ + + +
diff --git a/GUI/js/ui.js b/GUI/js/ui.js index b09399e..44182cb 100644 --- a/GUI/js/ui.js +++ b/GUI/js/ui.js @@ -10,6 +10,8 @@ let progressErrorContainer; let progressErrorMessage; let progressRetryInfo; let progressRetryBtn; +let progressJRRetryBtn; +let progressPWRRetryBtn; // Download retry state let currentDownloadState = { @@ -199,7 +201,8 @@ function updateProgress(data) { if ((data.error || (data.message && data.message.includes('failed'))) && !(data.retryState && data.retryState.isAutomaticRetry)) { const errorType = categorizeError(data.message); - showDownloadError(data.message, data.canRetry, errorType); + console.log('[UI] Showing download error:', { message: data.message, canRetry: data.canRetry, errorType }); + showDownloadError(data.message, data.canRetry, errorType, data); } else if (data.percent === 100) { hideDownloadError(); } else if (data.retryState && data.retryState.isAutomaticRetry) { @@ -230,9 +233,17 @@ function updateRetryState(retryState) { } } -function showDownloadError(errorMessage, canRetry = true, errorType = 'general') { - if (!progressErrorContainer || !progressErrorMessage || !progressRetryBtn) return; +function showDownloadError(errorMessage, canRetry = true, errorType = 'general', data = null) { + if (!progressErrorContainer || !progressErrorMessage) return; + console.log('[UI] showDownloadError called with:', { errorMessage, canRetry, errorType, data }); + console.log('[UI] Data properties:', { + hasData: !!data, + hasRetryData: !!(data && data.retryData), + dataErrorType: data && data.errorType, + dataIsJREError: data && data.retryData && data.retryData.isJREError + }); + currentDownloadState.lastError = errorMessage; currentDownloadState.canRetry = canRetry; currentDownloadState.errorType = errorType; @@ -242,13 +253,37 @@ function showDownloadError(errorMessage, canRetry = true, errorType = 'general') currentDownloadState.branch = data.retryData.branch; currentDownloadState.fileName = data.retryData.fileName; currentDownloadState.cacheDir = data.retryData.cacheDir; + // Override errorType if specified in data + if (data.errorType) { + currentDownloadState.errorType = data.errorType; + } } + // Hide all retry buttons first + if (progressRetryBtn) progressRetryBtn.style.display = 'none'; + if (progressJRRetryBtn) progressJRRetryBtn.style.display = 'none'; + if (progressPWRRetryBtn) progressPWRRetryBtn.style.display = 'none'; + // User-friendly error messages const userMessage = getErrorMessage(errorMessage, errorType); progressErrorMessage.textContent = userMessage; progressErrorContainer.style.display = 'block'; - progressRetryBtn.style.display = canRetry ? 'block' : 'none'; + + // Show appropriate retry button based on error type + if (canRetry) { + if (errorType === 'jre') { + if (progressJRRetryBtn) { + console.log('[UI] Showing JRE retry button'); + progressJRRetryBtn.style.display = 'block'; + } + } else { + // All other errors use PWR retry button (game download, butler, etc.) + if (progressPWRRetryBtn) { + console.log('[UI] Showing PWR retry button'); + progressPWRRetryBtn.style.display = 'block'; + } + } + } // Add visual indicators based on error type progressErrorContainer.className = `progress-error-container error-${errorType}`; @@ -261,6 +296,11 @@ function showDownloadError(errorMessage, canRetry = true, errorType = 'general') function hideDownloadError() { if (!progressErrorContainer) return; + // Hide all retry buttons + if (progressRetryBtn) progressRetryBtn.style.display = 'none'; + if (progressJRRetryBtn) progressJRRetryBtn.style.display = 'none'; + if (progressPWRRetryBtn) progressPWRRetryBtn.style.display = 'none'; + progressErrorContainer.style.display = 'none'; currentDownloadState.canRetry = false; currentDownloadState.lastError = null; @@ -589,6 +629,8 @@ function setupUI() { progressErrorMessage = document.getElementById('progressErrorMessage'); progressRetryInfo = document.getElementById('progressRetryInfo'); progressRetryBtn = document.getElementById('progressRetryBtn'); + progressJRRetryBtn = document.getElementById('progressJRRetryBtn'); + progressPWRRetryBtn = document.getElementById('progressPWRRetryBtn'); // Setup draggable progress bar setupProgressDrag(); @@ -784,6 +826,8 @@ function categorizeError(message) { return 'space'; } else if (msg.includes('conflict') || msg.includes('already exists')) { return 'conflict'; + } else if (msg.includes('jre') || msg.includes('java runtime')) { + return 'jre'; } else { return 'general'; } @@ -812,6 +856,8 @@ function getErrorMessage(technicalMessage, errorType) { return 'Insufficient disk space. Free up space and retry.'; case 'conflict': return 'Installation directory conflict. Please retry.'; + case 'jre': + return 'Java runtime download failed. Please retry.'; default: return 'Download failed. Please retry.'; } @@ -839,70 +885,192 @@ function updateConnectionQuality(quality) { // Enhanced retry button setup function setupRetryButton() { - if (!progressRetryBtn) return; - - progressRetryBtn.addEventListener('click', async () => { - if (!currentDownloadState.canRetry || currentDownloadState.isDownloading) { - return; - } - - // Disable retry button during retry - progressRetryBtn.disabled = true; - progressRetryBtn.textContent = '🔄 Retrying...'; - progressRetryBtn.classList.add('retrying'); - currentDownloadState.isDownloading = true; - - try { - // Hide error state during retry - hideDownloadError(); - - // Reset retry info styling for manual retries - if (progressRetryInfo) { - progressRetryInfo.style.background = ''; - progressRetryInfo.style.color = ''; - } - - // Update progress text with context-aware message - if (progressText) { - const contextMessage = getRetryContextMessage(); - progressText.textContent = contextMessage; + // Setup JRE retry button + if (progressJRRetryBtn) { + progressJRRetryBtn.addEventListener('click', async () => { + if (!currentDownloadState.canRetry || currentDownloadState.isDownloading) { + return; } + progressJRRetryBtn.disabled = true; + progressJRRetryBtn.textContent = 'Retrying...'; + progressJRRetryBtn.classList.add('retrying'); + currentDownloadState.isDownloading = true; - // Ensure retry data exists, create defaults if null - if (!currentDownloadState.retryData) { - currentDownloadState.retryData = { - branch: 'release', - fileName: '4.pwr' - }; - console.log('[UI] Created default retry data:', currentDownloadState.retryData); - } - - // Send retry request to backend - if (window.electronAPI && window.electronAPI.retryDownload) { - const result = await window.electronAPI.retryDownload(currentDownloadState.retryData); + try { + hideDownloadError(); - if (!result.success) { - throw new Error(result.error || 'Retry failed'); + if (progressRetryInfo) { + progressRetryInfo.style.background = ''; + progressRetryInfo.style.color = ''; + } + + if (progressText) { + progressText.textContent = 'Re-downloading Java runtime...'; } - } else { - // Fallback for development/testing - console.warn('electronAPI.retryDownload not available, simulating retry...'); - await new Promise(resolve => setTimeout(resolve, 2000)); - throw new Error('Retry API not available'); - } - } catch (error) { - console.error('Retry failed:', error); - const errorType = categorizeError(error.message); - showDownloadError(`Retry failed: ${error.message}`, true, errorType); - - // Reset retry button - progressRetryBtn.disabled = false; - progressRetryBtn.textContent = '🔄 Retry Download'; - progressRetryBtn.classList.remove('retrying'); - currentDownloadState.isDownloading = false; - } - }); + if (!currentDownloadState.retryData || currentDownloadState.errorType !== 'jre') { + currentDownloadState.retryData = { + isJREError: true, + jreUrl: '', + fileName: 'jre.tar.gz', + cacheDir: '', + osName: 'linux', + arch: 'amd64' + }; + console.log('[UI] Created default JRE retry data:', currentDownloadState.retryData); + } + + if (window.electronAPI && window.electronAPI.retryDownload) { + const result = await window.electronAPI.retryDownload(currentDownloadState.retryData); + if (!result.success) { + throw new Error(result.error || 'JRE retry failed'); + } + } else { + console.warn('electronAPI.retryDownload not available, simulating JRE retry...'); + await new Promise(resolve => setTimeout(resolve, 2000)); + throw new Error('JRE retry API not available'); + } + + } catch (error) { + console.error('JRE retry failed:', error); + showDownloadError(`JRE retry failed: ${error.message}`, true, 'jre'); + } finally { + if (progressJRRetryBtn) { + progressJRRetryBtn.disabled = false; + progressJRRetryBtn.textContent = 'Retry Java Download'; + progressJRRetryBtn.classList.remove('retrying'); + } + currentDownloadState.isDownloading = false; + } + }); + } + + // Setup PWR retry button + if (progressPWRRetryBtn) { + progressPWRRetryBtn.addEventListener('click', async () => { + if (!currentDownloadState.canRetry || currentDownloadState.isDownloading) { + return; + } + progressPWRRetryBtn.disabled = true; + progressPWRRetryBtn.textContent = 'Retrying...'; + progressPWRRetryBtn.classList.add('retrying'); + currentDownloadState.isDownloading = true; + + try { + hideDownloadError(); + + if (progressRetryInfo) { + progressRetryInfo.style.background = ''; + progressRetryInfo.style.color = ''; + } + + if (progressText) { + const contextMessage = getRetryContextMessage(); + progressText.textContent = contextMessage; + } + + if (!currentDownloadState.retryData || currentDownloadState.errorType === 'jre') { + currentDownloadState.retryData = { + branch: 'release', + fileName: '4.pwr' + }; + console.log('[UI] Created default PWR retry data:', currentDownloadState.retryData); + } + + if (window.electronAPI && window.electronAPI.retryDownload) { + const result = await window.electronAPI.retryDownload(currentDownloadState.retryData); + if (!result.success) { + throw new Error(result.error || 'Game retry failed'); + } + } else { + console.warn('electronAPI.retryDownload not available, simulating PWR retry...'); + await new Promise(resolve => setTimeout(resolve, 2000)); + throw new Error('Game retry API not available'); + } + + } catch (error) { + console.error('PWR retry failed:', error); + const errorType = categorizeError(error.message); + showDownloadError(`Game retry failed: ${error.message}`, true, errorType, error); + } finally { + if (progressPWRRetryBtn) { + progressPWRRetryBtn.disabled = false; + progressPWRRetryBtn.textContent = error && error.isJREError ? 'Retry Java Download' : 'Retry Game Download'; + progressPWRRetryBtn.classList.remove('retrying'); + } + currentDownloadState.isDownloading = false; + } + }); + } + + // Setup generic retry button (fallback) + if (progressRetryBtn) { + progressRetryBtn.addEventListener('click', async () => { + if (!currentDownloadState.canRetry || currentDownloadState.isDownloading) { + return; + } + progressRetryBtn.disabled = true; + progressRetryBtn.textContent = 'Retrying...'; + progressRetryBtn.classList.add('retrying'); + currentDownloadState.isDownloading = true; + + try { + hideDownloadError(); + + if (progressRetryInfo) { + progressRetryInfo.style.background = ''; + progressRetryInfo.style.color = ''; + } + + if (progressText) { + const contextMessage = getRetryContextMessage(); + progressText.textContent = contextMessage; + } + + if (!currentDownloadState.retryData) { + if (currentDownloadState.errorType === 'jre') { + currentDownloadState.retryData = { + isJREError: true, + jreUrl: '', + fileName: 'jre.tar.gz', + cacheDir: '', + osName: 'linux', + arch: 'amd64' + }; + } else { + currentDownloadState.retryData = { + branch: 'release', + fileName: '4.pwr' + }; + } + console.log('[UI] Created default retry data:', currentDownloadState.retryData); + } + + if (window.electronAPI && window.electronAPI.retryDownload) { + const result = await window.electronAPI.retryDownload(currentDownloadState.retryData); + if (!result.success) { + throw new Error(result.error || 'Retry failed'); + } + } else { + console.warn('electronAPI.retryDownload not available, simulating retry...'); + await new Promise(resolve => setTimeout(resolve, 2000)); + throw new Error('Retry API not available'); + } + + } catch (error) { + console.error('Retry failed:', error); + const errorType = categorizeError(error.message); + showDownloadError(`Retry failed: ${error.message}`, true, errorType); + } finally { + if (progressRetryBtn) { + progressRetryBtn.disabled = false; + progressRetryBtn.textContent = 'Retry Download'; + progressRetryBtn.classList.remove('retrying'); + } + currentDownloadState.isDownloading = false; + } + }); + } } function getRetryContextMessage() { @@ -925,6 +1093,8 @@ function getRetryContextMessage() { return 'Retrying with corrected permissions...'; case 'conflict': return 'Retrying after resolving conflicts...'; + case 'jre': + return 'Re-downloading Java runtime...'; default: return 'Initiating retry download...'; } diff --git a/GUI/style.css b/GUI/style.css index 8e492f1..8e28ef5 100644 --- a/GUI/style.css +++ b/GUI/style.css @@ -1815,6 +1815,12 @@ body { gap: 0.75rem; } +.progress-retry-buttons { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} + .progress-retry-info { color: #fbbf24; font-family: 'JetBrains Mono', monospace; diff --git a/backend/managers/gameManager.js b/backend/managers/gameManager.js index 3087147..2fb8b62 100644 --- a/backend/managers/gameManager.js +++ b/backend/managers/gameManager.js @@ -520,9 +520,17 @@ async function installGame(playerName = 'Player', progressCallback, javaPathOver try { await downloadJRE(progressCallback, customCacheDir, customJreDir); } catch (error) { + // Don't immediately fall back to system Java for JRE download errors - let user retry + if (error.isJREError) { + console.error('[Install] JRE download failed, allowing user retry:', error.message); + throw error; // Re-throw JRE errors to trigger retry UI + } + + // For non-download JRE errors, fall back to system Java const fallback = await detectSystemJava(); if (fallback) { javaBin = fallback; + console.log('[Install] Using system Java as fallback'); } else { throw error; } diff --git a/backend/managers/javaManager.js b/backend/managers/javaManager.js index c7b48ac..84af570 100644 --- a/backend/managers/javaManager.js +++ b/backend/managers/javaManager.js @@ -9,7 +9,7 @@ const tar = require('tar'); const { expandHome, JRE_DIR } = require('../core/paths'); const { getOS, getArch } = require('../utils/platformUtils'); const { loadConfig } = require('../core/config'); -const { downloadFile } = require('../utils/fileManager'); +const { downloadFile, retryDownload } = require('../utils/fileManager'); const execFileAsync = promisify(execFile); const JAVA_EXECUTABLE = 'java' + (process.platform === 'win32' ? '.exe' : ''); @@ -188,6 +188,20 @@ async function getJavaDetection() { }; } +// Manual retry function for JRE downloads +async function retryJREDownload(url, cacheFile, progressCallback) { + console.log('Initiating manual JRE retry...'); + + // Ensure cache directory exists before retrying + const cacheDir = path.dirname(cacheFile); + if (!fs.existsSync(cacheDir)) { + console.log('Creating JRE cache directory:', cacheDir); + fs.mkdirSync(cacheDir, { recursive: true }); + } + + return await retryDownload(url, cacheFile, progressCallback); +} + async function downloadJRE(progressCallback, cacheDir, jreDir = JRE_DIR) { if (!fs.existsSync(cacheDir)) { fs.mkdirSync(cacheDir, { recursive: true }); @@ -230,7 +244,40 @@ async function downloadJRE(progressCallback, cacheDir, jreDir = JRE_DIR) { progressCallback('Fetching Java runtime...', null, null, null, null); } console.log('Fetching Java runtime...'); - await downloadFile(platform.url, cacheFile, progressCallback); + let jreFile; + try { + jreFile = await downloadFile(platform.url, cacheFile, progressCallback); + + // If downloadFile returns false or undefined, it means the download failed + // We should retry the download with a manual retry + if (!jreFile || typeof jreFile !== 'string') { + console.log('[JRE Download] JRE file download failed or incomplete, attempting retry...'); + jreFile = await retryJREDownload(platform.url, cacheFile, progressCallback); + } + + // Double-check we have a valid file + if (!jreFile || typeof jreFile !== 'string') { + throw new Error(`JRE download failed: received invalid path ${jreFile}. Please retry download.`); + } + + } catch (downloadError) { + console.error('[JRE Download] JRE download failed:', downloadError.message); + + // Enhance error with retry information for the UI + const enhancedError = new Error(`JRE download failed: ${downloadError.message}`); + enhancedError.originalError = downloadError; + enhancedError.canRetry = downloadError.isConnectionLost ? false : (downloadError.canRetry !== false); + enhancedError.jreUrl = platform.url; + enhancedError.jreDest = cacheFile; + enhancedError.osName = osName; + enhancedError.arch = arch; + enhancedError.fileName = fileName; + enhancedError.cacheDir = cacheDir; + enhancedError.isJREError = true; // Flag to identify JRE errors + enhancedError.isConnectionLost = downloadError.isConnectionLost || false; + + throw enhancedError; + } console.log('Download finished'); } diff --git a/backend/utils/fileManager.js b/backend/utils/fileManager.js index 576cc96..972ca61 100644 --- a/backend/utils/fileManager.js +++ b/backend/utils/fileManager.js @@ -423,7 +423,8 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) { const canRetry = (error.canRetry === false) ? false : isRetryable; if (!canRetry || attempt === maxRetries - 1) { - retryState.canRetry = false; + // Don't set retryState.canRetry to false for max retries - user should still be able to retry manually + retryState.canRetry = error.canRetry === false ? false : true; console.error(`Non-retryable error or max retries reached: ${error.code || error.message}`); break; } @@ -439,6 +440,9 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) { enhancedError.retryState = retryState; enhancedError.lastError = lastError; enhancedError.detailedError = detailedError; + + // Allow manual retry unless it's a connection lost error + enhancedError.canRetry = !lastError?.isConnectionLost && lastError?.canRetry !== false; throw enhancedError; } @@ -543,6 +547,13 @@ async function retryDownload(url, dest, progressCallback, previousError = null) additionalRetries = Math.max(2, 5 - previousError.retryState.attempts); } + // Ensure cache directory exists before retrying + const destDir = path.dirname(dest); + if (!fs.existsSync(destDir)) { + console.log('Creating cache directory:', destDir); + fs.mkdirSync(destDir, { recursive: true }); + } + try { await downloadFile(url, dest, progressCallback, additionalRetries); console.log('Manual retry successful'); diff --git a/main.js b/main.js index 7e67543..e7e2767 100644 --- a/main.js +++ b/main.js @@ -3,6 +3,7 @@ require('dotenv').config({ path: path.join(__dirname, '.env') }); const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron'); const fs = require('fs'); const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveChatUsername, loadChatUsername, saveChatColor, loadChatColor, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched } = require('./backend/launcher'); +const { retryPWRDownload } = require('./backend/managers/gameManager'); const logger = require('./backend/logger'); const profileManager = require('./backend/managers/profileManager'); @@ -430,37 +431,41 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath, console.log('[Main] Returning success response for install-game:', successResponse); return successResponse; } catch (error) { - console.error('Install error:', error); + // console.error('Install error:', error); const errorMessage = error.message || error.toString(); // Enhanced error data extraction for both download and Butler errors let errorData = { message: errorMessage, error: true, - canRetry: true, + canRetry: true, // Default to true, will be overridden by specific error props retryData: null }; + // Prioritize JRE errors first + if (error.isJREError) { + console.log('[Main] Processing JRE download error with retry context'); + errorData.retryData = { + isJREError: true, + jreUrl: error.jreUrl, + fileName: error.fileName, + cacheDir: error.cacheDir, + osName: error.osName, + arch: error.arch + }; + // For JRE errors, allow manual retry unless explicitly disabled + errorData.canRetry = error.canRetry !== false; + errorData.errorType = 'jre'; + } // Handle Butler-specific errors - if (error.butlerError) { + else if (error.butlerError) { console.log('[Main] Processing Butler error with retry context'); errorData.retryData = { branch: error.branch || 'release', fileName: error.fileName || '4.pwr', cacheDir: error.cacheDir }; - errorData.canRetry = error.canRetry !== undefined ? error.canRetry : true; - - // Add Butler-specific error details - if (error.stderr) { - console.error('[Main] Butler stderr:', error.stderr); - } - if (error.stdout) { - console.log('[Main] Butler stdout:', error.stdout); - } - if (error.errorCode) { - console.log('[Main] Butler error code:', error.errorCode); - } + errorData.canRetry = error.canRetry !== false; } // Handle PWR download errors else if (error.branch && error.fileName) { @@ -470,7 +475,7 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath, fileName: error.fileName, cacheDir: error.cacheDir }; - errorData.canRetry = error.canRetry !== undefined ? error.canRetry : true; + errorData.canRetry = error.canRetry !== false; } // Default fallback for other errors else { @@ -479,6 +484,8 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath, branch: 'release', fileName: '4.pwr' }; + // For generic errors, assume it's retryable unless specified + errorData.canRetry = error.canRetry !== false; } // Send enhanced error info for retry UI @@ -636,7 +643,7 @@ ipcMain.handle('uninstall-game', async () => { try { await uninstallGame(); } catch (error) { - console.error('Uninstall error:', error); + // console.error('Uninstall error:', error); return { success: false, error: error.message }; } }); @@ -669,16 +676,7 @@ ipcMain.handle('repair-game', async () => { ipcMain.handle('retry-download', async (event, retryData) => { try { console.log('[IPC] retry-download called with data:', retryData); - - // Handle null retry data gracefully - if (!retryData || !retryData.branch || !retryData.fileName) { - console.log('[IPC] Invalid retry data, using defaults'); - retryData = { - branch: 'release', - fileName: '4.pwr' - }; - } - + const progressCallback = (message, percent, speed, downloaded, total, retryState) => { if (mainWindow && !mainWindow.isDestroyed()) { const data = { @@ -693,14 +691,36 @@ ipcMain.handle('retry-download', async (event, retryData) => { } }; + // Handle JRE download retries + if (retryData && retryData.isJREError) { + console.log(`[IPC] Retrying JRE download: jreUrl=${retryData.jreUrl}, fileName=${retryData.fileName}`); + console.log('[IPC] Full JRE retry data:', JSON.stringify(retryData, null, 2)); + + const { retryJREDownload } = require('./backend/managers/javaManager'); + await retryJREDownload(retryData.jreUrl, jreCacheFile, progressCallback); + const jreCacheFile = path.join(retryData.cacheDir, retryData.fileName); + + return { success: true }; + } + + // Handle PWR download retries (default) + if (!retryData || !retryData.branch || !retryData.fileName) { + console.log('[IPC] Invalid retry data, using PWR defaults'); + retryData = { + branch: 'release', + fileName: '4.pwr' + }; + } + // Extract PWR download info from retryData const branch = retryData.branch; const fileName = retryData.fileName; const cacheDir = retryData.cacheDir; console.log(`[IPC] Retrying PWR download: branch=${branch}, fileName=${fileName}`); + console.log('[IPC] Full PWR retry data:', JSON.stringify(retryData, null, 2)); - // Perform the retry with enhanced context + // Perform retry with enhanced context await retryPWRDownload(branch, fileName, progressCallback, cacheDir); return { success: true }; @@ -710,15 +730,28 @@ ipcMain.handle('retry-download', async (event, retryData) => { // Send error update to frontend with context if (mainWindow && !mainWindow.isDestroyed()) { - const data = { - message: errorMessage, - error: true, - canRetry: true, - retryData: { + const isJreError = retryData?.isJREError; + const errorRetryData = isJreError ? + { + isJREError: true, + jreUrl: retryData?.jreUrl, + fileName: retryData?.fileName, + cacheDir: retryData?.cacheDir, + osName: retryData?.osName, + arch: retryData?.arch + } : + { branch: retryData?.branch || 'release', fileName: retryData?.fileName || '4.pwr', cacheDir: retryData?.cacheDir - } + }; + + const data = { + message: errorMessage, + error: true, + canRetry: error.canRetry !== false, // Respect canRetry from the thrown error + retryData: errorRetryData, + errorType: isJreError ? 'jre' : 'general' // Add errorType for the UI }; mainWindow.webContents.send('progress-update', data); } @@ -846,7 +879,6 @@ ipcMain.handle('load-settings', async () => { }); const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod, getCurrentUuid, getAllUuidMappings, setUuidForUser, generateNewUuid, deleteUuidForUser, resetCurrentUserUuid } = require('./backend/launcher'); -const { retryPWRDownload } = require('./backend/managers/gameManager'); const os = require('os'); ipcMain.handle('get-local-app-data', async () => {