Release Stable Build v2.2.1 (#258)

* fix: resolve cross-platform EPERM permissions errors

modManager.js:
- Switch from hardcoded 'junction' to dynamic symlink type based on OS (fixing Linux EPERM).
- Add retry logic for directory removal to handle file locking race conditions.
- Improve broken symlink detection during profile sync.

gameManager.js:
- Implement retry loop (3 attempts) for game directory removal in updateGameFiles to prevent EBUSY/EPERM errors on Windows.

paths.js:
- Prevent fs.mkdirSync failure in getModsPath by pre-checking for broken symbolic links.

* fix: missing pacman builds

* prepare release for 2.1.1

minor fix for EPERM error permission

* prepare release 2.1.1

minor fix EPERM permission error

* prepare release 2.1.1

* Update README.md Windows Prequisites for ARM64 builds

* fix: remove broken symlink after detected

* fix: add pathexists for paths.js to check symlink

* fix: isbrokenlink should be true to remove the symlink

* add arch package .pkg.tar.zst for release

* fix: release workflow for build-arch and build-linux

* build-arch job now only build arch .pkg.tar.zst package instead of the whole generic linux.
* build-linux job now exclude .pacman package since its deprecated and should not be used.

* fix: removes pacman build as it replaced by tar.zst and adds build:arch shortcut for pkgbuild

* aur: add proper VCS (-git) PKGBUILD

created clean VCS-based PKGBUILD following arch packaging conventions.

this explicitly marked as a rolling (-git) build and derives its version dynamically from git tags and commit history via pkgver(). previous hybrid approach has been changed.

key changes:
- use -git suffix to clearly indicate rolling source builds
- set pkgver=0 and compute the actual version via pkgver()
- build only a directory layout using electron-builder (--dir)
- avoid generating AppImage, deb, rpm, or pacman installers
- align build and package steps with Arch packaging guidelines

note: this PKGBUILD is intended for development and AUR use only and is not suitable for binary redistribution or release artifacts.

* ci: add fixed-version PKGBUILD for Arch Linux releases

this PKGBUILD intended for CI and GitHub release artifacts. targets tagged releases only and uses a fixed pkgver that matches the corresponding git tag. all of the VCS logic has been removed to PKGBUILD-git to ensure reproducible builds and stable versioning suitable for binary distribution.

the build process relies on electron-builder directory output (--dir) and packages only the unpacked application into a standard Arch Linux package (.pkg.tar.zst). other distro format are excluded from this path and handled separately.

this change establishes a clear separation between:
- rolling AUR development builds (-git)
- CI-generated, versioned Arch Linux release packages

the result is predictable artifact naming, correct version alignment, and Arch-compliant packaging for downstream users.

* Update README.md

adds information for Arch build

* Update README.md

BUILD.md location was changed and now this link is poiting to nothing

* Update PKGBUILD

* Update PKGBUILD-git

* chore: fix ubuntu/debian part in README.md

* Polish language support (#195)

* Update support_request.yml

Added hardware specification

* Update bug_report.yml

Add logs textfield to bug report

* chore: add changelog in README.md

* fix screenshot input in feature_request.yml

* add hardware spec input in bug_report.yml

* fix: PKGBUILD pkgname variable fix

* userdata migration [need review from other OS]

* french translate

* Add German and Swedish translations

Added de.json and sv.json locale files for German and Swedish language support. Updated i18n.js to register 'de' and 'sv' as available languages in the launcher.

* Update README.md

* chore: add offline-mode warning to the README.md

* chore: add downloads counter in README.md

* fix: Steam Deck/Ubuntu crash - use system libzstd.so

The bundled libzstd.so is incompatible with glibc 2.41's stricter heap
validation, causing "free(): invalid pointer" crashes.

Solution: Automatically replace bundled libzstd.so with system version
on Linux. The launcher detects and symlinks to /usr/lib/libzstd.so.1.

- Auto-detect system libzstd at common paths (Arch, Debian, Fedora)
- Backup bundled version as libzstd.so.bundled
- Create symlink to system version
- Add HYTALE_NO_LIBZSTD_FIX=1 to disable if needed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: remove Windows and Linux ARM64 information on the README.md

* Update support_request.yml

* fix: improve update system UX and macOS compatibility

Update System Improvements:
- Fix duplicate update popups by disabling legacy updater.js
- Add skip button to update popup (shows after 30s, on error, or after download)
- Add macOS-specific handling with manual download as primary option
- Add missing open-download-page IPC handler
- Add missing unblockInterface() method to properly clean up after popup close
- Add quitAndInstallUpdate alias in preload for compatibility
- Remove pulse animation when download completes
- Fix manual download button to show correct status and close popup
- Sync player name to settings input after first install

Client Patcher Cleanup:
- Remove server patching code (server uses pre-patched JAR from CDN)
- Simplify to client-only patching
- Remove unused imports (crypto, AdmZip, execSync, spawn, javaManager)
- Remove unused methods (stringToUtf8, findAndReplaceDomainUtf8)
- Move localhost dev code to backup file for reference

Code Quality Fixes:
- Fix duplicate DOMContentLoaded handlers in install.js
- Fix duplicate checkForUpdates definition in preload.js
- Fix redundant if/else in onProgressUpdate callback
- Fix typo "Harwadre" -> "Hardware" in preload.js

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add Russian language support

Added Russian (ru) to the list of available languages.

* chore: drafting documentation on SERVER.md

* Some updates in Russian language localization file

* fix

* Update ru.json

* Fixed Java runtime name and fixed typo

* fixed untranslated place

* Update ru.json

* Update ru.json

* Update ru.json

* Update ru.json

* Update ru.json

* fix: timeout getLatestClient 

fixes #138

* fix: change default version to 7.pwr in main.js

* fix: change default release version to 7.pwr

* fix: change version release to 7.pwr

* docs: Add comprehensive troubleshooting guide (#209)

Add TROUBLESHOOTING.md with solutions for common issues including:

- Windows: Firewall configuration, duplicate mods, SmartScreen
- Linux: GPU detection (NVIDIA/AMD), SDL3_image/libpng dependencies,
  Wayland/X11 issues, Steam Deck support
- macOS: Rosetta 2 for Apple Silicon, code signing, quarantine
- Connection: Server boot failures, regional restrictions
- Authentication: Token errors, config reset procedures
- Avatar/Cosmetics: F2P limitations documentation
- Backup locations for all platforms
- Log locations for bug reports

Solutions compiled from closed GitHub issues (#205, #155, #90, #60,
#144, #192) and community feedback.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* Standardize language codes, improve formatting, and update all locale files. (#224)

* Update German (Germany) localization

* Update Español (España) localization

* Update French (France) localization

* Update Polish (Poland) localization

* Update Portuguese (Brazil) localization

* Update Russian (Russia) localization

* Update Swedish (Sweden) localization

* Update Turkish (Turkey) localization

* Update language codes, names and alphabetical in i18n system

* Changed Spanish language name to the Formal name "Spanish (Spain)"

* Fix PKGBUILD-git

* Fix PKGBUILD

* delete cache after installation

* Enforce 16-char player name limit and update mod sync

Added a maxlength attribute to the player name input and enforced a 16-character limit in both install and settings scripts, providing user feedback if exceeded. Refactored modManager.js to replace symlink-based mod management with a copy-based system, copying enabled mods to HytaleSaves\Mods and removing legacy symlink logic to improve compatibility and avoid permission issues.

* Update installation subtitle

* chore: update quickstart link in README.md

* chore: delete warning of Ubuntu-Debian at Linux Prequisites section

* added featured server list from api

* Add Featured Servers page to GUI

* Update Discord invite URL in client patcher

* Add differential update system

* Remove launcher chat and add Discord popup

* fix: removed 'check disk space' alert on permission file error

* fix: upgrade tar to ^7.5.6 version

* fix: re-add universal arch for mac

* fix: upgrade electron/rebuild to 4.0.3

* fix: removed override tar version

* fix: pkgbuild version to 2.1.2

* fix: src.tar.zst and srcinfo missing files

* feat: add Indonesian language translation

* fix: GPU preference hint to Laptop-only

* feat: create two columns for settings page

* Add Discord invite link to rpc

* docs: add recordings form, fix OS list

* Release v2.2.0

* Release v2.2.0

* Release v2.2.0

* chore: delete icon.ico, moved to build folder

* chore: delete icon.png, moved to build folder

* fix: build and release for tag push-only in release.yml

* fix: gamescope steam deck issue fixes #186 hopefully

* Support branch selection for server patching

* chose: add auto-patch system for pre-release JAR

* fix: preserves arch x64 on linux target for #242

* fix: removed arm64 flags

* fix: redo package.json arch

* update package-lock.json

* Update release.yml

* chore: sync package-lock with package.json

* fix: reorder fedora libzstd paths to first iteration

* feat: enhance gpu detection, drafting

* fix: comprehensive UUID/username persistence bug fixes (#252)

* fix: comprehensive UUID/username persistence bug fixes

Major fixes for UUID/skin reset issues that caused players to lose cosmetics:

Core fixes:
- Username rename now preserves UUID (atomic rename, not new identity)
- Atomic config writes with backup/recovery system
- Case-insensitive UUID lookup with case-preserving storage
- Pre-launch validation blocks play if no username configured
- Removed saveUsername calls from launch/install flows

UUID Modal fixes:
- Fixed isCurrent badge showing on wrong user
- Added switch identity button to change between saved usernames
- Fixed custom UUID input using unsaved DOM username
- UUID list now refreshes when player name changes
- Enabled copy/paste in custom UUID input field

UI/UX improvements:
- Added translation keys for switch username functionality
- CSS user-select fix for UUID input fields
- Allowed Ctrl+V/C/X/A shortcuts in Electron

Files: config.js, gameLauncher.js, gameManager.js, playerManager.js,
launcher.js, settings.js, main.js, preload.js, style.css, en.json

See UUID_BUGS_FIX_PLAN.md for detailed bug list (18 bugs, 16 fixed)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(i18n): add switch username translations to all locales

Added translation keys for username switching functionality:
- notifications.noUsername
- notifications.switchUsernameSuccess
- notifications.switchUsernameFailed
- notifications.playerNameTooLong
- confirm.switchUsernameTitle
- confirm.switchUsernameMessage
- confirm.switchUsernameButton

Languages updated: de-DE, es-ES, fr-FR, id-ID, pl-PL, pt-BR, ru-RU, sv-SE, tr-TR

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: move UUID_BUGS_FIX_PLAN.md to docs folder

* docs: update UUID_BUGS_FIX_PLAN with complete fix details

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* chore: rearrange, fix, and improve README.md

* chore: link downloads, platform, and version to release page in README.md

* chore: update discord link

* chore: insert contact link in CODE_OF_CONDUCT.md

* fix: missing version text on launcher

* chore: update quickstart button link to header

* chore: update discord link and give warning quickstart

* chore revise online play hosting instructions in README

Updated instructions for hosting an online game and clarified troubleshooting steps.

* Fix Turkish translations in tr-TR.json

* fix: EPERM error in Repair Game Button [windows testing needed]

* fix: invalid generated token that caused hangs on exit [windows testing needed]

* fix: major bug - hytale won't launch with laptop machine and ghost processes

* fix: discord RPC destroy error if not connected

* fix: major bug - detach game process to avoid launcher-held handles causing zombie process

* docs: add analysis on ghost process and launcher cleanup

* revert generateLocalTokens, wrong analysis on game launching issue

* revert add deps for generateLocalTokens

* Add proxy client and route downloads through it

* fix: Prevent JAR file corruption during proxy downloads

Fixed binary file corruption when downloading through proxy by using PassThrough stream to preserve data integrity while tracking download progress.

* Improve featured servers layout with Discord integration

- Add Discord button to server cards when discord link is present in API data
- Remove HF2P Servers section to use full width for featured servers
- Increase server card size (300x180px banner, larger fonts and spacing)
- Simplify layout from 2-column grid to single full-width container
- Discord button opens external browser with server invite link

* package version to 2.2.1

Update package.json version from 2.2.0 to 2.2.1 to publish a patch release.

* fix: add game_running_marker to prevent duplicate launches

* Add smart proxy with direct-fallback and logging

* fix: remove duplicate check

* fix: cache invalidation from .env prevents multiple launch attempts

for all env related, it is necessary to clear cache first, otherwise on few launch attempts the game wouldn't run

* fix: redact proxy_url and remove timed out emoji

* Prepare Release v2.2.1

* docs: enhance bug report template with placeholders and options

Updated the bug report template to include placeholders and additional Linux distributions.

* chore revise windows prerequisites and changelog

Updated prerequisites and changelog for version 2.2.1.

* chore: improvise badges, relocate star history, fix discord links

* chore: fix release notes for v2.2.1

---------

Co-authored-by: TalesAmaral <57869141+TalesAmaral@users.noreply.github.com>
Co-authored-by: walti0 <95646872+walti0@users.noreply.github.com>
Co-authored-by: AMIAY <letudiantenrap.collab@gmail.com>
Co-authored-by: sanasol <mail@sanasol.ws>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Terromur <79866197+Terromur@users.noreply.github.com>
Co-authored-by: Zakhar Smokotov <zaharb840@gmail.com>
Co-authored-by: xSamiVS <samtaiebc@gmail.com>
Co-authored-by: MetricsLite <66024355+MetricsLite@users.noreply.github.com>
This commit is contained in:
Fazri Gading
2026-02-02 12:58:56 +08:00
committed by GitHub
parent 6a66ed831c
commit 1cb08f029a
42 changed files with 3605 additions and 500 deletions

View File

@@ -7,7 +7,19 @@ const { spawn } = require('child_process');
const { v4: uuidv4 } = require('uuid');
const { getResolvedAppDir, findClientPath } = require('../core/paths');
const { setupWaylandEnvironment, setupGpuEnvironment } = require('../utils/platformUtils');
const { saveUsername, saveInstallPath, loadJavaPath, getUuidForUser, getAuthServerUrl, getAuthDomain, loadVersionBranch, loadVersionClient, saveVersionClient } = require('../core/config');
const {
saveInstallPath,
loadJavaPath,
getUuidForUser,
getAuthServerUrl,
getAuthDomain,
loadVersionBranch,
loadVersionClient,
saveVersionClient,
loadUsername,
hasUsername,
checkLaunchReady
} = require('../core/config');
const { resolveJavaPath, getJavaExec, getBundledJavaPath, detectSystemJava, JAVA_EXECUTABLE } = require('./javaManager');
const { getLatestClientVersion } = require('../services/versionManager');
const { FORCE_CLEAN_INSTALL_VERSION, CLEAN_INSTALL_TEST_VERSION } = require('../core/testConfig');
@@ -104,8 +116,56 @@ function generateLocalTokens(uuid, name) {
};
}
async function launchGame(playerName = 'Player', progressCallback, javaPathOverride, installPathOverride, gpuPreference = 'auto', branchOverride = null) {
// Synchronize server list on every game launch
async function launchGame(playerNameOverride = null, progressCallback, javaPathOverride, installPathOverride, gpuPreference = 'auto', branchOverride = null) {
// ==========================================================================
// CACHE INVALIDATION: Clear proxyClient module cache to force fresh .env load
// This prevents stale cached values from affecting multiple launch attempts
// ==========================================================================
try {
const proxyClientPath = require.resolve('../utils/proxyClient');
if (require.cache[proxyClientPath]) {
delete require.cache[proxyClientPath];
console.log('[Launcher] Cleared proxyClient cache for fresh .env load');
}
} catch (cacheErr) {
console.warn('[Launcher] Could not clear proxyClient cache:', cacheErr.message);
}
// ==========================================================================
// STEP 1: Validate player identity FIRST (before any other operations)
// ==========================================================================
const launchState = checkLaunchReady();
// Load username from config - single source of truth
let playerName = loadUsername();
if (!playerName) {
// No username configured - this is a critical error
const error = new Error('No username configured. Please set your username in Settings before playing.');
console.error('[Launcher] Launch blocked:', error.message);
throw error;
}
// Allow override only if explicitly provided (for testing/migration)
if (playerNameOverride && typeof playerNameOverride === 'string' && playerNameOverride.trim()) {
const overrideName = playerNameOverride.trim();
if (overrideName !== playerName && overrideName !== 'Player') {
console.warn(`[Launcher] Username override requested: "${overrideName}" (saved: "${playerName}")`);
// Use override for this session but DON'T save it - config is source of truth
playerName = overrideName;
}
}
// Warn if using default 'Player' name (likely misconfiguration)
if (playerName === 'Player') {
console.warn('[Launcher] Warning: Using default username "Player". This may cause cosmetic issues.');
}
console.log(`[Launcher] Launching game for player: "${playerName}"`);
// ==========================================================================
// STEP 2: Synchronize server list
// ==========================================================================
try {
console.log('[Launcher] Synchronizing server list...');
await syncServerList();
@@ -113,11 +173,14 @@ async function launchGame(playerName = 'Player', progressCallback, javaPathOverr
console.warn('[Launcher] Server list sync failed, continuing launch:', syncError.message);
}
// ==========================================================================
// STEP 3: Setup paths and directories
// ==========================================================================
const branch = branchOverride || loadVersionBranch();
const customAppDir = getResolvedAppDir(installPathOverride);
const customGameDir = path.join(customAppDir, branch, 'package', 'game', 'latest');
const customJreDir = path.join(customAppDir, branch, 'package', 'jre', 'latest');
// NEW 2.2.0: Use centralized UserData location
const userDataDir = getUserDataPath();
@@ -128,7 +191,10 @@ async function launchGame(playerName = 'Player', progressCallback, javaPathOverr
throw new Error('Game is not installed. Please install the game first.');
}
saveUsername(playerName);
// NOTE: We do NOT save username here anymore - username is only saved
// when user explicitly changes it in Settings. This prevents accidental
// overwrites from race conditions or default values.
if (installPathOverride) {
saveInstallPath(installPathOverride);
}
@@ -287,14 +353,12 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
console.log('Starting game...');
console.log(`Command: "${clientPath}" ${args.join(' ')}`);
const env = { ...process.env };
const waylandEnv = setupWaylandEnvironment();
Object.assign(env, waylandEnv);
const gpuEnv = setupGpuEnvironment(gpuPreference);
Object.assign(env, gpuEnv);
const env = { ...process.env };
const waylandEnv = setupWaylandEnvironment();
Object.assign(env, waylandEnv);
const gpuEnv = setupGpuEnvironment(gpuPreference);
Object.assign(env, gpuEnv);
// Linux: Replace bundled libzstd.so with system version to fix glibc 2.41+ crash
// The bundled libzstd causes "free(): invalid pointer" on Steam Deck / Ubuntu LTS
if (process.platform === 'linux' && process.env.HYTALE_NO_LIBZSTD_FIX !== '1') {
@@ -304,9 +368,9 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
// Common system libzstd paths
const systemLibzstdPaths = [
'/usr/lib64/libzstd.so.1', // Fedora/RHEL
'/usr/lib/libzstd.so.1', // Arch Linux, Steam Deck
'/usr/lib/x86_64-linux-gnu/libzstd.so.1', // Debian/Ubuntu
'/usr/lib64/libzstd.so.1' // Fedora/RHEL
'/usr/lib/x86_64-linux-gnu/libzstd.so.1' // Debian/Ubuntu
];
let systemLibzstd = null;
@@ -358,23 +422,35 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
const child = spawn(clientPath, args, spawnOptions);
// Release process reference immediately so it's truly independent
// This works on all platforms (Windows, macOS, Linux)
child.unref();
console.log(`Game process started with PID: ${child.pid}`);
let hasExited = false;
let outputReceived = false;
let launchCheckTimeout;
child.stdout.on('data', (data) => {
outputReceived = true;
console.log(`Game output: ${data.toString().trim()}`);
});
if (child.stdout) {
child.stdout.on('data', (data) => {
outputReceived = true;
const msg = data.toString().trim();
console.log(`Game output: ${msg}`);
});
}
child.stderr.on('data', (data) => {
outputReceived = true;
console.error(`Game error: ${data.toString().trim()}`);
});
if (child.stderr) {
child.stderr.on('data', (data) => {
outputReceived = true;
const msg = data.toString().trim();
console.error(`Game error: ${msg}`);
});
}
child.on('error', (error) => {
hasExited = true;
clearTimeout(launchCheckTimeout);
console.error(`Failed to start game process: ${error.message}`);
if (progressCallback) {
progressCallback(`Failed to start game: ${error.message}`, -1, null, null, null);
@@ -383,30 +459,30 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
child.on('exit', (code, signal) => {
hasExited = true;
clearTimeout(launchCheckTimeout);
if (code !== null) {
console.log(`Game process exited with code ${code}`);
if (code !== 0 && progressCallback) {
progressCallback(`Game exited with error code ${code}`, -1, null, null, null);
if (code !== 0) {
console.error(`[Launcher] Game crashed or exited with error code ${code}`);
if (progressCallback) {
progressCallback(`Game exited with error code ${code}`, -1, null, null, null);
}
}
} else if (signal) {
console.log(`Game process terminated by signal ${signal}`);
}
});
// Monitor game process status in background
setTimeout(() => {
if (!hasExited) {
console.log('Game appears to be running successfully');
child.unref();
if (progressCallback) {
progressCallback('Game launched successfully', 100, null, null, null);
}
} else if (!outputReceived) {
console.warn('Game process exited immediately with no output - possible issue with game files or dependencies');
}
}, 3000);
// Process is detached and unref'd - it runs independently from the launcher
// We cannot reliably detect if the game window actually appears from here,
// so we report success after spawning. stdout/stderr logging above provides debugging info.
console.log('Game process spawned and detached successfully');
if (progressCallback) {
progressCallback('Game launched successfully', 100, null, null, null);
}
// Return immediately, don't wait for setTimeout
// Return immediately after spawn
return { success: true, installed: true, launched: true, pid: child.pid };
} catch (spawnError) {
console.error(`Error spawning game process: ${spawnError.message}`);
@@ -417,10 +493,26 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
}
}
async function launchGameWithVersionCheck(playerName = 'Player', progressCallback, javaPathOverride, installPathOverride, gpuPreference = 'auto', branchOverride = null) {
async function launchGameWithVersionCheck(playerNameOverride = null, progressCallback, javaPathOverride, installPathOverride, gpuPreference = 'auto', branchOverride = null) {
try {
// ==========================================================================
// PRE-LAUNCH VALIDATION: Check username is configured
// ==========================================================================
const launchState = checkLaunchReady();
if (!launchState.hasUsername) {
const error = 'No username configured. Please set your username in Settings before playing.';
console.error('[Launcher] Launch blocked:', error);
if (progressCallback) {
progressCallback(error, -1, null, null, null);
}
return { success: false, error: error, needsUsername: true };
}
console.log(`[Launcher] Pre-launch check passed. Username: "${launchState.username}"`);
const branch = branchOverride || loadVersionBranch();
if (progressCallback) {
progressCallback('Checking for updates...', 0, null, null, null);
}
@@ -474,7 +566,7 @@ async function launchGameWithVersionCheck(playerName = 'Player', progressCallbac
progressCallback('Launching game...', 80, null, null, null);
}
const launchResult = await launchGame(playerName, progressCallback, javaPathOverride, installPathOverride, gpuPreference, branch);
const launchResult = await launchGame(playerNameOverride, progressCallback, javaPathOverride, installPathOverride, gpuPreference, branch);
// Ensure we always return a result
if (!launchResult) {