Compare commits

...

48 Commits

Author SHA1 Message Date
sanasol
dfe9ed2a89 ci: set 6-hour max timeout for macOS notarization
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 20:59:28 +01:00
sanasol
0aaf74a3db fix: add verbose logging to notarize script for debugging
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 15:14:59 +01:00
sanasol
be78f67439 chore: update package-lock.json
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 15:04:13 +01:00
sanasol
d0b9ae1da8 ci: separate macOS release from main release job
macOS notarization is slow (5-10 min). Now release is created
immediately when Windows/Linux/Arch complete, and macOS uploads
to the same release when notarization finishes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 15:01:57 +01:00
sanasol
e8105cb30e feat: add macOS code signing and notarization support
- Add entitlements.mac.plist for hardened runtime
- Add notarize.js post-sign hook for Apple notarization
- Update package.json with signing config and @electron/notarize dep
- Update GitHub Actions workflow with signing secrets

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 14:48:40 +01:00
AMIAY
79456e43a6 Merge pull request #213 from amiayweb/fix/update-system-improvements 2026-01-28 03:14:05 +01:00
sanasol
dd2dbc6f08 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>
2026-01-28 01:48:58 +01:00
Fazri Gading
c4acb32fcd Update support_request.yml 2026-01-28 05:16:00 +08:00
Fazri Gading
fbcbafb9b5 chore: remove Windows and Linux ARM64 information on the README.md 2026-01-28 04:26:42 +08:00
Terromur
86ed33358c Merge pull request #210 from amiayweb/fix/steamdeck-libzstd
fix: Steam Deck/Ubuntu crash - use system libzstd.so
2026-01-27 23:51:04 +05:00
sanasol
9ec97f9d33 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>
2026-01-27 19:40:42 +01:00
Fazri Gading
ee18455b4b chore: add downloads counter in README.md 2026-01-27 21:14:53 +08:00
Fazri Gading
a5c931b26d chore: add offline-mode warning to the README.md 2026-01-27 18:45:55 +08:00
Fazri Gading
661a0c9eed Update README.md 2026-01-27 17:38:33 +08:00
AMIAY
9025800820 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.
2026-01-27 04:29:01 +01:00
AMIAY
34ee099ae2 french translate 2026-01-27 03:26:43 +01:00
AMIAY
e56b12cd72 userdata migration [need review from other OS] 2026-01-27 01:44:58 +01:00
Fazri Gading
3edee4b4eb fix: PKGBUILD pkgname variable fix 2026-01-27 03:55:01 +08:00
Fazri Gading
e5fec7c326 Merge branch 'main' into develop 2026-01-27 03:42:40 +08:00
Fazri Gading
7d2672b684 add hardware spec input in bug_report.yml 2026-01-27 03:41:26 +08:00
Fazri Gading
01823729ec fix screenshot input in feature_request.yml 2026-01-27 03:40:22 +08:00
Fazri Gading
639a2ab1b5 chore: add changelog in README.md 2026-01-27 03:38:20 +08:00
Fazri Gading
6b76eb365e Update bug_report.yml
Add logs textfield to bug report
2026-01-27 03:21:47 +08:00
Fazri Gading
6fa933fece Update support_request.yml
Added hardware specification
2026-01-27 03:19:06 +08:00
walti0
e7023dcf95 Polish language support (#195) 2026-01-27 03:06:16 +08:00
Fazri Gading
f4d966ee65 chore: fix ubuntu/debian part in README.md 2026-01-27 02:16:01 +08:00
Fazri Gading
ca835a868b Merge pull request #188 from TalesAmaral/patch-1
Update README.md
2026-01-27 00:19:05 +08:00
Fazri Gading
3a1b6039d0 Merge branch 'develop' into patch-1 2026-01-27 00:18:33 +08:00
Fazri Gading
7828454631 Update PKGBUILD-git 2026-01-27 00:15:25 +08:00
Fazri Gading
cc1c6c334c Update PKGBUILD 2026-01-27 00:14:53 +08:00
TalesAmaral
081ac926e3 Update README.md
BUILD.md location was changed and now this link is poiting to nothing
2026-01-26 11:49:39 -03:00
Fazri Gading
75a450c9ec Update README.md
adds information for Arch build
2026-01-26 18:54:53 +08:00
Fazri Gading
e426690632 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.
2026-01-26 18:33:07 +08:00
Fazri Gading
78f76afe0a 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.
2026-01-26 18:20:37 +08:00
Fazri Gading
131de1dcd7 fix: removes pacman build as it replaced by tar.zst and adds build:arch shortcut for pkgbuild 2026-01-26 17:56:44 +08:00
Fazri Gading
b39877f561 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.
2026-01-26 17:46:40 +08:00
Fazri Gading
0b1b448cce Merge branch 'main' into develop 2026-01-26 13:56:33 +08:00
Fazri Gading
aed00cd067 add arch package .pkg.tar.zst for release 2026-01-26 13:52:18 +08:00
Fazri Gading
eff6fcd520 fix: isbrokenlink should be true to remove the symlink 2026-01-26 12:24:24 +08:00
Fazri Gading
94d4586b97 fix: add pathexists for paths.js to check symlink 2026-01-26 12:09:48 +08:00
Fazri Gading
20faf36b37 fix: remove broken symlink after detected 2026-01-26 12:01:46 +08:00
Fazri Gading
375b422c73 Update README.md Windows Prequisites for ARM64 builds 2026-01-26 11:33:00 +08:00
Fazri Gading
b668bdb45a prepare release 2.1.1 2026-01-26 09:48:26 +08:00
Fazri Gading
653d4429ed prepare release 2.1.1
minor fix EPERM permission error
2026-01-26 09:36:03 +08:00
Fazri Gading
17e15c17f0 prepare release for 2.1.1
minor fix for EPERM error permission
2026-01-26 09:34:16 +08:00
Fazri Gading
b99b22e8bf fix: missing pacman builds 2026-01-26 09:23:15 +08:00
Fazri Gading
9303c17e57 Merge branch 'develop' of https://github.com/amiayweb/Hytale-F2P into develop 2026-01-26 08:20:55 +08:00
Fazri Gading
615ee5cadc 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.
2026-01-26 08:19:13 +08:00
28 changed files with 2284 additions and 725 deletions

View File

@@ -55,6 +55,15 @@ body:
validations: validations:
required: true required: true
- type: textarea
id: hardwarespec
attributes:
label: Hardware Specification
description: Tell us your CPU, iGPU, dGPU, VRAM, and RAM information.
placeholder: "CPU: Intel i9-14900K 6.0 GHz | GPU: NVIDIA RTX 4090 | VRAM: 24 GB | RAM: 32 GB"
validations:
required: true
- type: dropdown - type: dropdown
id: os id: os
attributes: attributes:
@@ -71,8 +80,17 @@ body:
validations: validations:
required: true required: true
- type: textarea
id: logs
attributes:
label: Logs or Error Messages
description: If applicable, paste any error messages or logs here.
render: shell
validations:
required: true
- type: textarea - type: textarea
id: additional id: additional
attributes: attributes:
label: Additional context label: Additional context
description: Add any other context about the problem here. description: Add any other context about the problem here.

View File

@@ -39,7 +39,7 @@ body:
validations: validations:
required: true required: true
- type: screenshots - type: textarea
id: screenshots id: screenshots
attributes: attributes:
label: Screenshots (Optional) label: Screenshots (Optional)
@@ -49,4 +49,4 @@ body:
id: additional id: additional
attributes: attributes:
label: Additional context label: Additional context
description: Add any other context or screenshots about the feature request here. description: Add any other context or screenshots about the feature request here.

View File

@@ -1,8 +1,22 @@
name: Support Request name: Support Request
description: Request help or support description: Request help or support
title: "[SUPPORT] " title: "[SUPPORT] <ADD YOUR TITLE HERE>"
labels: ["support"] labels: ["support"]
body: body:
- type: dropdown
id: acknowledge
attributes:
label: Checklist
options:
- label: I have read the README.md before asking Support Request.
required: true
- label: I have read the TROUBLESHOOTING.md before asking Support Request.
required: true
- label: I have added title before submitting this Support Request.
required: true
- label: I acknowledge that my Support Request will not be responded as quick as in Discord Open-A-Ticket, I prefer this way.
required: true
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
@@ -24,16 +38,30 @@ body:
attributes: attributes:
label: Context label: Context
description: Provide any relevant context or background information. description: Provide any relevant context or background information.
placeholder: "I've tried..., but got..." placeholder: "I've tried these steps, but got..."
validations: validations:
required: true required: true
- type: input - type: textarea
id: hardwarespec
attributes:
label: Hardware Specification
description: Tell us your CPU, iGPU, dGPU, VRAM, and RAM information.
placeholder: "CPU: Intel i9-14900K 6.0 GHz | GPU: NVIDIA RTX 4090 | VRAM: 24 GB | RAM: 32 GB"
validations:
required: true
- type: dropdown
id: version id: version
attributes: attributes:
label: Version label: Version
description: What version are you using? description: What version are you using?
placeholder: "e.g. v2.0.11 stable/pre-release" options:
- v2.1.2
- v2.1.1
- v2.1.0
- v2.0.11
- v2.0.2
validations: validations:
required: true required: true
@@ -43,13 +71,12 @@ body:
label: Platform label: Platform
description: What platform are you using? description: What platform are you using?
options: options:
- Windows 10 - Windows 11 x64
- Windows 11 - Windows 10 x64
- macOS (Apple Silicon) - macOS ARM64 (Apple Silicon)
- macOS (Intel) - Linux x64 Ubuntu/Debian-based
- Linux Ubuntu/Debian-based - Linux x64 Fedora/RHEL-based
- Linux Fedora/RHEL-based - Linux x64 Arch-based
- Linux Arch-based
validations: validations:
required: true required: true
@@ -66,4 +93,4 @@ body:
id: additional id: additional
attributes: attributes:
label: Additional Information label: Additional Information
description: Any other information that might help us assist you. description: Any other information that might help us assist you.

View File

@@ -18,7 +18,7 @@ jobs:
node-version: '22' node-version: '22'
cache: 'npm' cache: 'npm'
- run: npm ci - run: npm ci
- name: Build Windows Packages - name: Build Windows Packages
run: npx electron-builder --win --publish never run: npx electron-builder --win --publish never
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
@@ -31,6 +31,7 @@ jobs:
build-macos: build-macos:
runs-on: macos-latest runs-on: macos-latest
timeout-minutes: 360 # Max allowed (6 hours) for notarization
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
@@ -40,6 +41,14 @@ jobs:
- run: npm ci - run: npm ci
- name: Build macOS Packages - name: Build macOS Packages
env:
# Code signing
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
# Notarization
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: npx electron-builder --mac --publish never run: npx electron-builder --mac --publish never
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
@@ -57,7 +66,7 @@ jobs:
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libarchive-tools sudo apt-get install -y libarchive-tools
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: '22' node-version: '22'
@@ -86,7 +95,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Install base packages - name: Install base packages
run: | run: |
pacman -Syu --noconfirm pacman -Syu --noconfirm
@@ -123,26 +132,39 @@ jobs:
*.src.tar.zst *.src.tar.zst
.SRCINFO .SRCINFO
# Create release with Windows, Linux, Arch (fast builds)
release: release:
needs: [build-windows, build-macos, build-linux, build-arch] needs: [build-windows, build-linux, build-arch]
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: | if: |
startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/tags/v') ||
github.ref == 'refs/heads/main' || github.ref == 'refs/heads/main' ||
github.event_name == 'workflow_dispatch' github.event_name == 'workflow_dispatch'
permissions: permissions:
contents: write contents: write
steps: steps:
# FIX: './package.json' Module Not Found in `Get version` step
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Download all artifacts - name: Download Windows artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
path: artifacts name: windows-builds
path: artifacts/windows-builds
- name: Download Linux artifacts
uses: actions/download-artifact@v4
with:
name: linux-builds
path: artifacts/linux-builds
- name: Download Arch artifacts
uses: actions/download-artifact@v4
with:
name: arch-package
path: artifacts/arch-package
- name: Display structure of downloaded files - name: Display structure of downloaded files
run: ls -R artifacts run: ls -R artifacts
@@ -155,18 +177,43 @@ jobs:
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
tag_name: ${{ github.ref_name }} tag_name: ${{ github.ref_name }}
# If it's a tag, use the tag.
# tag_name: ${{ github.ref_type == 'tag' && github.ref_name || format('v{0}.r{1}', steps.pkg_version.outputs.VERSION, github.run_number) }}
# If it's the 'release' branch, use 'v2.0.2-beta.r42'
# name: ${{ github.ref_type == 'tag' && github.ref_name || format('v{0}-beta.r{1}', steps.pkg_version.outputs.VERSION, github.run_number) }}
files: | files: |
artifacts/arch-package/*.pkg.tar.zst artifacts/arch-package/*.pkg.tar.zst
artifacts/arch-package/*.src.tar.zst artifacts/arch-package/*.src.tar.zst
artifacts/arch-package/.SRCINFO artifacts/arch-package/.SRCINFO
artifacts/linux-builds/**/* artifacts/linux-builds/*
artifacts/windows-builds/**/* artifacts/windows-builds/*
artifacts/macos-builds/**/*
generate_release_notes: true generate_release_notes: true
draft: true draft: true
prerelease: false prerelease: false
# Upload macOS builds separately (slow due to notarization)
release-macos:
needs: [build-macos, release]
runs-on: ubuntu-latest
if: |
startsWith(github.ref, 'refs/tags/v') ||
github.ref == 'refs/heads/main' ||
github.event_name == 'workflow_dispatch'
permissions:
contents: write
steps:
- name: Download macOS artifacts
uses: actions/download-artifact@v4
with:
name: macos-builds
path: artifacts/macos-builds
- name: Display macOS files
run: ls -R artifacts
- name: Upload macOS to Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
files: |
artifacts/macos-builds/*
draft: true
prerelease: false

View File

@@ -882,7 +882,7 @@
<script src="js/i18n.js"></script> <script src="js/i18n.js"></script>
<script type="module" src="js/settings.js"></script> <script type="module" src="js/settings.js"></script>
<script type="module" src="js/update.js"></script> <script type="module" src="js/update.js"></script>
<script src="js/updater.js"></script> <!-- updater.js disabled - using update.js instead which has skip button and macOS handling -->
</body> </body>

View File

@@ -4,9 +4,13 @@ const i18n = (() => {
let translations = {}; let translations = {};
const availableLanguages = [ const availableLanguages = [
{ code: 'en', name: 'English' }, { code: 'en', name: 'English' },
{ code: 'fr', name: 'Français' },
{ code: 'de', name: 'Deutsch' },
{ code: 'sv', name: 'Svenska' },
{ code: 'es-ES', name: 'Español (España)' }, { code: 'es-ES', name: 'Español (España)' },
{ code: 'pt-BR', name: 'Portuguese (Brazil)' }, { code: 'pt-BR', name: 'Portuguese (Brazil)' },
{ code: 'tr-TR', name: 'Turkish (Turkey)' } { code: 'tr-TR', name: 'Turkish (Turkey)' },
{ code: 'pl-PL', name: 'Polish (Poland)' }
]; ];
// Load single language file // Load single language file

View File

@@ -72,8 +72,11 @@ export async function installGame() {
setTimeout(() => { setTimeout(() => {
window.LauncherUI.hideProgress(); window.LauncherUI.hideProgress();
window.LauncherUI.showLauncherOrInstall(true); window.LauncherUI.showLauncherOrInstall(true);
// Sync player name to both launcher and settings inputs
const playerNameInput = document.getElementById('playerName'); const playerNameInput = document.getElementById('playerName');
if (playerNameInput) playerNameInput.value = playerName; if (playerNameInput) playerNameInput.value = playerName;
const settingsPlayerName = document.getElementById('settingsPlayerName');
if (settingsPlayerName) settingsPlayerName.value = playerName;
resetInstallButton(); resetInstallButton();
}, 2000); }, 2000);
} }
@@ -125,8 +128,11 @@ function simulateInstallation(playerName) {
setTimeout(() => { setTimeout(() => {
window.LauncherUI.hideProgress(); window.LauncherUI.hideProgress();
window.LauncherUI.showLauncherOrInstall(true); window.LauncherUI.showLauncherOrInstall(true);
// Sync player name to both launcher and settings inputs
const playerNameInput = document.getElementById('playerName'); const playerNameInput = document.getElementById('playerName');
if (playerNameInput) playerNameInput.value = playerName; if (playerNameInput) playerNameInput.value = playerName;
const settingsPlayerName = document.getElementById('settingsPlayerName');
if (settingsPlayerName) settingsPlayerName.value = playerName;
resetInstallButton(); resetInstallButton();
}, 2000); }, 2000);
} }
@@ -246,9 +252,3 @@ document.addEventListener('DOMContentLoaded', async () => {
setupInstallation(); setupInstallation();
await checkGameStatusAndShowInterface(); await checkGameStatusAndShowInterface();
}); });
window.browseInstallPath = browseInstallPath;
document.addEventListener('DOMContentLoaded', async () => {
setupInstallation();
await checkGameStatusAndShowInterface();
});

View File

@@ -6,12 +6,12 @@ class ClientUpdateManager {
} }
init() { init() {
window.electronAPI.onUpdatePopup((updateInfo) => { console.log('🔧 ClientUpdateManager initializing...');
this.showUpdatePopup(updateInfo);
});
// Listen for electron-updater events // Listen for electron-updater events from main.js
// This is the primary update trigger - main.js checks for updates on startup
window.electronAPI.onUpdateAvailable((updateInfo) => { window.electronAPI.onUpdateAvailable((updateInfo) => {
console.log('📥 update-available event received:', updateInfo);
this.showUpdatePopup(updateInfo); this.showUpdatePopup(updateInfo);
}); });
@@ -20,18 +20,30 @@ class ClientUpdateManager {
}); });
window.electronAPI.onUpdateDownloaded((updateInfo) => { window.electronAPI.onUpdateDownloaded((updateInfo) => {
console.log('📦 update-downloaded event received:', updateInfo);
this.showUpdateDownloaded(updateInfo); this.showUpdateDownloaded(updateInfo);
}); });
window.electronAPI.onUpdateError((errorInfo) => { window.electronAPI.onUpdateError((errorInfo) => {
console.log('❌ update-error event received:', errorInfo);
this.handleUpdateError(errorInfo); this.handleUpdateError(errorInfo);
}); });
this.checkForUpdatesOnDemand(); console.log('✅ ClientUpdateManager initialized');
// Note: Don't call checkForUpdatesOnDemand() here - main.js already checks
// for updates after 3 seconds and sends 'update-available' event.
// Calling it here would cause duplicate popups.
} }
showUpdatePopup(updateInfo) { showUpdatePopup(updateInfo) {
if (this.updatePopupVisible) return; console.log('🔔 showUpdatePopup called, updatePopupVisible:', this.updatePopupVisible);
// Check if popup already exists in DOM (extra safety)
if (this.updatePopupVisible || document.getElementById('update-popup-overlay')) {
console.log('⚠️ Update popup already visible, skipping');
return;
}
this.updatePopupVisible = true; this.updatePopupVisible = true;
@@ -92,7 +104,10 @@ class ClientUpdateManager {
</div> </div>
<div class="update-popup-footer"> <div class="update-popup-footer">
This popup cannot be closed until you update the launcher <span id="update-footer-text">Downloading update...</span>
<button id="update-skip-btn" class="update-skip-btn" style="display: none; margin-top: 0.5rem; background: transparent; border: 1px solid rgba(255,255,255,0.2); color: #9ca3af; padding: 0.5rem 1rem; border-radius: 0.25rem; cursor: pointer; font-size: 0.75rem;">
Skip for now (not recommended)
</button>
</div> </div>
</div> </div>
</div> </div>
@@ -113,16 +128,43 @@ class ClientUpdateManager {
installBtn.addEventListener('click', async (e) => { installBtn.addEventListener('click', async (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
installBtn.disabled = true; installBtn.disabled = true;
installBtn.innerHTML = '<i class="fas fa-spinner fa-spin" style="margin-right: 0.5rem;"></i>Installing...'; installBtn.innerHTML = '<i class="fas fa-spinner fa-spin" style="margin-right: 0.5rem;"></i>Installing...';
try { try {
await window.electronAPI.quitAndInstallUpdate(); await window.electronAPI.quitAndInstallUpdate();
// If we're still here after 5 seconds, the install probably failed
setTimeout(() => {
console.log('⚠️ Install may have failed - showing skip option');
installBtn.disabled = false;
installBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Try Again';
// Show skip button
const skipBtn = document.getElementById('update-skip-btn');
const footerText = document.getElementById('update-footer-text');
if (skipBtn) {
skipBtn.style.display = 'inline-block';
if (footerText) {
footerText.textContent = 'Install not working? Skip for now:';
}
}
}, 5000);
} catch (error) { } catch (error) {
console.error('❌ Error installing update:', error); console.error('❌ Error installing update:', error);
installBtn.disabled = false; installBtn.disabled = false;
installBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Install & Restart'; installBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Install & Restart';
// Show skip button on error
const skipBtn = document.getElementById('update-skip-btn');
const footerText = document.getElementById('update-footer-text');
if (skipBtn) {
skipBtn.style.display = 'inline-block';
if (footerText) {
footerText.textContent = 'Install failed. Skip for now:';
}
}
} }
}); });
} }
@@ -138,10 +180,15 @@ class ClientUpdateManager {
try { try {
await window.electronAPI.openDownloadPage(); await window.electronAPI.openDownloadPage();
console.log('✅ Download page opened, launcher will close...'); console.log('✅ Download page opened');
downloadBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Launcher closing...'; downloadBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Opened in browser';
// Close the popup after opening download page
setTimeout(() => {
this.closeUpdatePopup();
}, 1500);
} catch (error) { } catch (error) {
console.error('❌ Error opening download page:', error); console.error('❌ Error opening download page:', error);
downloadBtn.disabled = false; downloadBtn.disabled = false;
@@ -161,9 +208,39 @@ class ClientUpdateManager {
}); });
} }
// Show skip button after 30 seconds as fallback (in case update is stuck)
setTimeout(() => {
const skipBtn = document.getElementById('update-skip-btn');
const footerText = document.getElementById('update-footer-text');
if (skipBtn) {
skipBtn.style.display = 'inline-block';
if (footerText) {
footerText.textContent = 'Update taking too long?';
}
}
}, 30000);
const skipBtn = document.getElementById('update-skip-btn');
if (skipBtn) {
skipBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
this.closeUpdatePopup();
});
}
console.log('🔔 Update popup displayed with new style'); console.log('🔔 Update popup displayed with new style');
} }
closeUpdatePopup() {
const overlay = document.getElementById('update-popup-overlay');
if (overlay) {
overlay.remove();
}
this.updatePopupVisible = false;
this.unblockInterface();
}
updateDownloadProgress(progress) { updateDownloadProgress(progress) {
const progressBar = document.getElementById('update-progress-bar'); const progressBar = document.getElementById('update-progress-bar');
const progressPercent = document.getElementById('update-progress-percent'); const progressPercent = document.getElementById('update-progress-percent');
@@ -197,35 +274,96 @@ class ClientUpdateManager {
const statusText = document.getElementById('update-status-text'); const statusText = document.getElementById('update-status-text');
const progressContainer = document.getElementById('update-progress-container'); const progressContainer = document.getElementById('update-progress-container');
const buttonsContainer = document.getElementById('update-buttons-container'); const buttonsContainer = document.getElementById('update-buttons-container');
const installBtn = document.getElementById('update-install-btn');
const downloadBtn = document.getElementById('update-download-btn');
const skipBtn = document.getElementById('update-skip-btn');
const footerText = document.getElementById('update-footer-text');
const popupContainer = document.querySelector('.update-popup-container');
if (statusText) { // Remove breathing/pulse animation when download is complete
statusText.textContent = 'Update downloaded! Ready to install.'; if (popupContainer) {
popupContainer.classList.remove('update-popup-pulse');
} }
if (progressContainer) { if (progressContainer) {
progressContainer.style.display = 'none'; progressContainer.style.display = 'none';
} }
// Use platform info from main process if available, fallback to browser detection
const autoInstallSupported = updateInfo.autoInstallSupported !== undefined
? updateInfo.autoInstallSupported
: navigator.platform.toUpperCase().indexOf('MAC') < 0;
if (!autoInstallSupported) {
// macOS: Show manual download as primary since auto-update doesn't work
if (statusText) {
statusText.textContent = 'Update downloaded but auto-install may not work on macOS.';
}
if (installBtn) {
// Still show install button but as secondary option
installBtn.classList.add('update-download-btn-secondary');
installBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Try Install & Restart';
}
if (downloadBtn) {
// Make manual download primary
downloadBtn.classList.remove('update-download-btn-secondary');
downloadBtn.innerHTML = '<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>Download Manually (Recommended)';
}
if (footerText) {
footerText.textContent = 'Auto-install often fails on macOS:';
}
} else {
// Windows/Linux: Auto-install should work
if (statusText) {
statusText.textContent = 'Update downloaded! Ready to install.';
}
if (footerText) {
footerText.textContent = 'Click to install the update:';
}
}
if (buttonsContainer) { if (buttonsContainer) {
buttonsContainer.style.display = 'block'; buttonsContainer.style.display = 'block';
} }
console.log('✅ Update downloaded, ready to install'); // Always show skip button in downloaded state
if (skipBtn) {
skipBtn.style.display = 'inline-block';
console.log('✅ Skip button made visible');
} else {
console.error('❌ Skip button not found in DOM!');
}
console.log('✅ Update downloaded, ready to install. autoInstallSupported:', autoInstallSupported);
} }
handleUpdateError(errorInfo) { handleUpdateError(errorInfo) {
console.error('Update error:', errorInfo); console.error('Update error:', errorInfo);
// Show skip button immediately on any error
const skipBtn = document.getElementById('update-skip-btn');
const footerText = document.getElementById('update-footer-text');
if (skipBtn) {
skipBtn.style.display = 'inline-block';
if (footerText) {
footerText.textContent = 'Update failed. You can skip for now.';
}
}
// If manual download is required, update the UI (this will handle status text) // If manual download is required, update the UI (this will handle status text)
if (errorInfo.requiresManualDownload) { if (errorInfo.requiresManualDownload) {
this.showManualDownloadRequired(errorInfo); this.showManualDownloadRequired(errorInfo);
return; // Don't do anything else, showManualDownloadRequired handles everything return; // Don't do anything else, showManualDownloadRequired handles everything
} }
// For non-critical errors, just show error message without changing status // For non-critical errors, just show error message without changing status
const errorMessage = document.getElementById('update-error-message'); const errorMessage = document.getElementById('update-error-message');
const errorText = document.getElementById('update-error-text'); const errorText = document.getElementById('update-error-text');
if (errorMessage && errorText) { if (errorMessage && errorText) {
let message = errorInfo.message || 'An error occurred during the update process.'; let message = errorInfo.message || 'An error occurred during the update process.';
if (errorInfo.isMacSigningError) { if (errorInfo.isMacSigningError) {
@@ -289,6 +427,16 @@ class ClientUpdateManager {
buttonsContainer.style.display = 'block'; buttonsContainer.style.display = 'block';
} }
// Show skip button for manual download errors
const skipBtn = document.getElementById('update-skip-btn');
const footerText = document.getElementById('update-footer-text');
if (skipBtn) {
skipBtn.style.display = 'inline-block';
if (footerText) {
footerText.textContent = 'Or continue without updating:';
}
}
console.log('⚠️ Manual download required due to update error'); console.log('⚠️ Manual download required due to update error');
} }
@@ -300,13 +448,35 @@ class ClientUpdateManager {
document.body.classList.add('no-select'); document.body.classList.add('no-select');
document.addEventListener('keydown', this.blockKeyEvents.bind(this), true); // Store bound functions so we can remove them later
this._boundBlockKeyEvents = this.blockKeyEvents.bind(this);
document.addEventListener('contextmenu', this.blockContextMenu.bind(this), true); this._boundBlockContextMenu = this.blockContextMenu.bind(this);
document.addEventListener('keydown', this._boundBlockKeyEvents, true);
document.addEventListener('contextmenu', this._boundBlockContextMenu, true);
console.log('🚫 Interface blocked for update'); console.log('🚫 Interface blocked for update');
} }
unblockInterface() {
const mainContent = document.querySelector('.flex.w-full.h-screen');
if (mainContent) {
mainContent.classList.remove('interface-blocked');
}
document.body.classList.remove('no-select');
// Remove event listeners
if (this._boundBlockKeyEvents) {
document.removeEventListener('keydown', this._boundBlockKeyEvents, true);
}
if (this._boundBlockContextMenu) {
document.removeEventListener('contextmenu', this._boundBlockContextMenu, true);
}
console.log('✅ Interface unblocked');
}
blockKeyEvents(event) { blockKeyEvents(event) {
if (event.target.closest('#update-popup-overlay')) { if (event.target.closest('#update-popup-overlay')) {
if ((event.key === 'Enter' || event.key === ' ') && if ((event.key === 'Enter' || event.key === ' ') &&

283
GUI/locales/de.json Normal file
View File

@@ -0,0 +1,283 @@
{
"nav": {
"play": "Spielen",
"mods": "Mods",
"news": "Neuigkeiten",
"chat": "Spieler-Chat",
"settings": "Einstellungen"
},
"header": {
"playersLabel": "Spieler:",
"manageProfiles": "Profile verwalten",
"defaultProfile": "Standard"
},
"install": {
"title": "KOSTENLOSER LAUNCHER",
"playerName": "Spielername",
"playerNamePlaceholder": "Namen eingeben",
"gameBranch": "Spielversion",
"releaseVersion": "Release (Stabil)",
"preReleaseVersion": "Pre-Release (Experimentell)",
"customInstallation": "Benutzerdefinierte Installation",
"installationFolder": "Installationsordner",
"pathPlaceholder": "Standardspeicherort",
"browse": "Durchsuchen",
"installButton": "HYTALE INSTALLIEREN",
"installing": "INSTALLIERE..."
},
"play": {
"ready": "BEREIT ZUM SPIELEN",
"subtitle": "Starte Hytale und beginne das Abenteuer",
"playButton": "HYTALE SPIELEN",
"latestNews": "NEUESTE NACHRICHTEN",
"viewAll": "ALLE ANZEIGEN",
"checking": "ÜBERPRÜFE...",
"play": "SPIELEN"
},
"mods": {
"searchPlaceholder": "Mods suchen...",
"myMods": "MEINE MODS",
"previous": "ZURÜCK",
"next": "WEITER",
"page": "Seite",
"of": "von",
"modalTitle": "MEINE MODS",
"noModsFound": "Keine Mods gefunden",
"noModsFoundDesc": "Versuche deine Suche anzupassen",
"noModsInstalled": "Keine Mods installiert",
"noModsInstalledDesc": "Füge Mods von CurseForge hinzu oder importiere lokale Dateien",
"view": "ANZEIGEN",
"install": "INSTALLIEREN",
"installed": "INSTALLIERT",
"enable": "AKTIVIEREN",
"disable": "DEAKTIVIEREN",
"active": "AKTIV",
"disabled": "DEAKTIVIERT",
"delete": "Mod löschen",
"noDescription": "Keine Beschreibung verfügbar",
"confirmDelete": "Möchtest du \"{name}\" wirklich löschen?",
"confirmDeleteDesc": "Diese Aktion kann nicht rückgängig gemacht werden.",
"confirmDeletion": "Löschung bestätigen",
"apiKeyRequired": "API-Schlüssel erforderlich",
"apiKeyRequiredDesc": "CurseForge API-Schlüssel wird benötigt, um Mods zu durchsuchen"
},
"news": {
"title": "ALLE NACHRICHTEN",
"readMore": "Mehr lesen"
},
"chat": {
"title": "SPIELER-CHAT",
"pickColor": "Farbe",
"inputPlaceholder": "Nachricht eingeben...",
"send": "Senden",
"online": "online",
"charCounter": "{current}/{max}",
"secureChat": "Sicherer Chat - Links werden zensiert",
"joinChat": "Chat beitreten",
"chooseUsername": "Wähle einen Benutzernamen, um dem Spieler-Chat beizutreten",
"username": "Benutzername",
"usernamePlaceholder": "Benutzernamen eingeben...",
"usernameHint": "3-20 Zeichen, nur Buchstaben, Zahlen, - und _",
"joinButton": "Chat beitreten",
"colorModal": {
"title": "Benutzernamenfarbe anpassen",
"chooseSolid": "Wähle eine einfarbige Farbe:",
"customColor": "Benutzerdefinierte Farbe:",
"preview": "Vorschau:",
"previewUsername": "Benutzername",
"apply": "Farbe anwenden"
}
},
"settings": {
"title": "EINSTELLUNGEN",
"java": "Java Runtime",
"useCustomJava": "Benutzerdefinierten Java-Pfad verwenden",
"javaDescription": "Ersetze die mitgelieferte Java-Installation durch deine eigene",
"javaPath": "Java-Ausführungsdatei-Pfad",
"javaPathPlaceholder": "Java-Pfad auswählen...",
"javaBrowse": "Durchsuchen",
"javaHint": "Wähle den Java-Installationsordner (unterstützt Windows, Mac, Linux)",
"discord": "Discord-Integration",
"enableRPC": "Discord Rich Presence aktivieren",
"discordDescription": "Zeige deine Launcher-Aktivität auf Discord",
"game": "Spieloptionen",
"playerName": "Spielername",
"playerNamePlaceholder": "Spielernamen eingeben",
"playerNameHint": "Dieser Name wird im Spiel verwendet (1-16 Zeichen)",
"openGameLocation": "Spielordner öffnen",
"openGameLocationDesc": "Öffne den Spielinstallationsordner",
"account": "Spieler-UUID-Verwaltung",
"currentUUID": "Aktuelle UUID",
"uuidPlaceholder": "UUID wird geladen...",
"copyUUID": "UUID kopieren",
"regenerateUUID": "UUID neu generieren",
"uuidHint": "Deine eindeutige Spielerkennung für diesen Benutzernamen",
"manageUUIDs": "Alle UUIDs verwalten",
"manageUUIDsDesc": "Alle Spieler-UUIDs anzeigen und verwalten",
"language": "Sprache",
"selectLanguage": "Sprache auswählen",
"repairGame": "Spiel reparieren",
"reinstallGame": "Spieldateien neu installieren (behält Daten)",
"gpuPreference": "GPU-Präferenz",
"gpuHint": "Wähle deine bevorzugte GPU (Linux: betrifft DRI_PRIME)",
"gpuAuto": "Auto",
"gpuIntegrated": "Integriert",
"gpuDedicated": "Dediziert",
"logs": "SYSTEMPROTOKOLLE",
"logsCopy": "Kopieren",
"logsRefresh": "Aktualisieren",
"logsFolder": "Ordner öffnen",
"logsLoading": "Protokolle werden geladen...",
"closeLauncher": "Launcher-Verhalten",
"closeOnStart": "Launcher beim Spielstart schließen",
"closeOnStartDescription": "Schließe den Launcher automatisch, nachdem Hytale gestartet wurde",
"hwAccel": "Hardware-Beschleunigung",
"hwAccelDescription": "Hardware-Beschleunigung für den Launcher aktivieren",
"gameBranch": "Spiel-Branch",
"branchRelease": "Release",
"branchPreRelease": "Pre-Release",
"branchHint": "Wechsel zwischen stabiler Release- und experimenteller Pre-Release-Version",
"branchWarning": "Das Ändern des Branches lädt eine andere Spielversion herunter und installiert sie",
"branchSwitching": "Wechsle zu {branch}...",
"branchSwitched": "Erfolgreich zu {branch} gewechselt!",
"installRequired": "Installation erforderlich",
"branchInstallConfirm": "Das Spiel wird für den {branch}-Branch installiert. Fortfahren?"
},
"uuid": {
"modalTitle": "UUID-Verwaltung",
"currentUserUUID": "Aktuelle Benutzer-UUID",
"allPlayerUUIDs": "Alle Spieler-UUIDs",
"generateNew": "Neue UUID generieren",
"loadingUUIDs": "UUIDs werden geladen...",
"setCustomUUID": "Benutzerdefinierte UUID festlegen",
"customPlaceholder": "Benutzerdefinierte UUID eingeben (Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
"setUUID": "UUID festlegen",
"warning": "Warnung: Das Festlegen einer benutzerdefinierten UUID ändert deine aktuelle Spieleridentität",
"copyTooltip": "UUID kopieren",
"regenerateTooltip": "Neue UUID generieren"
},
"profiles": {
"modalTitle": "Profile verwalten",
"newProfilePlaceholder": "Neuer Profilname",
"createProfile": "Profil erstellen"
},
"discord": {
"notificationText": "Tritt unserer Discord-Community bei!",
"joinButton": "Discord beitreten"
},
"common": {
"confirm": "Bestätigen",
"cancel": "Abbrechen",
"save": "Speichern",
"close": "Schließen",
"delete": "Löschen",
"edit": "Bearbeiten",
"loading": "Lädt...",
"apply": "Anwenden",
"install": "Installieren"
},
"notifications": {
"gameDataNotFound": "Fehler: Spieldaten nicht gefunden",
"gameUpdatedSuccess": "Spiel erfolgreich aktualisiert! 🎉",
"updateFailed": "Update fehlgeschlagen: {error}",
"updateError": "Update-Fehler: {error}",
"discordEnabled": "Discord Rich Presence aktiviert",
"discordDisabled": "Discord Rich Presence deaktiviert",
"discordSaveFailed": "Discord-Einstellung konnte nicht gespeichert werden",
"playerNameRequired": "Bitte gib einen gültigen Spielernamen ein",
"playerNameSaved": "Spielername erfolgreich gespeichert",
"playerNameSaveFailed": "Spielername konnte nicht gespeichert werden",
"uuidCopied": "UUID in die Zwischenablage kopiert!",
"uuidCopyFailed": "UUID konnte nicht kopiert werden",
"uuidRegenNotAvailable": "UUID-Neugenerierung nicht verfügbar",
"uuidRegenFailed": "UUID konnte nicht neu generiert werden",
"uuidGenerated": "Neue UUID erfolgreich generiert!",
"uuidGeneratedShort": "Neue UUID generiert!",
"uuidGenerateFailed": "Neue UUID konnte nicht generiert werden",
"uuidRequired": "Bitte gib eine UUID ein",
"uuidInvalidFormat": "Ungültiges UUID-Format",
"uuidSetFailed": "Benutzerdefinierte UUID konnte nicht festgelegt werden",
"uuidSetSuccess": "Benutzerdefinierte UUID erfolgreich festgelegt!",
"uuidDeleteFailed": "UUID konnte nicht gelöscht werden",
"uuidDeleteSuccess": "UUID erfolgreich gelöscht!",
"modsDownloading": "{name} wird heruntergeladen...",
"modsTogglingMod": "Mod wird umgeschaltet...",
"modsDeletingMod": "Mod wird gelöscht...",
"modsLoadingMods": "Mods von CurseForge werden geladen...",
"modsInstalledSuccess": "{name} erfolgreich installiert! 🎉",
"modsDeletedSuccess": "{name} erfolgreich gelöscht",
"modsDownloadFailed": "Mod konnte nicht heruntergeladen werden: {error}",
"modsToggleFailed": "Mod konnte nicht umgeschaltet werden: {error}",
"modsDeleteFailed": "Mod konnte nicht gelöscht werden: {error}",
"modsModNotFound": "Mod-Informationen nicht gefunden",
"hwAccelSaved": "Hardware-Beschleunigungseinstellung gespeichert",
"hwAccelSaveFailed": "Hardware-Beschleunigungseinstellung konnte nicht gespeichert werden",
"javaPathCopied": "Java-Pfad in die Zwischenablage kopiert!",
"javaPathCopyFailed": "Java-Pfad konnte nicht kopiert werden",
"javaPathSaved": "Java-Pfad erfolgreich gespeichert!",
"javaPathSaveFailed": "Java-Pfad konnte nicht gespeichert werden",
"javaPathInvalid": "Ungültiger Java-Pfad",
"javaPathReset": "Java-Pfad auf Standardwerte zurückgesetzt",
"gameLocationError": "Spielordner konnte nicht geöffnet werden",
"launcherRestartRequired": "Launcher-Neustart erforderlich, um Änderungen anzuwenden",
"gameRepairConfirm": "Möchtest du das Spiel wirklich reparieren? Dies wird alle Spieldateien neu installieren.",
"gameRepairInProgress": "Spiel wird repariert...",
"gameRepairSuccess": "Spiel erfolgreich repariert!",
"gameRepairFailed": "Spielreparatur fehlgeschlagen: {error}",
"invalidUsername": "Ungültiger Benutzername",
"usernameInUse": "Benutzername bereits vergeben",
"chatJoinSuccess": "Du bist dem Chat beigetreten!",
"chatJoinFailed": "Chat-Beitritt fehlgeschlagen",
"messageTooLong": "Nachricht zu lang",
"messageSent": "Nachricht gesendet",
"messageSendFailed": "Nachricht konnte nicht gesendet werden",
"colorUpdated": "Farbe aktualisiert!",
"colorUpdateFailed": "Farbe konnte nicht aktualisiert werden",
"profileCreated": "Profil erfolgreich erstellt!",
"profileCreateFailed": "Profil konnte nicht erstellt werden",
"profileDeleted": "Profil gelöscht",
"profileDeleteFailed": "Profil konnte nicht gelöscht werden",
"profileSwitched": "Profil gewechselt zu: {name}",
"profileSwitchFailed": "Profilwechsel fehlgeschlagen",
"invalidProfileName": "Ungültiger Profilname",
"profileNameExists": "Ein Profil mit diesem Namen existiert bereits",
"noInternet": "Keine Internetverbindung",
"checkInternetConnection": "Überprüfe deine Internetverbindung",
"serverError": "Serverfehler. Bitte versuche es später erneut.",
"unknownError": "Ein unbekannter Fehler ist aufgetreten"
},
"confirm": {
"defaultTitle": "Aktion bestätigen",
"regenerateUuidTitle": "Neue UUID generieren",
"regenerateUuidMessage": "Möchtest du wirklich eine neue UUID generieren? Dies ändert deine Spieleridentität.",
"regenerateUuidButton": "Generieren",
"setCustomUuidTitle": "Benutzerdefinierte UUID festlegen",
"setCustomUuidMessage": "Möchtest du wirklich diese benutzerdefinierte UUID festlegen? Dies ändert deine Spieleridentität.",
"setCustomUuidButton": "UUID festlegen",
"deleteUuidTitle": "UUID löschen",
"deleteUuidMessage": "Möchtest du wirklich die UUID für \"{username}\" löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
"deleteUuidButton": "Löschen",
"uninstallGameTitle": "Spiel deinstallieren",
"uninstallGameMessage": "Möchtest du Hytale wirklich deinstallieren? Alle Spieldateien werden gelöscht.",
"uninstallGameButton": "Deinstallieren"
},
"progress": {
"initializing": "Initialisiere...",
"downloading": "Lädt herunter...",
"installing": "Installiere...",
"extracting": "Entpacke...",
"verifying": "Überprüfe...",
"switchingProfile": "Profil wird gewechselt...",
"profileSwitched": "Profil gewechselt!",
"startingGame": "Spiel wird gestartet...",
"launching": "STARTET...",
"uninstallingGame": "Spiel wird deinstalliert...",
"gameUninstalled": "Spiel erfolgreich deinstalliert!",
"uninstallFailed": "Deinstallation fehlgeschlagen: {error}",
"startingUpdate": "Obligatorisches Spiel-Update wird gestartet...",
"installationComplete": "Installation erfolgreich abgeschlossen!",
"installationFailed": "Installation fehlgeschlagen: {error}",
"installingGameFiles": "Spieldateien werden installiert...",
"installComplete": "Installation abgeschlossen!"
}
}

235
GUI/locales/fr.json Normal file
View File

@@ -0,0 +1,235 @@
{
"nav": {
"play": "Jouer",
"mods": "Mods",
"news": "Actualités",
"chat": "Chat Joueurs",
"settings": "Paramètres"
},
"header": {
"playersLabel": "Joueurs:",
"manageProfiles": "Gérer les Profils",
"defaultProfile": "Par défaut"
},
"install": {
"title": "LAUNCHER GRATUIT",
"playerName": "Nom du Joueur",
"playerNamePlaceholder": "Entrez votre nom",
"gameBranch": "Version du Jeu",
"releaseVersion": "Release (Stable)",
"preReleaseVersion": "Pré-Release (Expérimental)",
"customInstallation": "Installation Personnalisée",
"installationFolder": "Dossier d'Installation",
"pathPlaceholder": "Emplacement par défaut",
"browse": "Parcourir",
"installButton": "INSTALLER HYTALE",
"installing": "INSTALLATION..."
},
"play": {
"ready": "PRÊT À JOUER",
"subtitle": "Lancez Hytale et entrez dans l'aventure",
"playButton": "JOUER À HYTALE",
"latestNews": "DERNIÈRES ACTUALITÉS",
"viewAll": "VOIR TOUT",
"checking": "VÉRIFICATION...",
"play": "JOUER"
},
"mods": {
"searchPlaceholder": "Rechercher des mods...",
"myMods": "MES MODS",
"previous": "PRÉCÉDENT",
"next": "SUIVANT",
"page": "Page",
"of": "sur",
"modalTitle": "MES MODS",
"noModsFound": "Aucun Mod Trouvé",
"noModsFoundDesc": "Essayez d'ajuster votre recherche",
"noModsInstalled": "Aucun Mod Installé",
"noModsInstalledDesc": "Ajoutez des mods depuis CurseForge ou importez des fichiers locaux",
"view": "VOIR",
"install": "INSTALLER",
"installed": "INSTALLÉ",
"enable": "ACTIVER",
"disable": "DÉSACTIVER",
"active": "ACTIF",
"disabled": "DÉSACTIVÉ",
"delete": "Supprimer le mod",
"noDescription": "Aucune description disponible",
"confirmDelete": "Êtes-vous sûr de vouloir supprimer \"{name}\" ?",
"confirmDeleteDesc": "Cette action est irréversible.",
"confirmDeletion": "Confirmer la Suppression",
"apiKeyRequired": "Clé API Requise",
"apiKeyRequiredDesc": "Une clé API CurseForge est nécessaire pour parcourir les mods"
},
"news": {
"title": "TOUTES LES ACTUALITÉS",
"readMore": "Lire Plus"
},
"chat": {
"title": "CHAT JOUEURS",
"pickColor": "Couleur",
"inputPlaceholder": "Tapez votre message...",
"send": "Envoyer",
"online": "en ligne",
"charCounter": "{current}/{max}",
"secureChat": "Chat sécurisé - Les liens sont censurés",
"joinChat": "Rejoindre le Chat",
"chooseUsername": "Choisissez un nom d'utilisateur pour rejoindre le Chat Joueurs",
"username": "Nom d'utilisateur",
"usernamePlaceholder": "Entrez votre nom d'utilisateur...",
"usernameHint": "3-20 caractères, lettres, chiffres, - et _ uniquement",
"joinButton": "Rejoindre le Chat",
"colorModal": {
"title": "Personnaliser la Couleur du Nom",
"chooseSolid": "Choisissez une couleur unie:",
"customColor": "Couleur personnalisée:",
"preview": "Aperçu:",
"previewUsername": "Nom d'utilisateur",
"apply": "Appliquer la Couleur"
}
},
"settings": {
"title": "PARAMÈTRES",
"java": "Java Runtime",
"useCustomJava": "Utiliser un Chemin Java Personnalisé",
"javaDescription": "Remplacer le Java intégré par votre propre installation",
"javaPath": "Chemin de l'Exécutable Java",
"javaPathPlaceholder": "Sélectionnez le chemin Java...",
"javaBrowse": "Parcourir",
"javaHint": "Sélectionnez le dossier d'installation de Java (compatible Windows, Mac, Linux)",
"discord": "Intégration Discord",
"enableRPC": "Activer Discord Rich Presence",
"discordDescription": "Afficher votre activité du launcher sur Discord",
"game": "Options de Jeu",
"playerName": "Nom du Joueur",
"playerNamePlaceholder": "Entrez le nom du joueur",
"playerNameHint": "Ce nom sera utilisé en jeu (1-16 caractères)",
"openGameLocation": "Ouvrir l'Emplacement du Jeu",
"openGameLocationDesc": "Ouvrir le dossier d'installation du jeu",
"account": "Gestion UUID Joueur",
"currentUUID": "UUID Actuel",
"uuidPlaceholder": "Chargement UUID...",
"copyUUID": "Copier UUID",
"regenerateUUID": "Régénérer UUID",
"uuidHint": "Votre identifiant unique de joueur pour ce nom d'utilisateur",
"manageUUIDs": "Gérer Tous les UUIDs",
"manageUUIDsDesc": "Voir et gérer tous les UUIDs de joueurs",
"language": "Langue",
"selectLanguage": "Sélectionner la Langue",
"repairGame": "Réparer le Jeu",
"reinstallGame": "Réinstaller les fichiers du jeu (préserve les données)",
"gpuPreference": "Préférence GPU",
"gpuHint": "Sélectionnez votre GPU préféré (Linux: affecte DRI_PRIME)",
"gpuAuto": "Auto",
"gpuIntegrated": "Intégré",
"gpuDedicated": "Dédié",
"logs": "JOURNAUX SYSTÈME",
"logsCopy": "Copier",
"logsRefresh": "Actualiser",
"logsFolder": "Ouvrir le Dossier",
"logsLoading": "Chargement des journaux...",
"closeLauncher": "Comportement du Launcher",
"closeOnStart": "Fermer le Launcher au démarrage du jeu",
"closeOnStartDescription": "Fermer automatiquement le launcher après le lancement d'Hytale",
"hwAccel": "Accélération Matérielle",
"hwAccelDescription": "Activer l'accélération matérielle pour le launcher",
"gameBranch": "Branche du Jeu",
"branchRelease": "Release",
"branchPreRelease": "Pré-Release",
"branchHint": "Basculer entre la version stable release et la pré-release expérimentale",
"branchWarning": "Changer de branche téléchargera et installera une version différente du jeu",
"branchSwitching": "Passage à {branch}...",
"branchSwitched": "Passage à {branch} réussi!",
"installRequired": "Installation Requise",
"branchInstallConfirm": "Le jeu sera installé pour la branche {branch}. Continuer?"
},
"uuid": {
"modalTitle": "Gestion UUID",
"currentUserUUID": "UUID Utilisateur Actuel",
"allPlayerUUIDs": "Tous les UUIDs Joueurs",
"generateNew": "Générer Nouvel UUID",
"loadingUUIDs": "Chargement des UUIDs...",
"setCustomUUID": "Définir UUID Personnalisé",
"customPlaceholder": "Entrez UUID personnalisé (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
"setUUID": "Définir UUID",
"warning": "Attention: Définir un UUID personnalisé changera votre identité de joueur actuelle",
"copyTooltip": "Copier UUID",
"regenerateTooltip": "Générer Nouvel UUID"
},
"profiles": {
"modalTitle": "Gérer les Profils",
"newProfilePlaceholder": "Nom du Nouveau Profil",
"createProfile": "Créer un Profil"
},
"discord": {
"notificationText": "Rejoignez notre communauté Discord!",
"joinButton": "Rejoindre Discord"
},
"common": {
"confirm": "Confirmer",
"cancel": "Annuler",
"save": "Sauvegarder",
"close": "Fermer",
"delete": "Supprimer",
"edit": "Modifier",
"loading": "Chargement...",
"apply": "Appliquer",
"install": "Installer"
},
"notifications": {
"gameDataNotFound": "Erreur: Données du jeu introuvables",
"gameUpdatedSuccess": "Jeu mis à jour avec succès! 🎉",
"updateFailed": "Mise à jour échouée: {error}",
"updateError": "Erreur de mise à jour: {error}",
"discordEnabled": "Discord Rich Presence activé",
"discordDisabled": "Discord Rich Presence désactivé",
"discordSaveFailed": "Échec de la sauvegarde des paramètres Discord",
"playerNameRequired": "Veuillez entrer un nom de joueur valide",
"playerNameSaved": "Nom du joueur sauvegardé avec succès",
"playerNameSaveFailed": "Échec de la sauvegarde du nom du joueur",
"uuidCopied": "UUID copié dans le presse-papiers!",
"uuidCopyFailed": "Échec de la copie de l'UUID",
"uuidRegenNotAvailable": "Régénération UUID non disponible",
"uuidRegenFailed": "Échec de la régénération de l'UUID",
"uuidGenerated": "Nouvel UUID généré avec succès!",
"uuidGeneratedShort": "Nouvel UUID généré!",
"uuidGenerateFailed": "Échec de la génération du nouvel UUID",
"uuidRequired": "Veuillez entrer un UUID",
"uuidInvalidFormat": "Format UUID invalide",
"uuidSetFailed": "Échec de la définition de l'UUID personnalisé",
"uuidSetSuccess": "UUID personnalisé défini avec succès!",
"javaPathCopied": "Chemin Java copié dans le presse-papiers!",
"javaPathCopyFailed": "Échec de la copie du chemin Java",
"javaPathSaved": "Chemin Java sauvegardé avec succès!",
"javaPathSaveFailed": "Échec de la sauvegarde du chemin Java",
"javaPathInvalid": "Chemin Java invalide",
"javaPathReset": "Chemin Java réinitialisé aux valeurs par défaut",
"gameLocationError": "Impossible d'ouvrir l'emplacement du jeu",
"launcherRestartRequired": "Redémarrage du launcher requis pour appliquer les modifications",
"gameRepairConfirm": "Êtes-vous sûr de vouloir réparer le jeu? Cela réinstallera tous les fichiers du jeu.",
"gameRepairInProgress": "Réparation du jeu en cours...",
"gameRepairSuccess": "Jeu réparé avec succès!",
"gameRepairFailed": "Échec de la réparation du jeu: {error}",
"invalidUsername": "Nom d'utilisateur invalide",
"usernameInUse": "Nom d'utilisateur déjà utilisé",
"chatJoinSuccess": "Vous avez rejoint le chat!",
"chatJoinFailed": "Échec de la connexion au chat",
"messageTooLong": "Message trop long",
"messageSent": "Message envoyé",
"messageSendFailed": "Échec de l'envoi du message",
"colorUpdated": "Couleur mise à jour!",
"colorUpdateFailed": "Échec de la mise à jour de la couleur",
"profileCreated": "Profil créé avec succès!",
"profileCreateFailed": "Échec de la création du profil",
"profileDeleted": "Profil supprimé",
"profileDeleteFailed": "Échec de la suppression du profil",
"profileSwitched": "Profil changé vers: {name}",
"profileSwitchFailed": "Échec du changement de profil",
"invalidProfileName": "Nom de profil invalide",
"profileNameExists": "Un profil avec ce nom existe déjà",
"noInternet": "Pas de connexion Internet",
"checkInternetConnection": "Vérifiez votre connexion Internet",
"serverError": "Erreur serveur. Veuillez réessayer plus tard.",
"unknownError": "Une erreur inconnue s'est produite"
}
}

234
GUI/locales/pl-PL.json Normal file
View File

@@ -0,0 +1,234 @@
{
"nav": {
"play": "Graj",
"mods": "Mody",
"news": "Wiadomości",
"chat": "Chat z graczami",
"settings": "Ustawienia",
"skins": "Skiny"
},
"header": {
"playersLabel": "Graczy:",
"manageProfiles": "Zarządzaj Profilami",
"defaultProfile": "Domyślny",
"f2p": "FREE TO PLAY"
},
"install": {
"title": "FREE TO PLAY LAUNCHER",
"playerName": "Nazwa Gracza",
"playerNamePlaceholder": "Wprowadź Nazwę",
"customInstallation": "Dostosuj Instalacje",
"installationFolder": "Folder docelowy",
"pathPlaceholder": "Domyślna lokalizacja",
"browse": "Przeglądaj",
"installButton": "ZAINSTALUJ HYTALE",
"installing": "INSTALOWANIE..."
},
"play": {
"ready": "GOTOWE",
"subtitle": "Uruchom Hytale i rozpocznij przygodę",
"playButton": "GRAJ W HYTALE",
"latestNews": "NAJNOWSZE WIADOMOŚCI",
"viewAll": "ZOBACZ CAŁOŚĆ",
"checking": "SPRAWDZANIE...",
"play": "GRAJ"
},
"mods": {
"searchPlaceholder": "Wyszukaj mody...",
"myMods": "MOJE MODY",
"previous": "POPRZEDNIA",
"next": "NASTĘPNA",
"page": "Strona",
"of": "z",
"modalTitle": "MOJE MODY",
"noModsFound": "Nie Znaleziono Modów",
"noModsFoundDesc": "Spróbuj dostosować wyszukiwanie",
"noModsInstalled": "Brak Zainstalowanych Modów",
"noModsInstalledDesc": "Dodaj mody z CurseForge lub zaimportuj lokalne pliki",
"view": "WIDOK",
"install": "ZAINSTALUJ",
"installed": "ZAINSTALOWANE",
"enable": "WŁĄCZ",
"disable": "WYŁĄCZ",
"active": "AKTYWNE",
"disabled": "WYŁĄCZONE",
"delete": "Usuń mod",
"noDescription": "Brak opisu",
"confirmDelete": "Czy na pewno chcesz usunąć \"{name}\"?",
"confirmDeleteDesc": "Tej czynności nie można cofnąć.",
"confirmDeletion": "Potwierdź"
},
"news": {
"title": "WSZYSTKIE WIADOMOŚCI",
"readMore": "Zobacz Więcej"
},
"chat": {
"title": "Chat z graczami",
"pickColor": "Kolor",
"inputPlaceholder": "Wprowadź swoją wiadomość...",
"send": "Wyślij",
"online": "online",
"charCounter": "{current}/{max}",
"secureChat": "Bezpieczny czat Linki są ocenzurowane",
"joinChat": "Dołącz do Czatu",
"chooseUsername": "Wybierz nazwę użytkownika, aby dołączyć do Czatu z graczami",
"username": "Nazwa Gracza",
"usernamePlaceholder": "Wprowadź swoją nazwę...",
"usernameHint": "Między 3-20 znaków, tylko litery, cyfry i znaki - i _",
"joinButton": "Dołącz do Czatu",
"colorModal": {
"title": "Dostosuj Kolor Użytkownika",
"chooseSolid": "Wybierz jednolity kolor:",
"customColor": "Kolor niestandardowy:",
"preview": "Podgląd:",
"previewUsername": "Nazwa",
"apply": "Zastosuj Kolor"
}
},
"settings": {
"title": "USTAWIENIA",
"java": "Środowisko Java",
"useCustomJava": "Użyj niestandardowej ścieżki Java",
"javaDescription": "Zastąp dołączone środowisko wykonawcze Java własnym",
"javaPath": "Ścieżka Wykonywalna Java",
"javaPathPlaceholder": "Wybierz ścieżkę Java...",
"javaBrowse": "Przeglądaj",
"javaHint": "Wybierz folder instalacyjny Java (obsługiwane Windows, Mac, Linux)",
"discord": "Integracja z Discordem",
"enableRPC": "Włącz Discord Rich Presence",
"discordDescription": "Pokaż swoją aktywność na Discordzie",
"game": "Opcje gry",
"playerName": "Nazwa Gracza",
"playerNamePlaceholder": "Wprowadź swoją nazwę",
"playerNameHint": "Ta nazwa będzie używana w grze (1-16 znaków)",
"openGameLocation": "Otwórz Lokalizację Gry",
"openGameLocationDesc": "Otwórz folder instalacyjny gry",
"account": "Zarządzanie identyfikatorami UUID gracza",
"currentUUID": "Obecny UUID",
"uuidPlaceholder": "Ładowanie UUID...",
"copyUUID": "Skopiuj UUID",
"regenerateUUID": "Generuj UUID",
"uuidHint": "Twój unikalny identyfikator gracza dla tej nazwy użytkownika",
"manageUUIDs": "Zarządzaj wszystkimi UUID",
"manageUUIDsDesc": "Wyświetl i zarządzaj wszystkimi identyfikatorami UUID graczy",
"language": "Język",
"selectLanguage": "Wybierz Język",
"repairGame": "Napraw Grę",
"reinstallGame": "Zainstaluj ponownie pliki gry (zachowuje dane)",
"gpuPreference": "Preferencje GPU",
"gpuHint": "Wybierz preferowany procesor graficzny (Linux: wpływa na DRI_PRIME)",
"gpuAuto": "Auto",
"gpuIntegrated": "Zintegrowana",
"gpuDedicated": "Dedykowana",
"logs": "SYSTEM LOGS",
"logsCopy": "Kopiuj",
"logsRefresh": "Odśwież",
"logsFolder": "Otwórz Folder",
"logsLoading": "Ładowanie logów..."
},
"uuid": {
"modalTitle": "Zarządzanie UUID",
"currentUserUUID": "Aktualny UUID użytkownika",
"allPlayerUUIDs": "Wszystkie identyfikatory UUID graczy",
"generateNew": "Wygeneruj nowy UUID",
"loadingUUIDs": "Ładowanie UUID...",
"setCustomUUID": "Ustaw niestandardowy UUID",
"customPlaceholder": "Wprowadź niestandardowy UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
"setUUID": "Ustaw UUID",
"warning": "Ostrzeżenie: Ustawienie niestandardowego identyfikatora UUID spowoduje zmianę Twojego obecnego identyfikatora gracza",
"copyTooltip": "Kopiuj UUID",
"regenerateTooltip": "Wygeneruj nowy UUID"
},
"profiles": {
"modalTitle": "Zarządzaj Profilami",
"newProfilePlaceholder": "Nowa Nazwa Profilu",
"createProfile": "Utwórz Profil"
},
"discord": {
"notificationText": "Dołącz do naszej społeczności Discord!",
"joinButton": "Dołącz Discord"
},
"skins": {
"title": "Skiny",
"comingSoon": "Personalizacja skórek już wkrótce..."
},
"common": {
"confirm": "Potwierdź",
"cancel": "Anuluj",
"save": "Zapisz",
"close": "Zamknij",
"delete": "Usuń",
"edit": "Edytuj",
"loading": "Ładowanie...",
"apply": "Zastosuj"
},
"notifications": {
"gameDataNotFound": "Błąd: Nie znaleziono danych gry",
"gameUpdatedSuccess": "Gra została zaktualizowana pomyślnie! 🎉",
"updateFailed": "Aktualizacja nie powiodła się: {error}",
"updateError": "Błąd aktualizacji: {error}",
"discordEnabled": "Discord Rich Presence włączony",
"discordDisabled": "Discord Rich Presence wyłączony",
"discordSaveFailed": "Nie udało się zapisać ustawień Discorda",
"playerNameRequired": "Proszę podać prawidłową nazwę gracza",
"playerNameSaved": "Nazwa gracza została zapisana pomyślnie",
"playerNameSaveFailed": "Nie udało się zapisać nazwy gracza",
"uuidCopied": "Identyfikator UUID skopiowany do schowka!",
"uuidCopyFailed": "Nie udało się skopiować UUID",
"uuidRegenNotAvailable": "Ponowna gerowanie UUID niedostępne",
"uuidRegenFailed": "Nie udało się ponownie wygenerować UUID",
"uuidGenerated": "Nowy UUID został pomyślnie wygenerowany!",
"uuidGeneratedShort": "Wygenerowano nowy UUID!",
"uuidGenerateFailed": "Nie udało się wygenerować nowego UUID",
"uuidRequired": "Wprowadzić UUID",
"uuidInvalidFormat": "Nieprawidłowy format UUID",
"uuidSetFailed": "Nie udało się ustawić niestandardowego UUID",
"uuidSetSuccess": "Niestandardowy UUID został ustawiony pomyślnie!",
"uuidDeleteFailed": "Nie udało się usunąć UUID",
"uuidDeleteSuccess": "UUID został pomyślnie usunięty!",
"modsDownloading": "Pobieranie {name}...",
"modsTogglingMod": "Przełączanie moda...",
"modsDeletingMod": "Usuwanie moda...",
"modsLoadingMods": "Ładowanie modów z CurseForge...",
"modsInstalledSuccess": "{name} zainstalowany pomyślnie! 🎉",
"modsDeletedSuccess": "{name} usunięto pomyślnie",
"modsDownloadFailed": "Nie udało się pobrać moda: {error}",
"modsToggleFailed": "Nie udało się przełączyć moda: {error}",
"modsDeleteFailed": "Nie udało się usunąć moda: {error}",
"modsModNotFound": "Nie znaleziono informacji o modzie"
},
"confirm": {
"defaultTitle": "Potwierdź działanie",
"regenerateUuidTitle": "Wygeneruj nowy UUID",
"regenerateUuidMessage": "Czy na pewno chcesz wygenerować nowy UUID? To spowoduje zmianę Twojego identyfikatora gracza.",
"regenerateUuidButton": "Generuj",
"setCustomUuidTitle": "Ustaw niestandardowy UUID",
"setCustomUuidMessage": "Czy na pewno chcesz ustawić ten UUID? To spowoduje zmianę Twojego identyfikatora gracza.",
"setCustomUuidButton": "Ustaw UUID",
"deleteUuidTitle": "Usuń UUID",
"deleteUuidMessage": "Czy na pewno chcesz usunąć UUID dla \"{username}\"? Tej czynności nie można cofnąć.",
"deleteUuidButton": "Usuń",
"uninstallGameTitle": "Odinstaluj grę",
"uninstallGameMessage": "Czy na pewno chcesz odinstalować Hytale? Wszystkie pliki gry zostaną usunięte.",
"uninstallGameButton": "Odinstaluj"
},
"progress": {
"initializing": "Inicjalizacja...",
"downloading": "Pobieranie...",
"installing": "Instalowanie...",
"extracting": "Ekstraktowanie...",
"verifying": "Weryfikowanie...",
"switchingProfile": "Przełączanie profilu...",
"profileSwitched": "Profil zmieniony!",
"startingGame": "Uruchamianie gry...",
"launching": "URUCHAMIANIE...",
"uninstallingGame": "Odinstalowywanie gry...",
"gameUninstalled": "Gra została pomyślnie odinstalowana!",
"uninstallFailed": "Odinstalowanie nie powiodło się: {error}",
"startingUpdate": "Rozpoczynanie obowiązkowej aktualizacji gry...",
"installationComplete": "Instalacja zakończona pomyślnie!",
"installationFailed": "Instalacja nie powiodła się: {error}",
"installingGameFiles": "Instalowanie plików gry...",
"installComplete": "Instalacja zakończona!"
}
}

283
GUI/locales/sv.json Normal file
View File

@@ -0,0 +1,283 @@
{
"nav": {
"play": "Spela",
"mods": "Moddar",
"news": "Nyheter",
"chat": "Spelarchatt",
"settings": "Inställningar"
},
"header": {
"playersLabel": "Spelare:",
"manageProfiles": "Hantera profiler",
"defaultProfile": "Standard"
},
"install": {
"title": "GRATIS LAUNCHER",
"playerName": "Spelarnamn",
"playerNamePlaceholder": "Ange ditt namn",
"gameBranch": "Spelversion",
"releaseVersion": "Release (Stabil)",
"preReleaseVersion": "Pre-Release (Experimentell)",
"customInstallation": "Anpassad installation",
"installationFolder": "Installationsmapp",
"pathPlaceholder": "Standardplats",
"browse": "Bläddra",
"installButton": "INSTALLERA HYTALE",
"installing": "INSTALLERAR..."
},
"play": {
"ready": "REDO ATT SPELA",
"subtitle": "Starta Hytale och börja äventyret",
"playButton": "SPELA HYTALE",
"latestNews": "SENASTE NYHETERNA",
"viewAll": "VISA ALLA",
"checking": "KONTROLLERAR...",
"play": "SPELA"
},
"mods": {
"searchPlaceholder": "Sök moddar...",
"myMods": "MINA MODDAR",
"previous": "FÖREGÅENDE",
"next": "NÄSTA",
"page": "Sida",
"of": "av",
"modalTitle": "MINA MODDAR",
"noModsFound": "Inga moddar hittades",
"noModsFoundDesc": "Försök justera din sökning",
"noModsInstalled": "Inga moddar installerade",
"noModsInstalledDesc": "Lägg till moddar från CurseForge eller importera lokala filer",
"view": "VISA",
"install": "INSTALLERA",
"installed": "INSTALLERAD",
"enable": "AKTIVERA",
"disable": "INAKTIVERA",
"active": "AKTIV",
"disabled": "INAKTIVERAD",
"delete": "Ta bort modd",
"noDescription": "Ingen beskrivning tillgänglig",
"confirmDelete": "Är du säker på att du vill ta bort \"{name}\"?",
"confirmDeleteDesc": "Denna åtgärd kan inte ångras.",
"confirmDeletion": "Bekräfta borttagning",
"apiKeyRequired": "API-nyckel krävs",
"apiKeyRequiredDesc": "CurseForge API-nyckel behövs för att bläddra bland moddar"
},
"news": {
"title": "ALLA NYHETER",
"readMore": "Läs mer"
},
"chat": {
"title": "SPELARCHATT",
"pickColor": "Färg",
"inputPlaceholder": "Skriv ditt meddelande...",
"send": "Skicka",
"online": "online",
"charCounter": "{current}/{max}",
"secureChat": "Säker chatt - Länkar är censurerade",
"joinChat": "Gå med i chatten",
"chooseUsername": "Välj ett användarnamn för att gå med i spelarchartten",
"username": "Användarnamn",
"usernamePlaceholder": "Ange ditt användarnamn...",
"usernameHint": "3-20 tecken, endast bokstäver, siffror, - och _",
"joinButton": "Gå med i chatten",
"colorModal": {
"title": "Anpassa användarnamnsfargen",
"chooseSolid": "Välj en enfärgad färg:",
"customColor": "Anpassad färg:",
"preview": "Förhandsvisning:",
"previewUsername": "Användarnamn",
"apply": "Använd färg"
}
},
"settings": {
"title": "INSTÄLLNINGAR",
"java": "Java Runtime",
"useCustomJava": "Använd anpassad Java-sökväg",
"javaDescription": "Ersätt den medföljande Java-installationen med din egen",
"javaPath": "Java-körbar fil-sökväg",
"javaPathPlaceholder": "Välj Java-sökväg...",
"javaBrowse": "Bläddra",
"javaHint": "Välj Java-installationsmappen (stöder Windows, Mac, Linux)",
"discord": "Discord-integration",
"enableRPC": "Aktivera Discord Rich Presence",
"discordDescription": "Visa din launcher-aktivitet på Discord",
"game": "Spelalternativ",
"playerName": "Spelarnamn",
"playerNamePlaceholder": "Ange spelarnamn",
"playerNameHint": "Detta namn kommer att användas i spelet (1-16 tecken)",
"openGameLocation": "Öppna spelplats",
"openGameLocationDesc": "Öppna spelinstallationsmappen",
"account": "Spelare UUID-hantering",
"currentUUID": "Nuvarande UUID",
"uuidPlaceholder": "Laddar UUID...",
"copyUUID": "Kopiera UUID",
"regenerateUUID": "Återskapa UUID",
"uuidHint": "Din unika spelaridentifierare för detta användarnamn",
"manageUUIDs": "Hantera alla UUID:er",
"manageUUIDsDesc": "Visa och hantera alla spelare-UUID:er",
"language": "Språk",
"selectLanguage": "Välj språk",
"repairGame": "Reparera spel",
"reinstallGame": "Ominstallera spelfiler (bevarar data)",
"gpuPreference": "GPU-preferens",
"gpuHint": "Välj din föredragna GPU (Linux: påverkar DRI_PRIME)",
"gpuAuto": "Auto",
"gpuIntegrated": "Integrerad",
"gpuDedicated": "Dedikerad",
"logs": "SYSTEMLOGGAR",
"logsCopy": "Kopiera",
"logsRefresh": "Uppdatera",
"logsFolder": "Öppna mapp",
"logsLoading": "Laddar loggar...",
"closeLauncher": "Launcher-beteende",
"closeOnStart": "Stäng launcher vid spelstart",
"closeOnStartDescription": "Stäng automatiskt launcher efter att Hytale har startats",
"hwAccel": "Hårdvaruacceleration",
"hwAccelDescription": "Aktivera hårdvaruacceleration för launchern",
"gameBranch": "Spelgren",
"branchRelease": "Release",
"branchPreRelease": "Pre-Release",
"branchHint": "Växla mellan stabil release- och experimentell pre-release-version",
"branchWarning": "Att byta gren kommer att ladda ner och installera en annan spelversion",
"branchSwitching": "Byter till {branch}...",
"branchSwitched": "Bytte framgångsrikt till {branch}!",
"installRequired": "Installation krävs",
"branchInstallConfirm": "Spelet kommer att installeras för {branch}-grenen. Fortsätt?"
},
"uuid": {
"modalTitle": "UUID-hantering",
"currentUserUUID": "Nuvarande användar-UUID",
"allPlayerUUIDs": "Alla spelare-UUID:er",
"generateNew": "Generera ny UUID",
"loadingUUIDs": "Laddar UUID:er...",
"setCustomUUID": "Ange anpassad UUID",
"customPlaceholder": "Ange anpassad UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
"setUUID": "Ange UUID",
"warning": "Varning: Att ange en anpassad UUID kommer att ändra din nuvarande spelaridentitet",
"copyTooltip": "Kopiera UUID",
"regenerateTooltip": "Generera ny UUID"
},
"profiles": {
"modalTitle": "Hantera profiler",
"newProfilePlaceholder": "Nytt profilnamn",
"createProfile": "Skapa profil"
},
"discord": {
"notificationText": "Gå med i vår Discord-gemenskap!",
"joinButton": "Gå med i Discord"
},
"common": {
"confirm": "Bekräfta",
"cancel": "Avbryt",
"save": "Spara",
"close": "Stäng",
"delete": "Ta bort",
"edit": "Redigera",
"loading": "Laddar...",
"apply": "Verkställ",
"install": "Installera"
},
"notifications": {
"gameDataNotFound": "Fel: Speldata hittades inte",
"gameUpdatedSuccess": "Spelet uppdaterades framgångsrikt! 🎉",
"updateFailed": "Uppdatering misslyckades: {error}",
"updateError": "Uppdateringsfel: {error}",
"discordEnabled": "Discord Rich Presence aktiverad",
"discordDisabled": "Discord Rich Presence inaktiverad",
"discordSaveFailed": "Misslyckades med att spara Discord-inställning",
"playerNameRequired": "Ange ett giltigt spelarnamn",
"playerNameSaved": "Spelarnamn sparat framgångsrikt",
"playerNameSaveFailed": "Misslyckades med att spara spelarnamn",
"uuidCopied": "UUID kopierad till urklipp!",
"uuidCopyFailed": "Misslyckades med att kopiera UUID",
"uuidRegenNotAvailable": "UUID-återgenerering ej tillgänglig",
"uuidRegenFailed": "Misslyckades med att återgenerera UUID",
"uuidGenerated": "Ny UUID genererad framgångsrikt!",
"uuidGeneratedShort": "Ny UUID genererad!",
"uuidGenerateFailed": "Misslyckades med att generera ny UUID",
"uuidRequired": "Ange en UUID",
"uuidInvalidFormat": "Ogiltigt UUID-format",
"uuidSetFailed": "Misslyckades med att ange anpassad UUID",
"uuidSetSuccess": "Anpassad UUID angiven framgångsrikt!",
"uuidDeleteFailed": "Misslyckades med att ta bort UUID",
"uuidDeleteSuccess": "UUID borttagen framgångsrikt!",
"modsDownloading": "Laddar ner {name}...",
"modsTogglingMod": "Växlar modd...",
"modsDeletingMod": "Tar bort modd...",
"modsLoadingMods": "Laddar moddar från CurseForge...",
"modsInstalledSuccess": "{name} installerad framgångsrikt! 🎉",
"modsDeletedSuccess": "{name} borttagen framgångsrikt",
"modsDownloadFailed": "Misslyckades med att ladda ner modd: {error}",
"modsToggleFailed": "Misslyckades med att växla modd: {error}",
"modsDeleteFailed": "Misslyckades med att ta bort modd: {error}",
"modsModNotFound": "Moddinformation hittades inte",
"hwAccelSaved": "Hårdvaruaccelerationsinställning sparad",
"hwAccelSaveFailed": "Misslyckades med att spara hårdvaruaccelerationsinställning",
"javaPathCopied": "Java-sökväg kopierad till urklipp!",
"javaPathCopyFailed": "Misslyckades med att kopiera Java-sökväg",
"javaPathSaved": "Java-sökväg sparad framgångsrikt!",
"javaPathSaveFailed": "Misslyckades med att spara Java-sökväg",
"javaPathInvalid": "Ogiltig Java-sökväg",
"javaPathReset": "Java-sökväg återställd till standardvärden",
"gameLocationError": "Kunde inte öppna spelplats",
"launcherRestartRequired": "Launcher-omstart krävs för att tillämpa ändringar",
"gameRepairConfirm": "Är du säker på att du vill reparera spelet? Detta kommer att ominstallera alla spelfiler.",
"gameRepairInProgress": "Reparerar spel...",
"gameRepairSuccess": "Spel reparerat framgångsrikt!",
"gameRepairFailed": "Spelreparation misslyckades: {error}",
"invalidUsername": "Ogiltigt användarnamn",
"usernameInUse": "Användarnamn upptaget",
"chatJoinSuccess": "Du har gått med i chatten!",
"chatJoinFailed": "Misslyckades med att gå med i chatten",
"messageTooLong": "Meddelande för långt",
"messageSent": "Meddelande skickat",
"messageSendFailed": "Misslyckades med att skicka meddelande",
"colorUpdated": "Färg uppdaterad!",
"colorUpdateFailed": "Misslyckades med att uppdatera färg",
"profileCreated": "Profil skapad framgångsrikt!",
"profileCreateFailed": "Misslyckades med att skapa profil",
"profileDeleted": "Profil borttagen",
"profileDeleteFailed": "Misslyckades med att ta bort profil",
"profileSwitched": "Bytte profil till: {name}",
"profileSwitchFailed": "Profilbyte misslyckades",
"invalidProfileName": "Ogiltigt profilnamn",
"profileNameExists": "En profil med detta namn finns redan",
"noInternet": "Ingen internetanslutning",
"checkInternetConnection": "Kontrollera din internetanslutning",
"serverError": "Serverfel. Försök igen senare.",
"unknownError": "Ett okänt fel inträffade"
},
"confirm": {
"defaultTitle": "Bekräfta åtgärd",
"regenerateUuidTitle": "Generera ny UUID",
"regenerateUuidMessage": "Är du säker på att du vill generera en ny UUID? Detta kommer att ändra din spelaridentitet.",
"regenerateUuidButton": "Generera",
"setCustomUuidTitle": "Ange anpassad UUID",
"setCustomUuidMessage": "Är du säker på att du vill ange denna anpassade UUID? Detta kommer att ändra din spelaridentitet.",
"setCustomUuidButton": "Ange UUID",
"deleteUuidTitle": "Ta bort UUID",
"deleteUuidMessage": "Är du säker på att du vill ta bort UUID:n för \"{username}\"? Denna åtgärd kan inte ångras.",
"deleteUuidButton": "Ta bort",
"uninstallGameTitle": "Avinstallera spel",
"uninstallGameMessage": "Är du säker på att du vill avinstallera Hytale? Alla spelfiler kommer att tas bort.",
"uninstallGameButton": "Avinstallera"
},
"progress": {
"initializing": "Initierar...",
"downloading": "Laddar ner...",
"installing": "Installerar...",
"extracting": "Extraherar...",
"verifying": "Verifierar...",
"switchingProfile": "Byter profil...",
"profileSwitched": "Profil bytt!",
"startingGame": "Startar spel...",
"launching": "STARTAR...",
"uninstallingGame": "Avinstallerar spel...",
"gameUninstalled": "Spel avinstallerat framgångsrikt!",
"uninstallFailed": "Avinstallation misslyckades: {error}",
"startingUpdate": "Startar obligatorisk speluppdatering...",
"installationComplete": "Installation slutförd framgångsrikt!",
"installationFailed": "Installation misslyckades: {error}",
"installingGameFiles": "Installerar spelfiler...",
"installComplete": "Installation slutförd!"
}
}

View File

@@ -14,15 +14,15 @@ source=("$url/archive/v$pkgver.tar.gz" "Hytale-F2P.desktop")
sha256sums=('SKIP' '46488fada4775d9976d7b7b62f8d1f1f8d9a9a9d8f8aa9af4f2e2153019f6a30') sha256sums=('SKIP' '46488fada4775d9976d7b7b62f8d1f1f8d9a9a9d8f8aa9af4f2e2153019f6a30')
build() { build() {
cd "$_pkgname-$pkgver" cd "$pkgname-$pkgver"
npm ci npm ci
npm run build:arch npm run build:arch
} }
package() { package() {
cd "$_pkgname-$pkgver" cd "$pkgname-$pkgver"
install -d "$pkgdir/opt/$_pkgname" install -d "$pkgdir/opt/$pkgname"
cp -r dist/linux-unpacked/* "$pkgdir/opt/$_pkgname" cp -r dist/linux-unpacked/* "$pkgdir/opt/$pkgname"
install -Dm644 "$srcdir/$_pkgname.desktop" "$pkgdir/usr/share/applications/$_pkgname.desktop" install -Dm644 "$srcdir/$pkgname.desktop" "$pkgdir/usr/share/applications/$pkgname.desktop"
install -Dm644 GUI/icon.png "$pkgdir/usr/share/icons/hicolor/256x256/apps/$_pkgname.png" install -Dm644 GUI/icon.png "$pkgdir/usr/share/icons/hicolor/256x256/apps/$pkgname.png"
} }

View File

@@ -4,9 +4,10 @@
<h1>🎮 Hytale F2P Launcher 🚀</h1> <h1>🎮 Hytale F2P Launcher 🚀</h1>
<h2>💻 Cross-Platform Multiplayer 🖥️</h2> <h2>💻 Cross-Platform Multiplayer 🖥️</h2>
<h3>Available for Windows 🪟, macOS 🍎, and Linux 🐧</h3> <h3>Available for Windows 🪟, macOS 🍎, and Linux 🐧</h3>
<p><small>An unofficial cross-platform launcher for Hytale with automatic updates and multiplayer support (all OS supported)</small></p> <p><small>An unofficial cross-platform launcher for Hytale with automatic updates and multiplayer support!</small></p>
</header> </header>
![GitHub Downloads](https://img.shields.io/github/downloads/amiayweb/Hytale-F2P/total?style=for-the-badge)
![Version](https://img.shields.io/badge/Version-2.1.1-green?style=for-the-badge) ![Version](https://img.shields.io/badge/Version-2.1.1-green?style=for-the-badge)
![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS%20%7C%20Linux-orange?style=for-the-badge) ![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS%20%7C%20Linux-orange?style=for-the-badge)
![License](https://img.shields.io/badge/License-Educational-blue?style=for-the-badge) ![License](https://img.shields.io/badge/License-Educational-blue?style=for-the-badge)
@@ -118,9 +119,9 @@
<tr> <tr>
<td><b>🖥️ OS</b></td> <td><b>🖥️ OS</b></td>
<td colspan="3" align="center"> <td colspan="3" align="center">
Windows 10/11 (64-bit; X64/ARM64) | Linux (x64/ARM64) | macOS (Apple Silicon only) Windows 10/11 (64-bit X64) | Linux (x64) | macOS (ARM64/Apple Silicon)
<br /> <br />
<small><i>⚠️ Note: macOS Intel (x86) is not yet supported <sup><a href="#fn1" id="ref1">1</a></sup></i></small> <small><i>⚠️ Note: ARM64 (Windows & Linux), macOS (x86/Intel) <b>are not supported!</b> ⚠️</i></small>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -131,7 +132,7 @@
</tr> </tr>
<tr> <tr>
<td><b>🧠 RAM</b></td> <td><b>🧠 RAM</b></td>
<td>8GB (dGPU)<sup><a href="#fn1" id="ref2">2</a></sup> /<br>12GB (iGPU)<sup><a href="#fn1" id="ref3">3</a></sup></td> <td>8GB (dGPU) / 12GB (iGPU)<sup><a href="#fn1" id="ref1">1</a></sup></td>
<td>16 GB</td> <td>16 GB</td>
<td>32 GB</td> <td>32 GB</td>
</tr> </tr>
@@ -156,19 +157,19 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<p id="fn1"><sup>Note 1</sup> Hytale did not provide game files for macOS Intel, yet.</p> <p id="fn1"><sup>Note 1</sup> Using Discrete/Dedicated GPU (dGPU) must have 8 GB RAM minimum, while using Integrated GPU (iGPU) must have 12 GB RAM.</p>
<p id="fn2"><sup>Note 2</sup> Using Discrete/Dedicated GPU (dGPU) must have 8 GB RAM minimum.</p>
<p id="fn3"><sup>Note 3</sup> Using Integrated GPU (dGPU) must have 12 GB RAM minimum.</p>
> [!WARNING]
> Our launcher has **not yet** supported Offline Mode (playing Hytale without internet).
> We will surely add the feature as soon as possible. Kindly wait for the update.
---
### 🪟 Windows Prequisites ### 🪟 Windows Prequisites
* **
* **Java JDK 25:** * **Java JDK 25:**
* [Oracle](https://www.oracle.com/java/technologies/downloads/#jdk25-windows), **no** support for Windows ARM64 in both version 25 and 21. * [Oracle](https://www.oracle.com/java/technologies/downloads/#jdk25-windows)
* [Adoptium](https://adoptium.net/temurin/releases/?version=25), has Windows ARM64 support in version 21 only. * [Adoptium](https://adoptium.net/temurin/releases/?version=25)
* [Microsoft](https://learn.microsoft.com/en-us/java/openjdk/download), has Windows ARM64 support in version 25. * [Microsoft](https://learn.microsoft.com/en-us/java/openjdk/download), has Windows ARM64 support in version 25.
* Download from any vendor if your OS is not Windows with ARM64 architecture.
* **Latest Visual Studio Redist:** * **Latest Visual Studio Redist:**
* Download via [Microsoft Visual C++ Redistributable](https://aka.ms/vc14/vc_redist.x64.exe) * Download via [Microsoft Visual C++ Redistributable](https://aka.ms/vc14/vc_redist.x64.exe)
* Or [All-in-One by Techpowerup](https://www.techpowerup.com/download/visual-c-redistributable-runtime-package-all-in-one/) * Or [All-in-One by Techpowerup](https://www.techpowerup.com/download/visual-c-redistributable-runtime-package-all-in-one/)
@@ -178,9 +179,9 @@
> [!WARNING] > [!WARNING]
> Ubuntu-based Distro like ZorinOS or Pop!_OS or Linux Mint would encounter issues due to UbuntuLTS environment, [check this Discord post](https://discord.com/channels/1462260103951421493/1463662398501027973). > Ubuntu-based Distro like ZorinOS or Pop!_OS or Linux Mint would encounter issues due to UbuntuLTS environment, [check this Discord post](https://discord.com/channels/1462260103951421493/1463662398501027973).
* Make sure you have already installed newest **GPU driver**, consult your distro docs or wiki. * Make sure you have already installed newest **GPU driver** especially proprietary NVIDIA, consult your distro docs or wiki.
* Also make sure that your GPU can be connected to EGL, try checking it first (again, consult your distro docs or wiki) before installing Hytale game via our launcher.
* Install `libpng` package to avoid SDL3_Image error: * Install `libpng` package to avoid `SDL3_Image` error:
* `libpng16-16 libpng-dev` for Ubuntu/Debian-based Distro * `libpng16-16 libpng-dev` for Ubuntu/Debian-based Distro
* `libpng libpng-devel` for Fedora/RHEL-based Distro * `libpng libpng-devel` for Fedora/RHEL-based Distro
* `libpng` for Arch-based Distro * `libpng` for Arch-based Distro
@@ -194,11 +195,12 @@
1. **Prerequisites:** Ensure you have installed all [**Windows Prerequisites**](https://github.com/amiayweb/Hytale-F2P/tree/main?tab=readme-ov-file#-windows-prequisites) listed above. 1. **Prerequisites:** Ensure you have installed all [**Windows Prerequisites**](https://github.com/amiayweb/Hytale-F2P/tree/main?tab=readme-ov-file#-windows-prequisites) listed above.
2. **Download:** Get the latest `Hytale-F2P-Launcher.exe` from the [**Releases**](https://github.com/amiayweb/Hytale-F2P/releases/latest/) page. 2. **Download:** Get the latest `Hytale-F2P-Launcher.exe` from the [**Releases**](https://github.com/amiayweb/Hytale-F2P/releases/latest/) page.
3. **SmartScreen Note:** Since the executable is currently unsigned, Windows may show a "Windows protected your PC" popup. 3. **SmartScreen Note:** Since the executable is currently unsigned, Windows may show a "Windows protected your PC" popup.
* Click **More info**. * Click **More info**, then click **Run anyway**.
* Click **Run anyway**.
4. **Launch:** Once installed, you can launch the app directly from your Desktop or the Start menu. 4. **Launch:** Once installed, you can launch the app directly from your Desktop or the Start menu.
5. **Whitelist in Windows Firewall** [#192](https://github.com/amiayweb/Hytale-F2P/issues/192#issuecomment-3803042908)
--- * Open the Windows Start Menu and search for `Allow an app through Windows Firewall`
* Click "Change settings" (you may need Admin privileges) and Locate `HytaleClient.exe` in the list.
* Ensure both the Private and Public checkboxes are checked. Click OK to save.
### 🐧 Linux Installation ### 🐧 Linux Installation
@@ -243,8 +245,6 @@
* **Desktop Entry:** After installing via `.rpm`, `.deb`, or `.pkg.tar.zst`, the launcher should automatically appear in your App Library/Grid. * **Desktop Entry:** After installing via `.rpm`, `.deb`, or `.pkg.tar.zst`, the launcher should automatically appear in your App Library/Grid.
* Missing libxcrypt.so.1: Install `libxcrypt-compat` using your package manager * Missing libxcrypt.so.1: Install `libxcrypt-compat` using your package manager
---
### 🍎 macOS Installation ### 🍎 macOS Installation
> [!NOTE] > [!NOTE]
@@ -272,9 +272,9 @@ The `.zip` version is useful for users who prefer a portable installation or nee
--- ---
# How to Host a Server # 📢 How to Host a Server
## Host your Singleplayer Server (Online-Play Feature) ## 🌐 Host your Singleplayer Server (Online-Play Feature)
> [!NOTE] > [!NOTE]
> You have to play the game to host the server. See Dedicated Server section below if you want to host it without you playing as the host. > You have to play the game to host the server. See Dedicated Server section below if you want to host it without you playing as the host.
@@ -283,23 +283,29 @@ The `.zip` version is useful for users who prefer a portable installation or nee
2. Pause the game (Esc) > select Online Play > Turn on `Allow Other Players to Join` > Set password if needed > Press `Save`. 2. Pause the game (Esc) > select Online Play > Turn on `Allow Other Players to Join` > Set password if needed > Press `Save`.
3. Check the status `Connected via STUN` or `Connected via UPnP`. 3. Check the status `Connected via STUN` or `Connected via UPnP`.
## Dedicated Server ## 🖧 Host a Dedicated Server
> [!NOTE] > [!NOTE]
> If you have already `HytaleServer.jar` in `HytaleF2P/{release/pre-release}/package/game/latest/Server`, you can use it to host local dedicated server. > If you already have the patched `HytaleServer.jar` in `HytaleF2P/{release/pre-release}/package/game/latest/Server`, you can use it to host local dedicated server.
> [!TIP] > [!TIP]
> Use services like Playit.gg, Tailscale, Radmin VPN to share UDP connection if setting up router as an admin is not possible. > Use services like Playit.gg, Tailscale, Radmin VPN to share UDP connection if setting up router as an admin is not possible.
> [!WARNING] > [!WARNING]
> `Hytale-F2P-Server.rar` file is needed to set up a server on non-playing hardware (such as VPS/server hosting). > `Hytale-F2P-Server.rar` file is needed to set up a server on non-playing hardware (such as VPS/server hosting). Linux ARM64 is supported for server only.
> [!IMPORTANT] > [!IMPORTANT]
> See detailed information of setting up a server here: [SERVER.md](SERVER.md) > See detailed information of setting up a server here: [SERVER.md](SERVER.md). Download the latest patched JAR, the patched RAR, or the SH/BAT scripts from channel `#open-public-server` in our Discord Server.
--- ---
## 🛠️ Building from Source ## 🔧 Troubleshooting
See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) for detailed Troubleshooting guide.
---
## 🔨 Building from Source
See [BUILD.md](docs/BUILD.md) for comprehensive build instructions. See [BUILD.md](docs/BUILD.md) for comprehensive build instructions.
@@ -308,20 +314,21 @@ See [BUILD.md](docs/BUILD.md) for comprehensive build instructions.
## 📋 Changelog ## 📋 Changelog
### 🆕 v2.1.1 ### 🆕 v2.1.1
- 🛠️ **Fix EPERM** Issue - 🛠️ **Fix Bug EPERM**: EPERM or Error Permission in creating/removing process in reinstalling is now fixed.
- 🅰️ **Adds Better Arch Build** - 🅰️ **Adds .pkg.tar.zst Build for Arch Users**: This Arch-package has been needed since the first release.
- - ❎ **Removes .pacman Build for Arch**: Based on the established conventions within the Arch Linux community, the file extension .pacman should not be used for package files.
- 🌎 **New Translation**: New Polish 🇵🇱 Translation added to the Launcher.
<details> <details>
<summary>Click here to see older Changelogs</summary> <summary>Click here to see older Changelogs</summary>
### 🆕 v2.1.0 ### 🔄 v2.1.0
- 🚨 **Auto-Retry Downloads and Auto-Patch Files** — - 🚨 **Auto-Retry Downloads and Auto-Patch Files** —
- ⚡ **Hardware Acceleration** — - ⚡ **Hardware Acceleration** —
- 🔎 **Browse CurseForge Mods** — Browsing mods now easier with our dedicated CurseForge API Key. - 🔎 **Browse CurseForge Mods** — Browsing mods now easier with our dedicated CurseForge API Key.
- 🌎 **Fixes and Release New Translation** — Fixed 🇪🇸 🇧🇷 and added more translation for current build. Turkish 🇹🇷 language now added. - 🌎 **Fixes and Release New Translation** — Fixed 🇪🇸 🇧🇷 and added more translation for current build. Turkish 🇹🇷 language now added.
### 🆕 v2.0.2b *(Minor Update: Performance & Utilities)* ### 🔄 v2.0.2b *(Minor Update: Performance & Utilities)*
- 🌎 **Language Translation** — A big welcome for Spanish 🇪🇸 and Portuguese (Brazil) 🇧🇷 players! **Language setting can be found in the bottom part of Settings pane.** - 🌎 **Language Translation** — A big welcome for Spanish 🇪🇸 and Portuguese (Brazil) 🇧🇷 players! **Language setting can be found in the bottom part of Settings pane.**
- 💻 **Laptop/Hybrid GPU Performance Issue Fix** — Added automatic GPU detection system and options to choose which GPU will be used for the game, *specifically for Linux users*. - 💻 **Laptop/Hybrid GPU Performance Issue Fix** — Added automatic GPU detection system and options to choose which GPU will be used for the game, *specifically for Linux users*.
- 👨‍💻 **In-App Logging** — Reporting bugs and issues to `Github Issues` tab or `Open A Ticket` channel in our Discord Server has been made easier for players, no more finding logs file manually. - 👨‍💻 **In-App Logging** — Reporting bugs and issues to `Github Issues` tab or `Open A Ticket` channel in our Discord Server has been made easier for players, no more finding logs file manually.

View File

@@ -14,6 +14,21 @@ function getAppDir() {
} }
} }
/**
* Get centralized UserData saves directory (NEW in 2.1.2)
* UserData is now stored separately from game installation
*/
function getHytaleSavesDir() {
const home = os.homedir();
if (process.platform === 'win32') {
return path.join(home, 'AppData', 'Local', 'HytaleSaves');
} else if (process.platform === 'darwin') {
return path.join(home, 'Library', 'Application Support', 'HytaleSaves');
} else {
return path.join(home, '.hytalesaves');
}
}
const DEFAULT_APP_DIR = getAppDir(); const DEFAULT_APP_DIR = getAppDir();
function getResolvedAppDir(customPath) { function getResolvedAppDir(customPath) {
@@ -218,20 +233,8 @@ async function getModsPath(customInstallPath = null) {
function getProfilesDir(customInstallPath = null) { function getProfilesDir(customInstallPath = null) {
try { try {
// get UserData path // NEW 2.1.2: Use centralized UserData location
let installPath = customInstallPath; const userDataPath = getHytaleSavesDir();
if (!installPath) {
const configFile = path.join(DEFAULT_APP_DIR, 'config.json');
if (fs.existsSync(configFile)) {
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
installPath = config.installPath || '';
}
}
if (!installPath) installPath = getAppDir();
const branch = loadVersionBranch();
const gameLatest = path.join(installPath, branch, 'package', 'game', 'latest');
const userDataPath = findUserDataPath(gameLatest);
const profilesDir = path.join(userDataPath, 'Profiles'); const profilesDir = path.join(userDataPath, 'Profiles');
if (!fs.existsSync(profilesDir)) { if (!fs.existsSync(profilesDir)) {
@@ -247,6 +250,7 @@ function getProfilesDir(customInstallPath = null) {
module.exports = { module.exports = {
getAppDir, getAppDir,
getHytaleSavesDir,
getResolvedAppDir, getResolvedAppDir,
expandHome, expandHome,
APP_DIR, APP_DIR,

View File

@@ -12,6 +12,7 @@ const { resolveJavaPath, getJavaExec, getBundledJavaPath, detectSystemJava, JAVA
const { getLatestClientVersion } = require('../services/versionManager'); const { getLatestClientVersion } = require('../services/versionManager');
const { updateGameFiles } = require('./gameManager'); const { updateGameFiles } = require('./gameManager');
const { syncModsForCurrentProfile } = require('./modManager'); const { syncModsForCurrentProfile } = require('./modManager');
const { getUserDataPath } = require('../utils/userDataMigration');
// Client patcher for custom auth server (sanasol.ws) // Client patcher for custom auth server (sanasol.ws)
let clientPatcher = null; let clientPatcher = null;
@@ -106,7 +107,9 @@ async function launchGame(playerName = 'Player', progressCallback, javaPathOverr
const customAppDir = getResolvedAppDir(installPathOverride); const customAppDir = getResolvedAppDir(installPathOverride);
const customGameDir = path.join(customAppDir, branch, 'package', 'game', 'latest'); const customGameDir = path.join(customAppDir, branch, 'package', 'game', 'latest');
const customJreDir = path.join(customAppDir, branch, 'package', 'jre', 'latest'); const customJreDir = path.join(customAppDir, branch, 'package', 'jre', 'latest');
const userDataDir = path.join(customGameDir, 'Client', 'UserData');
// NEW 2.1.2: Use centralized UserData location
const userDataDir = getUserDataPath();
const gameLatest = customGameDir; const gameLatest = customGameDir;
let clientPath = findClientPath(gameLatest); let clientPath = findClientPath(gameLatest);
@@ -282,6 +285,55 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
const gpuEnv = setupGpuEnvironment(gpuPreference); const gpuEnv = setupGpuEnvironment(gpuPreference);
Object.assign(env, gpuEnv); 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') {
const clientDir = path.dirname(clientPath);
const bundledLibzstd = path.join(clientDir, 'libzstd.so');
const backupLibzstd = path.join(clientDir, 'libzstd.so.bundled');
// Common system libzstd paths
const systemLibzstdPaths = [
'/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
];
let systemLibzstd = null;
for (const p of systemLibzstdPaths) {
if (fs.existsSync(p)) {
systemLibzstd = p;
break;
}
}
if (systemLibzstd && fs.existsSync(bundledLibzstd)) {
try {
const stats = fs.lstatSync(bundledLibzstd);
// Only replace if it's not already a symlink to system version
if (!stats.isSymbolicLink()) {
// Backup bundled version
if (!fs.existsSync(backupLibzstd)) {
fs.renameSync(bundledLibzstd, backupLibzstd);
console.log(`Linux: Backed up bundled libzstd.so`);
} else {
fs.unlinkSync(bundledLibzstd);
}
// Create symlink to system version
fs.symlinkSync(systemLibzstd, bundledLibzstd);
console.log(`Linux: Linked libzstd.so to system version (${systemLibzstd}) for glibc 2.41+ compatibility`);
} else {
const linkTarget = fs.readlinkSync(bundledLibzstd);
console.log(`Linux: libzstd.so already linked to ${linkTarget}`);
}
} catch (libzstdError) {
console.warn(`Linux: Could not replace libzstd.so: ${libzstdError.message}`);
}
}
}
try { try {
let spawnOptions = { let spawnOptions = {
stdio: ['ignore', 'pipe', 'pipe'], stdio: ['ignore', 'pipe', 'pipe'],

View File

@@ -9,7 +9,7 @@ const { installButler } = require('./butlerManager');
const { downloadAndReplaceHomePageUI, downloadAndReplaceLogo } = require('./uiFileManager'); const { downloadAndReplaceHomePageUI, downloadAndReplaceLogo } = require('./uiFileManager');
const { saveUsername, saveInstallPath, loadJavaPath, CONFIG_FILE, loadConfig, loadVersionBranch, saveVersionClient, loadVersionClient } = require('../core/config'); const { saveUsername, saveInstallPath, loadJavaPath, CONFIG_FILE, loadConfig, loadVersionBranch, saveVersionClient, loadVersionClient } = require('../core/config');
const { resolveJavaPath, detectSystemJava, downloadJRE, getJavaExec, getBundledJavaPath } = require('./javaManager'); const { resolveJavaPath, detectSystemJava, downloadJRE, getJavaExec, getBundledJavaPath } = require('./javaManager');
const userDataBackup = require('../utils/userDataBackup'); const { getUserDataPath, migrateUserDataToCentralized } = require('../utils/userDataMigration');
async function downloadPWR(branch = 'release', fileName = '4.pwr', progressCallback, cacheDir = CACHE_DIR, manualRetry = false) { async function downloadPWR(branch = 'release', fileName = '4.pwr', progressCallback, cacheDir = CACHE_DIR, manualRetry = false) {
const osName = getOS(); const osName = getOS();
@@ -308,31 +308,25 @@ async function applyPWR(pwrFile, progressCallback, gameDir = GAME_DIR, toolsDir
async function updateGameFiles(newVersion, progressCallback, gameDir = GAME_DIR, toolsDir = TOOLS_DIR, cacheDir = CACHE_DIR, branchOverride = null) { async function updateGameFiles(newVersion, progressCallback, gameDir = GAME_DIR, toolsDir = TOOLS_DIR, cacheDir = CACHE_DIR, branchOverride = null) {
let tempUpdateDir; let tempUpdateDir;
let backupPath = null;
const branch = branchOverride || loadVersionBranch(); const branch = branchOverride || loadVersionBranch();
const installPath = path.dirname(path.dirname(path.dirname(path.dirname(gameDir)))); const installPath = path.dirname(path.dirname(path.dirname(path.dirname(gameDir))));
// Vérifier si on a version_client et version_branch dans config.json
const config = loadConfig(); const config = loadConfig();
const hasVersionConfig = !!(config.version_client && config.version_branch); const oldBranch = config.version_branch || 'release';
const oldBranch = config.version_branch || 'release'; // L'ancienne branche pour le backup
console.log(`[UpdateGameFiles] hasVersionConfig: ${hasVersionConfig}`);
console.log(`[UpdateGameFiles] Switching from ${oldBranch} to ${branch}`); console.log(`[UpdateGameFiles] Switching from ${oldBranch} to ${branch}`);
try { try {
if (progressCallback) { // NEW 2.1.2: Ensure UserData migration to centralized location
progressCallback('Backing up user data...', 5, null, null, null);
}
// Backup UserData AVANT de télécharger/installer (critical for same-branch updates)
try { try {
console.log(`[UpdateGameFiles] Attempting to backup UserData from old branch: ${oldBranch}`); console.log('[UpdateGameFiles] Ensuring UserData migration...');
backupPath = await userDataBackup.backupUserData(installPath, oldBranch, hasVersionConfig); const migrationResult = await migrateUserDataToCentralized();
if (backupPath) { if (migrationResult.migrated) {
console.log(`[UpdateGameFiles] ✓ UserData backed up from ${oldBranch}: ${backupPath}`); console.log('[UpdateGameFiles] ✓ UserData migrated to centralized location');
} else if (migrationResult.alreadyMigrated) {
console.log('[UpdateGameFiles] ✓ UserData already in centralized location');
} }
} catch (backupError) { } catch (migrationError) {
console.warn('[UpdateGameFiles] UserData backup failed:', backupError.message); console.warn('[UpdateGameFiles] UserData migration warning:', migrationError.message);
} }
if (progressCallback) { if (progressCallback) {
@@ -390,31 +384,9 @@ async function updateGameFiles(newVersion, progressCallback, gameDir = GAME_DIR,
const logoResult = await downloadAndReplaceLogo(gameDir, progressCallback); const logoResult = await downloadAndReplaceLogo(gameDir, progressCallback);
console.log('Logo@2x.png update result after update:', logoResult); console.log('Logo@2x.png update result after update:', logoResult);
// Ensure UserData directory exists // NEW 2.1.2: No longer create UserData in game installation
const userDataDir = path.join(gameDir, 'Client', 'UserData'); // UserData is now in centralized location (getUserDataPath())
if (!fs.existsSync(userDataDir)) { console.log('[UpdateGameFiles] UserData is managed in centralized location');
console.log(`[UpdateGameFiles] Creating UserData directory at: ${userDataDir}`);
fs.mkdirSync(userDataDir, { recursive: true });
}
if (progressCallback) {
progressCallback('Restoring user data...', 90, null, null, null);
}
// Restore UserData using new system
if (backupPath) {
try {
console.log(`[UpdateGameFiles] Restoring UserData from ${oldBranch} to ${branch}`);
console.log(`[UpdateGameFiles] Source backup: ${backupPath}`);
await userDataBackup.restoreUserData(backupPath, installPath, branch);
await userDataBackup.cleanupBackup(backupPath);
console.log(`[UpdateGameFiles] ✓ UserData migrated successfully from ${oldBranch} to ${branch}`);
} catch (restoreError) {
console.warn('[UpdateGameFiles] ✗ UserData restore failed:', restoreError.message);
}
} else {
console.log('[UpdateGameFiles] No backup to restore, empty UserData folder created');
}
console.log(`Game files updated successfully to version: ${newVersion}`); console.log(`Game files updated successfully to version: ${newVersion}`);
@@ -434,15 +406,6 @@ async function updateGameFiles(newVersion, progressCallback, gameDir = GAME_DIR,
} catch (error) { } catch (error) {
console.error('Error updating game files:', error); console.error('Error updating game files:', error);
if (backupPath) {
try {
await userDataBackup.cleanupBackup(backupPath);
console.log('UserData backup cleaned up after error');
} catch (cleanupError) {
console.warn('Could not clean up UserData backup:', cleanupError.message);
}
}
if (tempUpdateDir && fs.existsSync(tempUpdateDir)) { if (tempUpdateDir && fs.existsSync(tempUpdateDir)) {
fs.rmSync(tempUpdateDir, { recursive: true, force: true }); fs.rmSync(tempUpdateDir, { recursive: true, force: true });
} }
@@ -470,28 +433,18 @@ async function installGame(playerName = 'Player', progressCallback, javaPathOver
const customToolsDir = path.join(customAppDir, 'butler'); const customToolsDir = path.join(customAppDir, 'butler');
const customGameDir = path.join(customAppDir, branch, 'package', 'game', 'latest'); const customGameDir = path.join(customAppDir, branch, 'package', 'game', 'latest');
const customJreDir = path.join(customAppDir, branch, 'package', 'jre', 'latest'); const customJreDir = path.join(customAppDir, branch, 'package', 'jre', 'latest');
const userDataDir = path.join(customGameDir, 'Client', 'UserData');
// Vérifier si on a version_client et version_branch dans config.json
const config = loadConfig();
const hasVersionConfig = !!(config.version_client && config.version_branch);
console.log(`[InstallGame] Configuration detected - version_client: ${config.version_client}, version_branch: ${config.version_branch}`);
console.log(`[InstallGame] hasVersionConfig: ${hasVersionConfig}`);
// Backup UserData AVANT l'installation si nécessaire
let backupPath = null;
if (progressCallback) {
progressCallback('Checking for existing UserData...', 5, null, null, null);
}
// NEW 2.1.2: Ensure UserData migration to centralized location
try { try {
console.log(`[InstallGame] Attempting UserData backup (hasVersionConfig: ${hasVersionConfig})...`); console.log('[InstallGame] Ensuring UserData migration...');
backupPath = await userDataBackup.backupUserData(customAppDir, branch, hasVersionConfig); const migrationResult = await migrateUserDataToCentralized();
if (backupPath) { if (migrationResult.migrated) {
console.log(`[InstallGame] ✓ UserData backed up to: ${backupPath}`); console.log('[InstallGame] ✓ UserData migrated to centralized location');
} else if (migrationResult.alreadyMigrated) {
console.log('[InstallGame] ✓ UserData already in centralized location');
} }
} catch (backupError) { } catch (migrationError) {
console.warn('[InstallGame] UserData backup failed:', backupError.message); console.warn('[InstallGame] UserData migration warning:', migrationError.message);
} }
[customAppDir, customCacheDir, customToolsDir].forEach(dir => { [customAppDir, customCacheDir, customToolsDir].forEach(dir => {
@@ -500,10 +453,6 @@ async function installGame(playerName = 'Player', progressCallback, javaPathOver
} }
}); });
if (!fs.existsSync(userDataDir)) {
fs.mkdirSync(userDataDir, { recursive: true });
}
saveUsername(playerName); saveUsername(playerName);
if (installPathOverride) { if (installPathOverride) {
saveInstallPath(installPathOverride); saveInstallPath(installPathOverride);
@@ -595,29 +544,9 @@ async function installGame(playerName = 'Player', progressCallback, javaPathOver
const logoResult = await downloadAndReplaceLogo(customGameDir, progressCallback); const logoResult = await downloadAndReplaceLogo(customGameDir, progressCallback);
console.log('Logo@2x.png update result after installation:', logoResult); console.log('Logo@2x.png update result after installation:', logoResult);
// Ensure UserData directory exists // NEW 2.1.2: No longer create UserData in game installation
if (!fs.existsSync(userDataDir)) { // UserData is managed in centralized location (getUserDataPath())
console.log(`[InstallGame] Creating UserData directory at: ${userDataDir}`); console.log('[InstallGame] UserData is managed in centralized location');
fs.mkdirSync(userDataDir, { recursive: true });
}
// Restore UserData from backup if exists
if (backupPath) {
if (progressCallback) {
progressCallback('Restoring UserData...', 95, null, null, null);
}
try {
console.log(`[InstallGame] Restoring UserData from: ${backupPath}`);
await userDataBackup.restoreUserData(backupPath, customAppDir, branch);
await userDataBackup.cleanupBackup(backupPath);
console.log('[InstallGame] ✓ UserData restored successfully');
} catch (restoreError) {
console.warn('[InstallGame] ✗ UserData restore failed:', restoreError.message);
}
} else {
console.log('[InstallGame] No backup to restore, empty UserData folder created');
}
if (progressCallback) { if (progressCallback) {
progressCallback('Installation complete', 100, null, null, null); progressCallback('Installation complete', 100, null, null, null);

File diff suppressed because it is too large Load Diff

View File

@@ -46,7 +46,8 @@ class UserDataBackup {
console.log(`[UserDataBackup] Copying from ${userDataPath} to ${backupPath}...`); console.log(`[UserDataBackup] Copying from ${userDataPath} to ${backupPath}...`);
await fs.copy(userDataPath, backupPath, { await fs.copy(userDataPath, backupPath, {
overwrite: true, overwrite: true,
errorOnExist: false errorOnExist: false,
dereference: true // Follow symlinks to avoid EPERM errors on Windows
}); });
console.log('[UserDataBackup] ✓ Backup completed successfully'); console.log('[UserDataBackup] ✓ Backup completed successfully');
return backupPath; return backupPath;
@@ -82,7 +83,8 @@ class UserDataBackup {
await fs.copy(backupPath, userDataPath, { await fs.copy(backupPath, userDataPath, {
overwrite: true, overwrite: true,
errorOnExist: false errorOnExist: false,
dereference: true // Follow symlinks to avoid EPERM errors on Windows
}); });
console.log('UserData restore completed successfully'); console.log('UserData restore completed successfully');

View File

@@ -0,0 +1,172 @@
const fs = require('fs-extra');
const path = require('path');
const { getHytaleSavesDir, getResolvedAppDir } = require('../core/paths');
const { loadConfig, saveConfig } = require('../core/config');
/**
* NEW SYSTEM (2.1.2+): UserData Migration to Centralized Location
*
* UserData is now stored in a centralized location instead of inside game installation:
* - Windows: %LOCALAPPDATA%\HytaleSaves\
* - macOS: ~/Library/Application Support/HytaleSaves/
* - Linux: ~/.hytalesaves/
*
* This eliminates the need for backup/restore during updates.
*/
/**
* Check if migration to centralized UserData has been completed
*/
function isMigrationCompleted() {
const config = loadConfig();
return config.userDataMigrated === true;
}
/**
* Mark migration as completed
*/
function markMigrationCompleted() {
saveConfig({ userDataMigrated: true });
console.log('[UserDataMigration] Migration marked as completed in config');
}
/**
* Find old UserData location (pre-2.1.2)
* Searches in: installPath/branch/package/game/latest/Client/UserData
*/
function findOldUserDataPath() {
try {
const config = loadConfig();
const installPath = getResolvedAppDir();
const branch = config.version_branch || 'release';
console.log(`[UserDataMigration] Looking for old UserData...`);
console.log(`[UserDataMigration] Install path: ${installPath}`);
console.log(`[UserDataMigration] Branch: ${branch}`);
// Old location
const oldPath = path.join(installPath, branch, 'package', 'game', 'latest', 'Client', 'UserData');
console.log(`[UserDataMigration] Checking: ${oldPath}`);
console.log(`[UserDataMigration] Checking: ${oldPath}`);
if (fs.existsSync(oldPath)) {
console.log(`[UserDataMigration] ✓ Found old UserData at: ${oldPath}`);
return oldPath;
}
console.log(`[UserDataMigration] ✗ Not found at current branch location`);
// Try other branch if current doesn't exist
const otherBranch = branch === 'release' ? 'pre-release' : 'release';
const otherPath = path.join(installPath, otherBranch, 'package', 'game', 'latest', 'Client', 'UserData');
console.log(`[UserDataMigration] Checking other branch: ${otherPath}`);
console.log(`[UserDataMigration] Checking other branch: ${otherPath}`);
if (fs.existsSync(otherPath)) {
console.log(`[UserDataMigration] ✓ Found old UserData in other branch at: ${otherPath}`);
return otherPath;
}
console.log('[UserDataMigration] ✗ No old UserData found in any branch');
return null;
} catch (error) {
console.error('[UserDataMigration] Error finding old UserData:', error);
return null;
}
}
/**
* Migrate UserData from old location to new centralized location
* One-time operation when upgrading to 2.1.2
*/
async function migrateUserDataToCentralized() {
// Check if already migrated
if (isMigrationCompleted()) {
console.log('[UserDataMigration] Migration already completed, skipping');
return { success: true, alreadyMigrated: true };
}
console.log('[UserDataMigration] === Starting UserData Migration to Centralized Location ===');
const newUserDataPath = getHytaleSavesDir();
console.log(`[UserDataMigration] Target location: ${newUserDataPath}`);
// Ensure new directory exists
if (!fs.existsSync(newUserDataPath)) {
fs.mkdirSync(newUserDataPath, { recursive: true });
console.log('[UserDataMigration] Created new HytaleSaves directory');
}
// Find old UserData
const oldUserDataPath = findOldUserDataPath();
if (!oldUserDataPath) {
console.log('[UserDataMigration] No old UserData found - fresh install or already migrated');
// Don't mark as migrated - let it check again next time in case game gets installed later
return { success: true, freshInstall: true };
}
// Check if new location already has data (shouldn't happen, but safety check)
const existingFiles = fs.readdirSync(newUserDataPath);
if (existingFiles.length > 0) {
console.warn('[UserDataMigration] New location already contains files, marking as migrated to avoid re-attempts');
markMigrationCompleted();
return { success: true, skipped: true, reason: 'target_not_empty' };
}
try {
console.log(`[UserDataMigration] Copying from ${oldUserDataPath} to ${newUserDataPath}...`);
// Copy all UserData to new location
await fs.copy(oldUserDataPath, newUserDataPath, {
overwrite: false,
errorOnExist: false,
dereference: true // Follow symlinks to avoid EPERM errors on Windows
});
console.log('[UserDataMigration] ✓ UserData copied successfully');
// Mark migration as completed
markMigrationCompleted();
console.log('[UserDataMigration] === Migration Completed Successfully ===');
return {
success: true,
migrated: true,
from: oldUserDataPath,
to: newUserDataPath
};
} catch (error) {
console.error('[UserDataMigration] ✗ Migration failed:', error);
return {
success: false,
error: error.message,
from: oldUserDataPath,
to: newUserDataPath
};
}
}
/**
* Get the centralized UserData path (always use this in 2.1.2+)
* Ensures directory exists
*/
function getUserDataPath() {
const userDataPath = getHytaleSavesDir();
// Ensure directory exists
if (!fs.existsSync(userDataPath)) {
fs.mkdirSync(userDataPath, { recursive: true });
console.log(`[UserDataMigration] Created UserData directory: ${userDataPath}`);
}
return userDataPath;
}
module.exports = {
migrateUserDataToCentralized,
getUserDataPath,
isMigrationCompleted,
findOldUserDataPath
};

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,123 @@
# Steam Deck / Ubuntu LTS Crash Investigation
## Status: SOLVED
**Last updated:** 2026-01-27
**Solution:** Replace bundled `libzstd.so` with system version.
---
## Problem Summary
The Hytale F2P launcher's client patcher causes crashes on Steam Deck and Ubuntu LTS with the error:
```
free(): invalid pointer
```
or
```
SIGSEGV (Segmentation fault)
```
The crash occurs after successful authentication, specifically right after "Finished handling RequiredAssets".
**Affected Systems:**
- Steam Deck (glibc 2.41)
- Ubuntu LTS
**Working Systems:**
- macOS
- Windows
- Older Arch Linux (glibc < 2.41)
---
## Root Cause
The **bundled `libzstd.so`** in the game client is incompatible with glibc 2.41's stricter heap validation. When the game decompresses assets using this library, it triggers heap corruption detected by glibc 2.41.
The crash occurs in `libzstd.so` during `free()` after "Finished handling RequiredAssets" (asset decompression).
---
## Solution
Replace the bundled `libzstd.so` with the system's `libzstd.so.1`.
### Automatic (Launcher)
The launcher automatically detects and replaces `libzstd.so` on Linux systems. No manual action needed.
### Manual
```bash
cd ~/.hytalef2p/release/package/game/latest/Client
# Backup bundled version
mv libzstd.so libzstd.so.bundled
# Link to system version
# Steam Deck / Arch Linux:
ln -s /usr/lib/libzstd.so.1 libzstd.so
# Debian / Ubuntu:
ln -s /usr/lib/x86_64-linux-gnu/libzstd.so.1 libzstd.so
# Fedora / RHEL:
ln -s /usr/lib64/libzstd.so.1 libzstd.so
```
### Restore Original
```bash
cd ~/.hytalef2p/release/package/game/latest/Client
rm libzstd.so
mv libzstd.so.bundled libzstd.so
```
---
## Why This Works
1. The bundled `libzstd.so` was likely compiled with different allocator settings or an older toolchain
2. glibc 2.41 has stricter heap validation that catches invalid memory operations
3. The system `libzstd.so.1` is compiled with the system's glibc and uses compatible memory allocation patterns
4. By using the system library, we avoid the incompatibility entirely
---
## Previous Investigation (for reference)
### What Was Tried Before Finding Solution
| Approach | Result |
|----------|--------|
| jemalloc allocator | Worked ~30% of time, not stable |
| GLIBC_TUNABLES | No effect |
| taskset (CPU pinning) | Single core too slow |
| nice/chrt (scheduling) | No effect |
| Various patching approaches | All crashed |
### Key Insight
The crash was in `libzstd.so`, not in our patched code. The patching just changed timing enough to expose the libzstd incompatibility more frequently.
---
## GDB Stack Trace (Historical)
```
#0 0x00007ffff7d3f5a4 in ?? () from /usr/lib/libc.so.6
#1 raise () from /usr/lib/libc.so.6
#2 abort () from /usr/lib/libc.so.6
#3-#4 ?? () from /usr/lib/libc.so.6
#5 free () from /usr/lib/libc.so.6
#6 ?? () from libzstd.so <-- CRASH POINT (bundled library)
#7-#24 HytaleClient code (asset decompression)
```
---
## Branch
`fix/steamdeck-libzstd`

View File

@@ -0,0 +1,65 @@
# Steam Deck / Linux Crash Fix
## SOLUTION: Use system libzstd
The crash is caused by the bundled `libzstd.so` being incompatible with glibc 2.41's stricter heap validation.
### Automatic Fix
The launcher automatically replaces `libzstd.so` with the system version. No manual action needed.
### Manual Fix
```bash
cd ~/.hytalef2p/release/package/game/latest/Client
# Backup and replace
mv libzstd.so libzstd.so.bundled
ln -s /usr/lib/libzstd.so.1 libzstd.so
```
### Restore Original
```bash
cd ~/.hytalef2p/release/package/game/latest/Client
rm libzstd.so
mv libzstd.so.bundled libzstd.so
```
---
## Debug Commands (for troubleshooting)
### Check libzstd Status
```bash
# Check if symlinked
ls -la ~/.hytalef2p/release/package/game/latest/Client/libzstd.so
# Find system libzstd
find /usr/lib -name "libzstd.so*"
```
### Binary Validation
```bash
file ~/.hytalef2p/release/package/game/latest/Client/HytaleClient
ldd ~/.hytalef2p/release/package/game/latest/Client/HytaleClient
```
### Restore Client Binary
```bash
cd ~/.hytalef2p/release/package/game/latest/Client
cp HytaleClient.original HytaleClient
rm -f HytaleClient.patched_custom
```
---
## Environment Variables
| Variable | Description | Example |
|----------|-------------|---------|
| `HYTALE_AUTH_DOMAIN` | Custom auth domain | `auth.sanasol.ws` |
| `HYTALE_NO_LIBZSTD_FIX` | Disable libzstd replacement | `1` |

75
main.js
View File

@@ -5,6 +5,7 @@ const { autoUpdater } = require('electron-updater');
const fs = require('fs'); const fs = require('fs');
const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveChatUsername, loadChatUsername, saveChatColor, loadChatColor, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, saveLauncherHardwareAcceleration, loadLauncherHardwareAcceleration, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched } = require('./backend/launcher'); const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveChatUsername, loadChatUsername, saveChatColor, loadChatColor, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, saveLauncherHardwareAcceleration, loadLauncherHardwareAcceleration, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched } = require('./backend/launcher');
const { retryPWRDownload } = require('./backend/managers/gameManager'); const { retryPWRDownload } = require('./backend/managers/gameManager');
const { migrateUserDataToCentralized } = require('./backend/utils/userDataMigration');
// Handle Hardware Acceleration // Handle Hardware Acceleration
try { try {
@@ -175,7 +176,8 @@ function createWindow() {
initDiscordRPC(); initDiscordRPC();
// Configure and initialize electron-updater // Configure and initialize electron-updater
autoUpdater.autoDownload = false; // Enable auto-download so updates start immediately when available
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = true; autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.on('checking-for-update', () => { autoUpdater.on('checking-for-update', () => {
@@ -200,6 +202,20 @@ function createWindow() {
autoUpdater.on('error', (err) => { autoUpdater.on('error', (err) => {
console.error('Error in auto-updater:', err); console.error('Error in auto-updater:', err);
// Handle macOS code signing errors - requires manual download
if (mainWindow && !mainWindow.isDestroyed()) {
const isMacSigningError = process.platform === 'darwin' &&
(err.code === 'ERR_UPDATER_INVALID_SIGNATURE' ||
err.message.includes('signature') ||
err.message.includes('code sign'));
mainWindow.webContents.send('update-error', {
message: err.message,
isMacSigningError: isMacSigningError,
requiresManualDownload: isMacSigningError || process.platform === 'darwin'
});
}
}); });
autoUpdater.on('download-progress', (progressObj) => { autoUpdater.on('download-progress', (progressObj) => {
@@ -217,7 +233,10 @@ function createWindow() {
console.log('Update downloaded:', info.version); console.log('Update downloaded:', info.version);
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('update-downloaded', { mainWindow.webContents.send('update-downloaded', {
version: info.version version: info.version,
platform: process.platform,
// macOS auto-install often fails on unsigned apps
autoInstallSupported: process.platform !== 'darwin'
}); });
} }
}); });
@@ -298,6 +317,14 @@ app.whenReady().then(async () => {
// Initialize Profile Manager (runs migration if needed) // Initialize Profile Manager (runs migration if needed)
profileManager.init(); profileManager.init();
// Migrate UserData to centralized location (v2.1.2+)
console.log('[Startup] Checking UserData migration...');
try {
await migrateUserDataToCentralized();
} catch (error) {
console.error('[Startup] UserData migration failed:', error);
}
createSplashScreen(); createSplashScreen();
setTimeout(async () => { setTimeout(async () => {
@@ -850,6 +877,17 @@ ipcMain.handle('open-external', async (event, url) => {
} }
}); });
ipcMain.handle('open-download-page', async () => {
try {
// Open GitHub releases page for manual download
await shell.openExternal('https://github.com/amiayweb/Hytale-F2P/releases/latest');
return { success: true };
} catch (error) {
console.error('Failed to open download page:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('open-game-location', async () => { ipcMain.handle('open-game-location', async () => {
try { try {
const { getResolvedAppDir, loadVersionBranch } = require('./backend/launcher'); const { getResolvedAppDir, loadVersionBranch } = require('./backend/launcher');
@@ -1077,8 +1115,37 @@ ipcMain.handle('download-update', async () => {
} }
}); });
ipcMain.handle('install-update', () => { ipcMain.handle('install-update', async () => {
autoUpdater.quitAndInstall(false, true); console.log('[AutoUpdater] Installing update...');
// On macOS, quitAndInstall often fails silently
// Use a more aggressive approach
if (process.platform === 'darwin') {
console.log('[AutoUpdater] macOS detected, using force quit approach');
// Give user feedback that something is happening
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('update-installing');
}
// Small delay to show the "Installing..." state
await new Promise(resolve => setTimeout(resolve, 500));
try {
autoUpdater.quitAndInstall(false, true);
} catch (err) {
console.error('[AutoUpdater] quitAndInstall failed:', err);
// Force quit the app - the update should install on next launch
app.exit(0);
}
// If quitAndInstall didn't work, force exit after a delay
setTimeout(() => {
console.log('[AutoUpdater] Force exiting app...');
app.exit(0);
}, 2000);
} else {
autoUpdater.quitAndInstall(false, true);
}
}); });
ipcMain.handle('get-launcher-version', () => { ipcMain.handle('get-launcher-version', () => {

5
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "hytale-f2p-launcher", "name": "hytale-f2p-launcher",
"version": "2.1.1", "version": "2.1.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hytale-f2p-launcher", "name": "hytale-f2p-launcher",
"version": "2.1.1", "version": "2.1.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"adm-zip": "^0.5.10", "adm-zip": "^0.5.10",
@@ -19,6 +19,7 @@
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@electron/notarize": "^2.5.0",
"electron": "^40.0.0", "electron": "^40.0.0",
"electron-builder": "^26.4.0" "electron-builder": "^26.4.0"
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "hytale-f2p-launcher", "name": "hytale-f2p-launcher",
"version": "2.1.1", "version": "2.1.2",
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support", "description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
"homepage": "https://github.com/amiayweb/Hytale-F2P", "homepage": "https://github.com/amiayweb/Hytale-F2P",
"main": "main.js", "main": "main.js",
@@ -45,6 +45,7 @@
}, },
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@electron/notarize": "^2.5.0",
"electron": "^40.0.0", "electron": "^40.0.0",
"electron-builder": "^26.4.0" "electron-builder": "^26.4.0"
}, },
@@ -131,8 +132,13 @@
} }
], ],
"icon": "build/icon.icns", "icon": "build/icon.icns",
"category": "public.app-category.games" "category": "public.app-category.games",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist"
}, },
"afterSign": "scripts/notarize.js",
"nsis": { "nsis": {
"oneClick": false, "oneClick": false,
"allowToChangeInstallationDirectory": true, "allowToChangeInstallationDirectory": true,

View File

@@ -24,7 +24,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
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'),
// Harwadre Acceleration // Hardware Acceleration
saveLauncherHardwareAcceleration: (enabled) => ipcRenderer.invoke('save-launcher-hw-accel', enabled), saveLauncherHardwareAcceleration: (enabled) => ipcRenderer.invoke('save-launcher-hw-accel', enabled),
loadLauncherHardwareAcceleration: () => ipcRenderer.invoke('load-launcher-hw-accel'), loadLauncherHardwareAcceleration: () => ipcRenderer.invoke('load-launcher-hw-accel'),
@@ -50,14 +50,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
selectModFiles: () => ipcRenderer.invoke('select-mod-files'), selectModFiles: () => ipcRenderer.invoke('select-mod-files'),
copyModFile: (sourcePath, modsPath) => ipcRenderer.invoke('copy-mod-file', sourcePath, modsPath), copyModFile: (sourcePath, modsPath) => ipcRenderer.invoke('copy-mod-file', sourcePath, modsPath),
onProgressUpdate: (callback) => { onProgressUpdate: (callback) => {
ipcRenderer.on('progress-update', (event, data) => { ipcRenderer.on('progress-update', (event, data) => callback(data));
// Ensure data includes retry state if available
if (data && typeof data === 'object') {
callback(data);
} else {
callback(data);
}
});
}, },
onProgressComplete: (callback) => { onProgressComplete: (callback) => {
ipcRenderer.on('progress-complete', () => callback()); ipcRenderer.on('progress-complete', () => callback());
@@ -69,7 +62,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
ipcRenderer.on('installation-end', () => callback()); ipcRenderer.on('installation-end', () => callback());
}, },
getUserId: () => ipcRenderer.invoke('get-user-id'), getUserId: () => ipcRenderer.invoke('get-user-id'),
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
openDownloadPage: () => ipcRenderer.invoke('open-download-page'), openDownloadPage: () => ipcRenderer.invoke('open-download-page'),
getUpdateInfo: () => ipcRenderer.invoke('get-update-info'), getUpdateInfo: () => ipcRenderer.invoke('get-update-info'),
onUpdatePopup: (callback) => { onUpdatePopup: (callback) => {
@@ -126,6 +118,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'), checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
downloadUpdate: () => ipcRenderer.invoke('download-update'), downloadUpdate: () => ipcRenderer.invoke('download-update'),
installUpdate: () => ipcRenderer.invoke('install-update'), installUpdate: () => ipcRenderer.invoke('install-update'),
quitAndInstallUpdate: () => ipcRenderer.invoke('install-update'), // Alias for update.js compatibility
getLauncherVersion: () => ipcRenderer.invoke('get-launcher-version'), getLauncherVersion: () => ipcRenderer.invoke('get-launcher-version'),
onUpdateAvailable: (callback) => { onUpdateAvailable: (callback) => {
ipcRenderer.on('update-available', (event, data) => callback(data)); ipcRenderer.on('update-available', (event, data) => callback(data));

62
scripts/notarize.js Normal file
View File

@@ -0,0 +1,62 @@
console.log('[Notarize] Script loaded');
let notarize;
try {
notarize = require('@electron/notarize').notarize;
console.log('[Notarize] @electron/notarize loaded successfully');
} catch (err) {
console.error('[Notarize] Failed to load @electron/notarize:', err.message);
throw err;
}
const path = require('path');
exports.default = async function notarizing(context) {
console.log('[Notarize] afterSign hook called');
console.log('[Notarize] Context:', JSON.stringify({
platform: context.electronPlatformName,
appOutDir: context.appOutDir,
outDir: context.outDir
}, null, 2));
const { electronPlatformName, appOutDir } = context;
// Only notarize macOS builds
if (electronPlatformName !== 'darwin') {
console.log('[Notarize] Skipping: not macOS');
return;
}
// Check credentials
const hasAppleId = !!process.env.APPLE_ID;
const hasPassword = !!process.env.APPLE_APP_SPECIFIC_PASSWORD;
const hasTeamId = !!process.env.APPLE_TEAM_ID;
console.log('[Notarize] Credentials check:', { hasAppleId, hasPassword, hasTeamId });
if (!hasAppleId || !hasPassword || !hasTeamId) {
console.log('[Notarize] Skipping: missing credentials');
return;
}
const appName = context.packager.appInfo.productFilename;
const appPath = path.join(appOutDir, `${appName}.app`);
console.log('[Notarize] Starting notarization...');
console.log('[Notarize] App path:', appPath);
console.log('[Notarize] Team ID:', process.env.APPLE_TEAM_ID);
try {
await notarize({
appPath,
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD,
teamId: process.env.APPLE_TEAM_ID,
});
console.log('[Notarize] Notarization complete!');
} catch (error) {
console.error('[Notarize] Notarization failed:', error.message);
console.error('[Notarize] Full error:', error);
throw error;
}
};