diff --git a/GUI/js/settings.js b/GUI/js/settings.js index 6dd54b5..29b7065 100644 --- a/GUI/js/settings.js +++ b/GUI/js/settings.js @@ -167,6 +167,8 @@ function setupSettingsElements() { closeLauncherCheck = document.getElementById('closeLauncherCheck'); gpuPreferenceRadios = document.querySelectorAll('input[name="gpuPreference"]'); gameBranchRadios = document.querySelectorAll('input[name="gameBranch"]'); + + console.log('[Settings] gameBranchRadios found:', gameBranchRadios.length); // UUID Management elements @@ -541,7 +543,8 @@ document.addEventListener('DOMContentLoaded', initSettings); window.SettingsAPI = { getCurrentJavaPath, - getCurrentPlayerName + getCurrentPlayerName, + reloadBranch: loadVersionBranch }; async function loadCurrentUuid() { @@ -1078,11 +1081,13 @@ async function loadVersionBranch() { console.log('[Settings] Selected branch:', selectedBranch); // Update radio buttons - if (gameBranchRadios) { + if (gameBranchRadios && gameBranchRadios.length > 0) { gameBranchRadios.forEach(radio => { radio.checked = radio.value === selectedBranch; console.log(`[Settings] Radio ${radio.value}: ${radio.checked ? 'checked' : 'unchecked'}`); }); + } else { + console.warn('[Settings] gameBranchRadios not found or empty'); } return selectedBranch; diff --git a/GUI/js/ui.js b/GUI/js/ui.js index 4e7131b..b09399e 100644 --- a/GUI/js/ui.js +++ b/GUI/js/ui.js @@ -1,27 +1,27 @@ -let progressOverlay; -let progressBar; -let progressBarFill; -let progressText; -let progressPercent; -let progressSpeed; -let progressSize; -let progressErrorContainer; -let progressErrorMessage; -let progressRetryInfo; -let progressRetryBtn; - -// Download retry state -let currentDownloadState = { - isDownloading: false, - canRetry: false, - retryData: null, - lastError: null, - errorType: null, - branch: null, - fileName: null, - cacheDir: null -}; +let progressOverlay; +let progressBar; +let progressBarFill; +let progressText; +let progressPercent; +let progressSpeed; +let progressSize; +let progressErrorContainer; +let progressErrorMessage; +let progressRetryInfo; +let progressRetryBtn; + +// Download retry state +let currentDownloadState = { + isDownloading: false, + canRetry: false, + retryData: null, + lastError: null, + errorType: null, + branch: null, + fileName: null, + cacheDir: null +}; function showPage(pageId) { const pages = document.querySelectorAll('.page'); @@ -29,6 +29,15 @@ function showPage(pageId) { if (page.id === pageId) { page.classList.add('active'); page.style.display = ''; + + // Reload settings when settings page becomes visible + if (pageId === 'settings-page') { + console.log('[UI] Settings page activated, reloading branch...'); + // Dynamically import and call loadVersionBranch from settings + if (window.SettingsAPI && window.SettingsAPI.reloadBranch) { + window.SettingsAPI.reloadBranch(); + } + } } else { page.classList.remove('active'); page.style.display = 'none'; @@ -159,108 +168,108 @@ function hideProgress() { } } -function updateProgress(data) { - // Handle retry state - if (data.retryState) { - currentDownloadState.retryData = data.retryState; - updateRetryState(data.retryState); - } - - if (data.message && progressText) { - progressText.textContent = data.message; - } - - if (data.percent !== null && data.percent !== undefined) { - const percent = Math.min(100, Math.max(0, Math.round(data.percent))); - if (progressPercent) progressPercent.textContent = `${percent}%`; - if (progressBarFill) progressBarFill.style.width = `${percent}%`; - if (progressBar) progressBar.style.width = `${percent}%`; - } - - if (data.speed && data.downloaded && data.total) { - const speedMB = (data.speed / 1024 / 1024).toFixed(2); - const downloadedMB = (data.downloaded / 1024 / 1024).toFixed(2); - const totalMB = (data.total / 1024 / 1024).toFixed(2); - if (progressSpeed) progressSpeed.textContent = `${speedMB} MB/s`; - if (progressSize) progressSize.textContent = `${downloadedMB} / ${totalMB} MB`; - } - - // Handle error states with enhanced categorization - // Don't show error during automatic retries - let the retry message display instead - 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); - } else if (data.percent === 100) { - hideDownloadError(); - } else if (data.retryState && data.retryState.isAutomaticRetry) { - // Hide any existing error during automatic retries - hideDownloadError(); - } -} - -function updateRetryState(retryState) { - if (!progressRetryInfo) return; - - if (retryState.isAutomaticRetry && retryState.automaticStallRetries > 0) { - // Show automatic stall retry count - progressRetryInfo.textContent = `Auto-retry ${retryState.automaticStallRetries}/3`; - progressRetryInfo.style.display = 'block'; - progressRetryInfo.style.background = 'rgba(255, 193, 7, 0.2)'; // Light orange background for auto-retries - progressRetryInfo.style.color = '#ff9800'; // Orange text for auto-retries - } else if (retryState.attempts > 1) { - // Show manual retry count - progressRetryInfo.textContent = `Attempt ${retryState.attempts}/${retryState.maxRetries}`; - progressRetryInfo.style.display = 'block'; - progressRetryInfo.style.background = ''; // Reset background - progressRetryInfo.style.color = ''; // Reset color - } else { - progressRetryInfo.style.display = 'none'; - progressRetryInfo.style.background = ''; // Reset background - progressRetryInfo.style.color = ''; // Reset color - } -} - -function showDownloadError(errorMessage, canRetry = true, errorType = 'general') { - if (!progressErrorContainer || !progressErrorMessage || !progressRetryBtn) return; - - currentDownloadState.lastError = errorMessage; - currentDownloadState.canRetry = canRetry; - currentDownloadState.errorType = errorType; - - // Update retry context if available - if (data && data.retryData) { - currentDownloadState.branch = data.retryData.branch; - currentDownloadState.fileName = data.retryData.fileName; - currentDownloadState.cacheDir = data.retryData.cacheDir; - } - - // User-friendly error messages - const userMessage = getErrorMessage(errorMessage, errorType); - progressErrorMessage.textContent = userMessage; - progressErrorContainer.style.display = 'block'; - progressRetryBtn.style.display = canRetry ? 'block' : 'none'; - - // Add visual indicators based on error type - progressErrorContainer.className = `progress-error-container error-${errorType}`; - - if (progressOverlay) { - progressOverlay.classList.add('error-state'); - } -} - -function hideDownloadError() { - if (!progressErrorContainer) return; - - progressErrorContainer.style.display = 'none'; - currentDownloadState.canRetry = false; - currentDownloadState.lastError = null; - currentDownloadState.errorType = null; - - if (progressOverlay) { - progressOverlay.classList.remove('error-state'); - } -} +function updateProgress(data) { + // Handle retry state + if (data.retryState) { + currentDownloadState.retryData = data.retryState; + updateRetryState(data.retryState); + } + + if (data.message && progressText) { + progressText.textContent = data.message; + } + + if (data.percent !== null && data.percent !== undefined) { + const percent = Math.min(100, Math.max(0, Math.round(data.percent))); + if (progressPercent) progressPercent.textContent = `${percent}%`; + if (progressBarFill) progressBarFill.style.width = `${percent}%`; + if (progressBar) progressBar.style.width = `${percent}%`; + } + + if (data.speed && data.downloaded && data.total) { + const speedMB = (data.speed / 1024 / 1024).toFixed(2); + const downloadedMB = (data.downloaded / 1024 / 1024).toFixed(2); + const totalMB = (data.total / 1024 / 1024).toFixed(2); + if (progressSpeed) progressSpeed.textContent = `${speedMB} MB/s`; + if (progressSize) progressSize.textContent = `${downloadedMB} / ${totalMB} MB`; + } + + // Handle error states with enhanced categorization + // Don't show error during automatic retries - let the retry message display instead + 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); + } else if (data.percent === 100) { + hideDownloadError(); + } else if (data.retryState && data.retryState.isAutomaticRetry) { + // Hide any existing error during automatic retries + hideDownloadError(); + } +} + +function updateRetryState(retryState) { + if (!progressRetryInfo) return; + + if (retryState.isAutomaticRetry && retryState.automaticStallRetries > 0) { + // Show automatic stall retry count + progressRetryInfo.textContent = `Auto-retry ${retryState.automaticStallRetries}/3`; + progressRetryInfo.style.display = 'block'; + progressRetryInfo.style.background = 'rgba(255, 193, 7, 0.2)'; // Light orange background for auto-retries + progressRetryInfo.style.color = '#ff9800'; // Orange text for auto-retries + } else if (retryState.attempts > 1) { + // Show manual retry count + progressRetryInfo.textContent = `Attempt ${retryState.attempts}/${retryState.maxRetries}`; + progressRetryInfo.style.display = 'block'; + progressRetryInfo.style.background = ''; // Reset background + progressRetryInfo.style.color = ''; // Reset color + } else { + progressRetryInfo.style.display = 'none'; + progressRetryInfo.style.background = ''; // Reset background + progressRetryInfo.style.color = ''; // Reset color + } +} + +function showDownloadError(errorMessage, canRetry = true, errorType = 'general') { + if (!progressErrorContainer || !progressErrorMessage || !progressRetryBtn) return; + + currentDownloadState.lastError = errorMessage; + currentDownloadState.canRetry = canRetry; + currentDownloadState.errorType = errorType; + + // Update retry context if available + if (data && data.retryData) { + currentDownloadState.branch = data.retryData.branch; + currentDownloadState.fileName = data.retryData.fileName; + currentDownloadState.cacheDir = data.retryData.cacheDir; + } + + // User-friendly error messages + const userMessage = getErrorMessage(errorMessage, errorType); + progressErrorMessage.textContent = userMessage; + progressErrorContainer.style.display = 'block'; + progressRetryBtn.style.display = canRetry ? 'block' : 'none'; + + // Add visual indicators based on error type + progressErrorContainer.className = `progress-error-container error-${errorType}`; + + if (progressOverlay) { + progressOverlay.classList.add('error-state'); + } +} + +function hideDownloadError() { + if (!progressErrorContainer) return; + + progressErrorContainer.style.display = 'none'; + currentDownloadState.canRetry = false; + currentDownloadState.lastError = null; + currentDownloadState.errorType = null; + + if (progressOverlay) { + progressOverlay.classList.remove('error-state'); + } +} function setupAnimations() { document.body.style.opacity = '0'; @@ -568,24 +577,24 @@ function showNotification(message, type = 'info') { }, 5000); } -function setupUI() { - progressOverlay = document.getElementById('progressOverlay'); - progressBar = document.getElementById('progressBar'); - progressBarFill = document.getElementById('progressBarFill'); - progressText = document.getElementById('progressText'); - progressPercent = document.getElementById('progressPercent'); - progressSpeed = document.getElementById('progressSpeed'); - progressSize = document.getElementById('progressSize'); - progressErrorContainer = document.getElementById('progressErrorContainer'); - progressErrorMessage = document.getElementById('progressErrorMessage'); - progressRetryInfo = document.getElementById('progressRetryInfo'); - progressRetryBtn = document.getElementById('progressRetryBtn'); - - // Setup draggable progress bar - setupProgressDrag(); - - // Setup retry button - setupRetryButton(); +function setupUI() { + progressOverlay = document.getElementById('progressOverlay'); + progressBar = document.getElementById('progressBar'); + progressBarFill = document.getElementById('progressBarFill'); + progressText = document.getElementById('progressText'); + progressPercent = document.getElementById('progressPercent'); + progressSpeed = document.getElementById('progressSpeed'); + progressSize = document.getElementById('progressSize'); + progressErrorContainer = document.getElementById('progressErrorContainer'); + progressErrorMessage = document.getElementById('progressErrorMessage'); + progressRetryInfo = document.getElementById('progressRetryInfo'); + progressRetryBtn = document.getElementById('progressRetryBtn'); + + // Setup draggable progress bar + setupProgressDrag(); + + // Setup retry button + setupRetryButton(); lockPlayButton(true); @@ -753,175 +762,175 @@ function toggleMaximize() { } } -// Error categorization and user-friendly messages -function categorizeError(message) { - const msg = message.toLowerCase(); - - if (msg.includes('network') || msg.includes('connection') || msg.includes('offline')) { - return 'network'; - } else if (msg.includes('stalled') || msg.includes('timeout')) { - return 'stall'; - } else if (msg.includes('file') || msg.includes('disk')) { - return 'file'; - } else if (msg.includes('permission') || msg.includes('access')) { - return 'permission'; - } else if (msg.includes('server') || msg.includes('5')) { - return 'server'; - } else if (msg.includes('corrupted') || msg.includes('pwr file') || msg.includes('unexpected eof')) { - return 'corruption'; - } else if (msg.includes('butler') || msg.includes('patch installation')) { - return 'butler'; - } else if (msg.includes('space') || msg.includes('full') || msg.includes('device full')) { - return 'space'; - } else if (msg.includes('conflict') || msg.includes('already exists')) { - return 'conflict'; - } else { - return 'general'; - } -} - -function getErrorMessage(technicalMessage, errorType) { - // Technical errors go to console, user gets friendly messages - console.error(`Download error [${errorType}]:`, technicalMessage); - - switch (errorType) { - case 'network': - return 'Network connection lost. Please check your internet connection and retry.'; - case 'stall': - return 'Download stalled due to slow connection. Please retry.'; - case 'file': - return 'Unable to save file. Check disk space and permissions. Please retry.'; - case 'permission': - return 'Permission denied. Check if launcher has write access. Please retry.'; - case 'server': - return 'Server error. Please wait a moment and retry.'; - case 'corruption': - return 'Corrupted PWR file detected. File deleted and will retry.'; - case 'butler': - return 'Patch installation failed. Please retry.'; - case 'space': - return 'Insufficient disk space. Free up space and retry.'; - case 'conflict': - return 'Installation directory conflict. Please retry.'; - default: - return 'Download failed. Please retry.'; - } -} - -// Connection quality indicator (simplified) -function updateConnectionQuality(quality) { - if (!progressSize) return; - - const qualityColors = { - 'Good': '#10b981', - 'Fair': '#fbbf24', - 'Poor': '#f87171' - }; - - const color = qualityColors[quality] || '#6b7280'; - progressSize.style.color = color; - - // Add subtle quality indicator - if (progressSize.dataset.quality !== quality) { - progressSize.dataset.quality = quality; - progressSize.style.transition = 'color 0.5s ease'; - } -} - -// 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; - } - - // 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); - - if (!result.success) { - throw new Error(result.error || 'Retry failed'); - } - } 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; - } - }); -} - -function getRetryContextMessage() { - const errorType = currentDownloadState.errorType; - - switch (errorType) { - case 'network': - return 'Reconnecting and retrying download...'; - case 'stall': - return 'Resuming stalled download...'; - case 'server': - return 'Waiting for server and retrying...'; - case 'corruption': - return 'Re-downloading corrupted PWR file...'; - case 'butler': - return 'Re-attempting patch installation...'; - case 'space': - return 'Retrying after clearing disk space...'; - case 'permission': - return 'Retrying with corrected permissions...'; - case 'conflict': - return 'Retrying after resolving conflicts...'; - default: - return 'Initiating retry download...'; - } -} - -// Make toggleMaximize globally available -window.toggleMaximize = toggleMaximize; - -document.addEventListener('DOMContentLoaded', setupUI); +// Error categorization and user-friendly messages +function categorizeError(message) { + const msg = message.toLowerCase(); + + if (msg.includes('network') || msg.includes('connection') || msg.includes('offline')) { + return 'network'; + } else if (msg.includes('stalled') || msg.includes('timeout')) { + return 'stall'; + } else if (msg.includes('file') || msg.includes('disk')) { + return 'file'; + } else if (msg.includes('permission') || msg.includes('access')) { + return 'permission'; + } else if (msg.includes('server') || msg.includes('5')) { + return 'server'; + } else if (msg.includes('corrupted') || msg.includes('pwr file') || msg.includes('unexpected eof')) { + return 'corruption'; + } else if (msg.includes('butler') || msg.includes('patch installation')) { + return 'butler'; + } else if (msg.includes('space') || msg.includes('full') || msg.includes('device full')) { + return 'space'; + } else if (msg.includes('conflict') || msg.includes('already exists')) { + return 'conflict'; + } else { + return 'general'; + } +} + +function getErrorMessage(technicalMessage, errorType) { + // Technical errors go to console, user gets friendly messages + console.error(`Download error [${errorType}]:`, technicalMessage); + + switch (errorType) { + case 'network': + return 'Network connection lost. Please check your internet connection and retry.'; + case 'stall': + return 'Download stalled due to slow connection. Please retry.'; + case 'file': + return 'Unable to save file. Check disk space and permissions. Please retry.'; + case 'permission': + return 'Permission denied. Check if launcher has write access. Please retry.'; + case 'server': + return 'Server error. Please wait a moment and retry.'; + case 'corruption': + return 'Corrupted PWR file detected. File deleted and will retry.'; + case 'butler': + return 'Patch installation failed. Please retry.'; + case 'space': + return 'Insufficient disk space. Free up space and retry.'; + case 'conflict': + return 'Installation directory conflict. Please retry.'; + default: + return 'Download failed. Please retry.'; + } +} + +// Connection quality indicator (simplified) +function updateConnectionQuality(quality) { + if (!progressSize) return; + + const qualityColors = { + 'Good': '#10b981', + 'Fair': '#fbbf24', + 'Poor': '#f87171' + }; + + const color = qualityColors[quality] || '#6b7280'; + progressSize.style.color = color; + + // Add subtle quality indicator + if (progressSize.dataset.quality !== quality) { + progressSize.dataset.quality = quality; + progressSize.style.transition = 'color 0.5s ease'; + } +} + +// 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; + } + + // 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); + + if (!result.success) { + throw new Error(result.error || 'Retry failed'); + } + } 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; + } + }); +} + +function getRetryContextMessage() { + const errorType = currentDownloadState.errorType; + + switch (errorType) { + case 'network': + return 'Reconnecting and retrying download...'; + case 'stall': + return 'Resuming stalled download...'; + case 'server': + return 'Waiting for server and retrying...'; + case 'corruption': + return 'Re-downloading corrupted PWR file...'; + case 'butler': + return 'Re-attempting patch installation...'; + case 'space': + return 'Retrying after clearing disk space...'; + case 'permission': + return 'Retrying with corrected permissions...'; + case 'conflict': + return 'Retrying after resolving conflicts...'; + default: + return 'Initiating retry download...'; + } +} + +// Make toggleMaximize globally available +window.toggleMaximize = toggleMaximize; + +document.addEventListener('DOMContentLoaded', setupUI); diff --git a/backend/core/paths.js b/backend/core/paths.js index b82de75..17a7b92 100644 --- a/backend/core/paths.js +++ b/backend/core/paths.js @@ -1,6 +1,7 @@ const fs = require('fs'); const path = require('path'); const os = require('os'); +const { loadVersionBranch } = require('./config'); function getAppDir() { const home = os.homedir(); @@ -48,8 +49,20 @@ function expandHome(inputPath) { const APP_DIR = DEFAULT_APP_DIR; const CACHE_DIR = path.join(APP_DIR, 'cache'); const TOOLS_DIR = path.join(APP_DIR, 'butler'); -const GAME_DIR = path.join(APP_DIR, 'release', 'package', 'game', 'latest'); -const JRE_DIR = path.join(APP_DIR, 'release', 'package', 'jre', 'latest'); + +// Dynamic GAME_DIR and JRE_DIR based on version_branch from config +function getGameDir() { + const branch = loadVersionBranch(); + return path.join(APP_DIR, branch, 'package', 'game', 'latest'); +} + +function getJreDir() { + const branch = loadVersionBranch(); + return path.join(APP_DIR, branch, 'package', 'jre', 'latest'); +} + +const GAME_DIR = getGameDir(); +const JRE_DIR = getJreDir(); const PLAYER_ID_FILE = path.join(APP_DIR, 'player_id.json'); function getClientCandidates(gameLatest) { @@ -156,7 +169,8 @@ async function getModsPath(customInstallPath = null) { installPath = getAppDir(); } - const gameLatest = path.join(installPath, 'release', 'package', 'game', 'latest'); + const branch = loadVersionBranch(); + const gameLatest = path.join(installPath, branch, 'package', 'game', 'latest'); const userDataPath = findUserDataPath(gameLatest); @@ -195,7 +209,8 @@ function getProfilesDir(customInstallPath = null) { } if (!installPath) installPath = getAppDir(); - const gameLatest = path.join(installPath, 'release', 'package', 'game', 'latest'); + const branch = loadVersionBranch(); + const gameLatest = path.join(installPath, branch, 'package', 'game', 'latest'); const userDataPath = findUserDataPath(gameLatest); const profilesDir = path.join(userDataPath, 'Profiles'); @@ -219,6 +234,8 @@ module.exports = { TOOLS_DIR, GAME_DIR, JRE_DIR, + getGameDir, + getJreDir, PLAYER_ID_FILE, getClientCandidates, findClientPath, diff --git a/backend/managers/gameManager.js b/backend/managers/gameManager.js index d58e8c3..3087147 100644 --- a/backend/managers/gameManager.js +++ b/backend/managers/gameManager.js @@ -446,7 +446,11 @@ function isGameInstalled(branchOverride = null) { } async function installGame(playerName = 'Player', progressCallback, javaPathOverride, installPathOverride, branchOverride = null) { - const branch = branchOverride || loadVersionBranch(); + console.log(`[InstallGame] branchOverride parameter received: ${branchOverride}`); + const loadedBranch = loadVersionBranch(); + console.log(`[InstallGame] loadVersionBranch() returned: ${loadedBranch}`); + const branch = branchOverride || loadedBranch; + console.log(`[InstallGame] Final branch selected: ${branch}`); const customAppDir = getResolvedAppDir(installPathOverride); const customCacheDir = path.join(customAppDir, 'cache'); const customToolsDir = path.join(customAppDir, 'butler'); diff --git a/main.js b/main.js index 7b1cf60..7e67543 100644 --- a/main.js +++ b/main.js @@ -255,11 +255,11 @@ app.whenReady().then(async () => { mainWindow.webContents.send('lock-play-button', true); } - const progressCallback = (message, percent, speed, downloaded, total, retryState) => { - if (mainWindow && !mainWindow.isDestroyed()) { - mainWindow.webContents.send('first-launch-progress', { message, percent, speed, downloaded, total, retryState }); - } - }; + const progressCallback = (message, percent, speed, downloaded, total, retryState) => { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('first-launch-progress', { message, percent, speed, downloaded, total, retryState }); + } + }; const firstLaunchResult = await Promise.race([ handleFirstLaunchCheck(progressCallback), @@ -346,21 +346,21 @@ app.on('window-all-closed', () => { }); -ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, gpuPreference) => { - try { - const progressCallback = (message, percent, speed, downloaded, total, retryState) => { - if (mainWindow && !mainWindow.isDestroyed()) { - const data = { - message: message || null, - percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null, - speed: speed !== null && speed !== undefined ? speed : null, - downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null, - total: total !== null && total !== undefined ? total : null, - retryState: retryState || null - }; - mainWindow.webContents.send('progress-update', data); - } - }; +ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, gpuPreference) => { + try { + const progressCallback = (message, percent, speed, downloaded, total, retryState) => { + if (mainWindow && !mainWindow.isDestroyed()) { + const data = { + message: message || null, + percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null, + speed: speed !== null && speed !== undefined ? speed : null, + downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null, + total: total !== null && total !== undefined ? total : null, + retryState: retryState || null + }; + mainWindow.webContents.send('progress-update', data); + } + }; const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference); @@ -390,108 +390,113 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, g } }); -ipcMain.handle('install-game', async (event, playerName, javaPath, installPath, branch) => { - try { - console.log(`[IPC] install-game called with branch: ${branch || 'default'}`); - - // Signal installation start - if (mainWindow && !mainWindow.isDestroyed()) { - mainWindow.webContents.send('installation-start'); - } - - const progressCallback = (message, percent, speed, downloaded, total, retryState) => { - if (mainWindow && !mainWindow.isDestroyed()) { - const data = { - message: message || null, - percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null, - speed: speed !== null && speed !== undefined ? speed : null, - downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null, - total: total !== null && total !== undefined ? total : null, - retryState: retryState || null - }; - mainWindow.webContents.send('progress-update', data); - } - }; - - const result = await installGame(playerName, progressCallback, javaPath, installPath, branch); - - // Signal installation end - if (mainWindow && !mainWindow.isDestroyed()) { - mainWindow.webContents.send('installation-end'); - } - - // Ensure we always return a result for the IPC handler - const successResponse = result || { success: true }; - console.log('[Main] Returning success response for install-game:', successResponse); - return successResponse; - } catch (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, - retryData: null - }; - - // Handle Butler-specific errors - 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); - } - } - // Handle PWR download errors - else if (error.branch && error.fileName) { - console.log('[Main] Processing PWR download error with retry context'); - errorData.retryData = { - branch: error.branch, - fileName: error.fileName, - cacheDir: error.cacheDir - }; - errorData.canRetry = error.canRetry !== undefined ? error.canRetry : true; - } - // Default fallback for other errors - else { - console.log('[Main] Processing generic error, creating default retry data'); - errorData.retryData = { - branch: 'release', - fileName: '4.pwr' - }; - } - - // Send enhanced error info for retry UI - if (mainWindow && !mainWindow.isDestroyed()) { - console.log('[Main] Sending error data to renderer:', errorData); - mainWindow.webContents.send('progress-update', errorData); - } - - // Signal installation end on error too - if (mainWindow && !mainWindow.isDestroyed()) { - mainWindow.webContents.send('installation-end'); - } - - // Always return a proper response to prevent timeout - const errorResponse = { success: false, error: errorMessage }; - console.log('[Main] Returning error response for install-game:', errorResponse); - return errorResponse; - } +ipcMain.handle('install-game', async (event, playerName, javaPath, installPath, branch) => { + try { + console.log(`[IPC] install-game called with parameters:`); + console.log(` - playerName: ${playerName}`); + console.log(` - javaPath: ${javaPath}`); + console.log(` - installPath: ${installPath}`); + console.log(` - branch: ${branch}`); + console.log(`[IPC] branch type: ${typeof branch}, value: ${JSON.stringify(branch)}`); + + // Signal installation start + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('installation-start'); + } + + const progressCallback = (message, percent, speed, downloaded, total, retryState) => { + if (mainWindow && !mainWindow.isDestroyed()) { + const data = { + message: message || null, + percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null, + speed: speed !== null && speed !== undefined ? speed : null, + downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null, + total: total !== null && total !== undefined ? total : null, + retryState: retryState || null + }; + mainWindow.webContents.send('progress-update', data); + } + }; + + const result = await installGame(playerName, progressCallback, javaPath, installPath, branch); + + // Signal installation end + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('installation-end'); + } + + // Ensure we always return a result for the IPC handler + const successResponse = result || { success: true }; + console.log('[Main] Returning success response for install-game:', successResponse); + return successResponse; + } catch (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, + retryData: null + }; + + // Handle Butler-specific errors + 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); + } + } + // Handle PWR download errors + else if (error.branch && error.fileName) { + console.log('[Main] Processing PWR download error with retry context'); + errorData.retryData = { + branch: error.branch, + fileName: error.fileName, + cacheDir: error.cacheDir + }; + errorData.canRetry = error.canRetry !== undefined ? error.canRetry : true; + } + // Default fallback for other errors + else { + console.log('[Main] Processing generic error, creating default retry data'); + errorData.retryData = { + branch: 'release', + fileName: '4.pwr' + }; + } + + // Send enhanced error info for retry UI + if (mainWindow && !mainWindow.isDestroyed()) { + console.log('[Main] Sending error data to renderer:', errorData); + mainWindow.webContents.send('progress-update', errorData); + } + + // Signal installation end on error too + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('installation-end'); + } + + // Always return a proper response to prevent timeout + const errorResponse = { success: false, error: errorMessage }; + console.log('[Main] Returning error response for install-game:', errorResponse); + return errorResponse; + } }); ipcMain.handle('save-username', (event, username) => { @@ -581,19 +586,19 @@ ipcMain.handle('select-install-path', async () => { ipcMain.handle('accept-first-launch-update', async (event, existingGame) => { try { - const progressCallback = (message, percent, speed, downloaded, total, retryState) => { - if (mainWindow && !mainWindow.isDestroyed()) { - const data = { - message: message || null, - percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null, - speed: speed !== null && speed !== undefined ? speed : null, - downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null, - total: total !== null && total !== undefined ? total : null, - retryState: retryState || null - }; - mainWindow.webContents.send('first-launch-progress', data); - } - }; + const progressCallback = (message, percent, speed, downloaded, total, retryState) => { + if (mainWindow && !mainWindow.isDestroyed()) { + const data = { + message: message || null, + percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null, + speed: speed !== null && speed !== undefined ? speed : null, + downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null, + total: total !== null && total !== undefined ? total : null, + retryState: retryState || null + }; + mainWindow.webContents.send('first-launch-progress', data); + } + }; const result = await proposeGameUpdate(existingGame, progressCallback); @@ -636,94 +641,94 @@ ipcMain.handle('uninstall-game', async () => { } }); -ipcMain.handle('repair-game', async () => { - try { - const progressCallback = (message, percent, speed, downloaded, total, retryState) => { - if (mainWindow && !mainWindow.isDestroyed()) { - const data = { - message: message || null, - percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null, - speed: speed !== null && speed !== undefined ? speed : null, - downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null, - total: total !== null && total !== undefined ? total : null, - retryState: retryState || null - }; - mainWindow.webContents.send('progress-update', data); - } - }; - - const result = await repairGame(progressCallback); - return result; - } catch (error) { - console.error('Repair error:', error); - const errorMessage = error.message || error.toString(); - return { success: false, error: errorMessage }; - } -}); - -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 = { - message: message || null, - percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null, - speed: speed !== null && speed !== undefined ? speed : null, - downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null, - total: total !== null && total !== undefined ? total : null, - retryState: retryState || null - }; - mainWindow.webContents.send('progress-update', data); - } - }; - - // 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}`); - - // Perform the retry with enhanced context - await retryPWRDownload(branch, fileName, progressCallback, cacheDir); - - return { success: true }; - } catch (error) { - console.error('Retry download error:', error); - const errorMessage = error.message || error.toString(); - - // Send error update to frontend with context - if (mainWindow && !mainWindow.isDestroyed()) { - const data = { - message: errorMessage, - error: true, - canRetry: true, - retryData: { - branch: retryData?.branch || 'release', - fileName: retryData?.fileName || '4.pwr', - cacheDir: retryData?.cacheDir - } - }; - mainWindow.webContents.send('progress-update', data); - } - - // Always return a proper response to prevent timeout - const errorResponse = { success: false, error: errorMessage }; - console.log('[Main] Returning error response for retry-download:', errorResponse); - return errorResponse; - } -}); +ipcMain.handle('repair-game', async () => { + try { + const progressCallback = (message, percent, speed, downloaded, total, retryState) => { + if (mainWindow && !mainWindow.isDestroyed()) { + const data = { + message: message || null, + percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null, + speed: speed !== null && speed !== undefined ? speed : null, + downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null, + total: total !== null && total !== undefined ? total : null, + retryState: retryState || null + }; + mainWindow.webContents.send('progress-update', data); + } + }; + + const result = await repairGame(progressCallback); + return result; + } catch (error) { + console.error('Repair error:', error); + const errorMessage = error.message || error.toString(); + return { success: false, error: errorMessage }; + } +}); + +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 = { + message: message || null, + percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null, + speed: speed !== null && speed !== undefined ? speed : null, + downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null, + total: total !== null && total !== undefined ? total : null, + retryState: retryState || null + }; + mainWindow.webContents.send('progress-update', data); + } + }; + + // 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}`); + + // Perform the retry with enhanced context + await retryPWRDownload(branch, fileName, progressCallback, cacheDir); + + return { success: true }; + } catch (error) { + console.error('Retry download error:', error); + const errorMessage = error.message || error.toString(); + + // Send error update to frontend with context + if (mainWindow && !mainWindow.isDestroyed()) { + const data = { + message: errorMessage, + error: true, + canRetry: true, + retryData: { + branch: retryData?.branch || 'release', + fileName: retryData?.fileName || '4.pwr', + cacheDir: retryData?.cacheDir + } + }; + mainWindow.webContents.send('progress-update', data); + } + + // Always return a proper response to prevent timeout + const errorResponse = { success: false, error: errorMessage }; + console.log('[Main] Returning error response for retry-download:', errorResponse); + return errorResponse; + } +}); ipcMain.handle('get-hytale-news', async () => { try { @@ -747,8 +752,9 @@ ipcMain.handle('open-external', async (event, url) => { ipcMain.handle('open-game-location', async () => { try { - const { getResolvedAppDir } = require('./backend/launcher'); - const gameDir = path.join(getResolvedAppDir(), 'release', 'package', 'game'); + const { getResolvedAppDir, loadVersionBranch } = require('./backend/launcher'); + const branch = loadVersionBranch(); + const gameDir = path.join(getResolvedAppDir(), branch, 'package', 'game'); if (fs.existsSync(gameDir)) { await shell.openPath(gameDir); @@ -839,7 +845,7 @@ ipcMain.handle('load-settings', async () => { } }); -const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod, getCurrentUuid, getAllUuidMappings, setUuidForUser, generateNewUuid, deleteUuidForUser, resetCurrentUserUuid } = require('./backend/launcher'); +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'); diff --git a/preload.js b/preload.js index cb88bc0..33876c7 100644 --- a/preload.js +++ b/preload.js @@ -2,7 +2,7 @@ const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('electronAPI', { launchGame: (playerName, javaPath, installPath, gpuPreference) => ipcRenderer.invoke('launch-game', playerName, javaPath, installPath, gpuPreference), - installGame: (playerName, javaPath, installPath) => ipcRenderer.invoke('install-game', playerName, javaPath, installPath), + installGame: (playerName, javaPath, installPath, branch) => ipcRenderer.invoke('install-game', playerName, javaPath, installPath, branch), closeWindow: () => ipcRenderer.invoke('window-close'), minimizeWindow: () => ipcRenderer.invoke('window-minimize'), maximizeWindow: () => ipcRenderer.invoke('window-maximize'),