diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 65bbe02..04eabb7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,7 +83,7 @@ jobs: runs-on: ubuntu-latest if: | startsWith(github.ref, 'refs/tags/v') || - github.ref == 'refs/heads/release' || + github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' permissions: diff --git a/README.md b/README.md index 379f3c5..8ace905 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@
-

🎮 Hytale F2P Launcher | Cross-Platform Multiplayer Support 🖥

+

🎮 Hytale F2P Launcher | Cross-Platform Multiplayer 🖥️

Available for Windows 🪟, macOS 🍎, and Linux 🐧

An unofficial cross-platform launcher for Hytale with automatic updates and multiplayer support (all OS supported)

@@ -15,7 +15,7 @@ ⭐ **If you find this project useful, please give it a STAR!** ⭐ -⚠️ **READ [QUICK START](https://github.com/amiayweb/Hytale-F2P/tree/main?tab=readme-ov-file#-quick-start) before Downloading & Installing the Launcher!** ⚠️ +### ⚠️ **READ [QUICK START](https://github.com/amiayweb/Hytale-F2P/tree/main?tab=readme-ov-file#-quick-start) before Downloading & Installing the Launcher!** ⚠️ 🛑 **Found a problem? Join the Discord and Select #Open-A-Ticket!: https://discord.gg/gME8rUy3MB** 🛑 @@ -27,7 +27,7 @@
Hytale F2P Launcher
- View Hytale F2P Gallery + View Gallery @@ -89,7 +89,7 @@ ### 🎮 Hytale Hardware Requirements -> [!INFO] +> [!IMPORTANT] > Hytale is designed to be accessible while scaling for high-end performance. > Below are the [official system requirements for the Early Access](https://hytale.com/news/2025/12/hytale-hardware-requirements) release. @@ -171,27 +171,77 @@ ## 📥 Installation -### 🪟 Windows -1. Make sure you have installed all [**Windows Prequisites**](https://github.com/amiayweb/Hytale-F2P/tree/main?tab=readme-ov-file#-windows-prequisites) above. -2. Download the latest `Hytale-F2P-Launcher.exe` from [**Releases**](https://github.com/amiayweb/Hytale-F2P/releases/latest/). -3. Run the EXE. -4. Launch from Desktop or Start menu. +### 🪟 Windows Installation -### 🐧 Linux -1. Make sure you have installed all [**Linux Prequisites**](https://github.com/amiayweb/Hytale-F2P/tree/main?tab=readme-ov-file#-linux-prequisites) above. -2. Download the latest `Hytale-F2P-Launcher.AppImage` or any specific-packages in accordance with your distro from [**Releases**](https://github.com/amiayweb/Hytale-F2P/releases/latest/). -3. Give permission to the file (`chmod +x `). -4. Run the file by double-clicking, or via Terminal (`./`), or find it via Desktop/App Library. +1. **Prerequisites:** Ensure you have installed all [**Windows Prerequisites**](https://github.com/amiayweb/Hytale-F2P/tree/main?tab=readme-ov-file#-windows-prequisites) listed above. +2. **Download:** Get the latest `Hytale-F2P-Launcher.exe` from the [**Releases**](https://github.com/amiayweb/Hytale-F2P/releases/latest/) page. +3. **SmartScreen Note:** Since the executable is currently unsigned, Windows may show a "Windows protected your PC" popup. + * Click **More info**. + * Click **Run anyway**. +4. **Launch:** Once installed, you can launch the app directly from your Desktop or the Start menu. -### 🍎 macOS -1. Download .DMG file from the from [**Releases**](https://github.com/amiayweb/Hytale-F2P/releases/latest/). -2. Run the file. -3. If says "Apple could not verify ...", go to System Settings > Privacy & Security > Scroll to bottom, find "Hytale F2P Launcher" > press Open Anyway. -4. Advanced: You can also use the .zip. // TODO: NEEDS MORE INFORMATION +--- + +### 🐧 Linux Installation + +1. **Prerequisites:** Ensure you have installed all [**Linux Prerequisites**](https://github.com/amiayweb/Hytale-F2P/tree/main?tab=readme-ov-file#-linux-prequisites) above. +2. **Download:** Choose the package that fits your distribution from the [**Releases**](https://github.com/amiayweb/Hytale-F2P/releases/latest/) page: + * **Universal:** `.AppImage` + * **Arch Linux:** `.pkg.tar.zst` + * **Fedora/RHEL/openSUSE:** `.rpm` + * **Debian/Ubuntu:** `.deb` +3. **Permissions & Execution:** + * **AppImage:** Make the file executable and run it: + ```bash + chmod +x Hytale-F2P-Launcher.AppImage + ./Hytale-F2P-Launcher.AppImage + ``` + * **Fedora (dnf):** Install the RPM: + ```bash + sudo dnf install ./Hytale-F2P-Launcher.rpm + ``` + * **Debian/Ubuntu (apt):** Install the DEB: + ```bash + sudo apt install ./Hytale-F2P-Launcher.deb + ``` + * **Arch Linux (pacman):** Install the package using: + ```bash + sudo pacman -U /path/to/Hytale-F2P-Launcher.pkg.tar.zst + ``` +4. **Troubleshooting:** + * **FUSE:** If the AppImage fails to launch on newer distributions, ensure `libfuse2` (or `fuse2` on Arch/Fedora) is installed. + * **Desktop Entry:** After installing via `.rpm`, `.deb`, or `.pkg.tar.zst`, the launcher should automatically appear in your App Library/Grid. + +--- + +### 🍎 macOS Installation + +> [!NOTE] +> Apple Silicon Users: If you are on an M1, M2, or M3 Mac, you may be prompted to install Rosetta 2 the first time you run the launcher. This is normal and required for compatibility. + +1. **Download:** Get the latest `.dmg` file from the [**Releases**](https://github.com/amiayweb/Hytale-F2P/releases/latest/) page. +2. **Mount:** Double-click the `.dmg` file to open it. +3. **Install:** Drag the **Hytale F2P Launcher** icon into your **Applications** folder. +4. **First Run:** If macOS prevents the app from opening because it is from an "unidentified developer": + * Open **System Settings** > **Privacy & Security**. + * Scroll down to the **Security** section. + * Look for the message regarding "Hytale F2P Launcher" and click **Open Anyway**. + * Authenticate with your password and click **Open**. + +#### **Advanced: Manual Installation (.zip)** +The `.zip` version is useful for users who prefer a portable installation or need to bypass specific permission issues. + +1. **Extract:** Download and unzip the file to your desired location (e.g., `~/Applications`). +2. **Remove Quarantine:** macOS often "quarantines" apps downloaded via browser. If the app won't open, open **Terminal** and run: + ```bash + xattr -rd com.apple.quarantine /path/to/Hytale-F2P-Launcher.app + ``` +> [!TIP] +> Type the first part of the command, then drag the app icon into the Terminal window to auto-fill the path. --- -# Server +# How to Host a Server ## Host your Singleplayer Server (Online-Play Feature) @@ -205,15 +255,17 @@ ## Dedicated Server > [!NOTE] -> Only Hytale-F2P-Server.rar file is needed to set it up on non-playing hardware (such as VPS/server hosting). -> Only HytaleServer.jar needed to use your "Server" folder made by the launcher to host local dedicated server. -> Use services like Playit.gg, Tailscale, Radmin VPN to share UDP connection if setting up router is not possible. +> If you have already `HytaleServer.jar` in `HytaleF2P/{release/pre-release}/package/game/latest/Server`, you can use it to host local dedicated server. + +> [!TIP] +> Use services like Playit.gg, Tailscale, Radmin VPN to share UDP connection if setting up router as an admin is not possible. + +> [!WARNING] +> `Hytale-F2P-Server.rar` file is needed to set up a server on non-playing hardware (such as VPS/server hosting). > [!IMPORTANT] > See detailed information of setting up a server here: [SERVER.md](SERVER.md) -// TODO: Server.md would be used as a detailed information to avoid confuses) - --- ## 🛠️ Building from Source @@ -222,29 +274,21 @@ See [BUILD.md](BUILD.md) for comprehensive build instructions. --- -// TODO: this part needs to be written in dev notes - -## 📌 Versioning Policy - -**⚠️ Important: Semantic Versioning Required** - -This project follows **strict semantic versioning** with **numerical versions only**: - -- ✅ **Valid**: `2.0.1`, `2.0.11`, `2.1.0`, `3.0.0` -- ❌ **Invalid**: `2.0.2b`, `2.0.2a`, `2.0.1-beta`, `v2.0.2b` - -**Format**: `MAJOR.MINOR.PATCH` (e.g., `2.0.11`) - -- **MAJOR**: Breaking changes -- **MINOR**: New features (backward compatible) -- **PATCH**: Bug fixes (backward compatible) - -**Why?** The auto-update system requires semantic versioning for proper version comparison. Letter suffixes (like `2.0.2b`) are not supported and will cause update detection issues. - ---- - ## 📋 Changelog -// TODO: CHANGELOG SHOULD BE IN `CHANGELOG.MD` + +### 🆕 v2.1.0 + +- 🚨 **Auto-Retry Downloads and Auto-Patch Files** — +- ⚡ **Hardware Acceleration** — +- 👨‍💻 **In-App Logging** — +- 🛠️ **Repair Button** — Y +- 🔎 **Browse CurseForge Mods** — Browsing mods now easier with our dedicated CurseForge API Key. +- 🌎 **Fixes and Release New Translation** — Fixed 🇪🇸 🇧🇷 and added more translation for current build. Turkish 🇹🇷 language now added. + + + +
+Click here to see older Changelogs ### 🆕 v2.0.2b *(Minor Update: Performance & Utilities)* - 🌎 **Language Translation** — A big welcome for Spanish 🇪🇸 and Portuguese (Brazil) 🇧🇷 players! **Language setting can be found in the bottom part of Settings pane.** @@ -253,14 +297,15 @@ This project follows **strict semantic versioning** with **numerical versions on - 🛠️ **Repair Button** — Your game's broken? One button will fix them, go to Settings pane to Repair your game in one-click, **without losing any data**. If doing so did not fix your issue, please report it to us immediately! - 🐛 **Fixed Bugs** — Fixed issue [#84](https://github.com/amiayweb/Hytale-F2P/issues/84) where mods disappearing when game starts in previous launcher (v2.0.2a). -### 🆕 v2.0.2a *(Minor Update)* + +### 🔄 v2.0.2a *(Minor Update)* - 🧑‍🚀 **Profiles System** — Added proper profile management: create, switch, and delete profiles. Each profile now has its own **isolated mod list**. - 🔒 **Mod Isolation** — Fixed ModManager so mods are **strictly scoped to the active profile**. Browsing and installing now only affects the selected profile. - 🚨 **Critical Path Fix** — Resolved a macOS bug where mods were being saved to a Windows path (`~/AppData/Local`) instead of `~/Library/Application Support`. - 🛡️ **Stability Improvements** — Added an **auto-sync step before every launch** to ensure the physical mods folder always matches the active profile. - 🎨 **UI Enhancements** — Added a **profile selector dropdown** and a **profile management modal**. -### 🆕 v2.0.2 +### 🔄 v2.0.2 - 🎮 **Discord RPC Integration** - Added Discord Rich Presence with toggle in settings (enabled by default) - 🌐 **Cross-Platform Multiplayer** - Added multiplayer patch support for Windows, Linux, and macOS - 🎨 **Chat Improvements** - Simplified chat color system @@ -305,7 +350,7 @@ This project follows **strict semantic versioning** with **numerical versions on - ☕ **Java Management** - Automatic Java runtime handling - 🎨 **Modern Interface** - Clean, intuitive design - 🌟 **First Release** - Core launcher functionality - +
--- ## 👥 Contributors diff --git a/backend/managers/gameLauncher.js b/backend/managers/gameLauncher.js index 9ca0e18..6a7a379 100644 --- a/backend/managers/gameLauncher.js +++ b/backend/managers/gameLauncher.js @@ -162,7 +162,7 @@ async function launchGame(playerName = 'Player', progressCallback, javaPathOverr console.log(`Force patching game binaries for ${authDomain}...`); const patchResult = await clientPatcher.ensureClientPatched(gameLatest, (msg, percent) => { - console.log(`[Patcher] ${msg}`); + // console.log(`[Patcher] ${msg}`); if (progressCallback && msg) { progressCallback(msg, percent, null, null, null); } @@ -331,6 +331,7 @@ exec "$REAL_JAVA" "\${ARGS[@]}" } }); + // Monitor game process status in background setTimeout(() => { if (!hasExited) { console.log('Game appears to be running successfully'); @@ -343,6 +344,7 @@ exec "$REAL_JAVA" "\${ARGS[@]}" } }, 3000); + // Return immediately, don't wait for setTimeout return { success: true, installed: true, launched: true, pid: child.pid }; } catch (spawnError) { console.error(`Error spawning game process: ${spawnError.message}`); @@ -404,13 +406,22 @@ async function launchGameWithVersionCheck(playerName = 'Player', progressCallbac progressCallback('Launching game...', 80, null, null, null); } - return await launchGame(playerName, progressCallback, javaPathOverride, installPathOverride, gpuPreference, branch); + const launchResult = await launchGame(playerName, progressCallback, javaPathOverride, installPathOverride, gpuPreference, branch); + + // Ensure we always return a result + if (!launchResult) { + console.error('launchGame returned null/undefined, creating fallback response'); + return { success: false, error: 'Game launch failed - no response from launcher' }; + } + + return launchResult; } catch (error) { console.error('Error in version check and launch:', error); if (progressCallback) { progressCallback(`Error: ${error.message}`, -1, null, null, null); } - throw error; + // Always return an error response instead of throwing + return { success: false, error: error.message || 'Unknown launch error' }; } } diff --git a/backend/utils/clientPatcher.js b/backend/utils/clientPatcher.js index 09c08df..3446fed 100644 --- a/backend/utils/clientPatcher.js +++ b/backend/utils/clientPatcher.js @@ -525,17 +525,16 @@ class ClientPatcher { } /** - * Patch the server JAR using DualAuthPatcher for full dual auth support - * This uses the same patcher as the Docker server for consistency + * Patch the server JAR by downloading pre-patched version * @param {string} serverPath - Path to the HytaleServer.jar * @param {function} progressCallback - Optional callback for progress updates - * @param {string} javaPath - Path to Java executable + * @param {string} javaPath - Path to Java executable (unused, kept for compatibility) * @returns {object} Result object with success status and details */ async patchServer(serverPath, progressCallback, javaPath = null) { const newDomain = this.getNewDomain(); - console.log('=== Server Patcher v3.0 (DualAuth) ==='); + console.log('=== Server Patcher TEMP SYSTEM NEED TO BE FIXED ==='); console.log(`Target: ${serverPath}`); console.log(`Domain: ${newDomain}`); @@ -545,13 +544,13 @@ class ClientPatcher { return { success: false, error }; } - // Check if already patched with DualAuth + // Check if already patched const patchFlagFile = serverPath + '.dualauth_patched'; if (fs.existsSync(patchFlagFile)) { try { const flagData = JSON.parse(fs.readFileSync(patchFlagFile, 'utf8')); if (flagData.domain === newDomain) { - console.log(`Server already patched with DualAuth for ${newDomain}, skipping`); + console.log(`Server already patched for ${newDomain}, skipping`); if (progressCallback) progressCallback('Server already patched', 100); return { success: true, alreadyPatched: true }; } @@ -560,80 +559,99 @@ class ClientPatcher { } } - if (progressCallback) progressCallback('Preparing DualAuth patcher...', 10); - - // Find Java executable - use bundled JRE first (same as game uses) - const java = javaPath || this.findJava(); - if (!java) { - const error = 'Java not found. Please install the game first (it includes Java) or install Java 25 from: https://adoptium.net/'; - console.error(error); - return { success: false, error }; - } - console.log(`Using Java: ${java}`); - - // Setup patcher directory - const patcherDir = path.join(__dirname, '..', 'patcher'); - const patcherJava = path.join(patcherDir, 'DualAuthPatcher.java'); - const libDir = path.join(patcherDir, 'lib'); - - // Download patcher from hytale-auth-server if not present - if (progressCallback) progressCallback('Checking patcher...', 15); - try { - await this.ensurePatcherDownloaded(patcherDir); - } catch (e) { - const error = `Failed to download DualAuthPatcher: ${e.message}`; - console.error(error); - return { success: false, error }; - } - - if (!fs.existsSync(patcherJava)) { - const error = `DualAuthPatcher.java not found at ${patcherJava}`; - console.error(error); - return { success: false, error }; - } - - // Download ASM libraries if not present - if (progressCallback) progressCallback('Checking ASM libraries...', 20); - await this.ensureAsmLibraries(libDir); - - // Compile patcher if needed - if (progressCallback) progressCallback('Compiling patcher...', 30); - const compileResult = await this.compileDualAuthPatcher(java, patcherDir, libDir); - if (!compileResult.success) { - return { success: false, error: compileResult.error }; - } - // Create backup - if (progressCallback) progressCallback('Creating backup...', 40); + if (progressCallback) progressCallback('Creating backup...', 10); console.log('Creating backup...'); this.backupClient(serverPath); - // Run the patcher - if (progressCallback) progressCallback('Patching server JAR...', 50); - console.log('Running DualAuthPatcher...'); + // Download pre-patched JAR + if (progressCallback) progressCallback('Downloading patched server JAR...', 30); + console.log('Downloading pre-patched HytaleServer.jar'); - const classpath = [ - patcherDir, - path.join(libDir, 'asm-9.6.jar'), - path.join(libDir, 'asm-tree-9.6.jar'), - path.join(libDir, 'asm-util-9.6.jar') - ].join(process.platform === 'win32' ? ';' : ':'); + try { + const https = require('https'); + const url = 'https://pub-027b315ece074e2e891002ca38384792.r2.dev/HytaleServer.jar'; - const patchResult = await this.runDualAuthPatcher(java, classpath, serverPath, newDomain); + await new Promise((resolve, reject) => { + https.get(url, (response) => { + if (response.statusCode === 302 || response.statusCode === 301) { + // Follow redirect + https.get(response.headers.location, (redirectResponse) => { + if (redirectResponse.statusCode !== 200) { + reject(new Error(`Failed to download: HTTP ${redirectResponse.statusCode}`)); + return; + } + + const file = fs.createWriteStream(serverPath); + const totalSize = parseInt(redirectResponse.headers['content-length'], 10); + let downloaded = 0; + + redirectResponse.on('data', (chunk) => { + downloaded += chunk.length; + if (progressCallback && totalSize) { + const percent = 30 + Math.floor((downloaded / totalSize) * 60); + progressCallback(`Downloading... ${(downloaded / 1024 / 1024).toFixed(2)} MB`, percent); + } + }); + + redirectResponse.pipe(file); + file.on('finish', () => { + file.close(); + resolve(); + }); + }).on('error', reject); + } else if (response.statusCode === 200) { + const file = fs.createWriteStream(serverPath); + const totalSize = parseInt(response.headers['content-length'], 10); + let downloaded = 0; + + response.on('data', (chunk) => { + downloaded += chunk.length; + if (progressCallback && totalSize) { + const percent = 30 + Math.floor((downloaded / totalSize) * 60); + progressCallback(`Downloading... ${(downloaded / 1024 / 1024).toFixed(2)} MB`, percent); + } + }); + + response.pipe(file); + file.on('finish', () => { + file.close(); + resolve(); + }); + } else { + reject(new Error(`Failed to download: HTTP ${response.statusCode}`)); + } + }).on('error', (err) => { + fs.unlink(serverPath, () => {}); + reject(err); + }); + }); + + console.log(' Download successful'); - if (patchResult.success) { // Mark as patched fs.writeFileSync(patchFlagFile, JSON.stringify({ domain: newDomain, patchedAt: new Date().toISOString(), - patcher: 'DualAuthPatcher' + patcher: 'PrePatchedDownload', + source: 'https://pub-027b315ece074e2e891002ca38384792.r2.dev/HytaleServer.jar' })); if (progressCallback) progressCallback('Server patching complete', 100); console.log('=== Server Patching Complete ==='); - return { success: true, patchCount: patchResult.patchCount || 1 }; - } else { - return { success: false, error: patchResult.error }; + return { success: true, patchCount: 1 }; + + } catch (downloadError) { + console.error(`Failed to download patched JAR: ${downloadError.message}`); + + // Restore backup on failure + const backupPath = serverPath + '.original'; + if (fs.existsSync(backupPath)) { + fs.copyFileSync(backupPath, serverPath); + console.log('Restored backup after download failure'); + } + + return { success: false, error: `Failed to download patched server: ${downloadError.message}` }; } } @@ -802,10 +820,24 @@ class ClientPatcher { ].join(process.platform === 'win32' ? ';' : ':'); try { - execSync(`"${javac}" -cp "${classpath}" -d "${patcherDir}" "${patcherJava}"`, { + // Fix PATH for packaged Electron apps on Windows + const execOptions = { stdio: 'pipe', - cwd: patcherDir - }); + cwd: patcherDir, + env: { ...process.env } + }; + + // Add system32 to PATH for Windows to find cmd.exe + if (process.platform === 'win32') { + const systemRoot = process.env.SystemRoot || 'C:\\WINDOWS'; + const systemPath = `${systemRoot}\\system32;${systemRoot};${systemRoot}\\System32\\Wbem`; + execOptions.env.PATH = execOptions.env.PATH + ? `${systemPath};${execOptions.env.PATH}` + : systemPath; + execOptions.shell = true; + } + + execSync(`"${javac}" -cp "${classpath}" -d "${patcherDir}" "${patcherJava}"`, execOptions); console.log(' Compilation successful'); return { success: true }; } catch (e) { diff --git a/backend/utils/fileManager.js b/backend/utils/fileManager.js index bad80b3..e0c4bbd 100644 --- a/backend/utils/fileManager.js +++ b/backend/utils/fileManager.js @@ -58,11 +58,11 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) { console.log(`Download attempt ${attempt + 1}/${maxRetries} for ${url}`); if (attempt > 0 && progressCallback) { - // Exponential backoff with jitter - const baseDelay = 2000; + // Exponential backoff with jitter - longer delays for unstable connections + const baseDelay = 3000; const exponentialDelay = baseDelay * Math.pow(2, attempt - 1); - const jitter = Math.random() * 1000; - const delay = Math.min(exponentialDelay + jitter, 30000); + const jitter = Math.random() * 2000; + const delay = Math.min(exponentialDelay + jitter, 60000); progressCallback(`Retry ${attempt}/${maxRetries - 1}...`, null, null, null, null, retryState); await new Promise(resolve => setTimeout(resolve, delay)); @@ -78,9 +78,9 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) { const now = Date.now(); const timeSinceLastProgress = now - lastProgressTime; - // Only timeout if no data received for 5 minutes (300 seconds) - if (timeSinceLastProgress > 300000 && hasReceivedData) { - console.log('Download stalled for 5 minutes, aborting...'); + // Only timeout if no data received for 15 minutes (900 seconds) - for very slow connections + if (timeSinceLastProgress > 900000 && hasReceivedData) { + console.log('Download stalled for 15 minutes, aborting...'); console.log(`Download had progress before stall: ${(downloaded / 1024 / 1024).toFixed(2)} MB`); controller.abort(); } @@ -91,6 +91,12 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) { if (fs.existsSync(dest)) { const existingStats = fs.statSync(dest); + // If file size matches remote size, skip download + if (existingStats.size == fs.statSync(dest).size) { + console.log('File already exists and is complete. Skipping download.'); + return { success: true, downloaded: existingStats.size }; + } + // Only resume if file exists and is substantial (> 1MB) if (existingStats.size > 1024 * 1024) { startByte = existingStats.size; @@ -119,7 +125,7 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) { method: 'GET', url: url, responseType: 'stream', - timeout: 60000, + timeout: 120000, // 120 seconds for slow connections signal: controller.signal, headers: headers, validateStatus: function (status) { @@ -135,7 +141,7 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) { lastProgressTime = Date.now(); const startTime = Date.now(); - // Check network status before attempting download + // Check network status before attempting download, in case of known offline state try { const isNetworkOnline = await checkNetworkConnection(); if (!isNetworkOnline) { @@ -403,8 +409,9 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) { const retryableErrors = [ 'ECONNRESET', 'ENOTFOUND', 'ECONNREFUSED', 'ETIMEDOUT', 'ESOCKETTIMEDOUT', 'EPROTO', 'ENETDOWN', 'EHOSTUNREACH', + 'ECONNABORTED', 'EPIPE', 'ENETRESET', 'EADDRNOTAVAIL', 'ERR_NETWORK', 'ERR_INTERNET_DISCONNECTED', 'ERR_CONNECTION_RESET', - 'ERR_CONNECTION_TIMED_OUT', 'ERR_NAME_NOT_RESOLVED' + 'ERR_CONNECTION_TIMED_OUT', 'ERR_NAME_NOT_RESOLVED', 'ERR_CONNECTION_CLOSED' ]; const isRetryable = retryableErrors.includes(error.code) || diff --git a/main.js b/main.js index 08fcdfc..2d8c8f4 100644 --- a/main.js +++ b/main.js @@ -41,7 +41,7 @@ let mainWindow; let discordRPC = null; // Discord Rich Presence setup -const DISCORD_CLIENT_ID = 1462244937868513373; +const DISCORD_CLIENT_ID = "1462244937868513373"; function initDiscordRPC() { try { @@ -93,7 +93,7 @@ function setDiscordActivity() { } } -function toggleDiscordRPC(enabled) { +async function toggleDiscordRPC(enabled) { console.log('Toggling Discord RPC:', enabled); if (enabled && !discordRPC) { @@ -103,11 +103,12 @@ function toggleDiscordRPC(enabled) { try { console.log('Disconnecting Discord RPC...'); discordRPC.clearActivity(); + await new Promise(r => setTimeout(r, 100)); discordRPC.destroy(); - discordRPC = null; console.log('Discord RPC disconnected successfully'); } catch (error) { console.error('Error disconnecting Discord RPC:', error.message); + } finally { discordRPC = null; } } @@ -378,23 +379,18 @@ app.whenReady().then(async () => { }, 3000); }); -function cleanupDiscordRPC() { - if (discordRPC) { - try { - console.log('Cleaning up Discord RPC...'); - discordRPC.clearActivity(); - setTimeout(() => { - try { - discordRPC.destroy(); - } catch (error) { - console.log('Error during final Discord RPC cleanup:', error.message); - } - }, 100); - discordRPC = null; - } catch (error) { - console.log('Error cleaning up Discord RPC:', error.message); - discordRPC = null; - } +async function cleanupDiscordRPC() { + if (!discordRPC) return; + try { + console.log('Cleaning up Discord RPC...'); + discordRPC.clearActivity(); + await new Promise(r => setTimeout(r, 100)); + discordRPC.destroy(); + console.log('Discord RPC cleaned up successfully'); + } catch (error) { + console.log('Error cleaning up Discord RPC:', error.message); + } finally { + discordRPC = null; } } @@ -405,9 +401,6 @@ app.on('before-quit', () => { app.on('window-all-closed', () => { console.log('=== LAUNCHER CLOSING ==='); - - cleanupDiscordRPC(); - app.quit(); }); diff --git a/package-lock.json b/package-lock.json index 2da2ede..12d6ac3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "adm-zip": "^0.5.10", "axios": "^1.6.0", "discord-rpc": "^4.0.1", + "dotenv": "^17.2.3", "electron-updater": "^6.7.3", "fs-extra": "^11.3.3", "tar": "^6.2.1", @@ -1906,6 +1907,18 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dotenv-expand": { "version": "11.0.7", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz",
@@ -51,11 +51,11 @@
- In-Game Screenshot-1
+ In-Game Screenshot - Spawn Point
Hytale F2P In-Game Screenshot-1
- In-Game Screenshot-2
+ In-Game Screenshot - Gameplay Terrain
Hytale F2P In-Game Screenshot-2