@@ -662,7 +698,7 @@ async function loadAllUuids() {
if (window.electronAPI && window.electronAPI.getAllUuidMappings) {
const mappings = await window.electronAPI.getAllUuidMappings();
-
+
if (mappings.length === 0) {
uuidList.innerHTML = `
@@ -674,11 +710,11 @@ async function loadAllUuids() {
}
uuidList.innerHTML = '';
-
+
for (const mapping of mappings) {
const item = document.createElement('div');
item.className = `uuid-list-item${mapping.isCurrent ? ' current' : ''}`;
-
+
item.innerHTML = `
${escapeHtml(mapping.username)}
@@ -694,7 +730,7 @@ async function loadAllUuids() {
` : ''}
`;
-
+
uuidList.appendChild(item);
}
}
@@ -737,7 +773,7 @@ async function setCustomUuid() {
}
const uuid = customUuidInput.value.trim();
-
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!uuidRegex.test(uuid)) {
const msg = window.i18n ? window.i18n.t('notifications.uuidInvalidFormat') : 'Invalid UUID format';
@@ -767,33 +803,33 @@ async function setCustomUuid() {
}
}
- async function performSetCustomUuid(uuid) {
- try {
- if (window.electronAPI && window.electronAPI.setUuidForUser) {
- const username = getCurrentPlayerName();
- const result = await window.electronAPI.setUuidForUser(username, uuid);
-
- if (result.success) {
- if (currentUuidDisplay) currentUuidDisplay.value = uuid;
- if (modalCurrentUuid) modalCurrentUuid.value = uuid;
- if (customUuidInput) customUuidInput.value = '';
-
- const msg = window.i18n ? window.i18n.t('notifications.uuidSetSuccess') : 'Custom UUID set successfully!';
- showNotification(msg, 'success');
-
- await loadAllUuids();
- } else {
- throw new Error(result.error || 'Failed to set custom UUID');
- }
- }
- } catch (error) {
- console.error('Error setting custom UUID:', error);
- const msg = window.i18n ? window.i18n.t('notifications.uuidSetFailed').replace('{error}', error.message) : `Failed to set custom UUID: ${error.message}`;
- showNotification(msg, 'error');
- }
- }
+async function performSetCustomUuid(uuid) {
+ try {
+ if (window.electronAPI && window.electronAPI.setUuidForUser) {
+ const username = getCurrentPlayerName();
+ const result = await window.electronAPI.setUuidForUser(username, uuid);
-window.copyUuid = async function(uuid) {
+ if (result.success) {
+ if (currentUuidDisplay) currentUuidDisplay.value = uuid;
+ if (modalCurrentUuid) modalCurrentUuid.value = uuid;
+ if (customUuidInput) customUuidInput.value = '';
+
+ const msg = window.i18n ? window.i18n.t('notifications.uuidSetSuccess') : 'Custom UUID set successfully!';
+ showNotification(msg, 'success');
+
+ await loadAllUuids();
+ } else {
+ throw new Error(result.error || 'Failed to set custom UUID');
+ }
+ }
+ } catch (error) {
+ console.error('Error setting custom UUID:', error);
+ const msg = window.i18n ? window.i18n.t('notifications.uuidSetFailed').replace('{error}', error.message) : `Failed to set custom UUID: ${error.message}`;
+ showNotification(msg, 'error');
+ }
+}
+
+window.copyUuid = async function (uuid) {
try {
if (navigator.clipboard) {
await navigator.clipboard.writeText(uuid);
@@ -807,13 +843,13 @@ window.copyUuid = async function(uuid) {
}
};
-window.deleteUuid = async function(username) {
+window.deleteUuid = async function (username) {
try {
const message = window.i18n ? window.i18n.t('confirm.deleteUuidMessage').replace('{username}', username) : `Are you sure you want to delete the UUID for "${username}"? This action cannot be undone.`;
const title = window.i18n ? window.i18n.t('confirm.deleteUuidTitle') : 'Delete UUID';
const confirmBtn = window.i18n ? window.i18n.t('confirm.deleteUuidButton') : 'Delete';
const cancelBtn = window.i18n ? window.i18n.t('common.cancel') : 'Cancel';
-
+
showCustomConfirm(
message,
title,
@@ -834,21 +870,21 @@ window.deleteUuid = async function(username) {
async function performDeleteUuid(username) {
try {
if (window.electronAPI && window.electronAPI.deleteUuidForUser) {
- const result = await window.electronAPI.deleteUuidForUser(username);
-
- if (result.success) {
- const msg = window.i18n ? window.i18n.t('notifications.uuidDeleteSuccess') : 'UUID deleted successfully!';
- showNotification(msg, 'success');
- await loadAllUuids();
- } else {
- throw new Error(result.error || 'Failed to delete UUID');
- }
+ const result = await window.electronAPI.deleteUuidForUser(username);
+
+ if (result.success) {
+ const msg = window.i18n ? window.i18n.t('notifications.uuidDeleteSuccess') : 'UUID deleted successfully!';
+ showNotification(msg, 'success');
+ await loadAllUuids();
+ } else {
+ throw new Error(result.error || 'Failed to delete UUID');
}
- } catch (error) {
- console.error('Error deleting UUID:', error);
- const msg = window.i18n ? window.i18n.t('notifications.uuidDeleteFailed').replace('{error}', error.message) : `Failed to delete UUID: ${error.message}`;
- showNotification(msg, 'error');
}
+ } catch (error) {
+ console.error('Error deleting UUID:', error);
+ const msg = window.i18n ? window.i18n.t('notifications.uuidDeleteFailed').replace('{error}', error.message) : `Failed to delete UUID: ${error.message}`;
+ showNotification(msg, 'error');
+ }
}
function escapeHtml(text) {
@@ -915,10 +951,10 @@ async function handleBranchChange(event) {
}
// Confirm branch change
- const branchName = window.i18n ?
- window.i18n.t(`settings.branch${newBranch === 'pre-release' ? 'PreRelease' : 'Release'}`) :
+ const branchName = window.i18n ?
+ window.i18n.t(`settings.branch${newBranch === 'pre-release' ? 'PreRelease' : 'Release'}`) :
newBranch;
-
+
const message = window.i18n ?
window.i18n.t('settings.branchWarning') :
'Changing branch will download and install a different game version';
@@ -946,7 +982,7 @@ async function switchBranch(newBranch) {
const switchingMsg = window.i18n ?
window.i18n.t('settings.branchSwitching').replace('{branch}', newBranch) :
`Switching to ${newBranch}...`;
-
+
showNotification(switchingMsg, 'info');
// Lock play button
@@ -960,14 +996,14 @@ async function switchBranch(newBranch) {
// Suggest reinstalling
setTimeout(() => {
- const branchLabel = newBranch === 'release' ?
- (window.i18n ? window.i18n.t('install.releaseVersion') : 'Release') :
+ const branchLabel = newBranch === 'release' ?
+ (window.i18n ? window.i18n.t('install.releaseVersion') : 'Release') :
(window.i18n ? window.i18n.t('install.preReleaseVersion') : 'Pre-Release');
-
+
const confirmMsg = window.i18n ?
window.i18n.t('settings.branchInstallConfirm').replace('{branch}', branchLabel) :
`The game will be installed for the ${branchLabel} branch. Continue?`;
-
+
showCustomConfirm(
confirmMsg,
window.i18n ? window.i18n.t('settings.installRequired') : 'Installation Required',
@@ -980,31 +1016,31 @@ async function switchBranch(newBranch) {
try {
const playerName = await window.electronAPI.loadUsername();
const result = await window.electronAPI.installGame(playerName || 'Player', '', '', newBranch);
-
+
if (result.success) {
// Save branch ONLY after successful installation
await window.electronAPI.saveVersionBranch(newBranch);
-
+
const switchedMsg = window.i18n ?
window.i18n.t('settings.branchSwitched').replace('{branch}', newBranch) :
`Switched to ${newBranch} successfully!`;
-
- const successMsg = window.i18n ?
- window.i18n.t('progress.installationComplete') :
+
+ const successMsg = window.i18n ?
+ window.i18n.t('progress.installationComplete') :
'Installation completed successfully!';
-
+
showNotification(switchedMsg, 'success');
showNotification(successMsg, 'success');
-
+
// Refresh radio buttons to reflect the new branch
await loadVersionBranch();
console.log('[Settings] Radio buttons updated after branch switch');
-
+
setTimeout(() => {
if (window.LauncherUI) {
window.LauncherUI.hideProgress();
}
-
+
// Unlock play button
const playButton = document.getElementById('playButton');
if (playButton) {
@@ -1017,16 +1053,16 @@ async function switchBranch(newBranch) {
}
} catch (error) {
console.error('Installation error:', error);
- const errorMsg = window.i18n ?
- window.i18n.t('progress.installationFailed').replace('{error}', error.message) :
+ const errorMsg = window.i18n ?
+ window.i18n.t('progress.installationFailed').replace('{error}', error.message) :
`Installation failed: ${error.message}`;
-
+
showNotification(errorMsg, 'error');
-
+
if (window.LauncherUI) {
window.LauncherUI.hideProgress();
}
-
+
// Revert radio selection to old branch
loadVersionBranch().then(oldBranch => {
const radioToCheck = document.querySelector(`input[name="gameBranch"][value="${oldBranch}"]`);
@@ -1034,7 +1070,7 @@ async function switchBranch(newBranch) {
radioToCheck.checked = true;
}
});
-
+
// Unlock play button
const playButton = document.getElementById('playButton');
if (playButton) {
@@ -1075,11 +1111,11 @@ async function loadVersionBranch() {
if (window.electronAPI && window.electronAPI.loadVersionBranch) {
const branch = await window.electronAPI.loadVersionBranch();
console.log('[Settings] Loaded version_branch from config:', branch);
-
+
// Use default if branch is null/undefined
const selectedBranch = branch || 'release';
console.log('[Settings] Selected branch:', selectedBranch);
-
+
// Update radio buttons
if (gameBranchRadios && gameBranchRadios.length > 0) {
gameBranchRadios.forEach(radio => {
diff --git a/GUI/locales/en.json b/GUI/locales/en.json
index 9fb30f5..ac516fd 100644
--- a/GUI/locales/en.json
+++ b/GUI/locales/en.json
@@ -131,6 +131,8 @@
"closeLauncher": "Launcher Behavior",
"closeOnStart": "Close Launcher on game start",
"closeOnStartDescription": "Automatically close the launcher after Hytale has launched",
+ "hwAccel": "Hardware Acceleration",
+ "hwAccelDescription": "Enable hardware acceleration for the launcher",
"gameBranch": "Game Branch",
"branchRelease": "Release",
"branchPreRelease": "Pre-Release",
@@ -207,7 +209,9 @@
"modsDownloadFailed": "Failed to download mod: {error}",
"modsToggleFailed": "Failed to toggle mod: {error}",
"modsDeleteFailed": "Failed to delete mod: {error}",
- "modsModNotFound": "Mod information not found"
+ "modsModNotFound": "Mod information not found",
+ "hwAccelSaved": "Hardware acceleration setting saved",
+ "hwAccelSaveFailed": "Failed to save hardware acceleration setting"
},
"confirm": {
"defaultTitle": "Confirm action",
@@ -243,4 +247,4 @@
"installingGameFiles": "Installing game files...",
"installComplete": "Installation complete!"
}
-}
+}
\ No newline at end of file
diff --git a/backend/core/config.js b/backend/core/config.js
index 6e70a44..e118264 100644
--- a/backend/core/config.js
+++ b/backend/core/config.js
@@ -166,13 +166,22 @@ function loadCloseLauncherOnStart() {
return config.closeLauncherOnStart !== undefined ? config.closeLauncherOnStart : false;
}
+function saveLauncherHardwareAcceleration(enabled) {
+ saveConfig({ launcherHardwareAcceleration: !!enabled });
+}
+
+function loadLauncherHardwareAcceleration() {
+ const config = loadConfig();
+ return config.launcherHardwareAcceleration !== undefined ? config.launcherHardwareAcceleration : true;
+}
+
function saveModsToConfig(mods) {
try {
const config = loadConfig();
- // Config migration handles structure, but mod saves must go to the ACTIVE profile.
- // Global installedMods is kept mainly for reference/migration.
- // The profile is the source of truth for enabled mods.
+ // Config migration handles structure, but mod saves must go to the ACTIVE profile.
+ // Global installedMods is kept mainly for reference/migration.
+ // The profile is the source of truth for enabled mods.
if (config.activeProfileId && config.profiles && config.profiles[config.activeProfileId]) {
@@ -369,6 +378,11 @@ module.exports = {
// Close Launcher export
saveCloseLauncherOnStart,
loadCloseLauncherOnStart,
+
+ // Hardware Acceleration functions
+ saveLauncherHardwareAcceleration,
+ loadLauncherHardwareAcceleration,
+
// Version Management exports
saveVersionClient,
loadVersionClient,
diff --git a/backend/launcher.js b/backend/launcher.js
index 57f6686..e981456 100644
--- a/backend/launcher.js
+++ b/backend/launcher.js
@@ -19,6 +19,12 @@ const {
loadLanguage,
saveCloseLauncherOnStart,
loadCloseLauncherOnStart,
+
+ // Hardware Acceleration
+ saveLauncherHardwareAcceleration,
+ loadLauncherHardwareAcceleration,
+
+
saveModsToConfig,
loadModsFromConfig,
getUuidForUser,
@@ -125,20 +131,24 @@ module.exports = {
// Discord RPC functions
saveDiscordRPC,
loadDiscordRPC,
-
+
// Language functions
saveLanguage,
loadLanguage,
-
+
// Close Launcher functions
saveCloseLauncherOnStart,
loadCloseLauncherOnStart,
-
+
+ // Hardware Acceleration functions
+ saveLauncherHardwareAcceleration,
+ loadLauncherHardwareAcceleration,
+
// GPU Preference functions
saveGpuPreference,
loadGpuPreference,
detectGpu,
-
+
// Version functions
getLatestClientVersion,
saveVersionClient,
diff --git a/main.js b/main.js
index c3f7364..2d8c8f4 100644
--- a/main.js
+++ b/main.js
@@ -3,9 +3,20 @@ require('dotenv').config({ path: path.join(__dirname, '.env') });
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
const { autoUpdater } = require('electron-updater');
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 { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveChatUsername, loadChatUsername, saveChatColor, loadChatColor, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, saveLauncherHardwareAcceleration, loadLauncherHardwareAcceleration, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched } = require('./backend/launcher');
const { retryPWRDownload } = require('./backend/managers/gameManager');
+// Handle Hardware Acceleration
+try {
+ const hwEnabled = loadLauncherHardwareAcceleration();
+ if (!hwEnabled) {
+ console.log('Hardware acceleration disabled by user setting');
+ app.disableHardwareAcceleration();
+ }
+} catch (error) {
+ console.error('Failed to load hardware acceleration setting:', error);
+}
+
const logger = require('./backend/logger');
const profileManager = require('./backend/managers/profileManager');
@@ -166,11 +177,11 @@ function createWindow() {
// Configure and initialize electron-updater
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;
-
+
autoUpdater.on('checking-for-update', () => {
console.log('Checking for launcher updates...');
});
-
+
autoUpdater.on('update-available', (info) => {
console.log('Update available:', info.version);
if (mainWindow && !mainWindow.isDestroyed()) {
@@ -182,15 +193,15 @@ function createWindow() {
});
}
});
-
+
autoUpdater.on('update-not-available', (info) => {
console.log('Launcher is up to date:', info.version);
});
-
+
autoUpdater.on('error', (err) => {
console.error('Error in auto-updater:', err);
});
-
+
autoUpdater.on('download-progress', (progressObj) => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('update-download-progress', {
@@ -201,7 +212,7 @@ function createWindow() {
});
}
});
-
+
autoUpdater.on('update-downloaded', (info) => {
console.log('Update downloaded:', info.version);
if (mainWindow && !mainWindow.isDestroyed()) {
@@ -210,7 +221,7 @@ function createWindow() {
});
}
});
-
+
// Check for updates after 3 seconds
setTimeout(() => {
autoUpdater.checkForUpdates().catch(err => {
@@ -241,9 +252,9 @@ function createWindow() {
// Close application shortcuts
const isMac = process.platform === 'darwin';
- const quitShortcut = (isMac && input.meta && input.key.toLowerCase() === 'q') ||
- (!isMac && input.control && input.key.toLowerCase() === 'q') ||
- (!isMac && input.alt && input.key === 'F4');
+ const quitShortcut = (isMac && input.meta && input.key.toLowerCase() === 'q') ||
+ (!isMac && input.control && input.key.toLowerCase() === 'q') ||
+ (!isMac && input.alt && input.key === 'F4');
if (quitShortcut) {
app.quit();
@@ -446,7 +457,7 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath,
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');
@@ -625,6 +636,15 @@ ipcMain.handle('load-close-launcher', () => {
return loadCloseLauncherOnStart();
});
+ipcMain.handle('save-launcher-hw-accel', (event, enabled) => {
+ saveLauncherHardwareAcceleration(enabled);
+ return { success: true };
+});
+
+ipcMain.handle('load-launcher-hw-accel', () => {
+ return loadLauncherHardwareAcceleration();
+});
+
ipcMain.handle('select-install-path', async () => {
const result = await dialog.showOpenDialog(mainWindow, {
@@ -742,14 +762,14 @@ ipcMain.handle('retry-download', async (event, retryData) => {
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');
const jreCacheFile = path.join(retryData.cacheDir, retryData.fileName);
await retryJREDownload(retryData.jreUrl, jreCacheFile, progressCallback);
-
+
return { success: true };
}
-
+
// Handle PWR download retries (default)
if (!retryData || !retryData.branch || !retryData.fileName) {
console.log('[IPC] Invalid retry data, using PWR defaults');
@@ -758,23 +778,23 @@ ipcMain.handle('retry-download', async (event, retryData) => {
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 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 isJreError = retryData?.isJREError;
@@ -792,7 +812,7 @@ ipcMain.handle('retry-download', async (event, retryData) => {
fileName: retryData?.fileName || '4.pwr',
cacheDir: retryData?.cacheDir
};
-
+
const data = {
message: errorMessage,
error: true,
@@ -802,7 +822,7 @@ ipcMain.handle('retry-download', async (event, retryData) => {
};
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);
diff --git a/preload.js b/preload.js
index 0cd9091..d79ca99 100644
--- a/preload.js
+++ b/preload.js
@@ -23,6 +23,11 @@ contextBridge.exposeInMainWorld('electronAPI', {
loadLanguage: () => ipcRenderer.invoke('load-language'),
saveCloseLauncher: (enabled) => ipcRenderer.invoke('save-close-launcher', enabled),
loadCloseLauncher: () => ipcRenderer.invoke('load-close-launcher'),
+
+ // Harwadre Acceleration
+ saveLauncherHardwareAcceleration: (enabled) => ipcRenderer.invoke('save-launcher-hw-accel', enabled),
+ loadLauncherHardwareAcceleration: () => ipcRenderer.invoke('load-launcher-hw-accel'),
+
selectInstallPath: () => ipcRenderer.invoke('select-install-path'),
browseJavaPath: () => ipcRenderer.invoke('browse-java-path'),
isGameInstalled: () => ipcRenderer.invoke('is-game-installed'),