227 Commits
v2 ... main

Author SHA1 Message Date
AMIAY
46ed4cb2be Add project status and community links to README 2026-02-19 09:21:09 +01:00
AMIAY
6369fd7669 Remove all files 2026-02-19 04:05:00 +01:00
AMIAY
244fec0c77 Update Discord link for downloading latest version 2026-02-11 22:27:09 +01:00
AMIAY
ebc11de314 Revise problem reporting instructions in README (#284)
Updated warning message regarding Discord support and GitHub repo issues.
2026-02-11 22:25:58 +01:00
AMIAY
90448e200e Revise problem reporting instructions in README
Updated warning message regarding Discord support and GitHub repo issues.
2026-02-11 22:25:36 +01:00
AMIAY
e5b44341f1 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>
2026-02-11 11:13:14 +01:00
AMIAY
ae6a7db80a Update Discord link in README.md 2026-02-09 11:41:22 +01:00
AMIAY
48395fbff3 Merge pull request #278 from amiayweb/amiayweb-patch-1
Update Discord link for community help
2026-02-09 11:30:28 +01:00
AMIAY
aae90a72e8 Update Discord link for community help 2026-02-09 11:30:09 +01:00
AMIAY
a6c61aef68 Merge pull request #259 from amiayweb/fix/release-2-2-1-env 2026-02-02 08:05:00 +01:00
Fazri Gading
31653a37a7 fix: release v2.2.1 add virtual .env file creation in release workflow
Added steps to create a virtual .env file for different platforms during the release process.
2026-02-02 14:44:33 +08:00
Fazri Gading
1cb08f029a Release Stable Build v2.2.1 (#258)
* fix: resolve cross-platform EPERM permissions errors

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

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

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

* fix: missing pacman builds

* prepare release for 2.1.1

minor fix for EPERM error permission

* prepare release 2.1.1

minor fix EPERM permission error

* prepare release 2.1.1

* Update README.md Windows Prequisites for ARM64 builds

* fix: remove broken symlink after detected

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

* fix: isbrokenlink should be true to remove the symlink

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

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

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

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

* aur: add proper VCS (-git) PKGBUILD

created clean VCS-based PKGBUILD following arch packaging conventions.

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

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

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

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

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

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

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

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

* Update README.md

adds information for Arch build

* Update README.md

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

* Update PKGBUILD

* Update PKGBUILD-git

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

* Polish language support (#195)

* Update support_request.yml

Added hardware specification

* Update bug_report.yml

Add logs textfield to bug report

* chore: add changelog in README.md

* fix screenshot input in feature_request.yml

* add hardware spec input in bug_report.yml

* fix: PKGBUILD pkgname variable fix

* userdata migration [need review from other OS]

* french translate

* Add German and Swedish translations

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

* Update README.md

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

* chore: add downloads counter in README.md

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

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

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

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

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

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

* Update support_request.yml

* fix: improve update system UX and macOS compatibility

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

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

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

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

* Add Russian language support

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

* chore: drafting documentation on SERVER.md

* Some updates in Russian language localization file

* fix

* Update ru.json

* Fixed Java runtime name and fixed typo

* fixed untranslated place

* Update ru.json

* Update ru.json

* Update ru.json

* Update ru.json

* Update ru.json

* fix: timeout getLatestClient 

fixes #138

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

* fix: change default release version to 7.pwr

* fix: change version release to 7.pwr

* docs: Add comprehensive troubleshooting guide (#209)

Add TROUBLESHOOTING.md with solutions for common issues including:

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

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

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

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

* Update German (Germany) localization

* Update Español (España) localization

* Update French (France) localization

* Update Polish (Poland) localization

* Update Portuguese (Brazil) localization

* Update Russian (Russia) localization

* Update Swedish (Sweden) localization

* Update Turkish (Turkey) localization

* Update language codes, names and alphabetical in i18n system

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

* Fix PKGBUILD-git

* Fix PKGBUILD

* delete cache after installation

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

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

* Update installation subtitle

* chore: update quickstart link in README.md

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

* added featured server list from api

* Add Featured Servers page to GUI

* Update Discord invite URL in client patcher

* Add differential update system

* Remove launcher chat and add Discord popup

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

* fix: upgrade tar to ^7.5.6 version

* fix: re-add universal arch for mac

* fix: upgrade electron/rebuild to 4.0.3

* fix: removed override tar version

* fix: pkgbuild version to 2.1.2

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

* feat: add Indonesian language translation

* fix: GPU preference hint to Laptop-only

* feat: create two columns for settings page

* Add Discord invite link to rpc

* docs: add recordings form, fix OS list

* Release v2.2.0

* Release v2.2.0

* Release v2.2.0

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

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

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

* fix: gamescope steam deck issue fixes #186 hopefully

* Support branch selection for server patching

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

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

* fix: removed arm64 flags

* fix: redo package.json arch

* update package-lock.json

* Update release.yml

* chore: sync package-lock with package.json

* fix: reorder fedora libzstd paths to first iteration

* feat: enhance gpu detection, drafting

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

* fix: comprehensive UUID/username persistence bug fixes

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

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

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

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

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

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

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

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

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

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

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

* docs: move UUID_BUGS_FIX_PLAN.md to docs folder

* docs: update UUID_BUGS_FIX_PLAN with complete fix details

---------

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

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

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

* chore: update discord link

* chore: insert contact link in CODE_OF_CONDUCT.md

* fix: missing version text on launcher

* chore: update quickstart button link to header

* chore: update discord link and give warning quickstart

* chore revise online play hosting instructions in README

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

* Fix Turkish translations in tr-TR.json

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

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

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

* fix: discord RPC destroy error if not connected

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

* docs: add analysis on ghost process and launcher cleanup

* revert generateLocalTokens, wrong analysis on game launching issue

* revert add deps for generateLocalTokens

* Add proxy client and route downloads through it

* fix: Prevent JAR file corruption during proxy downloads

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

* Improve featured servers layout with Discord integration

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

* package version to 2.2.1

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

* fix: add game_running_marker to prevent duplicate launches

* Add smart proxy with direct-fallback and logging

* fix: remove duplicate check

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

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

* fix: redact proxy_url and remove timed out emoji

* Prepare Release v2.2.1

* docs: enhance bug report template with placeholders and options

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

* chore revise windows prerequisites and changelog

Updated prerequisites and changelog for version 2.2.1.

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

* chore: fix release notes for v2.2.1

---------

Co-authored-by: TalesAmaral <57869141+TalesAmaral@users.noreply.github.com>
Co-authored-by: walti0 <95646872+walti0@users.noreply.github.com>
Co-authored-by: AMIAY <letudiantenrap.collab@gmail.com>
Co-authored-by: sanasol <mail@sanasol.ws>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Terromur <79866197+Terromur@users.noreply.github.com>
Co-authored-by: Zakhar Smokotov <zaharb840@gmail.com>
Co-authored-by: xSamiVS <samtaiebc@gmail.com>
Co-authored-by: MetricsLite <66024355+MetricsLite@users.noreply.github.com>
2026-02-02 05:58:56 +01:00
AMIAY
6a66ed831c Merge pull request #251 from amiayweb/fix/arch-linux-build-fail
fix: missing encoding package dependency in package-lock
2026-01-31 16:34:43 +01:00
Fazri Gading
78bb10588d fix: missing encoding package dependency in package-lock 2026-01-31 23:31:30 +08:00
AMIAY
d27663a1ce revert 2026-01-31 16:23:35 +01:00
AMIAY
2db7d606bd Add 'encoding' dependency for test 2026-01-31 16:22:02 +01:00
Fazri Gading
39c12c0591 Release v2.2.0 - Fixed Arch failed build for Linux (#250)
* 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

---------

Co-authored-by: TalesAmaral <57869141+TalesAmaral@users.noreply.github.com>
Co-authored-by: walti0 <95646872+walti0@users.noreply.github.com>
Co-authored-by: AMIAY <letudiantenrap.collab@gmail.com>
Co-authored-by: sanasol <mail@sanasol.ws>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Terromur <79866197+Terromur@users.noreply.github.com>
Co-authored-by: Zakhar Smokotov <zaharb840@gmail.com>
Co-authored-by: xSamiVS <samtaiebc@gmail.com>
2026-01-31 23:08:40 +08:00
Fazri Gading
094bb938fc Release v2.2.0 - Fix AppImage Build (#247) 2026-01-31 15:26:44 +01:00
Fazri Gading
9c9b71bd4c Merge pull request #244 from amiayweb/fix/x64-appimage-issue
fix: preserves arch x64 on linux target for #242
2026-01-31 18:53:23 +08:00
Fazri Gading
c4bb15ce91 fix: preserves arch x64 on linux target for #242 2026-01-31 18:19:39 +08:00
Fazri Gading
8719cd3138 fix: revert to previous release.yml (#238) 2026-01-31 00:09:22 +01:00
Fazri Gading
611d436085 fix: change ownership back to the runner user (#237) 2026-01-30 23:55:17 +01:00
Fazri Gading
d5cc0868e9 Release Build v2.2.0 (#236)
* 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

---------

Co-authored-by: TalesAmaral <57869141+TalesAmaral@users.noreply.github.com>
Co-authored-by: walti0 <95646872+walti0@users.noreply.github.com>
Co-authored-by: AMIAY <letudiantenrap.collab@gmail.com>
Co-authored-by: sanasol <mail@sanasol.ws>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Terromur <79866197+Terromur@users.noreply.github.com>
Co-authored-by: Zakhar Smokotov <zaharb840@gmail.com>
Co-authored-by: xSamiVS <samtaiebc@gmail.com>
2026-01-30 23:19:46 +01:00
Fazri Gading
da186333cb Release Stable Build v2.1.1 (#198)
* 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

---------

Co-authored-by: TalesAmaral <57869141+TalesAmaral@users.noreply.github.com>
Co-authored-by: walti0 <95646872+walti0@users.noreply.github.com>
2026-01-27 04:11:10 +08:00
Fazri Gading
ae375f9b6e Release Stable Build v2.1.1 (#197)
* 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

* 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)

* add hardware specification in support_request.yml

* Add logs text field in bug_report.yml

* chore: add changelog in README.md

* fix screenshot input in feature_request.yml

* add hardware spec input in bug_report.yml

---------

Co-authored-by: TalesAmaral <57869141+TalesAmaral@users.noreply.github.com>
Co-authored-by: walti0 <95646872+walti0@users.noreply.github.com>
2026-01-27 03:44:24 +08:00
Fazri Gading
faf21b830b Merge pull request #196 from amiayweb/develop
Release v2.1.1: fix EPERM error and add ArchLinux package (.pkg.tar.zst)
2026-01-27 02:29:35 +08:00
Fazri Gading
6f10b1390d Release v2.1.1: fix EPERM error and add ArchLinux package (.tar.zst) (#185)
* 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
2026-01-26 14:14:26 +08:00
Fazri Gading
c4a32ce1e0 Release v2.1.1: Fix EPERM cross-platform error (#183)
* 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

* 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
2026-01-26 12:29:14 +08:00
AMIAY
7a9a67d8e8 Merge pull request #180 from amiayweb/develop
Release version v2.1.0
2026-01-25 23:24:19 +01:00
Fazri Gading
4c854953fe add support link on README 2026-01-26 06:02:35 +08:00
Fazri Gading
4cd0539ce3 Merge pull request #172 from Rahul-Sahani04/develop
feat: Add option to toggle hardware acceleration for launcher. Issue #170
2026-01-26 05:10:24 +08:00
Fazri Gading
fa2d451f90 Merge branch 'develop' into develop 2026-01-26 05:09:36 +08:00
Fazri Gading
a4faa7138c Merge branch 'develop' of https://github.com/amiayweb/Hytale-F2P into develop 2026-01-26 05:05:31 +08:00
Fazri Gading
d285dc7517 fix: async-await for toggle and cleanup discordRPC 2026-01-26 05:05:25 +08:00
Fazri Gading
ceadd69eea Update release.yml: changed heads ref 2026-01-26 05:03:31 +08:00
Fazri Gading
6f0dd27c1d Update README.md header 2026-01-26 04:59:41 +08:00
Fazri Gading
ba95187ee6 fix: err_bad_request code 416 due to file size matched remote size, updated timeout to 15mins 2026-01-26 04:58:02 +08:00
AMIAY
9e54e07b22 R2 cdn added 2026-01-25 21:26:46 +01:00
AMIAY
a8e7e57c86 Merge pull request #178 from fazrigading/develop
fix: discordRPC error due to incorrect type value, update dotenv in package-lock
2026-01-25 21:01:53 +01:00
Terromur
d1ab58d51b Merge pull request #177 from amiayweb/fix-icon
Fix icon
2026-01-26 00:30:09 +05:00
Fazri Gading
8781025df9 chore: update readme.md, todo changelog 2026-01-26 03:28:30 +08:00
Terromur
81c52e9507 Fix icon 2026-01-26 00:28:09 +05:00
Fazri Gading
45314620e4 fix: Discord ID int to str, duplicate run of cleanupDiscordRPC function, and dismiss setTimeout on discordRPC destroy 2026-01-26 02:16:33 +08:00
Fazri Gading
43d5d20351 chore: disable patcher log to reduce logging length 2026-01-26 01:52:54 +08:00
Fazri Gading
72b4e0cba8 Merge branch 'develop' of https://github.com/amiayweb/Hytale-F2P into develop 2026-01-26 00:27:25 +08:00
AMIAY
25d5131a7b Merge branch 'develop' of https://github.com/amiayweb/Hytale-F2P into develop 2026-01-25 17:02:06 +01:00
AMIAY
ad3c73563d temp jar patcher 2026-01-25 17:02:02 +01:00
Rahul-Sahani04
f0f19f690f feat: Add option to toggle hardware acceleration for launcher #170 2026-01-25 21:08:47 +05:30
Fazri Gading
b27860a655 fix: adds back dotenv in package-lock.json 2026-01-25 23:25:33 +08:00
Fazri Gading
9788d0e496 Merge branch 'develop' of https://github.com/amiayweb/Hytale-F2P into develop 2026-01-25 22:38:10 +08:00
Terromur
2a5780c2d4 Fix icon 2026-01-25 18:37:57 +05:00
AMIAY
8263b3f99b update pkgbuild 2026-01-25 14:37:00 +01:00
AMIAY
db56ef1624 onUpdateError fix 2026-01-25 14:31:17 +01:00
AMIAY
35f900d6ab Update fileManager.js 2026-01-25 14:15:19 +01:00
AMIAY
e1d1383ab7 Update package.json 2026-01-25 14:02:58 +01:00
AMIAY
8326deddb1 Merge branch 'develop' of https://github.com/amiayweb/Hytale-F2P into develop 2026-01-25 13:31:38 +01:00
AMIAY
b11b78f7dc trying 2026-01-25 13:31:08 +01:00
Fazri Gading
62a2d76e4a Merge branch 'develop' of https://github.com/amiayweb/Hytale-F2P into develop 2026-01-25 20:22:49 +08:00
Terromur
0ca8b4e02f Deleting garbage envs 2026-01-25 17:17:36 +05:00
Fazri Gading
c6a9d0ae07 merge last two commits to develop (#165)
* Add correct auto-detect version and commit

If a person uses PKGBUILD, it will automatically determine the latest version and commit.

* Remove maintainer and change to npm ci

---------

Co-authored-by: Terromur <79866197+Terromur@users.noreply.github.com>
2026-01-25 20:03:54 +08:00
Terromur
f438d6c8e0 Update PKGBUILD
Set png file from GUI/icon.png to 256x256 resolution for compatibility support.
2026-01-25 16:41:48 +05:00
Fazri Gading
f07e4a2004 Merge pull request #166 from amiayweb/main
merge last two commits from main
2026-01-25 18:37:47 +08:00
Fazri Gading
131580d3ba merge last two commits to develop (#165)
* Add correct auto-detect version and commit

If a person uses PKGBUILD, it will automatically determine the latest version and commit.

* Remove maintainer and change to npm ci

---------

Co-authored-by: Terromur <79866197+Terromur@users.noreply.github.com>
2026-01-25 18:36:40 +08:00
Fazri Gading
084347db03 prepare release for v2.1.0 (#164)
* fix: update tar to 7.5.6

* test: release on main branch using tag

* chore: remove previous release branch part

* fix: add deps for bsdtar

* fix: fix build tar.zst for arch

* fix: missing npm ci on release yml

* fix: remove pacman package json

* fix: revert tar version

* fix: revert tar in package-lock.json

* Update release.yml
2026-01-25 18:35:45 +08:00
Fazri Gading
589c5b457f Update release.yml 2026-01-25 18:34:01 +08:00
Fazri Gading
790d4d3f29 fix: revert tar in package-lock.json 2026-01-25 18:07:34 +08:00
Fazri Gading
52313910dc fix: revert tar version 2026-01-25 18:05:43 +08:00
Fazri Gading
a3f4d8e9d8 fix: remove pacman package json 2026-01-25 18:01:45 +08:00
Fazri Gading
86d617a4d3 fix: missing npm ci on release yml 2026-01-25 17:40:26 +08:00
Fazri Gading
0a97ac95fc fix: fix build tar.zst for arch 2026-01-25 17:27:55 +08:00
Fazri Gading
b94b45681b fix: add deps for bsdtar 2026-01-25 17:14:34 +08:00
Terromur
4086612e9d Remove maintainer and change to npm ci 2026-01-25 14:12:20 +05:00
Terromur
e7fca5a4c7 Add correct auto-detect version and commit
If a person uses PKGBUILD, it will automatically determine the latest version and commit.
2026-01-25 13:52:00 +05:00
Fazri Gading
e7bd20a1ec chore: remove previous release branch part 2026-01-25 16:50:28 +08:00
Fazri Gading
151b017653 test: release on main branch using tag 2026-01-25 16:46:26 +08:00
Fazri Gading
da3e14c434 fix: update tar to 7.5.6 2026-01-25 16:45:39 +08:00
Fazri Gading
6302734eeb fix: add missing icons for all platforms 2026-01-25 16:14:21 +08:00
Fazri Gading
07191860be chore: more detailed gitignore 2026-01-25 16:13:27 +08:00
Fazri Gading
2f767f191e chore: delete unused get-env-var functions 2026-01-25 15:31:29 +08:00
Fazri Gading
de9c7d81f5 fix: replace pacman build with pkg.tar.zst and remove its deps, changed CF and Discord key mode 2026-01-25 14:45:45 +08:00
Fazri Gading
4c3277392e merge branch 'main' (lost 4 commits) into develop 2026-01-25 13:54:15 +08:00
sanasol
f287cb55b9 Merge remote-tracking branch 'origin/develop' into develop 2026-01-25 01:27:29 +01:00
sanasol
d87db04653 feat(patcher): Implement DualAuth patcher with enhanced server patching
- Introduce DualAuthPatcher with support for hybrid authentication
- Update default auth domain to `auth.sanasol.ws`
- Integrate Java detection and bundled JRE handling for patcher execution
- Add server patch flag for avoiding redundant patching
- Automate DualAuthPatcher setup: download, compile, and execute with dependencies
- Enhance patching logic for extended logging and modularity
2026-01-25 01:27:19 +01:00
AMIAY
67aa41aefe fix 2026-01-25 01:03:49 +01:00
AMIAY
bd1dd146a9 Merge branch 'develop' of https://github.com/amiayweb/Hytale-F2P into develop 2026-01-25 00:19:14 +01:00
AMIAY
c8d7707b70 need test - electron updater 2026-01-25 00:19:11 +01:00
xSamiVS
127c38f98b Update Spanish locale, add missing CurseForge API Key translation, implement Turkish translation, and fix contributor links comma. (#135)
* Update Spanish locale and add missing CurseForge API Key translation

- Updated the Spanish locale name to distinguish between multiple locale types.
- Added missing translation for the page indicating the missing CurseForge API Key.

* Implemented Turkish locale support

* Add Turkish locale to available languages

* Add missing comma in contributor links

* Correct Portuguese language name in available languages

---------

Co-authored-by: Fazri Gading <fazrigading@gmail.com>
2026-01-25 06:01:42 +08:00
AMIAY
f974d9c767 Update package-lock.json 2026-01-24 22:33:18 +01:00
Fazri Gading
7e4a45e466 Merge branch 'release' into develop 2026-01-25 05:24:08 +08:00
Fazri Gading
ea21fb15d6 fix: JRE retry button 2026-01-25 05:18:22 +08:00
sanasol
3d54cea9e7 feat(patcher): Support variable-length domains (4-16 chars)
- Add support for domains from 4 to 16 characters
- Domains <= 10 chars: direct replacement, subdomains stripped
- Domains 11-16 chars: split mode (first 6 chars -> subdomain prefix)
- Add length-prefixed byte format encoding for client binary
- Verify binary contents when checking if already patched
- Detect file updates and archive old backups with timestamps
- Fallback to legacy UTF-16LE format for older binaries
- Update patcher version to 2.0.0

Based on patching approach from Mollomm1/Hytale-EMULATOR

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 21:11:20 +01:00
AMIAY
9f43a32779 fix hardcoded branch + pre-release/release issue 2026-01-24 19:01:34 +01:00
Fazri Gading
9c8a12f25c fix: lastProgressTime variable init 2026-01-25 01:58:54 +08:00
Fazri Gading
a7d0523186 feat: auto-resume download process & auto-retry if disconnected (#143) 2026-01-25 01:36:20 +08:00
AMIAY
a6f716c61b removed getInstalledClientVersion 2026-01-24 13:44:56 +01:00
AMIAY
ca8ed171d1 removed overlay installation 2026-01-24 13:32:36 +01:00
AMIAY
679799c074 fix installation branch 2026-01-24 12:33:42 +01:00
AMIAY
87b168dd4c fix 2026-01-24 12:22:15 +01:00
AMIAY
679f065e24 delete updateManager 2026-01-24 12:09:54 +01:00
AMIAY
ecae7d2ee5 update 2026-01-24 12:06:45 +01:00
AMIAY
fa50fec34d Merge branch 'develop' of https://github.com/amiayweb/Hytale-F2P into develop 2026-01-24 12:05:24 +01:00
AMIAY
c900129c1f fix patch 2026-01-24 12:05:10 +01:00
AMIAY
6b75858515 Delete .env.example 2026-01-24 12:03:07 +01:00
AMIAY
61bcdf9413 curseforge api 2026-01-24 12:01:37 +01:00
AMIAY
411d7d8aaf fix 2026-01-24 12:00:01 +01:00
Fazri Gading
8a87c7c4d9 docs: add and adjust more info on readme 2026-01-24 16:35:42 +08:00
Fazri Gading
34f93e962b docs: adjusted github template & add new contributors name (#133)
* docs: add new contributors to the list

* docs: fix template and new adjustments

* docs: fix github template & add new contributors

* removed config.yml
2026-01-24 15:57:20 +08:00
AMIAY
d8393543df fixing 2026-01-24 02:49:21 +01:00
Terromur
b62ffc126e Update PKGBUILD 2026-01-24 05:44:51 +05:00
AMIAY
3579d82776 fix (to try) 2026-01-24 01:41:09 +01:00
Terromur
b5c6c38d92 Update Hytale-F2P.desktop 2026-01-24 05:05:02 +05:00
Terromur
f932462578 Update PKGBUILD 2026-01-24 05:04:43 +05:00
Fazri Gading
e005b4293b docs: add footnotes and fixes 2026-01-24 04:10:54 +08:00
Fazri Gading
e43897f816 Draft Enhancement & Documentation for README.md
Needs some work on few TODO. Contributors PR are welcome.
2026-01-24 03:21:25 +08:00
AMIAY
3983fdb1bc pre-release & release game version [to check] 2026-01-23 17:54:57 +01:00
Fazri Gading
b46ce93af7 Release Stable Build v2.0.11 (#119)
* Add electron-updater auto-update support

- Install electron-updater package
- Configure GitHub releases publish settings
- Create AppUpdater class with full update lifecycle
- Integrate auto-update into main.js
- Add comprehensive documentation (AUTO-UPDATES.md, TESTING-UPDATES.md)
- Set up dev-app-update.yml for testing

* Add cache clearing documentation for electron-updater

- Introduced CLEAR-UPDATE-CACHE.md to guide users on clearing the electron-updater cache across macOS, Windows, and Linux.
- Added programmatic method for cache clearing in JavaScript.
- Enhanced update handling in main.js and preload.js to support new update events.
- Updated GUI styles for download buttons and progress indicators in update.js and style.css.

* Update auto-update UI and configuration

- Fix version display (newVersion field)
- Add download progress bar with real-time updates
- Reorder buttons: Install & Restart (primary), Manually Download (secondary)
- Update dev-app-update.yml to point to fork
- Update package.json version to 2.0.2

* Add installation effects and draggable progress bar

Introduces animated installation effects overlay and makes the progress bar draggable. Adds maximize window support, improves window controls styling, and enforces a single app instance. Removes the unused Skins page and related translations. Refines  various UI details for a more polished user experience.

* Adjust news card aspect ratio and add Play tab style

Set a default aspect ratio for .news-card and add a specific style for the LATEST NEWS section in the Play tab to override the aspect ratio and use full height.

* Add splash screen to launcher startup

Introduced a new splash screen (splash.html) and updated main.js to display it on startup before loading the main window. The splash screen is shown for 2.5 seconds as a placeholder for future loading logic, improving user experience during application launch.

* Display launcher version in UI

Adds a version display element to the bottom right of the UI, fetching the version from package.json via a new IPC handler. Updates main.js, preload.js, and ui.js to support retrieving and displaying the version, and adds relevant styles in style.css.

* Custom Mod loading fix (#92)

* feat: Add Repair Game functionality including UserData backup and cache clearing

* feat: Add In-App Logs Viewer and Logs Folder shortcut

* feat: Add Open Logs feature

* disable dev tools

* Fix Settings UI

* Implement custom mod loading, autoimport, auto repair

* Fixed Custom Mod loading issues and merge issues

* feat: Externalize sensitive API keys and Discord client ID into environment variables using dotenv.

* feat(mods): add profile-based mod management and auto-repair

* feat: add 'Close launcher on game start' option and improve app termination behavior (#93)

* update main branch to release/v2.0.2b (#86)

* add more linux pkgs, create auto-release and pre-release feature for Github Actions

* removed package-lock from gitignore

* update .gitignore for local build

* add package-lock.json to maintain stability development

* update version to 2.0.2b also add deps for rpm and arch

* update 2.0.2b: add arm64 support, product and executable name, maintainers; remove snap;

* update 2.0.2b: add latest.yml for win & linux, arm64 support; remove snap

* fix release build naming

* Prepare release v2.0.2b

* feat: add 'Close launcher on game start' option and improve app termination behavior

- Added 'Close launcher on game start' setting in GUI and backend.
- Implemented automatic app quit after game launch if setting is enabled.
- Added Cmd+Q (Mac) and Ctrl+Q/Alt+F4 (Win/Linux) shortcuts to quit the app.
- Updated 'window-close' handler to fully quit the app instead of just closing the window.
- Added i18n support for the new setting in English, Spanish, and Portuguese.

---------

Co-authored-by: Fazri Gading <fazrigading@gmail.com>
Co-authored-by: Arnav Singh <hi.arnavsingh3@gmail.com>

* Update publish config to point to chasem-dev fork

* Fix Linux metadata files in workflow and improve error handling

* Bump version to 2.0.5

* Bump version to 2.0.6

* Fix update popup showing for same version - add version comparison checks

* Bump version to 2.0.7

* Fix SHA512 checksum mismatch handling - clear cache and retry automatically

* Bump version to 2.0.8

* Bump version to 2.0.9

* Fix: Use explicit latest-linux.yml to prevent yml file collision

The glob pattern latest*.yml was matching both latest-linux.yml AND
latest.yml from the Linux build, causing the Windows latest.yml to be
overwritten with incorrect checksums.

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

* Bump version to 2.0.10

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

* Fix: Remove portable target to fix SHA512 checksum mismatch

The portable and nsis targets both produced x64.exe files with the same
name, causing one to overwrite the other. The latest.yml contained the
checksum from one build while the actual file was from the other build.

Removed portable target - nsis installer is sufficient.
Bump version to 2.0.11

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

* Remove outdated documentation files related to auto-updates, build instructions, and testing updates. Update `dev-app-update.yml` and `package.json` to reflect the correct GitHub owner. This cleanup streamlines the project and ensures accurate configuration for future updates.

* Add semantic versioning policy documentation - numerical versions only

* Update package-lock.json to include new dependencies and versions, enhancing project stability and compatibility.

* fixed imgur restriction for UK

* fix: adds EGL env var to detect installed NVIDIA GPU

* Update release.yml

* patch v2.0.11-beta: fix env issue in GA release, warn Intel Mac users, add com templates. (#115)

* fix: throw error for Intel Mac user
* docs: first draft of issue and PR template
* fix: env of curseforge API key and discord client ID

* implemented late patch should be in #115

* Final patch for release.yml v2.0.11

---------

Co-authored-by: chasem-dev <myers.a.chase@gmail.com>
Co-authored-by: AMIAY <letudiantenrap.collab@gmail.com>
Co-authored-by: Rahul Sahani <110347707+Rahul-Sahani04@users.noreply.github.com>
Co-authored-by: Arnav Singh <72737311+ArnavSingh77@users.noreply.github.com>
Co-authored-by: Arnav Singh <hi.arnavsingh3@gmail.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-24 00:07:59 +08:00
Fazri Gading
2a87acfe46 Final patch for release.yml v2.0.11 2026-01-23 22:46:31 +08:00
Fazri Gading
a2e2d5e5fd implemented late patch should be in #115 2026-01-23 22:10:35 +08:00
Fazri Gading
34143d9872 patch v2.0.11-beta: fix env issue in GA release, warn Intel Mac users, add com templates. (#115)
* fix: throw error for Intel Mac user
* docs: first draft of issue and PR template
* fix: env of curseforge API key and discord client ID
2026-01-23 21:30:27 +08:00
Fazri Gading
08c2218cf8 Merge branch 'feature/community-templates' into develop 2026-01-23 21:14:20 +08:00
Fazri Gading
032418b7f7 Merge branch 'fix/x86-mac-pwr-warning' into develop 2026-01-23 21:11:46 +08:00
Fazri Gading
fc05725a43 Merge pull request #114 from amiayweb/env-test
Merge v2.0.11-beta Build 2: env test & readme and server guide fix
2026-01-23 16:32:42 +08:00
Fazri Gading
203a56879f Update release.yml 2026-01-23 16:20:40 +08:00
Fazri Gading
7a0065ea2b Merge branch 'develop' of https://github.com/amiayweb/Hytale-F2P into develop 2026-01-23 14:48:23 +08:00
Fazri Gading
ac08eb50ff Merge #112: update README.md and merge PR #105
Update README.md and merge PR #105 to develop branch
2026-01-23 14:37:06 +08:00
Fazri Gading
70fe4203ef Merge branch 'develop' of https://github.com/amiayweb/Hytale-F2P into develop 2026-01-23 13:11:43 +08:00
Fazri Gading
f433120084 fix: adds EGL env var to detect installed NVIDIA GPU 2026-01-23 13:11:19 +08:00
AMIAY
f4099acbed Merge pull request #104 from chasem-dev/v2.0.11
V2.0.11 - Auto Updater
2026-01-23 02:04:15 +01:00
Fazri Gading
da843257c1 docs: first draft of issue and PR template 2026-01-23 06:00:36 +08:00
Fazri Gading
e4576042be fix: throw error for Intel Mac user 2026-01-23 05:57:21 +08:00
AMIAY
71974e031f Merge pull request #105 from gaurav-null/readme-tailscale-multiplayer
updated SERVER.md to add platform specification on radmin and add Tai…
2026-01-22 22:45:48 +01:00
guarav-null
5bd52f09db updated SERVER.md to add platform specification on radmin and add Tailscale guide for max compability 2026-01-23 03:05:29 +05:30
AMIAY
1ba6b22b74 fixed imgur restriction for UK 2026-01-22 20:40:28 +01:00
chasem-dev
a1bc88b754 Update package-lock.json to include new dependencies and versions, enhancing project stability and compatibility. 2026-01-22 14:07:04 -05:00
chasem-dev
24c2371b50 Add semantic versioning policy documentation - numerical versions only 2026-01-22 13:10:01 -05:00
chasem-dev
4c6e1a616e Merge upstream/develop into v2.0.11 - sync with main repository 2026-01-22 13:07:34 -05:00
chasem-dev
b54eb4e834 Remove outdated documentation files related to auto-updates, build instructions, and testing updates. Update dev-app-update.yml and package.json to reflect the correct GitHub owner. This cleanup streamlines the project and ensures accurate configuration for future updates. 2026-01-22 13:05:34 -05:00
chasem-dev
a1c74e4175 Fix: Remove portable target to fix SHA512 checksum mismatch
The portable and nsis targets both produced x64.exe files with the same
name, causing one to overwrite the other. The latest.yml contained the
checksum from one build while the actual file was from the other build.

Removed portable target - nsis installer is sufficient.
Bump version to 2.0.11

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-22 12:50:10 -05:00
chasem-dev
260e6c1126 Bump version to 2.0.10
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-22 11:53:34 -05:00
chasem-dev
6eb628559b Fix: Use explicit latest-linux.yml to prevent yml file collision
The glob pattern latest*.yml was matching both latest-linux.yml AND
latest.yml from the Linux build, causing the Windows latest.yml to be
overwritten with incorrect checksums.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-22 11:53:15 -05:00
chasem-dev
052b5dc7dc Bump version to 2.0.9 2026-01-22 11:30:59 -05:00
chasem-dev
7e9b5046df Bump version to 2.0.8 2026-01-22 11:13:01 -05:00
chasem-dev
204d6b21f6 Fix SHA512 checksum mismatch handling - clear cache and retry automatically 2026-01-22 11:12:59 -05:00
chasem-dev
740d516cfe Bump version to 2.0.7 2026-01-22 10:36:19 -05:00
chasem-dev
ce052add0d Fix update popup showing for same version - add version comparison checks 2026-01-22 10:36:18 -05:00
chasem-dev
d7a904c641 Bump version to 2.0.6 2026-01-22 10:18:33 -05:00
chasem-dev
d5d2f60c97 Bump version to 2.0.5 2026-01-22 10:18:30 -05:00
chasem-dev
61433bfeea Fix Linux metadata files in workflow and improve error handling 2026-01-22 10:18:28 -05:00
chasem-dev
9eb5d1759c Update publish config to point to chasem-dev fork 2026-01-22 09:11:10 -05:00
Arnav Singh
68d697576a feat: add 'Close launcher on game start' option and improve app termination behavior (#93)
* update main branch to release/v2.0.2b (#86)

* add more linux pkgs, create auto-release and pre-release feature for Github Actions

* removed package-lock from gitignore

* update .gitignore for local build

* add package-lock.json to maintain stability development

* update version to 2.0.2b also add deps for rpm and arch

* update 2.0.2b: add arm64 support, product and executable name, maintainers; remove snap;

* update 2.0.2b: add latest.yml for win & linux, arm64 support; remove snap

* fix release build naming

* Prepare release v2.0.2b

* feat: add 'Close launcher on game start' option and improve app termination behavior

- Added 'Close launcher on game start' setting in GUI and backend.
- Implemented automatic app quit after game launch if setting is enabled.
- Added Cmd+Q (Mac) and Ctrl+Q/Alt+F4 (Win/Linux) shortcuts to quit the app.
- Updated 'window-close' handler to fully quit the app instead of just closing the window.
- Added i18n support for the new setting in English, Spanish, and Portuguese.

---------

Co-authored-by: Fazri Gading <fazrigading@gmail.com>
Co-authored-by: Arnav Singh <hi.arnavsingh3@gmail.com>
2026-01-22 18:11:16 +08:00
Rahul Sahani
a8da559e93 Custom Mod loading fix (#92)
* feat: Add Repair Game functionality including UserData backup and cache clearing

* feat: Add In-App Logs Viewer and Logs Folder shortcut

* feat: Add Open Logs feature

* disable dev tools

* Fix Settings UI

* Implement custom mod loading, autoimport, auto repair

* Fixed Custom Mod loading issues and merge issues

* feat: Externalize sensitive API keys and Discord client ID into environment variables using dotenv.

* feat(mods): add profile-based mod management and auto-repair
2026-01-22 18:01:57 +08:00
AMIAY
828d05ca33 Update README.md 2026-01-22 10:46:09 +01:00
AMIAY
75f9403888 Display launcher version in UI
Adds a version display element to the bottom right of the UI, fetching the version from package.json via a new IPC handler. Updates main.js, preload.js, and ui.js to support retrieving and displaying the version, and adds relevant styles in style.css.
2026-01-22 08:07:32 +01:00
AMIAY
b61c94d348 Add splash screen to launcher startup
Introduced a new splash screen (splash.html) and updated main.js to display it on startup before loading the main window. The splash screen is shown for 2.5 seconds as a placeholder for future loading logic, improving user experience during application launch.
2026-01-22 07:59:27 +01:00
AMIAY
c0109575d6 Adjust news card aspect ratio and add Play tab style
Set a default aspect ratio for .news-card and add a specific style for the LATEST NEWS section in the Play tab to override the aspect ratio and use full height.
2026-01-22 07:43:39 +01:00
AMIAY
2a024b61dd Add installation effects and draggable progress bar
Introduces animated installation effects overlay and makes the progress bar draggable. Adds maximize window support, improves window controls styling, and enforces a single app instance. Removes the unused Skins page and related translations. Refines  various UI details for a more polished user experience.
2026-01-22 07:41:35 +01:00
chasem-dev
1c39e8e4c6 Update auto-update UI and configuration
- Fix version display (newVersion field)
- Add download progress bar with real-time updates
- Reorder buttons: Install & Restart (primary), Manually Download (secondary)
- Update dev-app-update.yml to point to fork
- Update package.json version to 2.0.2
2026-01-22 00:26:46 -05:00
chasem-dev
753bd4fd61 Add cache clearing documentation for electron-updater
- Introduced CLEAR-UPDATE-CACHE.md to guide users on clearing the electron-updater cache across macOS, Windows, and Linux.
- Added programmatic method for cache clearing in JavaScript.
- Enhanced update handling in main.js and preload.js to support new update events.
- Updated GUI styles for download buttons and progress indicators in update.js and style.css.
2026-01-22 00:26:01 -05:00
chasem-dev
cefb4c5575 Add electron-updater auto-update support
- Install electron-updater package
- Configure GitHub releases publish settings
- Create AppUpdater class with full update lifecycle
- Integrate auto-update into main.js
- Add comprehensive documentation (AUTO-UPDATES.md, TESTING-UPDATES.md)
- Set up dev-app-update.yml for testing
2026-01-22 00:03:02 -05:00
Fazri Gading
1c779e0e41 fix v2.0.2b changelog in README.md file 2026-01-22 02:18:17 +08:00
Fazri Gading
bb474fe233 Update README.md v2.0.2b changelog 2026-01-22 02:17:41 +08:00
Fazri Gading
917f5f455b Merge branch 'develop' 2026-01-22 02:15:36 +08:00
Fazri Gading
1dd42bdc79 Merge pull request #88 from amiayweb/release
merge v2.0.2b release files to main branch
2026-01-22 02:01:01 +08:00
Fazri Gading
7cfe3edd32 Merge branch 'main' into release 2026-01-22 01:44:53 +08:00
Fazri Gading
eb22758ab9 adds v2.0.2b changelog 2026-01-22 00:53:10 +08:00
Fazri Gading
42fd51486a feat: auto-detect GPU for Windows and MacOS (#87) 2026-01-21 22:13:47 +08:00
xSamiVS
9ef05e8322 Added internationalization support (i18n) (#74)
* - Implemented i18n.
- Updated UI elements to use localized strings for various messages and confirmations.
- Added language selection functionality in settings with appropriate event handling.
- Created English localization file with translations for all new strings.
- Updated backend to save and load user-selected language preferences.

* Add Spanish localization for the GUI

* Add Portuguese (Brazil) localization for the GUI

* update main branch to release/v2.0.2b (#86)

* add more linux pkgs, create auto-release and pre-release feature for Github Actions

* removed package-lock from gitignore

* update .gitignore for local build

* add package-lock.json to maintain stability development

* update version to 2.0.2b also add deps for rpm and arch

* update 2.0.2b: add arm64 support, product and executable name, maintainers; remove snap;

* update 2.0.2b: add latest.yml for win & linux, arm64 support; remove snap

* fix release build naming

* Prepare release v2.0.2b

* Update localization for game repair and GPU settings

Added new localization entries for game repair and GPU preferences.

* Update spanish localization for game repair and GPU settings

* Update portuguese (brazil) for game repair and GPU settings

* Update localization for system logs in English, Spanish, and Portuguese

---------

Co-authored-by: Fazri Gading <fazrigading@gmail.com>
2026-01-21 21:41:12 +08:00
Fazri Gading
4ac12e0e24 fix release version name 2026-01-21 21:01:32 +08:00
AMIAY
72a151930e Patch Discord invite URLs in client binary 2026-01-21 13:30:49 +01:00
Fazri Gading
a9644b8c64 remove v2 suffix from name and set consistent artifact name 2026-01-21 20:12:14 +08:00
Fazri Gading
9fc238e103 update executable name to be consistent with product name 2026-01-21 19:53:23 +08:00
Fazri Gading
b62e94a415 fix: package.json Module Not Found in 'Get version' step 2026-01-21 19:53:00 +08:00
Fazri Gading
3e82e8fadb add libarchive-tools for bsdtar in release workflow 2026-01-21 18:38:49 +08:00
Fazri Gading
a355133ccf update main branch to release/v2.0.2b (#86)
* add more linux pkgs, create auto-release and pre-release feature for Github Actions

* removed package-lock from gitignore

* update .gitignore for local build

* add package-lock.json to maintain stability development

* update version to 2.0.2b also add deps for rpm and arch

* update 2.0.2b: add arm64 support, product and executable name, maintainers; remove snap;

* update 2.0.2b: add latest.yml for win & linux, arm64 support; remove snap

* fix release build naming

* Prepare release v2.0.2b
2026-01-21 16:39:18 +08:00
Fazri Gading
ff5acb5278 fix release build naming 2026-01-21 16:24:47 +08:00
Fazri Gading
5d75ca80aa update 2.0.2b: add latest.yml for win & linux, arm64 support; remove snap 2026-01-21 16:07:57 +08:00
Fazri Gading
281aa6fcde update 2.0.2b: add arm64 support, product and executable name, maintainers; remove snap; 2026-01-21 16:07:12 +08:00
Fazri Gading
158d0af820 update version to 2.0.2b also add deps for rpm and arch 2026-01-21 16:05:20 +08:00
Fazri Gading
503f304704 add package-lock.json to maintain stability development 2026-01-21 16:04:40 +08:00
Fazri Gading
234e1e1008 update .gitignore for local build 2026-01-21 16:04:17 +08:00
Fazri Gading
d002e831cd removed package-lock from gitignore 2026-01-21 07:42:03 +08:00
Fazri Gading
048f2040f1 add more linux pkgs, create auto-release and pre-release feature for Github Actions 2026-01-21 06:41:25 +08:00
Rahul Sahani
b05aeef66d feat: Add Repair Game button, UserData backup and cache clearing (#79)
* feat: Add Repair Game functionality including UserData backup and cache clearing

* feat: Add In-App Logs Viewer and Logs Folder shortcut

* feat: Add Open Logs feature

* disable dev tools

* Fix Settings UI

* fix reorder settings section in index.html

relocated sections in settings from most used to least:
1. game options (playername, opengamedir, repair, GPUpreference)
2. player uuid management
3. discord integration rich presence
4. custom java path

---------

Co-authored-by: Fazri Gading <super.fai700@gmail.com>
2026-01-21 06:27:33 +08:00
Fazri Gading
30265549cf feat: Implement GPU preference system with auto-detection and UI enhancements (#82)
* feat: add GPU options for launcher

- Add GPU preference setting (Auto/Integrated/Dedicated)
- Implement Linux GPU selection with DRI_PRIME and NVIDIA env vars
- Add GPU detection using Electron's app.getGPUInfo()
- Update settings UI with GPU preference dropdown
- Integrate GPU preference into game launch process

* feat: detailed GPU info in auto-detection feature on startup

* add gpudetection feature to launcher backend

* feat: detailed GPU info in auto-detection feature on startup

* feat: auto-detect dedicated GPU on hybrid laptops (iGPU+dGPU)

* add fallbacks to and option to use integrated GPU

* add package-lock and fix deps version

* changed 'Nvidia' string to 'NVIDIA'

* fix: selecting `dedicated` option while using nvidia GPU did not set its specific env variables

* remove unused `CONFIG_FILE` variable on launcher core modules

* fix: duplicated save-load gpu detection functions

* move game option settings to the top, while custom java to the bottom

* fix: settings-header margin-bottom from 3rem to 1rem and supress line-clamp warning
2026-01-21 05:54:17 +08:00
Terromur
5a3efba1d6 Update discription 2026-01-21 01:43:40 +05:00
Terromur
479f24e86f Update PKGBUILD 2026-01-21 01:28:53 +05:00
Fazri Gading
611b7a7084 Merge branch 'main' into main 2026-01-21 04:05:02 +08:00
Terromur
e472435927 Update PKGBUILD 2026-01-21 00:34:52 +05:00
AMIAY
f40d0105df Update package-lock.json 2026-01-20 18:35:26 +01:00
AMIAY
96db9adf68 Update package.json 2026-01-20 18:34:57 +01:00
Fazri Gading
a0f49f126c add libarchive-tools dep for pacman package 2026-01-21 00:53:47 +08:00
Fazri Gading
f8333c09cd Update package.json 2026-01-21 00:43:29 +08:00
Fazri Gading
bcf0326763 Merge branch 'main' of https://github.com/fazrigading/Hytale-F2P 2026-01-21 00:31:20 +08:00
Fazri Gading
0a5c3db710 add 3 package formats for Linux, descriptions, and update launcher version to 2.0.2 2026-01-21 00:27:11 +08:00
Fazri Gading
905a9d754c Merge GPU preference feature branch to main branch version 2.0.2 for testing (#3)
* modernized UI for GPU Preference option

* feat: auto-detect dedicated GPU on hybrid laptops (iGPU+dGPU)

* feat: detailed GPU info in auto-detection feature on startup

* feat: add GPU options for launcher

- Add GPU preference setting (Auto/Integrated/Dedicated)
- Implement Linux GPU selection with DRI_PRIME and NVIDIA env vars
- Add GPU detection using Electron's app.getGPUInfo()
- Update settings UI with GPU preference dropdown
- Integrate GPU preference into game launch process

* feat: auto-detect dedicated GPU on hybrid laptops (iGPU+dGPU)

* added fallbacks to and option to use integrated GPU.

* add package-lock and fix deps version

* changed 'Nvidia' string to 'NVIDIA'

* fix: selecting `dedicated` option while using nvidia GPU did not set its specific env variables

* remove unused `CONFIG_FILE` variable on launcher core modules

* fix: duplicated save-load gpu detection functions

* move game option settings to the top, while custom java to the bottom

* fix: settings-header margin-bottom from 3rem to 1rem and supress line-clamp warning
2026-01-20 23:45:38 +08:00
Fazri Gading
99f032e9ab Merge branch 'main' of https://github.com/amiayweb/Hytale-F2P 2026-01-20 19:37:54 +08:00
AMIAY
5e3506a9ac Update README.md 2026-01-20 09:57:21 +01:00
AMIAY
f2a05d2079 Update release.yml 2026-01-20 09:49:11 +01:00
AMIAY
cece338609 Update release.yml 2026-01-20 09:48:15 +01:00
AMIAY
261582a882 Update release.yml 2026-01-20 09:47:34 +01:00
AMIAY
0f0f0fa308 Update release.yml 2026-01-20 09:46:33 +01:00
AMIAY
5e6a07f0a6 Allow manual trigger for release workflow 2026-01-20 09:42:16 +01:00
AMIAY
713377fdc6 Merge pull request #71 from Rahul-Sahani04/feature/profile-system
Profile System & Mod Loading Fixes
2026-01-20 09:33:20 +01:00
Fazri Gading
2671a59f38 Revert "add save-load GPU preference handler"
This reverts commit b957a76aede8f9425b0a283d1444a32f55baa95a.
2026-01-20 16:26:28 +08:00
Fazri Gading
8e9af9c768 add save-load GPU preference handler 2026-01-20 16:26:28 +08:00
Rahul Sahani
e962a8880d Delete PR_DESCRIPTION.md 2026-01-20 11:57:23 +05:30
Rahul Sahani
c2d5536dd0 Improve comments in profileManager.js
Refactor comments for clarity and conciseness in profileManager.js.
2026-01-20 11:57:01 +05:30
Rahul Sahani
727be2ca5c Improve comments in saveModsToConfig function
Refactor comments in saveModsToConfig function for clarity.
2026-01-20 11:55:18 +05:30
Rahul-Sahani04
64892c81e9 Profile System & Mod Loading Fixes
Added a full profile system and fixed a few critical mod loading issues.

What changed

Profiles — Implemented proper profile management (create, switch, delete). Each profile now has its own isolated mod list.

Mod Isolation — Fixed ModManager so mods are strictly scoped to the active profile. Browsing and installing only affects the selected profile.

Critical Fix — Fixed a path bug where mods were being saved to ~/AppData/Local on macOS (Windows path) instead of ~/Library/Application Support. Mods now save to the correct location and load correctly in-game.

Stability — Added an auto-sync step before every launch to make sure the physical mods folder always matches the active profile.

UI — Added a profile selector dropdown and a profile management modal.
2026-01-20 11:52:36 +05:30
AMIAY
fffc730788 Merge pull request #68 from Terromur/main
Update PKGBUILD
2026-01-19 23:36:24 +01:00
Terromur
c475ec7879 Update Hytale-F2P.desktop 2026-01-20 03:34:51 +05:00
Terromur
7efa0d07b0 Update PKGBUILD 2026-01-20 03:34:38 +05:00
AMIAY
21f8527ed4 update 2.0.2 2026-01-19 23:15:29 +01:00
AMIAY
300616ba82 Update SERVER.md 2026-01-19 01:55:33 +01:00
AMIAY
9fdd6f1f44 Update BUILD.md 2026-01-19 00:26:32 +01:00
AMIAY
f0b2342c71 Merge pull request #47 from GreenKod/refactor-split-file
refactor: split main file into smaller modules for better maintainabi…
2026-01-18 21:06:09 +01:00
greenkod
7dbc900338 refactor: split main file into smaller modules for better maintainability 2026-01-18 21:29:37 +03:00
AMIAY
bc31f58c9c Update print statement from 'Hello' to 'Goodbye' 2026-01-18 16:07:31 +01:00
AMIAY
7e5a1577a3 Update and rename styles.css to style.css 2026-01-18 16:07:19 +01:00
AMIAY
84f0c0ba71 Delete GUI/style.css 2026-01-18 16:07:10 +01:00
AMIAY
7ede6c2f27 Add files via upload 2026-01-18 15:42:24 +01:00
AMIAY
be1a24a992 Add files via upload 2026-01-18 15:42:23 +01:00
AMIAY
9a751958b0 Add files via upload 2026-01-18 15:42:22 +01:00
AMIAY
9fcf603e08 Add files via upload 2026-01-18 15:42:20 +01:00
AMIAY
4bc1661587 Update package.json with new configuration 2026-01-18 14:41:51 +01:00
AMIAY
512a53aee7 Enhance README with additional resources and links
Updated instructions to include links to Releases and SERVER.md.
2026-01-18 14:41:22 +01:00
AMIAY
cabb5a57d2 Add Hytale F2P Server Setup Guide
Added a comprehensive setup guide for the Hytale F2P server, including server file setup, network configuration with Radmin VPN, RAM allocation recommendations, and tips for optimal performance.
2026-01-18 14:40:21 +01:00
28 changed files with 4 additions and 10669 deletions

61
.github/README1.md vendored
View File

@@ -1,61 +0,0 @@
# GitHub Actions
## Build and Release Workflow
The `release.yml` workflow automatically builds the launcher for all platforms.
### Triggers
| Trigger | Builds | Creates Release |
|---------|--------|-----------------|
| Push to `main` | Yes | No |
| Push tag `v*` | Yes | Yes |
| Manual dispatch | Yes | No |
### Platforms
All builds run in parallel:
- **Linux** (ubuntu-latest): AppImage, deb
- **Windows** (windows-latest): NSIS installer, portable exe
- **macOS** (macos-latest): Universal DMG (Intel + Apple Silicon)
### Creating a Release
1. Update version in `package.json`
2. Commit and push to `main`
3. Create and push a version tag:
```bash
git tag v2.0.1
git push origin v2.0.1
```
The workflow will:
1. Build all platforms in parallel
2. Upload artifacts to GitHub Release
3. Generate release notes automatically
### Build Artifacts
After each build, artifacts are available in the Actions tab for 90 days:
- `linux-builds`: `.AppImage`, `.deb`
- `windows-builds`: `.exe`
- `macos-builds`: `.dmg`, `.zip`, `latest-mac.yml`
### Local Development
Build locally for your platform:
```bash
npm run build:linux
npm run build:win
npm run build:mac
```
Or build all platforms (requires appropriate OS):
```bash
npm run build:all
```

View File

@@ -1,92 +0,0 @@
name: Build and Release
on:
push:
branches:
- main
tags:
- 'v*'
workflow_dispatch:
jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm install
- run: npm ci
- run: npx electron-builder --linux --publish never
- uses: actions/upload-artifact@v4
with:
name: linux-builds
path: |
dist/*.AppImage
dist/*.deb
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm install
- run: npm ci
- run: npx electron-builder --win --publish never
- uses: actions/upload-artifact@v4
with:
name: windows-builds
path: |
dist/*.exe
build-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm install
- run: npm ci
- run: npx electron-builder --mac --publish never
- uses: actions/upload-artifact@v4
with:
name: macos-builds
path: |
dist/*.dmg
dist/*.zip
dist/latest-mac.yml
release:
needs: [build-linux, build-windows, build-macos]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Display structure of downloaded files
run: ls -R artifacts
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: |
artifacts/linux-builds/*
artifacts/windows-builds/*
artifacts/macos-builds/*
generate_release_notes: true
draft: false
prerelease: false

3
.gitignore vendored
View File

@@ -1,3 +0,0 @@
dist/*
node_modules/*
package-lock.json

View File

@@ -1,54 +0,0 @@
# Build Instructions
## Prerequisites
Install dependencies:
```bash
npm install
```
## Building
### Build for current platform:
```bash
npm run build
```
### Build for specific platform:
**Windows:**
```bash
npm run build:win
```
**Linux:**
```bash
npm run build:linux
```
**macOS:**
```bash
npm run build:mac
```
### Build for all platforms:
```bash
npm run build:all
```
## Output
Built executables will be in the `dist/` directory:
- **Windows**: `Hytale F2P Launcher Setup.exe` (NSIS installer) and `Hytale F2P Launcher.exe` (portable)
- **Linux**: `Hytale F2P Launcher.AppImage` and `Hytale F2P Launcher.deb`
- **macOS**: `Hytale F2P Launcher.dmg` and `Hytale F2P Launcher.zip`
## Notes
- Icons need to be placed in `build/` directory:
- `icon.ico` for Windows
- `icon.png` for Linux
- `icon.icns` for macOS
- To build for macOS on non-Mac systems, you'll need to run it on a Mac or use a CI/CD service

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

View File

@@ -1,423 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hytale F2P Launcher</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body class="bg-black text-white overflow-hidden font-sans select-none" tabindex="-1">
<div class="absolute inset-0 z-0">
<img src="https://i.imgur.com/Visrk66.png" alt="Background" class="w-full h-full object-cover" />
<div class="absolute inset-0 bg-black/60"></div>
<div class="absolute inset-0 bg-[url('data:image/svg+xml,%3Csvg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"%3E%3Cfilter id="noiseFilter"%3E%3CfeTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/%3E%3C/filter%3E%3Crect width="100%25" height="100%25" filter="url(%23noiseFilter)" opacity="0.1"/%3E%3C/svg%3E')] opacity-20"></div>
</div>
<div class="flex w-full h-screen relative z-10">
<nav class="sidebar">
<div class="sidebar-logo">
<img src="./icon.png" alt="Hytale Logo" />
</div>
<div class="sidebar-nav">
<div class="nav-item active" data-page="play">
<i class="fas fa-play"></i>
<span class="nav-tooltip">Play</span>
</div>
<div class="nav-item" data-page="mods">
<i class="fas fa-box"></i>
<span class="nav-tooltip">Mods</span>
</div>
<div class="nav-item" data-page="news">
<i class="fas fa-newspaper"></i>
<span class="nav-tooltip">News</span>
</div>
<div class="nav-item" data-page="chat">
<i class="fas fa-comments"></i>
<span class="nav-tooltip">Players Chat</span>
</div>
<div class="nav-item" data-page="settings">
<i class="fas fa-cog"></i>
<span class="nav-tooltip">Settings</span>
</div>
<div class="nav-item" data-page="skins">
<i class="fas fa-user"></i>
<span class="nav-tooltip">Skins</span>
</div>
</div>
</nav>
<main class="main-content">
<header class="header">
<div id="playersOnlineCounter" class="players-counter">
<i class="fas fa-users"></i>
<span class="counter-label">Players:</span>
<span id="onlineCount" class="counter-value">0</span>
</div>
<div class="window-controls">
<button class="control-btn minimize" onclick="window.electronAPI?.minimizeWindow()">
<i class="fas fa-minus"></i>
</button>
<button class="control-btn close" onclick="window.electronAPI?.closeWindow()">
<i class="fas fa-times"></i>
</button>
</div>
</header>
<div class="game-title-section">
<h1 class="game-title">
HY<span class="title-accent">TALE</span>
</h1>
<div class="game-tags">
<span class="tag">FREE TO PLAY</span>
</div>
</div>
<div class="content-pages">
<div id="install-page" class="page install-page">
<div class="install-content">
<div class="install-header">
<h1 class="install-title">
HYTA<span class="title-accent">LE</span>
</h1>
<p class="install-subtitle">FREE TO PLAY LAUNCHER</p>
</div>
<div class="install-form">
<div class="form-group">
<label class="form-label">Player Name</label>
<input type="text" id="installPlayerName" placeholder="Enter your name" class="form-input" value="Player" />
</div>
<div class="form-group">
<label class="checkbox-group">
<input type="checkbox" id="installCustomCheck" class="custom-checkbox">
<span class="checkbox-label">Custom Installation</span>
</label>
<div id="installCustomOptions" class="custom-options">
<div class="form-subgroup">
<label class="form-label">Installation Folder</label>
<div class="input-with-button">
<input type="text" id="installPath" placeholder="Default location" class="form-input" readonly />
<button onclick="browseInstallPath()" class="browse-btn">
<i class="fas fa-folder-open"></i>
</button>
</div>
</div>
</div>
</div>
<button id="installBtn" class="install-button" onclick="installGame()">
<i class="fas fa-download mr-2"></i>
<span id="installText">INSTALL HYTALE</span>
</button>
</div>
</div>
</div>
<div id="launcher-container" class="launcher-container" style="display: none;">
<div id="play-page" class="page active">
<div class="play-section">
<div class="play-content">
<div class="play-header">
<h2 class="play-title">
<i class="fas fa-play-circle mr-2"></i>
READY TO PLAY
</h2>
<p class="play-subtitle">Launch Hytale and enter the adventure</p>
</div>
<button id="homePlayBtn" class="home-play-button" onclick="launch()">
<i class="fas fa-play"></i>
<span>PLAY HYTALE</span>
</button>
</div>
</div>
<div class="news-section">
<div class="news-header">
<h2 class="news-title">
<i class="fas fa-star mr-2"></i>
LATEST NEWS
</h2>
<button class="view-all-btn" onclick="navigateToPage('news')">
VIEW ALL <i class="fas fa-arrow-right ml-1"></i>
</button>
</div>
<div id="newsGrid" class="news-grid-horizontal"></div>
</div>
</div>
<div id="mods-page" class="page">
<div class="mods-header">
<div class="mods-search-container">
<i class="fas fa-search"></i>
<input type="text" id="modsSearch" placeholder="Search mods..." class="mods-search" />
</div>
<div class="mods-actions">
<button id="myModsBtn" class="mods-btn-primary">
<i class="fas fa-box"></i>
MY MODS
</button>
</div>
</div>
<div id="browseModsList" class="mods-browse-container">
</div>
<div class="mods-pagination">
<button id="prevPage" class="pagination-btn">
<i class="fas fa-chevron-left"></i>
PREVIOUS
</button>
<span class="pagination-info">
Page <span id="currentPage">1</span> of <span id="totalPages">1</span>
</span>
<button id="nextPage" class="pagination-btn">
NEXT
<i class="fas fa-chevron-right"></i>
</button>
</div>
</div>
<div id="news-page" class="page">
<div class="news-header">
<h2 class="news-title">
<i class="fas fa-newspaper mr-2"></i>
ALL NEWS
</h2>
</div>
<div id="allNewsGrid" class="news-grid-full"></div>
</div>
<div id="chat-page" class="page">
<div class="chat-container">
<div class="chat-header">
<h2 class="chat-title">
<i class="fas fa-comments mr-2"></i>
PLAYERS CHAT
</h2>
<div class="chat-online-badge">
<i class="fas fa-circle"></i>
<span id="chatOnlineCount">0</span> online
</div>
</div>
<div class="chat-body">
<div id="chatMessages" class="chat-messages">
</div>
</div>
<div class="chat-footer">
<div class="chat-input-container">
<textarea
id="chatInput"
class="chat-input"
placeholder="Type your message... (Links are automatically censored)"
rows="1"
maxlength="500"
></textarea>
<button id="chatSendBtn" class="chat-send-btn">
<i class="fas fa-paper-plane"></i>
</button>
</div>
<div class="chat-footer-info">
<span class="chat-char-counter" id="chatCharCounter">0/500</span>
<span class="chat-warning-text">
<i class="fas fa-shield-alt"></i>
Secure chat - Links are censored
</span>
</div>
</div>
</div>
</div>
<div id="settings-page" class="page">
<div class="settings-container">
<div class="settings-header">
<h2 class="settings-title">
<i class="fas fa-cog mr-2"></i>
SETTINGS
</h2>
</div>
<div class="settings-content">
<div class="settings-section">
<h3 class="settings-section-title">
<i class="fas fa-coffee"></i>
Java Runtime
</h3>
<div class="settings-option">
<label class="settings-checkbox">
<input type="checkbox" id="customJavaCheck" />
<span class="checkmark"></span>
<div class="checkbox-content">
<div class="checkbox-title">Use Custom Java Path</div>
<div class="checkbox-description">Override the bundled Java runtime with your own installation</div>
</div>
</label>
</div>
<div id="customJavaOptions" class="custom-java-options" style="display: none;">
<div class="settings-input-group">
<label class="settings-input-label">Java Executable Path</label>
<div class="settings-input-with-button">
<input
type="text"
id="customJavaPath"
class="settings-input"
placeholder="Select Java path..."
readonly
/>
<button id="browseJavaBtn" class="settings-browse-btn">
<i class="fas fa-folder-open"></i>
Browse
</button>
</div>
<p class="settings-hint">
<i class="fas fa-info-circle"></i>
Select the Java installation folder (supports Windows, Mac, Linux)
</p>
</div>
</div>
</div>
<div class="settings-section">
<h3 class="settings-section-title">
<i class="fas fa-gamepad"></i>
Game Options
</h3>
<div class="settings-option">
<div class="settings-input-group">
<label class="settings-input-label">Player Name</label>
<input
type="text"
id="settingsPlayerName"
class="settings-input"
placeholder="Enter your player name"
maxlength="16"
/>
<p class="settings-hint">
<i class="fas fa-user"></i>
This name will be used in-game (1-16 characters)
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="skins-page" class="page">
<div class="placeholder-content">
<i class="fas fa-user text-6xl mb-4 text-purple-500"></i>
<h2>Skins</h2>
<p>Skin customization coming soon...</p>
</div>
</div>
</div>
</div>
</main>
</div>
<div id="myModsModal" class="mods-modal">
<div class="mods-modal-content">
<div class="mods-modal-header">
<h2 class="mods-modal-title">
<i class="fas fa-box mr-2"></i>
MY MODS
</h2>
<button id="closeMyModsModal" class="mods-modal-close">
<i class="fas fa-times"></i>
</button>
</div>
<div class="mods-modal-body">
<div id="installedModsList" class="installed-mods-list">
</div>
</div>
</div>
</div>
<div id="progressOverlay" class="progress-overlay" style="display: none;">
<div class="progress-content">
<div class="progress-info">
<span id="progressText">Initializing...</span>
<span id="progressPercent">0%</span>
</div>
<div class="progress-bar-container">
<div id="progressBarFill" class="progress-bar-fill"></div>
</div>
<div class="progress-details">
<span id="progressSpeed"></span>
<span id="progressSize"></span>
</div>
</div>
</div>
<div id="chatUsernameModal" class="chat-username-modal" style="display: none;">
<div class="chat-username-modal-content">
<div class="chat-username-modal-header">
<h2 class="chat-username-modal-title">
<i class="fas fa-comments mr-2"></i>
Join Chat
</h2>
</div>
<div class="chat-username-modal-body">
<p class="chat-username-modal-description">
Choose a username to join the Players Chat
</p>
<div class="chat-username-input-group">
<label for="chatUsernameInput" class="chat-username-label">Username</label>
<input
type="text"
id="chatUsernameInput"
class="chat-username-input"
placeholder="Enter your username..."
maxlength="20"
autocomplete="off"
/>
<span class="chat-username-hint">3-20 characters, letters, numbers, - and _ only</span>
<span id="chatUsernameError" class="chat-username-error"></span>
</div>
</div>
<div class="chat-username-modal-footer">
<button id="chatUsernameCancel" class="chat-username-btn-cancel">
<i class="fas fa-times"></i>
Cancel
</button>
<button id="chatUsernameSubmit" class="chat-username-btn-submit">
<i class="fas fa-check"></i>
Join Chat
</button>
</div>
</div>
</div>
<footer class="fixed bottom-0 left-0 right-0 z-50 bg-black/80 backdrop-blur-sm px-4 py-2">
<div class="flex items-center justify-center text-xs text-gray-400">
<span>Made by <a href="https://github.com/amiayweb" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@amiayweb</a></span>
<span class="mx-2">|</span>
<span>Contributors:
<a href="https://github.com/chasem-dev" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@chasem-dev</a>,
<a href="https://github.com/crimera" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@crimera</a>,
<a href="https://github.com/sanasol" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@sanasol</a>,
<a href="https://github.com/Terromur" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@terromur</a>,
<a href="https://github.com/ericiskoolbeans" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@ericiskoolbeans</a>
</span>
</div>
</footer>
<script type="module" src="js/script.js"></script>
<script type="module" src="js/update.js"></script>
</body>
</html>

View File

@@ -1,358 +0,0 @@
let socket = null;
let isAuthenticated = false;
let messageQueue = [];
let chatUsername = '';
const SOCKET_URL = 'http://3.10.208.30:3001';
const MAX_MESSAGE_LENGTH = 500;
export async function initChat() {
if (window.electronAPI?.loadChatUsername) {
chatUsername = await window.electronAPI.loadChatUsername();
}
if (!chatUsername || chatUsername.trim() === '') {
showUsernameModal();
return;
}
setupChatUI();
await connectToChat();
}
function showUsernameModal() {
const modal = document.getElementById('chatUsernameModal');
if (modal) {
modal.style.display = 'flex';
const input = document.getElementById('chatUsernameInput');
if (input) {
setTimeout(() => input.focus(), 100);
}
}
}
function hideUsernameModal() {
const modal = document.getElementById('chatUsernameModal');
if (modal) {
modal.style.display = 'none';
}
}
async function submitChatUsername() {
const input = document.getElementById('chatUsernameInput');
const errorMsg = document.getElementById('chatUsernameError');
if (!input) return;
const username = input.value.trim();
if (username.length === 0) {
if (errorMsg) errorMsg.textContent = 'Username cannot be empty';
return;
}
if (username.length < 3) {
if (errorMsg) errorMsg.textContent = 'Username must be at least 3 characters';
return;
}
if (username.length > 20) {
if (errorMsg) errorMsg.textContent = 'Username must be 20 characters or less';
return;
}
if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
if (errorMsg) errorMsg.textContent = 'Username can only contain letters, numbers, - and _';
return;
}
chatUsername = username;
if (window.electronAPI?.saveChatUsername) {
await window.electronAPI.saveChatUsername(username);
}
hideUsernameModal();
setupChatUI();
await connectToChat();
}
function setupChatUI() {
const sendBtn = document.getElementById('chatSendBtn');
const chatInput = document.getElementById('chatInput');
const chatMessages = document.getElementById('chatMessages');
if (!sendBtn || !chatInput || !chatMessages) {
console.warn('Chat UI elements not found');
return;
}
sendBtn.addEventListener('click', () => {
sendMessage();
});
chatInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
chatInput.addEventListener('input', () => {
if (chatInput.value.length > MAX_MESSAGE_LENGTH) {
chatInput.value = chatInput.value.substring(0, MAX_MESSAGE_LENGTH);
}
updateCharCounter();
});
updateCharCounter();
}
async function connectToChat() {
try {
if (!window.io) {
await loadSocketIO();
}
const userId = await window.electronAPI?.getUserId();
if (!userId) {
console.error('User ID not available');
addSystemMessage('Error: Could not connect to chat');
return;
}
if (!chatUsername || chatUsername.trim() === '') {
console.error('Chat username not set');
addSystemMessage('Error: Username not set');
return;
}
socket = io(SOCKET_URL, {
transports: ['websocket', 'polling'],
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000
});
socket.on('connect', () => {
console.log('Connected to chat server');
socket.emit('authenticate', { username: chatUsername, userId });
});
socket.on('authenticated', (data) => {
isAuthenticated = true;
addSystemMessage(`Connected as ${data.username}`);
while (messageQueue.length > 0) {
const msg = messageQueue.shift();
socket.emit('send_message', { message: msg });
}
});
socket.on('message', (data) => {
if (data.type === 'system') {
addSystemMessage(data.message);
} else if (data.type === 'user') {
addUserMessage(data.username, data.message, data.timestamp);
}
});
socket.on('users_update', (data) => {
updateOnlineCount(data.count);
});
socket.on('error', (data) => {
addSystemMessage(`Error: ${data.message}`, 'error');
});
socket.on('clear_chat', (data) => {
clearAllMessages();
addSystemMessage(data.message || 'Chat cleared by server', 'warning');
});
socket.on('disconnect', () => {
isAuthenticated = false;
console.log('Disconnected from chat server');
addSystemMessage('Disconnected from chat', 'error');
});
socket.on('connect_error', (error) => {
console.error('Connection error:', error);
addSystemMessage('Connection error. Retrying...', 'error');
});
} catch (error) {
console.error('Error connecting to chat:', error);
addSystemMessage('Failed to connect to chat server', 'error');
}
}
function loadSocketIO() {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://cdn.socket.io/4.6.1/socket.io.min.js';
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
function sendMessage() {
const chatInput = document.getElementById('chatInput');
const message = chatInput.value.trim();
if (!message || message.length === 0) {
return;
}
if (message.length > MAX_MESSAGE_LENGTH) {
addSystemMessage(`Message too long (max ${MAX_MESSAGE_LENGTH} characters)`, 'error');
return;
}
if (!socket || !isAuthenticated) {
messageQueue.push(message);
addSystemMessage('Connecting... Your message will be sent soon.', 'warning');
chatInput.value = '';
updateCharCounter();
return;
}
socket.emit('send_message', { message });
chatInput.value = '';
updateCharCounter();
}
function addUserMessage(username, message, timestamp) {
const chatMessages = document.getElementById('chatMessages');
if (!chatMessages) return;
const messageDiv = document.createElement('div');
messageDiv.className = 'chat-message user-message';
const time = new Date(timestamp).toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit'
});
messageDiv.innerHTML = `
<div class="message-header">
<span class="message-username">${escapeHtml(username)}</span>
<span class="message-time">${time}</span>
</div>
<div class="message-content">${message}</div>
`;
chatMessages.appendChild(messageDiv);
scrollToBottom();
}
function addSystemMessage(message, type = 'info') {
const chatMessages = document.getElementById('chatMessages');
if (!chatMessages) return;
const messageDiv = document.createElement('div');
messageDiv.className = `chat-message system-message system-${type}`;
messageDiv.innerHTML = `
<div class="message-content">
<i class="fas fa-info-circle"></i> ${escapeHtml(message)}
</div>
`;
chatMessages.appendChild(messageDiv);
scrollToBottom();
}
function updateOnlineCount(count) {
const onlineCountElement = document.getElementById('chatOnlineCount');
if (onlineCountElement) {
onlineCountElement.textContent = count;
}
}
function updateCharCounter() {
const chatInput = document.getElementById('chatInput');
const charCounter = document.getElementById('chatCharCounter');
if (chatInput && charCounter) {
const length = chatInput.value.length;
charCounter.textContent = `${length}/${MAX_MESSAGE_LENGTH}`;
if (length > MAX_MESSAGE_LENGTH * 0.9) {
charCounter.classList.add('warning');
} else {
charCounter.classList.remove('warning');
}
}
}
function scrollToBottom() {
const chatMessages = document.getElementById('chatMessages');
if (chatMessages) {
chatMessages.scrollTop = chatMessages.scrollHeight;
}
}
function clearAllMessages() {
const chatMessages = document.getElementById('chatMessages');
if (chatMessages) {
chatMessages.innerHTML = '';
console.log('Chat cleared');
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
window.addEventListener('beforeunload', () => {
if (socket && socket.connected) {
socket.disconnect();
}
});
document.addEventListener('DOMContentLoaded', () => {
const usernameSubmitBtn = document.getElementById('chatUsernameSubmit');
const usernameCancelBtn = document.getElementById('chatUsernameCancel');
const usernameInput = document.getElementById('chatUsernameInput');
if (usernameSubmitBtn) {
usernameSubmitBtn.addEventListener('click', submitChatUsername);
}
if (usernameCancelBtn) {
usernameCancelBtn.addEventListener('click', () => {
hideUsernameModal();
const playNavItem = document.querySelector('[data-page="play"]');
if (playNavItem) playNavItem.click();
});
}
if (usernameInput) {
usernameInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
submitChatUsername();
}
});
}
const chatNavItem = document.querySelector('[data-page="chat"]');
if (chatNavItem) {
chatNavItem.addEventListener('click', () => {
if (!socket) {
initChat();
}
});
}
});
window.ChatAPI = {
send: sendMessage,
disconnect: () => socket?.disconnect()
};

View File

@@ -1,194 +0,0 @@
let isDownloading = false;
let installPage;
let installBtn;
let installText;
let installPlayerName;
let installCustomCheck;
let installCustomOptions;
let installPathInput;
export function setupInstallation() {
installPage = document.getElementById('install-page');
installBtn = document.getElementById('installBtn');
installText = document.getElementById('installText');
installPlayerName = document.getElementById('installPlayerName');
installCustomCheck = document.getElementById('installCustomCheck');
installCustomOptions = document.getElementById('installCustomOptions');
installPathInput = document.getElementById('installPath');
if (installCustomCheck && installCustomOptions) {
installCustomCheck.addEventListener('change', (e) => {
if (e.target.checked) {
installCustomOptions.classList.add('show');
} else {
installCustomOptions.classList.remove('show');
}
});
}
if (installPlayerName) {
installPlayerName.addEventListener('change', savePlayerName);
}
}
export async function installGame() {
if (isDownloading || (installBtn && installBtn.disabled)) return;
const playerName = (installPlayerName ? installPlayerName.value.trim() : '') || 'Player';
const installPath = installPathInput ? installPathInput.value.trim() : '';
if (window.LauncherUI) window.LauncherUI.showProgress();
isDownloading = true;
if (installBtn) {
installBtn.disabled = true;
installText.textContent = 'INSTALLING...';
}
try {
if (window.electronAPI && window.electronAPI.installGame) {
const result = await window.electronAPI.installGame(playerName, '', installPath);
if (result.success) {
if (window.LauncherUI) {
window.LauncherUI.updateProgress({ message: 'Installation completed successfully!' });
setTimeout(() => {
window.LauncherUI.hideProgress();
window.LauncherUI.showLauncherOrInstall(true);
const playerNameInput = document.getElementById('playerName');
if (playerNameInput) playerNameInput.value = playerName;
}, 2000);
}
} else {
throw new Error(result.error || 'Installation failed');
}
} else {
simulateInstallation(playerName);
}
} catch (error) {
if (window.LauncherUI) {
window.LauncherUI.updateProgress({ message: `Installation failed: ${error.message}` });
setTimeout(() => {
window.LauncherUI.hideProgress();
resetInstallButton();
}, 3000);
}
}
}
function simulateInstallation(playerName) {
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 3;
if (progress > 100) progress = 100;
if (window.LauncherUI) {
window.LauncherUI.updateProgress({
percent: progress,
message: progress < 100 ? 'Installing game files...' : 'Installation complete!',
speed: 1024 * 1024 * (5 + Math.random() * 10),
downloaded: progress * 1024 * 1024 * 20,
total: 1024 * 1024 * 2000
});
}
if (progress >= 100) {
clearInterval(interval);
setTimeout(() => {
if (window.LauncherUI) {
window.LauncherUI.updateProgress({ message: 'Installation completed successfully!' });
setTimeout(() => {
window.LauncherUI.hideProgress();
window.LauncherUI.showLauncherOrInstall(true);
const playerNameInput = document.getElementById('playerName');
if (playerNameInput) playerNameInput.value = playerName;
resetInstallButton();
}, 2000);
}
}, 1000);
}
}, 200);
}
function resetInstallButton() {
isDownloading = false;
if (installBtn) {
installBtn.disabled = false;
installText.textContent = 'INSTALL HYTALE';
}
}
export async function browseInstallPath() {
try {
if (window.electronAPI && window.electronAPI.selectInstallPath) {
const result = await window.electronAPI.selectInstallPath();
if (result && installPathInput) {
installPathInput.value = result;
}
}
} catch (error) {
console.error('Error browsing install path:', error);
}
}
async function savePlayerName() {
try {
if (window.electronAPI && window.electronAPI.saveSettings) {
const playerName = (installPlayerName ? installPlayerName.value.trim() : '') || 'Player';
await window.electronAPI.saveSettings({ playerName });
}
} catch (error) {
console.error('Error saving player name:', error);
}
}
export async function checkGameStatusAndShowInterface() {
try {
if (window.electronAPI && window.electronAPI.isGameInstalled) {
const installed = await window.electronAPI.isGameInstalled();
if (window.LauncherUI) {
window.LauncherUI.showLauncherOrInstall(installed);
}
if (installed) {
await loadPlayerSettings();
}
} else {
if (window.LauncherUI) {
window.LauncherUI.showLauncherOrInstall(false);
}
}
} catch (error) {
console.error('Error checking game status:', error);
if (window.LauncherUI) {
window.LauncherUI.showLauncherOrInstall(false);
}
}
}
async function loadPlayerSettings() {
try {
if (window.electronAPI && window.electronAPI.loadSettings) {
const settings = await window.electronAPI.loadSettings();
if (settings) {
const playerNameInput = document.getElementById('playerName');
const javaPathInput = document.getElementById('javaPath');
if (settings.playerName && playerNameInput) {
playerNameInput.value = settings.playerName;
}
if (settings.javaPath && javaPathInput) {
javaPathInput.value = settings.javaPath;
}
}
}
} catch (error) {
console.error('Error loading settings:', error);
}
}
window.installGame = installGame;
window.browseInstallPath = browseInstallPath;
document.addEventListener('DOMContentLoaded', async () => {
setupInstallation();
await checkGameStatusAndShowInterface();
});

View File

@@ -1,235 +0,0 @@
let isDownloading = false;
let playBtn;
let playText;
let homePlayBtn;
let uninstallBtn;
let playerNameInput;
let javaPathInput;
export function setupLauncher() {
playBtn = document.getElementById('playBtn');
playText = document.getElementById('playText');
homePlayBtn = document.getElementById('homePlayBtn');
uninstallBtn = document.getElementById('uninstallBtn');
playerNameInput = document.getElementById('playerName');
javaPathInput = document.getElementById('javaPath');
if (playerNameInput) {
playerNameInput.addEventListener('change', savePlayerName);
}
if (javaPathInput) {
javaPathInput.addEventListener('change', saveJavaPath);
}
if (window.electronAPI && window.electronAPI.onProgressUpdate) {
window.electronAPI.onProgressUpdate((data) => {
if (window.LauncherUI) {
window.LauncherUI.showProgress();
window.LauncherUI.updateProgress(data);
}
});
}
}
export async function launch() {
if (isDownloading || (playBtn && playBtn.disabled)) return;
let playerName = 'Player';
if (window.SettingsAPI && window.SettingsAPI.getCurrentPlayerName) {
playerName = window.SettingsAPI.getCurrentPlayerName();
} else if (playerNameInput && playerNameInput.value.trim()) {
playerName = playerNameInput.value.trim();
}
let javaPath = '';
if (window.SettingsAPI && window.SettingsAPI.getCurrentJavaPath) {
javaPath = window.SettingsAPI.getCurrentJavaPath();
}
if (window.LauncherUI) window.LauncherUI.showProgress();
isDownloading = true;
if (playBtn) {
playBtn.disabled = true;
playText.textContent = 'LAUNCHING...';
}
try {
if (window.electronAPI && window.electronAPI.launchGame) {
const result = await window.electronAPI.launchGame(playerName, javaPath, '');
if (result.success) {
if (window.LauncherUI) {
window.LauncherUI.updateProgress({ message: 'Game started successfully!' });
setTimeout(() => {
window.LauncherUI.hideProgress();
if (window.electronAPI.minimizeWindow) {
window.electronAPI.minimizeWindow();
}
}, 2000);
}
} else {
throw new Error(result.error || 'Launch failed');
}
} else {
setTimeout(() => {
if (window.LauncherUI) {
window.LauncherUI.updateProgress({ message: 'Game started successfully!' });
setTimeout(() => {
window.LauncherUI.hideProgress();
resetPlayButton();
}, 2000);
}
}, 2000);
}
} catch (error) {
if (window.LauncherUI) {
window.LauncherUI.updateProgress({ message: `Failed: ${error.message}` });
setTimeout(() => {
window.LauncherUI.hideProgress();
resetPlayButton();
}, 3000);
}
}
}
export async function uninstallGame() {
if (!confirm('Are you sure you want to uninstall Hytale? All game files will be deleted.')) {
return;
}
if (window.LauncherUI) window.LauncherUI.showProgress();
if (window.LauncherUI) window.LauncherUI.updateProgress({ message: 'Uninstalling game...' });
if (uninstallBtn) uninstallBtn.disabled = true;
try {
if (window.electronAPI && window.electronAPI.uninstallGame) {
const result = await window.electronAPI.uninstallGame();
if (result.success) {
if (window.LauncherUI) {
window.LauncherUI.updateProgress({ message: 'Game uninstalled successfully!' });
setTimeout(() => {
window.LauncherUI.hideProgress();
window.LauncherUI.showLauncherOrInstall(false);
}, 2000);
}
} else {
throw new Error(result.error || 'Uninstall failed');
}
} else {
setTimeout(() => {
if (window.LauncherUI) {
window.LauncherUI.updateProgress({ message: 'Game uninstalled successfully!' });
setTimeout(() => {
window.LauncherUI.hideProgress();
window.LauncherUI.showLauncherOrInstall(false);
}, 2000);
}
}, 2000);
}
} catch (error) {
if (window.LauncherUI) {
window.LauncherUI.updateProgress({ message: `Uninstall failed: ${error.message}` });
setTimeout(() => window.LauncherUI.hideProgress(), 3000);
}
} finally {
if (uninstallBtn) uninstallBtn.disabled = false;
}
}
function resetPlayButton() {
isDownloading = false;
if (playBtn) {
playBtn.disabled = false;
playText.textContent = 'PLAY';
}
}
async function savePlayerName() {
try {
if (window.electronAPI && window.electronAPI.saveSettings) {
const playerName = (playerNameInput ? playerNameInput.value.trim() : '') || 'Player';
await window.electronAPI.saveSettings({ playerName });
}
} catch (error) {
console.error('Error saving player name:', error);
}
}
async function saveJavaPath() {
try {
if (window.electronAPI && window.electronAPI.saveSettings) {
const javaPath = (javaPathInput ? javaPathInput.value.trim() : '') || '';
await window.electronAPI.saveSettings({ javaPath });
}
} catch (error) {
console.error('Error saving Java path:', error);
}
}
function toggleCustomJava() {
if (!customJavaOptions) return;
if (customJavaCheck && customJavaCheck.checked) {
customJavaOptions.style.display = 'block';
} else {
customJavaOptions.style.display = 'none';
if (customJavaPath) customJavaPath.value = '';
saveCustomJavaPath('');
}
}
async function browseJavaPath() {
try {
if (window.electronAPI && window.electronAPI.browseJavaPath) {
const result = await window.electronAPI.browseJavaPath();
if (result && result.filePaths && result.filePaths.length > 0) {
const selectedPath = result.filePaths[0];
if (customJavaPath) {
customJavaPath.value = selectedPath;
}
await saveCustomJavaPath(selectedPath);
}
}
} catch (error) {
console.error('Error browsing Java path:', error);
}
}
async function saveCustomJavaPath(path) {
try {
if (window.electronAPI && window.electronAPI.saveJavaPath) {
await window.electronAPI.saveJavaPath(path);
}
} catch (error) {
console.error('Error saving custom Java path:', error);
}
}
async function loadCustomJavaPath() {
try {
if (window.electronAPI && window.electronAPI.loadJavaPath) {
const savedPath = await window.electronAPI.loadJavaPath();
if (savedPath && savedPath.trim()) {
if (customJavaPath) {
customJavaPath.value = savedPath;
}
if (customJavaCheck) {
customJavaCheck.checked = true;
}
if (customJavaOptions) {
customJavaOptions.style.display = 'block';
}
}
}
} catch (error) {
console.error('Error loading custom Java path:', error);
}
}
window.launch = launch;
window.uninstallGame = uninstallGame;
document.addEventListener('DOMContentLoaded', setupLauncher);

View File

@@ -1,724 +0,0 @@
const API_KEY = '$2a$10$bqk254NMZOWVTzLVJCcxEOmhcyUujKxA5xk.kQCN9q0KNYFJd5b32';
const CURSEFORGE_API = 'https://api.curseforge.com/v1';
const HYTALE_GAME_ID = 70216;
let installedMods = [];
let browseMods = [];
let searchQuery = '';
let modsPage = 0;
let modsPageSize = 20;
let modsTotalPages = 1;
export async function initModsManager() {
setupModsEventListeners();
await loadInstalledMods();
await loadBrowseMods();
}
function setupModsEventListeners() {
const searchInput = document.getElementById('modsSearch');
if (searchInput) {
let searchTimeout;
searchInput.addEventListener('input', (e) => {
searchQuery = e.target.value.toLowerCase().trim();
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
modsPage = 0;
loadBrowseMods();
}, 500);
});
}
const myModsBtn = document.getElementById('myModsBtn');
if (myModsBtn) {
myModsBtn.addEventListener('click', openMyModsModal);
}
const closeModalBtn = document.getElementById('closeMyModsModal');
if (closeModalBtn) {
closeModalBtn.addEventListener('click', closeMyModsModal);
}
const modal = document.getElementById('myModsModal');
if (modal) {
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closeMyModsModal();
}
});
}
const prevPageBtn = document.getElementById('prevPage');
const nextPageBtn = document.getElementById('nextPage');
if (prevPageBtn) {
prevPageBtn.addEventListener('click', () => {
if (modsPage > 0) {
modsPage--;
loadBrowseMods();
}
});
}
if (nextPageBtn) {
nextPageBtn.addEventListener('click', () => {
if (modsPage < modsTotalPages - 1) {
modsPage++;
loadBrowseMods();
}
});
}
}
function openMyModsModal() {
const modal = document.getElementById('myModsModal');
if (modal) {
modal.classList.add('active');
loadInstalledMods();
}
}
function closeMyModsModal() {
const modal = document.getElementById('myModsModal');
if (modal) {
modal.classList.remove('active');
}
}
async function loadInstalledMods() {
try {
const modsPath = await window.electronAPI?.getModsPath();
if (!modsPath) {
showInstalledModsError('Could not get mods directory');
return;
}
const mods = await window.electronAPI?.loadInstalledMods(modsPath);
installedMods = mods || [];
displayInstalledMods(installedMods);
} catch (error) {
console.error('Error loading installed mods:', error);
showInstalledModsError('Failed to load installed mods');
}
}
function displayInstalledMods(mods) {
const modsContainer = document.getElementById('installedModsList');
if (!modsContainer) return;
if (mods.length === 0) {
modsContainer.innerHTML = `
<div class=\"empty-installed-mods\">
<i class=\"fas fa-box-open\"></i>
<h4>No Mods Installed</h4>
<p>Add mods from CurseForge or import local files</p>
</div>
`;
return;
}
modsContainer.innerHTML = mods.map(mod => createInstalledModCard(mod)).join('');
mods.forEach(mod => {
const toggleBtn = document.getElementById(`toggle-installed-${mod.id}`);
const deleteBtn = document.getElementById(`delete-installed-${mod.id}`);
if (toggleBtn) {
toggleBtn.addEventListener('click', () => toggleMod(mod.id));
}
if (deleteBtn) {
deleteBtn.addEventListener('click', () => deleteMod(mod.id));
}
});
}
function createInstalledModCard(mod) {
const statusClass = mod.enabled ? 'text-primary' : 'text-zinc-500';
const statusText = mod.enabled ? 'ACTIVE' : 'DISABLED';
const toggleBtnClass = mod.enabled ? 'btn-disable' : 'btn-enable';
const toggleBtnText = mod.enabled ? 'DISABLE' : 'ENABLE';
const toggleIcon = mod.enabled ? 'fa-pause' : 'fa-play';
return `
<div class="installed-mod-card" data-mod-id="${mod.id}">
<div class="installed-mod-icon">
<i class="fas fa-cube"></i>
</div>
<div class="installed-mod-info">
<div class="installed-mod-header">
<h4 class="installed-mod-name">${mod.name}</h4>
<span class="installed-mod-version">v${mod.version}</span>
</div>
<p class="installed-mod-description">${mod.description || 'No description available'}</p>
</div>
<div class="installed-mod-actions">
<div class="installed-mod-status ${statusClass}">
<i class="fas fa-circle"></i>
${statusText}
</div>
<div class="installed-mod-buttons">
<button id="delete-installed-${mod.id}" class="installed-mod-btn-icon" title="Delete mod">
<i class="fas fa-trash"></i>
</button>
<button id="toggle-installed-${mod.id}" class="installed-mod-btn-toggle ${toggleBtnClass}">
<i class="fas ${toggleIcon}"></i>
${toggleBtnText}
</button>
</div>
</div>
</div>
`;
}
async function loadBrowseMods() {
const browseContainer = document.getElementById('browseModsList');
if (!browseContainer) return;
browseContainer.innerHTML = '<div class=\"loading-mods\"><div class=\"loading-spinner\"></div><span>Loading mods from CurseForge...</span></div>';
try {
if (!API_KEY || API_KEY.length < 10) {
browseContainer.innerHTML = `
<div class=\"empty-browse-mods\">
<i class=\"fas fa-key\"></i>
<h4>API Key Required</h4>
<p>CurseForge API key is needed to browse mods</p>
</div>
`;
return;
}
const offset = modsPage * modsPageSize;
let url = `${CURSEFORGE_API}/mods/search?gameId=${HYTALE_GAME_ID}&pageSize=${modsPageSize}&sortOrder=desc&sortField=6&index=${offset}`;
if (searchQuery && searchQuery.length > 0) {
url += `&searchFilter=${encodeURIComponent(searchQuery)}`;
}
console.log('Fetching mods from page', modsPage + 1, 'offset:', offset, 'search:', searchQuery || 'none', 'URL:', url);
const response = await fetch(url, {
headers: {
'x-api-key': API_KEY,
'Accept': 'application/json'
}
});
console.log('Response status:', response.status);
if (!response.ok) {
const errorText = await response.text();
console.error('API Error Response:', errorText);
throw new Error(`CurseForge API error: ${response.status} - ${errorText}`);
}
const data = await response.json();
console.log('API Response data:', data);
console.log('Total mods found:', data.data?.length || 0);
browseMods = (data.data || []).map(mod => ({
id: mod.id.toString(),
name: mod.name,
slug: mod.slug,
summary: mod.summary || 'No description available',
downloadCount: mod.downloadCount || 0,
author: mod.authors?.[0]?.name || 'Unknown',
version: mod.latestFiles?.[0]?.displayName || 'Unknown',
thumbnailUrl: mod.logo?.thumbnailUrl || null,
websiteUrl: mod.links?.websiteUrl || null,
modId: mod.id,
fileId: mod.latestFiles?.[0]?.id,
fileName: mod.latestFiles?.[0]?.fileName,
downloadUrl: mod.latestFiles?.[0]?.downloadUrl
}));
console.log('Processed mods:', browseMods.length);
modsTotalPages = Math.ceil((data.pagination?.totalCount || 1) / modsPageSize);
displayBrowseMods(browseMods);
updatePagination();
} catch (error) {
console.error('Error loading browse mods:', error);
browseContainer.innerHTML = `
<div class=\"empty-browse-mods error\">
<i class=\"fas fa-exclamation-triangle\"></i>
<h4>API Error</h4>
<p>Failed to load mods from CurseForge</p>
<small>${error.message}</small>
</div>
`;
}
}
function displayBrowseMods(mods) {
const browseContainer = document.getElementById('browseModsList');
if (!browseContainer) return;
if (mods.length === 0) {
browseContainer.innerHTML = `
<div class=\"empty-browse-mods\">
<i class=\"fas fa-search\"></i>
<h4>No Mods Found</h4>
<p>Try adjusting your search</p>
</div>
`;
return;
}
browseContainer.innerHTML = mods.map(mod => createBrowseModCard(mod)).join('');
mods.forEach(mod => {
const installBtn = document.getElementById(`install-${mod.id}`);
if (installBtn) {
installBtn.addEventListener('click', () => downloadAndInstallMod(mod));
}
});
}
function createBrowseModCard(mod) {
const isInstalled = installedMods.some(installed =>
installed.name.toLowerCase().includes(mod.name.toLowerCase()) ||
installed.curseForgeId == mod.id
);
return `
<div class=\"mod-card ${isInstalled ? 'installed' : ''}\" data-mod-id=\"${mod.id}\">
<div class=\"mod-image\">
${mod.thumbnailUrl ?
`<img src=\"${mod.thumbnailUrl}\" alt=\"${mod.name}\" onerror=\"this.parentElement.innerHTML='<i class=\\\"fas fa-puzzle-piece\\\"></i>'\">` :
`<i class=\"fas fa-puzzle-piece\"></i>`
}
</div>
<div class=\"mod-info\">
<div class=\"mod-header\">
<h3 class=\"mod-name\">${mod.name}</h3>
<span class=\"mod-version\">${mod.version}</span>
</div>
<p class=\"mod-description\">${mod.summary}</p>
<div class=\"mod-meta\">
<span class=\"mod-meta-item\">
<i class=\"fas fa-user\"></i>
${mod.author}
</span>
<span class=\"mod-meta-item\">
<i class=\"fas fa-download\"></i>
${formatNumber(mod.downloadCount)}
</span>
</div>
</div>
<div class=\"mod-actions\">
<button id=\"view-${mod.id}\" class=\"mod-btn-toggle bg-blue-600 text-white hover:bg-blue-700\" onclick=\"window.modsManager.viewModPage(${mod.id})\">
<i class=\"fas fa-external-link-alt\"></i>
VIEW
</button>
${!isInstalled ?
`<button id=\"install-${mod.id}\" class=\"mod-btn-toggle bg-primary text-black hover:bg-primary/80\">
<i class=\"fas fa-download\"></i>
INSTALL
</button>` :
`<button class=\"mod-btn-toggle bg-white/10 text-white\" disabled>
<i class=\"fas fa-check\"></i>
INSTALLED
</button>`
}
</div>
</div>
`;
}
async function downloadAndInstallMod(modInfo) {
try {
window.LauncherUI?.showProgress(`Downloading ${modInfo.name}...`);
const result = await window.electronAPI?.downloadMod(modInfo);
if (result?.success) {
const newMod = {
id: result.modInfo.id,
name: modInfo.name,
version: modInfo.version,
description: modInfo.summary,
author: modInfo.author,
enabled: true,
fileName: result.fileName,
fileSize: result.modInfo.fileSize,
dateInstalled: new Date().toISOString(),
curseForgeId: modInfo.modId,
curseForgeFileId: modInfo.fileId
};
installedMods.push(newMod);
await loadInstalledMods();
await loadBrowseMods();
window.LauncherUI?.hideProgress();
showNotification(`${modInfo.name} installed successfully! 🎉`, 'success');
} else {
throw new Error(result?.error || 'Failed to download mod');
}
} catch (error) {
console.error('Error downloading mod:', error);
window.LauncherUI?.hideProgress();
showNotification('Failed to download mod: ' + error.message, 'error');
}
}
async function toggleMod(modId) {
try {
window.LauncherUI?.showProgress('Toggling mod...');
const modsPath = await window.electronAPI?.getModsPath();
const result = await window.electronAPI?.toggleMod(modId, modsPath);
if (result?.success) {
await loadInstalledMods();
window.LauncherUI?.hideProgress();
} else {
throw new Error(result?.error || 'Failed to toggle mod');
}
} catch (error) {
console.error('Error toggling mod:', error);
window.LauncherUI?.hideProgress();
showNotification('Failed to toggle mod: ' + error.message, 'error');
}
}
async function deleteMod(modId) {
const mod = installedMods.find(m => m.id === modId);
if (!mod) return;
showConfirmModal(
`Are you sure you want to delete "${mod.name}"? This action cannot be undone.`,
async () => {
try {
window.LauncherUI?.showProgress('Deleting mod...');
const modsPath = await window.electronAPI?.getModsPath();
const result = await window.electronAPI?.uninstallMod(modId, modsPath);
if (result?.success) {
await loadInstalledMods();
await loadBrowseMods();
window.LauncherUI?.hideProgress();
showNotification(`"${mod.name}" deleted successfully`, 'success');
} else {
throw new Error(result?.error || 'Failed to delete mod');
}
} catch (error) {
console.error('Error deleting mod:', error);
window.LauncherUI?.hideProgress();
showNotification('Failed to delete mod: ' + error.message, 'error');
}
}
);
}
function formatNumber(num) {
if (!num) return '0';
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
return num.toString();
}
function showNotification(message, type = 'info', duration = 4000) {
const existing = document.querySelector(`.mod-notification.${type}`);
if (existing) {
existing.remove();
}
const notification = document.createElement('div');
notification.className = `mod-notification ${type}`;
const icons = {
success: 'fa-check-circle',
error: 'fa-exclamation-circle',
info: 'fa-info-circle',
warning: 'fa-exclamation-triangle'
};
const colors = {
success: '#10b981',
error: '#ef4444',
info: '#3b82f6',
warning: '#f59e0b'
};
notification.innerHTML = `
<div class="notification-content">
<i class="fas ${icons[type]}"></i>
<span>${message}</span>
</div>
<button class="notification-close" onclick="this.parentElement.remove()">
<i class="fas fa-times"></i>
</button>
`;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${colors[type]};
color: white;
padding: 16px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 10000;
min-width: 300px;
max-width: 400px;
transform: translateX(100%);
transition: transform 0.3s ease;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
font-weight: 500;
`;
const contentStyle = `
display: flex;
align-items: center;
gap: 10px;
flex: 1;
`;
const closeStyle = `
background: none;
border: none;
color: white;
cursor: pointer;
padding: 4px;
border-radius: 4px;
opacity: 0.8;
transition: opacity 0.2s;
margin-left: 10px;
`;
notification.querySelector('.notification-content').style.cssText = contentStyle;
notification.querySelector('.notification-close').style.cssText = closeStyle;
document.body.appendChild(notification);
// Animate in
setTimeout(() => {
notification.style.transform = 'translateX(0)';
}, 10);
// Auto remove
setTimeout(() => {
if (notification.parentElement) {
notification.style.transform = 'translateX(100%)';
setTimeout(() => {
notification.remove();
}, 300);
}
}, duration);
}
// Custom confirmation modal
function showConfirmModal(message, onConfirm, onCancel = null) {
const existingModal = document.querySelector('.mod-confirm-modal');
if (existingModal) {
existingModal.remove();
}
const modal = document.createElement('div');
modal.className = 'mod-confirm-modal';
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(4px);
z-index: 20000;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
`;
const dialog = document.createElement('div');
dialog.className = 'mod-confirm-dialog';
dialog.style.cssText = `
background: #1f2937;
border-radius: 12px;
padding: 0;
min-width: 400px;
max-width: 500px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6);
border: 1px solid rgba(239, 68, 68, 0.3);
transform: scale(0.9);
transition: transform 0.3s ease;
`;
dialog.innerHTML = `
<div style="padding: 24px; border-bottom: 1px solid rgba(255,255,255,0.1);">
<div style="display: flex; align-items: center; gap: 12px; color: #ef4444;">
<i class="fas fa-exclamation-triangle" style="font-size: 24px;"></i>
<h3 style="margin: 0; font-size: 1.2rem; font-weight: 600;">Confirm Deletion</h3>
</div>
</div>
<div style="padding: 24px; color: #e5e7eb;">
<p style="margin: 0; line-height: 1.5; font-size: 1rem;">${message}</p>
</div>
<div style="padding: 20px 24px; display: flex; gap: 12px; justify-content: flex-end; border-top: 1px solid rgba(255,255,255,0.1);">
<button class="mod-confirm-cancel" style="
background: transparent;
color: #9ca3af;
border: 1px solid rgba(156, 163, 175, 0.3);
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
transition: all 0.2s;
">Cancel</button>
<button class="mod-confirm-delete" style="
background: #ef4444;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
transition: all 0.2s;
">Delete</button>
</div>
`;
modal.appendChild(dialog);
document.body.appendChild(modal);
// Animate in
setTimeout(() => {
modal.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);
// Event handlers
const cancelBtn = dialog.querySelector('.mod-confirm-cancel');
const deleteBtn = dialog.querySelector('.mod-confirm-delete');
const closeModal = () => {
modal.style.opacity = '0';
dialog.style.transform = 'scale(0.9)';
setTimeout(() => {
modal.remove();
}, 300);
};
cancelBtn.onclick = () => {
closeModal();
if (onCancel) onCancel();
};
deleteBtn.onclick = () => {
closeModal();
onConfirm();
};
modal.onclick = (e) => {
if (e.target === modal) {
closeModal();
if (onCancel) onCancel();
}
};
// Escape key
const handleEscape = (e) => {
if (e.key === 'Escape') {
closeModal();
if (onCancel) onCancel();
document.removeEventListener('keydown', handleEscape);
}
};
document.addEventListener('keydown', handleEscape);
}
function updatePagination() {
const currentPageEl = document.getElementById('currentPage');
const totalPagesEl = document.getElementById('totalPages');
const prevBtn = document.getElementById('prevPage');
const nextBtn = document.getElementById('nextPage');
if (currentPageEl) currentPageEl.textContent = modsPage + 1;
if (totalPagesEl) totalPagesEl.textContent = modsTotalPages;
if (prevBtn) {
prevBtn.disabled = modsPage === 0;
prevBtn.style.opacity = modsPage === 0 ? '0.5' : '1';
prevBtn.style.cursor = modsPage === 0 ? 'not-allowed' : 'pointer';
}
if (nextBtn) {
nextBtn.disabled = modsPage >= modsTotalPages - 1;
nextBtn.style.opacity = modsPage >= modsTotalPages - 1 ? '0.5' : '1';
nextBtn.style.cursor = modsPage >= modsTotalPages - 1 ? 'not-allowed' : 'pointer';
}
}
function showInstalledModsError(message) {
const modsContainer = document.getElementById('installedModsList');
if (!modsContainer) return;
modsContainer.innerHTML = `
<div class=\"empty-installed-mods error\">
<i class=\"fas fa-exclamation-triangle\"></i>
<h4>Error</h4>
<p>${message}</p>
</div>
`;
}
function viewModPage(modId) {
console.log('Looking for mod with ID:', modId, 'Type:', typeof modId);
console.log('Available mods:', browseMods.map(m => ({ id: m.id, name: m.name, type: typeof m.id })));
const mod = browseMods.find(m => m.id.toString() === modId.toString());
if (mod) {
console.log('Found mod:', mod.name);
let modUrl;
if (mod.websiteUrl && mod.websiteUrl.includes('curseforge.com')) {
modUrl = mod.websiteUrl;
} else if (mod.slug) {
modUrl = `https://www.curseforge.com/hytale/mods/${mod.slug}`;
} else {
const nameSlug = mod.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
modUrl = `https://www.curseforge.com/hytale/mods/${nameSlug}`;
}
console.log('Opening URL:', modUrl);
if (window.electronAPI && window.electronAPI.openExternalLink) {
window.electronAPI.openExternalLink(modUrl);
} else {
if (window.electronAPI && window.electronAPI.shell) {
window.electronAPI.shell.openExternal(modUrl);
} else {
window.open(modUrl, '_blank');
}
}
} else {
console.error('Mod not found with ID:', modId);
showNotification('Mod information not found', 'error');
}
}
window.modsManager = {
toggleMod,
deleteMod,
openMyModsModal,
closeMyModsModal,
viewModPage
};
document.addEventListener('DOMContentLoaded', initModsManager);

View File

@@ -1,124 +0,0 @@
let newsData = [];
export async function loadNews() {
try {
if (window.electronAPI && window.electronAPI.getHytaleNews) {
try {
const realNews = await window.electronAPI.getHytaleNews();
if (realNews && realNews.length > 0) {
newsData = realNews.slice(0, 10).map((article, index) => ({
id: index + 1,
title: article.title,
summary: article.description,
type: "NEWS",
image: article.imageUrl || '',
date: formatDate(article.date),
url: article.destUrl
}));
displayHomeNews(newsData.slice(0, 5));
displayFullNews(newsData);
} else {
showErrorNews();
}
} catch (error) {
console.log('Failed to load news:', error.message);
showErrorNews();
}
} else {
showErrorNews();
}
} catch (error) {
console.error('Error loading news:', error);
showErrorNews();
}
}
function displayHomeNews(news) {
const newsGrid = document.getElementById('newsGrid');
if (!newsGrid) return;
newsGrid.innerHTML = news.map(article => `
<div class="news-item news-card" onclick="openNewsDetails(${article.id})">
<div class="news-image" style="background-image: url('${article.image}');"></div>
<div class="news-overlay">
<span class="news-type">${article.type}</span>
<span class="news-date">${article.date}</span>
</div>
<div class="news-content">
<h3 class="news-title">${article.title}</h3>
<p class="news-summary">${article.summary}</p>
</div>
</div>
`).join('');
}
function displayFullNews(news) {
const allNewsGrid = document.getElementById('allNewsGrid');
if (!allNewsGrid) return;
allNewsGrid.innerHTML = news.map(article => `
<div class="news-item news-card" onclick="openNewsDetails(${article.id})">
<div class="news-image" style="background-image: url('${article.image}');"></div>
<div class="news-overlay">
<span class="news-type">${article.type}</span>
<span class="news-date">${article.date}</span>
</div>
<div class="news-content">
<h3 class="news-title">${article.title}</h3>
<p class="news-summary">${article.summary}</p>
</div>
</div>
`).join('');
}
function showErrorNews() {
const newsGrid = document.getElementById('newsGrid');
if (newsGrid) {
newsGrid.innerHTML = `
<div class="loading-news">
<i class="fas fa-exclamation-triangle text-4xl mb-4 text-yellow-500"></i>
<span>Unable to load news</span>
</div>
`;
}
}
function openNewsDetails(newsId) {
const article = newsData.find(item => item.id === newsId);
if (article && article.url) {
openNewsArticle(article.url);
} else {
console.log('Opening news article:', article);
}
}
function openNewsArticle(url) {
if (url && url !== '#' && window.electronAPI && window.electronAPI.openExternal) {
window.electronAPI.openExternal(url);
}
}
function formatDate(dateString) {
if (!dateString) return 'RECENTLY';
const date = new Date(dateString);
const now = new Date();
const diffTime = Math.abs(now - date);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
if (diffDays === 1) return '1 DAY AGO';
if (diffDays < 7) return `${diffDays} DAYS AGO`;
if (diffDays < 30) return `${Math.ceil(diffDays / 7)} WEEKS AGO`;
return date.toLocaleDateString();
}
window.openNewsDetails = openNewsDetails;
window.navigateToPage = (page) => {
if (window.LauncherUI) {
window.LauncherUI.showPage(`${page}-page`);
window.LauncherUI.setActiveNav(page);
}
};
document.addEventListener('DOMContentLoaded', loadNews);

View File

@@ -1,154 +0,0 @@
const API_URL = 'http://3.10.208.30/api';
let updateInterval = null;
let currentUserId = null;
export async function initPlayersCounter() {
setupPlayersCounter();
if (window.electronAPI && window.electronAPI.getUserId) {
currentUserId = await window.electronAPI.getUserId();
} else {
console.error('Electron API not available');
return;
}
let username = 'Player';
if (window.electronAPI.loadUsername) {
const savedUsername = await window.electronAPI.loadUsername();
if (savedUsername) username = savedUsername;
}
await registerPlayer(username, currentUserId);
await fetchPlayerStats();
startAutoUpdate();
}
function setupPlayersCounter() {
const counterElement = document.getElementById('playersOnlineCounter');
if (!counterElement) {
console.warn('Players counter element not found');
}
}
async function fetchPlayerStats() {
try {
const response = await fetch(`${API_URL}/players/stats`);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
updateCounterDisplay(data);
} catch (error) {
console.error('Error fetching player stats:', error);
updateCounterDisplay({ online: 0, peak: 0 });
}
}
function updateCounterDisplay(stats) {
const counterElement = document.getElementById('playersOnlineCounter');
const onlineCount = document.getElementById('onlineCount');
if (onlineCount) {
onlineCount.textContent = stats.online || 0;
}
if (counterElement) {
counterElement.classList.add('updated');
setTimeout(() => {
counterElement.classList.remove('updated');
}, 300);
}
}
async function registerPlayer(username, userId) {
try {
const response = await fetch(`${API_URL}/players/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, userId })
});
if (!response.ok) {
throw new Error(`Failed to register player: ${response.status}`);
}
const data = await response.json();
currentUserId = userId;
console.log('Player registered:', data);
await fetchPlayerStats();
return data;
} catch (error) {
console.error('Error registering player:', error);
return null;
}
}
async function unregisterPlayer(userId) {
try {
const response = await fetch(`${API_URL}/players/unregister`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ userId })
});
if (!response.ok) {
throw new Error(`Failed to unregister player: ${response.status}`);
}
const data = await response.json();
currentUserId = null;
console.log('Player unregistered:', data);
await fetchPlayerStats();
return data;
} catch (error) {
console.error('Error unregistering player:', error);
return null;
}
}
function startAutoUpdate() {
updateInterval = setInterval(async () => {
await fetchPlayerStats();
if (currentUserId) {
const username = window.LauncherState?.username || 'Player';
await registerPlayer(username, currentUserId);
}
}, 3000);
}
function stopAutoUpdate() {
if (updateInterval) {
clearInterval(updateInterval);
updateInterval = null;
}
}
window.addEventListener('beforeunload', () => {
if (currentUserId) {
const data = JSON.stringify({ userId: currentUserId });
navigator.sendBeacon(`${API_URL}/players/unregister`, data);
}
stopAutoUpdate();
});
window.PlayersAPI = {
register: registerPlayer,
unregister: unregisterPlayer,
fetchStats: fetchPlayerStats
};
document.addEventListener('DOMContentLoaded', initPlayersCounter);

View File

@@ -1,9 +0,0 @@
import './ui.js';
import './install.js';
import './launcher.js';
import './news.js';
import './mods.js';
import './players.js';
import './chat.js';
import './settings.js';

View File

@@ -1,143 +0,0 @@
let customJavaCheck;
let customJavaOptions;
let customJavaPath;
let browseJavaBtn;
let settingsPlayerName;
export function initSettings() {
setupSettingsElements();
loadAllSettings();
}
function setupSettingsElements() {
customJavaCheck = document.getElementById('customJavaCheck');
customJavaOptions = document.getElementById('customJavaOptions');
customJavaPath = document.getElementById('customJavaPath');
browseJavaBtn = document.getElementById('browseJavaBtn');
settingsPlayerName = document.getElementById('settingsPlayerName');
if (customJavaCheck) {
customJavaCheck.addEventListener('change', toggleCustomJava);
}
if (browseJavaBtn) {
browseJavaBtn.addEventListener('click', browseJavaPath);
}
if (settingsPlayerName) {
settingsPlayerName.addEventListener('change', savePlayerName);
}
}
function toggleCustomJava() {
if (!customJavaOptions) return;
if (customJavaCheck && customJavaCheck.checked) {
customJavaOptions.style.display = 'block';
} else {
customJavaOptions.style.display = 'none';
if (customJavaPath) customJavaPath.value = '';
saveCustomJavaPath('');
}
}
async function browseJavaPath() {
try {
if (window.electronAPI && window.electronAPI.browseJavaPath) {
const result = await window.electronAPI.browseJavaPath();
if (result && result.filePaths && result.filePaths.length > 0) {
const selectedPath = result.filePaths[0];
if (customJavaPath) {
customJavaPath.value = selectedPath;
}
await saveCustomJavaPath(selectedPath);
}
}
} catch (error) {
console.error('Error browsing Java path:', error);
}
}
async function saveCustomJavaPath(path) {
try {
if (window.electronAPI && window.electronAPI.saveJavaPath) {
await window.electronAPI.saveJavaPath(path);
}
} catch (error) {
console.error('Error saving custom Java path:', error);
}
}
async function loadCustomJavaPath() {
try {
if (window.electronAPI && window.electronAPI.loadJavaPath) {
const savedPath = await window.electronAPI.loadJavaPath();
if (savedPath && savedPath.trim()) {
if (customJavaPath) {
customJavaPath.value = savedPath;
}
if (customJavaCheck) {
customJavaCheck.checked = true;
}
if (customJavaOptions) {
customJavaOptions.style.display = 'block';
}
}
}
} catch (error) {
console.error('Error loading custom Java path:', error);
}
}
async function savePlayerName() {
try {
if (window.electronAPI && window.electronAPI.saveUsername && settingsPlayerName) {
const playerName = settingsPlayerName.value.trim() || 'Player';
await window.electronAPI.saveUsername(playerName);
}
} catch (error) {
console.error('Error saving player name:', error);
}
}
async function loadPlayerName() {
try {
if (window.electronAPI && window.electronAPI.loadUsername && settingsPlayerName) {
const savedName = await window.electronAPI.loadUsername();
if (savedName) {
settingsPlayerName.value = savedName;
}
}
} catch (error) {
console.error('Error loading player name:', error);
}
}
async function loadAllSettings() {
await loadCustomJavaPath();
await loadPlayerName();
}
export function getCurrentJavaPath() {
if (customJavaCheck && customJavaCheck.checked && customJavaPath) {
return customJavaPath.value.trim();
}
return '';
}
export function getCurrentPlayerName() {
if (settingsPlayerName && settingsPlayerName.value.trim()) {
return settingsPlayerName.value.trim();
}
return 'Player';
}
document.addEventListener('DOMContentLoaded', initSettings);
window.SettingsAPI = {
getCurrentJavaPath,
getCurrentPlayerName
};

View File

@@ -1,469 +0,0 @@
let progressOverlay;
let progressBar;
let progressBarFill;
let progressText;
let progressPercent;
let progressSpeed;
let progressSize;
function showPage(pageId) {
const pages = document.querySelectorAll('.page');
pages.forEach(page => {
if (page.id === pageId) {
page.classList.add('active');
page.style.display = '';
} else {
page.classList.remove('active');
page.style.display = 'none';
}
});
}
function setActiveNav(page) {
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
if (item.getAttribute('data-page') === page) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
}
function handleNavigation() {
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
item.addEventListener('click', () => {
const page = item.getAttribute('data-page');
showPage(`${page}-page`);
setActiveNav(page);
});
});
}
function setupWindowControls() {
const minimizeBtn = document.querySelector('.window-controls .minimize');
const closeBtn = document.querySelector('.window-controls .close');
const windowControls = document.querySelector('.window-controls');
const header = document.querySelector('.header');
if (windowControls) {
windowControls.style.pointerEvents = 'auto';
windowControls.style.zIndex = '10000';
}
if (header) {
header.style.webkitAppRegion = 'drag';
if (windowControls) {
windowControls.style.webkitAppRegion = 'no-drag';
}
}
if (window.electronAPI) {
if (minimizeBtn) {
minimizeBtn.onclick = (e) => {
e.stopPropagation();
window.electronAPI.minimizeWindow();
};
}
if (closeBtn) {
closeBtn.onclick = (e) => {
e.stopPropagation();
window.electronAPI.closeWindow();
};
}
}
}
function showLauncherOrInstall(isInstalled) {
const launcher = document.getElementById('launcher-container');
const install = document.getElementById('install-page');
const sidebar = document.querySelector('.sidebar');
const gameTitle = document.querySelector('.game-title-section');
if (isInstalled) {
if (launcher) launcher.style.display = '';
if (install) install.style.display = 'none';
if (sidebar) sidebar.style.pointerEvents = 'auto';
if (gameTitle) gameTitle.style.display = '';
showPage('play-page');
setActiveNav('play');
} else {
if (launcher) launcher.style.display = 'none';
if (install) {
install.style.display = '';
install.classList.add('active');
}
if (sidebar) sidebar.style.pointerEvents = 'none';
if (gameTitle) gameTitle.style.display = 'none';
const pages = document.querySelectorAll('#launcher-container .page');
pages.forEach(page => page.classList.remove('active'));
}
}
function setupSidebarLogo() {
const logo = document.querySelector('.sidebar-logo img');
if (logo) {
logo.addEventListener('click', () => {
showPage('play-page');
setActiveNav('play');
});
}
}
function showProgress() {
if (progressOverlay) {
progressOverlay.style.display = 'block';
setTimeout(() => {
progressOverlay.style.opacity = '1';
progressOverlay.style.transform = 'translateY(0)';
}, 10);
}
}
function hideProgress() {
if (progressOverlay) {
progressOverlay.style.opacity = '0';
progressOverlay.style.transform = 'translateY(20px)';
setTimeout(() => {
progressOverlay.style.display = 'none';
}, 300);
}
}
function updateProgress(data) {
if (data.message && progressText) {
progressText.textContent = data.message;
}
if (data.percent !== null && data.percent !== undefined) {
const percent = Math.min(100, Math.max(0, Math.round(data.percent)));
if (progressPercent) progressPercent.textContent = `${percent}%`;
if (progressBarFill) progressBarFill.style.width = `${percent}%`;
if (progressBar) progressBar.style.width = `${percent}%`;
}
if (data.speed && data.downloaded && data.total) {
const speedMB = (data.speed / 1024 / 1024).toFixed(2);
const downloadedMB = (data.downloaded / 1024 / 1024).toFixed(2);
const totalMB = (data.total / 1024 / 1024).toFixed(2);
if (progressSpeed) progressSpeed.textContent = `${speedMB} MB/s`;
if (progressSize) progressSize.textContent = `${downloadedMB} / ${totalMB} MB`;
}
}
function setupAnimations() {
document.body.style.opacity = '0';
document.body.style.transform = 'translateY(20px)';
setTimeout(() => {
document.body.style.transition = 'all 0.6s ease';
document.body.style.opacity = '1';
document.body.style.transform = 'translateY(0)';
}, 100);
const style = document.createElement('style');
style.textContent = `
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`;
document.head.appendChild(style);
}
function setupFirstLaunchHandlers() {
console.log('Setting up first launch handlers...');
window.electronAPI.onFirstLaunchUpdate((data) => {
console.log('Received first launch update event:', data);
showFirstLaunchUpdateDialog(data);
});
window.electronAPI.onFirstLaunchWelcome(() => {
});
window.electronAPI.onFirstLaunchProgress((data) => {
showProgress();
updateProgress(data);
});
window.electronAPI.onLockPlayButton((locked) => {
lockPlayButton(locked);
});
}
function showFirstLaunchUpdateDialog(data) {
console.log('Creating first launch modal...');
const existingModal = document.querySelector('.first-launch-modal-overlay');
if (existingModal) {
existingModal.remove();
}
const modalOverlay = document.createElement('div');
modalOverlay.className = 'first-launch-modal-overlay';
modalOverlay.style.cssText = `
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
background: rgba(0, 0, 0, 0.95) !important;
backdrop-filter: blur(10px) !important;
z-index: 999999 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
pointer-events: all !important;
`;
const modalDialog = document.createElement('div');
modalDialog.className = 'first-launch-modal-dialog';
modalDialog.style.cssText = `
background: #1a1a1a !important;
border-radius: 12px !important;
padding: 0 !important;
width: 500px !important;
max-width: 90vw !important;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.8) !important;
border: 1px solid rgba(147, 51, 234, 0.5) !important;
overflow: hidden !important;
animation: modalSlideIn 0.3s ease-out !important;
`;
modalDialog.innerHTML = `
<div style="background: linear-gradient(135deg, rgba(147, 51, 234, 0.2), rgba(59, 130, 246, 0.2)); padding: 25px; border-bottom: 1px solid rgba(255,255,255,0.1);">
<h2 style="margin: 0; color: #fff; font-size: 1.5rem; font-weight: 600; text-align: center;">
🔄 Game Update Required
</h2>
</div>
<div style="padding: 30px; color: #e5e7eb; line-height: 1.6;">
<div style="text-align: center; margin-bottom: 25px;">
<p style="font-size: 1.1rem; margin-bottom: 15px;">
An existing Hytale installation has been detected and must be updated to the latest version.
</p>
<p style="color: #10b981; font-weight: 500; margin-bottom: 20px;">
✅ Your game saves and settings will be preserved
</p>
</div>
<div style="background: rgba(59, 130, 246, 0.1); padding: 20px; border-radius: 8px; border-left: 4px solid #3b82f6; margin: 20px 0;">
<p style="margin: 8px 0; font-family: 'Courier New', monospace; font-size: 0.9em;">
<strong>📁 Location:</strong> ${data.existingGame.installPath}
</p>
<p style="margin: 8px 0; font-family: 'Courier New', monospace; font-size: 0.9em;">
<strong>💾 UserData:</strong> ${data.existingGame.hasUserData ? '✅ Found (will be preserved)' : '❌ Not found'}
</p>
</div>
<div style="background: rgba(234, 179, 8, 0.1); padding: 15px; border-radius: 8px; border-left: 4px solid #eab308; margin: 20px 0;">
<p style="margin: 0; color: #fbbf24; font-weight: 500; font-size: 0.95em;">
⚠️ This update is mandatory and cannot be skipped
</p>
</div>
</div>
<div style="padding: 25px; border-top: 1px solid rgba(255,255,255,0.1); text-align: center;">
<button id="updateGameBtn" style="
background: linear-gradient(135deg, #9333ea, #3b82f6) !important;
color: white !important;
border: none !important;
padding: 15px 30px !important;
border-radius: 8px !important;
font-size: 1rem !important;
font-weight: 600 !important;
cursor: pointer !important;
transition: all 0.2s ease !important;
min-width: 200px !important;
" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
🚀 Update Game Now
</button>
</div>
`;
modalOverlay.appendChild(modalDialog);
modalOverlay.onclick = (e) => {
if (e.target === modalOverlay) {
e.preventDefault();
e.stopPropagation();
return false;
}
};
document.addEventListener('keydown', function preventEscape(e) {
if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
return false;
}
});
document.body.appendChild(modalOverlay);
const updateBtn = document.getElementById('updateGameBtn');
updateBtn.onclick = () => {
acceptFirstLaunchUpdate();
};
window.firstLaunchExistingGame = data.existingGame;
console.log('First launch modal created and displayed');
}
function lockPlayButton(locked) {
const playButton = document.getElementById('homePlayBtn');
if (!playButton) {
console.warn('Play button not found');
return;
}
if (locked) {
playButton.style.opacity = '0.5';
playButton.style.pointerEvents = 'none';
playButton.style.cursor = 'not-allowed';
playButton.setAttribute('data-locked', 'true');
const spanElement = playButton.querySelector('span');
if (spanElement) {
if (!playButton.getAttribute('data-original-text')) {
playButton.setAttribute('data-original-text', spanElement.textContent);
}
spanElement.textContent = 'CHECKING...';
}
console.log('Play button locked');
} else {
playButton.style.opacity = '';
playButton.style.pointerEvents = '';
playButton.style.cursor = '';
playButton.removeAttribute('data-locked');
const spanElement = playButton.querySelector('span');
const originalText = playButton.getAttribute('data-original-text');
if (spanElement && originalText) {
spanElement.textContent = originalText;
playButton.removeAttribute('data-original-text');
}
console.log('Play button unlocked');
}
}
async function acceptFirstLaunchUpdate() {
const existingGame = window.firstLaunchExistingGame;
if (!existingGame) {
showNotification('Error: Game data not found', 'error');
return;
}
const modal = document.querySelector('.first-launch-modal-overlay');
if (modal) {
modal.style.pointerEvents = 'none';
const btn = document.getElementById('updateGameBtn');
if (btn) {
btn.style.opacity = '0.5';
btn.style.cursor = 'not-allowed';
btn.textContent = '🔄 Updating...';
}
}
try {
showProgress();
updateProgress({ message: 'Starting mandatory game update...', percent: 0 });
const result = await window.electronAPI.acceptFirstLaunchUpdate(existingGame);
window.electronAPI.markAsLaunched && window.electronAPI.markAsLaunched();
if (modal) {
modal.remove();
}
lockPlayButton(false);
if (result.success) {
hideProgress();
showNotification('Game updated successfully! 🎉', 'success');
} else {
hideProgress();
showNotification(`Update failed: ${result.error}`, 'error');
}
} catch (error) {
if (modal) {
modal.remove();
}
lockPlayButton(false);
hideProgress();
showNotification(`Update error: ${error.message}`, 'error');
}
}
function dismissFirstLaunchDialog() {
const modal = document.querySelector('.first-launch-modal-overlay');
if (modal) {
modal.remove();
}
lockPlayButton(false);
window.electronAPI.markAsLaunched && window.electronAPI.markAsLaunched();
}
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('show');
}, 100);
setTimeout(() => {
notification.remove();
}, 5000);
}
function setupUI() {
progressOverlay = document.getElementById('progressOverlay');
progressBar = document.getElementById('progressBar');
progressBarFill = document.getElementById('progressBarFill');
progressText = document.getElementById('progressText');
progressPercent = document.getElementById('progressPercent');
progressSpeed = document.getElementById('progressSpeed');
progressSize = document.getElementById('progressSize');
lockPlayButton(true);
handleNavigation();
setupWindowControls();
setupSidebarLogo();
setupAnimations();
setupFirstLaunchHandlers();
document.body.focus();
}
window.LauncherUI = {
showPage,
setActiveNav,
showLauncherOrInstall,
showProgress,
hideProgress,
updateProgress
};
document.addEventListener('DOMContentLoaded', setupUI);

View File

@@ -1,162 +0,0 @@
class ClientUpdateManager {
constructor() {
this.updatePopupVisible = false;
this.init();
}
init() {
window.electronAPI.onUpdatePopup((updateInfo) => {
this.showUpdatePopup(updateInfo);
});
this.checkForUpdatesOnDemand();
}
showUpdatePopup(updateInfo) {
if (this.updatePopupVisible) return;
this.updatePopupVisible = true;
const popupHTML = `
<div id="update-popup-overlay">
<div class="update-popup-container update-popup-pulse">
<div class="update-popup-header">
<div class="update-popup-icon">
<i class="fas fa-download"></i>
</div>
<h2 class="update-popup-title">
NEW UPDATE AVAILABLE
</h2>
</div>
<div class="update-popup-versions">
<div class="version-row">
<span class="version-label">Current Version:</span>
<span class="version-current">${updateInfo.currentVersion}</span>
</div>
<div class="version-row">
<span class="version-label">New Version:</span>
<span class="version-new">${updateInfo.newVersion}</span>
</div>
</div>
<div class="update-popup-message">
A new version of Hytale F2P Launcher is available.<br>
Please download the latest version to continue using the launcher.
</div>
<button id="update-download-btn" class="update-download-btn">
<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>
Download Update
</button>
<div class="update-popup-footer">
This popup cannot be closed until you update the launcher
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', popupHTML);
this.blockInterface();
const downloadBtn = document.getElementById('update-download-btn');
if (downloadBtn) {
downloadBtn.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
downloadBtn.disabled = true;
downloadBtn.innerHTML = '<i class="fas fa-spinner fa-spin" style="margin-right: 0.5rem;"></i>Opening GitHub...';
try {
await window.electronAPI.openDownloadPage();
console.log('✅ Download page opened, launcher will close...');
downloadBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Launcher closing...';
} catch (error) {
console.error('❌ Error opening download page:', error);
downloadBtn.disabled = false;
downloadBtn.innerHTML = '<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>Download Update';
}
});
}
const overlay = document.getElementById('update-popup-overlay');
if (overlay) {
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
e.preventDefault();
e.stopPropagation();
return false;
}
});
}
console.log('🔔 Update popup displayed with new style');
}
blockInterface() {
const mainContent = document.querySelector('.flex.w-full.h-screen');
if (mainContent) {
mainContent.classList.add('interface-blocked');
}
document.body.classList.add('no-select');
document.addEventListener('keydown', this.blockKeyEvents.bind(this), true);
document.addEventListener('contextmenu', this.blockContextMenu.bind(this), true);
console.log('🚫 Interface blocked for update');
}
blockKeyEvents(event) {
if (event.target.closest('#update-popup-overlay')) {
if ((event.key === 'Enter' || event.key === ' ') &&
event.target.id === 'update-download-btn') {
return;
}
if (event.key !== 'Tab') {
event.preventDefault();
event.stopPropagation();
}
return;
}
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
return false;
}
blockContextMenu(event) {
if (!event.target.closest('#update-popup-overlay')) {
event.preventDefault();
event.stopPropagation();
return false;
}
}
async checkForUpdatesOnDemand() {
try {
const updateInfo = await window.electronAPI.checkForUpdates();
if (updateInfo.updateAvailable) {
this.showUpdatePopup(updateInfo);
}
return updateInfo;
} catch (error) {
console.error('Error checking for updates:', error);
return { updateAvailable: false, error: error.message };
}
}
}
document.addEventListener('DOMContentLoaded', () => {
window.updateManager = new ClientUpdateManager();
});
window.ClientUpdateManager = ClientUpdateManager;

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +0,0 @@
[Desktop Entry]
Type=Application
Name=Hytale-F2P
Comment=A modern, cross-platform launcher for Hytale with automatic updates and multi-client support
Exec=/opt/Hytale-F2P/hytale-f2p-launcher
Categories=Game;
Icon=Hytale-F2P
Terminal=false
StartupNotify=true

View File

@@ -1,31 +0,0 @@
# Maintainer: Terromur <terromuroz@proton.me>
pkgname=Hytale-F2P-git
_pkgname=Hytale-F2P
pkgver=2.0.0.r47.gebcfdc4
pkgrel=1
pkgdesc="HyLauncher - unofficial Hytale Launcher for free to play gamers"
arch=('x86_64')
url="https://github.com/amiayweb/Hytale-F2P"
license=('custom')
makedepends=('npm')
source=("git+$url.git" "Hytale-F2P.desktop")
sha256sums=('SKIP' '46488fada4775d9976d7b7b62f8d1f1f8d9a9a9d8f8aa9af4f2e2153019f6a30')
pkgver() {
cd "$_pkgname"
printf "2.0.0.r%s.g%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
}
build() {
cd "$_pkgname"
npm install
npm run build:linux
}
package() {
mkdir -p "$pkgdir/opt/$_pkgname"
cp -r "$_pkgname/dist/linux-unpacked/"* "$pkgdir/opt/$_pkgname"
install -Dm644 "$_pkgname.desktop" "$pkgdir/usr/share/applications/$_pkgname.desktop"
install -Dm644 "$_pkgname/icon.png" "$pkgdir/usr/share/icons/hicolor/512x512/apps/$_pkgname.png"
}

204
README.md
View File

@@ -1,203 +1,7 @@
# 🎮 Hytale F2P Launcher | Multiplayer Support
This project has been taken down by poor hypixel studios.
<div align="center">
![Version](https://img.shields.io/badge/Version-2.0.0-green?style=for-the-badge)
![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey?style=for-the-badge)
![License](https://img.shields.io/badge/License-Educational-blue?style=for-the-badge)
[![Join Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white)](https://discord.gg/MHkEjepMQ7)
**A modern, cross-platform offline launcher for Hytale with automatic updates and multiplayer support (windows users & non-premium only)**
[![GitHub stars](https://img.shields.io/github/stars/amiayweb/Hytale-F2P?style=social)](https://github.com/amiayweb/Hytale-F2P/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/amiayweb/Hytale-F2P?style=social)](https://github.com/amiayweb/Hytale-F2P/network/members)
**If you find this project useful, please give it a star!**
🛑 **Found a problem? Open an issue! Im on Windows, so I cant test on macOS or Linux.** 🛑
</div>
---
## 📸 Screenshots
<div align="center">
![Hytale F2P Launcher](https://i.imgur.com/9iDuzST.png)
![Hytale F2P Mods](https://i.imgur.com/NaareIS.png)
![Hytale F2P News](https://i.imgur.com/n1nEqRS.png)
![Hytale F2P Chat](https://i.imgur.com/Y4hL3sx.png)
</div>
---
## ✨ Features
🎯 **Core Features**
- 🔄 **Automatic Updates** - Smart version checking and seamless game updates
- 💾 **Data Preservation** - Intelligent UserData backup and restoration during updates
- 🌐 **Cross-Platform** - Full support for Windows, Linux (X11/Wayland), and macOS
-**Java Management** - Automatic Java runtime detection and installation
- 🎮 **Multiplayer Support** - Automatic multiplayer client installation (Windows)
🛡️ **Advanced Features**
- 📁 **Custom Installation** - Choose your own installation directory
- 🔍 **Smart Detection** - Automatic game and dependency detection
- 🗂️ **Mod Support** - Built-in mod management system
- 💬 **Player Chat** - Integrated chat system for community interaction
- 📰 **News Feed** - Stay updated with the latest Hytale news
- 🎨 **Modern UI** - Clean, responsive interface with dark theme
---
## 🚀 Quick Start
### 📥 Installation
#### Windows
1. Download the latest `Hytale-F2P.exe` from [**Releases**](https://github.com/amiayweb/Hytale-F2P/releases)
2. Run the installer
3. Launch from desktop or start menu
#### Linux
See [BUILD.md](BUILD.md) for detailed build instructions.
#### macOS
See [BUILD.md](BUILD.md) for detailed build instructions.
#### 🖥️ How to create server (Windows Only)?
1. Download the server files directly from: `http://3.10.208.30:3002/server`
2. Replace the existing files in your `HytaleF2P` installation folder
3. Run the server launcher (.bat) to start hosting your own Hytale server
4. You will need a third party software like Radmin VPN (check on youtube how to use Radmin VPN).
### 🎮 Usage
1. **Enter your player name**
2. **Click "PLAY"**
3. **Automatic setup** - The launcher handles everything automatically
4. **Game launches** - Enjoy playing Hytale!
---
## 🛠️ Building from Source
See [BUILD.md](BUILD.md) for comprehensive build instructions.
---
## 📋 Changelog
### 🆕 v2.0.0 *(Latest)*
-**Automatic Game Update System** - Smart version checking and seamless updates
-**Partial Automatic Launcher Update System** - This will inform you when I release a new update.
- 🛡️ **UserData Preservation** - Intelligent backup/restore of game saves during updates
- 🐧 **Enhanced Linux Support** - Full Wayland and X11 compatibility
- 🔄 **Multiplayer Auto-Install** - Automatic multiplayer client setup on updates (Windows)
- 📡 **API Integration** - Real-time version checking and client management
- 🎨 **UI Improvements** - Added contributor credits footer
- 🔄 **Complete Launcher Overhaul** - Total redesign of the launcher architecture and interface
- 🗂️ **Integrated Mod Manager** - Built-in mod installation, management
- 💬 **Community Chat System** - Real-time chat for launcher users to connect and communicate
### 🔧 v1.0.1
- 📁 **Custom Installation** - Choose installation directory with file browser
- 🏠 **Always on Top** - Launcher stays visible during installation
- 🧠 **Smart Detection** - Automatic game detection and UI adaptation
- 🗑️ **Uninstall Feature** - Easy game removal with one click
- 🔄 **Dynamic UI** - "INSTALL" vs "PLAY" button based on game state
- 🛠️ **Path Management** - Proper custom directory handling
- 💫 **UI Polish** - Improved layout and overflow prevention
### 🎉 v1.0.0 *(Initial Release)*
- 🎮 **Offline Gameplay** - Play Hytale without internet connection
-**Auto Installation** - One-click game setup
-**Java Management** - Automatic Java runtime handling
- 🎨 **Modern Interface** - Clean, intuitive design
- 🌟 **First Release** - Core launcher functionality
---
## 👥 Contributors
<div align="center">
**Made with ❤️ by the community**
[![Contributors](https://contrib.rocks/image?repo=amiayweb/Hytale-F2P)](https://github.com/amiayweb/Hytale-F2P/graphs/contributors)
</div>
### 🏆 Project Creator
- [**@amiayweb**](https://github.com/amiayweb) - *Lead Developer & Project Creator*
### 🌟 Contributors
- [**@chasem-dev**](https://github.com/chasem-dev) - *Issues fixer*
- [**@crimera**](https://github.com/crimera) - *Issues fixer*
- [**@sanasol**](https://github.com/sanasol) - *Issues fixer*
- [**@Terromur**](https://github.com/Terromur) - *Issues fixer*
- [**@Citeli-py**](https://github.com/Citeli-py) - *Issues fixer*
- [**@ericiskoolbeans**](https://github.com/ericiskoolbeans) - *Beta Tester*
---
## 📊 GitHub Stats
<div align="center">
![GitHub stars](https://img.shields.io/github/stars/amiayweb/Hytale-F2P?style=for-the-badge&logo=github)
![GitHub forks](https://img.shields.io/github/forks/amiayweb/Hytale-F2P?style=for-the-badge&logo=github)
![GitHub issues](https://img.shields.io/github/issues/amiayweb/Hytale-F2P?style=for-the-badge&logo=github)
![GitHub downloads](https://img.shields.io/github/downloads/amiayweb/Hytale-F2P/total?style=for-the-badge&logo=github)
</div>
## 📞 Support
<div align="center">
[![GitHub Issues](https://img.shields.io/badge/GitHub-Issues-red?style=for-the-badge&logo=github)](https://github.com/amiayweb/Hytale-F2P/issues)
[![Discussions](https://img.shields.io/badge/GitHub-Discussions-blue?style=for-the-badge&logo=github)](https://github.com/amiayweb/Hytale-F2P/discussions)
**Need help?** Open an [issue](https://github.com/amiayweb/Hytale-F2P/issues) or start a [discussion](https://github.com/amiayweb/Hytale-F2P/discussions)!
</div>
---
## ⚖️ Legal Disclaimer
<div align="center">
⚠️ **Important Notice** ⚠️
</div>
This launcher is created for **educational purposes only**.
🏛️ **Not Official** - This is an independent fan project **not affiliated with, endorsed by, or associated with** Hypixel Studios or Hytale.
🛡️ **No Warranty** - This software is provided **"as is"** without any warranty of any kind.
📝 **Responsibility** - The authors take no responsibility for how this software is used.
🛑 **Takedown Policy** - If Hypixel Studios or Hytale requests removal, this project will be taken down immediately.
❤️ **Support Official** - Please support the official game by purchasing it when available.
---
## 📬 Contact
[![Discord](https://img.shields.io/badge/Discord-amiay3-5865F2?logo=discord&logoColor=white)](https://discord.com/users/1433515183606599873)
<div align="center">
**⭐ Star this project if you found it helpful! ⭐**
*Made with ❤️ by [@amiayweb](https://github.com/amiayweb) and the amazing community*
[![Star History Chart](https://api.star-history.com/svg?repos=amiayweb/Hytale-F2P&type=date&legend=top-left)](https://www.star-history.com/#amiayweb/Hytale-F2P&type=date&legend=top-left)
</div>
We still owning the HF2P community this is not illegal so cry.
https://discord.gg/mzdfCJy5J
https://t.me/hf2p_og

File diff suppressed because it is too large Load Diff

View File

@@ -1,73 +0,0 @@
const axios = require('axios');
const UPDATE_CHECK_URL = 'http://3.10.208.30:3002/api/version_launcher';
const CURRENT_VERSION = '2.0.0';
const GITHUB_DOWNLOAD_URL = 'https://github.com/amiayweb/Hytale-F2P/';
class UpdateManager {
constructor() {
this.updateAvailable = false;
this.remoteVersion = null;
}
async checkForUpdates() {
try {
console.log('Checking for updates...');
console.log(`Local version: ${CURRENT_VERSION}`);
const response = await axios.get(UPDATE_CHECK_URL, {
timeout: 5000,
headers: {
'User-Agent': 'Hytale-F2P-Launcher'
}
});
if (response.data && response.data.launcher_version) {
this.remoteVersion = response.data.launcher_version;
console.log(`Remote version: ${this.remoteVersion}`);
if (this.remoteVersion !== CURRENT_VERSION) {
this.updateAvailable = true;
console.log('Update available!');
return {
updateAvailable: true,
currentVersion: CURRENT_VERSION,
newVersion: this.remoteVersion,
downloadUrl: GITHUB_DOWNLOAD_URL
};
} else {
console.log('Launcher is up to date');
return {
updateAvailable: false,
currentVersion: CURRENT_VERSION,
newVersion: this.remoteVersion
};
}
} else {
throw new Error('Invalid API response');
}
} catch (error) {
console.error('Error checking for updates:', error.message);
return {
updateAvailable: false,
error: error.message,
currentVersion: CURRENT_VERSION
};
}
}
getDownloadUrl() {
return GITHUB_DOWNLOAD_URL;
}
getUpdateInfo() {
return {
updateAvailable: this.updateAvailable,
currentVersion: CURRENT_VERSION,
remoteVersion: this.remoteVersion,
downloadUrl: this.getDownloadUrl()
};
}
}
module.exports = UpdateManager;

BIN
icon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

566
main.js
View File

@@ -1,566 +0,0 @@
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
const path = require('path');
const fs = require('fs');
const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveChatUsername, loadChatUsername, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, isGameInstalled, uninstallGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched } = require('./backend/launcher');
const UpdateManager = require('./backend/updateManager');
let mainWindow;
let updateManager;
let discordRPC = null;
// Discord Rich Presence setup
const DISCORD_CLIENT_ID = '1462244937868513373';
function initDiscordRPC() {
try {
const { Client } = require('discord-rpc');
discordRPC = new Client({ transport: 'ipc' });
discordRPC.on('ready', () => {
console.log('Discord RPC connected');
setDiscordActivity();
});
discordRPC.on('disconnected', () => {
console.log('Discord RPC disconnected');
});
discordRPC.login({ clientId: DISCORD_CLIENT_ID }).catch(err => {
console.log('Failed to connect to Discord:', err.message);
});
} catch (error) {
console.log('Discord RPC module not available:', error.message);
}
}
function setDiscordActivity() {
if (!discordRPC) return;
try {
discordRPC.setActivity({
details: 'Using HytaleF2P',
startTimestamp: Date.now(),
largeImageKey: 'hytale_logo',
largeImageText: 'Hytale F2P Launcher',
buttons: [
{
label: 'GitHub',
url: 'https://github.com/amiayweb/Hytale-F2P'
}
]
});
} catch (error) {
console.error('Failed to set Discord activity:', error.message);
}
}
function createWindow() {
mainWindow = new BrowserWindow({
width: 1280,
height: 720,
frame: false,
resizable: false,
alwaysOnTop: false,
backgroundColor: '#090909',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
devTools: false,
webSecurity: true
}
});
mainWindow.loadFile('GUI/index.html');
// Initialize Discord Rich Presence
initDiscordRPC();
updateManager = new UpdateManager();
setTimeout(async () => {
const updateInfo = await updateManager.checkForUpdates();
if (updateInfo.updateAvailable) {
mainWindow.webContents.send('show-update-popup', updateInfo);
}
}, 3000);
mainWindow.webContents.on('devtools-opened', () => {
mainWindow.webContents.closeDevTools();
});
mainWindow.webContents.on('before-input-event', (event, input) => {
if (input.control && input.shift && input.key.toLowerCase() === 'i') {
event.preventDefault();
}
if (input.control && input.shift && input.key.toLowerCase() === 'j') {
event.preventDefault();
}
if (input.control && input.shift && input.key.toLowerCase() === 'c') {
event.preventDefault();
}
if (input.key === 'F12') {
event.preventDefault();
}
if (input.key === 'F5') {
event.preventDefault();
}
});
mainWindow.webContents.on('context-menu', (e) => {
e.preventDefault();
});
mainWindow.webContents.setIgnoreMenuShortcuts(true);
}
app.whenReady().then(async () => {
createWindow();
setTimeout(async () => {
try {
console.log('Starting first launch check...');
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('lock-play-button', true);
}
const progressCallback = (message, percent, speed, downloaded, total) => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('first-launch-progress', { message, percent, speed, downloaded, total });
}
};
const firstLaunchResult = await handleFirstLaunchCheck(progressCallback);
console.log('First launch check result:', firstLaunchResult);
if (mainWindow && !mainWindow.isDestroyed()) {
if (firstLaunchResult.needsUpdate && firstLaunchResult.existingGame) {
console.log('Sending show-first-launch-update event...');
setTimeout(() => {
mainWindow.webContents.send('show-first-launch-update', {
existingGame: firstLaunchResult.existingGame,
isFirstLaunch: firstLaunchResult.isFirstLaunch
});
}, 1000);
} else if (firstLaunchResult.isFirstLaunch && !firstLaunchResult.existingGame) {
console.log('Sending show-first-launch-welcome event...');
setTimeout(() => {
mainWindow.webContents.send('show-first-launch-welcome');
}, 1000);
} else {
mainWindow.webContents.send('lock-play-button', false);
}
}
} catch (error) {
console.error('Error during first launch check:', error);
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('lock-play-button', false);
}
}
}, 3000);
});
app.on('window-all-closed', () => {
// Clean up Discord RPC connection
if (discordRPC) {
try {
discordRPC.destroy();
} catch (error) {
console.log('Error cleaning up Discord RPC:', error.message);
}
}
if (process.platform !== 'darwin') {
app.quit();
}
});
ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath) => {
try {
const progressCallback = (message, percent, speed, downloaded, total) => {
if (mainWindow && !mainWindow.isDestroyed()) {
const data = {
message: message || null,
percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null,
speed: speed !== null && speed !== undefined ? speed : null,
downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null,
total: total !== null && total !== undefined ? total : null
};
mainWindow.webContents.send('progress-update', data);
}
};
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath);
return result;
} catch (error) {
console.error('Launch error:', error);
const errorMessage = error.message || error.toString();
return { success: false, error: errorMessage };
}
});
ipcMain.handle('install-game', async (event, playerName, javaPath, installPath) => {
try {
const progressCallback = (message, percent, speed, downloaded, total) => {
if (mainWindow && !mainWindow.isDestroyed()) {
const data = {
message: message || null,
percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null,
speed: speed !== null && speed !== undefined ? speed : null,
downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null,
total: total !== null && total !== undefined ? total : null
};
mainWindow.webContents.send('progress-update', data);
}
};
const result = await installGame(playerName, progressCallback, javaPath, installPath);
return result;
} catch (error) {
console.error('Install error:', error);
const errorMessage = error.message || error.toString();
return { success: false, error: errorMessage };
}
});
ipcMain.handle('save-username', (event, username) => {
saveUsername(username);
return { success: true };
});
ipcMain.handle('load-username', () => {
return loadUsername();
});
ipcMain.handle('save-chat-username', async (event, chatUsername) => {
saveChatUsername(chatUsername);
});
ipcMain.handle('load-chat-username', async () => {
return loadChatUsername();
});
ipcMain.handle('save-java-path', (event, javaPath) => {
saveJavaPath(javaPath);
return { success: true };
});
ipcMain.handle('load-java-path', () => {
return loadJavaPath();
});
ipcMain.handle('save-install-path', (event, installPath) => {
saveInstallPath(installPath);
return { success: true };
});
ipcMain.handle('load-install-path', () => {
return loadInstallPath();
});
ipcMain.handle('select-install-path', async () => {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openDirectory'],
title: 'Select Installation Folder'
});
if (!result.canceled && result.filePaths.length > 0) {
return result.filePaths[0];
}
return null;
});
ipcMain.handle('accept-first-launch-update', async (event, existingGame) => {
try {
const progressCallback = (message, percent, speed, downloaded, total) => {
if (mainWindow && !mainWindow.isDestroyed()) {
const data = {
message: message || null,
percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null,
speed: speed !== null && speed !== undefined ? speed : null,
downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null,
total: total !== null && total !== undefined ? total : null
};
mainWindow.webContents.send('first-launch-progress', data);
}
};
const result = await proposeGameUpdate(existingGame, progressCallback);
return result;
} catch (error) {
console.error('First launch update error:', error);
const errorMessage = error.message || error.toString();
return { success: false, error: errorMessage };
}
});
ipcMain.handle('mark-as-launched', async () => {
try {
markAsLaunched();
return { success: true };
} catch (error) {
console.error('Mark as launched error:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('is-game-installed', () => {
return isGameInstalled();
});
ipcMain.handle('uninstall-game', async () => {
try {
await uninstallGame();
return { success: true };
} catch (error) {
console.error('Uninstall error:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('get-hytale-news', async () => {
try {
const news = await getHytaleNews();
return news;
} catch (error) {
console.error('News fetch error:', error);
return [];
}
});
ipcMain.handle('open-external', async (event, url) => {
try {
await shell.openExternal(url);
return { success: true };
} catch (error) {
console.error('Failed to open external URL:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('browse-java-path', async () => {
const isWindows = process.platform === 'win32';
const isMac = process.platform === 'darwin';
let dialogOptions;
if (isWindows) {
dialogOptions = {
properties: ['openFile'],
title: 'Select Java Executable',
filters: [
{ name: 'Java Executable', extensions: ['exe'] },
{ name: 'All Files', extensions: ['*'] }
]
};
} else if (isMac) {
dialogOptions = {
properties: ['openFile'],
title: 'Select Java Executable',
message: 'Select java executable (usually in /Library/Java/JavaVirtualMachines/*/Contents/Home/bin/java)',
filters: [
{ name: 'All Files', extensions: ['*'] }
]
};
} else {
dialogOptions = {
properties: ['openFile'],
title: 'Select Java Executable',
message: 'Select java executable (usually /usr/bin/java or similar)',
filters: [
{ name: 'All Files', extensions: ['*'] }
]
};
}
const result = await dialog.showOpenDialog(mainWindow, dialogOptions);
if (!result.canceled && result.filePaths.length > 0) {
return result.filePaths[0];
}
return null;
});
ipcMain.handle('save-settings', async (event, settings) => {
try {
if (settings.playerName) saveUsername(settings.playerName);
if (settings.javaPath !== undefined) saveJavaPath(settings.javaPath);
if (settings.installPath !== undefined) saveInstallPath(settings.installPath);
return { success: true };
} catch (error) {
console.error('Save settings error:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('load-settings', async () => {
try {
return {
playerName: loadUsername() || 'Player',
javaPath: loadJavaPath() || '',
installPath: loadInstallPath() || '',
customInstall: false
};
} catch (error) {
console.error('Load settings error:', error);
return {
playerName: 'Player',
javaPath: '',
installPath: '',
customInstall: false
};
}
});
const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod } = require('./backend/launcher');
const os = require('os');
ipcMain.handle('get-local-app-data', async () => {
return process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
});
ipcMain.handle('get-user-id', async () => {
try {
const { getOrCreatePlayerId } = require('./backend/launcher');
return await getOrCreatePlayerId();
} catch (error) {
console.error('Error getting user ID:', error);
return null;
}
});
ipcMain.handle('load-installed-mods', async (event, modsPath) => {
try {
return await loadInstalledMods(modsPath);
} catch (error) {
console.error('Error loading installed mods:', error);
return [];
}
});
ipcMain.handle('openExternalLink', async (event, url) => {
try {
console.log('Opening external URL:', url);
await shell.openExternal(url);
return { success: true };
} catch (error) {
console.error('Error opening external link:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('download-mod', async (event, modInfo) => {
try {
return await downloadMod(modInfo);
} catch (error) {
console.error('Error downloading mod:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('uninstall-mod', async (event, modId, modsPath) => {
try {
return await uninstallMod(modId, modsPath);
} catch (error) {
console.error('Error uninstalling mod:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('toggle-mod', async (event, modId, modsPath) => {
try {
return await toggleMod(modId, modsPath);
} catch (error) {
console.error('Error toggling mod:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('get-mods-path', async () => {
try {
return await getModsPath();
} catch (error) {
console.error('Error getting mods path:', error);
return null;
}
});
ipcMain.handle('select-mod-files', async () => {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openFile', 'multiSelections'],
title: 'Select Mod Files',
filters: [
{ name: 'Mod Files', extensions: ['jar', 'zip'] },
{ name: 'All Files', extensions: ['*'] }
]
});
if (!result.canceled && result.filePaths.length > 0) {
return result.filePaths;
}
return null;
});
ipcMain.handle('copy-mod-file', async (event, sourcePath, modsPath) => {
try {
const fileName = path.basename(sourcePath);
const destPath = path.join(modsPath, fileName);
fs.copyFileSync(sourcePath, destPath);
return { success: true, fileName };
} catch (error) {
console.error('Error copying mod file:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('check-for-updates', async () => {
try {
return await updateManager.checkForUpdates();
} catch (error) {
console.error('Error checking for updates:', error);
return { updateAvailable: false, error: error.message };
}
});
ipcMain.handle('open-download-page', async () => {
try {
await shell.openExternal(updateManager.getDownloadUrl());
setTimeout(() => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.close();
}
}, 1000);
return { success: true };
} catch (error) {
console.error('Error opening download page:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('get-update-info', async () => {
return updateManager.getUpdateInfo();
});
ipcMain.handle('window-close', () => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.close();
}
});
ipcMain.handle('window-minimize', () => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.minimize();
}
});

1
package-lock.json generated
View File

@@ -1 +0,0 @@

View File

@@ -1,121 +0,0 @@
{
"name": "hytale-f2p-launcher",
"version": "2.0.0",
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
"homepage": "https://github.com/amiayweb/Hytale-F2P",
"main": "main.js",
"scripts": {
"start": "electron .",
"dev": "electron . --dev",
"build": "electron-builder",
"build:win": "electron-builder --win",
"build:linux": "electron-builder --linux",
"build:mac": "electron-builder --mac",
"build:all": "electron-builder --win --linux --mac"
},
"keywords": [
"hytale",
"launcher",
"game",
"client",
"cross-platform",
"electron",
"auto-update",
"mod-manager",
"chat"
],
"author": {
"name": "AMIAY",
"email": "support@amiay.dev"
},
"license": "MIT",
"devDependencies": {
"electron": "^40.0.0",
"electron-builder": "^26.4.0"
},
"dependencies": {
"adm-zip": "^0.5.10",
"axios": "^1.6.0",
"discord-rpc": "^4.0.1",
"tar": "6.2.1",
"uuid": "^9.0.1"
},
"overrides": {
"tar": "$tar"
},
"build": {
"appId": "com.hytalef2p.launcher",
"productName": "Hytale F2P",
"directories": {
"output": "dist"
},
"files": [
"main.js",
"preload.js",
"backend/**/*",
"GUI/**/*",
"package.json"
],
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
},
{
"target": "portable",
"arch": [
"x64"
]
}
],
"icon": "icon.ico"
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": [
"x64"
]
},
{
"target": "deb",
"arch": [
"x64"
]
}
],
"icon": "build/icon.png",
"category": "Game",
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support"
},
"mac": {
"target": [
{
"target": "dmg",
"arch": [
"universal"
]
},
{
"target": "zip",
"arch": [
"universal"
]
}
],
"icon": "build/icon.icns",
"category": "public.app-category.games"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true
}
}
}

View File

@@ -1,58 +0,0 @@
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
launchGame: (playerName, javaPath, installPath) => ipcRenderer.invoke('launch-game', playerName, javaPath, installPath),
installGame: (playerName, javaPath, installPath) => ipcRenderer.invoke('install-game', playerName, javaPath, installPath),
closeWindow: () => ipcRenderer.invoke('window-close'),
minimizeWindow: () => ipcRenderer.invoke('window-minimize'),
saveUsername: (username) => ipcRenderer.invoke('save-username', username),
loadUsername: () => ipcRenderer.invoke('load-username'),
saveChatUsername: (chatUsername) => ipcRenderer.invoke('save-chat-username', chatUsername),
loadChatUsername: () => ipcRenderer.invoke('load-chat-username'),
saveJavaPath: (javaPath) => ipcRenderer.invoke('save-java-path', javaPath),
loadJavaPath: () => ipcRenderer.invoke('load-java-path'),
saveInstallPath: (installPath) => ipcRenderer.invoke('save-install-path', installPath),
loadInstallPath: () => ipcRenderer.invoke('load-install-path'),
selectInstallPath: () => ipcRenderer.invoke('select-install-path'),
browseJavaPath: () => ipcRenderer.invoke('browse-java-path'),
isGameInstalled: () => ipcRenderer.invoke('is-game-installed'),
uninstallGame: () => ipcRenderer.invoke('uninstall-game'),
getHytaleNews: () => ipcRenderer.invoke('get-hytale-news'),
openExternal: (url) => ipcRenderer.invoke('open-external', url),
openExternalLink: (url) => ipcRenderer.invoke('openExternalLink', url),
saveSettings: (settings) => ipcRenderer.invoke('save-settings', settings),
loadSettings: () => ipcRenderer.invoke('load-settings'),
getLocalAppData: () => ipcRenderer.invoke('get-local-app-data'),
getModsPath: () => ipcRenderer.invoke('get-mods-path'),
loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath),
downloadMod: (modInfo) => ipcRenderer.invoke('download-mod', modInfo),
uninstallMod: (modId, modsPath) => ipcRenderer.invoke('uninstall-mod', modId, modsPath),
toggleMod: (modId, modsPath) => ipcRenderer.invoke('toggle-mod', modId, modsPath),
selectModFiles: () => ipcRenderer.invoke('select-mod-files'),
copyModFile: (sourcePath, modsPath) => ipcRenderer.invoke('copy-mod-file', sourcePath, modsPath),
onProgressUpdate: (callback) => {
ipcRenderer.on('progress-update', (event, data) => callback(data));
},
getUserId: () => ipcRenderer.invoke('get-user-id'),
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
openDownloadPage: () => ipcRenderer.invoke('open-download-page'),
getUpdateInfo: () => ipcRenderer.invoke('get-update-info'),
onUpdatePopup: (callback) => {
ipcRenderer.on('show-update-popup', (event, data) => callback(data));
},
acceptFirstLaunchUpdate: (existingGame) => ipcRenderer.invoke('accept-first-launch-update', existingGame),
markAsLaunched: () => ipcRenderer.invoke('mark-as-launched'),
onFirstLaunchUpdate: (callback) => {
ipcRenderer.on('show-first-launch-update', (event, data) => callback(data));
},
onFirstLaunchWelcome: (callback) => {
ipcRenderer.on('show-first-launch-welcome', () => callback());
},
onFirstLaunchProgress: (callback) => {
ipcRenderer.on('first-launch-progress', (event, data) => callback(data));
},
onLockPlayButton: (callback) => {
ipcRenderer.on('lock-play-button', (event, locked) => callback(locked));
}
});