Compare commits

...

6 Commits

Author SHA1 Message Date
sanasol
ee53911a06 Revert debug builds, update fastutil issue docs
Agent and -Xshare:off both ruled out as causes.
Restored normal agent injection. Updated docs with
complete findings — issue remains unsolved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 23:15:01 +01:00
sanasol
43a2b6d004 debug: disable DualAuth agent to test fastutil classloader issue
Temporarily skip -javaagent injection to determine if agent's
appendToBootstrapClassLoaderSearch() causes the fastutil
ClassNotFoundException on affected Windows systems.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:47:46 +01:00
sanasol
ce6455314d debug: add -Xshare:off to JAVA_TOOL_OPTIONS to test fastutil classloader fix
Disables JVM Class Data Sharing when DualAuth agent is active.
May fix singleplayer crash (NoClassDefFoundError: fastutil) on some Windows systems
where appendToBootstrapClassLoaderSearch breaks CDS classloading.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:32:50 +01:00
sanasol
3abdd10cab v2.4.5: Add multi-instance setting for mod developers
Allow running multiple game clients simultaneously via a new
"Allow multiple game instances" toggle in Settings. When enabled,
skips the Electron single-instance lock and the pre-launch process
kill, so existing game instances stay alive.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 21:52:13 +01:00
sanasol
e1a3f919a2 Fix JRE flatten failing silently on Windows EPERM
When extracting the bundled JRE, flattenJREDir renames files from
the nested jdk subdirectory up one level. On Windows this fails with
EPERM when antivirus or file indexing holds handles open, leaving
the JRE nested and unfindable — causing "Server failed to boot".

- Fall back to copy+delete when rename gets EPERM/EACCES/EBUSY
- getBundledJavaPath checks nested JRE subdirs as last resort
2026-02-26 23:39:43 +01:00
sanasol
e3fe1b6a10 Delete server/commit-msg.txt 2026-02-26 19:45:57 +00:00
22 changed files with 205 additions and 106 deletions

View File

@@ -592,6 +592,18 @@
</div> </div>
</label> </label>
</div> </div>
<div class="settings-option">
<label class="settings-checkbox">
<input type="checkbox" id="allowMultiInstanceCheck" />
<span class="checkmark"></span>
<div class="checkbox-content">
<div class="checkbox-title" data-i18n="settings.allowMultiInstance">Allow multiple game instances</div>
<div class="checkbox-description" data-i18n="settings.allowMultiInstanceDescription">
Allow running multiple game clients at the same time (useful for mod development)
</div>
</div>
</label>
</div>
<div class="settings-option"> <div class="settings-option">
<label class="settings-checkbox"> <label class="settings-checkbox">
<input type="checkbox" id="launcherHwAccelCheck" /> <input type="checkbox" id="launcherHwAccelCheck" />

View File

@@ -6,6 +6,7 @@ let browseJavaBtn;
let settingsPlayerName; let settingsPlayerName;
let discordRPCCheck; let discordRPCCheck;
let closeLauncherCheck; let closeLauncherCheck;
let allowMultiInstanceCheck;
let launcherHwAccelCheck; let launcherHwAccelCheck;
let gpuPreferenceRadios; let gpuPreferenceRadios;
let gameBranchRadios; let gameBranchRadios;
@@ -171,6 +172,7 @@ function setupSettingsElements() {
settingsPlayerName = document.getElementById('settingsPlayerName'); settingsPlayerName = document.getElementById('settingsPlayerName');
discordRPCCheck = document.getElementById('discordRPCCheck'); discordRPCCheck = document.getElementById('discordRPCCheck');
closeLauncherCheck = document.getElementById('closeLauncherCheck'); closeLauncherCheck = document.getElementById('closeLauncherCheck');
allowMultiInstanceCheck = document.getElementById('allowMultiInstanceCheck');
launcherHwAccelCheck = document.getElementById('launcherHwAccelCheck'); launcherHwAccelCheck = document.getElementById('launcherHwAccelCheck');
gpuPreferenceRadios = document.querySelectorAll('input[name="gpuPreference"]'); gpuPreferenceRadios = document.querySelectorAll('input[name="gpuPreference"]');
gameBranchRadios = document.querySelectorAll('input[name="gameBranch"]'); gameBranchRadios = document.querySelectorAll('input[name="gameBranch"]');
@@ -218,6 +220,10 @@ function setupSettingsElements() {
closeLauncherCheck.addEventListener('change', saveCloseLauncher); closeLauncherCheck.addEventListener('change', saveCloseLauncher);
} }
if (allowMultiInstanceCheck) {
allowMultiInstanceCheck.addEventListener('change', saveAllowMultiInstance);
}
if (launcherHwAccelCheck) { if (launcherHwAccelCheck) {
launcherHwAccelCheck.addEventListener('change', saveLauncherHwAccel); launcherHwAccelCheck.addEventListener('change', saveLauncherHwAccel);
} }
@@ -415,6 +421,30 @@ async function loadCloseLauncher() {
} }
} }
async function saveAllowMultiInstance() {
try {
if (window.electronAPI && window.electronAPI.saveAllowMultiInstance && allowMultiInstanceCheck) {
const enabled = allowMultiInstanceCheck.checked;
await window.electronAPI.saveAllowMultiInstance(enabled);
}
} catch (error) {
console.error('Error saving multi-instance setting:', error);
}
}
async function loadAllowMultiInstance() {
try {
if (window.electronAPI && window.electronAPI.loadAllowMultiInstance) {
const enabled = await window.electronAPI.loadAllowMultiInstance();
if (allowMultiInstanceCheck) {
allowMultiInstanceCheck.checked = enabled;
}
}
} catch (error) {
console.error('Error loading multi-instance setting:', error);
}
}
async function saveLauncherHwAccel() { async function saveLauncherHwAccel() {
try { try {
if (window.electronAPI && window.electronAPI.saveLauncherHardwareAcceleration && launcherHwAccelCheck) { if (window.electronAPI && window.electronAPI.saveLauncherHardwareAcceleration && launcherHwAccelCheck) {
@@ -587,6 +617,7 @@ async function loadAllSettings() {
await loadCurrentUuid(); await loadCurrentUuid();
await loadDiscordRPC(); await loadDiscordRPC();
await loadCloseLauncher(); await loadCloseLauncher();
await loadAllowMultiInstance();
await loadLauncherHwAccel(); await loadLauncherHwAccel();
await loadGpuPreference(); await loadGpuPreference();
await loadVersionBranch(); await loadVersionBranch();

View File

@@ -140,6 +140,8 @@
"closeLauncher": "سلوك المشغل", "closeLauncher": "سلوك المشغل",
"closeOnStart": "إغلاق المشغل عند بدء اللعبة", "closeOnStart": "إغلاق المشغل عند بدء اللعبة",
"closeOnStartDescription": "إغلاق المشغل تلقائياً بعد تشغيل Hytale", "closeOnStartDescription": "إغلاق المشغل تلقائياً بعد تشغيل Hytale",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "تسريع الأجهزة (Hardware Acceleration)", "hwAccel": "تسريع الأجهزة (Hardware Acceleration)",
"hwAccelDescription": "تفعيل تسريع الأجهزة للمشغل", "hwAccelDescription": "تفعيل تسريع الأجهزة للمشغل",
"gameBranch": "فرع اللعبة", "gameBranch": "فرع اللعبة",

View File

@@ -140,6 +140,8 @@
"closeLauncher": "Launcher-Verhalten", "closeLauncher": "Launcher-Verhalten",
"closeOnStart": "Launcher beim Spielstart schließen", "closeOnStart": "Launcher beim Spielstart schließen",
"closeOnStartDescription": "Schließe den Launcher automatisch, nachdem Hytale gestartet wurde", "closeOnStartDescription": "Schließe den Launcher automatisch, nachdem Hytale gestartet wurde",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Hardware-Beschleunigung", "hwAccel": "Hardware-Beschleunigung",
"hwAccelDescription": "Hardware-Beschleunigung für den Launcher aktivieren", "hwAccelDescription": "Hardware-Beschleunigung für den Launcher aktivieren",
"gameBranch": "Spiel-Branch", "gameBranch": "Spiel-Branch",

View File

@@ -140,6 +140,8 @@
"closeLauncher": "Launcher Behavior", "closeLauncher": "Launcher Behavior",
"closeOnStart": "Close Launcher on game start", "closeOnStart": "Close Launcher on game start",
"closeOnStartDescription": "Automatically close the launcher after Hytale has launched", "closeOnStartDescription": "Automatically close the launcher after Hytale has launched",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Hardware Acceleration", "hwAccel": "Hardware Acceleration",
"hwAccelDescription": "Enable hardware acceleration for the launcher", "hwAccelDescription": "Enable hardware acceleration for the launcher",
"gameBranch": "Game Branch", "gameBranch": "Game Branch",

View File

@@ -140,6 +140,8 @@
"closeLauncher": "Comportamiento del Launcher", "closeLauncher": "Comportamiento del Launcher",
"closeOnStart": "Cerrar Launcher al iniciar el juego", "closeOnStart": "Cerrar Launcher al iniciar el juego",
"closeOnStartDescription": "Cierra automáticamente el launcher después de que Hytale se haya iniciado", "closeOnStartDescription": "Cierra automáticamente el launcher después de que Hytale se haya iniciado",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Aceleración por Hardware", "hwAccel": "Aceleración por Hardware",
"hwAccelDescription": "Habilitar aceleración por hardware para el launcher", "hwAccelDescription": "Habilitar aceleración por hardware para el launcher",
"gameBranch": "Rama del Juego", "gameBranch": "Rama del Juego",

View File

@@ -140,6 +140,8 @@
"closeLauncher": "Comportement du Launcher", "closeLauncher": "Comportement du Launcher",
"closeOnStart": "Fermer le Launcher au démarrage du jeu", "closeOnStart": "Fermer le Launcher au démarrage du jeu",
"closeOnStartDescription": "Fermer automatiquement le launcher après le lancement d'Hytale", "closeOnStartDescription": "Fermer automatiquement le launcher après le lancement d'Hytale",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Accélération Matérielle", "hwAccel": "Accélération Matérielle",
"hwAccelDescription": "Activer l'accélération matérielle pour le launcher", "hwAccelDescription": "Activer l'accélération matérielle pour le launcher",
"gameBranch": "Branche du Jeu", "gameBranch": "Branche du Jeu",

View File

@@ -140,6 +140,8 @@
"closeLauncher": "Perilaku Launcher", "closeLauncher": "Perilaku Launcher",
"closeOnStart": "Tutup launcher saat game dimulai", "closeOnStart": "Tutup launcher saat game dimulai",
"closeOnStartDescription": "Tutup launcher secara otomatis setelah Hytale diluncurkan", "closeOnStartDescription": "Tutup launcher secara otomatis setelah Hytale diluncurkan",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Akselerasi Perangkat Keras", "hwAccel": "Akselerasi Perangkat Keras",
"hwAccelDescription": "Aktifkan akselerasi perangkat keras untuk launcher`", "hwAccelDescription": "Aktifkan akselerasi perangkat keras untuk launcher`",
"gameBranch": "Cabang Game", "gameBranch": "Cabang Game",

View File

@@ -140,6 +140,8 @@
"closeLauncher": "Zachowanie Launchera", "closeLauncher": "Zachowanie Launchera",
"closeOnStart": "Zamknij Launcher przy starcie gry", "closeOnStart": "Zamknij Launcher przy starcie gry",
"closeOnStartDescription": "Automatycznie zamknij launcher po uruchomieniu Hytale", "closeOnStartDescription": "Automatycznie zamknij launcher po uruchomieniu Hytale",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Przyspieszenie Sprzętowe", "hwAccel": "Przyspieszenie Sprzętowe",
"hwAccelDescription": "Włącz przyspieszenie sprzętowe dla launchera", "hwAccelDescription": "Włącz przyspieszenie sprzętowe dla launchera",
"gameBranch": "Gałąź Gry", "gameBranch": "Gałąź Gry",

View File

@@ -140,6 +140,8 @@
"closeLauncher": "Comportamento do Lançador", "closeLauncher": "Comportamento do Lançador",
"closeOnStart": "Fechar Lançador ao iniciar o jogo", "closeOnStart": "Fechar Lançador ao iniciar o jogo",
"closeOnStartDescription": "Fechar automaticamente o lançador após o Hytale ter sido iniciado", "closeOnStartDescription": "Fechar automaticamente o lançador após o Hytale ter sido iniciado",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Aceleração de Hardware", "hwAccel": "Aceleração de Hardware",
"hwAccelDescription": "Ativar aceleração de hardware para o lançador", "hwAccelDescription": "Ativar aceleração de hardware para o lançador",
"gameBranch": "Versão do Jogo", "gameBranch": "Versão do Jogo",

View File

@@ -140,6 +140,8 @@
"closeLauncher": "Поведение лаунчера", "closeLauncher": "Поведение лаунчера",
"closeOnStart": "Закрыть лаунчер при старте игры", "closeOnStart": "Закрыть лаунчер при старте игры",
"closeOnStartDescription": "Автоматически закрыть лаунчер после запуска Hytale", "closeOnStartDescription": "Автоматически закрыть лаунчер после запуска Hytale",
"allowMultiInstance": "Несколько копий игры",
"allowMultiInstanceDescription": "Разрешить запуск нескольких клиентов одновременно (полезно для разработки модов)",
"hwAccel": "Аппаратное ускорение", "hwAccel": "Аппаратное ускорение",
"hwAccelDescription": "Включить аппаратное ускорение для лаунчера", "hwAccelDescription": "Включить аппаратное ускорение для лаунчера",
"gameBranch": "Ветка игры", "gameBranch": "Ветка игры",

View File

@@ -140,6 +140,8 @@
"closeLauncher": "Launcher-beteende", "closeLauncher": "Launcher-beteende",
"closeOnStart": "Stäng launcher vid spelstart", "closeOnStart": "Stäng launcher vid spelstart",
"closeOnStartDescription": "Stäng automatiskt launcher efter att Hytale har startats", "closeOnStartDescription": "Stäng automatiskt launcher efter att Hytale har startats",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Hårdvaruacceleration", "hwAccel": "Hårdvaruacceleration",
"hwAccelDescription": "Aktivera hårdvaruacceleration för launchern", "hwAccelDescription": "Aktivera hårdvaruacceleration för launchern",
"gameBranch": "Spelgren", "gameBranch": "Spelgren",

View File

@@ -140,6 +140,8 @@
"closeLauncher": "Başlatıcı Davranışı", "closeLauncher": "Başlatıcı Davranışı",
"closeOnStart": "Oyun başlatıldığında Başlatıcıyı Kapat", "closeOnStart": "Oyun başlatıldığında Başlatıcıyı Kapat",
"closeOnStartDescription": "Hytale başlatıldıktan sonra başlatıcıyı otomatik olarak kapatın", "closeOnStartDescription": "Hytale başlatıldıktan sonra başlatıcıyı otomatik olarak kapatın",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Donanım Hızlandırma", "hwAccel": "Donanım Hızlandırma",
"hwAccelDescription": "Başlatıcı için donanım hızlandırmasını etkinleştir", "hwAccelDescription": "Başlatıcı için donanım hızlandırmasını etkinleştir",
"gameBranch": "Oyun Dalı", "gameBranch": "Oyun Dalı",

View File

@@ -708,6 +708,15 @@ function loadLauncherHardwareAcceleration() {
return config.launcherHardwareAcceleration !== undefined ? config.launcherHardwareAcceleration : true; return config.launcherHardwareAcceleration !== undefined ? config.launcherHardwareAcceleration : true;
} }
function saveAllowMultiInstance(enabled) {
saveConfig({ allowMultiInstance: !!enabled });
}
function loadAllowMultiInstance() {
const config = loadConfig();
return config.allowMultiInstance !== undefined ? config.allowMultiInstance : false;
}
// ============================================================================= // =============================================================================
// MODS MANAGEMENT // MODS MANAGEMENT
// ============================================================================= // =============================================================================
@@ -1105,6 +1114,8 @@ module.exports = {
loadCloseLauncherOnStart, loadCloseLauncherOnStart,
saveLauncherHardwareAcceleration, saveLauncherHardwareAcceleration,
loadLauncherHardwareAcceleration, loadLauncherHardwareAcceleration,
saveAllowMultiInstance,
loadAllowMultiInstance,
// Mods // Mods
saveModsToConfig, saveModsToConfig,

View File

@@ -20,6 +20,8 @@ const {
saveLauncherHardwareAcceleration, saveLauncherHardwareAcceleration,
loadLauncherHardwareAcceleration, loadLauncherHardwareAcceleration,
saveAllowMultiInstance,
loadAllowMultiInstance,
loadConfig, loadConfig,
saveConfig, saveConfig,
@@ -151,6 +153,10 @@ module.exports = {
saveLauncherHardwareAcceleration, saveLauncherHardwareAcceleration,
loadLauncherHardwareAcceleration, loadLauncherHardwareAcceleration,
// Multi-instance functions
saveAllowMultiInstance,
loadAllowMultiInstance,
// Config functions // Config functions
loadConfig, loadConfig,
saveConfig, saveConfig,

View File

@@ -30,6 +30,7 @@ const { syncModsForCurrentProfile } = require('./modManager');
const { getUserDataPath } = require('../utils/userDataMigration'); const { getUserDataPath } = require('../utils/userDataMigration');
const { syncServerList } = require('../utils/serverListSync'); const { syncServerList } = require('../utils/serverListSync');
const { killGameProcesses } = require('./gameManager'); const { killGameProcesses } = require('./gameManager');
const { loadAllowMultiInstance } = require('../core/config');
// Client patcher for custom auth server (sanasol.ws) // Client patcher for custom auth server (sanasol.ws)
let clientPatcher = null; let clientPatcher = null;
@@ -464,8 +465,10 @@ async function launchGame(playerNameOverride = null, progressCallback, javaPathO
} }
// Kill any stalled game processes from a previous launch to prevent file locks // Kill any stalled game processes from a previous launch to prevent file locks
// and "game already running" issues // and "game already running" issues (skip if multi-instance mode is enabled)
if (!loadAllowMultiInstance()) {
await killGameProcesses(); await killGameProcesses();
}
// Remove AOT cache: generated by official Hytale JRE, incompatible with F2P JRE. // Remove AOT cache: generated by official Hytale JRE, incompatible with F2P JRE.
// Client adds -XX:AOTCache when this file exists, causing classloading failures. // Client adds -XX:AOTCache when this file exists, causing classloading failures.

View File

@@ -106,6 +106,23 @@ function getBundledJavaPath(jreDir = JRE_DIR) {
} }
} }
// Fallback: check for nested JRE directory (e.g. jdk-25.0.2+10-jre/bin/java)
// This happens when flattenJREDir fails due to EPERM/EACCES on Windows
try {
if (fs.existsSync(jreDir)) {
const entries = fs.readdirSync(jreDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && entry.name !== 'bin' && entry.name !== 'lib') {
const nestedCandidate = path.join(jreDir, entry.name, 'bin', JAVA_EXECUTABLE);
if (fs.existsSync(nestedCandidate)) {
console.log(`[JRE] Using nested Java path: ${nestedCandidate}`);
return nestedCandidate;
}
}
}
}
} catch (_) { /* ignore */ }
return null; return null;
} }
@@ -420,12 +437,48 @@ function flattenJREDir(jreLatest) {
for (const file of files) { for (const file of files) {
const oldPath = path.join(nested, file.name); const oldPath = path.join(nested, file.name);
const newPath = path.join(jreLatest, file.name); const newPath = path.join(jreLatest, file.name);
try {
fs.renameSync(oldPath, newPath); fs.renameSync(oldPath, newPath);
} catch (renameErr) {
if (renameErr.code === 'EPERM' || renameErr.code === 'EACCES' || renameErr.code === 'EBUSY') {
console.log(`[JRE] Rename failed for ${file.name} (${renameErr.code}), using copy fallback`);
copyRecursiveSync(oldPath, newPath);
} else {
throw renameErr;
}
}
} }
try {
fs.rmSync(nested, { recursive: true, force: true }); fs.rmSync(nested, { recursive: true, force: true });
} catch (rmErr) {
console.log('[JRE] Could not remove nested JRE dir (non-critical):', rmErr.message);
}
} catch (err) { } catch (err) {
console.log('Notice: could not restructure Java directory:', err.message); console.error('[JRE] Failed to restructure Java directory:', err.message);
// Last resort: check if java exists in a nested subdir and skip flatten
try {
const entries = fs.readdirSync(jreLatest, { withFileTypes: true });
const nestedDir = entries.find(e => e.isDirectory() && e.name !== 'bin' && e.name !== 'lib');
if (nestedDir) {
const nestedBin = path.join(jreLatest, nestedDir.name, 'bin', process.platform === 'win32' ? 'java.exe' : 'java');
if (fs.existsSync(nestedBin)) {
console.log(`[JRE] Java found in nested dir: ${nestedDir.name}, leaving structure as-is`);
}
}
} catch (_) { /* ignore */ }
}
}
function copyRecursiveSync(src, dest) {
const stat = fs.statSync(src);
if (stat.isDirectory()) {
fs.mkdirSync(dest, { recursive: true });
for (const child of fs.readdirSync(src)) {
copyRecursiveSync(path.join(src, child), path.join(dest, child));
}
} else {
fs.copyFileSync(src, dest);
} }
} }

View File

@@ -1,10 +1,10 @@
# Singleplayer Server Crash: fastutil ClassNotFoundException # Singleplayer Server Crash: fastutil ClassNotFoundException
## Status: Open (user-specific, Feb 24 2026) ## Status: Open — NO SOLUTION (Feb 24-27 2026)
## Symptom ## Symptom
Singleplayer server crashes immediately after DualAuth Agent installs successfully: Singleplayer server crashes immediately on boot:
``` ```
Exception in thread "main" java.lang.NoClassDefFoundError: it/unimi/dsi/fastutil/objects/ObjectArrayList Exception in thread "main" java.lang.NoClassDefFoundError: it/unimi/dsi/fastutil/objects/ObjectArrayList
@@ -16,98 +16,64 @@ Caused by: java.lang.ClassNotFoundException: it.unimi.dsi.fastutil.objects.Objec
Server exits with code 1. Multiplayer works fine for the same user. Server exits with code 1. Multiplayer works fine for the same user.
## Affected User ## Affected Users
1. **ヅ𝚃 JAYED !** (Feb 24) — Windows x86_64, had AOT cache errors before fastutil crash
2. **Asentrix** (Feb 27) — Windows x86_64 (NT 10.0.26200.0), RTX 4060, Launcher v2.4.4, NO AOT cache errors
- Discord: ヅ𝚃 JAYED !
- Platform: Windows (standard x86_64, NOT ARM)
- Reproduces 100% on singleplayer, every attempt - Reproduces 100% on singleplayer, every attempt
- Other users (including macOS/Linux) are NOT affected - Multiplayer works fine for both users
- macOS/Linux users are NOT affected
## What Works ## Ruled Out (confirmed via debug builds)
- Java wrapper correctly strips `-XX:+UseCompactObjectHeaders` | Suspect | Tested | Result |
- Java wrapper correctly injects `--disable-sentry` |---------|--------|--------|
- DualAuth Agent v1.1.12 installs successfully (STATIC mode) | **DualAuth Agent** | Debug build with agent completely disabled (`debug-no-agent` tag) | **Same crash.** Agent is innocent. |
- Multiplayer connections work fine | **`-Xshare:off` (CDS)** | Added to `JAVA_TOOL_OPTIONS` in launcher code (`debug-xshare-off` tag) | **Did not help.** CDS is not the cause. |
- Repair and reinstall did NOT fix the issue | **`-XX:+UseCompactObjectHeaders`** | Stripped via wrapper | **Did not help.** Server has `-XX:+IgnoreUnrecognizedVMOptions` anyway. |
| **Corrupted game files** | User did repair + full reinstall | **Same crash.** |
| **Java wrapper** | Logs confirm wrapper works correctly | Not the cause. |
| **ARM64/Parallels** | User is on standard Windows x86_64 | Not applicable. |
| **AOT cache** | Asentrix has no AOT errors (JAYED did), both crash the same way | Not the root cause. |
## Root Cause Analysis ## What We Know
`fastutil` (`it.unimi.dsi.fastutil`) should be bundled inside `HytaleServer.jar` (fat JAR). The `ClassNotFoundException` means the JVM's app classloader cannot find it despite it being in the JAR. - `fastutil` is bundled inside `HytaleServer.jar` (fat/shaded JAR) — same JAR for all users
- JVM's `BuiltinClassLoader` cannot find `it.unimi.dsi.fastutil.objects.ObjectArrayList` despite it being in the JAR
- Crash happens at `EarlyPluginLoader` static initializer (line 34) which imports `ObjectArrayList`
- The bundled JRE is identical for all users (downloaded by launcher)
- The issue is **not** caused by anything the F2P launcher adds — it's the vanilla server JVM failing to load its own classes
### Ruled Out ## Remaining Theories
- **Wrapper issue**: Wrapper is working correctly (confirmed in logs) 1. **Antivirus/security software** — Windows Defender or third-party AV intercepting JAR file reads. Real-time scanning + fat JAR = known conflict. **Untested** — user should try disabling AV temporarily.
- **UseCompactObjectHeaders**: Server also has `-XX:+IgnoreUnrecognizedVMOptions`, so unrecognized flags don't crash it 2. **Windows Insider build** — Asentrix is on NT 10.0.26200.0 (Windows 11 Dev/Insider). Bleeding-edge Windows may have JVM compatibility issues.
- **DualAuth Agent**: Works for all other users; agent installs successfully before the crash 3. **File locking** — Stalled `java.exe` processes holding `HytaleServer.jar` open (Asentrix had stalled processes killed at every launch).
- **Corrupted game files**: Repair/reinstall didn't help 4. **Corrupted JRE on disk** — Despite being the same download, filesystem or AV may have corrupted specific JRE files on their system.
- **ARM64/Parallels**: User is on standard Windows, not ARM
### Likely Causes (user-specific) ## Next Steps to Try
1. **Antivirus interference** — Windows Defender or third-party AV blocking Java from reading classes out of JAR files, especially with `-javaagent` active 1. **Disable Windows Defender** temporarily — the only quick test left
2. **Corrupted/incompatible JRE** — bundled JRE might be broken on their system 2. **Delete bundled JRE** and let launcher re-download — rules out local JRE corruption
3. **File locking** — another process holding HytaleServer.jar open 3. **Ask if official Hytale singleplayer works** — if it also crashes, it's their system (but F2P users may not have access)
## Debugging Steps (ask user) ## Update History
1. **Does official Hytale singleplayer work?** (without F2P launcher) ### Feb 24: First report (JAYED)
- Yes → something about our launch setup User reported singleplayer crash. Initial investigation found AOT cache errors + fastutil ClassNotFoundException. Stripping `-XX:+UseCompactObjectHeaders` did not help.
- No → their system/JRE issue
2. **Check antivirus** — add game directory to Windows Defender exclusions: ### Feb 27: Second report (Asentrix), extensive debugging
- Settings → Windows Security → Virus & threat protection → Exclusions - Asentrix hit same crash, no AOT errors — ruled out AOT as root cause
- Add their HytaleF2P install folder - Built `debug-xshare-off`: added `-Xshare:off` to `JAVA_TOOL_OPTIONS`**did not help**
- Built `debug-no-agent`: completely disabled DualAuth agent — **same crash**
3. **Verify fastutil is in the JAR**: - **Conclusion**: Neither the agent nor CDS is the cause. The JVM itself cannot load classes from the fat JAR on these specific Windows systems.
```cmd - Note: wrapper `injectArgs` append AFTER `-jar`, so they cannot inject JVM flags — only `JAVA_TOOL_OPTIONS` works for JVM flags
jar tf "D:\path\to\Server\HytaleServer.jar" | findstr fastutil
```
- If output shows fastutil classes → JAR is fine, classloader issue
- If no output → JAR is incomplete/corrupt (different from other users)
4. **Try without DualAuth agent** — rename `dualauth-agent.jar` in Server/ folder, retry singleplayer
- If works → agent's classloader manipulation breaks fastutil on their setup
- If still fails → unrelated to agent
5. **Check JRE version** — have them run:
```cmd
"D:\path\to\jre\latest\bin\java.exe" -version
```
## Update (Feb 24): `-XX:+UseCompactObjectHeaders` stripping removed from defaults
Stripping this flag did NOT fix the issue. The server already has `-XX:+IgnoreUnrecognizedVMOptions` so unrecognized flags are harmless. The flag was removed from default `stripFlags` in `backend/core/config.js`.
## Using the Java Wrapper to Strip JVM Flags
If a user needs to strip a specific JVM flag (e.g., for debugging or compatibility), they can do it via the launcher UI:
1. Open **Settings** → scroll to **Java Wrapper Configuration**
2. Under **JVM Flags to Remove**, type the flag (e.g. `-XX:+UseCompactObjectHeaders`) and click **Add**
3. The flag will be stripped from all JVM invocations at launch time
4. To inject custom arguments, use the **Arguments to Inject** section (with optional "Server Only" condition)
5. **Restore Defaults** resets to empty strip flags + `--disable-sentry` (server only)
The wrapper generates platform-specific scripts at launch time:
- **Windows**: `java-wrapper.bat` in `jre/latest/bin/`
- **macOS/Linux**: `java-wrapper` shell script in the same directory
Config is stored in `config.json` under `javaWrapperConfig`:
```json
{
"javaWrapperConfig": {
"stripFlags": ["-XX:+SomeFlag"],
"injectArgs": [
{ "arg": "--some-arg", "condition": "server" },
{ "arg": "--other-arg", "condition": "always" }
]
}
}
```
## Related ## Related
- Java wrapper config: `backend/core/config.js` (stripFlags / injectArgs) - Java wrapper config: `backend/core/config.js` (stripFlags / injectArgs)
- DualAuth Agent: v1.1.12, package `ws.sanasol.dualauth` - DualAuth Agent: v1.1.12, package `ws.sanasol.dualauth`
- Game version at time of report: `2026.02.19-1a311a592` - Game version at time of reports: `2026.02.19-1a311a592`
- Debug tags: `debug-xshare-off`, `debug-no-agent`
- Log submission IDs: `c88e7b71` (Asentrix initial), `0445e4dc` (xshare test), `748dceeb` (no-agent test)

16
main.js
View File

@@ -3,7 +3,7 @@ require('dotenv').config({ path: path.join(__dirname, '.env') });
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron'); const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
const { autoUpdater } = require('electron-updater'); const { autoUpdater } = require('electron-updater');
const fs = require('fs'); const fs = require('fs');
const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, saveLauncherHardwareAcceleration, loadLauncherHardwareAcceleration, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched, loadConfig, saveConfig, checkLaunchReady } = require('./backend/launcher'); const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, saveLauncherHardwareAcceleration, loadLauncherHardwareAcceleration, saveAllowMultiInstance, loadAllowMultiInstance, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched, loadConfig, saveConfig, checkLaunchReady } = require('./backend/launcher');
const { retryPWRDownload } = require('./backend/managers/gameManager'); const { retryPWRDownload } = require('./backend/managers/gameManager');
const { migrateUserDataToCentralized } = require('./backend/utils/userDataMigration'); const { migrateUserDataToCentralized } = require('./backend/utils/userDataMigration');
@@ -23,8 +23,9 @@ const profileManager = require('./backend/managers/profileManager');
logger.interceptConsole(); logger.interceptConsole();
// Single instance lock // Single instance lock (skip if multi-instance mode is enabled)
const gotTheLock = app.requestSingleInstanceLock(); const multiInstanceEnabled = loadAllowMultiInstance();
const gotTheLock = multiInstanceEnabled || app.requestSingleInstanceLock();
if (!gotTheLock) { if (!gotTheLock) {
console.log('Another instance is already running. Quitting...'); console.log('Another instance is already running. Quitting...');
@@ -740,6 +741,15 @@ ipcMain.handle('load-close-launcher', () => {
return loadCloseLauncherOnStart(); return loadCloseLauncherOnStart();
}); });
ipcMain.handle('save-allow-multi-instance', (event, enabled) => {
saveAllowMultiInstance(enabled);
return { success: true };
});
ipcMain.handle('load-allow-multi-instance', () => {
return loadAllowMultiInstance();
});
ipcMain.handle('save-launcher-hw-accel', (event, enabled) => { ipcMain.handle('save-launcher-hw-accel', (event, enabled) => {
saveLauncherHardwareAcceleration(enabled); saveLauncherHardwareAcceleration(enabled);
return { success: true }; return { success: true };

View File

@@ -1,6 +1,6 @@
{ {
"name": "hytale-f2p-launcher", "name": "hytale-f2p-launcher",
"version": "2.4.4", "version": "2.4.5",
"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://git.sanhost.net/sanasol/hytale-f2p", "homepage": "https://git.sanhost.net/sanasol/hytale-f2p",
"main": "main.js", "main": "main.js",

View File

@@ -20,6 +20,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
loadLanguage: () => ipcRenderer.invoke('load-language'), loadLanguage: () => ipcRenderer.invoke('load-language'),
saveCloseLauncher: (enabled) => ipcRenderer.invoke('save-close-launcher', enabled), saveCloseLauncher: (enabled) => ipcRenderer.invoke('save-close-launcher', enabled),
loadCloseLauncher: () => ipcRenderer.invoke('load-close-launcher'), loadCloseLauncher: () => ipcRenderer.invoke('load-close-launcher'),
saveAllowMultiInstance: (enabled) => ipcRenderer.invoke('save-allow-multi-instance', enabled),
loadAllowMultiInstance: () => ipcRenderer.invoke('load-allow-multi-instance'),
loadConfig: () => ipcRenderer.invoke('load-config'), loadConfig: () => ipcRenderer.invoke('load-config'),
saveConfig: (configUpdate) => ipcRenderer.invoke('save-config', configUpdate), saveConfig: (configUpdate) => ipcRenderer.invoke('save-config', configUpdate),

View File

@@ -1,17 +0,0 @@
## v2.4.0
### New Features
- **Send Logs** — One-click button to submit launcher & game logs to support. Collects launcher logs, game client logs, and config snapshot into a ZIP, uploads to server, and returns an 8-character ID to share with support ([320ca54](https://git.sanhost.net/sanasol/hytale-f2p/commit/320ca547585c67d0773dba262612db5026378f52), [19c8991](https://git.sanhost.net/sanasol/hytale-f2p/commit/19c8991a44641ebbf44eec73a0ecd9db05241c49))
- **Arabic (ar-SA) locale** with full RTL support (community contribution by @Yugurten) ([30929ee](https://git.sanhost.net/sanasol/hytale-f2p/commit/30929ee0da5a9c64e65869d6157bd705db3b80f0))
- **One-click dedicated server scripts** for self-hosting ([552ec42](https://git.sanhost.net/sanasol/hytale-f2p/commit/552ec42d6c7e1e7d1a2803d284019ccae963f41e))
### Bug Fixes
- Fix Intel Arc iGPU (Meteor Lake/Lunar Lake) on PCI bus 00 being misdetected as discrete GPU on dual-GPU Linux systems ([19c8991](https://git.sanhost.net/sanasol/hytale-f2p/commit/19c8991a44641ebbf44eec73a0ecd9db05241c49))
- Fix stalled game processes blocking launcher operations — automatic process cleanup on repair and relaunch ([e14d56e](https://git.sanhost.net/sanasol/hytale-f2p/commit/e14d56ef4846423c1fd172d88334cb76193ee741))
- Fix AOT cache crashes — stale cache cleared before game launch ([e14d56e](https://git.sanhost.net/sanasol/hytale-f2p/commit/e14d56ef4846423c1fd172d88334cb76193ee741))
- Fix Arabic RTL CSS syntax ([fb90277](https://git.sanhost.net/sanasol/hytale-f2p/commit/fb90277be9cf5f0b8a90195a7d089273b6be082b))
### Other
- Updated README with Forgejo URLs and server setup video ([a649bf1](https://git.sanhost.net/sanasol/hytale-f2p/commit/a649bf1fcc7cbb2cd0d9aa0160b07828a144b9dd), [66faa1b](https://git.sanhost.net/sanasol/hytale-f2p/commit/66faa1bb1e39575fecb462310af338d13b1cb183))
**Full changelog**: [v2.3.8...v2.4.0](https://git.sanhost.net/sanasol/hytale-f2p/compare/v2.3.8...v2.4.0)