mirror of
https://git.sanhost.net/sanasol/hytale-f2p
synced 2026-02-27 20:21:47 -03:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee53911a06 | ||
|
|
43a2b6d004 | ||
|
|
ce6455314d | ||
|
|
3abdd10cab | ||
|
|
e1a3f919a2 | ||
|
|
e3fe1b6a10 |
@@ -592,6 +592,18 @@
|
||||
</div>
|
||||
</label>
|
||||
</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">
|
||||
<label class="settings-checkbox">
|
||||
<input type="checkbox" id="launcherHwAccelCheck" />
|
||||
|
||||
@@ -6,6 +6,7 @@ let browseJavaBtn;
|
||||
let settingsPlayerName;
|
||||
let discordRPCCheck;
|
||||
let closeLauncherCheck;
|
||||
let allowMultiInstanceCheck;
|
||||
let launcherHwAccelCheck;
|
||||
let gpuPreferenceRadios;
|
||||
let gameBranchRadios;
|
||||
@@ -171,6 +172,7 @@ function setupSettingsElements() {
|
||||
settingsPlayerName = document.getElementById('settingsPlayerName');
|
||||
discordRPCCheck = document.getElementById('discordRPCCheck');
|
||||
closeLauncherCheck = document.getElementById('closeLauncherCheck');
|
||||
allowMultiInstanceCheck = document.getElementById('allowMultiInstanceCheck');
|
||||
launcherHwAccelCheck = document.getElementById('launcherHwAccelCheck');
|
||||
gpuPreferenceRadios = document.querySelectorAll('input[name="gpuPreference"]');
|
||||
gameBranchRadios = document.querySelectorAll('input[name="gameBranch"]');
|
||||
@@ -218,6 +220,10 @@ function setupSettingsElements() {
|
||||
closeLauncherCheck.addEventListener('change', saveCloseLauncher);
|
||||
}
|
||||
|
||||
if (allowMultiInstanceCheck) {
|
||||
allowMultiInstanceCheck.addEventListener('change', saveAllowMultiInstance);
|
||||
}
|
||||
|
||||
if (launcherHwAccelCheck) {
|
||||
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() {
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.saveLauncherHardwareAcceleration && launcherHwAccelCheck) {
|
||||
@@ -587,6 +617,7 @@ async function loadAllSettings() {
|
||||
await loadCurrentUuid();
|
||||
await loadDiscordRPC();
|
||||
await loadCloseLauncher();
|
||||
await loadAllowMultiInstance();
|
||||
await loadLauncherHwAccel();
|
||||
await loadGpuPreference();
|
||||
await loadVersionBranch();
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"closeLauncher": "سلوك المشغل",
|
||||
"closeOnStart": "إغلاق المشغل عند بدء اللعبة",
|
||||
"closeOnStartDescription": "إغلاق المشغل تلقائياً بعد تشغيل Hytale",
|
||||
"allowMultiInstance": "Allow multiple game instances",
|
||||
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
|
||||
"hwAccel": "تسريع الأجهزة (Hardware Acceleration)",
|
||||
"hwAccelDescription": "تفعيل تسريع الأجهزة للمشغل",
|
||||
"gameBranch": "فرع اللعبة",
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"closeLauncher": "Launcher-Verhalten",
|
||||
"closeOnStart": "Launcher beim Spielstart schließen",
|
||||
"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",
|
||||
"hwAccelDescription": "Hardware-Beschleunigung für den Launcher aktivieren",
|
||||
"gameBranch": "Spiel-Branch",
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"closeLauncher": "Launcher Behavior",
|
||||
"closeOnStart": "Close Launcher on game start",
|
||||
"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",
|
||||
"hwAccelDescription": "Enable hardware acceleration for the launcher",
|
||||
"gameBranch": "Game Branch",
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"closeLauncher": "Comportamiento del Launcher",
|
||||
"closeOnStart": "Cerrar Launcher al iniciar el juego",
|
||||
"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",
|
||||
"hwAccelDescription": "Habilitar aceleración por hardware para el launcher",
|
||||
"gameBranch": "Rama del Juego",
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"closeLauncher": "Comportement du Launcher",
|
||||
"closeOnStart": "Fermer le Launcher au démarrage du jeu",
|
||||
"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",
|
||||
"hwAccelDescription": "Activer l'accélération matérielle pour le launcher",
|
||||
"gameBranch": "Branche du Jeu",
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"closeLauncher": "Perilaku Launcher",
|
||||
"closeOnStart": "Tutup launcher saat game dimulai",
|
||||
"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",
|
||||
"hwAccelDescription": "Aktifkan akselerasi perangkat keras untuk launcher`",
|
||||
"gameBranch": "Cabang Game",
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"closeLauncher": "Zachowanie Launchera",
|
||||
"closeOnStart": "Zamknij Launcher przy starcie gry",
|
||||
"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",
|
||||
"hwAccelDescription": "Włącz przyspieszenie sprzętowe dla launchera",
|
||||
"gameBranch": "Gałąź Gry",
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"closeLauncher": "Comportamento do Lançador",
|
||||
"closeOnStart": "Fechar Lançador ao iniciar o jogo",
|
||||
"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",
|
||||
"hwAccelDescription": "Ativar aceleração de hardware para o lançador",
|
||||
"gameBranch": "Versão do Jogo",
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"closeLauncher": "Поведение лаунчера",
|
||||
"closeOnStart": "Закрыть лаунчер при старте игры",
|
||||
"closeOnStartDescription": "Автоматически закрыть лаунчер после запуска Hytale",
|
||||
"allowMultiInstance": "Несколько копий игры",
|
||||
"allowMultiInstanceDescription": "Разрешить запуск нескольких клиентов одновременно (полезно для разработки модов)",
|
||||
"hwAccel": "Аппаратное ускорение",
|
||||
"hwAccelDescription": "Включить аппаратное ускорение для лаунчера",
|
||||
"gameBranch": "Ветка игры",
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"closeLauncher": "Launcher-beteende",
|
||||
"closeOnStart": "Stäng launcher vid spelstart",
|
||||
"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",
|
||||
"hwAccelDescription": "Aktivera hårdvaruacceleration för launchern",
|
||||
"gameBranch": "Spelgren",
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"closeLauncher": "Başlatıcı Davranışı",
|
||||
"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",
|
||||
"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",
|
||||
"hwAccelDescription": "Başlatıcı için donanım hızlandırmasını etkinleştir",
|
||||
"gameBranch": "Oyun Dalı",
|
||||
|
||||
@@ -708,6 +708,15 @@ function loadLauncherHardwareAcceleration() {
|
||||
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
|
||||
// =============================================================================
|
||||
@@ -1105,6 +1114,8 @@ module.exports = {
|
||||
loadCloseLauncherOnStart,
|
||||
saveLauncherHardwareAcceleration,
|
||||
loadLauncherHardwareAcceleration,
|
||||
saveAllowMultiInstance,
|
||||
loadAllowMultiInstance,
|
||||
|
||||
// Mods
|
||||
saveModsToConfig,
|
||||
|
||||
@@ -20,6 +20,8 @@ const {
|
||||
|
||||
saveLauncherHardwareAcceleration,
|
||||
loadLauncherHardwareAcceleration,
|
||||
saveAllowMultiInstance,
|
||||
loadAllowMultiInstance,
|
||||
|
||||
loadConfig,
|
||||
saveConfig,
|
||||
@@ -151,6 +153,10 @@ module.exports = {
|
||||
saveLauncherHardwareAcceleration,
|
||||
loadLauncherHardwareAcceleration,
|
||||
|
||||
// Multi-instance functions
|
||||
saveAllowMultiInstance,
|
||||
loadAllowMultiInstance,
|
||||
|
||||
// Config functions
|
||||
loadConfig,
|
||||
saveConfig,
|
||||
|
||||
@@ -30,6 +30,7 @@ const { syncModsForCurrentProfile } = require('./modManager');
|
||||
const { getUserDataPath } = require('../utils/userDataMigration');
|
||||
const { syncServerList } = require('../utils/serverListSync');
|
||||
const { killGameProcesses } = require('./gameManager');
|
||||
const { loadAllowMultiInstance } = require('../core/config');
|
||||
|
||||
// Client patcher for custom auth server (sanasol.ws)
|
||||
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
|
||||
// and "game already running" issues
|
||||
// and "game already running" issues (skip if multi-instance mode is enabled)
|
||||
if (!loadAllowMultiInstance()) {
|
||||
await killGameProcesses();
|
||||
}
|
||||
|
||||
// Remove AOT cache: generated by official Hytale JRE, incompatible with F2P JRE.
|
||||
// Client adds -XX:AOTCache when this file exists, causing classloading failures.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -420,12 +437,48 @@ function flattenJREDir(jreLatest) {
|
||||
for (const file of files) {
|
||||
const oldPath = path.join(nested, file.name);
|
||||
const newPath = path.join(jreLatest, file.name);
|
||||
try {
|
||||
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 });
|
||||
} catch (rmErr) {
|
||||
console.log('[JRE] Could not remove nested JRE dir (non-critical):', rmErr.message);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Singleplayer Server Crash: fastutil ClassNotFoundException
|
||||
|
||||
## Status: Open (user-specific, Feb 24 2026)
|
||||
## Status: Open — NO SOLUTION (Feb 24-27 2026)
|
||||
|
||||
## 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
|
||||
@@ -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.
|
||||
|
||||
## 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
|
||||
- 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`
|
||||
- Java wrapper correctly injects `--disable-sentry`
|
||||
- DualAuth Agent v1.1.12 installs successfully (STATIC mode)
|
||||
- Multiplayer connections work fine
|
||||
- Repair and reinstall did NOT fix the issue
|
||||
| Suspect | Tested | Result |
|
||||
|---------|--------|--------|
|
||||
| **DualAuth Agent** | Debug build with agent completely disabled (`debug-no-agent` tag) | **Same crash.** Agent is innocent. |
|
||||
| **`-Xshare:off` (CDS)** | Added to `JAVA_TOOL_OPTIONS` in launcher code (`debug-xshare-off` tag) | **Did not help.** CDS is not the cause. |
|
||||
| **`-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)
|
||||
- **UseCompactObjectHeaders**: Server also has `-XX:+IgnoreUnrecognizedVMOptions`, so unrecognized flags don't crash it
|
||||
- **DualAuth Agent**: Works for all other users; agent installs successfully before the crash
|
||||
- **Corrupted game files**: Repair/reinstall didn't help
|
||||
- **ARM64/Parallels**: User is on standard Windows, not ARM
|
||||
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.
|
||||
2. **Windows Insider build** — Asentrix is on NT 10.0.26200.0 (Windows 11 Dev/Insider). Bleeding-edge Windows may have JVM compatibility issues.
|
||||
3. **File locking** — Stalled `java.exe` processes holding `HytaleServer.jar` open (Asentrix had stalled processes killed at every launch).
|
||||
4. **Corrupted JRE on disk** — Despite being the same download, filesystem or AV may have corrupted specific JRE files on their system.
|
||||
|
||||
### 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
|
||||
2. **Corrupted/incompatible JRE** — bundled JRE might be broken on their system
|
||||
3. **File locking** — another process holding HytaleServer.jar open
|
||||
1. **Disable Windows Defender** temporarily — the only quick test left
|
||||
2. **Delete bundled JRE** and let launcher re-download — rules out local JRE corruption
|
||||
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)
|
||||
- Yes → something about our launch setup
|
||||
- No → their system/JRE issue
|
||||
### Feb 24: First report (JAYED)
|
||||
User reported singleplayer crash. Initial investigation found AOT cache errors + fastutil ClassNotFoundException. Stripping `-XX:+UseCompactObjectHeaders` did not help.
|
||||
|
||||
2. **Check antivirus** — add game directory to Windows Defender exclusions:
|
||||
- Settings → Windows Security → Virus & threat protection → Exclusions
|
||||
- Add their HytaleF2P install folder
|
||||
|
||||
3. **Verify fastutil is in the JAR**:
|
||||
```cmd
|
||||
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" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
### Feb 27: Second report (Asentrix), extensive debugging
|
||||
- Asentrix hit same crash, no AOT errors — ruled out AOT as root cause
|
||||
- Built `debug-xshare-off`: added `-Xshare:off` to `JAVA_TOOL_OPTIONS` — **did not help**
|
||||
- Built `debug-no-agent`: completely disabled DualAuth agent — **same crash**
|
||||
- **Conclusion**: Neither the agent nor CDS is the cause. The JVM itself cannot load classes from the fat JAR on these specific Windows systems.
|
||||
- Note: wrapper `injectArgs` append AFTER `-jar`, so they cannot inject JVM flags — only `JAVA_TOOL_OPTIONS` works for JVM flags
|
||||
|
||||
## Related
|
||||
|
||||
- Java wrapper config: `backend/core/config.js` (stripFlags / injectArgs)
|
||||
- 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
16
main.js
@@ -3,7 +3,7 @@ require('dotenv').config({ path: path.join(__dirname, '.env') });
|
||||
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
|
||||
const { autoUpdater } = require('electron-updater');
|
||||
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 { migrateUserDataToCentralized } = require('./backend/utils/userDataMigration');
|
||||
|
||||
@@ -23,8 +23,9 @@ const profileManager = require('./backend/managers/profileManager');
|
||||
|
||||
logger.interceptConsole();
|
||||
|
||||
// Single instance lock
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
// Single instance lock (skip if multi-instance mode is enabled)
|
||||
const multiInstanceEnabled = loadAllowMultiInstance();
|
||||
const gotTheLock = multiInstanceEnabled || app.requestSingleInstanceLock();
|
||||
|
||||
if (!gotTheLock) {
|
||||
console.log('Another instance is already running. Quitting...');
|
||||
@@ -740,6 +741,15 @@ ipcMain.handle('load-close-launcher', () => {
|
||||
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) => {
|
||||
saveLauncherHardwareAcceleration(enabled);
|
||||
return { success: true };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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",
|
||||
"homepage": "https://git.sanhost.net/sanasol/hytale-f2p",
|
||||
"main": "main.js",
|
||||
|
||||
@@ -20,6 +20,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
loadLanguage: () => ipcRenderer.invoke('load-language'),
|
||||
saveCloseLauncher: (enabled) => ipcRenderer.invoke('save-close-launcher', enabled),
|
||||
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'),
|
||||
saveConfig: (configUpdate) => ipcRenderer.invoke('save-config', configUpdate),
|
||||
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user