From 8e9af9c768c6cc6ab1e77ddb35e6d7829eacc339 Mon Sep 17 00:00:00 2001 From: Fazri Gading Date: Mon, 19 Jan 2026 00:08:40 +0800 Subject: [PATCH 1/6] add save-load GPU preference handler --- main.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/main.js b/main.js index 10719a4..cfe26a1 100644 --- a/main.js +++ b/main.js @@ -276,7 +276,7 @@ app.on('window-all-closed', () => { } }); -ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath) => { +ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, gpuPreference) => { try { const progressCallback = (message, percent, speed, downloaded, total) => { if (mainWindow && !mainWindow.isDestroyed()) { @@ -291,7 +291,7 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath) = } }; - const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath); + const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference); return result; } catch (error) { @@ -704,6 +704,26 @@ ipcMain.handle('get-update-info', async () => { return updateManager.getUpdateInfo(); }); +ipcMain.handle('get-gpu-info', () => { + try { + return app.getGPUInfo('complete'); + } catch (error) { + console.error('Error getting GPU info:', error); + return {}; + } +}); + +ipcMain.handle('save-gpu-preference', (event, gpuPreference) => { + const { saveGPUPreference } = require('./backend/launcher'); + saveGPUPreference(gpuPreference); + return { success: true }; +}); + +ipcMain.handle('load-gpu-preference', () => { + const { loadGPUPreference } = require('./backend/launcher'); + return loadGPUPreference(); +}); + ipcMain.handle('window-close', () => { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.close(); From 2671a59f38f883af0997e7da66d70f4226e29f54 Mon Sep 17 00:00:00 2001 From: Fazri Gading Date: Mon, 19 Jan 2026 00:10:15 +0800 Subject: [PATCH 2/6] Revert "add save-load GPU preference handler" This reverts commit b957a76aede8f9425b0a283d1444a32f55baa95a. --- main.js | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/main.js b/main.js index cfe26a1..10719a4 100644 --- a/main.js +++ b/main.js @@ -276,7 +276,7 @@ app.on('window-all-closed', () => { } }); -ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, gpuPreference) => { +ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath) => { try { const progressCallback = (message, percent, speed, downloaded, total) => { if (mainWindow && !mainWindow.isDestroyed()) { @@ -291,7 +291,7 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, g } }; - const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference); + const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath); return result; } catch (error) { @@ -704,26 +704,6 @@ ipcMain.handle('get-update-info', async () => { return updateManager.getUpdateInfo(); }); -ipcMain.handle('get-gpu-info', () => { - try { - return app.getGPUInfo('complete'); - } catch (error) { - console.error('Error getting GPU info:', error); - return {}; - } -}); - -ipcMain.handle('save-gpu-preference', (event, gpuPreference) => { - const { saveGPUPreference } = require('./backend/launcher'); - saveGPUPreference(gpuPreference); - return { success: true }; -}); - -ipcMain.handle('load-gpu-preference', () => { - const { loadGPUPreference } = require('./backend/launcher'); - return loadGPUPreference(); -}); - ipcMain.handle('window-close', () => { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.close(); From 905a9d754c67fc114a3e5cb08d7d82056b94cd6e Mon Sep 17 00:00:00 2001 From: Fazri Gading Date: Tue, 20 Jan 2026 23:45:38 +0800 Subject: [PATCH 3/6] Merge GPU preference feature branch to main branch version 2.0.2 for testing (#3) * modernized UI for GPU Preference option * feat: auto-detect dedicated GPU on hybrid laptops (iGPU+dGPU) * feat: detailed GPU info in auto-detection feature on startup * feat: add GPU options for launcher - Add GPU preference setting (Auto/Integrated/Dedicated) - Implement Linux GPU selection with DRI_PRIME and NVIDIA env vars - Add GPU detection using Electron's app.getGPUInfo() - Update settings UI with GPU preference dropdown - Integrate GPU preference into game launch process * feat: auto-detect dedicated GPU on hybrid laptops (iGPU+dGPU) * added fallbacks to and option to use integrated GPU. * add package-lock and fix deps version * changed 'Nvidia' string to 'NVIDIA' * fix: selecting `dedicated` option while using nvidia GPU did not set its specific env variables * remove unused `CONFIG_FILE` variable on launcher core modules * fix: duplicated save-load gpu detection functions * move game option settings to the top, while custom java to the bottom * fix: settings-header margin-bottom from 3rem to 1rem and supress line-clamp warning --- GUI/index.html | 77 ++++++++++++++++++++++++++ GUI/js/launcher.js | 13 ++++- GUI/js/settings.js | 86 +++++++++++++++++++++++++++++ GUI/style.css | 62 ++++++++++++++++++++- backend/core/config.js | 14 ++++- backend/launcher.js | 16 ++++-- backend/managers/gameLauncher.js | 17 +++--- backend/utils/platformUtils.js | 93 +++++++++++++++++++++++++++++++- main.js | 48 +++++++++++++++-- package.json | 3 -- preload.js | 7 ++- 11 files changed, 413 insertions(+), 23 deletions(-) diff --git a/GUI/index.html b/GUI/index.html index db18cb6..b97cf8f 100644 --- a/GUI/index.html +++ b/GUI/index.html @@ -365,6 +365,41 @@ + +
+ +
+ + + + + + +
+

+ + Select your preferred GPU (Linux: affects DRI_PRIME) +

+
+
+ + +
+

+ + Discord Integration +

+ +
+ +
@@ -406,6 +441,48 @@
+ +
+

+ + Java Runtime +

+ +
+ +
+ + +
+ diff --git a/GUI/js/launcher.js b/GUI/js/launcher.js index 8dc2cd5..2aa78ac 100644 --- a/GUI/js/launcher.js +++ b/GUI/js/launcher.js @@ -211,6 +211,15 @@ export async function launch() { if (window.SettingsAPI && window.SettingsAPI.getCurrentJavaPath) { javaPath = window.SettingsAPI.getCurrentJavaPath(); } + + let gpuPreference = 'auto'; + try { + if (window.electronAPI && window.electronAPI.loadGpuPreference) { + gpuPreference = await window.electronAPI.loadGpuPreference(); + } + } catch (error) { + console.error('Error loading GPU preference:', error); + } if (window.LauncherUI) window.LauncherUI.showProgress(); isDownloading = true; @@ -223,8 +232,8 @@ export async function launch() { if (window.LauncherUI) window.LauncherUI.updateProgress({ message: 'Starting game...' }); if (window.electronAPI && window.electronAPI.launchGame) { - const result = await window.electronAPI.launchGame(playerName, javaPath, ''); - + const result = await window.electronAPI.launchGame(playerName, javaPath, '', gpuPreference); + isDownloading = false; if (window.LauncherUI) { diff --git a/GUI/js/settings.js b/GUI/js/settings.js index 0910734..b62979b 100644 --- a/GUI/js/settings.js +++ b/GUI/js/settings.js @@ -5,6 +5,7 @@ let customJavaPath; let browseJavaBtn; let settingsPlayerName; let discordRPCCheck; +let gpuPreferenceRadios; // UUID Management elements let currentUuidDisplay; @@ -142,6 +143,7 @@ function showCustomConfirm(message, title = 'Confirm Action', onConfirm, onCance document.addEventListener('keydown', handleEscape); } + export function initSettings() { setupSettingsElements(); loadAllSettings(); @@ -154,6 +156,7 @@ function setupSettingsElements() { browseJavaBtn = document.getElementById('browseJavaBtn'); settingsPlayerName = document.getElementById('settingsPlayerName'); discordRPCCheck = document.getElementById('discordRPCCheck'); + gpuPreferenceRadios = document.querySelectorAll('input[name="gpuPreference"]'); // UUID Management elements currentUuidDisplay = document.getElementById('currentUuid'); @@ -226,6 +229,15 @@ function setupSettingsElements() { } }); } + + if (gpuPreferenceRadios) { + gpuPreferenceRadios.forEach(radio => { + radio.addEventListener('change', async () => { + await saveGpuPreference(); + await updateGpuLabel(); + }); + }); + } } function toggleCustomJava() { @@ -361,11 +373,85 @@ async function loadPlayerName() { } } +async function saveGpuPreference() { + try { + if (window.electronAPI && window.electronAPI.saveGpuPreference && gpuPreferenceRadios) { + const gpuPreference = Array.from(gpuPreferenceRadios).find(radio => radio.checked)?.value || 'auto'; + await window.electronAPI.saveGpuPreference(gpuPreference); + } + } catch (error) { + console.error('Error saving GPU preference:', error); + } +} + +async function updateGpuLabel() { + const detectionInfo = document.getElementById('gpu-detection-info'); + if (!detectionInfo) return; + + if (gpuPreferenceRadios) { + const checked = Array.from(gpuPreferenceRadios).find(radio => radio.checked); + if (checked) { + try { + if (window.electronAPI && window.electronAPI.getDetectedGpu) { + const detected = await window.electronAPI.getDetectedGpu(); + if (checked.value === 'auto') { + if (detected.dedicatedName) { + detectionInfo.textContent = `dGPU detected, using ${detected.dedicatedName}`; + } else { + detectionInfo.textContent = `dGPU not detected, using iGPU (${detected.integratedName}) instead`; + } + detectionInfo.style.display = 'block'; + } else if (checked.value === 'integrated') { + detectionInfo.textContent = `Detected: ${detected.integratedName}`; + detectionInfo.style.display = 'block'; + } else if (checked.value === 'dedicated') { + if (detected.dedicatedName) { + detectionInfo.textContent = `Detected: ${detected.dedicatedName}`; + } else { + detectionInfo.textContent = `No dedicated GPU detected`; + } + detectionInfo.style.display = 'block'; + } else { + detectionInfo.style.display = 'none'; + } + } + } catch (error) { + console.error('Error getting detected GPU:', error); + detectionInfo.style.display = 'none'; + } + } else { + detectionInfo.style.display = 'none'; + } + } else { + detectionInfo.style.display = 'none'; + } +} + +async function loadGpuPreference() { + try { + if (window.electronAPI && window.electronAPI.loadGpuPreference && gpuPreferenceRadios) { + const savedPreference = await window.electronAPI.loadGpuPreference(); + if (savedPreference) { + for (const radio of gpuPreferenceRadios) { + if (radio.value === savedPreference) { + radio.checked = true; + break; + } + } + await updateGpuLabel(); + } + } + } catch (error) { + console.error('Error loading GPU preference:', error); + } +} + async function loadAllSettings() { await loadCustomJavaPath(); await loadPlayerName(); await loadCurrentUuid(); await loadDiscordRPC(); + await loadGpuPreference(); } async function openGameLocation() { diff --git a/GUI/style.css b/GUI/style.css index 75eef8f..ae95b6b 100644 --- a/GUI/style.css +++ b/GUI/style.css @@ -1005,6 +1005,7 @@ body { transform: translateY(0.5rem); transition: all 0.3s ease 0.1s; display: -webkit-box; + line-clamp: 2; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; @@ -1888,6 +1889,7 @@ body { line-height: 1.5; margin: 0.75rem 0; display: -webkit-box; + line-clamp: 3; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; @@ -2675,6 +2677,7 @@ body { color: rgba(255, 255, 255, 0.5); line-height: 1.4; display: -webkit-box; + line-clamp: 2; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; @@ -3095,6 +3098,7 @@ body { overflow: hidden; text-overflow: ellipsis; display: -webkit-box; + line-clamp: 2; -webkit-line-clamp: 2; -webkit-box-orient: vertical; word-break: break-word; @@ -3758,7 +3762,7 @@ body { } .settings-header { - margin-bottom: 3rem; + margin-bottom: 1rem; text-align: center; } @@ -4047,6 +4051,14 @@ body { font-size: 0.75rem; } +.gpu-detection-info { + margin-top: 0.5rem; + font-size: 0.8rem; + color: rgba(255, 255, 255, 0.7); + font-weight: 500; + display: none; +} + #settings-page { opacity: 0; @@ -4078,7 +4090,53 @@ body { background: linear-gradient(135deg, #a855f7, #60a5fa); } - +.segmented-control { + display: flex; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + backdrop-filter: blur(10px); + overflow: hidden; + transition: all 0.3s ease; +} +.segmented-control input[type="radio"] { + display: none; +} +.segmented-control label { + flex: 1; + padding: 0.75rem 1rem; + text-align: center; + cursor: pointer; + transition: all 0.3s ease; + color: #d1d5db; + font-size: 0.875rem; + background: transparent; + border-right: 1px solid rgba(255, 255, 255, 0.1); + font-family: 'JetBrains Mono', monospace; + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 500; +} +.segmented-control label:last-child { + border-right: none; +} +.segmented-control input[type="radio"]:checked + label { + background: linear-gradient(135deg, #9333ea, #3b82f6); + color: white; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); +} +.segmented-control label:hover { + background: rgba(255, 255, 255, 0.1); + color: white; +} +.segmented-control input[type="radio"]:checked + label:hover { + background: linear-gradient(135deg, #7c3aed, #2563eb); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2); +} +.segmented-control:focus-within { + border-color: rgba(147, 51, 234, 0.3); + box-shadow: 0 0 20px rgba(147, 51, 234, 0.1); +} #update-popup-overlay { diff --git a/backend/core/config.js b/backend/core/config.js index 3f048e7..c5f2d1b 100644 --- a/backend/core/config.js +++ b/backend/core/config.js @@ -277,6 +277,15 @@ function loadChatColor() { return config.chatColor || '#3498db'; } +function saveGpuPreference(gpuPreference) { + saveConfig({ gpuPreference: gpuPreference || 'auto' }); +} + +function loadGpuPreference() { + const config = loadConfig(); + return config.gpuPreference || 'auto'; +} + module.exports = { loadConfig, saveConfig, @@ -308,5 +317,8 @@ module.exports = { setUuidForUser, generateNewUuid, deleteUuidForUser, - resetCurrentUserUuid + resetCurrentUserUuid, + // GPU Preference exports + saveGpuPreference, + loadGpuPreference }; diff --git a/backend/launcher.js b/backend/launcher.js index 405fba6..0299639 100644 --- a/backend/launcher.js +++ b/backend/launcher.js @@ -20,14 +20,16 @@ const { getUuidForUser, isFirstLaunch, markAsLaunched, - CONFIG_FILE, // UUID Management getCurrentUuid, getAllUuidMappings, setUuidForUser, generateNewUuid, deleteUuidForUser, - resetCurrentUserUuid + resetCurrentUserUuid, + // GPU Preference + saveGpuPreference, + loadGpuPreference } = require('./core/config'); const { getResolvedAppDir, getModsPath } = require('./core/paths'); @@ -77,6 +79,9 @@ const { handleFirstLaunchCheck } = require('./services/firstLaunch'); +// Utils +const { detectGpu } = require('./utils/platformUtils'); + // Re-export all functions to maintain backward compatibility module.exports = { // Game launch functions @@ -111,6 +116,11 @@ module.exports = { saveDiscordRPC, loadDiscordRPC, + // GPU Preference functions + saveGpuPreference, + loadGpuPreference, + detectGpu, + // Version functions getInstalledClientVersion, getLatestClientVersion, @@ -150,7 +160,7 @@ module.exports = { checkExistingGameInstallation, proposeGameUpdate, handleFirstLaunchCheck, - + // Path functions getResolvedAppDir }; diff --git a/backend/managers/gameLauncher.js b/backend/managers/gameLauncher.js index 46425b7..19d1c20 100644 --- a/backend/managers/gameLauncher.js +++ b/backend/managers/gameLauncher.js @@ -6,7 +6,7 @@ const { promisify } = require('util'); const { spawn } = require('child_process'); const { v4: uuidv4 } = require('uuid'); const { getResolvedAppDir, findClientPath } = require('../core/paths'); -const { setupWaylandEnvironment } = require('../utils/platformUtils'); +const { setupWaylandEnvironment, setupGpuEnvironment } = require('../utils/platformUtils'); const { saveUsername, saveInstallPath, loadJavaPath, getUuidForUser, getAuthServerUrl, getAuthDomain } = require('../core/config'); const { resolveJavaPath, getJavaExec, getBundledJavaPath, detectSystemJava, JAVA_EXECUTABLE } = require('./javaManager'); const { getInstalledClientVersion, getLatestClientVersion } = require('../services/versionManager'); @@ -101,7 +101,7 @@ function generateLocalTokens(uuid, name) { }; } -async function launchGame(playerName = 'Player', progressCallback, javaPathOverride, installPathOverride) { +async function launchGame(playerName = 'Player', progressCallback, javaPathOverride, installPathOverride, gpuPreference = 'auto') { const customAppDir = getResolvedAppDir(installPathOverride); const customGameDir = path.join(customAppDir, 'release', 'package', 'game', 'latest'); const customJreDir = path.join(customAppDir, 'release', 'package', 'jre', 'latest'); @@ -276,10 +276,13 @@ exec "$REAL_JAVA" "\${ARGS[@]}" console.log('Starting game...'); console.log(`Command: "${clientPath}" ${args.join(' ')}`); - const env = { ...process.env }; + const env = { ...process.env }; - const waylandEnv = setupWaylandEnvironment(); - Object.assign(env, waylandEnv); + const waylandEnv = setupWaylandEnvironment(); + Object.assign(env, waylandEnv); + + const gpuEnv = setupGpuEnvironment(gpuPreference); + Object.assign(env, gpuEnv); try { let spawnOptions = { @@ -352,7 +355,7 @@ exec "$REAL_JAVA" "\${ARGS[@]}" } } -async function launchGameWithVersionCheck(playerName = 'Player', progressCallback, javaPathOverride, installPathOverride) { +async function launchGameWithVersionCheck(playerName = 'Player', progressCallback, javaPathOverride, installPathOverride, gpuPreference = 'auto') { try { if (progressCallback) { progressCallback('Checking for updates...', 0, null, null, null); @@ -403,7 +406,7 @@ async function launchGameWithVersionCheck(playerName = 'Player', progressCallbac progressCallback('Launching game...', 80, null, null, null); } - return await launchGame(playerName, progressCallback, javaPathOverride, installPathOverride); + return await launchGame(playerName, progressCallback, javaPathOverride, installPathOverride, gpuPreference); } catch (error) { console.error('Error in version check and launch:', error); if (progressCallback) { diff --git a/backend/utils/platformUtils.js b/backend/utils/platformUtils.js index 6460eb8..91e255a 100644 --- a/backend/utils/platformUtils.js +++ b/backend/utils/platformUtils.js @@ -65,9 +65,100 @@ function setupWaylandEnvironment() { return envVars; } +function detectGpu() { + if (process.platform !== 'linux') { + return { mode: 'integrated', vendor: 'intel', integratedName: 'Unknown', dedicatedName: null }; + } + + try { + const output = execSync('lspci -nn | grep \'VGA\\|3D\'', { encoding: 'utf8' }); + // console.log('GPU detection raw output:', output); + const lines = output.split('\n').filter(line => line.trim()); + // console.log('GPU detection parsed lines:', lines); + + let integratedName = null; + let dedicatedName = null; + let hasNvidia = false; + let hasAmd = false; + + for (const line of lines) { + // console.log('Checking line:', line); + if (line.includes('VGA') || line.includes('3D')) { + // console.log('Line contains VGA or 3D'); + + const match = line.match(/\[([^\]]+)\]/g); + let modelName = null; + if (match && match.length >= 2) { + modelName = match[1].slice(1, -1); + } + + if (line.includes('10de:') || line.toLowerCase().includes('nvidia')) { + hasNvidia = true; + dedicatedName = "NVIDIA " + modelName || 'NVIDIA GPU'; + console.log('Detected NVIDIA GPU:', dedicatedName); + } else if (line.includes('1002:') || line.toLowerCase().includes('amd') || line.toLowerCase().includes('radeon')) { + hasAmd = true; + dedicatedName = "AMD " + modelName || 'AMD GPU'; + console.log('Detected AMD GPU:', dedicatedName); + } else if (line.includes('8086:') || line.toLowerCase().includes('intel')) { + integratedName = "Intel " + modelName || 'Intel GPU'; + console.log('Detected Intel GPU:', integratedName); + } + } + } + + // console.log('hasNvidia:', hasNvidia, 'hasAmd:', hasAmd, 'integratedName:', integratedName, 'dedicatedName:', dedicatedName); + + if (hasNvidia) { + return { mode: 'dedicated', vendor: 'nvidia', integratedName: integratedName || 'Intel GPU', dedicatedName }; + } else if (hasAmd) { + return { mode: 'dedicated', vendor: 'amd', integratedName: integratedName || 'Intel GPU', dedicatedName }; + } else { + return { mode: 'integrated', vendor: 'intel', integratedName: integratedName || 'Intel GPU', dedicatedName: null }; + } + } catch (error) { + console.warn('GPU detection failed, falling back to integrated:', error.message); + return { mode: 'integrated', vendor: 'intel', integratedName: 'Unknown', dedicatedName: null }; + } +} + +function setupGpuEnvironment(gpuPreference) { + if (process.platform !== 'linux') { + return {}; + } + + let finalPreference = gpuPreference; + let detected = detectGpu(); + + if (gpuPreference === 'auto') { + finalPreference = detected.mode; + console.log(`Auto-detected GPU: ${detected.vendor} (${detected.mode})`); + } + + console.log('Preferred GPU set to:', finalPreference); + + const envVars = {}; + + if (finalPreference === 'dedicated') { + envVars.DRI_PRIME = '1'; + if (detected.vendor === 'nvidia') { + envVars.__NV_PRIME_RENDER_OFFLOAD = '1'; + envVars.__GLX_VENDOR_LIBRARY_NAME = 'nvidia'; + envVars.__GL_SHADER_DISK_CACHE = '1'; + envVars.__GL_SHADER_DISK_CACHE_PATH = '/tmp'; + } + console.log('GPU environment variables:', envVars); + } else { + console.log('Using integrated GPU, no environment variables set'); + } + return envVars; +} + module.exports = { getOS, getArch, isWaylandSession, - setupWaylandEnvironment + setupWaylandEnvironment, + detectGpu, + setupGpuEnvironment }; diff --git a/main.js b/main.js index b914b22..919224d 100644 --- a/main.js +++ b/main.js @@ -161,6 +161,19 @@ app.whenReady().then(async () => { console.log('Node.js version:', process.versions.node); console.log('Log directory:', logger.getLogDirectory()); + try { + const { loadGpuPreference, detectGpu } = require('./backend/launcher'); + const savedPreference = loadGpuPreference(); + if (savedPreference === 'auto') { + global.detectedGpu = detectGpu(); // if 'auto' selected = preload GPU detection + console.log('GPU auto-detection completed on startup:', global.detectedGpu); + } else { + console.log('GPU preference is manual, skipping auto-detection'); + } + } catch (error) { + console.warn('Failed to preload GPU detection:', error.message); + global.detectedGpu = { mode: 'integrated', vendor: 'intel' }; + } // Initialize Profile Manager (runs migration if needed) @@ -282,7 +295,7 @@ app.on('window-all-closed', () => { } }); -ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath) => { +ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, gpuPreference) => { try { const progressCallback = (message, percent, speed, downloaded, total) => { if (mainWindow && !mainWindow.isDestroyed()) { @@ -297,8 +310,8 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath) = } }; - const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath); - + const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference); + return result; } catch (error) { console.error('Launch error:', error); @@ -710,6 +723,35 @@ ipcMain.handle('get-update-info', async () => { return updateManager.getUpdateInfo(); }); +ipcMain.handle('get-gpu-info', () => { + try { + return app.getGPUInfo('complete'); + } catch (error) { + console.error('Error getting GPU info:', error); + return {}; + } +}); + +ipcMain.handle('save-gpu-preference', (event, gpuPreference) => { + const { saveGpuPreference } = require('./backend/launcher'); + saveGpuPreference(gpuPreference); + return { success: true }; +}); + +ipcMain.handle('load-gpu-preference', () => { + const { loadGpuPreference } = require('./backend/launcher'); + return loadGpuPreference(); +}); + +ipcMain.handle('get-detected-gpu', () => { + if (global.detectedGpu) { + return global.detectedGpu; + } + const { detectGpu } = require('./backend/launcher'); + global.detectedGpu = detectGpu(); + return global.detectedGpu; +}); + ipcMain.handle('window-close', () => { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.close(); diff --git a/package.json b/package.json index 986f9eb..2d79aa9 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,3 @@ } } } - - - diff --git a/preload.js b/preload.js index 93ac491..fb3aafc 100644 --- a/preload.js +++ b/preload.js @@ -1,7 +1,7 @@ const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('electronAPI', { - launchGame: (playerName, javaPath, installPath) => ipcRenderer.invoke('launch-game', playerName, javaPath, installPath), + launchGame: (playerName, javaPath, installPath, gpuPreference) => ipcRenderer.invoke('launch-game', playerName, javaPath, installPath, gpuPreference), installGame: (playerName, javaPath, installPath) => ipcRenderer.invoke('install-game', playerName, javaPath, installPath), closeWindow: () => ipcRenderer.invoke('window-close'), minimizeWindow: () => ipcRenderer.invoke('window-minimize'), @@ -48,6 +48,11 @@ contextBridge.exposeInMainWorld('electronAPI', { onUpdatePopup: (callback) => { ipcRenderer.on('show-update-popup', (event, data) => callback(data)); }, + + getGpuInfo: () => ipcRenderer.invoke('get-gpu-info'), + saveGpuPreference: (gpuPreference) => ipcRenderer.invoke('save-gpu-preference', gpuPreference), + loadGpuPreference: () => ipcRenderer.invoke('load-gpu-preference'), + getDetectedGpu: () => ipcRenderer.invoke('get-detected-gpu'), acceptFirstLaunchUpdate: (existingGame) => ipcRenderer.invoke('accept-first-launch-update', existingGame), markAsLaunched: () => ipcRenderer.invoke('mark-as-launched'), From 0a5c3db710a3f8494bca722730329a529f3e9905 Mon Sep 17 00:00:00 2001 From: Fazri Gading Date: Wed, 21 Jan 2026 00:27:11 +0800 Subject: [PATCH 4/6] add 3 package formats for Linux, descriptions, and update launcher version to 2.0.2 --- package.json | 53 ++++++++++++++-------------------------------------- 1 file changed, 14 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index 986f9eb..c4c18ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hytale-f2p-launcherv2", - "version": "2.0.1", + "version": "2.0.2", "description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support", "homepage": "https://github.com/amiayweb/Hytale-F2P", "main": "main.js", @@ -58,35 +58,19 @@ ], "win": { "target": [ - { - "target": "nsis", - "arch": [ - "x64" - ] - }, - { - "target": "portable", - "arch": [ - "x64" - ] - } + { "target": "nsis", "arch": ["x64"]}, + { "target": "portable", "arch": ["x64"]} ], - "icon": "icon.ico" + "icon": "icon.ico", + "description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support" }, "linux": { "target": [ - { - "target": "AppImage", - "arch": [ - "x64" - ] - }, - { - "target": "deb", - "arch": [ - "x64" - ] - } + { "target": "AppImage", "arch": ["x64"] }, + { "target": "deb", "arch": ["x64"] }, + { "target": "rpm", "arch": ["x64"] }, + { "target": "pacman", "arch": ["x64"] }, + { "target": "snap", "arch": ["x64"] } ], "icon": "build/icon.png", "category": "Game", @@ -94,21 +78,12 @@ }, "mac": { "target": [ - { - "target": "dmg", - "arch": [ - "universal" - ] - }, - { - "target": "zip", - "arch": [ - "universal" - ] - } + { "target": "dmg", "arch": ["universal"] }, + { "target": "zip", "arch": ["universal"]} ], "icon": "build/icon.icns", - "category": "public.app-category.games" + "category": "public.app-category.games", + "description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support" }, "nsis": { "oneClick": false, From f8333c09cdef6d5fff22e2f3c2addc3aa494289f Mon Sep 17 00:00:00 2001 From: Fazri Gading Date: Wed, 21 Jan 2026 00:43:29 +0800 Subject: [PATCH 5/6] Update package.json --- package.json | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2afdc2f..39aefc4 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,7 @@ { "target": "nsis", "arch": ["x64"]}, { "target": "portable", "arch": ["x64"]} ], - "icon": "icon.ico", - "description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support" + "icon": "icon.ico" }, "linux": { "target": [ @@ -73,8 +72,7 @@ { "target": "snap", "arch": ["x64"] } ], "icon": "build/icon.png", - "category": "Game", - "description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support" + "category": "Game" }, "mac": { "target": [ @@ -82,8 +80,7 @@ { "target": "zip", "arch": ["universal"]} ], "icon": "build/icon.icns", - "category": "public.app-category.games", - "description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support" + "category": "public.app-category.games" }, "nsis": { "oneClick": false, @@ -93,3 +90,4 @@ } } } + From a0f49f126c9033f592011c50794c1c7639ba6479 Mon Sep 17 00:00:00 2001 From: Fazri Gading Date: Wed, 21 Jan 2026 00:53:47 +0800 Subject: [PATCH 6/6] add libarchive-tools dep for pacman package --- .github/workflows/release.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 965be39..61de406 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,6 +13,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + + - name: Install dependencies for Pacman build + run: | + sudo apt-get update + sudo apt-get install -y libarchive-tools + - uses: actions/setup-node@v4 with: node-version: '22'