Add files via upload

This commit is contained in:
AMIAY
2026-01-18 01:50:47 +01:00
committed by GitHub
parent b6bb1c722a
commit b7e44ca418
4 changed files with 961 additions and 142 deletions

446
main.js
View File

@@ -1,8 +1,58 @@
const { app, BrowserWindow, ipcMain, dialog } = require('electron'); const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
const path = require('path'); const path = require('path');
const { launchGame, saveUsername, loadUsername, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, isGameInstalled, uninstallGame } = require('./backend/launcher'); const fs = require('fs');
const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveChatUsername, loadChatUsername, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, isGameInstalled, uninstallGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched } = require('./backend/launcher');
const UpdateManager = require('./backend/updateManager');
let mainWindow; let mainWindow;
let updateManager;
let discordRPC = null;
// Discord Rich Presence setup
const DISCORD_CLIENT_ID = '1462244937868513373';
function initDiscordRPC() {
try {
const { Client } = require('discord-rpc');
discordRPC = new Client({ transport: 'ipc' });
discordRPC.on('ready', () => {
console.log('Discord RPC connected');
setDiscordActivity();
});
discordRPC.on('disconnected', () => {
console.log('Discord RPC disconnected');
});
discordRPC.login({ clientId: DISCORD_CLIENT_ID }).catch(err => {
console.log('Failed to connect to Discord:', err.message);
});
} catch (error) {
console.log('Discord RPC module not available:', error.message);
}
}
function setDiscordActivity() {
if (!discordRPC) return;
try {
discordRPC.setActivity({
details: 'Using HytaleF2P',
startTimestamp: Date.now(),
largeImageKey: 'hytale_logo',
largeImageText: 'Hytale F2P Launcher',
buttons: [
{
label: 'GitHub',
url: 'https://github.com/amiayweb/Hytale-F2P'
}
]
});
} catch (error) {
console.error('Failed to set Discord activity:', error.message);
}
}
function createWindow() { function createWindow() {
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
@@ -10,21 +60,33 @@ function createWindow() {
height: 720, height: 720,
frame: false, frame: false,
resizable: false, resizable: false,
alwaysOnTop: true, alwaysOnTop: false,
backgroundColor: '#090909', backgroundColor: '#090909',
webPreferences: { webPreferences: {
preload: path.join(__dirname, 'preload.js'), preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
devTools: false, devTools: true,
webSecurity: true webSecurity: true
} }
}); });
mainWindow.loadFile('index.html'); mainWindow.loadFile('GUI/index.html');
mainWindow.webContents.on('devtools-opened', () => { // Initialize Discord Rich Presence
mainWindow.webContents.closeDevTools(); initDiscordRPC();
updateManager = new UpdateManager();
setTimeout(async () => {
const updateInfo = await updateManager.checkForUpdates();
if (updateInfo.updateAvailable) {
mainWindow.webContents.send('show-update-popup', updateInfo);
}
}, 3000);
mainWindow.webContents.on('devtools-opened', () => {
mainWindow.webContents.closeDevTools();
}); });
mainWindow.webContents.on('before-input-event', (event, input) => { mainWindow.webContents.on('before-input-event', (event, input) => {
@@ -45,6 +107,7 @@ function createWindow() {
} }
}); });
mainWindow.webContents.on('context-menu', (e) => { mainWindow.webContents.on('context-menu', (e) => {
e.preventDefault(); e.preventDefault();
}); });
@@ -52,11 +115,67 @@ function createWindow() {
mainWindow.webContents.setIgnoreMenuShortcuts(true); mainWindow.webContents.setIgnoreMenuShortcuts(true);
} }
app.whenReady().then(() => { app.whenReady().then(async () => {
createWindow(); createWindow();
setTimeout(async () => {
try {
console.log('Starting first launch check...');
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('lock-play-button', true);
}
const progressCallback = (message, percent, speed, downloaded, total) => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('first-launch-progress', { message, percent, speed, downloaded, total });
}
};
const firstLaunchResult = await handleFirstLaunchCheck(progressCallback);
console.log('First launch check result:', firstLaunchResult);
if (mainWindow && !mainWindow.isDestroyed()) {
if (firstLaunchResult.needsUpdate && firstLaunchResult.existingGame) {
console.log('Sending show-first-launch-update event...');
setTimeout(() => {
mainWindow.webContents.send('show-first-launch-update', {
existingGame: firstLaunchResult.existingGame,
isFirstLaunch: firstLaunchResult.isFirstLaunch
});
}, 1000);
} else if (firstLaunchResult.isFirstLaunch && !firstLaunchResult.existingGame) {
console.log('Sending show-first-launch-welcome event...');
setTimeout(() => {
mainWindow.webContents.send('show-first-launch-welcome');
}, 1000);
} else {
mainWindow.webContents.send('lock-play-button', false);
}
}
} catch (error) {
console.error('Error during first launch check:', error);
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('lock-play-button', false);
}
}
}, 3000);
}); });
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
// Clean up Discord RPC connection
if (discordRPC) {
try {
discordRPC.destroy();
} catch (error) {
console.log('Error cleaning up Discord RPC:', error.message);
}
}
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
app.quit(); app.quit();
} }
@@ -77,9 +196,9 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath) =
} }
}; };
await launchGame(playerName, progressCallback, javaPath, installPath); const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath);
return { success: true }; return result;
} catch (error) { } catch (error) {
console.error('Launch error:', error); console.error('Launch error:', error);
const errorMessage = error.message || error.toString(); const errorMessage = error.message || error.toString();
@@ -87,12 +206,29 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath) =
} }
}); });
ipcMain.handle('window-close', () => { ipcMain.handle('install-game', async (event, playerName, javaPath, installPath) => {
if (mainWindow) mainWindow.close(); try {
}); const progressCallback = (message, percent, speed, downloaded, total) => {
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
};
mainWindow.webContents.send('progress-update', data);
}
};
ipcMain.handle('window-minimize', () => { const result = await installGame(playerName, progressCallback, javaPath, installPath);
if (mainWindow) mainWindow.minimize();
return result;
} catch (error) {
console.error('Install error:', error);
const errorMessage = error.message || error.toString();
return { success: false, error: errorMessage };
}
}); });
ipcMain.handle('save-username', (event, username) => { ipcMain.handle('save-username', (event, username) => {
@@ -103,7 +239,13 @@ ipcMain.handle('save-username', (event, username) => {
ipcMain.handle('load-username', () => { ipcMain.handle('load-username', () => {
return loadUsername(); return loadUsername();
}); });
ipcMain.handle('save-chat-username', async (event, chatUsername) => {
saveChatUsername(chatUsername);
});
ipcMain.handle('load-chat-username', async () => {
return loadChatUsername();
});
ipcMain.handle('save-java-path', (event, javaPath) => { ipcMain.handle('save-java-path', (event, javaPath) => {
saveJavaPath(javaPath); saveJavaPath(javaPath);
return { success: true }; return { success: true };
@@ -134,6 +276,41 @@ ipcMain.handle('select-install-path', async () => {
return null; return null;
}); });
ipcMain.handle('accept-first-launch-update', async (event, existingGame) => {
try {
const progressCallback = (message, percent, speed, downloaded, total) => {
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
};
mainWindow.webContents.send('first-launch-progress', data);
}
};
const result = await proposeGameUpdate(existingGame, progressCallback);
return result;
} catch (error) {
console.error('First launch update error:', error);
const errorMessage = error.message || error.toString();
return { success: false, error: errorMessage };
}
});
ipcMain.handle('mark-as-launched', async () => {
try {
markAsLaunched();
return { success: true };
} catch (error) {
console.error('Mark as launched error:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('is-game-installed', () => { ipcMain.handle('is-game-installed', () => {
return isGameInstalled(); return isGameInstalled();
}); });
@@ -147,3 +324,242 @@ ipcMain.handle('uninstall-game', async () => {
return { success: false, error: error.message }; return { success: false, error: error.message };
} }
}); });
ipcMain.handle('get-hytale-news', async () => {
try {
const news = await getHytaleNews();
return news;
} catch (error) {
console.error('News fetch error:', error);
return [];
}
});
ipcMain.handle('open-external', async (event, url) => {
try {
await shell.openExternal(url);
return { success: true };
} catch (error) {
console.error('Failed to open external URL:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('browse-java-path', async () => {
const isWindows = process.platform === 'win32';
const isMac = process.platform === 'darwin';
let dialogOptions;
if (isWindows) {
dialogOptions = {
properties: ['openFile'],
title: 'Select Java Executable',
filters: [
{ name: 'Java Executable', extensions: ['exe'] },
{ name: 'All Files', extensions: ['*'] }
]
};
} else if (isMac) {
dialogOptions = {
properties: ['openFile'],
title: 'Select Java Executable',
message: 'Select java executable (usually in /Library/Java/JavaVirtualMachines/*/Contents/Home/bin/java)',
filters: [
{ name: 'All Files', extensions: ['*'] }
]
};
} else {
dialogOptions = {
properties: ['openFile'],
title: 'Select Java Executable',
message: 'Select java executable (usually /usr/bin/java or similar)',
filters: [
{ name: 'All Files', extensions: ['*'] }
]
};
}
const result = await dialog.showOpenDialog(mainWindow, dialogOptions);
if (!result.canceled && result.filePaths.length > 0) {
return result.filePaths[0];
}
return null;
});
ipcMain.handle('save-settings', async (event, settings) => {
try {
if (settings.playerName) saveUsername(settings.playerName);
if (settings.javaPath !== undefined) saveJavaPath(settings.javaPath);
if (settings.installPath !== undefined) saveInstallPath(settings.installPath);
return { success: true };
} catch (error) {
console.error('Save settings error:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('load-settings', async () => {
try {
return {
playerName: loadUsername() || 'Player',
javaPath: loadJavaPath() || '',
installPath: loadInstallPath() || '',
customInstall: false
};
} catch (error) {
console.error('Load settings error:', error);
return {
playerName: 'Player',
javaPath: '',
installPath: '',
customInstall: false
};
}
});
const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod } = require('./backend/launcher');
const os = require('os');
ipcMain.handle('get-local-app-data', async () => {
return process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
});
ipcMain.handle('get-user-id', async () => {
try {
const { getOrCreatePlayerId } = require('./backend/launcher');
return await getOrCreatePlayerId();
} catch (error) {
console.error('Error getting user ID:', error);
return null;
}
});
ipcMain.handle('load-installed-mods', async (event, modsPath) => {
try {
return await loadInstalledMods(modsPath);
} catch (error) {
console.error('Error loading installed mods:', error);
return [];
}
});
ipcMain.handle('openExternalLink', async (event, url) => {
try {
console.log('Opening external URL:', url);
await shell.openExternal(url);
return { success: true };
} catch (error) {
console.error('Error opening external link:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('download-mod', async (event, modInfo) => {
try {
return await downloadMod(modInfo);
} catch (error) {
console.error('Error downloading mod:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('uninstall-mod', async (event, modId, modsPath) => {
try {
return await uninstallMod(modId, modsPath);
} catch (error) {
console.error('Error uninstalling mod:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('toggle-mod', async (event, modId, modsPath) => {
try {
return await toggleMod(modId, modsPath);
} catch (error) {
console.error('Error toggling mod:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('get-mods-path', async () => {
try {
return await getModsPath();
} catch (error) {
console.error('Error getting mods path:', error);
return null;
}
});
ipcMain.handle('select-mod-files', async () => {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openFile', 'multiSelections'],
title: 'Select Mod Files',
filters: [
{ name: 'Mod Files', extensions: ['jar', 'zip'] },
{ name: 'All Files', extensions: ['*'] }
]
});
if (!result.canceled && result.filePaths.length > 0) {
return result.filePaths;
}
return null;
});
ipcMain.handle('copy-mod-file', async (event, sourcePath, modsPath) => {
try {
const fileName = path.basename(sourcePath);
const destPath = path.join(modsPath, fileName);
fs.copyFileSync(sourcePath, destPath);
return { success: true, fileName };
} catch (error) {
console.error('Error copying mod file:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('check-for-updates', async () => {
try {
return await updateManager.checkForUpdates();
} catch (error) {
console.error('Error checking for updates:', error);
return { updateAvailable: false, error: error.message };
}
});
ipcMain.handle('open-download-page', async () => {
try {
await shell.openExternal(updateManager.getDownloadUrl());
setTimeout(() => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.close();
}
}, 1000);
return { success: true };
} catch (error) {
console.error('Error opening download page:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('get-update-info', async () => {
return updateManager.getUpdateInfo();
});
ipcMain.handle('window-close', () => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.close();
}
});
ipcMain.handle('window-minimize', () => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.minimize();
}
});

560
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "hytale-f2p-launcher", "name": "hytale-f2p-launcher",
"version": "1.0.1", "version": "2.0.0",
"description": "Hytale F2P Launcher", "description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"start": "electron .", "start": "electron .",
@@ -14,7 +14,14 @@
}, },
"keywords": [ "keywords": [
"hytale", "hytale",
"launcher" "launcher",
"game",
"client",
"cross-platform",
"electron",
"auto-update",
"mod-manager",
"chat"
], ],
"author": { "author": {
"name": "AMIAY", "name": "AMIAY",
@@ -28,12 +35,13 @@
"dependencies": { "dependencies": {
"adm-zip": "^0.5.10", "adm-zip": "^0.5.10",
"axios": "^1.6.0", "axios": "^1.6.0",
"discord-rpc": "^4.0.1",
"tar": "^7.0.0", "tar": "^7.0.0",
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"build": { "build": {
"appId": "com.hytalef2p.launcher", "appId": "com.hytalef2p.launcher",
"productName": "Hytale F2P Launcher", "productName": "Hytale F2P",
"directories": { "directories": {
"output": "dist" "output": "dist"
}, },
@@ -41,7 +49,7 @@
"main.js", "main.js",
"preload.js", "preload.js",
"backend/**/*", "backend/**/*",
"index.html", "GUI/**/*",
"package.json" "package.json"
], ],
"win": { "win": {

View File

@@ -2,18 +2,57 @@ const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', { contextBridge.exposeInMainWorld('electronAPI', {
launchGame: (playerName, javaPath, installPath) => ipcRenderer.invoke('launch-game', playerName, javaPath, installPath), launchGame: (playerName, javaPath, installPath) => ipcRenderer.invoke('launch-game', playerName, javaPath, installPath),
installGame: (playerName, javaPath, installPath) => ipcRenderer.invoke('install-game', playerName, javaPath, installPath),
closeWindow: () => ipcRenderer.invoke('window-close'), closeWindow: () => ipcRenderer.invoke('window-close'),
minimizeWindow: () => ipcRenderer.invoke('window-minimize'), minimizeWindow: () => ipcRenderer.invoke('window-minimize'),
saveUsername: (username) => ipcRenderer.invoke('save-username', username), saveUsername: (username) => ipcRenderer.invoke('save-username', username),
loadUsername: () => ipcRenderer.invoke('load-username'), loadUsername: () => ipcRenderer.invoke('load-username'),
saveChatUsername: (chatUsername) => ipcRenderer.invoke('save-chat-username', chatUsername),
loadChatUsername: () => ipcRenderer.invoke('load-chat-username'),
saveJavaPath: (javaPath) => ipcRenderer.invoke('save-java-path', javaPath), saveJavaPath: (javaPath) => ipcRenderer.invoke('save-java-path', javaPath),
loadJavaPath: () => ipcRenderer.invoke('load-java-path'), loadJavaPath: () => ipcRenderer.invoke('load-java-path'),
saveInstallPath: (installPath) => ipcRenderer.invoke('save-install-path', installPath), saveInstallPath: (installPath) => ipcRenderer.invoke('save-install-path', installPath),
loadInstallPath: () => ipcRenderer.invoke('load-install-path'), loadInstallPath: () => ipcRenderer.invoke('load-install-path'),
selectInstallPath: () => ipcRenderer.invoke('select-install-path'), selectInstallPath: () => ipcRenderer.invoke('select-install-path'),
browseJavaPath: () => ipcRenderer.invoke('browse-java-path'),
isGameInstalled: () => ipcRenderer.invoke('is-game-installed'), isGameInstalled: () => ipcRenderer.invoke('is-game-installed'),
uninstallGame: () => ipcRenderer.invoke('uninstall-game'), uninstallGame: () => ipcRenderer.invoke('uninstall-game'),
getHytaleNews: () => ipcRenderer.invoke('get-hytale-news'),
openExternal: (url) => ipcRenderer.invoke('open-external', url),
openExternalLink: (url) => ipcRenderer.invoke('openExternalLink', url),
saveSettings: (settings) => ipcRenderer.invoke('save-settings', settings),
loadSettings: () => ipcRenderer.invoke('load-settings'),
getLocalAppData: () => ipcRenderer.invoke('get-local-app-data'),
getModsPath: () => ipcRenderer.invoke('get-mods-path'),
loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath),
downloadMod: (modInfo) => ipcRenderer.invoke('download-mod', modInfo),
uninstallMod: (modId, modsPath) => ipcRenderer.invoke('uninstall-mod', modId, modsPath),
toggleMod: (modId, modsPath) => ipcRenderer.invoke('toggle-mod', modId, modsPath),
selectModFiles: () => ipcRenderer.invoke('select-mod-files'),
copyModFile: (sourcePath, modsPath) => ipcRenderer.invoke('copy-mod-file', sourcePath, modsPath),
onProgressUpdate: (callback) => { onProgressUpdate: (callback) => {
ipcRenderer.on('progress-update', (event, data) => callback(data)); ipcRenderer.on('progress-update', (event, data) => callback(data));
},
getUserId: () => ipcRenderer.invoke('get-user-id'),
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
openDownloadPage: () => ipcRenderer.invoke('open-download-page'),
getUpdateInfo: () => ipcRenderer.invoke('get-update-info'),
onUpdatePopup: (callback) => {
ipcRenderer.on('show-update-popup', (event, data) => callback(data));
},
acceptFirstLaunchUpdate: (existingGame) => ipcRenderer.invoke('accept-first-launch-update', existingGame),
markAsLaunched: () => ipcRenderer.invoke('mark-as-launched'),
onFirstLaunchUpdate: (callback) => {
ipcRenderer.on('show-first-launch-update', (event, data) => callback(data));
},
onFirstLaunchWelcome: (callback) => {
ipcRenderer.on('show-first-launch-welcome', () => callback());
},
onFirstLaunchProgress: (callback) => {
ipcRenderer.on('first-launch-progress', (event, data) => callback(data));
},
onLockPlayButton: (callback) => {
ipcRenderer.on('lock-play-button', (event, locked) => callback(locked));
} }
}); });