mirror of
https://git.sanhost.net/sanasol/hytale-f2p
synced 2026-02-26 10:31:47 -03:00
Develop (#282)
* fix: resolve cross-platform EPERM permissions errors modManager.js: - Switch from hardcoded 'junction' to dynamic symlink type based on OS (fixing Linux EPERM). - Add retry logic for directory removal to handle file locking race conditions. - Improve broken symlink detection during profile sync. gameManager.js: - Implement retry loop (3 attempts) for game directory removal in updateGameFiles to prevent EBUSY/EPERM errors on Windows. paths.js: - Prevent fs.mkdirSync failure in getModsPath by pre-checking for broken symbolic links. * fix: missing pacman builds * prepare release for 2.1.1 minor fix for EPERM error permission * prepare release 2.1.1 minor fix EPERM permission error * prepare release 2.1.1 * Update README.md Windows Prequisites for ARM64 builds * fix: remove broken symlink after detected * fix: add pathexists for paths.js to check symlink * fix: isbrokenlink should be true to remove the symlink * add arch package .pkg.tar.zst for release * fix: release workflow for build-arch and build-linux * build-arch job now only build arch .pkg.tar.zst package instead of the whole generic linux. * build-linux job now exclude .pacman package since its deprecated and should not be used. * fix: removes pacman build as it replaced by tar.zst and adds build:arch shortcut for pkgbuild * aur: add proper VCS (-git) PKGBUILD created clean VCS-based PKGBUILD following arch packaging conventions. this explicitly marked as a rolling (-git) build and derives its version dynamically from git tags and commit history via pkgver(). previous hybrid approach has been changed. key changes: - use -git suffix to clearly indicate rolling source builds - set pkgver=0 and compute the actual version via pkgver() - build only a directory layout using electron-builder (--dir) - avoid generating AppImage, deb, rpm, or pacman installers - align build and package steps with Arch packaging guidelines note: this PKGBUILD is intended for development and AUR use only and is not suitable for binary redistribution or release artifacts. * ci: add fixed-version PKGBUILD for Arch Linux releases this PKGBUILD intended for CI and GitHub release artifacts. targets tagged releases only and uses a fixed pkgver that matches the corresponding git tag. all of the VCS logic has been removed to PKGBUILD-git to ensure reproducible builds and stable versioning suitable for binary distribution. the build process relies on electron-builder directory output (--dir) and packages only the unpacked application into a standard Arch Linux package (.pkg.tar.zst). other distro format are excluded from this path and handled separately. this change establishes a clear separation between: - rolling AUR development builds (-git) - CI-generated, versioned Arch Linux release packages the result is predictable artifact naming, correct version alignment, and Arch-compliant packaging for downstream users. * Update README.md adds information for Arch build * Update README.md BUILD.md location was changed and now this link is poiting to nothing * Update PKGBUILD * Update PKGBUILD-git * chore: fix ubuntu/debian part in README.md * Polish language support (#195) * Update support_request.yml Added hardware specification * Update bug_report.yml Add logs textfield to bug report * chore: add changelog in README.md * fix screenshot input in feature_request.yml * add hardware spec input in bug_report.yml * fix: PKGBUILD pkgname variable fix * userdata migration [need review from other OS] * french translate * Add German and Swedish translations Added de.json and sv.json locale files for German and Swedish language support. Updated i18n.js to register 'de' and 'sv' as available languages in the launcher. * Update README.md * chore: add offline-mode warning to the README.md * chore: add downloads counter in README.md * fix: Steam Deck/Ubuntu crash - use system libzstd.so The bundled libzstd.so is incompatible with glibc 2.41's stricter heap validation, causing "free(): invalid pointer" crashes. Solution: Automatically replace bundled libzstd.so with system version on Linux. The launcher detects and symlinks to /usr/lib/libzstd.so.1. - Auto-detect system libzstd at common paths (Arch, Debian, Fedora) - Backup bundled version as libzstd.so.bundled - Create symlink to system version - Add HYTALE_NO_LIBZSTD_FIX=1 to disable if needed Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: remove Windows and Linux ARM64 information on the README.md * Update support_request.yml * fix: improve update system UX and macOS compatibility Update System Improvements: - Fix duplicate update popups by disabling legacy updater.js - Add skip button to update popup (shows after 30s, on error, or after download) - Add macOS-specific handling with manual download as primary option - Add missing open-download-page IPC handler - Add missing unblockInterface() method to properly clean up after popup close - Add quitAndInstallUpdate alias in preload for compatibility - Remove pulse animation when download completes - Fix manual download button to show correct status and close popup - Sync player name to settings input after first install Client Patcher Cleanup: - Remove server patching code (server uses pre-patched JAR from CDN) - Simplify to client-only patching - Remove unused imports (crypto, AdmZip, execSync, spawn, javaManager) - Remove unused methods (stringToUtf8, findAndReplaceDomainUtf8) - Move localhost dev code to backup file for reference Code Quality Fixes: - Fix duplicate DOMContentLoaded handlers in install.js - Fix duplicate checkForUpdates definition in preload.js - Fix redundant if/else in onProgressUpdate callback - Fix typo "Harwadre" -> "Hardware" in preload.js Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add Russian language support Added Russian (ru) to the list of available languages. * chore: drafting documentation on SERVER.md * Some updates in Russian language localization file * fix * Update ru.json * Fixed Java runtime name and fixed typo * fixed untranslated place * Update ru.json * Update ru.json * Update ru.json * Update ru.json * Update ru.json * fix: timeout getLatestClient fixes #138 * fix: change default version to 7.pwr in main.js * fix: change default release version to 7.pwr * fix: change version release to 7.pwr * docs: Add comprehensive troubleshooting guide (#209) Add TROUBLESHOOTING.md with solutions for common issues including: - Windows: Firewall configuration, duplicate mods, SmartScreen - Linux: GPU detection (NVIDIA/AMD), SDL3_image/libpng dependencies, Wayland/X11 issues, Steam Deck support - macOS: Rosetta 2 for Apple Silicon, code signing, quarantine - Connection: Server boot failures, regional restrictions - Authentication: Token errors, config reset procedures - Avatar/Cosmetics: F2P limitations documentation - Backup locations for all platforms - Log locations for bug reports Solutions compiled from closed GitHub issues (#205, #155, #90, #60, #144, #192) and community feedback. Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> * Standardize language codes, improve formatting, and update all locale files. (#224) * Update German (Germany) localization * Update Español (España) localization * Update French (France) localization * Update Polish (Poland) localization * Update Portuguese (Brazil) localization * Update Russian (Russia) localization * Update Swedish (Sweden) localization * Update Turkish (Turkey) localization * Update language codes, names and alphabetical in i18n system * Changed Spanish language name to the Formal name "Spanish (Spain)" * Fix PKGBUILD-git * Fix PKGBUILD * delete cache after installation * Enforce 16-char player name limit and update mod sync Added a maxlength attribute to the player name input and enforced a 16-character limit in both install and settings scripts, providing user feedback if exceeded. Refactored modManager.js to replace symlink-based mod management with a copy-based system, copying enabled mods to HytaleSaves\Mods and removing legacy symlink logic to improve compatibility and avoid permission issues. * Update installation subtitle * chore: update quickstart link in README.md * chore: delete warning of Ubuntu-Debian at Linux Prequisites section * added featured server list from api * Add Featured Servers page to GUI * Update Discord invite URL in client patcher * Add differential update system * Remove launcher chat and add Discord popup * fix: removed 'check disk space' alert on permission file error * fix: upgrade tar to ^7.5.6 version * fix: re-add universal arch for mac * fix: upgrade electron/rebuild to 4.0.3 * fix: removed override tar version * fix: pkgbuild version to 2.1.2 * fix: src.tar.zst and srcinfo missing files * feat: add Indonesian language translation * fix: GPU preference hint to Laptop-only * feat: create two columns for settings page * Add Discord invite link to rpc * docs: add recordings form, fix OS list * Release v2.2.0 * Release v2.2.0 * Release v2.2.0 * chore: delete icon.ico, moved to build folder * chore: delete icon.png, moved to build folder * fix: build and release for tag push-only in release.yml * fix: gamescope steam deck issue fixes #186 hopefully * Support branch selection for server patching * chose: add auto-patch system for pre-release JAR * fix: preserves arch x64 on linux target for #242 * fix: removed arm64 flags * fix: redo package.json arch * update package-lock.json * Update release.yml * chore: sync package-lock with package.json * fix: reorder fedora libzstd paths to first iteration * feat: enhance gpu detection, drafting * fix: comprehensive UUID/username persistence bug fixes (#252) * fix: comprehensive UUID/username persistence bug fixes Major fixes for UUID/skin reset issues that caused players to lose cosmetics: Core fixes: - Username rename now preserves UUID (atomic rename, not new identity) - Atomic config writes with backup/recovery system - Case-insensitive UUID lookup with case-preserving storage - Pre-launch validation blocks play if no username configured - Removed saveUsername calls from launch/install flows UUID Modal fixes: - Fixed isCurrent badge showing on wrong user - Added switch identity button to change between saved usernames - Fixed custom UUID input using unsaved DOM username - UUID list now refreshes when player name changes - Enabled copy/paste in custom UUID input field UI/UX improvements: - Added translation keys for switch username functionality - CSS user-select fix for UUID input fields - Allowed Ctrl+V/C/X/A shortcuts in Electron Files: config.js, gameLauncher.js, gameManager.js, playerManager.js, launcher.js, settings.js, main.js, preload.js, style.css, en.json See UUID_BUGS_FIX_PLAN.md for detailed bug list (18 bugs, 16 fixed) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(i18n): add switch username translations to all locales Added translation keys for username switching functionality: - notifications.noUsername - notifications.switchUsernameSuccess - notifications.switchUsernameFailed - notifications.playerNameTooLong - confirm.switchUsernameTitle - confirm.switchUsernameMessage - confirm.switchUsernameButton Languages updated: de-DE, es-ES, fr-FR, id-ID, pl-PL, pt-BR, ru-RU, sv-SE, tr-TR Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: move UUID_BUGS_FIX_PLAN.md to docs folder * docs: update UUID_BUGS_FIX_PLAN with complete fix details --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> * chore: rearrange, fix, and improve README.md * chore: link downloads, platform, and version to release page in README.md * chore: update discord link * chore: insert contact link in CODE_OF_CONDUCT.md * fix: missing version text on launcher * chore: update quickstart button link to header * chore: update discord link and give warning quickstart * chore revise online play hosting instructions in README Updated instructions for hosting an online game and clarified troubleshooting steps. * Fix Turkish translations in tr-TR.json * fix: EPERM error in Repair Game Button [windows testing needed] * fix: invalid generated token that caused hangs on exit [windows testing needed] * fix: major bug - hytale won't launch with laptop machine and ghost processes * fix: discord RPC destroy error if not connected * fix: major bug - detach game process to avoid launcher-held handles causing zombie process * docs: add analysis on ghost process and launcher cleanup * revert generateLocalTokens, wrong analysis on game launching issue * revert add deps for generateLocalTokens * Add proxy client and route downloads through it * fix: Prevent JAR file corruption during proxy downloads Fixed binary file corruption when downloading through proxy by using PassThrough stream to preserve data integrity while tracking download progress. * Improve featured servers layout with Discord integration - Add Discord button to server cards when discord link is present in API data - Remove HF2P Servers section to use full width for featured servers - Increase server card size (300x180px banner, larger fonts and spacing) - Simplify layout from 2-column grid to single full-width container - Discord button opens external browser with server invite link * package version to 2.2.1 Update package.json version from 2.2.0 to 2.2.1 to publish a patch release. * fix: add game_running_marker to prevent duplicate launches * Add smart proxy with direct-fallback and logging * fix: remove duplicate check * fix: cache invalidation from .env prevents multiple launch attempts for all env related, it is necessary to clear cache first, otherwise on few launch attempts the game wouldn't run * fix: redact proxy_url and remove timed out emoji * Prepare Release v2.2.1 * docs: enhance bug report template with placeholders and options Updated the bug report template to include placeholders and additional Linux distributions. * chore revise windows prerequisites and changelog Updated prerequisites and changelog for version 2.2.1. * chore: improvise badges, relocate star history, fix discord links * chore: fix release notes for v2.2.1 * feat(macos): add code signing and notarization support Add macOS code signing and notarization for Gatekeeper compatibility: - Add hardened runtime configuration in package.json - Add entitlements.mac.plist for required app permissions - Enable built-in electron-builder notarization - Add code signing and notarization secrets to workflow Required GitHub Secrets: - CSC_LINK: Base64-encoded .p12 certificate file - CSC_KEY_PASSWORD: Password for the .p12 certificate - APPLE_ID: Apple Developer account email - APPLE_APP_SPECIFIC_PASSWORD: App-specific password from appleid.apple.com - APPLE_TEAM_ID: 10-character Apple Developer Team ID Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Revise and enhance Hytale F2P Server Guide Updated the Hytale F2P Server Guide with new sections and improved formatting. * Update SERVER.md official accounts info Added CloudNord hosting information and new section for playing online with official accounts. * Update SERVER.md * refactor: replace pre-patched JAR download with ByteBuddy agent Migrate from downloading pre-patched server JARs from CDN to downloading the DualAuth ByteBuddy Agent from GitHub releases. The server JAR stays pristine - auth patching happens at runtime via -javaagent: flag. clientPatcher.js: - Replace patchServer() with ensureAgentAvailable() - Download dualauth-agent.jar to Server/ directory - Remove serverJarContainsDualAuth() and validateServerJarSize() gameLauncher.js: - Set JAVA_TOOL_OPTIONS env var with -javaagent: for runtime patching - Update logging to show agent status instead of server patch count Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Bump package version to 2.2.2 Update package.json version from 2.2.1 to 2.2.2 to mark a patch release. * Use new version API and default to v8 PWR --------- Co-authored-by: Fazri Gading <fazrigading@gmail.com> Co-authored-by: TalesAmaral <57869141+TalesAmaral@users.noreply.github.com> Co-authored-by: walti0 <95646872+walti0@users.noreply.github.com> Co-authored-by: Fazri Gading <super.fai700@gmail.com> Co-authored-by: sanasol <mail@sanasol.ws> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Terromur <79866197+Terromur@users.noreply.github.com> Co-authored-by: Zakhar Smokotov <zaharb840@gmail.com> Co-authored-by: xSamiVS <samtaiebc@gmail.com> Co-authored-by: MetricsLite <66024355+MetricsLite@users.noreply.github.com>
This commit is contained in:
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@@ -54,6 +54,14 @@ jobs:
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
- 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:
|
||||||
@@ -61,6 +69,7 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
dist/*.dmg
|
dist/*.dmg
|
||||||
dist/*.zip
|
dist/*.zip
|
||||||
|
dist/*.blockmap
|
||||||
dist/latest-mac.yml
|
dist/latest-mac.yml
|
||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
### ⚠️ **WARNING: READ [QUICK START](#-quick-start) before Downloading & Installing the Launcher!** ⚠️
|
### ⚠️ **WARNING: READ [QUICK START](#-quick-start) before Downloading & Installing the Launcher!** ⚠️
|
||||||
|
|
||||||
#### 🛑 **Found a problem? [Join the HF2P Discord](https://discord.gg/9zkSbQYZjA) and head to `#-⚠️-community-help`** 🛑
|
#### 🛑 **Found a problem? [Join the HF2P Discord](https://discord.gg/hf2pdc) and head to `#-⚠️-community-help`** 🛑
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
👍 If you like the project, <b>feel free to support us via Buy Me a Coffee!</b> ☕<br>
|
👍 If you like the project, <b>feel free to support us via Buy Me a Coffee!</b> ☕<br>
|
||||||
|
|||||||
126
SERVER.md
126
SERVER.md
@@ -6,15 +6,16 @@ Play with friends online! This guide covers both easy in-game hosting and advanc
|
|||||||
|
|
||||||
**Table of Contents**
|
**Table of Contents**
|
||||||
|
|
||||||
|
* [\[NEW!\] Play Online with Official Accounts 🆕](#new-play-online-with-official-accounts-)
|
||||||
* ["Server" Term and Definition](#server-term-and-definiton)
|
* ["Server" Term and Definition](#server-term-and-definiton)
|
||||||
* [Server Directory Location](#server-directory-location)
|
* [Server Directory Location](#server-directory-location)
|
||||||
* [A. Online Play Feature](#a-online-play-feature)
|
* [A. Host Your Singleplayer World](#a-host-your-singleplayer-world)
|
||||||
* [1. Host Your Singleplayer World using In-Game Invite Code](#1-host-your-singleplayer-world-using-in-game-invite-code)
|
* [1. Using Online-Play Feature In-Game Invite Code](#1-using-online-play-feature--in-game-invite-code)
|
||||||
* [Common Issues (UPnP/NAT/STUN) on Online Play](#common-issues-upnpnatstun-on-online-play)
|
* [Common Issues (UPnP/NAT/STUN) on Online Play](#common-issues-upnpnatstun-on-online-play)
|
||||||
* [2. Host Your Singleplayer World using Tailscale](#2-host-your-singleplayer-world-using-tailscale)
|
* [2. Using Tailscale](#2-using-tailscale)
|
||||||
|
* [3. Using Radmin VPN](#3-using-radmin-vpn)
|
||||||
* [B. Local Dedicated Server](#b-local-dedicated-server)
|
* [B. Local Dedicated Server](#b-local-dedicated-server)
|
||||||
* [1. Using Playit.gg (Recommended) ✅](#1-using-playitgg-recommended-)
|
* [1. Using Playit.gg (Recommended) ✅](#1-using-playitgg-recommended-)
|
||||||
* [2. Using Radmin VPN](#2-using-radmin-vpn)
|
|
||||||
* [C. 24/7 Dedicated Server (Advanced)](#c-247-dedicated-server-advanced)
|
* [C. 24/7 Dedicated Server (Advanced)](#c-247-dedicated-server-advanced)
|
||||||
* [Step 1: Get the Files Ready](#step-1-get-the-files-ready)
|
* [Step 1: Get the Files Ready](#step-1-get-the-files-ready)
|
||||||
* [Step 2: Place HytaleServer.jar in the Server directory](#step-2-place-hytaleserverjar-in-the-server-directory)
|
* [Step 2: Place HytaleServer.jar in the Server directory](#step-2-place-hytaleserverjar-in-the-server-directory)
|
||||||
@@ -32,6 +33,69 @@ Play with friends online! This guide covers both easy in-game hosting and advanc
|
|||||||
* [10. Getting Help](#10-getting-help)
|
* [10. Getting Help](#10-getting-help)
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<div align='center'>
|
||||||
|
<h3>
|
||||||
|
<b>
|
||||||
|
Do you want to create Hytale Game Server with EASY SETUP, AFFORDABLE PRICE, AND 24/7 SUPPORT?
|
||||||
|
</b>
|
||||||
|
</h3>
|
||||||
|
<h2>
|
||||||
|
<b>
|
||||||
|
<a href="https://cloudnord.net/hytale-server-hosting">CLOUDNORD</a> is the ANSWER! HF2P Server is available!
|
||||||
|
</b>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
**CloudNord's Hytale, Minecraft, and Game Hosting** is at the core of our Server Hosting business. Join our Gaming community and experience our large choice of premium game servers, we’ve got you covered with super high-performance hardware, fantastic support options, and powerful server hosting to build and explore your worlds without limits!
|
||||||
|
|
||||||
|
**Order your Hytale, Minecraft, or other game servers today!**
|
||||||
|
Choose Java Edition, Bedrock Edition, Cross-Play, or any of our additional supported games.
|
||||||
|
Enjoy **20% OFF** all new game servers, **available now for a limited time!** Don’t miss out.
|
||||||
|
|
||||||
|
### **CloudNord key hosting features include:**
|
||||||
|
- Instant Server Setup ⚡
|
||||||
|
- High Performance Game Servers 🚀
|
||||||
|
- Game DDoS Protection 🛡️
|
||||||
|
- Intelligent Game Backups 🧠
|
||||||
|
- Quick Modpack Installer 🔧
|
||||||
|
- Quick Plugin & Mod Installer 🧰
|
||||||
|
- Full File Access 🗃️
|
||||||
|
- 24/7 Support 📞 🏪
|
||||||
|
- Powerful Game Control Server Panel 💪
|
||||||
|
|
||||||
|
### **Check Us Out:**
|
||||||
|
* 👉 CloudNord Website: https://cloudnord.net/hytalef2p
|
||||||
|
* 👉 CloudNord Discord: https://discord.gg/TYxGrmUz4Y
|
||||||
|
* 👉 CloudNord Reviews: https://www.trustpilot.com/review/cloudnord.net?page=2&stars=5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [NEW!] Play Online with Official Accounts 🆕
|
||||||
|
|
||||||
|
**Documentations:**
|
||||||
|
* [Hytale-Server-Docker by Sanasol](https://github.com/sanasol/hytale-server-docker/tree/main?tab=readme-ov-file#dual-authentication)
|
||||||
|
|
||||||
|
**Requirements:**
|
||||||
|
* Using the patched HytaleServer.jar
|
||||||
|
* Has Official Account with Purchased status on Official Hytale Website.
|
||||||
|
* This official account holder can be the server hoster or one of the players.
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Running the patched HytaleServer.jar with either [B. Local Dedicated Server](#b-local-dedicated-server) or [C. 24/7 Dedicated Server (Advanced)](#c-247-dedicated-server-advanced) successfully.
|
||||||
|
2. On the server's console/terminal/CMD, server admin **MUST RUN THIS EACH BOOT** to allow players with Official Hytale game license to connect on the server:
|
||||||
|
```
|
||||||
|
/auth logout
|
||||||
|
/auth persistence Encrypted
|
||||||
|
/auth login device
|
||||||
|
```
|
||||||
|
3. Server console will show instructions, an URL and a code; these will be revoked after 10 minutes if not authorized.
|
||||||
|
4. The server hoster can open the URL directly to browser by holding Ctrl then Click on it, or copy and send it to the player with official account.
|
||||||
|
5. Once it authorized, the official accounts can join server with F2P players.
|
||||||
|
6. If you want to modify anything, look at the [Hytale-Server-Docker](https://github.com/sanasol/hytale-server-docker/) above, give the repo a STAR too.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### "Server" Term and Definiton
|
### "Server" Term and Definiton
|
||||||
|
|
||||||
"HytaleServer.jar", which called as "Server", functions as the place of authentication of the client that supposed to go to Hytale Official Authentication System but we managed our way to redirect it on our service (Thanks to Sanasol), handling approximately thousands of players worldwide to play this game for free.
|
"HytaleServer.jar", which called as "Server", functions as the place of authentication of the client that supposed to go to Hytale Official Authentication System but we managed our way to redirect it on our service (Thanks to Sanasol), handling approximately thousands of players worldwide to play this game for free.
|
||||||
@@ -41,14 +105,15 @@ Kindly support us via [our Buy Me a Coffee link](https://buymeacoffee.com/hf2p)
|
|||||||
|
|
||||||
### Server Directory Location
|
### Server Directory Location
|
||||||
|
|
||||||
Here are the directory locations of Server folder if you have installed
|
Here are the directory locations of Server folder if you have installed it on default instalation location:
|
||||||
- **Windows:** `%localappdata%\HytaleF2P\release\package\game\latest\Server`
|
- **Windows:** `%localappdata%\HytaleF2P\release\package\game\latest\Server`
|
||||||
- **macOS:** `~/Library/Application Support/HytaleF2P/release/package/game/latest/Server`
|
- **macOS:** `~/Library/Application Support/HytaleF2P/release/package/game/latest/Server`
|
||||||
- **Linux:** `~/.hytalef2p/release/package/game/latest/Server`
|
- **Linux:** `~/.hytalef2p/release/package/game/latest/Server`
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> This location only exists if the user installed the game using our launcher. The `Server` folder needed to auth the HytaleClient to play Hytale online
|
> This location only exists if the user installed the game using our launcher.
|
||||||
> (for now; we planned to add offline mode in later version of our launcher).
|
> The `Server` folder needed to auth the HytaleClient to play Hytale in Singleplayer/Multiplayer for now.
|
||||||
|
> (We planned to add offline mode in later version of our launcher).
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> Hosting a dedicated Hytale server will not need the exact similar tree. You can put it anywhere, as long as the directory has `Assets.zip` which
|
> Hosting a dedicated Hytale server will not need the exact similar tree. You can put it anywhere, as long as the directory has `Assets.zip` which
|
||||||
@@ -64,6 +129,7 @@ Terms and conditions applies.
|
|||||||
## 1. Using Online-Play Feature / In-Game Invite Code
|
## 1. Using Online-Play Feature / In-Game Invite Code
|
||||||
|
|
||||||
The easiest way to play with friends - no manual server setup required!
|
The easiest way to play with friends - no manual server setup required!
|
||||||
|
|
||||||
*The game automatically handles networking using UPnP/STUN/NAT traversal.*
|
*The game automatically handles networking using UPnP/STUN/NAT traversal.*
|
||||||
|
|
||||||
**For Online Play to work, you need:**
|
**For Online Play to work, you need:**
|
||||||
@@ -112,6 +178,7 @@ Warning: Your network configuration may prevent other players from connecting.
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details><summary><b>b. "UPnP Failed" or "Port Mapping Failed" Warning</b></summary>
|
<details><summary><b>b. "UPnP Failed" or "Port Mapping Failed" Warning</b></summary>
|
||||||
|
|
||||||
**Check your router:**
|
**Check your router:**
|
||||||
1. Log into router admin panel (usually `192.168.1.1` or `192.168.0.1`)
|
1. Log into router admin panel (usually `192.168.1.1` or `192.168.0.1`)
|
||||||
2. Find UPnP settings (often under "Advanced" or "NAT")
|
2. Find UPnP settings (often under "Advanced" or "NAT")
|
||||||
@@ -123,7 +190,8 @@ Warning: Your network configuration may prevent other players from connecting.
|
|||||||
- See "Port Forwarding" or "Workarounds or NAT/CGNAT" sections below
|
- See "Port Forwarding" or "Workarounds or NAT/CGNAT" sections below
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details><summary><b>c. "Strict NAT" or "Symmetric NAT" Warning</b></summary>
|
<details><summary><b>c. "Connected via STUN", "Strict NAT" or "Symmetric NAT" Warning</b></summary>
|
||||||
|
|
||||||
Some routers have restrictive NAT that blocks peer connections.
|
Some routers have restrictive NAT that blocks peer connections.
|
||||||
|
|
||||||
**Try:**
|
**Try:**
|
||||||
@@ -133,6 +201,7 @@ Some routers have restrictive NAT that blocks peer connections.
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
## 2. Using Tailscale
|
## 2. Using Tailscale
|
||||||
|
|
||||||
Tailscale creates mesh VPN service that streamlines connecting devices and services securely across different networks. And **works crossplatform!!**
|
Tailscale creates mesh VPN service that streamlines connecting devices and services securely across different networks. And **works crossplatform!!**
|
||||||
|
|
||||||
1. All members are required to download [Tailscale](https://tailscale.com/download) on your device.
|
1. All members are required to download [Tailscale](https://tailscale.com/download) on your device.
|
||||||
@@ -148,6 +217,17 @@ Tailscale creates mesh VPN service that streamlines connecting devices and servi
|
|||||||
* Use the new share code to connect
|
* Use the new share code to connect
|
||||||
* To test your connection, ping the host's ipv4 mentioned in Tailscale
|
* To test your connection, ping the host's ipv4 mentioned in Tailscale
|
||||||
|
|
||||||
|
## 3. Using Radmin VPN
|
||||||
|
|
||||||
|
Creates a virtual LAN - all players need to install it:
|
||||||
|
|
||||||
|
1. Download [Radmin VPN](https://www.radmin-vpn.com/) - All players install it
|
||||||
|
2. One person create a room/network, others join with network name/password
|
||||||
|
3. Host joined the world, others will connect to it.
|
||||||
|
4. Open Hytale Game > Servers > Add Servers > Direct Connect > Type IP Address of the Host from Radmin.
|
||||||
|
|
||||||
|
These options bypass all NAT/CGNAT issues. But for **Windows machines only!**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# B. Local Dedicated Server
|
# B. Local Dedicated Server
|
||||||
@@ -167,11 +247,12 @@ Free tunneling service - only the host needs to install it:
|
|||||||
* Right-click file > Properties > Turn on 'Executable as a Program' | or `chmod +x playit-linux-amd64` on terminal
|
* Right-click file > Properties > Turn on 'Executable as a Program' | or `chmod +x playit-linux-amd64` on terminal
|
||||||
* Run by double-clicking the file or `./playit-linux-amd64` via terminal
|
* Run by double-clicking the file or `./playit-linux-amd64` via terminal
|
||||||
5. Open the URL/link by `Ctrl+Click` it. If unable, select the URL, then Right-Click to Copy (`Ctrl+Shift+C` for Linux) then Paste the URL into your browser to link it with your created account.
|
5. Open the URL/link by `Ctrl+Click` it. If unable, select the URL, then Right-Click to Copy (`Ctrl+Shift+C` for Linux) then Paste the URL into your browser to link it with your created account.
|
||||||
6. **WARNING: Do not close the terminal if you are still playing or hosting the server**
|
6. Once it done, download the `run_server_with_tokens (1)` script file (`.BAT` for Windows, `.SH` for Linux) from our Discord server > channel `#open-public-server`
|
||||||
7. Once it done, download the `run_server_with_tokens` script file (`.BAT` for Windows, `.SH` for Linux) from our Discord server > channel `#open-public-server`
|
7. Put the script file to the `Server` folder in `HytaleF2P` directory (`%localappdata%\HytaleF2P\release\package\game\latest\Server`)
|
||||||
8. Put the script file to the `Server` folder in `HytaleF2P` directory (`%localappdata%\HytaleF2P\release\package\game\latest\Server`)
|
8. Rename the script file to `run_server_with_tokens` to make it easier if you run it with Terminal, then do Method A or B.
|
||||||
9. Copy the `Assets.zip` from the `%localappdata%\HytaleF2P\release\package\game\latest\` folder to the `Server\` folder. (TIP: You can use Symlink of that file to reduce disk usage!)
|
9. If you put it in `Server` folder in `HytaleF2P` launcher, change `ASSETS_PATH="${ASSETS_PATH:-./Assets.zip}"` inside the script to be `ASSETS_PATH="${ASSETS_PATH:-../Assets.zip}"`. NOTICE THE `./` and `../` DIFFERENCE.
|
||||||
10. Double-click the .BAT file to host your server, wait until it shows:
|
10. Copy the `Assets.zip` from the `%localappdata%\HytaleF2P\release\package\game\latest\` folder to the `Server\` folder. (TIP: You can use Symlink of that file to reduce disk usage!)
|
||||||
|
11. Double-click the .BAT file to host your server, wait until it shows:
|
||||||
```
|
```
|
||||||
===================================================
|
===================================================
|
||||||
Hytale Server Booted! [Multiplayer, Fresh Universe]
|
Hytale Server Booted! [Multiplayer, Fresh Universe]
|
||||||
@@ -180,16 +261,12 @@ Hytale Server Booted! [Multiplayer, Fresh Universe]
|
|||||||
11. Connect to the server by go to `Servers` in your game client, press `Add Server`, type `localhost` in the address box, use any name for your server.
|
11. Connect to the server by go to `Servers` in your game client, press `Add Server`, type `localhost` in the address box, use any name for your server.
|
||||||
12. Send the public address in Step 3 to your friends.
|
12. Send the public address in Step 3 to your friends.
|
||||||
|
|
||||||
## 2. Using Radmin VPN
|
> [!CAUTION]
|
||||||
|
> Do not close the Playit.gg Terminal OR HytaleServer Terminal if you are still playing or hosting the server.
|
||||||
|
|
||||||
Creates a virtual LAN - all players need to install it:
|
## 2. Using Tailscale [DRAFT]
|
||||||
|
|
||||||
1. Download [Radmin VPN](https://www.radmin-vpn.com/) - All players install it
|
Tailscale
|
||||||
2. One person create a room/network, others join with network name/password
|
|
||||||
3. Host joined the world, others will connect to it.
|
|
||||||
4. Open Hytale Game > Servers > Add Servers > Direct Connect > Type IP Address of the Host from Radmin.
|
|
||||||
|
|
||||||
These options bypass all NAT/CGNAT issues. But for **Windows machines only!**
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -228,12 +305,12 @@ For 24/7 servers, custom configurations, or hosting on a VPS/dedicated machine.
|
|||||||
|
|
||||||
**Windows:**
|
**Windows:**
|
||||||
```batch
|
```batch
|
||||||
run_server.bat
|
run_server_with_token.bat
|
||||||
```
|
```
|
||||||
|
|
||||||
**macOS / Linux:**
|
**macOS / Linux:**
|
||||||
```bash
|
```bash
|
||||||
./run_server.sh
|
./run_server_with_token.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -503,3 +580,6 @@ See [Docker documentation](https://github.com/Hybrowse/hytale-server-docker) for
|
|||||||
- Auth Server: sanasol.ws
|
- Auth Server: sanasol.ws
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const FORCE_CLEAN_INSTALL_VERSION = false;
|
const FORCE_CLEAN_INSTALL_VERSION = false;
|
||||||
const CLEAN_INSTALL_TEST_VERSION = '4.pwr';
|
const CLEAN_INSTALL_TEST_VERSION = 'v4';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
FORCE_CLEAN_INSTALL_VERSION,
|
FORCE_CLEAN_INSTALL_VERSION,
|
||||||
|
|||||||
@@ -252,8 +252,8 @@ async function launchGame(playerNameOverride = null, progressCallback, javaPathO
|
|||||||
if (patchResult.client) {
|
if (patchResult.client) {
|
||||||
console.log(` Client: ${patchResult.client.patchCount || 0} occurrences`);
|
console.log(` Client: ${patchResult.client.patchCount || 0} occurrences`);
|
||||||
}
|
}
|
||||||
if (patchResult.server) {
|
if (patchResult.agent) {
|
||||||
console.log(` Server: ${patchResult.server.patchCount || 0} occurrences`);
|
console.log(` Agent: ${patchResult.agent.alreadyExists ? 'already present' : patchResult.agent.success ? 'downloaded' : 'failed'}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('Game patching failed:', patchResult.error);
|
console.warn('Game patching failed:', patchResult.error);
|
||||||
@@ -408,6 +408,17 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DualAuth Agent: Set JAVA_TOOL_OPTIONS so java picks up -javaagent: flag
|
||||||
|
// This enables runtime auth patching without modifying the server JAR
|
||||||
|
const agentJar = path.join(gameLatest, 'Server', 'dualauth-agent.jar');
|
||||||
|
if (fs.existsSync(agentJar)) {
|
||||||
|
const agentFlag = `-javaagent:${agentJar}`;
|
||||||
|
env.JAVA_TOOL_OPTIONS = env.JAVA_TOOL_OPTIONS
|
||||||
|
? `${env.JAVA_TOOL_OPTIONS} ${agentFlag}`
|
||||||
|
: agentFlag;
|
||||||
|
console.log('DualAuth Agent: enabled via JAVA_TOOL_OPTIONS');
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let spawnOptions = {
|
let spawnOptions = {
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ async function safeRemoveDirectory(dirPath, maxRetries = 3) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadPWR(branch = 'release', fileName = '7.pwr', progressCallback, cacheDir = CACHE_DIR, manualRetry = false) {
|
async function downloadPWR(branch = 'release', fileName = 'v8', progressCallback, cacheDir = CACHE_DIR, manualRetry = false) {
|
||||||
const osName = getOS();
|
const osName = getOS();
|
||||||
const arch = getArch();
|
const arch = getArch();
|
||||||
|
|
||||||
@@ -72,8 +72,23 @@ async function downloadPWR(branch = 'release', fileName = '7.pwr', progressCallb
|
|||||||
throw new Error('Hytale x86_64 Intel Mac Support has not been released yet. Please check back later.');
|
throw new Error('Hytale x86_64 Intel Mac Support has not been released yet. Please check back later.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `https://game-patches.hytale.com/patches/${osName}/${arch}/${branch}/0/${fileName}`;
|
const { getPWRUrlFromNewAPI } = require('../services/versionManager');
|
||||||
const dest = path.join(cacheDir, `${branch}_${fileName}`);
|
|
||||||
|
let url;
|
||||||
|
let isUsingNewAPI = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`[DownloadPWR] Fetching URL from new API for branch: ${branch}, version: ${fileName}`);
|
||||||
|
url = await getPWRUrlFromNewAPI(branch, fileName);
|
||||||
|
isUsingNewAPI = true;
|
||||||
|
console.log(`[DownloadPWR] Using new API URL: ${url}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[DownloadPWR] Failed to get URL from new API: ${error.message}`);
|
||||||
|
console.log(`[DownloadPWR] Falling back to old URL format`);
|
||||||
|
url = `https://game-patches.hytale.com/patches/${osName}/${arch}/${branch}/0/${fileName}.pwr`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dest = path.join(cacheDir, `${branch}_${fileName}.pwr`);
|
||||||
|
|
||||||
// Check if file exists and validate it
|
// Check if file exists and validate it
|
||||||
if (fs.existsSync(dest) && !manualRetry) {
|
if (fs.existsSync(dest) && !manualRetry) {
|
||||||
@@ -93,7 +108,7 @@ async function downloadPWR(branch = 'release', fileName = '7.pwr', progressCallb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Fetching PWR patch file:', url);
|
console.log(`Fetching PWR patch file from ${isUsingNewAPI ? 'NEW API' : 'old API'}:`, url);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (manualRetry) {
|
if (manualRetry) {
|
||||||
|
|||||||
@@ -6,32 +6,186 @@ const { smartRequest } = require('../utils/proxyClient');
|
|||||||
|
|
||||||
const BASE_PATCH_URL = 'https://game-patches.hytale.com/patches';
|
const BASE_PATCH_URL = 'https://game-patches.hytale.com/patches';
|
||||||
const MANIFEST_API = 'https://files.hytalef2p.com/api/patch_manifest';
|
const MANIFEST_API = 'https://files.hytalef2p.com/api/patch_manifest';
|
||||||
|
const NEW_API_URL = 'https://thecute.cloud/ShipOfYarn/api.php';
|
||||||
|
|
||||||
|
let apiCache = null;
|
||||||
|
let apiCacheTime = 0;
|
||||||
|
const API_CACHE_DURATION = 60000; // 1 minute
|
||||||
|
|
||||||
|
async function fetchNewAPI() {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
if (apiCache && (now - apiCacheTime) < API_CACHE_DURATION) {
|
||||||
|
console.log('[NewAPI] Using cached API data');
|
||||||
|
return apiCache;
|
||||||
|
}
|
||||||
|
|
||||||
async function getLatestClientVersion(branch = 'release') {
|
|
||||||
try {
|
try {
|
||||||
console.log(`Fetching latest client version from API (branch: ${branch})...`);
|
console.log('[NewAPI] Fetching from:', NEW_API_URL);
|
||||||
const response = await smartRequest(`https://files.hytalef2p.com/api/version_client?branch=${branch}`, {
|
const response = await axios.get(NEW_API_URL, {
|
||||||
timeout: 40000,
|
timeout: 15000,
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': 'Hytale-F2P-Launcher'
|
'User-Agent': 'Hytale-F2P-Launcher'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.data && response.data.client_version) {
|
if (response.data && response.data.hytale) {
|
||||||
const version = response.data.client_version;
|
apiCache = response.data;
|
||||||
console.log(`Latest client version for ${branch}: ${version}`);
|
apiCacheTime = now;
|
||||||
return version;
|
console.log('[NewAPI] API data fetched and cached successfully');
|
||||||
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
console.log('Warning: Invalid API response, falling back to latest known version (7.pwr)');
|
throw new Error('Invalid API response structure');
|
||||||
return '7.pwr';
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching client version:', error.message);
|
console.error('[NewAPI] Error fetching API:', error.message);
|
||||||
console.log('Warning: API unavailable, falling back to latest known version (7.pwr)');
|
if (apiCache) {
|
||||||
return '7.pwr';
|
console.log('[NewAPI] Using expired cache due to error');
|
||||||
|
return apiCache;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getLatestVersionFromNewAPI(branch = 'release') {
|
||||||
|
try {
|
||||||
|
const apiData = await fetchNewAPI();
|
||||||
|
const osName = getOS();
|
||||||
|
const arch = getArch();
|
||||||
|
|
||||||
|
let osKey = osName;
|
||||||
|
if (osName === 'darwin') {
|
||||||
|
osKey = 'mac';
|
||||||
|
}
|
||||||
|
|
||||||
|
const branchData = apiData.hytale[branch];
|
||||||
|
if (!branchData || !branchData[osKey]) {
|
||||||
|
throw new Error(`No data found for branch: ${branch}, OS: ${osKey}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const osData = branchData[osKey];
|
||||||
|
|
||||||
|
const versions = Object.keys(osData).filter(key => key.endsWith('.pwr'));
|
||||||
|
|
||||||
|
if (versions.length === 0) {
|
||||||
|
throw new Error(`No .pwr files found for ${osKey}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionNumbers = versions.map(v => {
|
||||||
|
const match = v.match(/v(\d+)/);
|
||||||
|
return match ? parseInt(match[1]) : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const latestVersionNumber = Math.max(...versionNumbers);
|
||||||
|
console.log(`[NewAPI] Latest version number: ${latestVersionNumber} for branch ${branch}`);
|
||||||
|
|
||||||
|
return `v${latestVersionNumber}`;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[NewAPI] Error getting latest version:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPWRUrlFromNewAPI(branch = 'release', version = 'v8') {
|
||||||
|
try {
|
||||||
|
const apiData = await fetchNewAPI();
|
||||||
|
const osName = getOS();
|
||||||
|
const arch = getArch();
|
||||||
|
|
||||||
|
let osKey = osName;
|
||||||
|
if (osName === 'darwin') {
|
||||||
|
osKey = 'mac';
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileName;
|
||||||
|
if (osName === 'windows') {
|
||||||
|
fileName = `${version}-windows-amd64.pwr`;
|
||||||
|
} else if (osName === 'linux') {
|
||||||
|
fileName = `${version}-linux-amd64.pwr`;
|
||||||
|
} else if (osName === 'darwin') {
|
||||||
|
fileName = `${version}-darwin-arm64.pwr`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const branchData = apiData.hytale[branch];
|
||||||
|
if (!branchData || !branchData[osKey]) {
|
||||||
|
throw new Error(`No data found for branch: ${branch}, OS: ${osKey}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const osData = branchData[osKey];
|
||||||
|
const url = osData[fileName];
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
throw new Error(`No URL found for ${fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[NewAPI] URL for ${fileName}: ${url}`);
|
||||||
|
return url;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[NewAPI] Error getting PWR URL:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLatestClientVersion(branch = 'release') {
|
||||||
|
try {
|
||||||
|
console.log(`[NewAPI] Fetching latest client version from new API (branch: ${branch})...`);
|
||||||
|
|
||||||
|
// Utiliser la nouvelle API
|
||||||
|
const latestVersion = await getLatestVersionFromNewAPI(branch);
|
||||||
|
console.log(`[NewAPI] Latest client version for ${branch}: ${latestVersion}`);
|
||||||
|
return latestVersion;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[NewAPI] Error fetching client version from new API:', error.message);
|
||||||
|
console.log('[NewAPI] Falling back to old API...');
|
||||||
|
|
||||||
|
// Fallback vers l'ancienne API si la nouvelle échoue
|
||||||
|
try {
|
||||||
|
const response = await smartRequest(`https://files.hytalef2p.com/api/version_client?branch=${branch}`, {
|
||||||
|
timeout: 40000,
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Hytale-F2P-Launcher'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data && response.data.client_version) {
|
||||||
|
const version = response.data.client_version;
|
||||||
|
console.log(`Latest client version for ${branch} (old API): ${version}`);
|
||||||
|
return version;
|
||||||
|
} else {
|
||||||
|
console.log('Warning: Invalid API response, falling back to latest known version (v8)');
|
||||||
|
return 'v8';
|
||||||
|
}
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error('Error fetching client version from old API:', fallbackError.message);
|
||||||
|
console.log('Warning: Both APIs unavailable, falling back to latest known version (v8)');
|
||||||
|
return 'v8';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction utilitaire pour extraire le numéro de version
|
||||||
|
// Supporte les formats: "7.pwr", "v8", "v8-windows-amd64.pwr", etc.
|
||||||
|
function extractVersionNumber(version) {
|
||||||
|
if (!version) return 0;
|
||||||
|
|
||||||
|
// Nouveau format: "v8" ou "v8-xxx.pwr"
|
||||||
|
const vMatch = version.match(/v(\d+)/);
|
||||||
|
if (vMatch) {
|
||||||
|
return parseInt(vMatch[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ancien format: "7.pwr"
|
||||||
|
const pwrMatch = version.match(/(\d+)\.pwr/);
|
||||||
|
if (pwrMatch) {
|
||||||
|
return parseInt(pwrMatch[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: essayer de parser directement
|
||||||
|
const num = parseInt(version);
|
||||||
|
return isNaN(num) ? 0 : num;
|
||||||
|
}
|
||||||
|
|
||||||
function buildArchiveUrl(buildNumber, branch = 'release') {
|
function buildArchiveUrl(buildNumber, branch = 'release') {
|
||||||
const os = getOS();
|
const os = getOS();
|
||||||
const arch = getArch();
|
const arch = getArch();
|
||||||
@@ -50,7 +204,7 @@ async function checkArchiveExists(buildNumber, branch = 'release') {
|
|||||||
|
|
||||||
async function discoverAvailableVersions(latestKnown, branch = 'release', maxProbe = 50) {
|
async function discoverAvailableVersions(latestKnown, branch = 'release', maxProbe = 50) {
|
||||||
const available = [];
|
const available = [];
|
||||||
const latest = parseInt(latestKnown.replace('.pwr', ''));
|
const latest = extractVersionNumber(latestKnown);
|
||||||
|
|
||||||
for (let i = latest; i >= Math.max(1, latest - maxProbe); i--) {
|
for (let i = latest; i >= Math.max(1, latest - maxProbe); i--) {
|
||||||
const exists = await checkArchiveExists(i, branch);
|
const exists = await checkArchiveExists(i, branch);
|
||||||
@@ -77,7 +231,7 @@ async function fetchPatchManifest(branch = 'release') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function extractVersionDetails(targetVersion, branch = 'release') {
|
async function extractVersionDetails(targetVersion, branch = 'release') {
|
||||||
const buildNumber = parseInt(targetVersion.replace('.pwr', ''));
|
const buildNumber = extractVersionNumber(targetVersion);
|
||||||
const previousBuild = buildNumber - 1;
|
const previousBuild = buildNumber - 1;
|
||||||
|
|
||||||
const manifest = await fetchPatchManifest(branch);
|
const manifest = await fetchPatchManifest(branch);
|
||||||
@@ -103,8 +257,8 @@ function canUseDifferentialUpdate(currentVersion, targetDetails) {
|
|||||||
|
|
||||||
if (!currentVersion) return false;
|
if (!currentVersion) return false;
|
||||||
|
|
||||||
const currentBuild = parseInt(currentVersion.replace('.pwr', ''));
|
const currentBuild = extractVersionNumber(currentVersion);
|
||||||
const expectedSource = parseInt(targetDetails.sourceVersion?.replace('.pwr', '') || '0');
|
const expectedSource = extractVersionNumber(targetDetails.sourceVersion);
|
||||||
|
|
||||||
return currentBuild === expectedSource;
|
return currentBuild === expectedSource;
|
||||||
}
|
}
|
||||||
@@ -112,8 +266,8 @@ function canUseDifferentialUpdate(currentVersion, targetDetails) {
|
|||||||
function needsIntermediatePatches(currentVersion, targetVersion) {
|
function needsIntermediatePatches(currentVersion, targetVersion) {
|
||||||
if (!currentVersion) return [];
|
if (!currentVersion) return [];
|
||||||
|
|
||||||
const current = parseInt(currentVersion.replace('.pwr', ''));
|
const current = extractVersionNumber(currentVersion);
|
||||||
const target = parseInt(targetVersion.replace('.pwr', ''));
|
const target = extractVersionNumber(targetVersion);
|
||||||
|
|
||||||
const intermediates = [];
|
const intermediates = [];
|
||||||
for (let i = current + 1; i <= target; i++) {
|
for (let i = current + 1; i <= target; i++) {
|
||||||
@@ -160,5 +314,9 @@ module.exports = {
|
|||||||
needsIntermediatePatches,
|
needsIntermediatePatches,
|
||||||
computeFileChecksum,
|
computeFileChecksum,
|
||||||
validateChecksum,
|
validateChecksum,
|
||||||
getInstalledClientVersion
|
getInstalledClientVersion,
|
||||||
|
fetchNewAPI,
|
||||||
|
getLatestVersionFromNewAPI,
|
||||||
|
getPWRUrlFromNewAPI,
|
||||||
|
extractVersionNumber
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ const ORIGINAL_DOMAIN = 'hytale.com';
|
|||||||
const MIN_DOMAIN_LENGTH = 4;
|
const MIN_DOMAIN_LENGTH = 4;
|
||||||
const MAX_DOMAIN_LENGTH = 16;
|
const MAX_DOMAIN_LENGTH = 16;
|
||||||
|
|
||||||
|
// DualAuth ByteBuddy Agent (runtime class transformation, no JAR modification)
|
||||||
|
const DUALAUTH_AGENT_URL = 'https://github.com/sanasol/hytale-auth-server/releases/latest/download/dualauth-agent.jar';
|
||||||
|
const DUALAUTH_AGENT_FILENAME = 'dualauth-agent.jar';
|
||||||
|
|
||||||
function getTargetDomain() {
|
function getTargetDomain() {
|
||||||
if (process.env.HYTALE_AUTH_DOMAIN) {
|
if (process.env.HYTALE_AUTH_DOMAIN) {
|
||||||
return process.env.HYTALE_AUTH_DOMAIN;
|
return process.env.HYTALE_AUTH_DOMAIN;
|
||||||
@@ -23,7 +27,7 @@ const DEFAULT_NEW_DOMAIN = 'auth.sanasol.ws';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Patches HytaleClient binary to replace hytale.com with custom domain
|
* Patches HytaleClient binary to replace hytale.com with custom domain
|
||||||
* Server patching is done via pre-patched JAR download from CDN
|
* Server auth is handled by DualAuth ByteBuddy Agent (-javaagent: flag)
|
||||||
*
|
*
|
||||||
* Supports domains from 4 to 16 characters:
|
* Supports domains from 4 to 16 characters:
|
||||||
* - All F2P traffic routes to single endpoint: https://{domain} (no subdomains)
|
* - All F2P traffic routes to single endpoint: https://{domain} (no subdomains)
|
||||||
@@ -494,211 +498,95 @@ class ClientPatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if server JAR contains DualAuth classes (was patched)
|
* Get the path to the DualAuth Agent JAR in a directory
|
||||||
*/
|
*/
|
||||||
serverJarContainsDualAuth(serverPath) {
|
getAgentPath(dir) {
|
||||||
try {
|
return path.join(dir, DUALAUTH_AGENT_FILENAME);
|
||||||
const data = fs.readFileSync(serverPath);
|
|
||||||
// Check for DualAuthContext class signature in JAR
|
|
||||||
const signature = Buffer.from('DualAuthContext', 'utf8');
|
|
||||||
return data.includes(signature);
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate downloaded file is not corrupt/partial
|
* Download DualAuth ByteBuddy Agent (replaces old pre-patched JAR approach)
|
||||||
* Server JAR should be at least 50MB
|
* The agent provides runtime class transformation via -javaagent: flag
|
||||||
|
* No server JAR modification needed - original JAR stays pristine
|
||||||
*/
|
*/
|
||||||
validateServerJarSize(serverPath) {
|
async ensureAgentAvailable(serverDir, progressCallback) {
|
||||||
try {
|
const agentPath = this.getAgentPath(serverDir);
|
||||||
const stats = fs.statSync(serverPath);
|
|
||||||
const minSize = 50 * 1024 * 1024; // 50MB minimum
|
|
||||||
if (stats.size < minSize) {
|
|
||||||
console.error(` Downloaded JAR too small: ${(stats.size / 1024 / 1024).toFixed(2)} MB (expected >50MB)`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
console.log(` Downloaded size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
console.log('=== DualAuth Agent (ByteBuddy) ===');
|
||||||
* Patch server JAR by downloading pre-patched version from CDN
|
console.log(`Target: ${agentPath}`);
|
||||||
*/
|
|
||||||
async patchServer(serverPath, progressCallback, branch = 'release') {
|
|
||||||
const newDomain = this.getNewDomain();
|
|
||||||
|
|
||||||
console.log('=== Server Patcher (Pre-patched Download) ===');
|
// Check if agent already exists and is valid
|
||||||
console.log(`Target: ${serverPath}`);
|
if (fs.existsSync(agentPath)) {
|
||||||
console.log(`Branch: ${branch}`);
|
|
||||||
console.log(`Domain: ${newDomain}`);
|
|
||||||
|
|
||||||
if (!fs.existsSync(serverPath)) {
|
|
||||||
const error = `Server JAR not found: ${serverPath}`;
|
|
||||||
console.error(error);
|
|
||||||
return { success: false, error };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if already patched
|
|
||||||
const patchFlagFile = serverPath + '.dualauth_patched';
|
|
||||||
let needsRestore = false;
|
|
||||||
|
|
||||||
if (fs.existsSync(patchFlagFile)) {
|
|
||||||
try {
|
try {
|
||||||
const flagData = JSON.parse(fs.readFileSync(patchFlagFile, 'utf8'));
|
const stats = fs.statSync(agentPath);
|
||||||
if (flagData.domain === newDomain && flagData.branch === branch) {
|
if (stats.size > 1024) {
|
||||||
// Verify JAR actually contains DualAuth classes (game may have auto-updated)
|
console.log(`DualAuth Agent present (${(stats.size / 1024).toFixed(0)} KB)`);
|
||||||
if (this.serverJarContainsDualAuth(serverPath)) {
|
if (progressCallback) progressCallback('DualAuth Agent ready', 100);
|
||||||
console.log(`Server already patched for ${newDomain} (${branch}), skipping`);
|
return { success: true, agentPath, alreadyExists: true };
|
||||||
if (progressCallback) progressCallback('Server already patched', 100);
|
|
||||||
return { success: true, alreadyPatched: true };
|
|
||||||
} else {
|
|
||||||
console.log(' Flag exists but JAR not patched (was auto-updated?), will re-download...');
|
|
||||||
// Delete stale flag file
|
|
||||||
try { fs.unlinkSync(patchFlagFile); } catch (e) { /* ignore */ }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(`Server patched for "${flagData.domain}" (${flagData.branch}), need to change to "${newDomain}" (${branch})`);
|
|
||||||
needsRestore = true;
|
|
||||||
}
|
}
|
||||||
|
// File exists but too small - corrupt, re-download
|
||||||
|
console.log('Agent file appears corrupt, re-downloading...');
|
||||||
|
fs.unlinkSync(agentPath);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Flag file corrupt, re-patch
|
console.warn('Could not check agent file:', e.message);
|
||||||
console.log(' Flag file corrupt, will re-download');
|
|
||||||
try { fs.unlinkSync(patchFlagFile); } catch (e) { /* ignore */ }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore backup if patched for different domain
|
// Download agent from GitHub releases
|
||||||
if (needsRestore) {
|
if (progressCallback) progressCallback('Downloading DualAuth Agent...', 20);
|
||||||
const backupPath = serverPath + '.original';
|
console.log(`Downloading from: ${DUALAUTH_AGENT_URL}`);
|
||||||
if (fs.existsSync(backupPath)) {
|
|
||||||
if (progressCallback) progressCallback('Restoring original for domain change...', 5);
|
|
||||||
console.log('Restoring original JAR from backup for re-patching...');
|
|
||||||
fs.copyFileSync(backupPath, serverPath);
|
|
||||||
if (fs.existsSync(patchFlagFile)) {
|
|
||||||
fs.unlinkSync(patchFlagFile);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn(' No backup found to restore - will download fresh patched JAR');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create backup
|
|
||||||
if (progressCallback) progressCallback('Creating backup...', 10);
|
|
||||||
console.log('Creating backup...');
|
|
||||||
const backupResult = this.backupClient(serverPath);
|
|
||||||
if (!backupResult) {
|
|
||||||
console.warn(' Could not create backup - proceeding without backup');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only support standard domain (auth.sanasol.ws) via pre-patched download
|
|
||||||
if (newDomain !== 'auth.sanasol.ws' && newDomain !== 'sanasol.ws') {
|
|
||||||
console.error(`Domain "${newDomain}" requires DualAuthPatcher - only auth.sanasol.ws is supported via pre-patched download`);
|
|
||||||
return { success: false, error: `Unsupported domain: ${newDomain}. Only auth.sanasol.ws is supported.` };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download pre-patched JAR
|
|
||||||
if (progressCallback) progressCallback('Downloading patched server JAR...', 30);
|
|
||||||
console.log('Downloading pre-patched HytaleServer.jar...');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let url;
|
// Ensure server directory exists
|
||||||
if (branch === 'pre-release') {
|
if (!fs.existsSync(serverDir)) {
|
||||||
url = 'https://patcher.authbp.xyz/download/patched_prerelease';
|
fs.mkdirSync(serverDir, { recursive: true });
|
||||||
console.log(' Using pre-release patched server from:', url);
|
|
||||||
} else {
|
|
||||||
url = 'https://patcher.authbp.xyz/download/patched_release';
|
|
||||||
console.log(' Using release patched server from:', url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = fs.createWriteStream(serverPath);
|
const tmpPath = agentPath + '.tmp';
|
||||||
let totalSize = 0;
|
const file = fs.createWriteStream(tmpPath);
|
||||||
let downloaded = 0;
|
|
||||||
|
|
||||||
const stream = await smartDownloadStream(url, (chunk, downloadedBytes, total) => {
|
const stream = await smartDownloadStream(DUALAUTH_AGENT_URL, (chunk, downloadedBytes, total) => {
|
||||||
downloaded = downloadedBytes;
|
if (progressCallback && total) {
|
||||||
totalSize = total;
|
const percent = 20 + Math.floor((downloadedBytes / total) * 70);
|
||||||
if (progressCallback && totalSize) {
|
progressCallback(`Downloading agent... ${(downloadedBytes / 1024).toFixed(0)} KB`, percent);
|
||||||
const percent = 30 + Math.floor((downloaded / totalSize) * 60);
|
|
||||||
progressCallback(`Downloading... ${(downloaded / 1024 / 1024).toFixed(2)} MB`, percent);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.pipe(file);
|
stream.pipe(file);
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
file.on('finish', () => {
|
file.on('finish', () => { file.close(); resolve(); });
|
||||||
file.close();
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
file.on('error', reject);
|
file.on('error', reject);
|
||||||
stream.on('error', reject);
|
stream.on('error', reject);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(' Download successful');
|
// Verify download
|
||||||
|
const stats = fs.statSync(tmpPath);
|
||||||
// Verify downloaded JAR size and contents
|
if (stats.size < 1024) {
|
||||||
if (progressCallback) progressCallback('Verifying downloaded JAR...', 95);
|
fs.unlinkSync(tmpPath);
|
||||||
|
const error = 'Downloaded agent too small (corrupt or failed download)';
|
||||||
if (!this.validateServerJarSize(serverPath)) {
|
console.error(error);
|
||||||
console.error('Downloaded JAR appears corrupt or incomplete');
|
return { success: false, error };
|
||||||
|
|
||||||
// Restore backup on verification failure
|
|
||||||
const backupPath = serverPath + '.original';
|
|
||||||
if (fs.existsSync(backupPath)) {
|
|
||||||
fs.copyFileSync(backupPath, serverPath);
|
|
||||||
console.log('Restored backup after verification failure');
|
|
||||||
}
|
|
||||||
|
|
||||||
return { success: false, error: 'Downloaded JAR verification failed - file too small (corrupt/partial download)' };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.serverJarContainsDualAuth(serverPath)) {
|
// Atomic move
|
||||||
console.error('Downloaded JAR does not contain DualAuth classes - invalid or corrupt download');
|
if (fs.existsSync(agentPath)) {
|
||||||
|
fs.unlinkSync(agentPath);
|
||||||
// Restore backup on verification failure
|
|
||||||
const backupPath = serverPath + '.original';
|
|
||||||
if (fs.existsSync(backupPath)) {
|
|
||||||
fs.copyFileSync(backupPath, serverPath);
|
|
||||||
console.log('Restored backup after verification failure');
|
|
||||||
}
|
|
||||||
|
|
||||||
return { success: false, error: 'Downloaded JAR verification failed - missing DualAuth classes' };
|
|
||||||
}
|
}
|
||||||
console.log(' Verification successful - DualAuth classes present');
|
fs.renameSync(tmpPath, agentPath);
|
||||||
|
|
||||||
// Mark as patched
|
console.log(`DualAuth Agent downloaded (${(stats.size / 1024).toFixed(0)} KB)`);
|
||||||
const sourceUrl = branch === 'pre-release'
|
if (progressCallback) progressCallback('DualAuth Agent ready', 100);
|
||||||
? 'https://patcher.authbp.xyz/download/patched_prerelease'
|
return { success: true, agentPath };
|
||||||
: 'https://patcher.authbp.xyz/download/patched_release';
|
|
||||||
|
|
||||||
fs.writeFileSync(patchFlagFile, JSON.stringify({
|
|
||||||
domain: newDomain,
|
|
||||||
branch: branch,
|
|
||||||
patchedAt: new Date().toISOString(),
|
|
||||||
patcher: 'PrePatchedDownload',
|
|
||||||
source: sourceUrl
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (progressCallback) progressCallback('Server patching complete', 100);
|
|
||||||
console.log('=== Server Patching Complete ===');
|
|
||||||
return { success: true, patchCount: 1 };
|
|
||||||
|
|
||||||
} catch (downloadError) {
|
} catch (downloadError) {
|
||||||
console.error(`Failed to download patched JAR: ${downloadError.message}`);
|
console.error(`Failed to download DualAuth Agent: ${downloadError.message}`);
|
||||||
|
// Clean up temp file
|
||||||
// Restore backup on failure
|
const tmpPath = agentPath + '.tmp';
|
||||||
const backupPath = serverPath + '.original';
|
if (fs.existsSync(tmpPath)) {
|
||||||
if (fs.existsSync(backupPath)) {
|
try { fs.unlinkSync(tmpPath); } catch (e) { /* ignore */ }
|
||||||
fs.copyFileSync(backupPath, serverPath);
|
|
||||||
console.log('Restored backup after download failure');
|
|
||||||
}
|
}
|
||||||
|
return { success: false, error: downloadError.message };
|
||||||
return { success: false, error: `Failed to download patched server: ${downloadError.message}` };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -743,12 +631,12 @@ class ClientPatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure both client and server are patched before launching
|
* Ensure client is patched and DualAuth Agent is available before launching
|
||||||
*/
|
*/
|
||||||
async ensureClientPatched(gameDir, progressCallback, javaPath = null, branch = 'release') {
|
async ensureClientPatched(gameDir, progressCallback, javaPath = null, branch = 'release') {
|
||||||
const results = {
|
const results = {
|
||||||
client: null,
|
client: null,
|
||||||
server: null,
|
agent: null,
|
||||||
success: true
|
success: true
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -765,22 +653,23 @@ class ClientPatcher {
|
|||||||
results.client = { success: false, error: 'Client binary not found' };
|
results.client = { success: false, error: 'Client binary not found' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverPath = this.findServerPath(gameDir);
|
// Download DualAuth ByteBuddy Agent (runtime patching, no JAR modification)
|
||||||
if (serverPath) {
|
const serverDir = path.join(gameDir, 'Server');
|
||||||
if (progressCallback) progressCallback('Patching server JAR...', 50);
|
if (fs.existsSync(serverDir)) {
|
||||||
results.server = await this.patchServer(serverPath, (msg, pct) => {
|
if (progressCallback) progressCallback('Checking DualAuth Agent...', 50);
|
||||||
|
results.agent = await this.ensureAgentAvailable(serverDir, (msg, pct) => {
|
||||||
if (progressCallback) {
|
if (progressCallback) {
|
||||||
progressCallback(`Server: ${msg}`, pct ? 50 + pct / 2 : null);
|
progressCallback(`Agent: ${msg}`, pct ? 50 + pct / 2 : null);
|
||||||
}
|
}
|
||||||
}, branch);
|
});
|
||||||
} else {
|
} else {
|
||||||
console.warn('Could not find HytaleServer.jar');
|
console.warn('Server directory not found, skipping agent download');
|
||||||
results.server = { success: false, error: 'Server JAR not found' };
|
results.agent = { success: true, skipped: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
results.success = (results.client && results.client.success) || (results.server && results.server.success);
|
results.success = (results.client && results.client.success) || (results.agent && results.agent.success);
|
||||||
results.alreadyPatched = (results.client && results.client.alreadyPatched) && (results.server && results.server.alreadyPatched);
|
results.alreadyPatched = (results.client && results.client.alreadyPatched) && (results.agent && results.agent.alreadyExists);
|
||||||
results.patchCount = (results.client ? results.client.patchCount || 0 : 0) + (results.server ? results.server.patchCount || 0 : 0);
|
results.patchCount = results.client ? results.client.patchCount || 0 : 0;
|
||||||
|
|
||||||
if (progressCallback) progressCallback('Patching complete', 100);
|
if (progressCallback) progressCallback('Patching complete', 100);
|
||||||
|
|
||||||
|
|||||||
18
build/entitlements.mac.plist
Normal file
18
build/entitlements.mac.plist
Normal 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>
|
||||||
8
main.js
8
main.js
@@ -627,7 +627,7 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath,
|
|||||||
console.log('[Main] Processing Butler error with retry context');
|
console.log('[Main] Processing Butler error with retry context');
|
||||||
errorData.retryData = {
|
errorData.retryData = {
|
||||||
branch: error.branch || 'release',
|
branch: error.branch || 'release',
|
||||||
fileName: error.fileName || '7.pwr',
|
fileName: error.fileName || 'v8',
|
||||||
cacheDir: error.cacheDir
|
cacheDir: error.cacheDir
|
||||||
};
|
};
|
||||||
errorData.canRetry = error.canRetry !== false;
|
errorData.canRetry = error.canRetry !== false;
|
||||||
@@ -647,7 +647,7 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath,
|
|||||||
console.log('[Main] Processing generic error, creating default retry data');
|
console.log('[Main] Processing generic error, creating default retry data');
|
||||||
errorData.retryData = {
|
errorData.retryData = {
|
||||||
branch: 'release',
|
branch: 'release',
|
||||||
fileName: '7.pwr'
|
fileName: 'v8'
|
||||||
};
|
};
|
||||||
// For generic errors, assume it's retryable unless specified
|
// For generic errors, assume it's retryable unless specified
|
||||||
errorData.canRetry = error.canRetry !== false;
|
errorData.canRetry = error.canRetry !== false;
|
||||||
@@ -887,7 +887,7 @@ ipcMain.handle('retry-download', async (event, retryData) => {
|
|||||||
console.log('[IPC] Invalid retry data, using PWR defaults');
|
console.log('[IPC] Invalid retry data, using PWR defaults');
|
||||||
retryData = {
|
retryData = {
|
||||||
branch: 'release',
|
branch: 'release',
|
||||||
fileName: '7.pwr'
|
fileName: 'v8'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -921,7 +921,7 @@ ipcMain.handle('retry-download', async (event, retryData) => {
|
|||||||
} :
|
} :
|
||||||
{
|
{
|
||||||
branch: retryData?.branch || 'release',
|
branch: retryData?.branch || 'release',
|
||||||
fileName: retryData?.fileName || '7.pwr',
|
fileName: retryData?.fileName || 'v8',
|
||||||
cacheDir: retryData?.cacheDir
|
cacheDir: retryData?.cacheDir
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hytale-f2p-launcher",
|
"name": "hytale-f2p-launcher",
|
||||||
"version": "2.2.1",
|
"version": "2.2.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",
|
||||||
@@ -104,7 +104,12 @@
|
|||||||
],
|
],
|
||||||
"icon": "build/icon.icns",
|
"icon": "build/icon.icns",
|
||||||
"artifactName": "${name}_${version}_${arch}.${ext}",
|
"artifactName": "${name}_${version}_${arch}.${ext}",
|
||||||
"category": "public.app-category.games"
|
"category": "public.app-category.games",
|
||||||
|
"hardenedRuntime": true,
|
||||||
|
"gatekeeperAssess": false,
|
||||||
|
"entitlements": "build/entitlements.mac.plist",
|
||||||
|
"entitlementsInherit": "build/entitlements.mac.plist",
|
||||||
|
"notarize": true
|
||||||
},
|
},
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"oneClick": false,
|
"oneClick": false,
|
||||||
|
|||||||
Reference in New Issue
Block a user