mirror of
https://github.com/amiayweb/Hytale-F2P.git
synced 2026-02-26 09:21:58 -03:00
Add files via upload
This commit is contained in:
26
README.md
26
README.md
@@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
[](https://discord.gg/MHkEjepMQ7)
|
|
||||||
|
|
||||||
**A modern, cross-platform offline launcher for Hytale with automatic updates and multiplayer support (windows users & non-premium only)**
|
**A modern, cross-platform offline launcher for Hytale with automatic updates and multiplayer support (windows users & non-premium only)**
|
||||||
|
|
||||||
@@ -67,12 +66,6 @@ See [BUILD.md](BUILD.md) for detailed build instructions or [**Releases**](https
|
|||||||
#### 🖥️ How to create server (Windows Only)?
|
#### 🖥️ How to create server (Windows Only)?
|
||||||
See [SERVER.md](SERVER.md)
|
See [SERVER.md](SERVER.md)
|
||||||
|
|
||||||
### 🎮 Usage
|
|
||||||
|
|
||||||
1. **Enter your player name**
|
|
||||||
2. **Click "PLAY"**
|
|
||||||
3. **Automatic setup** - The launcher handles everything automatically
|
|
||||||
4. **Game launches** - Enjoy playing Hytale!
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -84,7 +77,17 @@ See [BUILD.md](BUILD.md) for comprehensive build instructions.
|
|||||||
|
|
||||||
## 📋 Changelog
|
## 📋 Changelog
|
||||||
|
|
||||||
### 🆕 v2.0.0 *(Latest)*
|
### 🆕 v2.0.1 *(Latest)*
|
||||||
|
- 📊 **Advanced Logging System** - Complete logging with timestamps, file rotation, and session tracking
|
||||||
|
- 🔧 **Play Button Fix** - Resolved issue where play button could get stuck in "CHECKING..." state
|
||||||
|
- 💬 **Discord Integration** - Added closable Discord notification for community engagement
|
||||||
|
- 📁 **Game Location Access** - New "Open Game Location" button in settings for easy file access
|
||||||
|
- 🎯 **UI Polish** - Removed bounce animation from player counter for smoother experience
|
||||||
|
- 🛡️ **Stability Improvements** - Enhanced error handling and process lifecycle management
|
||||||
|
- ⚡ **Performance Optimizations** - Faster startup times and better resource management
|
||||||
|
- 🔄 **Timeout Protection** - Added safety timeouts to prevent launcher freezing
|
||||||
|
|
||||||
|
### 🔄 v2.0.0
|
||||||
- ✅ **Automatic Game Update System** - Smart version checking and seamless updates
|
- ✅ **Automatic Game Update System** - Smart version checking and seamless updates
|
||||||
- ✅ **Partial Automatic Launcher Update System** - This will inform you when I release a new update.
|
- ✅ **Partial Automatic Launcher Update System** - This will inform you when I release a new update.
|
||||||
- 🛡️ **UserData Preservation** - Intelligent backup/restore of game saves during updates
|
- 🛡️ **UserData Preservation** - Intelligent backup/restore of game saves during updates
|
||||||
@@ -184,11 +187,6 @@ This launcher is created for **educational purposes only**.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📬 Contact
|
|
||||||
|
|
||||||
[](https://discord.com/users/1433515183606599873)
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
**⭐ Star this project if you found it helpful! ⭐**
|
**⭐ Star this project if you found it helpful! ⭐**
|
||||||
|
|||||||
131
main.js
131
main.js
@@ -3,6 +3,9 @@ const path = require('path');
|
|||||||
const fs = require('fs');
|
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 { 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');
|
const UpdateManager = require('./backend/updateManager');
|
||||||
|
const logger = require('./backend/logger');
|
||||||
|
|
||||||
|
logger.interceptConsole();
|
||||||
|
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
let updateManager;
|
let updateManager;
|
||||||
@@ -66,7 +69,7 @@ function createWindow() {
|
|||||||
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
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -116,9 +119,30 @@ function createWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(async () => {
|
app.whenReady().then(async () => {
|
||||||
|
console.log('=== HYTALE F2P LAUNCHER STARTED ===');
|
||||||
|
console.log('Platform:', process.platform);
|
||||||
|
console.log('Architecture:', process.arch);
|
||||||
|
console.log('Electron version:', process.versions.electron);
|
||||||
|
console.log('Node.js version:', process.versions.node);
|
||||||
|
console.log('Log directory:', logger.getLogDirectory());
|
||||||
|
|
||||||
createWindow();
|
createWindow();
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
|
let timeoutReached = false;
|
||||||
|
|
||||||
|
const unlockPlayButton = () => {
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
mainWindow.webContents.send('lock-play-button', false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
timeoutReached = true;
|
||||||
|
console.warn('First launch check timeout reached, unlocking play button');
|
||||||
|
unlockPlayButton();
|
||||||
|
}, 15000);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Starting first launch check...');
|
console.log('Starting first launch check...');
|
||||||
|
|
||||||
@@ -132,7 +156,19 @@ app.whenReady().then(async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const firstLaunchResult = await handleFirstLaunchCheck(progressCallback);
|
const firstLaunchResult = await Promise.race([
|
||||||
|
handleFirstLaunchCheck(progressCallback),
|
||||||
|
new Promise((_, reject) => {
|
||||||
|
setTimeout(() => reject(new Error('First launch check timeout')), 12000);
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
if (timeoutReached) {
|
||||||
|
console.log('Timeout already reached, skipping result processing');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('First launch check result:', firstLaunchResult);
|
console.log('First launch check result:', firstLaunchResult);
|
||||||
|
|
||||||
@@ -141,32 +177,39 @@ app.whenReady().then(async () => {
|
|||||||
console.log('Sending show-first-launch-update event...');
|
console.log('Sending show-first-launch-update event...');
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
mainWindow.webContents.send('show-first-launch-update', {
|
mainWindow.webContents.send('show-first-launch-update', {
|
||||||
existingGame: firstLaunchResult.existingGame,
|
existingGame: firstLaunchResult.existingGame,
|
||||||
isFirstLaunch: firstLaunchResult.isFirstLaunch
|
isFirstLaunch: firstLaunchResult.isFirstLaunch
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
} else if (firstLaunchResult.isFirstLaunch && !firstLaunchResult.existingGame) {
|
} else if (firstLaunchResult.isFirstLaunch && !firstLaunchResult.existingGame) {
|
||||||
console.log('Sending show-first-launch-welcome event...');
|
console.log('Sending show-first-launch-welcome event...');
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
mainWindow.webContents.send('show-first-launch-welcome');
|
mainWindow.webContents.send('show-first-launch-welcome');
|
||||||
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
mainWindow.webContents.send('lock-play-button', false);
|
unlockPlayButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
console.error('Error during first launch check:', error);
|
console.error('Error during first launch check:', error);
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (!timeoutReached) {
|
||||||
mainWindow.webContents.send('lock-play-button', false);
|
unlockPlayButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 3000);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
|
console.log('=== LAUNCHER CLOSING ===');
|
||||||
|
|
||||||
// Clean up Discord RPC connection
|
// Clean up Discord RPC connection
|
||||||
if (discordRPC) {
|
if (discordRPC) {
|
||||||
try {
|
try {
|
||||||
@@ -198,6 +241,12 @@ 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);
|
||||||
|
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
setTimeout(() => {
|
||||||
|
mainWindow.webContents.send('progress-complete');
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Launch error:', error);
|
console.error('Launch error:', error);
|
||||||
@@ -223,6 +272,12 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath)
|
|||||||
|
|
||||||
const result = await installGame(playerName, progressCallback, javaPath, installPath);
|
const result = await installGame(playerName, progressCallback, javaPath, installPath);
|
||||||
|
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
setTimeout(() => {
|
||||||
|
mainWindow.webContents.send('progress-complete');
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Install error:', error);
|
console.error('Install error:', error);
|
||||||
@@ -257,6 +312,7 @@ ipcMain.handle('load-java-path', () => {
|
|||||||
|
|
||||||
ipcMain.handle('save-install-path', (event, installPath) => {
|
ipcMain.handle('save-install-path', (event, installPath) => {
|
||||||
saveInstallPath(installPath);
|
saveInstallPath(installPath);
|
||||||
|
logger.updateInstallPath();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -311,8 +367,16 @@ ipcMain.handle('mark-as-launched', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('is-game-installed', () => {
|
ipcMain.handle('is-game-installed', async () => {
|
||||||
return isGameInstalled();
|
try {
|
||||||
|
return await Promise.race([
|
||||||
|
Promise.resolve(isGameInstalled()),
|
||||||
|
new Promise((resolve) => setTimeout(() => resolve(false), 5000))
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking game installation:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('uninstall-game', async () => {
|
ipcMain.handle('uninstall-game', async () => {
|
||||||
@@ -345,6 +409,23 @@ 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');
|
||||||
|
|
||||||
|
if (fs.existsSync(gameDir)) {
|
||||||
|
await shell.openPath(gameDir);
|
||||||
|
return { success: true };
|
||||||
|
} else {
|
||||||
|
throw new Error('Game directory not found');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to open game location:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('browse-java-path', async () => {
|
ipcMain.handle('browse-java-path', async () => {
|
||||||
const isWindows = process.platform === 'win32';
|
const isWindows = process.platform === 'win32';
|
||||||
const isMac = process.platform === 'darwin';
|
const isMac = process.platform === 'darwin';
|
||||||
@@ -392,7 +473,10 @@ ipcMain.handle('save-settings', async (event, settings) => {
|
|||||||
try {
|
try {
|
||||||
if (settings.playerName) saveUsername(settings.playerName);
|
if (settings.playerName) saveUsername(settings.playerName);
|
||||||
if (settings.javaPath !== undefined) saveJavaPath(settings.javaPath);
|
if (settings.javaPath !== undefined) saveJavaPath(settings.javaPath);
|
||||||
if (settings.installPath !== undefined) saveInstallPath(settings.installPath);
|
if (settings.installPath !== undefined) {
|
||||||
|
saveInstallPath(settings.installPath);
|
||||||
|
logger.updateInstallPath();
|
||||||
|
}
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Save settings error:', error);
|
console.error('Save settings error:', error);
|
||||||
@@ -564,3 +648,34 @@ ipcMain.handle('window-minimize', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-log-directory', () => {
|
||||||
|
return logger.getLogDirectory();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-recent-logs', async (event, maxLines = 100) => {
|
||||||
|
try {
|
||||||
|
const logDir = logger.getLogDirectory();
|
||||||
|
if (!logDir) return null;
|
||||||
|
|
||||||
|
// Find the most recent log file
|
||||||
|
const files = fs.readdirSync(logDir)
|
||||||
|
.filter(file => file.startsWith('launcher-') && file.endsWith('.log'))
|
||||||
|
.map(file => ({
|
||||||
|
name: file,
|
||||||
|
path: path.join(logDir, file),
|
||||||
|
mtime: fs.statSync(path.join(logDir, file)).mtime
|
||||||
|
}))
|
||||||
|
.sort((a, b) => b.mtime - a.mtime);
|
||||||
|
|
||||||
|
if (files.length === 0) return null;
|
||||||
|
|
||||||
|
const latestLogFile = files[0].path;
|
||||||
|
const content = fs.readFileSync(latestLogFile, 'utf8');
|
||||||
|
const lines = content.split('\n');
|
||||||
|
|
||||||
|
return lines.slice(-maxLines).join('\n');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reading logs:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hytale-f2p-launcher",
|
"name": "hytale-f2p-launcher",
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
|
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
|
||||||
"homepage": "https://github.com/amiayweb/Hytale-F2P",
|
"homepage": "https://github.com/amiayweb/Hytale-F2P",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
@@ -120,3 +120,4 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
getHytaleNews: () => ipcRenderer.invoke('get-hytale-news'),
|
getHytaleNews: () => ipcRenderer.invoke('get-hytale-news'),
|
||||||
openExternal: (url) => ipcRenderer.invoke('open-external', url),
|
openExternal: (url) => ipcRenderer.invoke('open-external', url),
|
||||||
openExternalLink: (url) => ipcRenderer.invoke('openExternalLink', url),
|
openExternalLink: (url) => ipcRenderer.invoke('openExternalLink', url),
|
||||||
|
openGameLocation: () => ipcRenderer.invoke('open-game-location'),
|
||||||
saveSettings: (settings) => ipcRenderer.invoke('save-settings', settings),
|
saveSettings: (settings) => ipcRenderer.invoke('save-settings', settings),
|
||||||
loadSettings: () => ipcRenderer.invoke('load-settings'),
|
loadSettings: () => ipcRenderer.invoke('load-settings'),
|
||||||
getLocalAppData: () => ipcRenderer.invoke('get-local-app-data'),
|
getLocalAppData: () => ipcRenderer.invoke('get-local-app-data'),
|
||||||
@@ -33,6 +34,9 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
onProgressUpdate: (callback) => {
|
onProgressUpdate: (callback) => {
|
||||||
ipcRenderer.on('progress-update', (event, data) => callback(data));
|
ipcRenderer.on('progress-update', (event, data) => callback(data));
|
||||||
},
|
},
|
||||||
|
onProgressComplete: (callback) => {
|
||||||
|
ipcRenderer.on('progress-complete', () => callback());
|
||||||
|
},
|
||||||
getUserId: () => ipcRenderer.invoke('get-user-id'),
|
getUserId: () => ipcRenderer.invoke('get-user-id'),
|
||||||
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
|
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
|
||||||
openDownloadPage: () => ipcRenderer.invoke('open-download-page'),
|
openDownloadPage: () => ipcRenderer.invoke('open-download-page'),
|
||||||
@@ -54,5 +58,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
},
|
},
|
||||||
onLockPlayButton: (callback) => {
|
onLockPlayButton: (callback) => {
|
||||||
ipcRenderer.on('lock-play-button', (event, locked) => callback(locked));
|
ipcRenderer.on('lock-play-button', (event, locked) => callback(locked));
|
||||||
}
|
},
|
||||||
|
|
||||||
|
getLogDirectory: () => ipcRenderer.invoke('get-log-directory'),
|
||||||
|
getRecentLogs: (maxLines) => ipcRenderer.invoke('get-recent-logs', maxLines)
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user