Compare commits

..

1 Commits

Author SHA1 Message Date
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
95 changed files with 857 additions and 24833 deletions

View File

@@ -36,8 +36,7 @@ This Code of Conduct applies within all community spaces, and also applies when
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [Community Chat, message Founders/Devs](https://chat.sanhost.net/invite/Tfz4jCK4). Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [Discord Server, message Founders/Devs](https://discord.gg/hf2pdc). All complaints will be reviewed and investigated promptly and fairly.
<!-- Discord: https://discord.gg/Fhbb9Yk5WW --> All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident. All community leaders are obligated to respect the privacy and security of the reporter of any incident.

View File

@@ -22,7 +22,7 @@ body:
value: | value: |
If you need help or support with using the launcher, please fill out this support request. If you need help or support with using the launcher, please fill out this support request.
Provide as much detail as possible so we can assist you effectively. Provide as much detail as possible so we can assist you effectively.
**Need a quick assistance?** Join our [TG Group](https://t.me/sanhostnet) | [TG Channel](https://t.me/hf2p_og) | [Community Chat](https://chat.sanhost.net/invite/Tfz4jCK4)! **Need a quick assistance?** Please Open-A-Ticket in our [Discord Server](https://discord.gg/gME8rUy3MB)!
- type: textarea - type: textarea
id: question id: question

View File

@@ -6,139 +6,201 @@ on:
- 'v*' - 'v*'
workflow_dispatch: workflow_dispatch:
env:
# Domain for small API calls (goes through Cloudflare - fine for <100MB)
FORGEJO_API: https://git.sanhost.net/api/v1
# Direct upload URL (bypasses Cloudflare for large files) - set in repo secrets
FORGEJO_UPLOAD: ${{ secrets.FORGEJO_UPLOAD_URL }}
jobs: jobs:
create-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Draft Release
run: |
curl -s -X POST "${FORGEJO_API}/repos/${GITHUB_REPOSITORY}/releases" \
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"${{ github.ref_name }}\",\"name\":\"${{ github.ref_name }}\",\"body\":\"Release ${{ github.ref_name }}\",\"draft\":true,\"prerelease\":false}" \
-o release.json
cat release.json
echo "RELEASE_ID=$(cat release.json | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')" >> $GITHUB_ENV
build-windows: build-windows:
needs: [create-release] runs-on: windows-latest
runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Wine for cross-compilation
run: |
sudo dpkg --add-architecture i386
sudo mkdir -pm755 /etc/apt/keyrings
sudo wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key
sudo wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/$(lsb_release -cs)/winehq-$(lsb_release -cs).sources
sudo apt-get update
sudo apt-get install -y --install-recommends winehq-stable
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: '22' node-version: '22'
cache: 'npm'
- run: npm ci - run: npm ci
- name: Build Windows Packages - name: Create Virtual .env File
run: npx electron-builder --win --publish never --config.npmRebuild=false # Because main.js needed physical env, we need to create virtual one to store it
- name: Upload to Release
run: | run: |
RELEASE_ID=$(curl -s "${FORGEJO_API}/repos/${GITHUB_REPOSITORY}/releases/tags/${{ github.ref_name }}" \ $env_content = @"
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])') HF2P_PROXY_URL=${{ secrets.HF2P_PROXY_URL }}
echo "Release ID: ${RELEASE_ID}" HF2P_SECRET_KEY=${{ secrets.HF2P_SECRET_KEY }}
echo "Upload URL: ${FORGEJO_UPLOAD}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets" "@
for file in dist/*.exe dist/*.exe.blockmap dist/latest.yml; do Set-Content -Path .env -Value $env_content
[ -f "$file" ] || continue
echo "Uploading $file ($(stat -c%s "$file" 2>/dev/null || stat -f%z "$file") bytes)..." - name: Build Windows Packages
HTTP_CODE=$(curl -w '%{http_code}' --max-time 600 -X POST "${FORGEJO_UPLOAD}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=$(basename $file)" \ run: npx electron-builder --win --publish never
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \ - uses: actions/upload-artifact@v4
-F "attachment=@${file}" -o /tmp/upload_response.txt 2>/tmp/upload_err.txt) with:
if [ "$HTTP_CODE" = "201" ]; then name: windows-builds
echo "OK — uploaded $(basename $file)" path: |
else dist/*.exe
echo "FAILED (HTTP $HTTP_CODE): $(cat /tmp/upload_response.txt)" dist/*.exe.blockmap
echo "Curl stderr: $(cat /tmp/upload_err.txt)" dist/latest.yml
fi
done
build-macos: build-macos:
needs: [create-release]
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: '22' node-version: '22'
cache: 'npm'
- run: npm ci - run: npm ci
- name: Create Virtual .env File
run: |
cat << EOF > .env
HF2P_PROXY_URL=${{ secrets.HF2P_PROXY_URL }}
HF2P_SECRET_KEY=${{ secrets.HF2P_SECRET_KEY }}
EOF
- name: Build macOS Packages - name: Build macOS Packages
env: env:
# Code signing
CSC_LINK: ${{ secrets.CSC_LINK }} CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
# Notarization
APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: npx electron-builder --mac --publish never run: npx electron-builder --mac --publish never
- uses: actions/upload-artifact@v4
- name: Upload to Release with:
run: | name: macos-builds
RELEASE_ID=$(curl -s "${FORGEJO_API}/repos/${GITHUB_REPOSITORY}/releases/tags/${{ github.ref_name }}" \ path: |
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])') dist/*.dmg
echo "Release ID: ${RELEASE_ID}" dist/*.zip
for file in dist/*.dmg dist/*.zip dist/*.blockmap dist/latest-mac.yml; do dist/*.blockmap
[ -f "$file" ] || continue dist/latest-mac.yml
echo "Uploading $file..."
HTTP_CODE=$(curl -w '%{http_code}' --max-time 600 -X POST "${FORGEJO_UPLOAD}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=$(basename $file)" \
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
-F "attachment=@${file}" -o /tmp/upload_response.txt 2>/tmp/upload_err.txt)
if [ "$HTTP_CODE" = "201" ]; then
echo "OK — uploaded $(basename $file)"
else
echo "FAILED (HTTP $HTTP_CODE): $(cat /tmp/upload_response.txt)"
echo "Curl stderr: $(cat /tmp/upload_err.txt)"
fi
done
build-linux: build-linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [create-release]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install build dependencies - name: Install build dependencies
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libarchive-tools rpm sudo apt-get install -y libarchive-tools
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: '22' node-version: '22'
cache: 'npm'
- run: npm ci - run: npm ci
- name: Build Linux Packages - name: Create Virtual .env File
run: npx electron-builder --linux AppImage deb rpm pacman --publish never
- name: Upload to Release
run: | run: |
RELEASE_ID=$(curl -s "${FORGEJO_API}/repos/${GITHUB_REPOSITORY}/releases/tags/${{ github.ref_name }}" \ cat << EOF > .env
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])') HF2P_PROXY_URL=${{ secrets.HF2P_PROXY_URL }}
echo "Release ID: ${RELEASE_ID}" HF2P_SECRET_KEY=${{ secrets.HF2P_SECRET_KEY }}
for file in dist/*.AppImage dist/*.AppImage.blockmap dist/*.deb dist/*.rpm dist/*.pacman dist/latest-linux.yml; do EOF
[ -f "$file" ] || continue
echo "Uploading $file ($(stat -c%s "$file" 2>/dev/null || stat -f%z "$file") bytes)..." - name: Build Linux Packages
HTTP_CODE=$(curl -w '%{http_code}' --max-time 600 -X POST "${FORGEJO_UPLOAD}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=$(basename $file)" \ run: |
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \ npx electron-builder --linux AppImage deb rpm --publish never
-F "attachment=@${file}" -o /tmp/upload_response.txt 2>/tmp/upload_err.txt) - uses: actions/upload-artifact@v4
if [ "$HTTP_CODE" = "201" ]; then with:
echo "OK — uploaded $(basename $file)" name: linux-builds
else path: |
echo "FAILED (HTTP $HTTP_CODE): $(cat /tmp/upload_response.txt)" dist/*.AppImage
echo "Curl stderr: $(cat /tmp/upload_err.txt)" dist/*.AppImage.blockmap
fi dist/*.deb
done dist/*.rpm
dist/latest-linux.yml
build-arch:
runs-on: ubuntu-latest
container:
image: archlinux:latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install base packages
run: |
pacman -Syu --noconfirm
pacman -S --noconfirm \
base-devel \
git \
nodejs \
npm \
rpm-tools \
libxcrypt-compat
- name: Create build user
run: |
useradd -m builder
echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
- name: Fix Permissions
run: chown -R builder:builder .
- name: Build Arch Package
run: |
sudo -u builder bash << 'EOF'
set -e
cat << EOP > .env
HF2P_PROXY_URL=${{ secrets.HF2P_PROXY_URL }}
HF2P_SECRET_KEY=${{ secrets.HF2P_SECRET_KEY }}
EOP
makepkg --printsrcinfo > .SRCINFO
makepkg -s --noconfirm
EOF
- name: Fix permissions for upload
if: always()
run: |
sudo chown -R $(id -u):$(id -g) .
- name: Upload Arch Package
uses: actions/upload-artifact@v4
with:
name: arch-package
path: |
*.pkg.tar.zst
.SRCINFO
release:
needs: [build-windows, build-macos, build-linux, build-arch]
runs-on: ubuntu-latest
if: |
startsWith(github.ref, 'refs/tags/v') ||
github.ref == 'refs/heads/main' ||
github.event_name == 'workflow_dispatch'
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Display structure of downloaded files
run: ls -R artifacts
- name: Get version from package.json
id: pkg_version
run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
files: |
artifacts/arch-package/*.pkg.tar.zst
artifacts/arch-package/.SRCINFO
artifacts/linux-builds/**/*
artifacts/windows-builds/**/*
artifacts/macos-builds/**/*
generate_release_notes: true
draft: true
prerelease: false

3
.gitignore vendored
View File

@@ -17,9 +17,6 @@ dist/
# Project Specific: Downloaded patcher (from hytale-auth-server) # Project Specific: Downloaded patcher (from hytale-auth-server)
backend/patcher/ backend/patcher/
# Private docs (local only)
docs/PATCH_CDN_INFRASTRUCTURE.md
# macOS Specific # macOS Specific
.DS_Store .DS_Store
*.zst.DS_Store *.zst.DS_Store

View File

@@ -8,10 +8,9 @@
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> <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 rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link <link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Noto+Sans+Arabic:wght@300;400;500;600;700&display=swap" 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"> rel="stylesheet">
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="style-RTL.css">
</head> </head>
<body class="bg-black text-white overflow-hidden font-sans select-none" tabindex="-1"> <body class="bg-black text-white overflow-hidden font-sans select-none" tabindex="-1">
@@ -57,8 +56,8 @@
<span class="nav-tooltip">Logs</span> <span class="nav-tooltip">Logs</span>
</div> </div>
<div class="nav-item" onclick="openDiscordExternal()"> <div class="nav-item" onclick="openDiscordExternal()">
<i class="fas fa-comments"></i> <i class="fab fa-discord"></i>
<span class="nav-tooltip">Community Chat</span> <span class="nav-tooltip">Discord</span>
</div> </div>
</div> </div>
@@ -73,40 +72,22 @@
<span id="onlineCount" class="counter-value">0</span> <span id="onlineCount" class="counter-value">0</span>
</div> </div>
<div class="identity-selector" id="identitySelector">
<button class="identity-btn" onclick="toggleIdentityDropdown()">
<i class="fas fa-id-badge"></i>
<span id="currentIdentityName">Player</span>
<i class="fas fa-chevron-down"></i>
</button>
<div class="identity-dropdown" id="identityDropdown">
<div class="identity-list" id="identityList"></div>
<div class="identity-divider"></div>
<div class="identity-action" onclick="openIdentityManager()">
<i class="fas fa-fingerprint"></i>
<span data-i18n="header.manageIdentities">Manage</span>
</div>
</div>
<span class="header-tooltip" data-i18n="header.identityTooltip">Your player name &amp; UUID used in-game</span>
</div>
<div class="profile-selector" id="profileSelector"> <div class="profile-selector" id="profileSelector">
<button class="profile-btn" onclick="toggleProfileDropdown()"> <button class="profile-btn" onclick="toggleProfileDropdown()">
<i class="fas fa-sliders-h"></i> <i class="fas fa-user-circle"></i>
<span id="currentProfileName">Default</span> <span id="currentProfileName">Default</span>
<i class="fas fa-chevron-down"></i> <i class="fas fa-chevron-down"></i>
</button> </button>
<div class="profile-dropdown" id="profileDropdown"> <div class="profile-dropdown" id="profileDropdown">
<div class="profile-list" id="profileList"> <div class="profile-list" id="profileList">
<!-- Configurations populated by JS --> <!-- Profiles populated by JS -->
</div> </div>
<div class="profile-divider"></div> <div class="profile-divider"></div>
<div class="profile-action" onclick="openProfileManager()"> <div class="profile-action" onclick="openProfileManager()">
<i class="fas fa-cog"></i> <i class="fas fa-cog"></i>
<span data-i18n="header.manageProfiles">Manage</span> <span data-i18n="header.manageProfiles">Manage Profiles</span>
</div> </div>
</div> </div>
<span class="header-tooltip" data-i18n="header.configTooltip">Game config: mods, Java &amp; memory settings</span>
</div> </div>
<div class="window-controls"> <div class="window-controls">
@@ -217,21 +198,6 @@
<i class="fas fa-play"></i> <i class="fas fa-play"></i>
<span data-i18n="play.playButton">PLAY HYTALE</span> <span data-i18n="play.playButton">PLAY HYTALE</span>
</button> </button>
<div style="display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 12px; font-size: 12px;">
<span style="color: #93a3b8;">Telegram:</span>
<a href="#" onclick="window.electronAPI?.openExternal('https://t.me/sanhostnet'); return false;" style="color: #93a3b8; text-decoration: none; display: flex; align-items: center; gap: 4px; transition: color 0.2s;" onmouseover="this.style.color='#60a5fa'" onmouseout="this.style.color='#93a3b8'">
<i class="fas fa-users"></i> Group
</a>
<span style="color: #4b5563;">|</span>
<a href="#" onclick="window.electronAPI?.openExternal('https://t.me/hf2p_og'); return false;" style="color: #93a3b8; text-decoration: none; display: flex; align-items: center; gap: 4px; transition: color 0.2s;" onmouseover="this.style.color='#60a5fa'" onmouseout="this.style.color='#93a3b8'">
<i class="fab fa-telegram"></i> Channel
</a>
<span style="color: #4b5563;">|</span>
<a href="#" onclick="openDiscordExternal(); return false;" style="color: #93a3b8; text-decoration: none; display: flex; align-items: center; gap: 4px; transition: color 0.2s;" onmouseover="this.style.color='#60a5fa'" onmouseout="this.style.color='#93a3b8'">
<i class="fas fa-comments"></i> Community Chat
</a>
</div>
</div> </div>
</div> </div>
@@ -463,68 +429,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="settings-section">
<h3 class="settings-section-title">
<i class="fas fa-scroll"></i>
<span data-i18n="settings.wrapperConfig">Java Wrapper Configuration</span>
</h3>
<p class="settings-hint" style="margin-bottom: 12px;">
<i class="fas fa-info-circle"></i>
<span data-i18n="settings.wrapperConfigHint">Configure how the Java wrapper handles JVM flags and arguments at launch time.</span>
</p>
<!-- Strip Flags -->
<label class="settings-label" style="margin-bottom: 6px;">
<span data-i18n="settings.wrapperStripFlags">JVM Flags to Remove</span>
</label>
<div id="wrapperStripFlagsList" class="wrapper-items-list"></div>
<div style="display: flex; gap: 6px; margin-top: 6px;">
<input type="text" id="wrapperAddFlagInput" class="settings-input" style="flex:1;"
data-i18n-placeholder="settings.wrapperAddFlagPlaceholder" placeholder="e.g. -XX:+SomeFlag" spellcheck="false">
<button id="wrapperAddFlagBtn" class="settings-browse-btn">
<i class="fas fa-plus"></i>
<span data-i18n="settings.wrapperAdd">Add</span>
</button>
</div>
<!-- Inject Args -->
<label class="settings-label" style="margin-top: 16px; margin-bottom: 6px;">
<span data-i18n="settings.wrapperInjectArgs">Arguments to Inject</span>
</label>
<div id="wrapperInjectArgsList" class="wrapper-items-list"></div>
<div style="display: flex; gap: 6px; margin-top: 6px;">
<input type="text" id="wrapperAddArgInput" class="settings-input" style="flex:1;"
data-i18n-placeholder="settings.wrapperAddArgPlaceholder" placeholder="e.g. --some-flag" spellcheck="false">
<select id="wrapperAddArgCondition" class="wrapper-condition-select">
<option value="server" data-i18n="settings.wrapperConditionServer">Server Only</option>
<option value="always" data-i18n="settings.wrapperConditionAlways">Always</option>
</select>
<button id="wrapperAddArgBtn" class="settings-browse-btn">
<i class="fas fa-plus"></i>
<span data-i18n="settings.wrapperAdd">Add</span>
</button>
</div>
<!-- Restore Defaults -->
<div style="margin-top: 12px;">
<button id="wrapperRestoreDefaultsBtn" class="settings-browse-btn" style="background: rgba(239, 68, 68, 0.2); border-color: rgba(239, 68, 68, 0.3);">
<i class="fas fa-undo"></i>
<span data-i18n="settings.wrapperRestoreDefaults">Restore Defaults</span>
</button>
</div>
<!-- Script Preview (collapsible) -->
<div style="margin-top: 12px;">
<button id="wrapperPreviewToggle" class="wrapper-preview-toggle">
<i class="fas fa-chevron-right" id="wrapperPreviewChevron"></i>
<span data-i18n="settings.wrapperAdvancedPreview">Advanced: Script Preview</span>
</button>
<div id="wrapperPreviewContainer" style="display: none; margin-top: 8px;">
<pre id="wrapperPreviewContent" class="wrapper-preview-content"></pre>
</div>
</div>
</div>
</div> </div>
<div class="settings-column"> <div class="settings-column">
@@ -592,18 +496,6 @@
</div> </div>
</label> </label>
</div> </div>
<div class="settings-option">
<label class="settings-checkbox">
<input type="checkbox" id="allowMultiInstanceCheck" />
<span class="checkmark"></span>
<div class="checkbox-content">
<div class="checkbox-title" data-i18n="settings.allowMultiInstance">Allow multiple game instances</div>
<div class="checkbox-description" data-i18n="settings.allowMultiInstanceDescription">
Allow running multiple game clients at the same time (useful for mod development)
</div>
</div>
</label>
</div>
<div class="settings-option"> <div class="settings-option">
<label class="settings-checkbox"> <label class="settings-checkbox">
<input type="checkbox" id="launcherHwAccelCheck" /> <input type="checkbox" id="launcherHwAccelCheck" />
@@ -682,9 +574,6 @@
<i class="fas fa-folder-open"></i> <span data-i18n="settings.logsFolder">Open <i class="fas fa-folder-open"></i> <span data-i18n="settings.logsFolder">Open
Folder</span> Folder</span>
</button> </button>
<button class="logs-action-btn logs-send-btn" id="sendLogsBtn" onclick="sendLogs()">
<i class="fas fa-paper-plane"></i> <span data-i18n="settings.logsSend">Send Logs</span>
</button>
</div> </div>
</div> </div>
<div id="logsTerminal" class="logs-terminal"> <div id="logsTerminal" class="logs-terminal">
@@ -708,11 +597,7 @@
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</button> </button>
</div> </div>
<div class="mods-modal-body" style="padding-top: 0;"> <div class="mods-modal-body">
<div class="mods-search-container" style="margin: 1.5rem; margin-bottom: 1rem;">
<i class="fas fa-search"></i>
<input type="text" id="myModsSearch" placeholder="Search installed mods..." class="mods-search" />
</div>
<div id="installedModsList" class="installed-mods-list"> <div id="installedModsList" class="installed-mods-list">
</div> </div>
</div> </div>
@@ -766,39 +651,27 @@
</div> </div>
<div class="uuid-modal-body"> <div class="uuid-modal-body">
<div class="uuid-list-section"> <div class="uuid-current-section">
<div class="uuid-list-header"> <h3 class="uuid-section-title" data-i18n="uuid.currentUserUUID">Current User UUID</h3>
<h3 class="uuid-section-title" data-i18n="uuid.allPlayerUUIDs">All Player UUIDs</h3> <div class="uuid-current-display">
<button id="addIdentityBtn" class="uuid-generate-btn"> <input type="text" id="modalCurrentUuid" class="uuid-display-input" readonly />
<i class="fas fa-plus"></i> <button id="modalCopyUuidBtn" class="uuid-action-btn copy-btn" title="Copy UUID">
<span data-i18n="uuid.addIdentity">Add Identity</span> <i class="fas fa-copy"></i>
</button> </button>
</div> <button id="modalRegenerateUuidBtn" class="uuid-action-btn regenerate-btn"
<div id="uuidAddForm" class="uuid-add-form" style="display: none;"> title="Generate New UUID">
<div class="uuid-add-form-row">
<input type="text" id="addIdentityUsername" class="uuid-input"
data-i18n-placeholder="uuid.usernamePlaceholder"
placeholder="Username" maxlength="16" />
</div>
<div class="uuid-add-form-row">
<input type="text" id="addIdentityUuid" class="uuid-input"
data-i18n-placeholder="uuid.customPlaceholder"
placeholder="UUID (auto-generated)" maxlength="36" />
<button id="addIdentityRegenerateBtn" class="uuid-action-btn regenerate-btn"
title="Generate new UUID">
<i class="fas fa-sync-alt"></i> <i class="fas fa-sync-alt"></i>
</button> </button>
</div> </div>
<div class="uuid-add-form-actions">
<button id="addIdentityConfirmBtn" class="uuid-set-btn">
<i class="fas fa-check"></i>
<span data-i18n="uuid.add">Add</span>
</button>
<button id="addIdentityCancelBtn" class="uuid-cancel-btn">
<i class="fas fa-times"></i>
<span data-i18n="uuid.cancel">Cancel</span>
</button>
</div> </div>
<div class="uuid-list-section">
<div class="uuid-list-header">
<h3 class="uuid-section-title" data-i18n="uuid.allPlayerUUIDs">All Player UUIDs</h3>
<button id="generateNewUuidBtn" class="uuid-generate-btn">
<i class="fas fa-plus"></i>
<span data-i18n="uuid.generateNew">Generate New UUID</span>
</button>
</div> </div>
<div id="uuidList" class="uuid-list"> <div id="uuidList" class="uuid-list">
<div class="uuid-loading"> <div class="uuid-loading">
@@ -808,12 +681,7 @@
</div> </div>
</div> </div>
<div class="uuid-advanced-section"> <div class="uuid-custom-section">
<button id="uuidAdvancedToggle" class="uuid-advanced-toggle">
<i class="fas fa-chevron-right uuid-advanced-chevron"></i>
<span data-i18n="uuid.advanced">Advanced</span>
</button>
<div id="uuidAdvancedContent" class="uuid-advanced-content" style="display: none;">
<h3 class="uuid-section-title" data-i18n="uuid.setCustomUUID">Set Custom UUID</h3> <h3 class="uuid-section-title" data-i18n="uuid.setCustomUUID">Set Custom UUID</h3>
<div class="uuid-custom-form"> <div class="uuid-custom-form">
<input type="text" id="customUuidInput" class="uuid-input" <input type="text" id="customUuidInput" class="uuid-input"
@@ -832,15 +700,14 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Profile Manager Modal --> <!-- Profile Manager Modal -->
<div id="profileManagerModal" class="profile-modal" style="display: none;"> <div id="profileManagerModal" class="profile-modal" style="display: none;">
<div class="profile-modal-content"> <div class="profile-modal-content">
<div class="profile-modal-header"> <div class="profile-modal-header">
<h2 class="profile-modal-title"> <h2 class="profile-modal-title">
<i class="fas fa-sliders-h mr-2"></i> <i class="fas fa-users-cog mr-2"></i>
<span data-i18n="configurations.modalTitle">Manage Configurations</span> <span data-i18n="profiles.modalTitle">Manage Profiles</span>
</h2> </h2>
<button class="modal-close-btn" onclick="closeProfileManager()"> <button class="modal-close-btn" onclick="closeProfileManager()">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
@@ -851,10 +718,10 @@
<!-- Populated by JS --> <!-- Populated by JS -->
</div> </div>
<div class="profile-create-section"> <div class="profile-create-section">
<input type="text" id="newProfileName" data-i18n-placeholder="configurations.newProfilePlaceholder" <input type="text" id="newProfileName" data-i18n-placeholder="profiles.newProfilePlaceholder"
class="profile-input" maxlength="20"> class="profile-input" maxlength="20">
<button class="profile-create-btn" onclick="createNewProfile()"> <button class="profile-create-btn" onclick="createNewProfile()">
<i class="fas fa-plus"></i> <span data-i18n="configurations.createProfile">Create Configuration</span> <i class="fas fa-plus"></i> <span data-i18n="profiles.createProfile">Create Profile</span>
</button> </button>
</div> </div>
</div> </div>
@@ -900,14 +767,14 @@
<div class="modal-content discord-popup-modal"> <div class="modal-content discord-popup-modal">
<div class="modal-header"> <div class="modal-header">
<div class="discord-popup-header"> <div class="discord-popup-header">
<i class="fas fa-comments"></i> <i class="fab fa-discord"></i>
<h2 class="modal-title">Join Our Community</h2> <h2 class="modal-title">Join Our Discord Community</h2>
</div> </div>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="discord-popup-body"> <div class="discord-popup-body">
<p class="discord-popup-text"> <p class="discord-popup-text">
Join our community and stay connected! Join our community of over <strong>5000 members</strong> and stay connected!
</p> </p>
<p class="discord-popup-text"> <p class="discord-popup-text">
Get the latest news, updates, and announcements about the launcher. Get the latest news, updates, and announcements about the launcher.
@@ -918,8 +785,8 @@
<div class="discord-popup-actions"> <div class="discord-popup-actions">
<button class="discord-popup-btn primary" onclick="joinDiscord()"> <button class="discord-popup-btn primary" onclick="joinDiscord()">
<i class="fas fa-comments"></i> <i class="fab fa-discord"></i>
Join Community Chat Join Discord
</button> </button>
<button class="discord-popup-btn secondary" onclick="closeDiscordPopup()"> <button class="discord-popup-btn secondary" onclick="closeDiscordPopup()">
Maybe Later Maybe Later
@@ -934,25 +801,6 @@
<script src="js/featured.js"></script> <script src="js/featured.js"></script>
<script type="module" src="js/settings.js"></script> <script type="module" src="js/settings.js"></script>
<script type="module" src="js/update.js"></script> <script type="module" src="js/update.js"></script>
<!-- Version Selection Modal (Isolated Container) -->
<div id="versionSelectModal" class="modal-overlay" style="display: none; position: fixed; inset: 0; z-index: 9999; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(5px); align-items: center; justify-content: center;">
<div class="glass-panel" style="width: 100%; max-width: 600px; max-height: 80vh; display: flex; flex-direction: column; border-radius: 12px; overflow: hidden; margin: 20px;">
<div class="modal-header" style="display: flex; justify-content: space-between; align-items: center; padding: 1.5rem; border-bottom: 1px solid rgba(255, 255, 255, 0.1);">
<h3 style="margin: 0; font-size: 1.25rem;">Select Version</h3>
<button id="closeVersionModal" class="modal-close" style="background: none; border: none; color: #a0a0a0; font-size: 1.25rem; cursor: pointer;"><i class="fas fa-times"></i></button>
</div>
<div class="modal-body" style="padding: 1.5rem; overflow-y: auto;">
<div id="versionList" class="version-list-container">
<div class="loading-versions" style="display: flex; flex-direction: column; align-items: center; gap: 1rem; color: #a0a0a0;">
<i class="fas fa-spinner fa-spin fa-2x"></i>
<span>Loading versions...</span>
</div>
</div>
</div>
</div>
</div>
<!-- updater.js disabled - using update.js instead which has skip button and macOS handling --> <!-- updater.js disabled - using update.js instead which has skip button and macOS handling -->
</body> </body>

View File

@@ -12,18 +12,9 @@ const i18n = (() => {
{ code: 'ru-RU', name: 'Russian (Russia)' }, { code: 'ru-RU', name: 'Russian (Russia)' },
{ code: 'sv-SE', name: 'Swedish (Sweden)' }, { code: 'sv-SE', name: 'Swedish (Sweden)' },
{ code: 'tr-TR', name: 'Turkish (Turkey)' }, { code: 'tr-TR', name: 'Turkish (Turkey)' },
{ code: 'id-ID', name: 'Indonesian (Indonesia)' }, { code: 'id-ID', name: 'Indonesian (Indonesia)' }
{ code: 'ar-SA', name: 'Arabic (Saudi Arabia)' }
]; ];
// RTL languages
const rtlLanguages = ['ar-SA'];
// Check if current language is RTL
function isRTL() {
return rtlLanguages.includes(currentLang);
}
// Load single language file // Load single language file
async function loadLanguage(lang) { async function loadLanguage(lang) {
if (translations[lang]) return true; if (translations[lang]) return true;
@@ -82,24 +73,6 @@ const i18n = (() => {
const key = el.getAttribute('data-i18n-title'); const key = el.getAttribute('data-i18n-title');
el.title = t(key); el.title = t(key);
}); });
// Update RTL layout
updateRTL();
}
// Update RTL layout
function updateRTL() {
const html = document.documentElement;
const body = document.body;
if (isRTL()) {
html.setAttribute('dir', 'rtl');
html.setAttribute('lang', currentLang);
body.classList.add('rtl');
} else {
html.removeAttribute('dir');
html.setAttribute('lang', currentLang);
body.classList.remove('rtl');
}
} }
// Initialize - load saved language only // Initialize - load saved language only
@@ -115,8 +88,7 @@ const i18n = (() => {
t, t,
setLanguage, setLanguage,
getAvailableLanguages: () => availableLanguages, getAvailableLanguages: () => availableLanguages,
getCurrentLanguage: () => currentLang, getCurrentLanguage: () => currentLang
isRTL
}; };
})(); })();

View File

@@ -35,21 +35,13 @@ export function setupLauncher() {
// Initial Profile Load // Initial Profile Load
loadProfiles(); loadProfiles();
// Initial Identity Load // Close dropdown on outside click
loadIdentities();
// Close dropdowns on outside click
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
const profileSelector = document.getElementById('profileSelector'); const selector = document.getElementById('profileSelector');
if (profileSelector && !profileSelector.contains(e.target)) { if (selector && !selector.contains(e.target)) {
const dropdown = document.getElementById('profileDropdown'); const dropdown = document.getElementById('profileDropdown');
if (dropdown) dropdown.classList.remove('show'); if (dropdown) dropdown.classList.remove('show');
} }
const identitySelector = document.getElementById('identitySelector');
if (identitySelector && !identitySelector.contains(e.target)) {
const dropdown = document.getElementById('identityDropdown');
if (dropdown) dropdown.classList.remove('show');
}
}); });
} }
@@ -91,7 +83,7 @@ function renderProfileList(profiles, activeProfile) {
managerList.innerHTML = profiles.map(p => ` managerList.innerHTML = profiles.map(p => `
<div class="profile-manager-item ${p.id === activeProfile.id ? 'active' : ''}"> <div class="profile-manager-item ${p.id === activeProfile.id ? 'active' : ''}">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<i class="fas fa-sliders-h text-xl text-gray-400"></i> <i class="fas fa-user-circle text-xl text-gray-400"></i>
<div> <div>
<div class="font-bold">${p.name}</div> <div class="font-bold">${p.name}</div>
<div class="text-xs text-gray-500">ID: ${p.id.substring(0, 8)}...</div> <div class="text-xs text-gray-500">ID: ${p.id.substring(0, 8)}...</div>
@@ -114,6 +106,13 @@ function updateCurrentProfileUI(profile) {
} }
} }
window.toggleProfileDropdown = () => {
const dropdown = document.getElementById('profileDropdown');
if (dropdown) {
dropdown.classList.toggle('show');
}
};
window.openProfileManager = () => { window.openProfileManager = () => {
const modal = document.getElementById('profileManagerModal'); const modal = document.getElementById('profileManagerModal');
if (modal) { if (modal) {
@@ -147,7 +146,7 @@ window.createNewProfile = async () => {
}; };
window.deleteProfile = async (id) => { window.deleteProfile = async (id) => {
if (!confirm('Are you sure you want to delete this configuration? Mod settings will be lost.')) return; if (!confirm('Are you sure you want to delete this profile? parameters and mods configuration will be lost.')) return;
try { try {
await window.electronAPI.profile.delete(id); await window.electronAPI.profile.delete(id);
@@ -161,7 +160,7 @@ window.deleteProfile = async (id) => {
window.switchProfile = async (id) => { window.switchProfile = async (id) => {
try { try {
if (window.LauncherUI) window.LauncherUI.showProgress(); if (window.LauncherUI) window.LauncherUI.showProgress();
const switchingMsg = window.i18n ? window.i18n.t('progress.switchingProfile') : 'Switching configuration...'; const switchingMsg = window.i18n ? window.i18n.t('progress.switchingProfile') : 'Switching Profile...';
if (window.LauncherUI) window.LauncherUI.updateProgress({ message: switchingMsg }); if (window.LauncherUI) window.LauncherUI.updateProgress({ message: switchingMsg });
await window.electronAPI.profile.activate(id); await window.electronAPI.profile.activate(id);
@@ -180,7 +179,7 @@ window.switchProfile = async (id) => {
if (dropdown) dropdown.classList.remove('show'); if (dropdown) dropdown.classList.remove('show');
if (window.LauncherUI) { if (window.LauncherUI) {
const switchedMsg = window.i18n ? window.i18n.t('progress.profileSwitched') : 'Configuration switched!'; const switchedMsg = window.i18n ? window.i18n.t('progress.profileSwitched') : 'Profile Switched!';
window.LauncherUI.updateProgress({ message: switchedMsg }); window.LauncherUI.updateProgress({ message: switchedMsg });
setTimeout(() => window.LauncherUI.hideProgress(), 1000); setTimeout(() => window.LauncherUI.hideProgress(), 1000);
} }
@@ -677,121 +676,6 @@ async function loadCustomJavaPath() {
} }
} }
// ==========================================
// IDENTITY SWITCHER
// ==========================================
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
async function loadIdentities() {
try {
if (!window.electronAPI) return;
const nameEl = document.getElementById('currentIdentityName');
// Load current username
let currentUsername = 'Player';
if (window.electronAPI.loadUsername) {
const name = await window.electronAPI.loadUsername();
if (name) currentUsername = name;
}
if (nameEl) nameEl.textContent = currentUsername;
// Load all identities for dropdown
const list = document.getElementById('identityList');
if (!list || !window.electronAPI.getAllUuidMappings) return;
const mappings = await window.electronAPI.getAllUuidMappings();
renderIdentityList(mappings, currentUsername);
} catch (error) {
console.error('Failed to load identities:', error);
}
}
function renderIdentityList(mappings, currentUsername) {
const list = document.getElementById('identityList');
if (!list) return;
if (!mappings || mappings.length === 0) {
list.innerHTML = '<div class="identity-empty">No identities</div>';
return;
}
list.innerHTML = mappings.map(m => {
const safe = escapeHtml(m.username);
return `
<div class="identity-item ${m.username === currentUsername ? 'active' : ''}"
onclick="switchIdentity('${safe.replace(/'/g, "&#39;")}')">
<span>${safe}</span>
${m.username === currentUsername ? '<i class="fas fa-check ml-auto"></i>' : ''}
</div>
`;
}).join('');
}
window.toggleIdentityDropdown = () => {
const dropdown = document.getElementById('identityDropdown');
if (dropdown) {
dropdown.classList.toggle('show');
// Close profile dropdown
const profileDropdown = document.getElementById('profileDropdown');
if (profileDropdown) profileDropdown.classList.remove('show');
}
};
window.openIdentityManager = () => {
// Close dropdown
const dropdown = document.getElementById('identityDropdown');
if (dropdown) dropdown.classList.remove('show');
// Open UUID modal from settings
if (window.openUuidModal) {
window.openUuidModal();
}
};
window.switchIdentity = async (username) => {
try {
if (!window.electronAPI || !window.electronAPI.saveUsername) return;
const result = await window.electronAPI.saveUsername(username);
if (result && result.success === false) {
throw new Error(result.error || 'Failed to switch identity');
}
// Refresh identity dropdown
await loadIdentities();
// Close dropdown
const dropdown = document.getElementById('identityDropdown');
if (dropdown) dropdown.classList.remove('show');
// Update settings page username field and UUID display
const settingsInput = document.getElementById('settingsPlayerName');
if (settingsInput) settingsInput.value = username;
if (window.loadCurrentUuid) window.loadCurrentUuid();
} catch (error) {
console.error('Failed to switch identity:', error);
}
};
// Make loadIdentities available globally for settings.js to call
window.loadIdentities = loadIdentities;
window.toggleProfileDropdown = () => {
const dropdown = document.getElementById('profileDropdown');
if (dropdown) {
dropdown.classList.toggle('show');
// Close identity dropdown
const identityDropdown = document.getElementById('identityDropdown');
if (identityDropdown) identityDropdown.classList.remove('show');
}
};
window.launch = launch; window.launch = launch;
window.uninstallGame = uninstallGame; window.uninstallGame = uninstallGame;
window.repairGame = repairGame; window.repairGame = repairGame;

View File

@@ -66,113 +66,6 @@ async function openLogsFolder() {
await window.electronAPI.openLogsFolder(); await window.electronAPI.openLogsFolder();
} }
async function sendLogs() {
const btn = document.getElementById('sendLogsBtn');
if (!btn || btn.disabled) return;
// Get i18n strings with fallbacks
const i18n = window.i18n || {};
const sendingText = (i18n.settings && i18n.settings.logsSending) || 'Sending...';
const sentText = (i18n.settings && i18n.settings.logsSent) || 'Sent!';
const failedText = (i18n.settings && i18n.settings.logsSendFailed) || 'Failed';
const sendText = (i18n.settings && i18n.settings.logsSend) || 'Send Logs';
const originalHTML = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = `<i class="fas fa-spinner fa-spin"></i> ${sendingText}`;
try {
const result = await window.electronAPI.sendLogs();
if (result.success) {
btn.innerHTML = `<i class="fas fa-check"></i> ${sentText}`;
showLogSubmissionResult(result.id);
} else {
btn.innerHTML = `<i class="fas fa-times"></i> ${failedText}`;
console.error('Send logs failed:', result.error);
// Show error notification if available
if (window.LauncherUI && window.LauncherUI.showNotification) {
window.LauncherUI.showNotification(result.error || 'Failed to send logs', 'error');
}
}
} catch (err) {
console.error('Send logs error:', err);
btn.innerHTML = `<i class="fas fa-times"></i> ${failedText}`;
}
setTimeout(() => {
btn.disabled = false;
btn.innerHTML = originalHTML;
}, 3000);
}
function showLogSubmissionResult(id) {
// Remove existing popup if any
const existing = document.getElementById('logSubmissionPopup');
if (existing) existing.remove();
const i18n = window.i18n || {};
const idLabel = (i18n.settings && i18n.settings.logsSubmissionId) || 'Submission ID';
const copyText = (i18n.common && i18n.common.copy) || 'Copy';
const closeText = (i18n.common && i18n.common.close) || 'Close';
const shareText = (i18n.settings && i18n.settings.logsShareId) || 'Share this ID with support when reporting issues';
const popup = document.createElement('div');
popup.id = 'logSubmissionPopup';
popup.style.cssText = `
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
background: rgba(20, 20, 35, 0.98); border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 12px; padding: 24px 32px; z-index: 10000;
box-shadow: 0 20px 60px rgba(0,0,0,0.5); text-align: center;
min-width: 320px; backdrop-filter: blur(10px);
`;
popup.innerHTML = `
<div style="margin-bottom: 16px;">
<i class="fas fa-check-circle" style="font-size: 2em; color: #00d4ff;"></i>
</div>
<div style="color: #888; font-size: 0.85em; margin-bottom: 8px;">${idLabel}</div>
<div id="logSubId" style="font-family: monospace; font-size: 1.5em; color: #00d4ff; letter-spacing: 2px; margin-bottom: 12px; user-select: all;">${id}</div>
<div style="color: #666; font-size: 0.8em; margin-bottom: 20px;">${shareText}</div>
<div style="display: flex; gap: 10px; justify-content: center;">
<button onclick="copyLogSubmissionId('${id}')" style="
background: rgba(0,212,255,0.2); border: 1px solid rgba(0,212,255,0.3);
color: #00d4ff; padding: 8px 20px; border-radius: 6px; cursor: pointer;
font-size: 0.9em;
"><i class="fas fa-copy"></i> ${copyText}</button>
<button onclick="document.getElementById('logSubmissionPopup').remove()" style="
background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2);
color: #ccc; padding: 8px 20px; border-radius: 6px; cursor: pointer;
font-size: 0.9em;
">${closeText}</button>
</div>
`;
document.body.appendChild(popup);
// Auto-close after 30s
setTimeout(() => {
if (document.getElementById('logSubmissionPopup')) {
popup.remove();
}
}, 30000);
}
async function copyLogSubmissionId(id) {
try {
await navigator.clipboard.writeText(id);
const btn = event.target.closest('button');
if (btn) {
const orig = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i> Copied!';
setTimeout(() => { btn.innerHTML = orig; }, 1500);
}
} catch (err) {
console.error('Failed to copy submission ID:', err);
}
}
function openLogs() { function openLogs() {
// Navigation is handled by sidebar logic, but we can trigger a refresh // Navigation is handled by sidebar logic, but we can trigger a refresh
window.LauncherUI.showPage('logs-page'); window.LauncherUI.showPage('logs-page');
@@ -184,8 +77,6 @@ function openLogs() {
window.refreshLogs = refreshLogs; window.refreshLogs = refreshLogs;
window.copyLogs = copyLogs; window.copyLogs = copyLogs;
window.openLogsFolder = openLogsFolder; window.openLogsFolder = openLogsFolder;
window.sendLogs = sendLogs;
window.copyLogSubmissionId = copyLogSubmissionId;
window.openLogs = openLogs; window.openLogs = openLogs;
// Auto-load logs when the page becomes active // Auto-load logs when the page becomes active

View File

@@ -49,18 +49,6 @@ function setupModsEventListeners() {
closeModalBtn.addEventListener('click', closeMyModsModal); closeModalBtn.addEventListener('click', closeMyModsModal);
} }
const myModsSearchInput = document.getElementById('myModsSearch');
if (myModsSearchInput) {
let myModsSearchTimeout;
myModsSearchInput.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase().trim();
clearTimeout(myModsSearchTimeout);
myModsSearchTimeout = setTimeout(() => {
filterInstalledMods(query);
}, 300);
});
}
const modal = document.getElementById('myModsModal'); const modal = document.getElementById('myModsModal');
if (modal) { if (modal) {
modal.addEventListener('click', (e) => { modal.addEventListener('click', (e) => {
@@ -90,30 +78,12 @@ function setupModsEventListeners() {
} }
}); });
} }
const browseContainer = document.getElementById('browseModsList');
if (browseContainer) {
browseContainer.addEventListener('click', (e) => {
const installBtn = e.target.closest('[data-install-mod-id]');
if (installBtn) {
const modId = installBtn.getAttribute('data-install-mod-id');
const mod = browseMods.find(m => m.id == modId);
if (mod) {
openVersionSelectModal(mod);
}
}
});
}
} }
function openMyModsModal() { function openMyModsModal() {
const modal = document.getElementById('myModsModal'); const modal = document.getElementById('myModsModal');
if (modal) { if (modal) {
modal.classList.add('active'); modal.classList.add('active');
const searchInput = document.getElementById('myModsSearch');
if (searchInput) {
searchInput.value = '';
}
loadInstalledMods(); loadInstalledMods();
} }
} }
@@ -122,10 +92,6 @@ function closeMyModsModal() {
const modal = document.getElementById('myModsModal'); const modal = document.getElementById('myModsModal');
if (modal) { if (modal) {
modal.classList.remove('active'); modal.classList.remove('active');
const searchInput = document.getElementById('myModsSearch');
if (searchInput) {
searchInput.value = '';
}
} }
} }
@@ -147,39 +113,19 @@ async function loadInstalledMods() {
} }
} }
function filterInstalledMods(query) {
if (!query || query === '') {
displayInstalledMods(installedMods);
return;
}
const filtered = installedMods.filter(mod => {
const nameMatch = mod.name?.toLowerCase().includes(query);
const fileNameMatch = mod.fileName?.toLowerCase().includes(query);
const descriptionMatch = mod.description?.toLowerCase().includes(query);
const authorMatch = mod.author?.toLowerCase().includes(query);
return nameMatch || fileNameMatch || descriptionMatch || authorMatch;
});
displayInstalledMods(filtered);
}
function displayInstalledMods(mods) { function displayInstalledMods(mods) {
const modsContainer = document.getElementById('installedModsList'); const modsContainer = document.getElementById('installedModsList');
if (!modsContainer) return; if (!modsContainer) return;
if (mods.length === 0) { if (mods.length === 0) {
const searchInput = document.getElementById('myModsSearch');
const isSearching = searchInput && searchInput.value.trim() !== '';
modsContainer.innerHTML = ` modsContainer.innerHTML = `
<div class=\"empty-installed-mods\"> <div class=\"empty-installed-mods\">
<i class=\"fas fa-${isSearching ? 'search' : 'box-open'}\"></i> <i class=\"fas fa-box-open\"></i>
<h4 data-i18n="${isSearching ? 'mods.noModsFound' : 'mods.noModsInstalled'}">${isSearching ? 'No Mods Found' : 'No Mods Installed'}</h4> <h4 data-i18n="mods.noModsInstalled">No Mods Installed</h4>
<p data-i18n="${isSearching ? 'mods.noModsFoundDesc' : 'mods.noModsInstalledDesc'}">${isSearching ? 'Try a different search term' : 'Add mods from CurseForge or import local files'}</p> <p data-i18n="mods.noModsInstalledDesc">Add mods from CurseForge or import local files</p>
</div> </div>
`; `;
if (window.i18n && !isSearching) { if (window.i18n) {
const container = modsContainer.querySelector('.empty-installed-mods'); const container = modsContainer.querySelector('.empty-installed-mods');
container.querySelector('h4').textContent = window.i18n.t('mods.noModsInstalled'); container.querySelector('h4').textContent = window.i18n.t('mods.noModsInstalled');
container.querySelector('p').textContent = window.i18n.t('mods.noModsInstalledDesc'); container.querySelector('p').textContent = window.i18n.t('mods.noModsInstalledDesc');
@@ -219,7 +165,7 @@ function createInstalledModCard(mod) {
<div class="installed-mod-info"> <div class="installed-mod-info">
<div class="installed-mod-header"> <div class="installed-mod-header">
<h4 class="installed-mod-name">${mod.name}</h4> <h4 class="installed-mod-name">${mod.name}</h4>
<span class="installed-mod-version">${mod.fileName || 'v' + mod.version}</span> <span class="installed-mod-version">v${mod.version}</span>
</div> </div>
<p class="installed-mod-description">${mod.description || (window.i18n ? window.i18n.t('mods.noDescription') : 'No description available')}</p> <p class="installed-mod-description">${mod.description || (window.i18n ? window.i18n.t('mods.noDescription') : 'No description available')}</p>
</div> </div>
@@ -349,6 +295,13 @@ function displayBrowseMods(mods) {
} }
browseContainer.innerHTML = mods.map(mod => createBrowseModCard(mod)).join(''); 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) { function createBrowseModCard(mod) {
@@ -397,12 +350,12 @@ function createBrowseModCard(mod) {
${window.i18n ? window.i18n.t('mods.view') : 'VIEW'} ${window.i18n ? window.i18n.t('mods.view') : 'VIEW'}
</button> </button>
${!isInstalled ? ${!isInstalled ?
`<button data-install-mod-id=\"${mod.id}\" class=\"mod-btn-toggle bg-primary text-black hover:bg-primary/80\"> `<button id="install-${mod.id}" class="mod-btn-toggle bg-primary text-black hover:bg-primary/80">
<i class=\"fas fa-download\"></i> <i class="fas fa-download"></i>
${window.i18n ? window.i18n.t('mods.install') : 'INSTALL'} ${window.i18n ? window.i18n.t('mods.install') : 'INSTALL'}
</button>` : </button>` :
`<button class=\"mod-btn-toggle bg-white/10 text-white\" disabled> `<button class="mod-btn-toggle bg-white/10 text-white" disabled>
<i class=\"fas fa-check\"></i> <i class="fas fa-check"></i>
${window.i18n ? window.i18n.t('mods.installed') : 'INSTALLED'} ${window.i18n ? window.i18n.t('mods.installed') : 'INSTALLED'}
</button>` </button>`
} }
@@ -411,104 +364,6 @@ function createBrowseModCard(mod) {
`; `;
} }
let currentSelectedMod = null;
function openVersionSelectModal(mod) {
currentSelectedMod = mod;
const modal = document.getElementById('versionSelectModal');
const closeBtn = document.getElementById('closeVersionModal');
const versionList = document.getElementById('versionList');
if (modal) {
modal.style.display = 'flex';
modal.classList.add('active');
const closeHandler = () => {
modal.classList.remove('active');
setTimeout(() => {
modal.style.display = 'none';
}, 300);
currentSelectedMod = null;
};
if (closeBtn) {
closeBtn.onclick = closeHandler;
}
modal.onclick = (e) => {
if (e.target === modal) closeHandler();
};
loadModVersions(mod.id, versionList);
}
}
async function loadModVersions(modId, container) {
container.innerHTML = `
<div class="loading-versions">
<i class="fas fa-spinner fa-spin fa-2x" style="margin-bottom: 10px; display: block;"></i>
<span>Loading versions...</span>
</div>
`;
try {
const versions = await window.electronAPI.getModFiles(modId);
if (!versions || versions.length === 0) {
container.innerHTML = `<div class="p-4 text-center text-gray-400" style="padding: 2rem;">No versions found for this mod.</div>`;
return;
}
// Sort versions by date desc (API returns desc but ensure)
versions.sort((a, b) => new Date(b.fileDate) - new Date(a.fileDate));
container.innerHTML = versions.map(file => `
<div class="version-item">
<div class="version-info">
<div class="version-name">${file.displayName}</div>
<div class="version-meta">
<span><i class="fas fa-calendar"></i> ${new Date(file.fileDate).toLocaleDateString()}</span>
<span><i class="fas fa-download"></i> ${formatNumber(file.downloadCount)}</span>
<span><i class="fas fa-file-archive"></i> ${(file.fileLength / 1024 / 1024).toFixed(2)} MB</span>
</div>
</div>
<div class="version-actions">
<button class="btn-install" data-file-id="${file.id}">
Install
</button>
</div>
</div>
`).join('');
// Add event listeners securely
container.querySelectorAll('.btn-install').forEach((btn, index) => {
const file = versions[index]; // Map index to file data
btn.onclick = () => installVersion(file);
});
} catch (error) {
console.error('Error loading versions:', error);
container.innerHTML = `<div class="p-4 text-center text-red-400" style="padding: 2rem;">Error loading versions.<br><small>${error.message}</small></div>`;
}
}
async function installVersion(file) {
if (!currentSelectedMod) return;
const modal = document.getElementById('versionSelectModal');
modal.style.display = 'none';
const modInfo = {
...currentSelectedMod,
fileId: file.id,
downloadUrl: file.downloadUrl,
fileName: file.fileName,
fileSize: file.fileLength
};
await downloadAndInstallMod(modInfo);
currentSelectedMod = null;
}
async function downloadAndInstallMod(modInfo) { async function downloadAndInstallMod(modInfo) {
try { try {
const downloadMsg = window.i18n ? window.i18n.t('notifications.modsDownloading').replace('{name}', modInfo.name) : `Downloading ${modInfo.name}...`; const downloadMsg = window.i18n ? window.i18n.t('notifications.modsDownloading').replace('{name}', modInfo.name) : `Downloading ${modInfo.name}...`;
@@ -907,10 +762,7 @@ window.modsManager = {
closeMyModsModal, closeMyModsModal,
viewModPage, viewModPage,
loadInstalledMods, loadInstalledMods,
loadBrowseMods, loadBrowseMods
openVersionSelectModal
}; };
// Remove auto-init since we are now calling it from script.js explicitly document.addEventListener('DOMContentLoaded', initModsManager);
// which guarantees order and environment readiness
// document.addEventListener('DOMContentLoaded', initModsManager);

View File

@@ -2,7 +2,7 @@ import './ui.js';
import './install.js'; import './install.js';
import './launcher.js'; import './launcher.js';
import './news.js'; import './news.js';
import { initModsManager } from './mods.js'; import './mods.js';
import './players.js'; import './players.js';
import './settings.js'; import './settings.js';
import './logs.js'; import './logs.js';
@@ -15,12 +15,6 @@ let i18nInitialized = false;
if (document.readyState === 'complete' || document.readyState === 'interactive') { if (document.readyState === 'complete' || document.readyState === 'interactive') {
updateLanguageSelector(); updateLanguageSelector();
initModsManager();
} else {
document.addEventListener('DOMContentLoaded', () => {
updateLanguageSelector();
initModsManager();
});
} }
})(); })();
@@ -59,8 +53,7 @@ window.closeDiscordPopup = function() {
}; };
window.joinDiscord = async function() { window.joinDiscord = async function() {
// await window.electronAPI?.openExternal('https://discord.gg/Fhbb9Yk5WW'); await window.electronAPI?.openExternal('https://discord.gg/hf2pdc');
await window.electronAPI?.openExternal('https://chat.sanhost.net/invite/Tfz4jCK4');
try { try {
await window.electronAPI?.saveConfig({ discordPopup: true }); await window.electronAPI?.saveConfig({ discordPopup: true });

View File

@@ -6,7 +6,6 @@ let browseJavaBtn;
let settingsPlayerName; let settingsPlayerName;
let discordRPCCheck; let discordRPCCheck;
let closeLauncherCheck; let closeLauncherCheck;
let allowMultiInstanceCheck;
let launcherHwAccelCheck; let launcherHwAccelCheck;
let gpuPreferenceRadios; let gpuPreferenceRadios;
let gameBranchRadios; let gameBranchRadios;
@@ -19,15 +18,10 @@ let regenerateUuidBtn;
let manageUuidsBtn; let manageUuidsBtn;
let uuidModal; let uuidModal;
let uuidModalClose; let uuidModalClose;
let addIdentityBtn; let modalCurrentUuid;
let uuidAddForm; let modalCopyUuidBtn;
let addIdentityUsername; let modalRegenerateUuidBtn;
let addIdentityUuid; let generateNewUuidBtn;
let addIdentityRegenerateBtn;
let addIdentityConfirmBtn;
let addIdentityCancelBtn;
let uuidAdvancedToggle;
let uuidAdvancedContent;
let uuidList; let uuidList;
let customUuidInput; let customUuidInput;
let setCustomUuidBtn; let setCustomUuidBtn;
@@ -172,7 +166,6 @@ function setupSettingsElements() {
settingsPlayerName = document.getElementById('settingsPlayerName'); settingsPlayerName = document.getElementById('settingsPlayerName');
discordRPCCheck = document.getElementById('discordRPCCheck'); discordRPCCheck = document.getElementById('discordRPCCheck');
closeLauncherCheck = document.getElementById('closeLauncherCheck'); closeLauncherCheck = document.getElementById('closeLauncherCheck');
allowMultiInstanceCheck = document.getElementById('allowMultiInstanceCheck');
launcherHwAccelCheck = document.getElementById('launcherHwAccelCheck'); launcherHwAccelCheck = document.getElementById('launcherHwAccelCheck');
gpuPreferenceRadios = document.querySelectorAll('input[name="gpuPreference"]'); gpuPreferenceRadios = document.querySelectorAll('input[name="gpuPreference"]');
gameBranchRadios = document.querySelectorAll('input[name="gameBranch"]'); gameBranchRadios = document.querySelectorAll('input[name="gameBranch"]');
@@ -187,15 +180,10 @@ function setupSettingsElements() {
manageUuidsBtn = document.getElementById('manageUuidsBtn'); manageUuidsBtn = document.getElementById('manageUuidsBtn');
uuidModal = document.getElementById('uuidModal'); uuidModal = document.getElementById('uuidModal');
uuidModalClose = document.getElementById('uuidModalClose'); uuidModalClose = document.getElementById('uuidModalClose');
addIdentityBtn = document.getElementById('addIdentityBtn'); modalCurrentUuid = document.getElementById('modalCurrentUuid');
uuidAddForm = document.getElementById('uuidAddForm'); modalCopyUuidBtn = document.getElementById('modalCopyUuidBtn');
addIdentityUsername = document.getElementById('addIdentityUsername'); modalRegenerateUuidBtn = document.getElementById('modalRegenerateUuidBtn');
addIdentityUuid = document.getElementById('addIdentityUuid'); generateNewUuidBtn = document.getElementById('generateNewUuidBtn');
addIdentityRegenerateBtn = document.getElementById('addIdentityRegenerateBtn');
addIdentityConfirmBtn = document.getElementById('addIdentityConfirmBtn');
addIdentityCancelBtn = document.getElementById('addIdentityCancelBtn');
uuidAdvancedToggle = document.getElementById('uuidAdvancedToggle');
uuidAdvancedContent = document.getElementById('uuidAdvancedContent');
uuidList = document.getElementById('uuidList'); uuidList = document.getElementById('uuidList');
customUuidInput = document.getElementById('customUuidInput'); customUuidInput = document.getElementById('customUuidInput');
setCustomUuidBtn = document.getElementById('setCustomUuidBtn'); setCustomUuidBtn = document.getElementById('setCustomUuidBtn');
@@ -220,10 +208,6 @@ function setupSettingsElements() {
closeLauncherCheck.addEventListener('change', saveCloseLauncher); closeLauncherCheck.addEventListener('change', saveCloseLauncher);
} }
if (allowMultiInstanceCheck) {
allowMultiInstanceCheck.addEventListener('change', saveAllowMultiInstance);
}
if (launcherHwAccelCheck) { if (launcherHwAccelCheck) {
launcherHwAccelCheck.addEventListener('change', saveLauncherHwAccel); launcherHwAccelCheck.addEventListener('change', saveLauncherHwAccel);
} }
@@ -246,24 +230,16 @@ function setupSettingsElements() {
uuidModalClose.addEventListener('click', closeUuidModal); uuidModalClose.addEventListener('click', closeUuidModal);
} }
if (addIdentityBtn) { if (modalCopyUuidBtn) {
addIdentityBtn.addEventListener('click', showAddIdentityForm); modalCopyUuidBtn.addEventListener('click', copyCurrentUuid);
} }
if (addIdentityRegenerateBtn) { if (modalRegenerateUuidBtn) {
addIdentityRegenerateBtn.addEventListener('click', regenerateAddIdentityUuid); modalRegenerateUuidBtn.addEventListener('click', regenerateCurrentUuid);
} }
if (addIdentityConfirmBtn) { if (generateNewUuidBtn) {
addIdentityConfirmBtn.addEventListener('click', confirmAddIdentity); generateNewUuidBtn.addEventListener('click', generateNewUuid);
}
if (addIdentityCancelBtn) {
addIdentityCancelBtn.addEventListener('click', hideAddIdentityForm);
}
if (uuidAdvancedToggle) {
uuidAdvancedToggle.addEventListener('click', toggleAdvancedSection);
} }
if (setCustomUuidBtn) { if (setCustomUuidBtn) {
@@ -421,30 +397,6 @@ async function loadCloseLauncher() {
} }
} }
async function saveAllowMultiInstance() {
try {
if (window.electronAPI && window.electronAPI.saveAllowMultiInstance && allowMultiInstanceCheck) {
const enabled = allowMultiInstanceCheck.checked;
await window.electronAPI.saveAllowMultiInstance(enabled);
}
} catch (error) {
console.error('Error saving multi-instance setting:', error);
}
}
async function loadAllowMultiInstance() {
try {
if (window.electronAPI && window.electronAPI.loadAllowMultiInstance) {
const enabled = await window.electronAPI.loadAllowMultiInstance();
if (allowMultiInstanceCheck) {
allowMultiInstanceCheck.checked = enabled;
}
}
} catch (error) {
console.error('Error loading multi-instance setting:', error);
}
}
async function saveLauncherHwAccel() { async function saveLauncherHwAccel() {
try { try {
if (window.electronAPI && window.electronAPI.saveLauncherHardwareAcceleration && launcherHwAccelCheck) { if (window.electronAPI && window.electronAPI.saveLauncherHardwareAcceleration && launcherHwAccelCheck) {
@@ -515,9 +467,6 @@ async function savePlayerName() {
// Also refresh the UUID list to update which entry is marked as current // Also refresh the UUID list to update which entry is marked as current
await loadAllUuids(); await loadAllUuids();
// Refresh header identity dropdown
if (window.loadIdentities) window.loadIdentities();
} catch (error) { } catch (error) {
console.error('Error saving player name:', error); console.error('Error saving player name:', error);
const errorMsg = window.i18n ? window.i18n.t('notifications.playerNameSaveFailed') : 'Failed to save player name'; const errorMsg = window.i18n ? window.i18n.t('notifications.playerNameSaveFailed') : 'Failed to save player name';
@@ -617,11 +566,9 @@ async function loadAllSettings() {
await loadCurrentUuid(); await loadCurrentUuid();
await loadDiscordRPC(); await loadDiscordRPC();
await loadCloseLauncher(); await loadCloseLauncher();
await loadAllowMultiInstance();
await loadLauncherHwAccel(); await loadLauncherHwAccel();
await loadGpuPreference(); await loadGpuPreference();
await loadVersionBranch(); await loadVersionBranch();
await loadWrapperConfigUI();
} }
@@ -682,6 +629,7 @@ async function loadCurrentUuid() {
const uuid = await window.electronAPI.getCurrentUuid(); const uuid = await window.electronAPI.getCurrentUuid();
if (uuid) { if (uuid) {
if (currentUuidDisplay) currentUuidDisplay.value = uuid; if (currentUuidDisplay) currentUuidDisplay.value = uuid;
if (modalCurrentUuid) modalCurrentUuid.value = uuid;
} }
} }
} catch (error) { } catch (error) {
@@ -691,7 +639,7 @@ async function loadCurrentUuid() {
async function copyCurrentUuid() { async function copyCurrentUuid() {
try { try {
const uuid = currentUuidDisplay ? currentUuidDisplay.value : null; const uuid = currentUuidDisplay ? currentUuidDisplay.value : modalCurrentUuid?.value;
if (uuid && navigator.clipboard) { if (uuid && navigator.clipboard) {
await navigator.clipboard.writeText(uuid); await navigator.clipboard.writeText(uuid);
const msg = window.i18n ? window.i18n.t('notifications.uuidCopied') : 'UUID copied to clipboard!'; const msg = window.i18n ? window.i18n.t('notifications.uuidCopied') : 'UUID copied to clipboard!';
@@ -739,13 +687,13 @@ async function performRegenerateUuid() {
const result = await window.electronAPI.resetCurrentUserUuid(); const result = await window.electronAPI.resetCurrentUserUuid();
if (result.success && result.uuid) { if (result.success && result.uuid) {
if (currentUuidDisplay) currentUuidDisplay.value = result.uuid; if (currentUuidDisplay) currentUuidDisplay.value = result.uuid;
if (modalCurrentUuid) modalCurrentUuid.value = result.uuid;
const msg = window.i18n ? window.i18n.t('notifications.uuidGenerated') : 'New UUID generated successfully!'; const msg = window.i18n ? window.i18n.t('notifications.uuidGenerated') : 'New UUID generated successfully!';
showNotification(msg, 'success'); showNotification(msg, 'success');
if (uuidModal && uuidModal.style.display !== 'none') { if (uuidModal && uuidModal.style.display !== 'none') {
await loadAllUuids(); await loadAllUuids();
} }
if (window.loadIdentities) window.loadIdentities();
} else { } else {
throw new Error(result.error || 'Failed to generate new UUID'); throw new Error(result.error || 'Failed to generate new UUID');
} }
@@ -768,10 +716,6 @@ async function openUuidModal() {
} }
} }
// Expose globally so identity dropdown and Escape handler can use them
window.openUuidModal = openUuidModal;
window.loadCurrentUuid = loadCurrentUuid;
function closeUuidModal() { function closeUuidModal() {
if (uuidModal) { if (uuidModal) {
uuidModal.classList.remove('active'); uuidModal.classList.remove('active');
@@ -780,7 +724,6 @@ function closeUuidModal() {
}, 300); }, 300);
} }
} }
window.closeUuidModal = closeUuidModal;
async function loadAllUuids() { async function loadAllUuids() {
try { try {
@@ -825,9 +768,6 @@ async function loadAllUuids() {
<button class="uuid-item-btn copy" onclick="copyUuid('${mapping.uuid}')" title="Copy UUID"> <button class="uuid-item-btn copy" onclick="copyUuid('${mapping.uuid}')" title="Copy UUID">
<i class="fas fa-copy"></i> <i class="fas fa-copy"></i>
</button> </button>
${mapping.isCurrent ? `<button class="uuid-item-btn regenerate" onclick="regenerateUuidForUser('${escapeHtml(mapping.username)}')" title="Regenerate UUID">
<i class="fas fa-sync-alt"></i>
</button>` : ''}
${!mapping.isCurrent ? `<button class="uuid-item-btn delete" onclick="deleteUuid('${escapeHtml(mapping.username)}')" title="Delete UUID"> ${!mapping.isCurrent ? `<button class="uuid-item-btn delete" onclick="deleteUuid('${escapeHtml(mapping.username)}')" title="Delete UUID">
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>
</button>` : ''} </button>` : ''}
@@ -850,119 +790,23 @@ async function loadAllUuids() {
} }
} }
async function showAddIdentityForm() { async function generateNewUuid() {
if (!uuidAddForm) return;
uuidAddForm.style.display = 'block';
if (addIdentityUsername) {
addIdentityUsername.value = '';
addIdentityUsername.focus();
}
if (addIdentityUuid) {
try { try {
if (window.electronAPI && window.electronAPI.generateNewUuid) { if (window.electronAPI && window.electronAPI.generateNewUuid) {
const newUuid = await window.electronAPI.generateNewUuid(); const newUuid = await window.electronAPI.generateNewUuid();
if (newUuid) addIdentityUuid.value = newUuid; if (newUuid) {
} if (customUuidInput) customUuidInput.value = newUuid;
} catch (e) { const msg = window.i18n ? window.i18n.t('notifications.uuidGeneratedShort') : 'New UUID generated!';
console.error('Error pre-generating UUID:', e);
}
}
if (addIdentityBtn) addIdentityBtn.style.display = 'none';
}
function hideAddIdentityForm() {
if (uuidAddForm) uuidAddForm.style.display = 'none';
if (addIdentityBtn) addIdentityBtn.style.display = '';
}
async function regenerateAddIdentityUuid() {
try {
if (window.electronAPI && window.electronAPI.generateNewUuid) {
const newUuid = await window.electronAPI.generateNewUuid();
if (newUuid && addIdentityUuid) {
addIdentityUuid.value = newUuid;
}
}
} catch (error) {
console.error('Error generating UUID:', error);
}
}
async function confirmAddIdentity() {
try {
const username = addIdentityUsername ? addIdentityUsername.value.trim() : '';
const uuid = addIdentityUuid ? addIdentityUuid.value.trim() : '';
if (!username) {
const msg = window.i18n ? window.i18n.t('notifications.playerNameRequired') : 'Please enter a username';
showNotification(msg, 'error');
return;
}
if (username.length > 16) {
const msg = window.i18n ? window.i18n.t('notifications.playerNameTooLong') : 'Username must be 16 characters or less';
showNotification(msg, 'error');
return;
}
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!uuid || !uuidRegex.test(uuid)) {
const msg = window.i18n ? window.i18n.t('notifications.uuidInvalidFormat') : 'Invalid UUID format';
showNotification(msg, 'error');
return;
}
if (window.electronAPI && window.electronAPI.setUuidForUser) {
const result = await window.electronAPI.setUuidForUser(username, uuid);
if (result.success) {
const msg = window.i18n ? window.i18n.t('notifications.identityAdded') : 'Identity added successfully!';
showNotification(msg, 'success'); showNotification(msg, 'success');
hideAddIdentityForm();
await loadAllUuids();
if (window.loadIdentities) window.loadIdentities();
} else {
throw new Error(result.error || 'Failed to add identity');
} }
} }
} catch (error) { } catch (error) {
console.error('Error adding identity:', error); console.error('Error generating new UUID:', error);
const msg = window.i18n ? window.i18n.t('notifications.identityAddFailed') : 'Failed to add identity'; const msg = window.i18n ? window.i18n.t('notifications.uuidGenerateFailed') : 'Failed to generate new UUID';
showNotification(msg, 'error'); showNotification(msg, 'error');
} }
} }
function toggleAdvancedSection() {
if (!uuidAdvancedContent || !uuidAdvancedToggle) return;
const isOpen = uuidAdvancedContent.style.display !== 'none';
uuidAdvancedContent.style.display = isOpen ? 'none' : 'block';
const chevron = uuidAdvancedToggle.querySelector('.uuid-advanced-chevron');
if (chevron) {
chevron.classList.toggle('open', !isOpen);
}
}
window.regenerateUuidForUser = async function (username) {
try {
const message = window.i18n ? window.i18n.t('confirm.regenerateUuidMessage') : 'Are you sure you want to generate a new UUID? This will change your player identity.';
const title = window.i18n ? window.i18n.t('confirm.regenerateUuidTitle') : 'Generate New UUID';
const confirmBtn = window.i18n ? window.i18n.t('confirm.regenerateUuidButton') : 'Generate';
const cancelBtn = window.i18n ? window.i18n.t('common.cancel') : 'Cancel';
showCustomConfirm(
message,
title,
async () => {
await performRegenerateUuid();
},
null,
confirmBtn,
cancelBtn
);
} catch (error) {
console.error('Error regenerating UUID:', error);
}
};
async function setCustomUuid() { async function setCustomUuid() {
try { try {
if (!customUuidInput || !customUuidInput.value.trim()) { if (!customUuidInput || !customUuidInput.value.trim()) {
@@ -1020,13 +864,13 @@ async function performSetCustomUuid(uuid) {
if (result.success) { if (result.success) {
if (currentUuidDisplay) currentUuidDisplay.value = uuid; if (currentUuidDisplay) currentUuidDisplay.value = uuid;
if (modalCurrentUuid) modalCurrentUuid.value = uuid;
if (customUuidInput) customUuidInput.value = ''; if (customUuidInput) customUuidInput.value = '';
const msg = window.i18n ? window.i18n.t('notifications.uuidSetSuccess') : 'Custom UUID set successfully!'; const msg = window.i18n ? window.i18n.t('notifications.uuidSetSuccess') : 'Custom UUID set successfully!';
showNotification(msg, 'success'); showNotification(msg, 'success');
await loadAllUuids(); await loadAllUuids();
if (window.loadIdentities) window.loadIdentities();
} else { } else {
throw new Error(result.error || 'Failed to set custom UUID'); throw new Error(result.error || 'Failed to set custom UUID');
} }
@@ -1105,9 +949,6 @@ async function performSwitchToUsername(username) {
// Refresh the UUID list to show new "Current" badge // Refresh the UUID list to show new "Current" badge
await loadAllUuids(); await loadAllUuids();
// Refresh header identity dropdown
if (window.loadIdentities) window.loadIdentities();
const msg = window.i18n const msg = window.i18n
? window.i18n.t('notifications.switchUsernameSuccess').replace('{username}', username) ? window.i18n.t('notifications.switchUsernameSuccess').replace('{username}', username)
: `Switched to "${username}" successfully!`; : `Switched to "${username}" successfully!`;
@@ -1155,7 +996,6 @@ async function performDeleteUuid(username) {
const msg = window.i18n ? window.i18n.t('notifications.uuidDeleteSuccess') : 'UUID deleted successfully!'; const msg = window.i18n ? window.i18n.t('notifications.uuidDeleteSuccess') : 'UUID deleted successfully!';
showNotification(msg, 'success'); showNotification(msg, 'success');
await loadAllUuids(); await loadAllUuids();
if (window.loadIdentities) window.loadIdentities();
} else { } else {
throw new Error(result.error || 'Failed to delete UUID'); throw new Error(result.error || 'Failed to delete UUID');
} }
@@ -1414,235 +1254,3 @@ async function loadVersionBranch() {
return 'release'; return 'release';
} }
} }
// === Java Wrapper Configuration UI ===
let _wrapperConfig = null;
let _wrapperPreviewOpen = false;
async function loadWrapperConfigUI() {
try {
if (!window.electronAPI || !window.electronAPI.loadWrapperConfig) return;
_wrapperConfig = await window.electronAPI.loadWrapperConfig();
renderStripFlagsList();
renderInjectArgsList();
setupWrapperEventListeners();
} catch (error) {
console.error('Error loading wrapper config UI:', error);
}
}
function renderStripFlagsList() {
const container = document.getElementById('wrapperStripFlagsList');
if (!container || !_wrapperConfig) return;
if (_wrapperConfig.stripFlags.length === 0) {
container.innerHTML = '<div class="wrapper-items-empty">No flags configured</div>';
return;
}
container.innerHTML = '';
_wrapperConfig.stripFlags.forEach((flag, index) => {
const item = document.createElement('div');
item.className = 'wrapper-item';
item.innerHTML = `
<span class="wrapper-item-text">${escapeHtml(flag)}</span>
<button class="wrapper-item-delete" data-index="${index}" title="Remove">
<i class="fas fa-trash-alt"></i>
</button>
`;
item.querySelector('.wrapper-item-delete').addEventListener('click', () => removeStripFlag(index));
container.appendChild(item);
});
}
function renderInjectArgsList() {
const container = document.getElementById('wrapperInjectArgsList');
if (!container || !_wrapperConfig) return;
if (_wrapperConfig.injectArgs.length === 0) {
container.innerHTML = '<div class="wrapper-items-empty">No arguments configured</div>';
return;
}
container.innerHTML = '';
_wrapperConfig.injectArgs.forEach((entry, index) => {
const item = document.createElement('div');
item.className = 'wrapper-item';
const serverLabel = window.i18n ? window.i18n.t('settings.wrapperConditionServer') : 'Server Only';
const alwaysLabel = window.i18n ? window.i18n.t('settings.wrapperConditionAlways') : 'Always';
item.innerHTML = `
<span class="wrapper-item-text">${escapeHtml(entry.arg)}</span>
<div class="wrapper-item-condition">
<select data-index="${index}">
<option value="server"${entry.condition === 'server' ? ' selected' : ''}>${serverLabel}</option>
<option value="always"${entry.condition === 'always' ? ' selected' : ''}>${alwaysLabel}</option>
</select>
</div>
<button class="wrapper-item-delete" data-index="${index}" title="Remove">
<i class="fas fa-trash-alt"></i>
</button>
`;
item.querySelector('select').addEventListener('change', (e) => updateArgCondition(index, e.target.value));
item.querySelector('.wrapper-item-delete').addEventListener('click', () => removeInjectArg(index));
container.appendChild(item);
});
}
async function addStripFlag() {
const input = document.getElementById('wrapperAddFlagInput');
if (!input || !_wrapperConfig) return;
const flag = input.value.trim();
if (!flag) return;
if (_wrapperConfig.stripFlags.includes(flag)) {
const msg = window.i18n ? window.i18n.t('notifications.wrapperFlagExists') : 'This flag is already in the list';
showNotification(msg, 'error');
return;
}
_wrapperConfig.stripFlags.push(flag);
input.value = '';
renderStripFlagsList();
await saveWrapperConfigToBackend();
await updateWrapperPreview();
}
async function removeStripFlag(index) {
if (!_wrapperConfig) return;
_wrapperConfig.stripFlags.splice(index, 1);
renderStripFlagsList();
await saveWrapperConfigToBackend();
await updateWrapperPreview();
}
async function addInjectArg() {
const input = document.getElementById('wrapperAddArgInput');
const condSelect = document.getElementById('wrapperAddArgCondition');
if (!input || !condSelect || !_wrapperConfig) return;
const arg = input.value.trim();
if (!arg) return;
const exists = _wrapperConfig.injectArgs.some(e => e.arg === arg);
if (exists) {
const msg = window.i18n ? window.i18n.t('notifications.wrapperArgExists') : 'This argument is already in the list';
showNotification(msg, 'error');
return;
}
_wrapperConfig.injectArgs.push({ arg, condition: condSelect.value });
input.value = '';
renderInjectArgsList();
await saveWrapperConfigToBackend();
await updateWrapperPreview();
}
async function removeInjectArg(index) {
if (!_wrapperConfig) return;
_wrapperConfig.injectArgs.splice(index, 1);
renderInjectArgsList();
await saveWrapperConfigToBackend();
await updateWrapperPreview();
}
async function updateArgCondition(index, condition) {
if (!_wrapperConfig || !_wrapperConfig.injectArgs[index]) return;
_wrapperConfig.injectArgs[index].condition = condition;
await saveWrapperConfigToBackend();
await updateWrapperPreview();
}
async function saveWrapperConfigToBackend() {
try {
const result = await window.electronAPI.saveWrapperConfig(_wrapperConfig);
if (!result || !result.success) {
throw new Error(result?.error || 'Save failed');
}
} catch (error) {
console.error('Error saving wrapper config:', error);
const msg = window.i18n ? window.i18n.t('notifications.wrapperConfigSaveFailed') : 'Failed to save wrapper configuration';
showNotification(msg, 'error');
}
}
function setupWrapperEventListeners() {
const addFlagBtn = document.getElementById('wrapperAddFlagBtn');
const addFlagInput = document.getElementById('wrapperAddFlagInput');
const addArgBtn = document.getElementById('wrapperAddArgBtn');
const addArgInput = document.getElementById('wrapperAddArgInput');
const restoreBtn = document.getElementById('wrapperRestoreDefaultsBtn');
const previewToggle = document.getElementById('wrapperPreviewToggle');
if (addFlagBtn) addFlagBtn.addEventListener('click', addStripFlag);
if (addFlagInput) addFlagInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') addStripFlag(); });
if (addArgBtn) addArgBtn.addEventListener('click', addInjectArg);
if (addArgInput) addArgInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') addInjectArg(); });
if (restoreBtn) {
restoreBtn.addEventListener('click', () => {
const message = window.i18n ? window.i18n.t('confirm.resetWrapperMessage') : 'Are you sure you want to restore defaults? Your custom changes will be lost.';
const title = window.i18n ? window.i18n.t('confirm.resetWrapperTitle') : 'Restore Defaults';
showCustomConfirm(message, title, async () => {
try {
const result = await window.electronAPI.resetWrapperConfig();
if (result && result.success) {
_wrapperConfig = result.config;
renderStripFlagsList();
renderInjectArgsList();
await updateWrapperPreview();
const msg = window.i18n ? window.i18n.t('notifications.wrapperConfigReset') : 'Wrapper configuration restored to defaults';
showNotification(msg, 'success');
} else {
throw new Error(result?.error || 'Reset failed');
}
} catch (error) {
console.error('Error resetting wrapper config:', error);
const msg = window.i18n ? window.i18n.t('notifications.wrapperConfigResetFailed') : 'Failed to restore wrapper configuration';
showNotification(msg, 'error');
}
});
});
}
if (previewToggle) {
previewToggle.addEventListener('click', toggleWrapperPreview);
}
}
async function toggleWrapperPreview() {
const container = document.getElementById('wrapperPreviewContainer');
const chevron = document.getElementById('wrapperPreviewChevron');
if (!container) return;
_wrapperPreviewOpen = !_wrapperPreviewOpen;
if (_wrapperPreviewOpen) {
container.style.display = 'block';
if (chevron) chevron.classList.add('expanded');
await updateWrapperPreview();
} else {
container.style.display = 'none';
if (chevron) chevron.classList.remove('expanded');
}
}
async function updateWrapperPreview() {
if (!_wrapperPreviewOpen || !_wrapperConfig) return;
const previewEl = document.getElementById('wrapperPreviewContent');
if (!previewEl) return;
try {
const platform = await window.electronAPI.getCurrentPlatform();
const script = await window.electronAPI.previewWrapperScript(_wrapperConfig, platform);
previewEl.textContent = script;
} catch (error) {
previewEl.textContent = 'Error generating preview: ' + error.message;
}
}

View File

@@ -79,18 +79,12 @@ function setupWindowControls() {
const header = document.querySelector('.header'); const header = document.querySelector('.header');
const profileSelector = document.querySelector('.profile-selector'); const profileSelector = document.querySelector('.profile-selector');
const identitySelector = document.querySelector('.identity-selector');
if (profileSelector) { if (profileSelector) {
profileSelector.style.pointerEvents = 'auto'; profileSelector.style.pointerEvents = 'auto';
profileSelector.style.zIndex = '10000'; profileSelector.style.zIndex = '10000';
} }
if (identitySelector) {
identitySelector.style.pointerEvents = 'auto';
identitySelector.style.zIndex = '10000';
}
if (windowControls) { if (windowControls) {
windowControls.style.pointerEvents = 'auto'; windowControls.style.pointerEvents = 'auto';
windowControls.style.zIndex = '10000'; windowControls.style.zIndex = '10000';
@@ -104,9 +98,6 @@ function setupWindowControls() {
if (profileSelector) { if (profileSelector) {
profileSelector.style.webkitAppRegion = 'no-drag'; profileSelector.style.webkitAppRegion = 'no-drag';
} }
if (identitySelector) {
identitySelector.style.webkitAppRegion = 'no-drag';
}
} }
if (window.electronAPI) { if (window.electronAPI) {
@@ -1112,56 +1103,9 @@ function getRetryContextMessage() {
} }
window.openDiscordExternal = function() { window.openDiscordExternal = function() {
// window.electronAPI?.openExternal('https://discord.gg/Fhbb9Yk5WW'); window.electronAPI?.openExternal('https://discord.gg/hf2pdc');
window.electronAPI?.openExternal('https://chat.sanhost.net/invite/Tfz4jCK4');
}; };
window.toggleMaximize = toggleMaximize; window.toggleMaximize = toggleMaximize;
// Global Escape key handler for closing popups/modals/dropdowns
document.addEventListener('keydown', (e) => {
if (e.key !== 'Escape') return;
// Custom confirm dialogs handle their own Escape — skip if one is open
if (document.querySelector('.custom-confirm-modal')) return;
// Close modals (highest priority)
const profileModal = document.getElementById('profileManagerModal');
if (profileModal && profileModal.style.display !== 'none') {
if (window.closeProfileManager) window.closeProfileManager();
return;
}
const uuidModal = document.getElementById('uuidModal');
if (uuidModal && uuidModal.style.display !== 'none') {
if (window.closeUuidModal) window.closeUuidModal();
return;
}
const discordModal = document.getElementById('discordPopupModal');
if (discordModal && discordModal.style.display !== 'none') {
discordModal.style.display = 'none';
return;
}
const versionModal = document.getElementById('versionSelectModal');
if (versionModal && versionModal.style.display !== 'none') {
versionModal.style.display = 'none';
return;
}
// Close dropdowns (lower priority)
const identityDropdown = document.getElementById('identityDropdown');
if (identityDropdown && identityDropdown.classList.contains('show')) {
identityDropdown.classList.remove('show');
return;
}
const profileDropdown = document.getElementById('profileDropdown');
if (profileDropdown && profileDropdown.classList.contains('show')) {
profileDropdown.classList.remove('show');
return;
}
});
document.addEventListener('DOMContentLoaded', setupUI); document.addEventListener('DOMContentLoaded', setupUI);

View File

@@ -1,273 +0,0 @@
{
"nav": {
"play": "لعب",
"mods": "المودات",
"news": "الأخبار",
"chat": "دردشة اللاعبين",
"settings": "الإعدادات"
},
"header": {
"playersLabel": "اللاعبون:",
"manageProfiles": "إدارة",
"manageIdentities": "إدارة",
"identityTooltip": "اسم اللاعب ومعرّف UUID المستخدمان في اللعبة",
"configTooltip": "إعدادات اللعبة: المودات، Java والذاكرة",
"defaultProfile": "الافتراضي"
},
"install": {
"title": "مشغل اللعب المجاني",
"playerName": "اسم اللاعب",
"playerNamePlaceholder": "أدخل اسمك",
"gameBranch": "إصدار اللعبة",
"releaseVersion": "إصدار نهائي (مستقر)",
"preReleaseVersion": "إصدار تجريبي (تجريبي)",
"customInstallation": "تثبيت مخصص",
"installationFolder": "مجلد التثبيت",
"pathPlaceholder": "الموقع الافتراضي",
"browse": "تصفح",
"installButton": "تثبيت HYTALE",
"installing": "جاري التثبيت..."
},
"play": {
"ready": "جاهز للعب",
"subtitle": "شغل Hytale وابدأ المغامرة",
"playButton": "لعب HYTALE",
"latestNews": "آخر الأخبار",
"viewAll": "عرض الكل",
"checking": "جاري التحقق...",
"play": "بدء"
},
"mods": {
"searchPlaceholder": "البحث عن مودات...",
"myMods": "موداتي",
"previous": "السابق",
"next": "التالي",
"page": "صفحة",
"of": "من",
"modalTitle": "موداتي",
"noModsFound": "لم يتم العثور على مودات",
"noModsFoundDesc": "حاول تعديل معايير البحث",
"noModsInstalled": "لا توجد مودات مثبتة",
"noModsInstalledDesc": "أضف مودات من CurseForge أو استورد ملفات محلية",
"view": "عرض",
"install": "تثبيت",
"installed": "مثبت",
"enable": "تفعيل",
"disable": "تعطيل",
"active": "نشط",
"disabled": "معطل",
"delete": "حذف المود",
"noDescription": "لا يوجد وصف متاح",
"confirmDelete": "هل أنت متأكد أنك تريد حذف \"{name}\"؟",
"confirmDeleteDesc": "لا يمكن التراجع عن هذا الإجراء.",
"confirmDeletion": "تأكيد الحذف",
"apiKeyRequired": "مطلوب مفتاح API",
"apiKeyRequiredDesc": "مطلوب مفتاح CurseForge API لتصفح المودات"
},
"news": {
"title": "كل الأخبار",
"readMore": "اقرأ المزيد"
},
"chat": {
"title": "دردشة اللاعبين",
"pickColor": "اللون",
"inputPlaceholder": "اكتب رسالتك...",
"send": "إرسال",
"online": "متصل",
"charCounter": "{current}/{max}",
"secureChat": "دردشة آمنة - الروابط محجوبة",
"joinChat": "انضمام للدردشة",
"chooseUsername": "اختر اسم مستخدم للانضمام إلى دردشة اللاعبين",
"username": "اسم المستخدم",
"usernamePlaceholder": "أدخل اسم المستخدم...",
"usernameHint": "3-20 حرفاً، حروف، أرقام، و - و _ فقط",
"joinButton": "انضمام",
"colorModal": {
"title": "تخصيص لون اسم المستخدم",
"chooseSolid": "اختر لوناً ثابتاً:",
"customColor": "لون مخصص:",
"preview": "معاينة:",
"previewUsername": "اسم المستخدم",
"apply": "تطبيق اللون"
}
},
"settings": {
"title": "الإعدادات",
"java": "بيئة تشغيل جافا",
"useCustomJava": "استخدام مسار جافا مخصص",
"javaDescription": "تجاوز بيئة جافا المرفقة واستخدام تثبيت خاص بك",
"javaPath": "مسار ملف جافا التنفيذي",
"javaPathPlaceholder": "اختر مسار جافا...",
"javaBrowse": "تصفح",
"javaHint": "اختر مجلد تثبيت جافا (يدعم ويندوز، ماك، ولينكس)",
"discord": "تكامل ديسكورد",
"enableRPC": "تفعيل نشاط ديسكورد (Rich Presence)",
"discordDescription": "إظهار نشاط المشغل الخاص بك على ديسكورد",
"game": "خيارات اللعبة",
"playerName": "اسم اللاعب",
"playerNamePlaceholder": "أدخل اسم اللاعب",
"playerNameHint": "سيتم استخدام هذا الاسم داخل اللعبة (1-16 حرفاً)",
"openGameLocation": "فتح موقع اللعبة",
"openGameLocationDesc": "فتح مجلد تثبيت اللعبة",
"account": "إدارة UUID اللاعب",
"currentUUID": "الـ UUID الحالي",
"uuidPlaceholder": "جاري تحميل UUID...",
"copyUUID": "نسخ UUID",
"regenerateUUID": "إعادة إنشاء UUID",
"uuidHint": "معرف اللاعب الفريد الخاص بك لهذا الاسم",
"manageUUIDs": "إدارة جميع الـ UUIDs",
"manageUUIDsDesc": "عرض وإدارة جميع معرفات اللاعبين",
"language": "اللغة",
"selectLanguage": "اختر اللغة",
"repairGame": "إصلاح اللعبة",
"reinstallGame": "إعادة تثبيت ملفات اللعبة (يحفظ البيانات)",
"gpuPreference": "تفضيل معالج الرسوميات (GPU)",
"gpuHint": "ميزة للمحمول فقط؛ اضبطها على Integrated إذا كنت تستخدم كمبيوتر مكتبي",
"gpuAuto": "تلقائي",
"gpuIntegrated": "مدمج",
"gpuDedicated": "منفصل",
"logs": "سجلات النظام",
"logsCopy": "نسخ",
"logsRefresh": "تحديث",
"logsFolder": "فتح المجلد",
"logsSend": "إرسال السجلات",
"logsSending": "جارٍ الإرسال...",
"logsSent": "تم الإرسال!",
"logsSendFailed": "فشل",
"logsSubmissionId": "معرف الإرسال",
"logsShareId": "شارك هذا المعرف مع الدعم عند الإبلاغ عن المشاكل",
"logsLoading": "جاري تحميل السجلات...",
"closeLauncher": "سلوك المشغل",
"closeOnStart": "إغلاق المشغل عند بدء اللعبة",
"closeOnStartDescription": "إغلاق المشغل تلقائياً بعد تشغيل Hytale",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "تسريع الأجهزة (Hardware Acceleration)",
"hwAccelDescription": "تفعيل تسريع الأجهزة للمشغل",
"gameBranch": "فرع اللعبة",
"branchRelease": "إصدار نهائي",
"branchPreRelease": "إصدار تجريبي",
"branchHint": "التبديل بين الإصدار المستقر والإصدار التجريبي",
"branchWarning": "تغيير الفرع سيؤدي إلى تحميل وتثبيت نسخة مختلفة من اللعبة",
"branchSwitching": "جاري التبديل إلى {branch}...",
"branchSwitched": "تم التبديل إلى {branch} بنجاح!",
"installRequired": "التثبيت مطلوب",
"branchInstallConfirm": "سيتم تثبيت اللعبة لفرع {branch}. هل تريد الاستمرار؟"
},
"uuid": {
"modalTitle": "إدارة UUID",
"allPlayerUUIDs": "جميع معرفات UUID للاعبين",
"addIdentity": "إضافة هوية",
"usernamePlaceholder": "اسم المستخدم",
"add": "إضافة",
"cancel": "إلغاء",
"advanced": "متقدم",
"loadingUUIDs": "جاري تحميل الـ UUIDs...",
"setCustomUUID": "تعيين UUID مخصص",
"customPlaceholder": "أدخل UUID مخصص (الصيغة: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
"setUUID": "تعيين UUID",
"warning": "تحذير: تعيين UUID مخصص سيغير هوية اللاعب الحالية",
"copyTooltip": "نسخ UUID",
"regenerateTooltip": "إنشاء UUID جديد"
},
"configurations": {
"modalTitle": "إدارة التكوينات",
"newProfilePlaceholder": "اسم التكوين الجديد",
"createProfile": "إنشاء تكوين"
},
"discord": {
"notificationText": "انضم إلى مجتمعنا على ديسكورد!",
"joinButton": "انضم إلى ديسكورد"
},
"common": {
"confirm": "تأكيد",
"cancel": "إلغاء",
"save": "حفظ",
"close": "إغلاق",
"delete": "حذف",
"edit": "تعديل",
"loading": "جاري التحميل...",
"apply": "تطبيق",
"install": "تثبيت"
},
"notifications": {
"gameDataNotFound": "خطأ: لم يتم العثور على بيانات اللعبة",
"gameUpdatedSuccess": "تم تحديث اللعبة بنجاح! 🎉",
"updateFailed": "فشل التحديث: {error}",
"updateError": "خطأ في التحديث: {error}",
"discordEnabled": "تم تفعيل نشاط ديسكورد",
"discordDisabled": "تم تعطيل نشاط ديسكورد",
"discordSaveFailed": "فشل حفظ إعدادات ديسكورد",
"playerNameRequired": "يرجى إدخال اسم لاعب صالح",
"playerNameSaved": "تم حفظ اسم اللاعب بنجاح",
"playerNameSaveFailed": "فشل حفظ اسم اللاعب",
"uuidCopied": "تم نسخ الـ UUID إلى الحافظة!",
"uuidCopyFailed": "فشل نسخ الـ UUID",
"uuidRegenNotAvailable": "إعادة إنشاء UUID غير متاحة",
"uuidRegenFailed": "فشل إعادة إنشاء الـ UUID",
"uuidGenerated": "تم إنشاء UUID جديد بنجاح!",
"uuidGeneratedShort": "تم إنشاء UUID جديد!",
"uuidGenerateFailed": "فشل إنشاء UUID جديد",
"uuidRequired": "يرجى إدخال UUID",
"uuidInvalidFormat": "صيغة UUID غير صالحة",
"uuidSetFailed": "فشل تعيين الـ UUID المخصص",
"uuidSetSuccess": "تم تعيين الـ UUID المخصص بنجاح!",
"uuidDeleteFailed": "فشل حذف الـ UUID",
"uuidDeleteSuccess": "تم حذف الـ UUID بنجاح!",
"modsDownloading": "جاري تحميل {name}...",
"modsTogglingMod": "جاري تبديل حالة المود...",
"modsDeletingMod": "جاري حذف المود...",
"modsLoadingMods": "جاري تحميل المودات من CurseForge...",
"modsInstalledSuccess": "تم تثبيت {name} بنجاح! 🎉",
"modsDeletedSuccess": "تم حذف {name} بنجاح",
"modsDownloadFailed": "فشل تحميل المود: {error}",
"modsToggleFailed": "فشل تبديل المود: {error}",
"modsDeleteFailed": "فشل حذف المود: {error}",
"modsModNotFound": "لم يتم العثور على معلومات المود",
"hwAccelSaved": "تم حفظ إعداد تسريع الأجهزة",
"hwAccelSaveFailed": "فشل حفظ إعداد تسريع الأجهزة",
"noUsername": "لم يتم تهيئة اسم مستخدم. يرجى حفظ اسم المستخدم أولاً.",
"identityAdded": "تمت إضافة الهوية بنجاح!",
"identityAddFailed": "فشل في إضافة الهوية",
"switchUsernameSuccess": "تم التبديل إلى المستخدم \"{username}\" بنجاح!",
"switchUsernameFailed": "فشل تبديل اسم المستخدم",
"playerNameTooLong": "يجب أن يكون اسم اللاعب 16 حرفاً أو أقل"
},
"confirm": {
"defaultTitle": "تأكيد الإجراء",
"regenerateUuidTitle": "إنشاء UUID جديد",
"regenerateUuidMessage": "هل أنت متأكد أنك تريد إنشاء UUID جديد؟ سيؤدي ذلك إلى تغيير هوية اللاعب الخاصة بك.",
"regenerateUuidButton": "إنشاء",
"setCustomUuidTitle": "تعيين UUID مخصص",
"setCustomUuidMessage": "هل أنت متأكد أنك تريد تعيين هذا الـ UUID المخصص؟ سيؤدي ذلك إلى تغيير هوية اللاعب الخاصة بك.",
"setCustomUuidButton": "تعيين UUID",
"deleteUuidTitle": "حذف UUID",
"deleteUuidMessage": "هل أنت متأكد أنك تريد حذف الـ UUID الخاص بـ \"{username}\"؟ لا يمكن التراجع عن هذا الإجراء.",
"deleteUuidButton": "حذف",
"uninstallGameTitle": "إلغاء تثبيت اللعبة",
"uninstallGameMessage": "هل أنت متأكد أنك تريد إلغاء تثبيت Hytale؟ سيتم حذف جميع ملفات اللعبة.",
"uninstallGameButton": "إلغاء التثبيت",
"switchUsernameTitle": "تبديل الهوية",
"switchUsernameMessage": "التبديل إلى اسم المستخدم \"{username}\"؟ سيؤدي هذا إلى تغيير هوية اللاعب الحالية.",
"switchUsernameButton": "تبديل"
},
"progress": {
"initializing": "جاري التهيئة...",
"downloading": "جاري التحميل...",
"installing": "جاري التثبيت...",
"extracting": "جاري الاستخراج...",
"verifying": "جاري التحقق...",
"switchingProfile": "جاري تبديل التكوين...",
"profileSwitched": "تم تبديل التكوين!",
"startingGame": "جاري بدء اللعبة...",
"launching": "جاري التشغيل...",
"uninstallingGame": "جاري إلغاء تثبيت اللعبة...",
"gameUninstalled": "تم إلغاء تثبيت اللعبة بنجاح!",
"uninstallFailed": "فشل إلغاء التثبيت: {error}",
"startingUpdate": "جاري بدء تحديث اللعبة الإجباري...",
"installationComplete": "تم اكتمال التثبيت بنجاح!",
"installationFailed": "فشل التثبيت: {error}",
"installingGameFiles": "جاري تثبيت ملفات اللعبة...",
"installComplete": "اكتمل التثبيت!"
}
}

View File

@@ -8,10 +8,7 @@
}, },
"header": { "header": {
"playersLabel": "Spieler:", "playersLabel": "Spieler:",
"manageProfiles": "Verwalten", "manageProfiles": "Profile verwalten",
"manageIdentities": "Verwalten",
"identityTooltip": "Dein Spielername und UUID im Spiel",
"configTooltip": "Spielkonfiguration: Mods, Java- und Speichereinstellungen",
"defaultProfile": "Standard" "defaultProfile": "Standard"
}, },
"install": { "install": {
@@ -130,18 +127,10 @@
"logsCopy": "Kopieren", "logsCopy": "Kopieren",
"logsRefresh": "Aktualisieren", "logsRefresh": "Aktualisieren",
"logsFolder": "Ordner öffnen", "logsFolder": "Ordner öffnen",
"logsSend": "Logs senden",
"logsSending": "Senden...",
"logsSent": "Gesendet!",
"logsSendFailed": "Fehlgeschlagen",
"logsSubmissionId": "Einreichungs-ID",
"logsShareId": "Teilen Sie diese ID dem Support mit, wenn Sie Probleme melden",
"logsLoading": "Protokolle werden geladen...", "logsLoading": "Protokolle werden geladen...",
"closeLauncher": "Launcher-Verhalten", "closeLauncher": "Launcher-Verhalten",
"closeOnStart": "Launcher beim Spielstart schließen", "closeOnStart": "Launcher beim Spielstart schließen",
"closeOnStartDescription": "Schließe den Launcher automatisch, nachdem Hytale gestartet wurde", "closeOnStartDescription": "Schließe den Launcher automatisch, nachdem Hytale gestartet wurde",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Hardware-Beschleunigung", "hwAccel": "Hardware-Beschleunigung",
"hwAccelDescription": "Hardware-Beschleunigung für den Launcher aktivieren", "hwAccelDescription": "Hardware-Beschleunigung für den Launcher aktivieren",
"gameBranch": "Spiel-Branch", "gameBranch": "Spiel-Branch",
@@ -156,12 +145,9 @@
}, },
"uuid": { "uuid": {
"modalTitle": "UUID-Verwaltung", "modalTitle": "UUID-Verwaltung",
"currentUserUUID": "Aktuelle Benutzer-UUID",
"allPlayerUUIDs": "Alle Spieler-UUIDs", "allPlayerUUIDs": "Alle Spieler-UUIDs",
"addIdentity": "Identität hinzufügen", "generateNew": "Neue UUID generieren",
"usernamePlaceholder": "Benutzername",
"add": "Hinzufügen",
"cancel": "Abbrechen",
"advanced": "Erweitert",
"loadingUUIDs": "UUIDs werden geladen...", "loadingUUIDs": "UUIDs werden geladen...",
"setCustomUUID": "Benutzerdefinierte UUID festlegen", "setCustomUUID": "Benutzerdefinierte UUID festlegen",
"customPlaceholder": "Benutzerdefinierte UUID eingeben (Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Benutzerdefinierte UUID eingeben (Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -170,10 +156,10 @@
"copyTooltip": "UUID kopieren", "copyTooltip": "UUID kopieren",
"regenerateTooltip": "Neue UUID generieren" "regenerateTooltip": "Neue UUID generieren"
}, },
"configurations": { "profiles": {
"modalTitle": "Konfigurationen verwalten", "modalTitle": "Profile verwalten",
"newProfilePlaceholder": "Neuer Konfigurationsname", "newProfilePlaceholder": "Neuer Profilname",
"createProfile": "Konfiguration erstellen" "createProfile": "Profil erstellen"
}, },
"discord": { "discord": {
"notificationText": "Tritt unserer Discord-Community bei!", "notificationText": "Tritt unserer Discord-Community bei!",
@@ -227,8 +213,6 @@
"hwAccelSaved": "Hardware-Beschleunigungseinstellung gespeichert", "hwAccelSaved": "Hardware-Beschleunigungseinstellung gespeichert",
"hwAccelSaveFailed": "Hardware-Beschleunigungseinstellung konnte nicht gespeichert werden", "hwAccelSaveFailed": "Hardware-Beschleunigungseinstellung konnte nicht gespeichert werden",
"noUsername": "Kein Benutzername konfiguriert. Bitte speichere zuerst deinen Benutzernamen.", "noUsername": "Kein Benutzername konfiguriert. Bitte speichere zuerst deinen Benutzernamen.",
"identityAdded": "Identität erfolgreich hinzugefügt!",
"identityAddFailed": "Fehler beim Hinzufügen der Identität",
"switchUsernameSuccess": "Erfolgreich zu \"{username}\" gewechselt!", "switchUsernameSuccess": "Erfolgreich zu \"{username}\" gewechselt!",
"switchUsernameFailed": "Benutzername konnte nicht gewechselt werden", "switchUsernameFailed": "Benutzername konnte nicht gewechselt werden",
"playerNameTooLong": "Spielername darf maximal 16 Zeichen haben" "playerNameTooLong": "Spielername darf maximal 16 Zeichen haben"
@@ -257,8 +241,8 @@
"installing": "Installiere...", "installing": "Installiere...",
"extracting": "Entpacke...", "extracting": "Entpacke...",
"verifying": "Überprüfe...", "verifying": "Überprüfe...",
"switchingProfile": "Konfiguration wird gewechselt...", "switchingProfile": "Profil wird gewechselt...",
"profileSwitched": "Konfiguration gewechselt!", "profileSwitched": "Profil gewechselt!",
"startingGame": "Spiel wird gestartet...", "startingGame": "Spiel wird gestartet...",
"launching": "STARTET...", "launching": "STARTET...",
"uninstallingGame": "Spiel wird deinstalliert...", "uninstallingGame": "Spiel wird deinstalliert...",

View File

@@ -8,10 +8,7 @@
}, },
"header": { "header": {
"playersLabel": "Players:", "playersLabel": "Players:",
"manageProfiles": "Manage", "manageProfiles": "Manage Profiles",
"manageIdentities": "Manage",
"identityTooltip": "Your player name & UUID used in-game",
"configTooltip": "Game config: mods, Java & memory settings",
"defaultProfile": "Default" "defaultProfile": "Default"
}, },
"install": { "install": {
@@ -130,18 +127,10 @@
"logsCopy": "Copy", "logsCopy": "Copy",
"logsRefresh": "Refresh", "logsRefresh": "Refresh",
"logsFolder": "Open Folder", "logsFolder": "Open Folder",
"logsSend": "Send Logs",
"logsSending": "Sending...",
"logsSent": "Sent!",
"logsSendFailed": "Failed",
"logsSubmissionId": "Submission ID",
"logsShareId": "Share this ID with support when reporting issues",
"logsLoading": "Loading logs...", "logsLoading": "Loading logs...",
"closeLauncher": "Launcher Behavior", "closeLauncher": "Launcher Behavior",
"closeOnStart": "Close Launcher on game start", "closeOnStart": "Close Launcher on game start",
"closeOnStartDescription": "Automatically close the launcher after Hytale has launched", "closeOnStartDescription": "Automatically close the launcher after Hytale has launched",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Hardware Acceleration", "hwAccel": "Hardware Acceleration",
"hwAccelDescription": "Enable hardware acceleration for the launcher", "hwAccelDescription": "Enable hardware acceleration for the launcher",
"gameBranch": "Game Branch", "gameBranch": "Game Branch",
@@ -152,39 +141,25 @@
"branchSwitching": "Switching to {branch}...", "branchSwitching": "Switching to {branch}...",
"branchSwitched": "Switched to {branch} successfully!", "branchSwitched": "Switched to {branch} successfully!",
"installRequired": "Installation Required", "installRequired": "Installation Required",
"branchInstallConfirm": "The game will be installed for the {branch} branch. Continue?", "branchInstallConfirm": "The game will be installed for the {branch} branch. Continue?"
"wrapperConfig": "Java Wrapper Configuration",
"wrapperConfigHint": "Configure how the Java wrapper handles JVM flags and arguments at launch time.",
"wrapperStripFlags": "JVM Flags to Remove",
"wrapperInjectArgs": "Arguments to Inject",
"wrapperAddFlagPlaceholder": "e.g. -XX:+SomeFlag",
"wrapperAddArgPlaceholder": "e.g. --some-flag",
"wrapperAdd": "Add",
"wrapperConditionServer": "Server Only",
"wrapperConditionAlways": "Always",
"wrapperRestoreDefaults": "Restore Defaults",
"wrapperAdvancedPreview": "Advanced: Script Preview"
}, },
"uuid": { "uuid": {
"modalTitle": "UUID Management", "modalTitle": "UUID Management",
"currentUserUUID": "Current User UUID",
"allPlayerUUIDs": "All Player UUIDs", "allPlayerUUIDs": "All Player UUIDs",
"addIdentity": "Add Identity", "generateNew": "Generate New UUID",
"usernamePlaceholder": "Username",
"add": "Add",
"cancel": "Cancel",
"advanced": "Advanced",
"loadingUUIDs": "Loading UUIDs...", "loadingUUIDs": "Loading UUIDs...",
"setCustomUUID": "Set Custom UUID for Current User", "setCustomUUID": "Set Custom UUID",
"customPlaceholder": "Enter custom UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Enter custom UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
"setUUID": "Set UUID", "setUUID": "Set UUID",
"warning": "Warning: Setting a custom UUID will change your current player identity", "warning": "Warning: Setting a custom UUID will change your current player identity",
"copyTooltip": "Copy UUID", "copyTooltip": "Copy UUID",
"regenerateTooltip": "Generate New UUID" "regenerateTooltip": "Generate New UUID"
}, },
"configurations": { "profiles": {
"modalTitle": "Manage Configurations", "modalTitle": "Manage Profiles",
"newProfilePlaceholder": "New Configuration Name", "newProfilePlaceholder": "New Profile Name",
"createProfile": "Create Configuration" "createProfile": "Create Profile"
}, },
"discord": { "discord": {
"notificationText": "Join our Discord community!", "notificationText": "Join our Discord community!",
@@ -238,17 +213,9 @@
"hwAccelSaved": "Hardware acceleration setting saved", "hwAccelSaved": "Hardware acceleration setting saved",
"hwAccelSaveFailed": "Failed to save hardware acceleration setting", "hwAccelSaveFailed": "Failed to save hardware acceleration setting",
"noUsername": "No username configured. Please save your username first.", "noUsername": "No username configured. Please save your username first.",
"identityAdded": "Identity added successfully!",
"identityAddFailed": "Failed to add identity",
"switchUsernameSuccess": "Switched to \"{username}\" successfully!", "switchUsernameSuccess": "Switched to \"{username}\" successfully!",
"switchUsernameFailed": "Failed to switch username", "switchUsernameFailed": "Failed to switch username",
"playerNameTooLong": "Player name must be 16 characters or less", "playerNameTooLong": "Player name must be 16 characters or less"
"wrapperConfigSaved": "Wrapper configuration saved",
"wrapperConfigSaveFailed": "Failed to save wrapper configuration",
"wrapperConfigReset": "Wrapper configuration restored to defaults",
"wrapperConfigResetFailed": "Failed to restore wrapper configuration",
"wrapperFlagExists": "This flag is already in the list",
"wrapperArgExists": "This argument is already in the list"
}, },
"confirm": { "confirm": {
"defaultTitle": "Confirm action", "defaultTitle": "Confirm action",
@@ -266,9 +233,7 @@
"uninstallGameButton": "Uninstall", "uninstallGameButton": "Uninstall",
"switchUsernameTitle": "Switch Identity", "switchUsernameTitle": "Switch Identity",
"switchUsernameMessage": "Switch to username \"{username}\"? This will change your current player identity.", "switchUsernameMessage": "Switch to username \"{username}\"? This will change your current player identity.",
"switchUsernameButton": "Switch", "switchUsernameButton": "Switch"
"resetWrapperTitle": "Restore Defaults",
"resetWrapperMessage": "Are you sure you want to restore the default wrapper configuration? Your custom changes will be lost."
}, },
"progress": { "progress": {
"initializing": "Initializing...", "initializing": "Initializing...",
@@ -276,8 +241,8 @@
"installing": "Installing...", "installing": "Installing...",
"extracting": "Extracting...", "extracting": "Extracting...",
"verifying": "Verifying...", "verifying": "Verifying...",
"switchingProfile": "Switching configuration...", "switchingProfile": "Switching profile...",
"profileSwitched": "Configuration switched!", "profileSwitched": "Profile switched!",
"startingGame": "Starting game...", "startingGame": "Starting game...",
"launching": "LAUNCHING...", "launching": "LAUNCHING...",
"uninstallingGame": "Uninstalling game...", "uninstallingGame": "Uninstalling game...",

View File

@@ -8,10 +8,7 @@
}, },
"header": { "header": {
"playersLabel": "Jugadores:", "playersLabel": "Jugadores:",
"manageProfiles": "Gestionar", "manageProfiles": "Gestionar Perfiles",
"manageIdentities": "Gestionar",
"identityTooltip": "Tu nombre de jugador y UUID usados en el juego",
"configTooltip": "Configuración del juego: mods, Java y memoria",
"defaultProfile": "Predeterminado" "defaultProfile": "Predeterminado"
}, },
"install": { "install": {
@@ -130,18 +127,10 @@
"logsCopy": "Copiar", "logsCopy": "Copiar",
"logsRefresh": "Actualizar", "logsRefresh": "Actualizar",
"logsFolder": "Abrir Carpeta", "logsFolder": "Abrir Carpeta",
"logsSend": "Enviar logs",
"logsSending": "Enviando...",
"logsSent": "Enviado!",
"logsSendFailed": "Fallido",
"logsSubmissionId": "ID de envío",
"logsShareId": "Comparte este ID con soporte al reportar problemas",
"logsLoading": "Cargando registros...", "logsLoading": "Cargando registros...",
"closeLauncher": "Comportamiento del Launcher", "closeLauncher": "Comportamiento del Launcher",
"closeOnStart": "Cerrar Launcher al iniciar el juego", "closeOnStart": "Cerrar Launcher al iniciar el juego",
"closeOnStartDescription": "Cierra automáticamente el launcher después de que Hytale se haya iniciado", "closeOnStartDescription": "Cierra automáticamente el launcher después de que Hytale se haya iniciado",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Aceleración por Hardware", "hwAccel": "Aceleración por Hardware",
"hwAccelDescription": "Habilitar aceleración por hardware para el launcher", "hwAccelDescription": "Habilitar aceleración por hardware para el launcher",
"gameBranch": "Rama del Juego", "gameBranch": "Rama del Juego",
@@ -156,12 +145,9 @@
}, },
"uuid": { "uuid": {
"modalTitle": "Gestión de UUID", "modalTitle": "Gestión de UUID",
"currentUserUUID": "UUID del usuario actual",
"allPlayerUUIDs": "Todos los UUIDs de jugadores", "allPlayerUUIDs": "Todos los UUIDs de jugadores",
"addIdentity": "Añadir identidad", "generateNew": "Generar nuevo UUID",
"usernamePlaceholder": "Nombre de usuario",
"add": "Añadir",
"cancel": "Cancelar",
"advanced": "Avanzado",
"loadingUUIDs": "Cargando UUIDs...", "loadingUUIDs": "Cargando UUIDs...",
"setCustomUUID": "Establecer UUID personalizado", "setCustomUUID": "Establecer UUID personalizado",
"customPlaceholder": "Ingresa un UUID personalizado (formato: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Ingresa un UUID personalizado (formato: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -170,10 +156,10 @@
"copyTooltip": "Copiar UUID", "copyTooltip": "Copiar UUID",
"regenerateTooltip": "Generar nuevo UUID" "regenerateTooltip": "Generar nuevo UUID"
}, },
"configurations": { "profiles": {
"modalTitle": "Gestionar Configuraciones", "modalTitle": "Gestionar perfiles",
"newProfilePlaceholder": "Nombre de la nueva configuración", "newProfilePlaceholder": "Nombre del nuevo perfil",
"createProfile": "Crear Configuración" "createProfile": "Crear perfil"
}, },
"discord": { "discord": {
"notificationText": "¡Únete a nuestra comunidad de Discord!", "notificationText": "¡Únete a nuestra comunidad de Discord!",
@@ -227,8 +213,6 @@
"hwAccelSaved": "Configuración de aceleración por hardware guardada", "hwAccelSaved": "Configuración de aceleración por hardware guardada",
"hwAccelSaveFailed": "Error al guardar la configuración de aceleración por hardware", "hwAccelSaveFailed": "Error al guardar la configuración de aceleración por hardware",
"noUsername": "No hay nombre de usuario configurado. Por favor, guarda tu nombre de usuario primero.", "noUsername": "No hay nombre de usuario configurado. Por favor, guarda tu nombre de usuario primero.",
"identityAdded": "¡Identidad añadida con éxito!",
"identityAddFailed": "Error al añadir identidad",
"switchUsernameSuccess": "¡Cambiado a \"{username}\" con éxito!", "switchUsernameSuccess": "¡Cambiado a \"{username}\" con éxito!",
"switchUsernameFailed": "Error al cambiar nombre de usuario", "switchUsernameFailed": "Error al cambiar nombre de usuario",
"playerNameTooLong": "El nombre del jugador debe tener 16 caracteres o menos" "playerNameTooLong": "El nombre del jugador debe tener 16 caracteres o menos"
@@ -257,8 +241,8 @@
"installing": "Instalando...", "installing": "Instalando...",
"extracting": "Extrayendo...", "extracting": "Extrayendo...",
"verifying": "Verificando...", "verifying": "Verificando...",
"switchingProfile": "Cambiando configuración...", "switchingProfile": "Cambiando perfil...",
"profileSwitched": Configuración cambiada!", "profileSwitched": Perfil cambiado!",
"startingGame": "Iniciando juego...", "startingGame": "Iniciando juego...",
"launching": "INICIANDO...", "launching": "INICIANDO...",
"uninstallingGame": "Desinstalando juego...", "uninstallingGame": "Desinstalando juego...",

View File

@@ -8,10 +8,7 @@
}, },
"header": { "header": {
"playersLabel": "Joueurs:", "playersLabel": "Joueurs:",
"manageProfiles": "Gérer", "manageProfiles": "Gérer les Profils",
"manageIdentities": "Gérer",
"identityTooltip": "Votre nom de joueur et UUID utilisés en jeu",
"configTooltip": "Configuration du jeu : mods, Java et mémoire",
"defaultProfile": "Par défaut" "defaultProfile": "Par défaut"
}, },
"install": { "install": {
@@ -130,18 +127,10 @@
"logsCopy": "Copier", "logsCopy": "Copier",
"logsRefresh": "Actualiser", "logsRefresh": "Actualiser",
"logsFolder": "Ouvrir le Dossier", "logsFolder": "Ouvrir le Dossier",
"logsSend": "Envoyer les logs",
"logsSending": "Envoi...",
"logsSent": "Envoyé !",
"logsSendFailed": "Échoué",
"logsSubmissionId": "ID de soumission",
"logsShareId": "Partagez cet ID avec le support pour signaler des problèmes",
"logsLoading": "Chargement des journaux...", "logsLoading": "Chargement des journaux...",
"closeLauncher": "Comportement du Launcher", "closeLauncher": "Comportement du Launcher",
"closeOnStart": "Fermer le Launcher au démarrage du jeu", "closeOnStart": "Fermer le Launcher au démarrage du jeu",
"closeOnStartDescription": "Fermer automatiquement le launcher après le lancement d'Hytale", "closeOnStartDescription": "Fermer automatiquement le launcher après le lancement d'Hytale",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Accélération Matérielle", "hwAccel": "Accélération Matérielle",
"hwAccelDescription": "Activer l'accélération matérielle pour le launcher", "hwAccelDescription": "Activer l'accélération matérielle pour le launcher",
"gameBranch": "Branche du Jeu", "gameBranch": "Branche du Jeu",
@@ -156,12 +145,9 @@
}, },
"uuid": { "uuid": {
"modalTitle": "Gestion UUID", "modalTitle": "Gestion UUID",
"currentUserUUID": "UUID Utilisateur Actuel",
"allPlayerUUIDs": "Tous les UUIDs Joueurs", "allPlayerUUIDs": "Tous les UUIDs Joueurs",
"addIdentity": "Ajouter une identité", "generateNew": "Générer Nouvel UUID",
"usernamePlaceholder": "Nom d'utilisateur",
"add": "Ajouter",
"cancel": "Annuler",
"advanced": "Avancé",
"loadingUUIDs": "Chargement des UUIDs...", "loadingUUIDs": "Chargement des UUIDs...",
"setCustomUUID": "Définir UUID Personnalisé", "setCustomUUID": "Définir UUID Personnalisé",
"customPlaceholder": "Entrez UUID personnalisé (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Entrez UUID personnalisé (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -170,10 +156,10 @@
"copyTooltip": "Copier UUID", "copyTooltip": "Copier UUID",
"regenerateTooltip": "Générer Nouvel UUID" "regenerateTooltip": "Générer Nouvel UUID"
}, },
"configurations": { "profiles": {
"modalTitle": "Gérer les Configurations", "modalTitle": "Gérer les Profils",
"newProfilePlaceholder": "Nom de la Nouvelle Configuration", "newProfilePlaceholder": "Nom du Nouveau Profil",
"createProfile": "Créer une Configuration" "createProfile": "Créer un Profil"
}, },
"discord": { "discord": {
"notificationText": "Rejoignez notre communauté Discord!", "notificationText": "Rejoignez notre communauté Discord!",
@@ -227,8 +213,6 @@
"hwAccelSaved": "Paramètre d'accélération matérielle sauvegardé", "hwAccelSaved": "Paramètre d'accélération matérielle sauvegardé",
"hwAccelSaveFailed": "Échec de la sauvegarde du paramètre d'accélération matérielle", "hwAccelSaveFailed": "Échec de la sauvegarde du paramètre d'accélération matérielle",
"noUsername": "Aucun nom d'utilisateur configuré. Veuillez d'abord enregistrer votre nom d'utilisateur.", "noUsername": "Aucun nom d'utilisateur configuré. Veuillez d'abord enregistrer votre nom d'utilisateur.",
"identityAdded": "Identité ajoutée avec succès !",
"identityAddFailed": "Échec de l'ajout de l'identité",
"switchUsernameSuccess": "Basculé vers \"{username}\" avec succès!", "switchUsernameSuccess": "Basculé vers \"{username}\" avec succès!",
"switchUsernameFailed": "Échec du changement de nom d'utilisateur", "switchUsernameFailed": "Échec du changement de nom d'utilisateur",
"playerNameTooLong": "Le nom du joueur doit comporter 16 caractères ou moins" "playerNameTooLong": "Le nom du joueur doit comporter 16 caractères ou moins"
@@ -257,8 +241,8 @@
"installing": "Installation...", "installing": "Installation...",
"extracting": "Extraction...", "extracting": "Extraction...",
"verifying": "Vérification...", "verifying": "Vérification...",
"switchingProfile": "Changement de configuration...", "switchingProfile": "Changement de profil...",
"profileSwitched": "Configuration changée !", "profileSwitched": "Profil changé!",
"startingGame": "Démarrage du jeu...", "startingGame": "Démarrage du jeu...",
"launching": "LANCEMENT...", "launching": "LANCEMENT...",
"uninstallingGame": "Désinstallation du jeu...", "uninstallingGame": "Désinstallation du jeu...",

View File

@@ -8,10 +8,7 @@
}, },
"header": { "header": {
"playersLabel": "Pemain:", "playersLabel": "Pemain:",
"manageProfiles": "Kelola", "manageProfiles": "Kelola Profil",
"manageIdentities": "Kelola",
"identityTooltip": "Nama pemain & UUID yang digunakan dalam game",
"configTooltip": "Konfigurasi game: mod, Java & pengaturan memori",
"defaultProfile": "Default" "defaultProfile": "Default"
}, },
"install": { "install": {
@@ -130,18 +127,10 @@
"logsCopy": "Salin", "logsCopy": "Salin",
"logsRefresh": "Segarkan", "logsRefresh": "Segarkan",
"logsFolder": "Buka Folder", "logsFolder": "Buka Folder",
"logsSend": "Kirim Log",
"logsSending": "Mengirim...",
"logsSent": "Terkirim!",
"logsSendFailed": "Gagal",
"logsSubmissionId": "ID Pengiriman",
"logsShareId": "Bagikan ID ini ke dukungan saat melaporkan masalah",
"logsLoading": "Memuat log...", "logsLoading": "Memuat log...",
"closeLauncher": "Perilaku Launcher", "closeLauncher": "Perilaku Launcher",
"closeOnStart": "Tutup launcher saat game dimulai", "closeOnStart": "Tutup launcher saat game dimulai",
"closeOnStartDescription": "Tutup launcher secara otomatis setelah Hytale diluncurkan", "closeOnStartDescription": "Tutup launcher secara otomatis setelah Hytale diluncurkan",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Akselerasi Perangkat Keras", "hwAccel": "Akselerasi Perangkat Keras",
"hwAccelDescription": "Aktifkan akselerasi perangkat keras untuk launcher`", "hwAccelDescription": "Aktifkan akselerasi perangkat keras untuk launcher`",
"gameBranch": "Cabang Game", "gameBranch": "Cabang Game",
@@ -156,12 +145,9 @@
}, },
"uuid": { "uuid": {
"modalTitle": "Manajemen UUID", "modalTitle": "Manajemen UUID",
"currentUserUUID": "UUID Pengguna Saat Ini",
"allPlayerUUIDs": "Semua UUID Pemain", "allPlayerUUIDs": "Semua UUID Pemain",
"addIdentity": "Tambah Identitas", "generateNew": "Hasilkan UUID Baru",
"usernamePlaceholder": "Nama Pengguna",
"add": "Tambah",
"cancel": "Batal",
"advanced": "Lanjutan",
"loadingUUIDs": "Memuat UUID...", "loadingUUIDs": "Memuat UUID...",
"setCustomUUID": "Setel UUID Kustom", "setCustomUUID": "Setel UUID Kustom",
"customPlaceholder": "Masukkan UUID kustom (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Masukkan UUID kustom (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -170,10 +156,10 @@
"copyTooltip": "Salin UUID", "copyTooltip": "Salin UUID",
"regenerateTooltip": "Hasilkan UUID Baru" "regenerateTooltip": "Hasilkan UUID Baru"
}, },
"configurations": { "profiles": {
"modalTitle": "Kelola Konfigurasi", "modalTitle": "Kelola Profil",
"newProfilePlaceholder": "Nama Konfigurasi Baru", "newProfilePlaceholder": "Nama Profil Baru",
"createProfile": "Buat Konfigurasi" "createProfile": "Buat Profil"
}, },
"discord": { "discord": {
"notificationText": "Gabung komunitas Discord kami!", "notificationText": "Gabung komunitas Discord kami!",
@@ -227,8 +213,6 @@
"hwAccelSaved": "Pengaturan akselerasi perangkat keras disimpan", "hwAccelSaved": "Pengaturan akselerasi perangkat keras disimpan",
"hwAccelSaveFailed": "Gagal menyimpan pengaturan akselerasi perangkat keras", "hwAccelSaveFailed": "Gagal menyimpan pengaturan akselerasi perangkat keras",
"noUsername": "Nama pengguna belum dikonfigurasi. Silakan simpan nama pengguna terlebih dahulu.", "noUsername": "Nama pengguna belum dikonfigurasi. Silakan simpan nama pengguna terlebih dahulu.",
"identityAdded": "Identitas berhasil ditambahkan!",
"identityAddFailed": "Gagal menambahkan identitas",
"switchUsernameSuccess": "Berhasil beralih ke \"{username}\"!", "switchUsernameSuccess": "Berhasil beralih ke \"{username}\"!",
"switchUsernameFailed": "Gagal beralih nama pengguna", "switchUsernameFailed": "Gagal beralih nama pengguna",
"playerNameTooLong": "Nama pemain harus 16 karakter atau kurang" "playerNameTooLong": "Nama pemain harus 16 karakter atau kurang"
@@ -257,8 +241,8 @@
"installing": "Menginstal...", "installing": "Menginstal...",
"extracting": "Mengekstrak...", "extracting": "Mengekstrak...",
"verifying": "Memverifikasi...", "verifying": "Memverifikasi...",
"switchingProfile": "Beralih konfigurasi...", "switchingProfile": "Beralih profil...",
"profileSwitched": "Konfigurasi dialihkan!", "profileSwitched": "Profil dialihkan!",
"startingGame": "Memulai game...", "startingGame": "Memulai game...",
"launching": "MELUNCURKAN...", "launching": "MELUNCURKAN...",
"uninstallingGame": "Menghapus instalasi game...", "uninstallingGame": "Menghapus instalasi game...",

View File

@@ -8,10 +8,7 @@
}, },
"header": { "header": {
"playersLabel": "Graczy:", "playersLabel": "Graczy:",
"manageProfiles": "Zarządzaj", "manageProfiles": "Zarządzaj Profilami",
"manageIdentities": "Zarządzaj",
"identityTooltip": "Twoja nazwa gracza i UUID używane w grze",
"configTooltip": "Konfiguracja gry: mody, Java i ustawienia pamięci",
"defaultProfile": "Domyślny" "defaultProfile": "Domyślny"
}, },
"install": { "install": {
@@ -130,18 +127,10 @@
"logsCopy": "Kopiuj", "logsCopy": "Kopiuj",
"logsRefresh": "Odśwież", "logsRefresh": "Odśwież",
"logsFolder": "Otwórz Folder", "logsFolder": "Otwórz Folder",
"logsSend": "Wyślij logi",
"logsSending": "Wysyłanie...",
"logsSent": "Wysłano!",
"logsSendFailed": "Błąd",
"logsSubmissionId": "ID zgłoszenia",
"logsShareId": "Udostępnij ten ID wsparciu technicznemu przy zgłaszaniu problemów",
"logsLoading": "Ładowanie logów...", "logsLoading": "Ładowanie logów...",
"closeLauncher": "Zachowanie Launchera", "closeLauncher": "Zachowanie Launchera",
"closeOnStart": "Zamknij Launcher przy starcie gry", "closeOnStart": "Zamknij Launcher przy starcie gry",
"closeOnStartDescription": "Automatycznie zamknij launcher po uruchomieniu Hytale", "closeOnStartDescription": "Automatycznie zamknij launcher po uruchomieniu Hytale",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Przyspieszenie Sprzętowe", "hwAccel": "Przyspieszenie Sprzętowe",
"hwAccelDescription": "Włącz przyspieszenie sprzętowe dla launchera", "hwAccelDescription": "Włącz przyspieszenie sprzętowe dla launchera",
"gameBranch": "Gałąź Gry", "gameBranch": "Gałąź Gry",
@@ -156,12 +145,9 @@
}, },
"uuid": { "uuid": {
"modalTitle": "Zarządzanie UUID", "modalTitle": "Zarządzanie UUID",
"currentUserUUID": "Aktualny UUID użytkownika",
"allPlayerUUIDs": "Wszystkie identyfikatory UUID graczy", "allPlayerUUIDs": "Wszystkie identyfikatory UUID graczy",
"addIdentity": "Dodaj tożsamość", "generateNew": "Wygeneruj nowy UUID",
"usernamePlaceholder": "Nazwa użytkownika",
"add": "Dodaj",
"cancel": "Anuluj",
"advanced": "Zaawansowane",
"loadingUUIDs": "Ładowanie UUID...", "loadingUUIDs": "Ładowanie UUID...",
"setCustomUUID": "Ustaw niestandardowy UUID", "setCustomUUID": "Ustaw niestandardowy UUID",
"customPlaceholder": "Wprowadź niestandardowy UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Wprowadź niestandardowy UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -170,10 +156,10 @@
"copyTooltip": "Kopiuj UUID", "copyTooltip": "Kopiuj UUID",
"regenerateTooltip": "Wygeneruj nowy UUID" "regenerateTooltip": "Wygeneruj nowy UUID"
}, },
"configurations": { "profiles": {
"modalTitle": "Zarządzaj Konfiguracjami", "modalTitle": "Zarządzaj Profilami",
"newProfilePlaceholder": "Nazwa Nowej Konfiguracji", "newProfilePlaceholder": "Nowa Nazwa Profilu",
"createProfile": "Utwórz Konfigurację" "createProfile": "Utwórz Profil"
}, },
"discord": { "discord": {
"notificationText": "Dołącz do naszej społeczności Discord!", "notificationText": "Dołącz do naszej społeczności Discord!",
@@ -227,8 +213,6 @@
"hwAccelSaved": "Zapisano ustawienie przyspieszenia sprzętowego", "hwAccelSaved": "Zapisano ustawienie przyspieszenia sprzętowego",
"hwAccelSaveFailed": "Nie udało się zapisać ustawienia przyspieszenia sprzętowego", "hwAccelSaveFailed": "Nie udało się zapisać ustawienia przyspieszenia sprzętowego",
"noUsername": "Nie skonfigurowano nazwy użytkownika. Najpierw zapisz swoją nazwę użytkownika.", "noUsername": "Nie skonfigurowano nazwy użytkownika. Najpierw zapisz swoją nazwę użytkownika.",
"identityAdded": "Tożsamość dodana pomyślnie!",
"identityAddFailed": "Nie udało się dodać tożsamości",
"switchUsernameSuccess": "Pomyślnie przełączono na \"{username}\"!", "switchUsernameSuccess": "Pomyślnie przełączono na \"{username}\"!",
"switchUsernameFailed": "Nie udało się przełączyć nazwy użytkownika", "switchUsernameFailed": "Nie udało się przełączyć nazwy użytkownika",
"playerNameTooLong": "Nazwa gracza musi mieć 16 znaków lub mniej" "playerNameTooLong": "Nazwa gracza musi mieć 16 znaków lub mniej"
@@ -257,8 +241,8 @@
"installing": "Instalowanie...", "installing": "Instalowanie...",
"extracting": "Ekstraktowanie...", "extracting": "Ekstraktowanie...",
"verifying": "Weryfikowanie...", "verifying": "Weryfikowanie...",
"switchingProfile": "Przełączanie konfiguracji...", "switchingProfile": "Przełączanie profilu...",
"profileSwitched": "Konfiguracja zmieniona!", "profileSwitched": "Profil zmieniony!",
"startingGame": "Uruchamianie gry...", "startingGame": "Uruchamianie gry...",
"launching": "URUCHAMIANIE...", "launching": "URUCHAMIANIE...",
"uninstallingGame": "Odinstalowywanie gry...", "uninstallingGame": "Odinstalowywanie gry...",

View File

@@ -8,10 +8,7 @@
}, },
"header": { "header": {
"playersLabel": "Jogadores:", "playersLabel": "Jogadores:",
"manageProfiles": "Gerenciar", "manageProfiles": "Gerenciar Perfis",
"manageIdentities": "Gerenciar",
"identityTooltip": "Seu nome de jogador e UUID usados no jogo",
"configTooltip": "Configuração do jogo: mods, Java e memória",
"defaultProfile": "Padrão" "defaultProfile": "Padrão"
}, },
"install": { "install": {
@@ -130,18 +127,10 @@
"logsCopy": "Copiar", "logsCopy": "Copiar",
"logsRefresh": "Atualizar", "logsRefresh": "Atualizar",
"logsFolder": "Abrir Pasta", "logsFolder": "Abrir Pasta",
"logsSend": "Enviar logs",
"logsSending": "Enviando...",
"logsSent": "Enviado!",
"logsSendFailed": "Falhou",
"logsSubmissionId": "ID de envio",
"logsShareId": "Compartilhe este ID com o suporte ao relatar problemas",
"logsLoading": "Carregando registros...", "logsLoading": "Carregando registros...",
"closeLauncher": "Comportamento do Lançador", "closeLauncher": "Comportamento do Lançador",
"closeOnStart": "Fechar Lançador ao iniciar o jogo", "closeOnStart": "Fechar Lançador ao iniciar o jogo",
"closeOnStartDescription": "Fechar automaticamente o lançador após o Hytale ter sido iniciado", "closeOnStartDescription": "Fechar automaticamente o lançador após o Hytale ter sido iniciado",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Aceleração de Hardware", "hwAccel": "Aceleração de Hardware",
"hwAccelDescription": "Ativar aceleração de hardware para o lançador", "hwAccelDescription": "Ativar aceleração de hardware para o lançador",
"gameBranch": "Versão do Jogo", "gameBranch": "Versão do Jogo",
@@ -156,12 +145,9 @@
}, },
"uuid": { "uuid": {
"modalTitle": "Gerenciamento de UUID", "modalTitle": "Gerenciamento de UUID",
"currentUserUUID": "UUID do usuário atual",
"allPlayerUUIDs": "Todos os UUIDs de jogadores", "allPlayerUUIDs": "Todos os UUIDs de jogadores",
"addIdentity": "Adicionar identidade", "generateNew": "Gerar novo UUID",
"usernamePlaceholder": "Nome de usuário",
"add": "Adicionar",
"cancel": "Cancelar",
"advanced": "Avançado",
"loadingUUIDs": "Carregando UUIDs...", "loadingUUIDs": "Carregando UUIDs...",
"setCustomUUID": "Definir UUID personalizado", "setCustomUUID": "Definir UUID personalizado",
"customPlaceholder": "Digite um UUID personalizado (formato: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Digite um UUID personalizado (formato: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -170,10 +156,10 @@
"copyTooltip": "Copiar UUID", "copyTooltip": "Copiar UUID",
"regenerateTooltip": "Gerar novo UUID" "regenerateTooltip": "Gerar novo UUID"
}, },
"configurations": { "profiles": {
"modalTitle": "Gerenciar Configurações", "modalTitle": "Gerenciar perfis",
"newProfilePlaceholder": "Nome da Nova Configuração", "newProfilePlaceholder": "Nome do novo perfil",
"createProfile": "Criar Configuração" "createProfile": "Criar perfil"
}, },
"discord": { "discord": {
"notificationText": "Junte-se à nossa comunidade do Discord!", "notificationText": "Junte-se à nossa comunidade do Discord!",
@@ -227,8 +213,6 @@
"hwAccelSaved": "Configuração de aceleração de hardware salva", "hwAccelSaved": "Configuração de aceleração de hardware salva",
"hwAccelSaveFailed": "Falha ao salvar configuração de aceleração de hardware", "hwAccelSaveFailed": "Falha ao salvar configuração de aceleração de hardware",
"noUsername": "Nenhum nome de usuário configurado. Por favor, salve seu nome de usuário primeiro.", "noUsername": "Nenhum nome de usuário configurado. Por favor, salve seu nome de usuário primeiro.",
"identityAdded": "Identidade adicionada com sucesso!",
"identityAddFailed": "Falha ao adicionar identidade",
"switchUsernameSuccess": "Alterado para \"{username}\" com sucesso!", "switchUsernameSuccess": "Alterado para \"{username}\" com sucesso!",
"switchUsernameFailed": "Falha ao trocar nome de usuário", "switchUsernameFailed": "Falha ao trocar nome de usuário",
"playerNameTooLong": "O nome do jogador deve ter 16 caracteres ou menos" "playerNameTooLong": "O nome do jogador deve ter 16 caracteres ou menos"
@@ -257,8 +241,8 @@
"installing": "Instalando...", "installing": "Instalando...",
"extracting": "Extraindo...", "extracting": "Extraindo...",
"verifying": "Verificando...", "verifying": "Verificando...",
"switchingProfile": "Alternando configuração...", "switchingProfile": "Alternando perfil...",
"profileSwitched": "Configuração alternada!", "profileSwitched": "Perfil alternado!",
"startingGame": "Iniciando jogo...", "startingGame": "Iniciando jogo...",
"launching": "INICIANDO...", "launching": "INICIANDO...",
"uninstallingGame": "Desinstalando jogo...", "uninstallingGame": "Desinstalando jogo...",

View File

@@ -8,10 +8,7 @@
}, },
"header": { "header": {
"playersLabel": "Игроки:", "playersLabel": "Игроки:",
"manageProfiles": "Управление", "manageProfiles": "Управлять профилями:",
"manageIdentities": "Управление",
"identityTooltip": "Ваш игровой ник и UUID, используемые в игре",
"configTooltip": "Конфигурация игры: моды, Java и настройки памяти",
"defaultProfile": "По умолчанию" "defaultProfile": "По умолчанию"
}, },
"install": { "install": {
@@ -130,18 +127,10 @@
"logsCopy": "Копировать", "logsCopy": "Копировать",
"logsRefresh": "Обновить", "logsRefresh": "Обновить",
"logsFolder": "Открыть папку", "logsFolder": "Открыть папку",
"logsSend": "Отправить логи",
"logsSending": "Отправка...",
"logsSent": "Отправлено!",
"logsSendFailed": "Ошибка",
"logsSubmissionId": "ID отправки",
"logsShareId": "Поделитесь этим ID с поддержкой при обращении",
"logsLoading": "Загрузка логов...", "logsLoading": "Загрузка логов...",
"closeLauncher": "Поведение лаунчера", "closeLauncher": "Поведение лаунчера",
"closeOnStart": "Закрыть лаунчер при старте игры", "closeOnStart": "Закрыть лаунчер при старте игры",
"closeOnStartDescription": "Автоматически закрыть лаунчер после запуска Hytale", "closeOnStartDescription": "Автоматически закрыть лаунчер после запуска Hytale",
"allowMultiInstance": "Несколько копий игры",
"allowMultiInstanceDescription": "Разрешить запуск нескольких клиентов одновременно (полезно для разработки модов)",
"hwAccel": "Аппаратное ускорение", "hwAccel": "Аппаратное ускорение",
"hwAccelDescription": "Включить аппаратное ускорение для лаунчера", "hwAccelDescription": "Включить аппаратное ускорение для лаунчера",
"gameBranch": "Ветка игры", "gameBranch": "Ветка игры",
@@ -156,12 +145,9 @@
}, },
"uuid": { "uuid": {
"modalTitle": "Управление UUID", "modalTitle": "Управление UUID",
"currentUserUUID": "UUID текущего пользователя",
"allPlayerUUIDs": "UUID всех игроков", "allPlayerUUIDs": "UUID всех игроков",
"addIdentity": "Добавить личность", "generateNew": "Сгенерировать новый UUID",
"usernamePlaceholder": "Имя пользователя",
"add": "Добавить",
"cancel": "Отмена",
"advanced": "Дополнительно",
"loadingUUIDs": "Загрузка UUID...", "loadingUUIDs": "Загрузка UUID...",
"setCustomUUID": "Установить кастомный UUID", "setCustomUUID": "Установить кастомный UUID",
"customPlaceholder": "Ввести кастомный UUID (форматы: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Ввести кастомный UUID (форматы: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -170,10 +156,10 @@
"copyTooltip": "Скопировать UUID", "copyTooltip": "Скопировать UUID",
"regenerateTooltip": "Сгенерировать новый UUID" "regenerateTooltip": "Сгенерировать новый UUID"
}, },
"configurations": { "profiles": {
"modalTitle": "Управление конфигурациями", "modalTitle": "Управление профилями",
"newProfilePlaceholder": "Название новой конфигурации", "newProfilePlaceholder": "Новое имя профиля",
"createProfile": "Создать конфигурацию" "createProfile": "Создать профиль"
}, },
"discord": { "discord": {
"notificationText": "Присоединитесь к нашему сообществу в Discord!", "notificationText": "Присоединитесь к нашему сообществу в Discord!",
@@ -227,8 +213,6 @@
"hwAccelSaved": "Настройка аппаратного ускорения сохранена!", "hwAccelSaved": "Настройка аппаратного ускорения сохранена!",
"hwAccelSaveFailed": "Не удалось сохранить настройку аппаратного ускорения", "hwAccelSaveFailed": "Не удалось сохранить настройку аппаратного ускорения",
"noUsername": "Имя пользователя не настроено. Пожалуйста, сначала сохраните имя пользователя.", "noUsername": "Имя пользователя не настроено. Пожалуйста, сначала сохраните имя пользователя.",
"identityAdded": "Личность успешно добавлена!",
"identityAddFailed": "Не удалось добавить личность",
"switchUsernameSuccess": "Успешно переключено на \"{username}\"!", "switchUsernameSuccess": "Успешно переключено на \"{username}\"!",
"switchUsernameFailed": "Не удалось переключить имя пользователя", "switchUsernameFailed": "Не удалось переключить имя пользователя",
"playerNameTooLong": "Имя игрока должно быть не более 16 символов" "playerNameTooLong": "Имя игрока должно быть не более 16 символов"
@@ -257,8 +241,8 @@
"installing": "Установка...", "installing": "Установка...",
"extracting": "Извлечение...", "extracting": "Извлечение...",
"verifying": "Проверка...", "verifying": "Проверка...",
"switchingProfile": "Смена конфигурации...", "switchingProfile": "Смена профиля...",
"profileSwitched": "Конфигурация изменена!", "profileSwitched": "Профиль сменён!",
"startingGame": "Запуск игры...", "startingGame": "Запуск игры...",
"launching": "ЗАПУСК...", "launching": "ЗАПУСК...",
"uninstallingGame": "Удаление игры...", "uninstallingGame": "Удаление игры...",

View File

@@ -8,10 +8,7 @@
}, },
"header": { "header": {
"playersLabel": "Spelare:", "playersLabel": "Spelare:",
"manageProfiles": "Hantera", "manageProfiles": "Hantera profiler",
"manageIdentities": "Hantera",
"identityTooltip": "Ditt spelarnamn och UUID som används i spelet",
"configTooltip": "Spelkonfiguration: moddar, Java- och minnesinställningar",
"defaultProfile": "Standard" "defaultProfile": "Standard"
}, },
"install": { "install": {
@@ -130,18 +127,10 @@
"logsCopy": "Kopiera", "logsCopy": "Kopiera",
"logsRefresh": "Uppdatera", "logsRefresh": "Uppdatera",
"logsFolder": "Öppna mapp", "logsFolder": "Öppna mapp",
"logsSend": "Skicka loggar",
"logsSending": "Skickar...",
"logsSent": "Skickat!",
"logsSendFailed": "Misslyckades",
"logsSubmissionId": "Inlämnings-ID",
"logsShareId": "Dela detta ID med support vid felanmälan",
"logsLoading": "Laddar loggar...", "logsLoading": "Laddar loggar...",
"closeLauncher": "Launcher-beteende", "closeLauncher": "Launcher-beteende",
"closeOnStart": "Stäng launcher vid spelstart", "closeOnStart": "Stäng launcher vid spelstart",
"closeOnStartDescription": "Stäng automatiskt launcher efter att Hytale har startats", "closeOnStartDescription": "Stäng automatiskt launcher efter att Hytale har startats",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Hårdvaruacceleration", "hwAccel": "Hårdvaruacceleration",
"hwAccelDescription": "Aktivera hårdvaruacceleration för launchern", "hwAccelDescription": "Aktivera hårdvaruacceleration för launchern",
"gameBranch": "Spelgren", "gameBranch": "Spelgren",
@@ -156,12 +145,9 @@
}, },
"uuid": { "uuid": {
"modalTitle": "UUID-hantering", "modalTitle": "UUID-hantering",
"currentUserUUID": "Nuvarande användar-UUID",
"allPlayerUUIDs": "Alla spelare-UUID:er", "allPlayerUUIDs": "Alla spelare-UUID:er",
"addIdentity": "Lägg till identitet", "generateNew": "Generera ny UUID",
"usernamePlaceholder": "Användarnamn",
"add": "Lägg till",
"cancel": "Avbryt",
"advanced": "Avancerat",
"loadingUUIDs": "Laddar UUID:er...", "loadingUUIDs": "Laddar UUID:er...",
"setCustomUUID": "Ange anpassad UUID", "setCustomUUID": "Ange anpassad UUID",
"customPlaceholder": "Ange anpassad UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Ange anpassad UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -170,10 +156,10 @@
"copyTooltip": "Kopiera UUID", "copyTooltip": "Kopiera UUID",
"regenerateTooltip": "Generera ny UUID" "regenerateTooltip": "Generera ny UUID"
}, },
"configurations": { "profiles": {
"modalTitle": "Hantera konfigurationer", "modalTitle": "Hantera profiler",
"newProfilePlaceholder": "Nytt konfigurationsnamn", "newProfilePlaceholder": "Nytt profilnamn",
"createProfile": "Skapa konfiguration" "createProfile": "Skapa profil"
}, },
"discord": { "discord": {
"notificationText": "Gå med i vår Discord-gemenskap!", "notificationText": "Gå med i vår Discord-gemenskap!",
@@ -227,8 +213,6 @@
"hwAccelSaved": "Hårdvaruaccelerationsinställning sparad", "hwAccelSaved": "Hårdvaruaccelerationsinställning sparad",
"hwAccelSaveFailed": "Misslyckades med att spara hårdvaruaccelerationsinställning", "hwAccelSaveFailed": "Misslyckades med att spara hårdvaruaccelerationsinställning",
"noUsername": "Inget användarnamn konfigurerat. Vänligen spara ditt användarnamn först.", "noUsername": "Inget användarnamn konfigurerat. Vänligen spara ditt användarnamn först.",
"identityAdded": "Identitet tillagd!",
"identityAddFailed": "Kunde inte lägga till identitet",
"switchUsernameSuccess": "Bytte till \"{username}\" framgångsrikt!", "switchUsernameSuccess": "Bytte till \"{username}\" framgångsrikt!",
"switchUsernameFailed": "Misslyckades med att byta användarnamn", "switchUsernameFailed": "Misslyckades med att byta användarnamn",
"playerNameTooLong": "Spelarnamnet måste vara 16 tecken eller mindre" "playerNameTooLong": "Spelarnamnet måste vara 16 tecken eller mindre"
@@ -257,8 +241,8 @@
"installing": "Installerar...", "installing": "Installerar...",
"extracting": "Extraherar...", "extracting": "Extraherar...",
"verifying": "Verifierar...", "verifying": "Verifierar...",
"switchingProfile": "Byter konfiguration...", "switchingProfile": "Byter profil...",
"profileSwitched": "Konfiguration bytt!", "profileSwitched": "Profil bytt!",
"startingGame": "Startar spel...", "startingGame": "Startar spel...",
"launching": "STARTAR...", "launching": "STARTAR...",
"uninstallingGame": "Avinstallerar spel...", "uninstallingGame": "Avinstallerar spel...",

View File

@@ -8,10 +8,7 @@
}, },
"header": { "header": {
"playersLabel": "Oyuncular:", "playersLabel": "Oyuncular:",
"manageProfiles": "Yönet", "manageProfiles": "Profilleri Yönet",
"manageIdentities": "Yönet",
"identityTooltip": "Oyun içinde kullanılan oyuncu adınız ve UUID'niz",
"configTooltip": "Oyun yapılandırması: modlar, Java ve bellek ayarları",
"defaultProfile": "Varsayılan" "defaultProfile": "Varsayılan"
}, },
"install": { "install": {
@@ -130,18 +127,10 @@
"logsCopy": "Kopyala", "logsCopy": "Kopyala",
"logsRefresh": "Yenile", "logsRefresh": "Yenile",
"logsFolder": "Klasörü Aç", "logsFolder": "Klasörü Aç",
"logsSend": "Logları Gönder",
"logsSending": "Gönderiliyor...",
"logsSent": "Gönderildi!",
"logsSendFailed": "Başarısız",
"logsSubmissionId": "Gönderim ID",
"logsShareId": "Sorun bildirirken bu ID'yi destekle paylaşın",
"logsLoading": "Loglar yükleniyor...", "logsLoading": "Loglar yükleniyor...",
"closeLauncher": "Başlatıcı Davranışı", "closeLauncher": "Başlatıcı Davranışı",
"closeOnStart": "Oyun başlatıldığında Başlatıcıyı Kapat", "closeOnStart": "Oyun başlatıldığında Başlatıcıyı Kapat",
"closeOnStartDescription": "Hytale başlatıldıktan sonra başlatıcıyı otomatik olarak kapatın", "closeOnStartDescription": "Hytale başlatıldıktan sonra başlatıcıyı otomatik olarak kapatın",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Donanım Hızlandırma", "hwAccel": "Donanım Hızlandırma",
"hwAccelDescription": "Başlatıcı için donanım hızlandırmasını etkinleştir", "hwAccelDescription": "Başlatıcı için donanım hızlandırmasını etkinleştir",
"gameBranch": "Oyun Dalı", "gameBranch": "Oyun Dalı",
@@ -156,12 +145,9 @@
}, },
"uuid": { "uuid": {
"modalTitle": "UUID Yönetimi", "modalTitle": "UUID Yönetimi",
"currentUserUUID": "Geçerli Kullanıcı UUID",
"allPlayerUUIDs": "Tüm Oyuncu UUID'leri", "allPlayerUUIDs": "Tüm Oyuncu UUID'leri",
"addIdentity": "Kimlik Ekle", "generateNew": "Yeni UUID Oluştur",
"usernamePlaceholder": "Kullanıcı Adı",
"add": "Ekle",
"cancel": "İptal",
"advanced": "Gelişmiş",
"loadingUUIDs": "UUID'ler yükleniyor...", "loadingUUIDs": "UUID'ler yükleniyor...",
"setCustomUUID": "Özel UUID Ayarla", "setCustomUUID": "Özel UUID Ayarla",
"customPlaceholder": "Özel UUID girin (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Özel UUID girin (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -170,10 +156,10 @@
"copyTooltip": "UUID'yi Kopyala", "copyTooltip": "UUID'yi Kopyala",
"regenerateTooltip": "Yeni UUID Oluştur" "regenerateTooltip": "Yeni UUID Oluştur"
}, },
"configurations": { "profiles": {
"modalTitle": "Yapılandırmaları Yönet", "modalTitle": "Profilleri Yönet",
"newProfilePlaceholder": "Yeni Yapılandırma Adı", "newProfilePlaceholder": "Yeni Profil Adı",
"createProfile": "Yapılandırma Oluştur" "createProfile": "Profil Oluştur"
}, },
"discord": { "discord": {
"notificationText": "Discord topluluğumuza katılın!", "notificationText": "Discord topluluğumuza katılın!",
@@ -227,8 +213,6 @@
"hwAccelSaved": "Donanım hızlandırma ayarı kaydedildi", "hwAccelSaved": "Donanım hızlandırma ayarı kaydedildi",
"hwAccelSaveFailed": "Donanım hızlandırma ayarı kaydedilemedi", "hwAccelSaveFailed": "Donanım hızlandırma ayarı kaydedilemedi",
"noUsername": "Kullanıcı adı yapılandırılmadı. Lütfen önce kullanıcı adınızı kaydedin.", "noUsername": "Kullanıcı adı yapılandırılmadı. Lütfen önce kullanıcı adınızı kaydedin.",
"identityAdded": "Kimlik başarıyla eklendi!",
"identityAddFailed": "Kimlik eklenemedi",
"switchUsernameSuccess": "\"{username}\" adına başarıyla geçildi!", "switchUsernameSuccess": "\"{username}\" adına başarıyla geçildi!",
"switchUsernameFailed": "Kullanıcı adı değiştirilemedi", "switchUsernameFailed": "Kullanıcı adı değiştirilemedi",
"playerNameTooLong": "Oyuncu adı 16 karakter veya daha az olmalıdır" "playerNameTooLong": "Oyuncu adı 16 karakter veya daha az olmalıdır"
@@ -257,8 +241,8 @@
"installing": "Kuruluyur...", "installing": "Kuruluyur...",
"extracting": "Ayıklanıyor...", "extracting": "Ayıklanıyor...",
"verifying": "Doğrulanıyor...", "verifying": "Doğrulanıyor...",
"switchingProfile": "Yapılandırma değiştiriliyor...", "switchingProfile": "Profil değiştiriliyor...",
"profileSwitched": "Yapılandırma değiştirildi!", "profileSwitched": "Profil değiştirildi!",
"startingGame": "Oyun başlatılıyor...", "startingGame": "Oyun başlatılıyor...",
"launching": "BAŞLATILIYOR...", "launching": "BAŞLATILIYOR...",
"uninstallingGame": "Oyun kaldırılıyor...", "uninstallingGame": "Oyun kaldırılıyor...",

View File

@@ -1,209 +0,0 @@
body.rtl {
direction: rtl;
font-family: 'Noto Sans Arabic', 'Space Grotesk', sans-serif;
}
body.rtl .sidebar {
right: 0;
left: auto;
border-right: none;
border-left: 1px solid rgba(255, 255, 255, 0.1);
}
body.rtl .nav-item.active::before {
right: -8px;
left: auto;
border-radius: 4px 0 0 4px;
}
body.rtl .nav-tooltip {
right: 100%;
left: auto;
margin-right: 0.5rem;
margin-left: 0;
}
body.rtl .nav-item:hover .nav-tooltip {
transform: translateX(-8px);
}
body.rtl .main-content {
margin-right: 80px;
margin-left: 0;
}
/* Header Layout*/
body.rtl .players-counter {
order: 2;
margin-left: 1.5rem;
margin-right: 0;
}
body.rtl .profile-selector {
order: -1;
}
body.rtl .window-controls {
order: 3;
flex-direction: row;
}
body.rtl .profile-dropdown {
right: auto;
left: 0;
}
body.rtl .form-group {
text-align: right;
}
body.rtl .radio-label,
body.rtl .checkbox-group {
flex-direction: row-reverse;
}
body.rtl .form-input {
border-radius: 0 8px 8px 0;
}
body.rtl .mods-pagination {
flex-direction: row-reverse;
}
body.rtl .pagination-btn:first-child i {
transform: rotate(180deg);
}
body.rtl .pagination-btn:last-child i {
transform: rotate(180deg);
}
/* UUID Display */
body.rtl .uuid-display-container {
flex-direction: row-reverse;
}
body.rtl .uuid-btn {
border-radius: 0 8px 8px 0;
}
body.rtl .uuid-input {
border-radius: 8px 0 0 8px;
}
body.rtl .segmented-control {
flex-direction: row-reverse;
}
/* Mod Grid Layout */
body.rtl .mods-search {
text-align: right;
}
body.rtl .mods-search-container {
flex-direction: row-reverse;
}
body.rtl .mods-actions {
order: -1;
}
body.rtl .mod-card {
direction: rtl;
}
body.rtl .installed-mod-card {
direction: rtl;
}
body.rtl .installed-mod-card .mod-info {
text-align: right;
}
body.rtl .mods-header {
flex-direction: row-reverse;
}
body.rtl .news-section .news-header {
flex-direction: row-reverse;
}
/* Settings Layout */
body.rtl .settings-option {
text-align: right;
}
body.rtl .settings-input-group {
text-align: right;
}
body.rtl .settings-input {
border-radius: 0 8px 8px 0;
}
body.rtl .settings-section-title i {
margin-right: 0;
margin-left: 0.5rem;
}
body.rtl .settings-hint i {
margin-right: 0;
margin-left: 0.5rem;
}
body.rtl .custom-options,
body.rtl .custom-java-options {
text-align: right;
}
body.rtl .checkbox-content {
margin-right: 2rem;
margin-left: 0;
}
body.rtl .btn-content {
text-align: right;
}
/* Icons & Transformations */
body.rtl .news-title i {
padding-left: 0.5rem;
}
body.rtl .uuid-modal-title i {
padding-left: 0.5rem;
}
body.rtl .mods-modal-title i {
padding-left: 0.5rem;
}
body.rtl .view-all-btn i,
body.rtl .sidebar-nav div i,
body.rtl .logs-header i,
body.rtl .home-play-button i {
transform: scaleX(-1);
}
body.rtl .play-title i {
transform: scaleX(-1);
padding-right: 0.5rem;
}
body.rtl .logs-terminal {
direction: ltr;
text-align: left;
}
body.rtl .version-display-bottom {
right: auto;
left: 1rem;
}

View File

@@ -873,22 +873,6 @@ body {
border-color: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.2);
} }
.logs-action-btn.logs-send-btn {
background: rgba(0, 212, 255, 0.15);
border-color: rgba(0, 212, 255, 0.3);
color: #00d4ff;
}
.logs-action-btn.logs-send-btn:hover {
background: rgba(0, 212, 255, 0.25);
border-color: rgba(0, 212, 255, 0.5);
}
.logs-action-btn.logs-send-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.logs-terminal { .logs-terminal {
flex: 1; flex: 1;
background: #0d1117; background: #0d1117;
@@ -4829,140 +4813,6 @@ select.settings-input option {
transform: translateY(0); transform: translateY(0);
} }
.wrapper-items-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.wrapper-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
transition: all 0.3s ease;
}
.wrapper-item:hover {
border-color: rgba(147, 51, 234, 0.3);
background: rgba(147, 51, 234, 0.05);
}
.wrapper-item-text {
font-family: 'JetBrains Mono', 'Courier New', monospace;
font-size: 0.82rem;
color: #e5e7eb;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.wrapper-item-condition select {
background: rgba(0, 0, 0, 0.4);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px;
color: #e5e7eb;
font-size: 0.75rem;
padding: 4px 8px;
cursor: pointer;
margin: 0 8px;
}
.wrapper-item-condition select:focus {
outline: none;
border-color: rgba(147, 51, 234, 0.5);
}
.wrapper-item-delete {
padding: 4px 8px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
color: rgba(255, 255, 255, 0.5);
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.8rem;
display: flex;
align-items: center;
justify-content: center;
min-width: 28px;
height: 28px;
}
.wrapper-item-delete:hover {
background: rgba(239, 68, 68, 0.2);
border-color: rgba(239, 68, 68, 0.4);
color: #ef4444;
}
.wrapper-items-empty {
font-style: italic;
text-align: center;
color: rgba(255, 255, 255, 0.4);
padding: 12px;
font-size: 0.82rem;
}
.wrapper-condition-select {
background: rgba(0, 0, 0, 0.4);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px;
color: #e5e7eb;
font-size: 0.82rem;
padding: 6px 10px;
cursor: pointer;
}
.wrapper-condition-select:focus {
outline: none;
border-color: rgba(147, 51, 234, 0.5);
}
.wrapper-preview-toggle {
background: none;
border: none;
color: rgba(255, 255, 255, 0.6);
cursor: pointer;
font-size: 0.82rem;
padding: 4px 0;
display: flex;
align-items: center;
gap: 6px;
transition: color 0.2s;
}
.wrapper-preview-toggle:hover {
color: rgba(255, 255, 255, 0.9);
}
.wrapper-preview-toggle i {
transition: transform 0.2s;
font-size: 0.7rem;
}
.wrapper-preview-toggle i.expanded {
transform: rotate(90deg);
}
.wrapper-preview-content {
background: rgba(0, 0, 0, 0.4);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
color: rgba(255, 255, 255, 0.7);
font-family: 'JetBrains Mono', 'Courier New', monospace;
font-size: 0.72rem;
line-height: 1.5;
padding: 12px;
max-height: 300px;
overflow: auto;
white-space: pre;
tab-size: 4;
}
.settings-hint { .settings-hint {
margin-top: 0.5rem; margin-top: 0.5rem;
font-size: 0.8rem; font-size: 0.8rem;
@@ -5575,8 +5425,9 @@ input[type="text"].uuid-input,
font-family: 'Space Grotesk', sans-serif; font-family: 'Space Grotesk', sans-serif;
} }
.uuid-current-section,
.uuid-list-section, .uuid-list-section,
.uuid-advanced-section { .uuid-custom-section {
background: rgba(255, 255, 255, 0.03); background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px; border-radius: 12px;
@@ -5823,96 +5674,6 @@ input[type="text"].uuid-input,
color: #3b82f6; color: #3b82f6;
} }
.uuid-item-btn.regenerate:hover {
background: rgba(249, 115, 22, 0.2);
border-color: rgba(249, 115, 22, 0.4);
color: #f97316;
}
/* Add Identity Form */
.uuid-add-form {
background: rgba(147, 51, 234, 0.05);
border: 1px dashed rgba(147, 51, 234, 0.3);
border-radius: 8px;
padding: 1rem;
margin-bottom: 0.75rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.uuid-add-form-row {
display: flex;
align-items: stretch;
gap: 0.5rem;
}
.uuid-add-form-row .uuid-input {
flex: 1;
padding: 0.75rem 1rem;
border-radius: 8px;
}
.uuid-add-form-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
}
.uuid-cancel-btn {
padding: 0.75rem 1.25rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
color: rgba(255, 255, 255, 0.7);
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 0.5rem;
}
.uuid-cancel-btn:hover {
background: rgba(239, 68, 68, 0.15);
border-color: rgba(239, 68, 68, 0.3);
color: #ef4444;
}
/* Advanced Section */
.uuid-advanced-toggle {
display: flex;
align-items: center;
gap: 0.5rem;
background: none;
border: none;
color: rgba(255, 255, 255, 0.6);
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
padding: 0.5rem 0;
transition: color 0.3s ease;
font-family: 'Space Grotesk', sans-serif;
}
.uuid-advanced-toggle:hover {
color: rgba(255, 255, 255, 0.9);
}
.uuid-advanced-chevron {
font-size: 0.75rem;
transition: transform 0.3s ease;
}
.uuid-advanced-chevron.open {
transform: rotate(90deg);
}
.uuid-advanced-content {
margin-top: 1rem;
}
@media (max-width: 600px) { @media (max-width: 600px) {
.uuid-modal-content { .uuid-modal-content {
width: 95vw; width: 95vw;
@@ -5924,8 +5685,8 @@ input[type="text"].uuid-input,
gap: 1.5rem; gap: 1.5rem;
} }
.uuid-custom-form, .uuid-current-display,
.uuid-add-form-row { .uuid-custom-form {
flex-direction: column; flex-direction: column;
} }
@@ -6302,155 +6063,6 @@ input[type="text"].uuid-input,
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5); box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
} }
/* Header Tooltip Styles */
.header-tooltip {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%) translateY(4px);
font-size: 0.65rem;
font-weight: 500;
letter-spacing: 0.03em;
color: #9ca3af;
opacity: 0;
transition: opacity 0.2s ease;
pointer-events: none;
white-space: nowrap;
background: rgba(0, 0, 0, 0.85);
padding: 0.3rem 0.6rem;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.08);
z-index: 1;
}
.identity-selector:hover .header-tooltip,
.profile-selector:hover .header-tooltip {
opacity: 1;
}
.identity-dropdown.show ~ .header-tooltip,
.profile-dropdown.show ~ .header-tooltip {
opacity: 0 !important;
}
/* Identity Selector Styles */
.identity-selector {
position: relative;
pointer-events: auto;
margin-left: 1rem;
z-index: 9999 !important;
}
.identity-btn {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 1rem;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
color: white;
cursor: pointer;
font-family: 'Space Grotesk', sans-serif;
font-weight: 600;
transition: all 0.2s ease;
z-index: 100000 !important;
pointer-events: auto !important;
}
.identity-btn:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(34, 197, 94, 0.4);
}
.identity-btn i {
color: #22c55e;
}
.identity-dropdown {
position: absolute;
top: 100%;
left: 0;
margin-top: 0.5rem;
width: 200px;
background: rgba(20, 20, 20, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 0.5rem;
display: none;
flex-direction: column;
z-index: 2000;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
animation: fadeIn 0.1s ease;
}
.identity-dropdown.show {
display: flex;
}
.identity-list {
max-height: 200px;
overflow-y: auto;
}
.identity-item {
padding: 0.6rem 0.8rem;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
color: #ccc;
transition: all 0.2s;
}
.identity-item:hover {
background: rgba(255, 255, 255, 0.1);
color: white;
}
.identity-item.active {
background: rgba(34, 197, 94, 0.2);
color: white;
font-weight: bold;
}
.identity-item.active::before {
content: '\2022';
color: #22c55e;
font-size: 1.2rem;
}
.identity-divider {
height: 1px;
background: rgba(255, 255, 255, 0.1);
margin: 0.5rem 0;
}
.identity-action {
padding: 0.6rem 0.8rem;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
color: #22c55e;
font-weight: 600;
}
.identity-action:hover {
background: rgba(34, 197, 94, 0.1);
}
.identity-empty {
padding: 0.6rem 0.8rem;
color: #666;
font-size: 0.85rem;
text-align: center;
}
/* Profile Selector Styles */ /* Profile Selector Styles */
.profile-selector { .profile-selector {
position: relative; position: relative;
@@ -6848,85 +6460,3 @@ input[type="text"].uuid-input,
opacity: 0.5; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;
} }
/* Version Selection Styles */
.version-list-container::-webkit-scrollbar {
width: 6px;
}
.version-list-container::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
}
.version-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
margin-bottom: 0.5rem;
transition: all 0.2s ease;
}
.version-item:hover {
background: rgba(255, 255, 255, 0.1);
transform: translateX(4px);
}
.version-info {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.version-name {
font-weight: 600;
color: #fff;
font-size: 0.95rem;
}
.version-meta {
font-size: 0.8rem;
color: #a0a0a0;
display: flex;
gap: 1rem;
align-items: center;
}
.version-meta span {
display: flex;
align-items: center;
gap: 0.35rem;
}
.version-meta i {
font-size: 0.8em;
opacity: 0.7;
}
.version-actions .btn-install {
padding: 0.5rem 1.25rem;
background: linear-gradient(135deg, #3b82f6, #2563eb);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
font-size: 0.85rem;
transition: all 0.2s;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
}
.version-actions .btn-install:hover {
transform: translateY(-1px);
box-shadow: 0 6px 8px rgba(0, 0, 0, 0.3);
background: linear-gradient(135deg, #4f93f6, #3b82f6);
}
.loading-versions {
padding: 3rem;
text-align: center;
}

View File

@@ -18,8 +18,7 @@
### ⚠️ **WARNING: READ [QUICK START](#-quick-start) before Downloading & Installing the Launcher!** ⚠️ ### ⚠️ **WARNING: READ [QUICK START](#-quick-start) before Downloading & Installing the Launcher!** ⚠️
#### 🛑 **Found a problem? [TG Group](https://t.me/sanhostnet) | [TG Channel](https://t.me/hf2p_og) | [Community Chat](https://chat.sanhost.net/invite/Tfz4jCK4)** 🛑 #### 🛑 **Found a problem? [Join the HF2P Discord](https://discord.gg/hf2pdc) and head to `#-⚠️-community-help`** 🛑
<!-- #### 🛑 **Found a problem? [Join the HF2P Discord](https://discord.gg/Fhbb9Yk5WW) and head to `#-⚠️-community-help`** 🛑 -->
<p> <p>
👍 If you like the project, <b>feel free to support us via Buy Me a Coffee!</b> ☕<br> 👍 If you like the project, <b>feel free to support us via Buy Me a Coffee!</b> ☕<br>
@@ -456,8 +455,7 @@ See [BUILD.md](docs/BUILD.md) for comprehensive build instructions.
<div align="center"> <div align="center">
**Questions? Ads? Collaboration? Endorsement? Other business-related?** **Questions? Ads? Collaboration? Endorsement? Other business-related?**
Message the founders at [TG Group](https://t.me/sanhostnet) | [TG Channel](https://t.me/hf2p_og) | [Community Chat](https://chat.sanhost.net/invite/Tfz4jCK4) Message the founders at https://discord.gg/hf2pdc
<!-- Message the founders at https://discord.gg/Fhbb9Yk5WW -->
</div> </div>

View File

@@ -2,8 +2,7 @@
Play with friends online! This guide covers both easy in-game hosting and advanced dedicated server setup. Play with friends online! This guide covers both easy in-game hosting and advanced dedicated server setup.
### **DOWNLOAD SERVER FILES (JAR/RAR/SCRIPTS) HERE: https://chat.sanhost.net/invite/Tfz4jCK4** ### **DOWNLOAD SERVER FILES (JAR/RAR/SCRIPTS) HERE: https://discord.gg/hf2pdc**
<!-- ### **DOWNLOAD SERVER FILES (JAR/RAR/SCRIPTS) HERE: https://discord.gg/Fhbb9Yk5WW** -->
**Table of Contents** **Table of Contents**

View File

@@ -1,7 +1,6 @@
# Hytale F2P Launcher - Troubleshooting Guide # Hytale F2P Launcher - Troubleshooting Guide
This guide covers common issues and their solutions. If your issue isn't listed here, please check [existing issues](https://github.com/amiayweb/Hytale-F2P/issues) or join our [TG Group](https://t.me/sanhostnet) | [TG Channel](https://t.me/hf2p_og) | [Community Chat](https://chat.sanhost.net/invite/Tfz4jCK4). This guide covers common issues and their solutions. If your issue isn't listed here, please check [existing issues](https://github.com/amiayweb/Hytale-F2P/issues) or join our [Discord](https://discord.gg/gME8rUy3MB).
<!-- Discord: https://discord.gg/Fhbb9Yk5WW -->
--- ---
@@ -438,8 +437,7 @@ Game sessions have a 10-hour TTL. This is by design for security.
If your issue isn't resolved by this guide: If your issue isn't resolved by this guide:
1. **Check existing issues:** [GitHub Issues](https://github.com/amiayweb/Hytale-F2P/issues) 1. **Check existing issues:** [GitHub Issues](https://github.com/amiayweb/Hytale-F2P/issues)
2. **Join Community:** [TG Group](https://t.me/sanhostnet) | [TG Channel](https://t.me/hf2p_og) | [Chat](https://chat.sanhost.net/invite/Tfz4jCK4) 2. **Join Discord:** [discord.gg/gME8rUy3MB](https://discord.gg/gME8rUy3MB)
<!-- Discord: https://discord.gg/Fhbb9Yk5WW -->
3. **Open a new issue** with: 3. **Open a new issue** with:
- Your operating system and version - Your operating system and version
- Launcher version - Launcher version

View File

@@ -4,10 +4,6 @@ const logger = require('./logger');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const os = require('os'); const os = require('os');
const https = require('https');
const FORGEJO_API = 'https://git.sanhost.net/api/v1';
const FORGEJO_REPO = 'sanasol/hytale-f2p';
class AppUpdater { class AppUpdater {
constructor(mainWindow) { constructor(mainWindow) {
@@ -18,34 +14,6 @@ class AppUpdater {
this.setupAutoUpdater(); this.setupAutoUpdater();
} }
/**
* Fetch the latest non-draft release tag from Forgejo and set the feed URL
*/
async _resolveUpdateUrl() {
return new Promise((resolve, reject) => {
https.get(`${FORGEJO_API}/repos/${FORGEJO_REPO}/releases?limit=5`, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => {
try {
const releases = JSON.parse(data);
const latest = releases.find(r => !r.draft && !r.prerelease);
if (latest) {
const url = `https://git.sanhost.net/${FORGEJO_REPO}/releases/download/${latest.tag_name}`;
console.log(`Auto-update URL resolved to: ${url}`);
autoUpdater.setFeedURL({ provider: 'generic', url });
resolve(url);
} else {
reject(new Error('No published release found'));
}
} catch (e) {
reject(e);
}
});
}).on('error', reject);
});
}
setupAutoUpdater() { setupAutoUpdater() {
// Configure logger for electron-updater // Configure logger for electron-updater
@@ -248,10 +216,8 @@ class AppUpdater {
} }
checkForUpdatesAndNotify() { checkForUpdatesAndNotify() {
// Resolve latest release URL then check for updates // Check for updates and notify if available
this._resolveUpdateUrl().catch(err => { autoUpdater.checkForUpdatesAndNotify().catch(err => {
console.warn('Failed to resolve update URL:', err.message);
}).then(() => autoUpdater.checkForUpdatesAndNotify()).catch(err => {
console.error('Failed to check for updates:', err); console.error('Failed to check for updates:', err);
// Network errors are not critical - just log and continue // Network errors are not critical - just log and continue
@@ -279,10 +245,8 @@ class AppUpdater {
} }
checkForUpdates() { checkForUpdates() {
// Manual check - resolve latest release URL first // Manual check for updates (returns promise)
return this._resolveUpdateUrl().catch(err => { return autoUpdater.checkForUpdates().catch(err => {
console.warn('Failed to resolve update URL:', err.message);
}).then(() => autoUpdater.checkForUpdates()).catch(err => {
console.error('Failed to check for updates:', err); console.error('Failed to check for updates:', err);
// Network errors are not critical - just return no update available // Network errors are not critical - just return no update available

View File

@@ -54,7 +54,6 @@ function getAppDir() {
const CONFIG_FILE = path.join(getAppDir(), 'config.json'); const CONFIG_FILE = path.join(getAppDir(), 'config.json');
const CONFIG_BACKUP = path.join(getAppDir(), 'config.json.bak'); const CONFIG_BACKUP = path.join(getAppDir(), 'config.json.bak');
const CONFIG_TEMP = path.join(getAppDir(), 'config.json.tmp'); const CONFIG_TEMP = path.join(getAppDir(), 'config.json.tmp');
const UUID_STORE_FILE = path.join(getAppDir(), 'uuid-store.json');
// ============================================================================= // =============================================================================
// CONFIG VALIDATION // CONFIG VALIDATION
@@ -153,22 +152,6 @@ function saveConfig(update) {
// Load current config // Load current config
const currentConfig = loadConfig(); const currentConfig = loadConfig();
// SAFETY: If config file exists on disk but loadConfig() returned empty,
// something is wrong (file locked, corrupted, etc.). Refuse to save
// because merging with {} would wipe all existing data (userUuids, username, etc.)
if (Object.keys(currentConfig).length === 0 && fs.existsSync(CONFIG_FILE)) {
const fileSize = fs.statSync(CONFIG_FILE).size;
if (fileSize > 2) { // More than just "{}"
console.error(`[Config] REFUSING to save — loaded empty but file exists (${fileSize} bytes). Retrying load...`);
// Wait and retry the load
const delay = attempt * 200;
const start = Date.now();
while (Date.now() - start < delay) { /* busy wait */ }
continue;
}
}
const newConfig = { ...currentConfig, ...update }; const newConfig = { ...currentConfig, ...update };
const data = JSON.stringify(newConfig, null, 2); const data = JSON.stringify(newConfig, null, 2);
@@ -255,18 +238,11 @@ function saveUsername(username) {
// Check if we're actually changing the username (case-insensitive comparison) // Check if we're actually changing the username (case-insensitive comparison)
const isRename = currentName && currentName.toLowerCase() !== newName.toLowerCase(); const isRename = currentName && currentName.toLowerCase() !== newName.toLowerCase();
// Also update UUID store (source of truth)
migrateUuidStoreIfNeeded();
const uuidStore = loadUuidStore();
if (isRename) { if (isRename) {
// Find the UUID for the current username // Find the UUID for the current username
const currentKey = Object.keys(userUuids).find( const currentKey = Object.keys(userUuids).find(
k => k.toLowerCase() === currentName.toLowerCase() k => k.toLowerCase() === currentName.toLowerCase()
); );
const currentStoreKey = Object.keys(uuidStore).find(
k => k.toLowerCase() === currentName.toLowerCase()
);
if (currentKey && userUuids[currentKey]) { if (currentKey && userUuids[currentKey]) {
// Check if target username already exists (would be a different identity) // Check if target username already exists (would be a different identity)
@@ -282,9 +258,6 @@ function saveUsername(username) {
const uuid = userUuids[currentKey]; const uuid = userUuids[currentKey];
delete userUuids[currentKey]; delete userUuids[currentKey];
userUuids[newName] = uuid; userUuids[newName] = uuid;
// Same in UUID store
if (currentStoreKey) delete uuidStore[currentStoreKey];
uuidStore[newName] = uuid;
console.log(`[Config] Renamed identity: "${currentKey}" → "${newName}" (UUID preserved: ${uuid})`); console.log(`[Config] Renamed identity: "${currentKey}" → "${newName}" (UUID preserved: ${uuid})`);
} }
} }
@@ -297,20 +270,11 @@ function saveUsername(username) {
const uuid = userUuids[currentKey]; const uuid = userUuids[currentKey];
delete userUuids[currentKey]; delete userUuids[currentKey];
userUuids[newName] = uuid; userUuids[newName] = uuid;
// Same in UUID store
const storeKey = Object.keys(uuidStore).find(k => k.toLowerCase() === currentName.toLowerCase());
if (storeKey) {
delete uuidStore[storeKey];
uuidStore[newName] = uuid;
}
console.log(`[Config] Updated username case: "${currentKey}" → "${newName}"`); console.log(`[Config] Updated username case: "${currentKey}" → "${newName}"`);
} }
} }
// Save UUID store // Save both username and updated userUuids
saveUuidStore(uuidStore);
// Save both username and updated userUuids to config
saveConfig({ username: newName, userUuids }); saveConfig({ username: newName, userUuids });
console.log(`[Config] Username saved: "${newName}"`); console.log(`[Config] Username saved: "${newName}"`);
return newName; return newName;
@@ -346,7 +310,6 @@ function hasUsername() {
// ============================================================================= // =============================================================================
// UUID MANAGEMENT - Persistent and safe // UUID MANAGEMENT - Persistent and safe
// Uses separate uuid-store.json as source of truth (survives config.json corruption)
// ============================================================================= // =============================================================================
/** /**
@@ -357,55 +320,10 @@ function normalizeUsername(username) {
return username.trim().toLowerCase(); return username.trim().toLowerCase();
} }
/**
* Load UUID store from separate file (independent of config.json)
*/
function loadUuidStore() {
try {
if (fs.existsSync(UUID_STORE_FILE)) {
const data = fs.readFileSync(UUID_STORE_FILE, 'utf8');
if (data.trim()) {
return JSON.parse(data);
}
}
} catch (err) {
console.error('[UUID Store] Failed to load:', err.message);
}
return {};
}
/**
* Save UUID store to separate file (atomic write)
*/
function saveUuidStore(store) {
try {
const dir = path.dirname(UUID_STORE_FILE);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
const tmpFile = UUID_STORE_FILE + '.tmp';
fs.writeFileSync(tmpFile, JSON.stringify(store, null, 2), 'utf8');
fs.renameSync(tmpFile, UUID_STORE_FILE);
} catch (err) {
console.error('[UUID Store] Failed to save:', err.message);
}
}
/**
* One-time migration: copy userUuids from config.json to uuid-store.json
*/
function migrateUuidStoreIfNeeded() {
if (fs.existsSync(UUID_STORE_FILE)) return; // Already migrated
const config = loadConfig();
if (config.userUuids && Object.keys(config.userUuids).length > 0) {
console.log('[UUID Store] Migrating', Object.keys(config.userUuids).length, 'UUIDs from config.json');
saveUuidStore(config.userUuids);
}
}
/** /**
* Get UUID for a username * Get UUID for a username
* Source of truth: uuid-store.json (separate from config.json) * Creates new UUID only if user explicitly doesn't exist
* Also writes to config.json for backward compatibility * Uses case-insensitive lookup to prevent duplicates, but preserves original case for display
* Creates new UUID only if user doesn't exist in EITHER store
*/ */
function getUuidForUser(username) { function getUuidForUser(username) {
const { v4: uuidv4 } = require('uuid'); const { v4: uuidv4 } = require('uuid');
@@ -417,69 +335,32 @@ function getUuidForUser(username) {
const displayName = username.trim(); const displayName = username.trim();
const normalizedLookup = displayName.toLowerCase(); const normalizedLookup = displayName.toLowerCase();
// Ensure UUID store exists (one-time migration from config.json)
migrateUuidStoreIfNeeded();
// 1. Check UUID store first (source of truth)
const uuidStore = loadUuidStore();
const storeKey = Object.keys(uuidStore).find(k => k.toLowerCase() === normalizedLookup);
if (storeKey) {
const existingUuid = uuidStore[storeKey];
// Update case if needed
if (storeKey !== displayName) {
console.log(`[UUID Store] Updating username case: "${storeKey}" → "${displayName}"`);
delete uuidStore[storeKey];
uuidStore[displayName] = existingUuid;
saveUuidStore(uuidStore);
}
// Sync to config.json (backward compat, non-critical)
try {
const config = loadConfig(); const config = loadConfig();
const configUuids = config.userUuids || {}; const userUuids = config.userUuids || {};
const configKey = Object.keys(configUuids).find(k => k.toLowerCase() === normalizedLookup);
if (!configKey || configUuids[configKey] !== existingUuid) { // Case-insensitive lookup - find existing key regardless of case
if (configKey) delete configUuids[configKey]; const existingKey = Object.keys(userUuids).find(k => k.toLowerCase() === normalizedLookup);
configUuids[displayName] = existingUuid;
saveConfig({ userUuids: configUuids }); if (existingKey) {
} // Found existing - return UUID, update display name if case changed
} catch (e) { const existingUuid = userUuids[existingKey];
// Non-critical — UUID store is the source of truth
// If user typed different case, update the key to new case (preserving UUID)
if (existingKey !== displayName) {
console.log(`[Config] Updating username case: "${existingKey}" → "${displayName}"`);
delete userUuids[existingKey];
userUuids[displayName] = existingUuid;
saveConfig({ userUuids });
} }
console.log(`[UUID] ${displayName}${existingUuid} (from uuid-store)`);
return existingUuid; return existingUuid;
} }
// 2. Fallback: check config.json (recovery if uuid-store.json was lost) // Create new UUID for new user - store with original case
const config = loadConfig();
const userUuids = config.userUuids || {};
const configKey = Object.keys(userUuids).find(k => k.toLowerCase() === normalizedLookup);
if (configKey) {
const recoveredUuid = userUuids[configKey];
console.warn(`[UUID] RECOVERED "${displayName}" → ${recoveredUuid} from config.json (uuid-store was missing)`);
// Save to UUID store
uuidStore[displayName] = recoveredUuid;
saveUuidStore(uuidStore);
return recoveredUuid;
}
// 3. New user — generate UUID, save to BOTH stores
const newUuid = uuidv4(); const newUuid = uuidv4();
console.log(`[UUID] NEW user "${displayName}" → ${newUuid}`);
// Save to UUID store (source of truth)
uuidStore[displayName] = newUuid;
saveUuidStore(uuidStore);
// Save to config.json (backward compat)
userUuids[displayName] = newUuid; userUuids[displayName] = newUuid;
saveConfig({ userUuids }); saveConfig({ userUuids });
console.log(`[Config] Created new UUID for "${displayName}": ${newUuid}`);
return newUuid; return newUuid;
} }
@@ -499,26 +380,22 @@ function getCurrentUuid() {
* Get all UUID mappings (raw object) * Get all UUID mappings (raw object)
*/ */
function getAllUuidMappings() { function getAllUuidMappings() {
migrateUuidStoreIfNeeded();
const uuidStore = loadUuidStore();
// Fallback to config if uuid-store is empty
if (Object.keys(uuidStore).length === 0) {
const config = loadConfig(); const config = loadConfig();
return config.userUuids || {}; return config.userUuids || {};
} }
return uuidStore;
}
/** /**
* Get all UUID mappings as array with current user flag * Get all UUID mappings as array with current user flag
*/ */
function getAllUuidMappingsArray() { function getAllUuidMappingsArray() {
const allMappings = getAllUuidMappings(); const config = loadConfig();
const userUuids = config.userUuids || {};
const currentUsername = loadUsername(); const currentUsername = loadUsername();
// Case-insensitive comparison for isCurrent
const normalizedCurrent = currentUsername ? currentUsername.toLowerCase() : null; const normalizedCurrent = currentUsername ? currentUsername.toLowerCase() : null;
return Object.entries(allMappings).map(([username, uuid]) => ({ return Object.entries(userUuids).map(([username, uuid]) => ({
username, username, // Original case preserved
uuid, uuid,
isCurrent: username.toLowerCase() === normalizedCurrent isCurrent: username.toLowerCase() === normalizedCurrent
})); }));
@@ -542,20 +419,16 @@ function setUuidForUser(username, uuid) {
const displayName = username.trim(); const displayName = username.trim();
const normalizedLookup = displayName.toLowerCase(); const normalizedLookup = displayName.toLowerCase();
// 1. Update UUID store (source of truth)
migrateUuidStoreIfNeeded();
const uuidStore = loadUuidStore();
const storeKey = Object.keys(uuidStore).find(k => k.toLowerCase() === normalizedLookup);
if (storeKey) delete uuidStore[storeKey];
uuidStore[displayName] = uuid;
saveUuidStore(uuidStore);
// 2. Update config.json (backward compat)
const config = loadConfig(); const config = loadConfig();
const userUuids = config.userUuids || {}; const userUuids = config.userUuids || {};
// Remove any existing entry with same name (case-insensitive)
const existingKey = Object.keys(userUuids).find(k => k.toLowerCase() === normalizedLookup); const existingKey = Object.keys(userUuids).find(k => k.toLowerCase() === normalizedLookup);
if (existingKey) delete userUuids[existingKey]; if (existingKey) {
delete userUuids[existingKey];
}
// Store with original case
userUuids[displayName] = uuid; userUuids[displayName] = uuid;
saveConfig({ userUuids }); saveConfig({ userUuids });
@@ -581,30 +454,20 @@ function deleteUuidForUser(username) {
} }
const normalizedLookup = username.trim().toLowerCase(); const normalizedLookup = username.trim().toLowerCase();
let deleted = false;
// 1. Delete from UUID store (source of truth)
migrateUuidStoreIfNeeded();
const uuidStore = loadUuidStore();
const storeKey = Object.keys(uuidStore).find(k => k.toLowerCase() === normalizedLookup);
if (storeKey) {
delete uuidStore[storeKey];
saveUuidStore(uuidStore);
deleted = true;
}
// 2. Delete from config.json (backward compat)
const config = loadConfig(); const config = loadConfig();
const userUuids = config.userUuids || {}; const userUuids = config.userUuids || {};
// Case-insensitive lookup
const existingKey = Object.keys(userUuids).find(k => k.toLowerCase() === normalizedLookup); const existingKey = Object.keys(userUuids).find(k => k.toLowerCase() === normalizedLookup);
if (existingKey) { if (existingKey) {
delete userUuids[existingKey]; delete userUuids[existingKey];
saveConfig({ userUuids }); saveConfig({ userUuids });
deleted = true; console.log(`[Config] UUID deleted for "${username}"`);
return true;
} }
if (deleted) console.log(`[Config] UUID deleted for "${username}"`); return false;
return deleted;
} }
/** /**
@@ -708,15 +571,6 @@ function loadLauncherHardwareAcceleration() {
return config.launcherHardwareAcceleration !== undefined ? config.launcherHardwareAcceleration : true; return config.launcherHardwareAcceleration !== undefined ? config.launcherHardwareAcceleration : true;
} }
function saveAllowMultiInstance(enabled) {
saveConfig({ allowMultiInstance: !!enabled });
}
function loadAllowMultiInstance() {
const config = loadConfig();
return config.allowMultiInstance !== undefined ? config.allowMultiInstance : false;
}
// ============================================================================= // =============================================================================
// MODS MANAGEMENT // MODS MANAGEMENT
// ============================================================================= // =============================================================================
@@ -867,212 +721,6 @@ function checkLaunchReady() {
}; };
} }
// =============================================================================
// JAVA WRAPPER CONFIGURATION (Structured)
// =============================================================================
const DEFAULT_WRAPPER_CONFIG = {
stripFlags: [],
injectArgs: [
{ arg: '--disable-sentry', condition: 'server' }
]
};
function getDefaultWrapperConfig() {
return JSON.parse(JSON.stringify(DEFAULT_WRAPPER_CONFIG));
}
function loadWrapperConfig() {
const config = loadConfig();
if (config.javaWrapperConfig && typeof config.javaWrapperConfig === 'object') {
const wc = config.javaWrapperConfig;
if (Array.isArray(wc.stripFlags) && Array.isArray(wc.injectArgs)) {
const loaded = JSON.parse(JSON.stringify(wc));
// Normalize entries: ensure every injectArg has a valid condition
for (const entry of loaded.injectArgs) {
if (!['server', 'always'].includes(entry.condition)) {
entry.condition = 'always';
}
}
return loaded;
}
}
return getDefaultWrapperConfig();
}
function saveWrapperConfig(wrapperConfig) {
if (!wrapperConfig || typeof wrapperConfig !== 'object') {
throw new Error('Invalid wrapper config');
}
if (!Array.isArray(wrapperConfig.stripFlags) || !Array.isArray(wrapperConfig.injectArgs)) {
throw new Error('Invalid wrapper config structure');
}
// Validate injectArgs entries
for (const entry of wrapperConfig.injectArgs) {
if (!entry.arg || typeof entry.arg !== 'string') {
throw new Error('Each inject arg must have a string "arg" property');
}
if (!['server', 'always'].includes(entry.condition)) {
throw new Error('Inject arg condition must be "server" or "always"');
}
}
saveConfig({ javaWrapperConfig: wrapperConfig });
console.log('[Config] Wrapper config saved');
}
function resetWrapperConfig() {
const config = loadConfig();
delete config.javaWrapperConfig;
delete config.javaWrapperScripts; // Clean up legacy key if present
// Write the cleaned config using the same atomic pattern as saveConfig.
// We cannot use saveConfig() here because it merges (spread) which cannot remove keys.
const data = JSON.stringify(config, null, 2);
fs.writeFileSync(CONFIG_TEMP, data, 'utf8');
if (fs.existsSync(CONFIG_FILE)) {
fs.copyFileSync(CONFIG_FILE, CONFIG_BACKUP);
}
fs.renameSync(CONFIG_TEMP, CONFIG_FILE);
console.log('[Config] Wrapper config reset to default');
return getDefaultWrapperConfig();
}
/**
* Generate a platform-specific wrapper script from structured config
* @param {Object} config - { stripFlags: string[], injectArgs: {arg, condition}[] }
* @param {string} platform - 'darwin', 'win32', or 'linux'
* @param {string|null} javaBin - Path to real java binary (required for darwin/linux)
* @returns {string} Generated script content
*/
function generateWrapperScript(config, platform, javaBin) {
const { stripFlags, injectArgs } = config;
const alwaysArgs = injectArgs.filter(a => a.condition === 'always');
const serverArgs = injectArgs.filter(a => a.condition === 'server');
if (platform === 'win32') {
return _generateWindowsWrapper(stripFlags, alwaysArgs, serverArgs);
} else {
return _generateUnixWrapper(stripFlags, alwaysArgs, serverArgs, javaBin);
}
}
function _generateUnixWrapper(stripFlags, alwaysArgs, serverArgs, javaBin) {
const lines = [
'#!/bin/bash',
'# Java wrapper - generated by HytaleF2P launcher',
`REAL_JAVA="${javaBin || '${JAVA_BIN}'}"`,
'ARGS=("$@")',
''
];
// Strip flags
if (stripFlags.length > 0) {
lines.push('# Strip JVM flags');
lines.push('FILTERED_ARGS=()');
lines.push('for arg in "${ARGS[@]}"; do');
lines.push(' case "$arg" in');
for (const flag of stripFlags) {
lines.push(` "${flag}") echo "[Wrapper] Stripped: $arg" ;;`);
}
lines.push(' *) FILTERED_ARGS+=("$arg") ;;');
lines.push(' esac');
lines.push('done');
} else {
lines.push('FILTERED_ARGS=("${ARGS[@]}")');
}
lines.push('');
// Always-inject args
if (alwaysArgs.length > 0) {
lines.push('# Inject args (always)');
for (const a of alwaysArgs) {
lines.push(`FILTERED_ARGS+=("${a.arg}")`);
lines.push(`echo "[Wrapper] Injected ${a.arg}"`);
}
lines.push('');
}
// Server-conditional args (appended after HytaleServer.jar if present)
if (serverArgs.length > 0) {
lines.push('# Inject args (server only)');
lines.push('IS_SERVER=false');
lines.push('for arg in "${FILTERED_ARGS[@]}"; do');
lines.push(' if [[ "$arg" == *"HytaleServer.jar"* ]]; then');
lines.push(' IS_SERVER=true');
lines.push(' break');
lines.push(' fi');
lines.push('done');
lines.push('if [ "$IS_SERVER" = true ]; then');
for (const a of serverArgs) {
lines.push(` FILTERED_ARGS+=("${a.arg}")`);
lines.push(` echo "[Wrapper] Injected ${a.arg}"`);
}
lines.push('fi');
lines.push('');
}
lines.push('echo "[Wrapper] Executing: $REAL_JAVA ${FILTERED_ARGS[*]}"');
lines.push('exec "$REAL_JAVA" "${FILTERED_ARGS[@]}"');
lines.push('');
return lines.join('\n');
}
function _generateWindowsWrapper(stripFlags, alwaysArgs, serverArgs) {
const lines = [
'@echo off',
'setlocal EnableDelayedExpansion',
'',
'REM Java wrapper - generated by HytaleF2P launcher',
'set "REAL_JAVA=%~dp0java-original.exe"',
'set "ARGS=%*"',
''
];
// Strip flags using string replacement
if (stripFlags.length > 0) {
lines.push('REM Strip JVM flags');
for (const flag of stripFlags) {
lines.push(`set "ARGS=!ARGS:${flag}=!"`);
}
lines.push('');
}
// Always-inject args
const alwaysExtra = alwaysArgs.map(a => a.arg).join(' ');
// Server-conditional args
if (serverArgs.length > 0) {
const serverExtra = serverArgs.map(a => a.arg).join(' ');
lines.push('REM Check if running HytaleServer.jar and inject server args');
lines.push('echo !ARGS! | findstr /i "HytaleServer.jar" >nul 2>&1');
lines.push('if "!ERRORLEVEL!"=="0" (');
if (alwaysExtra) {
lines.push(` echo [Wrapper] Injected ${alwaysExtra} ${serverExtra}`);
lines.push(` "%REAL_JAVA%" !ARGS! ${alwaysExtra} ${serverExtra}`);
} else {
lines.push(` echo [Wrapper] Injected ${serverExtra}`);
lines.push(` "%REAL_JAVA%" !ARGS! ${serverExtra}`);
}
lines.push(') else (');
if (alwaysExtra) {
lines.push(` "%REAL_JAVA%" !ARGS! ${alwaysExtra}`);
} else {
lines.push(' "%REAL_JAVA%" !ARGS!');
}
lines.push(')');
} else if (alwaysExtra) {
lines.push(`"%REAL_JAVA%" !ARGS! ${alwaysExtra}`);
} else {
lines.push('"%REAL_JAVA%" !ARGS!');
}
lines.push('exit /b !ERRORLEVEL!');
lines.push('');
return lines.join('\r\n');
}
// ============================================================================= // =============================================================================
// EXPORTS // EXPORTS
// ============================================================================= // =============================================================================
@@ -1114,8 +762,6 @@ module.exports = {
loadCloseLauncherOnStart, loadCloseLauncherOnStart,
saveLauncherHardwareAcceleration, saveLauncherHardwareAcceleration,
loadLauncherHardwareAcceleration, loadLauncherHardwareAcceleration,
saveAllowMultiInstance,
loadAllowMultiInstance,
// Mods // Mods
saveModsToConfig, saveModsToConfig,
@@ -1141,14 +787,6 @@ module.exports = {
saveVersionBranch, saveVersionBranch,
loadVersionBranch, loadVersionBranch,
// Java Wrapper Config
getDefaultWrapperConfig,
loadWrapperConfig,
saveWrapperConfig,
resetWrapperConfig,
generateWrapperScript,
// Constants // Constants
CONFIG_FILE, CONFIG_FILE
UUID_STORE_FILE
}; };

View File

@@ -20,8 +20,6 @@ const {
saveLauncherHardwareAcceleration, saveLauncherHardwareAcceleration,
loadLauncherHardwareAcceleration, loadLauncherHardwareAcceleration,
saveAllowMultiInstance,
loadAllowMultiInstance,
loadConfig, loadConfig,
saveConfig, saveConfig,
@@ -47,13 +45,7 @@ const {
saveVersionClient, saveVersionClient,
loadVersionClient, loadVersionClient,
saveVersionBranch, saveVersionBranch,
loadVersionBranch, loadVersionBranch
// Java Wrapper Config
getDefaultWrapperConfig,
loadWrapperConfig,
saveWrapperConfig,
resetWrapperConfig,
generateWrapperScript
} = require('./core/config'); } = require('./core/config');
const { getResolvedAppDir, getModsPath } = require('./core/paths'); const { getResolvedAppDir, getModsPath } = require('./core/paths');
@@ -86,8 +78,7 @@ const {
loadInstalledMods, loadInstalledMods,
downloadMod, downloadMod,
uninstallMod, uninstallMod,
toggleMod, toggleMod
getModFiles
} = require('./managers/modManager'); } = require('./managers/modManager');
// Services // Services
@@ -153,10 +144,6 @@ module.exports = {
saveLauncherHardwareAcceleration, saveLauncherHardwareAcceleration,
loadLauncherHardwareAcceleration, loadLauncherHardwareAcceleration,
// Multi-instance functions
saveAllowMultiInstance,
loadAllowMultiInstance,
// Config functions // Config functions
loadConfig, loadConfig,
saveConfig, saveConfig,
@@ -194,7 +181,6 @@ module.exports = {
downloadMod, downloadMod,
uninstallMod, uninstallMod,
toggleMod, toggleMod,
getModFiles,
saveModsToConfig, saveModsToConfig,
loadModsFromConfig, loadModsFromConfig,
@@ -211,13 +197,6 @@ module.exports = {
proposeGameUpdate, proposeGameUpdate,
handleFirstLaunchCheck, handleFirstLaunchCheck,
// Java Wrapper Config functions
getDefaultWrapperConfig,
loadWrapperConfig,
saveWrapperConfig,
resetWrapperConfig,
generateWrapperScript,
// Path functions // Path functions
getResolvedAppDir getResolvedAppDir
}; };

View File

@@ -3,7 +3,7 @@ const path = require('path');
const { execFile } = require('child_process'); const { execFile } = require('child_process');
const { downloadFile, retryDownload } = require('../utils/fileManager'); const { downloadFile, retryDownload } = require('../utils/fileManager');
const { getOS, getArch } = require('../utils/platformUtils'); const { getOS, getArch } = require('../utils/platformUtils');
const { validateChecksum, extractVersionDetails, getInstalledClientVersion, getUpdatePlan, extractVersionNumber, getAllMirrorUrls, getPatchesBaseUrl } = require('../services/versionManager'); const { validateChecksum, extractVersionDetails, canUseDifferentialUpdate, needsIntermediatePatches, getInstalledClientVersion } = require('../services/versionManager');
const { installButler } = require('./butlerManager'); const { installButler } = require('./butlerManager');
const { GAME_DIR, CACHE_DIR, TOOLS_DIR } = require('../core/paths'); const { GAME_DIR, CACHE_DIR, TOOLS_DIR } = require('../core/paths');
const { saveVersionClient } = require('../core/config'); const { saveVersionClient } = require('../core/config');
@@ -31,62 +31,15 @@ async function acquireGameArchive(downloadUrl, targetPath, checksum, progressCal
console.log(`Downloading game archive from: ${downloadUrl}`); console.log(`Downloading game archive from: ${downloadUrl}`);
// Try primary URL first, then mirror URLs on timeout/connection failure
const mirrors = await getAllMirrorUrls();
const primaryBase = await getPatchesBaseUrl();
const urlsToTry = [downloadUrl];
// Build mirror URLs by replacing the base URL
for (const mirror of mirrors) {
if (mirror !== primaryBase && downloadUrl.startsWith(primaryBase)) {
const mirrorUrl = downloadUrl.replace(primaryBase, mirror);
if (!urlsToTry.includes(mirrorUrl)) {
urlsToTry.push(mirrorUrl);
}
}
}
let lastError;
for (let i = 0; i < urlsToTry.length; i++) {
const url = urlsToTry[i];
try { try {
if (i > 0) {
console.log(`[Download] Trying mirror ${i}: ${url}`);
if (progressCallback) {
progressCallback(`Trying alternative mirror (${i}/${urlsToTry.length - 1})...`, 0, null, null, null);
}
// Clean up partial download from previous attempt
if (fs.existsSync(targetPath)) {
try { fs.unlinkSync(targetPath); } catch (e) {}
}
}
if (allowRetry) { if (allowRetry) {
await retryDownload(url, targetPath, progressCallback); await retryDownload(downloadUrl, targetPath, progressCallback);
} else { } else {
await downloadFile(url, targetPath, progressCallback); await downloadFile(downloadUrl, targetPath, progressCallback);
} }
lastError = null;
break; // Success
} catch (error) { } catch (error) {
lastError = error; const enhancedError = new Error(`Archive download failed: ${error.message}`);
const isConnectionError = error.message && ( enhancedError.originalError = error;
error.message.includes('ETIMEDOUT') ||
error.message.includes('ECONNREFUSED') ||
error.message.includes('ECONNABORTED') ||
error.message.includes('timeout')
);
if (isConnectionError && i < urlsToTry.length - 1) {
console.warn(`[Download] Connection failed (${error.message}), will try mirror...`);
continue;
}
// Non-connection error or last mirror — throw
break;
}
}
if (lastError) {
const enhancedError = new Error(`Archive download failed: ${lastError.message}`);
enhancedError.originalError = lastError;
enhancedError.downloadUrl = downloadUrl; enhancedError.downloadUrl = downloadUrl;
enhancedError.targetPath = targetPath; enhancedError.targetPath = targetPath;
throw enhancedError; throw enhancedError;
@@ -203,15 +156,15 @@ async function performIntelligentUpdate(targetVersion, branch = 'release', progr
console.log(`Initiating intelligent update to version ${targetVersion}`); console.log(`Initiating intelligent update to version ${targetVersion}`);
const currentVersion = getInstalledClientVersion(); const currentVersion = getInstalledClientVersion();
const currentBuild = extractVersionNumber(currentVersion) || 0; console.log(`Current version: ${currentVersion || 'none (clean install)'}`);
const targetBuild = extractVersionNumber(targetVersion); console.log(`Target version: ${targetVersion}`);
console.log(`Current build: ${currentBuild}, Target build: ${targetBuild}, Branch: ${branch}`); console.log(`Branch: ${branch}`);
// For non-release branches, always do full install
if (branch !== 'release') { if (branch !== 'release') {
console.log('Pre-release branch detected - forcing full archive download'); console.log(`Pre-release branch detected - forcing full archive download`);
const versionDetails = await extractVersionDetails(targetVersion, branch); const versionDetails = await extractVersionDetails(targetVersion, branch);
const archivePath = path.join(cacheDir, `${branch}_0_to_${targetBuild}.pwr`); const archiveName = path.basename(versionDetails.fullUrl);
const archivePath = path.join(cacheDir, `${branch}_${archiveName}`);
if (progressCallback) { if (progressCallback) {
progressCallback('Downloading full game archive (pre-release)...', 0, null, null, null); progressCallback('Downloading full game archive (pre-release)...', 0, null, null, null);
@@ -224,14 +177,14 @@ async function performIntelligentUpdate(targetVersion, branch = 'release', progr
return; return;
} }
// Clean install (no current version) if (!currentVersion) {
if (currentBuild === 0) {
console.log('No existing installation detected - downloading full archive'); console.log('No existing installation detected - downloading full archive');
const versionDetails = await extractVersionDetails(targetVersion, branch); const versionDetails = await extractVersionDetails(targetVersion, branch);
const archivePath = path.join(cacheDir, `${branch}_0_to_${targetBuild}.pwr`); const archiveName = path.basename(versionDetails.fullUrl);
const archivePath = path.join(cacheDir, `${branch}_${archiveName}`);
if (progressCallback) { if (progressCallback) {
progressCallback(`Downloading full game archive (first install - v${targetBuild})...`, 0, null, null, null); progressCallback(`Downloading full game archive (first install - v${targetVersion})...`, 0, null, null, null);
} }
await acquireGameArchive(versionDetails.fullUrl, archivePath, null, progressCallback); await acquireGameArchive(versionDetails.fullUrl, archivePath, null, progressCallback);
@@ -241,67 +194,59 @@ async function performIntelligentUpdate(targetVersion, branch = 'release', progr
return; return;
} }
// Already at target const patchesToApply = needsIntermediatePatches(currentVersion, targetVersion);
if (currentBuild >= targetBuild) {
console.log('Already at target version or newer'); if (patchesToApply.length === 0) {
console.log('Already at target version or invalid version sequence');
return; return;
} }
// Use mirror's update plan for optimal patch routing console.log(`Applying ${patchesToApply.length} differential patch(es): ${patchesToApply.join(' -> ')}`);
try {
const plan = await getUpdatePlan(currentBuild, targetBuild, branch);
console.log(`Applying ${plan.steps.length} patch(es): ${plan.steps.map(s => `${s.from}\u2192${s.to}`).join(' + ')}`); for (let i = 0; i < patchesToApply.length; i++) {
const patchVersion = patchesToApply[i];
const versionDetails = await extractVersionDetails(patchVersion, branch);
for (let i = 0; i < plan.steps.length; i++) { const canDifferential = canUseDifferentialUpdate(getInstalledClientVersion(), versionDetails);
const step = plan.steps[i];
const stepName = `${step.from}_to_${step.to}`; if (!canDifferential || !versionDetails.differentialUrl) {
const archivePath = path.join(cacheDir, `${branch}_${stepName}.pwr`); console.log(`WARNING: Differential patch not available for ${patchVersion}, using full archive`);
const isDifferential = step.from !== 0; const archiveName = path.basename(versionDetails.fullUrl);
const archivePath = path.join(cacheDir, `${branch}_${archiveName}`);
if (progressCallback) { if (progressCallback) {
progressCallback(`Downloading patch ${i + 1}/${plan.steps.length}: ${stepName}...`, 0, null, null, null); progressCallback(`Downloading full archive for ${patchVersion} (${i + 1}/${patchesToApply.length})...`, 0, null, null, null);
}
await acquireGameArchive(step.url, archivePath, null, progressCallback);
if (progressCallback) {
progressCallback(`Applying patch ${i + 1}/${plan.steps.length}: ${stepName}...`, 50, null, null, null);
}
await deployGameArchive(archivePath, gameDir, toolsDir, progressCallback, isDifferential);
// Clean up patch file
if (fs.existsSync(archivePath)) {
try {
fs.unlinkSync(archivePath);
console.log(`Cleaned up: ${stepName}.pwr`);
} catch (cleanupErr) {
console.warn(`Failed to cleanup: ${cleanupErr.message}`);
}
}
saveVersionClient(`v${step.to}`);
console.log(`Patch ${stepName} applied (${i + 1}/${plan.steps.length})`);
}
console.log(`Update completed. Version ${targetVersion} is now installed.`);
} catch (planError) {
console.error('Update plan failed:', planError.message);
console.log('Falling back to full archive download');
// Fallback: full install
const versionDetails = await extractVersionDetails(targetVersion, branch);
const archivePath = path.join(cacheDir, `${branch}_0_to_${targetBuild}.pwr`);
if (progressCallback) {
progressCallback(`Downloading full game archive (fallback)...`, 0, null, null, null);
} }
await acquireGameArchive(versionDetails.fullUrl, archivePath, null, progressCallback); await acquireGameArchive(versionDetails.fullUrl, archivePath, null, progressCallback);
await deployGameArchive(archivePath, gameDir, toolsDir, progressCallback, false); await deployGameArchive(archivePath, gameDir, toolsDir, progressCallback, false);
saveVersionClient(targetVersion); } else {
console.log(`Applying differential patch: ${versionDetails.sourceVersion} -> ${patchVersion}`);
const archiveName = path.basename(versionDetails.differentialUrl);
const archivePath = path.join(cacheDir, `${branch}_patch_${archiveName}`);
if (progressCallback) {
progressCallback(`Applying patch ${i + 1}/${patchesToApply.length}: ${patchVersion}...`, 0, null, null, null);
} }
await acquireGameArchive(versionDetails.differentialUrl, archivePath, versionDetails.checksum, progressCallback);
await deployGameArchive(archivePath, gameDir, toolsDir, progressCallback, true);
if (fs.existsSync(archivePath)) {
try {
fs.unlinkSync(archivePath);
console.log(`Cleaned up patch file: ${archiveName}`);
} catch (cleanupErr) {
console.warn(`Failed to cleanup patch file: ${cleanupErr.message}`);
}
}
}
saveVersionClient(patchVersion);
console.log(`Patch ${patchVersion} applied successfully (${i + 1}/${patchesToApply.length})`);
}
console.log(`Update completed successfully. Version ${targetVersion} is now installed.`);
} }
async function ensureGameInstalled(targetVersion, branch = 'release', progressCallback, gameDir = GAME_DIR, cacheDir = CACHE_DIR, toolsDir = TOOLS_DIR) { async function ensureGameInstalled(targetVersion, branch = 'release', progressCallback, gameDir = GAME_DIR, cacheDir = CACHE_DIR, toolsDir = TOOLS_DIR) {

View File

@@ -18,9 +18,7 @@ const {
saveVersionClient, saveVersionClient,
loadUsername, loadUsername,
hasUsername, hasUsername,
checkLaunchReady, checkLaunchReady
loadWrapperConfig,
generateWrapperScript
} = require('../core/config'); } = require('../core/config');
const { resolveJavaPath, getJavaExec, getBundledJavaPath, detectSystemJava, JAVA_EXECUTABLE } = require('./javaManager'); const { resolveJavaPath, getJavaExec, getBundledJavaPath, detectSystemJava, JAVA_EXECUTABLE } = require('./javaManager');
const { getLatestClientVersion } = require('../services/versionManager'); const { getLatestClientVersion } = require('../services/versionManager');
@@ -29,8 +27,6 @@ const { ensureGameInstalled } = require('./differentialUpdateManager');
const { syncModsForCurrentProfile } = require('./modManager'); const { syncModsForCurrentProfile } = require('./modManager');
const { getUserDataPath } = require('../utils/userDataMigration'); const { getUserDataPath } = require('../utils/userDataMigration');
const { syncServerList } = require('../utils/serverListSync'); const { syncServerList } = require('../utils/serverListSync');
const { killGameProcesses } = require('./gameManager');
const { loadAllowMultiInstance } = require('../core/config');
// Client patcher for custom auth server (sanasol.ws) // Client patcher for custom auth server (sanasol.ws)
let clientPatcher = null; let clientPatcher = null;
@@ -65,39 +61,12 @@ async function fetchAuthTokens(uuid, name) {
} }
const data = await response.json(); const data = await response.json();
const identityToken = data.IdentityToken || data.identityToken;
const sessionToken = data.SessionToken || data.sessionToken;
// Verify the identity token has the correct username
// This catches cases where the auth server defaults to "Player"
try {
const parts = identityToken.split('.');
if (parts.length >= 2) {
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
if (payload.username && payload.username !== name && name !== 'Player') {
console.warn(`[Auth] Token username mismatch: token has "${payload.username}", expected "${name}". Retrying...`);
// Retry once with explicit name
const retryResponse = await fetch(`${authServerUrl}/game-session/child`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ uuid: uuid, name: name, scopes: ['hytale:server', 'hytale:client'] })
});
if (retryResponse.ok) {
const retryData = await retryResponse.json();
console.log('[Auth] Retry successful');
return {
identityToken: retryData.IdentityToken || retryData.identityToken,
sessionToken: retryData.SessionToken || retryData.sessionToken
};
}
}
}
} catch (verifyErr) {
console.warn('[Auth] Token verification skipped:', verifyErr.message);
}
console.log('Auth tokens received from server'); console.log('Auth tokens received from server');
return { identityToken, sessionToken };
return {
identityToken: data.IdentityToken || data.identityToken,
sessionToken: data.SessionToken || data.sessionToken
};
} catch (error) { } catch (error) {
console.error('Failed to fetch auth tokens:', error.message); console.error('Failed to fetch auth tokens:', error.message);
// Fallback to local generation if server unavailable // Fallback to local generation if server unavailable
@@ -254,7 +223,6 @@ async function launchGame(playerNameOverride = null, progressCallback, javaPathO
} }
const uuid = getUuidForUser(playerName); const uuid = getUuidForUser(playerName);
console.log(`[Launcher] UUID for "${playerName}": ${uuid} (verify this stays constant across launches)`);
// Fetch tokens from auth server // Fetch tokens from auth server
if (progressCallback) { if (progressCallback) {
@@ -331,13 +299,23 @@ async function launchGame(playerNameOverride = null, progressCallback, javaPathO
console.log('Signed server binaries (after patching)'); console.log('Signed server binaries (after patching)');
} }
// Create java wrapper (must be signed on macOS)
if (javaBin && fs.existsSync(javaBin)) { if (javaBin && fs.existsSync(javaBin)) {
const javaWrapperPath = path.join(path.dirname(javaBin), 'java-wrapper'); const javaWrapperPath = path.join(path.dirname(javaBin), 'java-wrapper');
const wrapperScript = generateWrapperScript(loadWrapperConfig(), 'darwin', javaBin); const wrapperScript = `#!/bin/bash
# Java wrapper for macOS - adds --disable-sentry to fix Sentry hang issue
REAL_JAVA="${javaBin}"
ARGS=("$@")
for i in "\${!ARGS[@]}"; do
if [[ "\${ARGS[$i]}" == *"HytaleServer.jar"* ]]; then
ARGS=("\${ARGS[@]:0:$((i+1))}" "--disable-sentry" "\${ARGS[@]:$((i+1))}")
break
fi
done
exec "$REAL_JAVA" "\${ARGS[@]}"
`;
fs.writeFileSync(javaWrapperPath, wrapperScript, { mode: 0o755 }); fs.writeFileSync(javaWrapperPath, wrapperScript, { mode: 0o755 });
await signPath(javaWrapperPath, false); await signPath(javaWrapperPath, false);
console.log('Created java wrapper from config template'); console.log('Created java wrapper with --disable-sentry fix');
javaBin = javaWrapperPath; javaBin = javaWrapperPath;
} }
} catch (signError) { } catch (signError) {
@@ -346,40 +324,6 @@ async function launchGame(playerNameOverride = null, progressCallback, javaPathO
} }
} }
// Windows: Create java wrapper to strip/inject JVM flags per wrapper config
if (process.platform === 'win32' && javaBin && fs.existsSync(javaBin)) {
try {
const javaDir = path.dirname(javaBin);
const javaOriginal = path.join(javaDir, 'java-original.exe');
const javaWrapperPath = path.join(javaDir, 'java-wrapper.bat');
if (!fs.existsSync(javaOriginal)) {
fs.copyFileSync(javaBin, javaOriginal);
console.log('Backed up java.exe as java-original.exe');
}
const wrapperScript = generateWrapperScript(loadWrapperConfig(), 'win32', null);
fs.writeFileSync(javaWrapperPath, wrapperScript);
console.log('Created Windows java wrapper from config template');
javaBin = javaWrapperPath;
} catch (wrapperError) {
console.log('Notice: Windows java wrapper creation failed:', wrapperError.message);
}
}
// Linux: Create java wrapper to strip/inject JVM flags per wrapper config
if (process.platform === 'linux' && javaBin && fs.existsSync(javaBin)) {
try {
const javaWrapperPath = path.join(path.dirname(javaBin), 'java-wrapper');
const wrapperScript = generateWrapperScript(loadWrapperConfig(), 'linux', javaBin);
fs.writeFileSync(javaWrapperPath, wrapperScript, { mode: 0o755 });
console.log('Created Linux java wrapper from config template');
javaBin = javaWrapperPath;
} catch (wrapperError) {
console.log('Notice: Linux java wrapper creation failed:', wrapperError.message);
}
}
const args = [ const args = [
'--app-dir', gameLatest, '--app-dir', gameLatest,
'--java-exec', javaBin, '--java-exec', javaBin,
@@ -464,29 +408,11 @@ async function launchGame(playerNameOverride = null, progressCallback, javaPathO
} }
} }
// Kill any stalled game processes from a previous launch to prevent file locks
// and "game already running" issues (skip if multi-instance mode is enabled)
if (!loadAllowMultiInstance()) {
await killGameProcesses();
}
// Remove AOT cache: generated by official Hytale JRE, incompatible with F2P JRE.
// Client adds -XX:AOTCache when this file exists, causing classloading failures.
const aotCache = path.join(gameLatest, 'Server', 'HytaleServer.aot');
if (fs.existsSync(aotCache)) {
try {
fs.unlinkSync(aotCache);
console.log('Removed incompatible AOT cache (HytaleServer.aot)');
} catch (aotErr) {
console.warn('Could not remove AOT cache:', aotErr.message);
}
}
// DualAuth Agent: Set JAVA_TOOL_OPTIONS so java picks up -javaagent: flag // DualAuth Agent: Set JAVA_TOOL_OPTIONS so java picks up -javaagent: flag
// This enables runtime auth patching without modifying the server JAR // This enables runtime auth patching without modifying the server JAR
const agentJar = path.join(gameLatest, 'Server', 'dualauth-agent.jar'); const agentJar = path.join(gameLatest, 'Server', 'dualauth-agent.jar');
if (fs.existsSync(agentJar)) { if (fs.existsSync(agentJar)) {
const agentFlag = `-javaagent:"${agentJar}"`; const agentFlag = `-javaagent:${agentJar}`;
env.JAVA_TOOL_OPTIONS = env.JAVA_TOOL_OPTIONS env.JAVA_TOOL_OPTIONS = env.JAVA_TOOL_OPTIONS
? `${env.JAVA_TOOL_OPTIONS} ${agentFlag}` ? `${env.JAVA_TOOL_OPTIONS} ${agentFlag}`
: agentFlag; : agentFlag;

View File

@@ -5,7 +5,7 @@ const { promisify } = require('util');
const { getResolvedAppDir, findClientPath, findUserDataPath, findUserDataRecursive, GAME_DIR, CACHE_DIR, TOOLS_DIR } = require('../core/paths'); const { getResolvedAppDir, findClientPath, findUserDataPath, findUserDataRecursive, GAME_DIR, CACHE_DIR, TOOLS_DIR } = require('../core/paths');
const { getOS, getArch } = require('../utils/platformUtils'); const { getOS, getArch } = require('../utils/platformUtils');
const { downloadFile, retryDownload, retryStalledDownload, MAX_AUTOMATIC_STALL_RETRIES } = require('../utils/fileManager'); const { downloadFile, retryDownload, retryStalledDownload, MAX_AUTOMATIC_STALL_RETRIES } = require('../utils/fileManager');
const { getLatestClientVersion, getInstalledClientVersion, getUpdatePlan, extractVersionNumber } = require('../services/versionManager'); const { getLatestClientVersion, getInstalledClientVersion } = require('../services/versionManager');
const { FORCE_CLEAN_INSTALL_VERSION, CLEAN_INSTALL_TEST_VERSION } = require('../core/testConfig'); const { FORCE_CLEAN_INSTALL_VERSION, CLEAN_INSTALL_TEST_VERSION } = require('../core/testConfig');
const { installButler } = require('./butlerManager'); const { installButler } = require('./butlerManager');
const { downloadAndReplaceHomePageUI, downloadAndReplaceLogo } = require('./uiFileManager'); const { downloadAndReplaceHomePageUI, downloadAndReplaceLogo } = require('./uiFileManager');
@@ -39,41 +39,6 @@ async function isGameRunning() {
} }
} }
// Force-kill stalled game processes to release file locks before repair/reinstall.
// Cross-platform: Windows (taskkill/PowerShell), macOS (pkill), Linux (pkill).
async function killGameProcesses() {
const killed = [];
async function tryKill(command, label) {
try {
await execAsync(command);
killed.push(label);
} catch (_) { /* process not found is expected */ }
}
if (process.platform === 'win32') {
// Kill client
await tryKill('taskkill /F /IM "HytaleClient.exe" /T', 'HytaleClient.exe');
// Kill java.exe instances running HytaleServer.jar via PowerShell
// (Get-CimInstance replaces deprecated wmic, works on Windows 10+)
await tryKill(
'powershell -NoProfile -Command "Get-CimInstance Win32_Process -Filter \\"name=\'java.exe\'\\" | Where-Object { $_.CommandLine -like \'*HytaleServer*\' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }"',
'java.exe(HytaleServer)'
);
} else {
// macOS and Linux
await tryKill('pkill -9 -f HytaleClient', 'HytaleClient');
await tryKill('pkill -9 -f HytaleServer', 'HytaleServer');
}
if (killed.length > 0) {
console.log(`[GameManager] Force-killed stalled processes: ${killed.join(', ')}`);
// Wait for OS to release file handles
await new Promise(resolve => setTimeout(resolve, 2000));
}
return killed;
}
// Helper function to safely remove directory with retry logic // Helper function to safely remove directory with retry logic
async function safeRemoveDirectory(dirPath, maxRetries = 3) { async function safeRemoveDirectory(dirPath, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) { for (let attempt = 1; attempt <= maxRetries; attempt++) {
@@ -87,11 +52,6 @@ async function safeRemoveDirectory(dirPath, maxRetries = 3) {
console.warn(`Attempt ${attempt}/${maxRetries} failed to remove ${dirPath}: ${error.message}`); console.warn(`Attempt ${attempt}/${maxRetries} failed to remove ${dirPath}: ${error.message}`);
if (attempt < maxRetries) { if (attempt < maxRetries) {
// On EPERM/EBUSY, try killing stalled game processes that hold file locks
if (attempt === 1 && (error.code === 'EPERM' || error.code === 'EBUSY')) {
console.log('Permission error detected, killing stalled game processes...');
await killGameProcesses();
}
// Wait before retrying (exponential backoff) // Wait before retrying (exponential backoff)
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000); const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
console.log(`Waiting ${delay}ms before retry...`); console.log(`Waiting ${delay}ms before retry...`);
@@ -104,7 +64,7 @@ async function safeRemoveDirectory(dirPath, maxRetries = 3) {
} }
} }
async function downloadPWR(branch = 'release', fileName = 'v8', progressCallback, cacheDir = CACHE_DIR, manualRetry = false, directUrl = null, expectedSize = null) { async function downloadPWR(branch = 'release', fileName = 'v8', progressCallback, cacheDir = CACHE_DIR, manualRetry = false) {
const osName = getOS(); const osName = getOS();
const arch = getArch(); const arch = getArch();
@@ -112,69 +72,43 @@ async function downloadPWR(branch = 'release', fileName = 'v8', progressCallback
throw new Error('Hytale x86_64 Intel Mac Support has not been released yet. Please check back later.'); throw new Error('Hytale x86_64 Intel Mac Support has not been released yet. Please check back later.');
} }
const { getPWRUrlFromNewAPI } = require('../services/versionManager');
let url; let url;
let isUsingNewAPI = false;
if (directUrl) {
url = directUrl;
console.log(`[DownloadPWR] Using direct URL: ${url}`);
} else {
const { getPWRUrl } = require('../services/versionManager');
try { try {
console.log(`[DownloadPWR] Fetching mirror URL for branch: ${branch}, version: ${fileName}`); console.log(`[DownloadPWR] Fetching URL from new API for branch: ${branch}, version: ${fileName}`);
url = await getPWRUrl(branch, fileName); url = await getPWRUrlFromNewAPI(branch, fileName);
console.log(`[DownloadPWR] Mirror URL: ${url}`); isUsingNewAPI = true;
console.log(`[DownloadPWR] Using new API URL: ${url}`);
} catch (error) { } catch (error) {
console.error(`[DownloadPWR] Failed to get mirror URL: ${error.message}`); console.error(`[DownloadPWR] Failed to get URL from new API: ${error.message}`);
const { getPatchesBaseUrl } = require('../services/versionManager'); console.log(`[DownloadPWR] Falling back to old URL format`);
const baseUrl = await getPatchesBaseUrl(); url = `https://game-patches.hytale.com/patches/${osName}/${arch}/${branch}/0/${fileName}.pwr`;
url = `${baseUrl}/${osName}/${arch}/${branch}/0_to_${extractVersionNumber(fileName)}.pwr`;
console.log(`[DownloadPWR] Fallback URL: ${url}`);
}
}
// Look up expected file size from manifest if not provided
if (!expectedSize) {
try {
const { fetchMirrorManifest } = require('../services/versionManager');
const manifest = await fetchMirrorManifest();
// Try to match: "0_to_11" format or "v11" format
const versionMatch = fileName.match(/^(\d+)_to_(\d+)$/);
let manifestKey;
if (versionMatch) {
manifestKey = `${osName}/${arch}/${branch}/${fileName}.pwr`;
} else {
const buildNum = extractVersionNumber(fileName);
manifestKey = `${osName}/${arch}/${branch}/0_to_${buildNum}.pwr`;
}
if (manifest.files[manifestKey]) {
expectedSize = manifest.files[manifestKey].size;
console.log(`[PWR] Expected size from manifest: ${(expectedSize / 1024 / 1024).toFixed(2)} MB`);
}
} catch (e) {
console.log(`[PWR] Could not fetch expected size from manifest: ${e.message}`);
}
} }
const dest = path.join(cacheDir, `${branch}_${fileName}.pwr`); const dest = path.join(cacheDir, `${branch}_${fileName}.pwr`);
// Check if file exists and validate it // Check if file exists and validate it
if (fs.existsSync(dest) && !manualRetry) { if (fs.existsSync(dest) && !manualRetry) {
console.log('PWR file found in cache:', dest);
// Validate file size (PWR files should be > 1MB and >= 1.5GB for complete downloads)
const stats = fs.statSync(dest); const stats = fs.statSync(dest);
if (stats.size > 1024 * 1024) { if (stats.size < 1024 * 1024) {
// Validate against expected size - reject if file is truncated (< 99% of expected) return false;
if (expectedSize && stats.size < expectedSize * 0.99) {
console.log(`[PWR] Cached file truncated: ${(stats.size / 1024 / 1024).toFixed(2)} MB, expected ${(expectedSize / 1024 / 1024).toFixed(2)} MB. Deleting and re-downloading.`);
fs.unlinkSync(dest);
} else {
console.log(`[PWR] Using cached file: ${dest} (${(stats.size / 1024 / 1024).toFixed(2)} MB)`);
return dest;
} }
} else {
console.log(`[PWR] Cached file too small (${stats.size} bytes), re-downloading`); // Check if file is under 1.5 GB (incomplete download)
const sizeInMB = stats.size / 1024 / 1024;
if (sizeInMB < 1500) {
console.log(`[PWR Validation] File appears incomplete: ${sizeInMB.toFixed(2)} MB < 1.5 GB`);
return false;
} }
} }
console.log(`[DownloadPWR] Downloading from: ${url}`); console.log(`Fetching PWR patch file from ${isUsingNewAPI ? 'NEW API' : 'old API'}:`, url);
try { try {
if (manualRetry) { if (manualRetry) {
@@ -200,7 +134,7 @@ async function downloadPWR(branch = 'release', fileName = 'v8', progressCallback
const retryStats = fs.statSync(dest); const retryStats = fs.statSync(dest);
console.log(`PWR file downloaded (auto-retry), size: ${(retryStats.size / 1024 / 1024).toFixed(2)} MB`); console.log(`PWR file downloaded (auto-retry), size: ${(retryStats.size / 1024 / 1024).toFixed(2)} MB`);
if (!validatePWRFile(dest, expectedSize)) { if (!validatePWRFile(dest)) {
console.log(`[PWR Validation] PWR file validation failed after auto-retry, deleting corrupted file: ${dest}`); console.log(`[PWR Validation] PWR file validation failed after auto-retry, deleting corrupted file: ${dest}`);
fs.unlinkSync(dest); fs.unlinkSync(dest);
throw new Error('Downloaded PWR file is corrupted or invalid after automatic retry. Please retry manually'); throw new Error('Downloaded PWR file is corrupted or invalid after automatic retry. Please retry manually');
@@ -251,7 +185,7 @@ async function downloadPWR(branch = 'release', fileName = 'v8', progressCallback
const stats = fs.statSync(dest); const stats = fs.statSync(dest);
console.log(`PWR file downloaded, size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`); console.log(`PWR file downloaded, size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
if (!validatePWRFile(dest, expectedSize)) { if (!validatePWRFile(dest)) {
console.log(`[PWR Validation] PWR file validation failed, deleting corrupted file: ${dest}`); console.log(`[PWR Validation] PWR file validation failed, deleting corrupted file: ${dest}`);
fs.unlinkSync(dest); fs.unlinkSync(dest);
throw new Error('Downloaded PWR file is corrupted or invalid. Please retry'); throw new Error('Downloaded PWR file is corrupted or invalid. Please retry');
@@ -269,7 +203,7 @@ async function retryPWRDownload(branch, fileName, progressCallback, cacheDir = C
return await downloadPWR(branch, fileName, progressCallback, cacheDir, true); return await downloadPWR(branch, fileName, progressCallback, cacheDir, true);
} }
async function applyPWR(pwrFile, progressCallback, gameDir = GAME_DIR, toolsDir = TOOLS_DIR, branch = 'release', cacheDir = CACHE_DIR, skipExistingCheck = false) { async function applyPWR(pwrFile, progressCallback, gameDir = GAME_DIR, toolsDir = TOOLS_DIR, branch = 'release', cacheDir = CACHE_DIR) {
console.log(`[Butler] Starting PWR application with:`); console.log(`[Butler] Starting PWR application with:`);
console.log(`[Butler] - PWR file: ${pwrFile}`); console.log(`[Butler] - PWR file: ${pwrFile}`);
console.log(`[Butler] - Staging dir: ${path.join(gameDir, 'staging-temp')}`); console.log(`[Butler] - Staging dir: ${path.join(gameDir, 'staging-temp')}`);
@@ -293,13 +227,12 @@ async function applyPWR(pwrFile, progressCallback, gameDir = GAME_DIR, toolsDir
const gameLatest = gameDir; const gameLatest = gameDir;
const stagingDir = path.join(gameLatest, 'staging-temp'); const stagingDir = path.join(gameLatest, 'staging-temp');
if (!skipExistingCheck) {
const clientPath = findClientPath(gameLatest); const clientPath = findClientPath(gameLatest);
if (clientPath) { if (clientPath) {
console.log('Game files detected, skipping patch installation.'); console.log('Game files detected, skipping patch installation.');
return; return;
} }
}
// Validate and prepare directories // Validate and prepare directories
validateGameDirectory(gameLatest, stagingDir); validateGameDirectory(gameLatest, stagingDir);
@@ -479,65 +412,6 @@ async function updateGameFiles(newVersion, progressCallback, gameDir = GAME_DIR,
} }
console.log(`Updating game files to version: ${newVersion} (branch: ${branch})`); console.log(`Updating game files to version: ${newVersion} (branch: ${branch})`);
// Determine update strategy: intermediate patches vs full reinstall
const currentVersion = loadVersionClient();
const currentBuild = extractVersionNumber(currentVersion) || 0;
const targetBuild = extractVersionNumber(newVersion);
let useIntermediatePatches = false;
let updatePlan = null;
if (currentBuild > 0 && currentBuild < targetBuild) {
try {
updatePlan = await getUpdatePlan(currentBuild, targetBuild, branch);
useIntermediatePatches = !updatePlan.isFullInstall;
if (useIntermediatePatches) {
const totalMB = (updatePlan.totalSize / 1024 / 1024).toFixed(0);
console.log(`[UpdateGameFiles] Using intermediate patches: ${updatePlan.steps.map(s => `${s.from}\u2192${s.to}`).join(' + ')} (${totalMB} MB)`);
}
} catch (planError) {
console.warn('[UpdateGameFiles] Could not get update plan, falling back to full install:', planError.message);
}
}
if (useIntermediatePatches && updatePlan) {
// Apply intermediate patches directly to game dir
for (let i = 0; i < updatePlan.steps.length; i++) {
const step = updatePlan.steps[i];
const stepName = `${step.from}_to_${step.to}`;
if (progressCallback) {
const progress = 20 + Math.round((i / updatePlan.steps.length) * 60);
progressCallback(`Downloading patch ${i + 1}/${updatePlan.steps.length} (${stepName})...`, progress, null, null, null);
}
const pwrFile = await downloadPWR(branch, stepName, progressCallback, cacheDir, false, step.url, step.size);
if (!pwrFile) {
throw new Error(`Failed to download patch ${stepName}`);
}
if (progressCallback) {
progressCallback(`Applying patch ${i + 1}/${updatePlan.steps.length} (${stepName})...`, null, null, null, null);
}
await applyPWR(pwrFile, progressCallback, gameDir, toolsDir, branch, cacheDir, true);
// Clean up PWR file from cache
try {
if (fs.existsSync(pwrFile)) {
fs.unlinkSync(pwrFile);
}
} catch (delErr) {
console.warn('[UpdateGameFiles] Failed to delete PWR from cache:', delErr.message);
}
// Save intermediate version so we can resume if interrupted
saveVersionClient(`v${step.to}`);
console.log(`[UpdateGameFiles] Applied patch ${stepName} (${i + 1}/${updatePlan.steps.length})`);
}
} else {
// Full install: download 0->target, apply to temp dir, swap
tempUpdateDir = path.join(gameDir, '..', 'temp_update'); tempUpdateDir = path.join(gameDir, '..', 'temp_update');
if (fs.existsSync(tempUpdateDir)) { if (fs.existsSync(tempUpdateDir)) {
@@ -556,7 +430,7 @@ async function updateGameFiles(newVersion, progressCallback, gameDir = GAME_DIR,
} }
await applyPWR(pwrFile, progressCallback, tempUpdateDir, toolsDir, branch, cacheDir); await applyPWR(pwrFile, progressCallback, tempUpdateDir, toolsDir, branch, cacheDir);
// Delete PWR file from cache after successful update
try { try {
if (fs.existsSync(pwrFile)) { if (fs.existsSync(pwrFile)) {
fs.unlinkSync(pwrFile); fs.unlinkSync(pwrFile);
@@ -565,7 +439,6 @@ async function updateGameFiles(newVersion, progressCallback, gameDir = GAME_DIR,
} catch (delErr) { } catch (delErr) {
console.warn('[UpdateGameFiles] Failed to delete PWR file from cache:', delErr.message); console.warn('[UpdateGameFiles] Failed to delete PWR file from cache:', delErr.message);
} }
if (progressCallback) { if (progressCallback) {
progressCallback('Replacing game files...', 80, null, null, null); progressCallback('Replacing game files...', 80, null, null, null);
} }
@@ -590,7 +463,6 @@ async function updateGameFiles(newVersion, progressCallback, gameDir = GAME_DIR,
} }
fs.renameSync(tempUpdateDir, gameDir); fs.renameSync(tempUpdateDir, gameDir);
}
const homeUIResult = await downloadAndReplaceHomePageUI(gameDir, progressCallback); const homeUIResult = await downloadAndReplaceHomePageUI(gameDir, progressCallback);
console.log('HomePage.ui update result after update:', homeUIResult); console.log('HomePage.ui update result after update:', homeUIResult);
@@ -873,14 +745,11 @@ async function repairGame(progressCallback, branchOverride = null) {
progressCallback('Removing old game files...', 30, null, null, null); progressCallback('Removing old game files...', 30, null, null, null);
} }
// Kill stalled game processes before attempting to delete files // Check if game is running before attempting to delete files
const gameRunning = await isGameRunning(); const gameRunning = await isGameRunning();
if (gameRunning) { if (gameRunning) {
console.warn('[RepairGame] Game processes detected. Force-killing to release file locks...'); console.warn('[RepairGame] Game appears to be running. This may cause permission errors during repair.');
if (progressCallback) { console.log('[RepairGame] Please close the game before repairing, or wait for the repair to complete.');
progressCallback('Stopping stalled game processes...', 20, null, null, null);
}
await killGameProcesses();
} }
// Delete Game and Cache Directory with retry logic // Delete Game and Cache Directory with retry logic
@@ -964,8 +833,7 @@ function validateGameDirectory(gameDir, stagingDir) {
} }
// Enhanced PWR file validation // Enhanced PWR file validation
// Accepts intermediate patches (50+ MB) and full installs (1.5+ GB) function validatePWRFile(filePath) {
function validatePWRFile(filePath, expectedSize = null) {
try { try {
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
return false; return false;
@@ -974,20 +842,27 @@ function validatePWRFile(filePath, expectedSize = null) {
const stats = fs.statSync(filePath); const stats = fs.statSync(filePath);
const sizeInMB = stats.size / 1024 / 1024; const sizeInMB = stats.size / 1024 / 1024;
// PWR files should be at least 1 MB
if (stats.size < 1024 * 1024) { if (stats.size < 1024 * 1024) {
console.log(`[PWR Validation] File too small: ${sizeInMB.toFixed(2)} MB`);
return false; return false;
} }
// Validate against expected size if known (reject if < 99% of expected) // Check if file is under 1.5 GB (incomplete download)
if (expectedSize && stats.size < expectedSize * 0.99) { if (sizeInMB < 1500) {
const expectedMB = expectedSize / 1024 / 1024; console.log(`[PWR Validation] File appears incomplete: ${sizeInMB.toFixed(2)} MB < 1.5 GB`);
console.log(`[PWR Validation] File truncated: ${sizeInMB.toFixed(2)} MB, expected ${expectedMB.toFixed(2)} MB`);
return false; return false;
} }
console.log(`[PWR Validation] File size: ${sizeInMB.toFixed(2)} MB - OK`); // Basic file header validation (PWR files should have specific headers)
const buffer = fs.readFileSync(filePath, { start: 0, end: 20 });
if (buffer.length < 10) {
return false;
}
// Check for common PWR magic bytes or patterns
// This is a basic check - could be enhanced with actual PWR format specification
const header = buffer.toString('hex', 0, 10);
console.log(`[PWR Validation] File header: ${header}`);
return true; return true;
} catch (error) { } catch (error) {
console.error(`[PWR Validation] Error:`, error.message); console.error(`[PWR Validation] Error:`, error.message);
@@ -1007,6 +882,5 @@ module.exports = {
installGame, installGame,
uninstallGame, uninstallGame,
checkExistingGameInstallation, checkExistingGameInstallation,
repairGame, repairGame
killGameProcesses
}; };

View File

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

View File

@@ -285,27 +285,6 @@ async function toggleMod(modId, modsPath) {
} }
} }
async function getModFiles(modId) {
try {
const response = await axios.get(`https://api.curseforge.com/v1/mods/${modId}/files`, {
headers: {
'x-api-key': API_KEY,
'Accept': 'application/json'
},
params: {
pageSize: 20,
sortOrder: 'desc'
}
});
return response.data.data;
} catch (error) {
console.error('Error fetching mod files:', error);
return [];
}
}
async function syncModsForCurrentProfile() { async function syncModsForCurrentProfile() {
try { try {
const activeProfile = profileManager.getActiveProfile(); const activeProfile = profileManager.getActiveProfile();
@@ -476,6 +455,5 @@ module.exports = {
syncModsForCurrentProfile, syncModsForCurrentProfile,
generateModId, generateModId,
extractModName, extractModName,
extractVersion, extractVersion
getModFiles
}; };

View File

@@ -1,500 +1,287 @@
const axios = require('axios'); const axios = require('axios');
const crypto = require('crypto'); const crypto = require('crypto');
const fs = require('fs'); const fs = require('fs');
const path = require('path');
const { getOS, getArch } = require('../utils/platformUtils'); const { getOS, getArch } = require('../utils/platformUtils');
const { smartRequest } = require('../utils/proxyClient');
// Patches base URL fetched dynamically via multi-source fallback chain const BASE_PATCH_URL = 'https://game-patches.hytale.com/patches';
const AUTH_DOMAIN = process.env.HYTALE_AUTH_DOMAIN || 'auth.sanasol.ws'; const MANIFEST_API = 'https://files.hytalef2p.com/api/patch_manifest';
const PATCHES_CONFIG_SOURCES = [ const NEW_API_URL = 'https://thecute.cloud/ShipOfYarn/api.php';
{ type: 'http', url: `https://${AUTH_DOMAIN}/api/patches-config`, name: 'primary' },
{ type: 'http', url: 'https://htdwnldsan.top/patches-config', name: 'backup-1' },
{ type: 'http', url: 'https://dl1.htdwnldsan.top/patches-config', name: 'backup-2' },
{ type: 'doh', name: '_patches.htdwnldsan.top', name_label: 'dns-txt' },
];
const HARDCODED_FALLBACK = 'https://dl.vboro.de/patches';
// Alternative mirrors (non-Cloudflare) for regions where CF is blocked let apiCache = null;
const NON_CF_MIRRORS = [ let apiCacheTime = 0;
'https://dl1.htdwnldsan.top', const API_CACHE_DURATION = 60000; // 1 minute
'https://htdwnldsan.top/patches',
];
// Fallback: latest known build number if manifest is unreachable async function fetchNewAPI() {
const FALLBACK_LATEST_BUILD = 11;
let patchesBaseUrl = null;
let patchesConfigTime = 0;
const PATCHES_CONFIG_CACHE_DURATION = 300000; // 5 minutes
let manifestCache = null;
let manifestCacheTime = 0;
const MANIFEST_CACHE_DURATION = 60000; // 1 minute
// Disk cache path for patches URL (survives restarts)
function getDiskCachePath() {
const os = require('os');
const home = os.homedir();
let appDir;
if (process.platform === 'win32') {
appDir = path.join(home, 'AppData', 'Local', 'HytaleF2P');
} else if (process.platform === 'darwin') {
appDir = path.join(home, 'Library', 'Application Support', 'HytaleF2P');
} else {
appDir = path.join(home, '.hytalef2p');
}
return path.join(appDir, 'patches-url-cache.json');
}
function saveDiskCache(url) {
try {
const cachePath = getDiskCachePath();
const dir = path.dirname(cachePath);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(cachePath, JSON.stringify({ patches_url: url, ts: Date.now() }), 'utf8');
} catch (e) {
// Non-critical, ignore
}
}
function loadDiskCache() {
try {
const cachePath = getDiskCachePath();
if (fs.existsSync(cachePath)) {
const data = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
if (data && data.patches_url) return data.patches_url;
}
} catch (e) {
// Non-critical, ignore
}
return null;
}
/**
* Fetch patches URL from a single HTTP config endpoint
*/
async function fetchFromHttp(url) {
const response = await axios.get(url, {
timeout: 8000,
headers: { 'User-Agent': 'Hytale-F2P-Launcher' }
});
if (response.data && response.data.patches_url) {
return response.data.patches_url.replace(/\/+$/, '');
}
throw new Error('Invalid response');
}
/**
* Fetch patches URL from DNS TXT record via DNS-over-HTTPS
*/
async function fetchFromDoh(recordName) {
const dohEndpoints = [
{ url: 'https://dns.google/resolve', params: { name: recordName, type: 'TXT' } },
{ url: 'https://cloudflare-dns.com/dns-query', params: { name: recordName, type: 'TXT' }, headers: { 'Accept': 'application/dns-json' } },
];
for (const endpoint of dohEndpoints) {
try {
const response = await axios.get(endpoint.url, {
params: endpoint.params,
headers: { 'User-Agent': 'Hytale-F2P-Launcher', ...(endpoint.headers || {}) },
timeout: 5000
});
const answers = response.data && response.data.Answer;
if (answers && answers.length > 0) {
// TXT records are quoted, strip quotes
const txt = answers[0].data.replace(/^"|"$/g, '');
if (txt.startsWith('http')) return txt.replace(/\/+$/, '');
}
} catch (e) {
// Try next DoH endpoint
}
}
throw new Error('All DoH endpoints failed');
}
/**
* Fetch patches base URL with hardened multi-source fallback chain:
* 1. Memory cache (5 min)
* 2. HTTP: auth.sanasol.ws (primary)
* 3. HTTP: htdwnldsan.top (backup, different host/domain/registrar)
* 4. DNS TXT: _patches.htdwnldsan.top via DoH (different protocol layer)
* 5. Disk cache (survives restarts, never expires)
* 6. Hardcoded fallback URL (last resort)
*/
async function getPatchesBaseUrl() {
const now = Date.now(); const now = Date.now();
// 1. Memory cache if (apiCache && (now - apiCacheTime) < API_CACHE_DURATION) {
if (patchesBaseUrl && (now - patchesConfigTime) < PATCHES_CONFIG_CACHE_DURATION) { console.log('[NewAPI] Using cached API data');
return patchesBaseUrl; return apiCache;
} }
// 2-4. Try all sources: HTTP endpoints first, then DoH
for (const source of PATCHES_CONFIG_SOURCES) {
try { try {
let url; console.log('[NewAPI] Fetching from:', NEW_API_URL);
if (source.type === 'http') { const response = await axios.get(NEW_API_URL, {
console.log(`[Mirror] Trying ${source.name}: ${source.url}`);
url = await fetchFromHttp(source.url);
} else if (source.type === 'doh') {
console.log(`[Mirror] Trying ${source.name_label}: ${source.name}`);
url = await fetchFromDoh(source.name);
}
if (url) {
patchesBaseUrl = url;
patchesConfigTime = now;
saveDiskCache(url);
console.log(`[Mirror] Patches URL (via ${source.name || source.name_label}): ${url}`);
return url;
}
} catch (e) {
console.warn(`[Mirror] ${source.name || source.name_label} failed: ${e.message}`);
}
}
// 5. Stale memory cache (any age)
if (patchesBaseUrl) {
console.log('[Mirror] All sources failed, using stale memory cache:', patchesBaseUrl);
return patchesBaseUrl;
}
// 6. Disk cache (survives restarts)
const diskUrl = loadDiskCache();
if (diskUrl) {
patchesBaseUrl = diskUrl;
console.log('[Mirror] All sources failed, using disk cache:', diskUrl);
return diskUrl;
}
// 7. Hardcoded fallback
console.warn('[Mirror] All sources + caches exhausted, using hardcoded fallback:', HARDCODED_FALLBACK);
patchesBaseUrl = HARDCODED_FALLBACK;
return HARDCODED_FALLBACK;
}
/**
* Get all available mirror base URLs (primary + non-Cloudflare fallbacks)
* Used by download logic to retry on different mirrors when primary is blocked
*/
async function getAllMirrorUrls() {
const primary = await getPatchesBaseUrl();
// Deduplicate: don't include mirrors that match primary
const mirrors = NON_CF_MIRRORS.filter(m => m !== primary);
return [primary, ...mirrors];
}
/**
* Fetch the mirror manifest — tries primary URL first, then non-Cloudflare mirrors
*/
async function fetchMirrorManifest() {
const now = Date.now();
if (manifestCache && (now - manifestCacheTime) < MANIFEST_CACHE_DURATION) {
console.log('[Mirror] Using cached manifest');
return manifestCache;
}
const mirrors = await getAllMirrorUrls();
for (let i = 0; i < mirrors.length; i++) {
const baseUrl = mirrors[i];
const manifestUrl = `${baseUrl}/manifest.json`;
try {
console.log(`[Mirror] Fetching manifest from: ${manifestUrl}`);
const response = await axios.get(manifestUrl, {
timeout: 15000, timeout: 15000,
maxRedirects: 5, headers: {
headers: { 'User-Agent': 'Hytale-F2P-Launcher' } 'User-Agent': 'Hytale-F2P-Launcher'
}
}); });
if (response.data && response.data.files) { if (response.data && response.data.hytale) {
manifestCache = response.data; apiCache = response.data;
manifestCacheTime = now; apiCacheTime = now;
// If a non-primary mirror worked, switch to it for downloads too console.log('[NewAPI] API data fetched and cached successfully');
if (i > 0) {
console.log(`[Mirror] Primary unreachable, switching to mirror: ${baseUrl}`);
patchesBaseUrl = baseUrl;
patchesConfigTime = now;
saveDiskCache(baseUrl);
}
console.log('[Mirror] Manifest fetched successfully');
return response.data; return response.data;
} else {
throw new Error('Invalid API response structure');
} }
throw new Error('Invalid manifest structure');
} catch (error) { } catch (error) {
const isTimeout = error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED' || error.message.includes('timeout'); console.error('[NewAPI] Error fetching API:', error.message);
console.error(`[Mirror] Error fetching manifest from ${baseUrl}: ${error.message}${isTimeout ? ' (Cloudflare may be blocked)' : ''}`); if (apiCache) {
if (i < mirrors.length - 1) { console.log('[NewAPI] Using expired cache due to error');
console.log(`[Mirror] Trying next mirror...`); return apiCache;
} }
throw error;
} }
} }
// All mirrors failed — use cached manifest if available async function getLatestVersionFromNewAPI(branch = 'release') {
if (manifestCache) { try {
console.log('[Mirror] All mirrors failed, using expired cache'); const apiData = await fetchNewAPI();
return manifestCache; const osName = getOS();
}
throw new Error('All mirrors failed and no cached manifest available');
}
/**
* Parse manifest to get available patches for current platform
* Returns array of { from, to, key, size }
*/
function getPlatformPatches(manifest, branch = 'release') {
const os = getOS();
const arch = getArch(); const arch = getArch();
const prefix = `${os}/${arch}/${branch}/`;
const patches = [];
for (const [key, info] of Object.entries(manifest.files)) { let osKey = osName;
if (key.startsWith(prefix) && key.endsWith('.pwr')) { if (osName === 'darwin') {
const filename = key.slice(prefix.length, -4); // e.g., "0_to_11" osKey = 'mac';
const match = filename.match(/^(\d+)_to_(\d+)$/); }
if (match) {
patches.push({ const branchData = apiData.hytale[branch];
from: parseInt(match[1]), if (!branchData || !branchData[osKey]) {
to: parseInt(match[2]), throw new Error(`No data found for branch: ${branch}, OS: ${osKey}`);
key, }
size: info.size
const osData = branchData[osKey];
const versions = Object.keys(osData).filter(key => key.endsWith('.pwr'));
if (versions.length === 0) {
throw new Error(`No .pwr files found for ${osKey}`);
}
const versionNumbers = versions.map(v => {
const match = v.match(/v(\d+)/);
return match ? parseInt(match[1]) : 0;
}); });
}
}
}
return patches; const latestVersionNumber = Math.max(...versionNumbers);
} console.log(`[NewAPI] Latest version number: ${latestVersionNumber} for branch ${branch}`);
/** return `v${latestVersionNumber}`;
* Find optimal patch path using BFS with download size minimization } catch (error) {
* Returns array of { from, to, url, size, key } steps, or null if no path found console.error('[NewAPI] Error getting latest version:', error.message);
*/ throw error;
async function findOptimalPatchPath(currentBuild, targetBuild, patches) {
if (currentBuild >= targetBuild) return [];
const baseUrl = await getPatchesBaseUrl();
const edges = {};
for (const patch of patches) {
if (!edges[patch.from]) edges[patch.from] = [];
edges[patch.from].push(patch);
}
const queue = [{ build: currentBuild, path: [], totalSize: 0 }];
let bestPath = null;
let bestSize = Infinity;
while (queue.length > 0) {
const { build, path, totalSize } = queue.shift();
if (build === targetBuild) {
if (totalSize < bestSize) {
bestPath = path;
bestSize = totalSize;
}
continue;
}
if (totalSize >= bestSize) continue;
const nextEdges = edges[build] || [];
for (const edge of nextEdges) {
if (edge.to <= build || edge.to > targetBuild) continue;
if (path.some(p => p.to === edge.to)) continue;
queue.push({
build: edge.to,
path: [...path, {
from: edge.from,
to: edge.to,
url: `${baseUrl}/${edge.key}`,
size: edge.size,
key: edge.key
}],
totalSize: totalSize + edge.size
});
} }
} }
return bestPath; async function getPWRUrlFromNewAPI(branch = 'release', version = 'v8') {
try {
const apiData = await fetchNewAPI();
const osName = getOS();
const arch = getArch();
let osKey = osName;
if (osName === 'darwin') {
osKey = 'mac';
} }
/** let fileName;
* Get the optimal update plan from currentBuild to targetBuild if (osName === 'windows') {
* Returns { steps: [{from, to, url, size}], totalSize, isFullInstall } fileName = `${version}-windows-amd64.pwr`;
*/ } else if (osName === 'linux') {
async function getUpdatePlan(currentBuild, targetBuild, branch = 'release') { fileName = `${version}-linux-amd64.pwr`;
const manifest = await fetchMirrorManifest(); } else if (osName === 'darwin') {
const patches = getPlatformPatches(manifest, branch); fileName = `${version}-darwin-arm64.pwr`;
// Try optimal path
const steps = await findOptimalPatchPath(currentBuild, targetBuild, patches);
if (steps && steps.length > 0) {
const totalSize = steps.reduce((sum, s) => sum + s.size, 0);
console.log(`[Mirror] Update plan: ${steps.map(s => `${s.from}\u2192${s.to}`).join(' + ')} (${(totalSize / 1024 / 1024).toFixed(0)} MB)`);
return { steps, totalSize, isFullInstall: steps.length === 1 && steps[0].from === 0 };
} }
// Fallback: full install 0 -> target const branchData = apiData.hytale[branch];
const fullPatch = patches.find(p => p.from === 0 && p.to === targetBuild); if (!branchData || !branchData[osKey]) {
if (fullPatch) { throw new Error(`No data found for branch: ${branch}, OS: ${osKey}`);
const baseUrl = await getPatchesBaseUrl();
const step = {
from: 0,
to: targetBuild,
url: `${baseUrl}/${fullPatch.key}`,
size: fullPatch.size,
key: fullPatch.key
};
console.log(`[Mirror] Full install: 0\u2192${targetBuild} (${(fullPatch.size / 1024 / 1024).toFixed(0)} MB)`);
return { steps: [step], totalSize: fullPatch.size, isFullInstall: true };
} }
throw new Error(`No patch path found from build ${currentBuild} to ${targetBuild} for ${getOS()}/${getArch()}`); const osData = branchData[osKey];
const url = osData[fileName];
if (!url) {
throw new Error(`No URL found for ${fileName}`);
}
console.log(`[NewAPI] URL for ${fileName}: ${url}`);
return url;
} catch (error) {
console.error('[NewAPI] Error getting PWR URL:', error.message);
throw error;
}
} }
async function getLatestClientVersion(branch = 'release') { async function getLatestClientVersion(branch = 'release') {
try { try {
console.log(`[Mirror] Fetching latest client version (branch: ${branch})...`); console.log(`[NewAPI] Fetching latest client version from new API (branch: ${branch})...`);
const manifest = await fetchMirrorManifest();
const patches = getPlatformPatches(manifest, branch);
if (patches.length === 0) { // Utiliser la nouvelle API
console.log(`[Mirror] No patches for branch '${branch}', using fallback`); const latestVersion = await getLatestVersionFromNewAPI(branch);
return `v${FALLBACK_LATEST_BUILD}`; console.log(`[NewAPI] Latest client version for ${branch}: ${latestVersion}`);
} return latestVersion;
const latestBuild = Math.max(...patches.map(p => p.to));
console.log(`[Mirror] Latest client version: v${latestBuild}`);
return `v${latestBuild}`;
} catch (error) { } catch (error) {
console.error('[Mirror] Error:', error.message); console.error('[NewAPI] Error fetching client version from new API:', error.message);
return `v${FALLBACK_LATEST_BUILD}`; console.log('[NewAPI] Falling back to old API...');
}
}
/**
* Get PWR download URL for fresh install (0 -> target)
* Backward-compatible with old getPWRUrlFromNewAPI signature
* Checks mirror first, then constructs URL for the branch
*/
async function getPWRUrl(branch = 'release', version = 'v11') {
const targetBuild = extractVersionNumber(version);
const os = getOS();
const arch = getArch();
// Fallback vers l'ancienne API si la nouvelle échoue
try { try {
const manifest = await fetchMirrorManifest(); const response = await smartRequest(`https://files.hytalef2p.com/api/version_client?branch=${branch}`, {
const patches = getPlatformPatches(manifest, branch); timeout: 40000,
const fullPatch = patches.find(p => p.from === 0 && p.to === targetBuild); headers: {
'User-Agent': 'Hytale-F2P-Launcher'
if (fullPatch) {
const baseUrl = await getPatchesBaseUrl();
const url = `${baseUrl}/${fullPatch.key}`;
console.log(`[Mirror] PWR URL: ${url}`);
return url;
} }
});
if (patches.length > 0) { if (response.data && response.data.client_version) {
// Branch exists in mirror but no full patch for this target - construct URL const version = response.data.client_version;
console.log(`[Mirror] No 0->${targetBuild} patch found, constructing URL`); console.log(`Latest client version for ${branch} (old API): ${version}`);
return version;
} else { } else {
console.log(`[Mirror] Branch '${branch}' not in mirror, constructing URL`); console.log('Warning: Invalid API response, falling back to latest known version (v8)');
return 'v8';
}
} catch (fallbackError) {
console.error('Error fetching client version from old API:', fallbackError.message);
console.log('Warning: Both APIs unavailable, falling back to latest known version (v8)');
return 'v8';
}
} }
} catch (error) {
console.error('[Mirror] Error getting PWR URL:', error.message);
} }
// Construct mirror URL (will work if patch was uploaded but manifest is stale) // Fonction utilitaire pour extraire le numéro de version
const baseUrl = await getPatchesBaseUrl(); // Supporte les formats: "7.pwr", "v8", "v8-windows-amd64.pwr", etc.
return `${baseUrl}/${os}/${arch}/${branch}/0_to_${targetBuild}.pwr`;
}
// Backward-compatible alias
const getPWRUrlFromNewAPI = getPWRUrl;
// Utility function to extract version number
// Supports: "7.pwr", "v8", "v8-windows-amd64.pwr", "5_to_10", etc.
function extractVersionNumber(version) { function extractVersionNumber(version) {
if (!version) return 0; if (!version) return 0;
// New format: "v8" or "v8-xxx.pwr" // Nouveau format: "v8" ou "v8-xxx.pwr"
const vMatch = version.match(/v(\d+)/); const vMatch = version.match(/v(\d+)/);
if (vMatch) return parseInt(vMatch[1]); if (vMatch) {
return parseInt(vMatch[1]);
}
// Old format: "7.pwr" // Ancien format: "7.pwr"
const pwrMatch = version.match(/(\d+)\.pwr/); const pwrMatch = version.match(/(\d+)\.pwr/);
if (pwrMatch) return parseInt(pwrMatch[1]); if (pwrMatch) {
return parseInt(pwrMatch[1]);
}
// Fallback // Fallback: essayer de parser directement
const num = parseInt(version); const num = parseInt(version);
return isNaN(num) ? 0 : num; return isNaN(num) ? 0 : num;
} }
async function buildArchiveUrl(buildNumber, branch = 'release') { function buildArchiveUrl(buildNumber, branch = 'release') {
const baseUrl = await getPatchesBaseUrl();
const os = getOS(); const os = getOS();
const arch = getArch(); const arch = getArch();
return `${baseUrl}/${os}/${arch}/${branch}/0_to_${buildNumber}.pwr`; return `${BASE_PATCH_URL}/${os}/${arch}/${branch}/0/${buildNumber}.pwr`;
} }
async function checkArchiveExists(buildNumber, branch = 'release') { async function checkArchiveExists(buildNumber, branch = 'release') {
const url = await buildArchiveUrl(buildNumber, branch); const url = buildArchiveUrl(buildNumber, branch);
try { try {
const response = await axios.head(url, { timeout: 10000 }); const response = await axios.head(url, { timeout: 10000 });
return response.status === 200; return response.status === 200;
} catch { } catch (error) {
return false; return false;
} }
} }
async function discoverAvailableVersions(latestKnown, branch = 'release') { async function discoverAvailableVersions(latestKnown, branch = 'release', maxProbe = 50) {
const available = [];
const latest = extractVersionNumber(latestKnown);
for (let i = latest; i >= Math.max(1, latest - maxProbe); i--) {
const exists = await checkArchiveExists(i, branch);
if (exists) {
available.push(`${i}.pwr`);
}
}
return available;
}
async function fetchPatchManifest(branch = 'release') {
try { try {
const manifest = await fetchMirrorManifest(); const os = getOS();
const patches = getPlatformPatches(manifest, branch); const arch = getArch();
const versions = [...new Set(patches.map(p => p.to))].sort((a, b) => b - a); const response = await smartRequest(`${MANIFEST_API}?branch=${branch}&os=${os}&arch=${arch}`, {
return versions.map(v => `${v}.pwr`); timeout: 10000
} catch { });
return []; return response.data.patches || {};
} catch (error) {
console.error('Failed to fetch patch manifest:', error.message);
return {};
} }
} }
async function extractVersionDetails(targetVersion, branch = 'release') { async function extractVersionDetails(targetVersion, branch = 'release') {
const buildNumber = extractVersionNumber(targetVersion); const buildNumber = extractVersionNumber(targetVersion);
const fullUrl = await buildArchiveUrl(buildNumber, branch); const previousBuild = buildNumber - 1;
const manifest = await fetchPatchManifest(branch);
const patchInfo = manifest[buildNumber];
return { return {
version: targetVersion, version: targetVersion,
buildNumber, buildNumber: buildNumber,
buildName: `HYTALE-Build-${buildNumber}`, buildName: `HYTALE-Build-${buildNumber}`,
fullUrl, fullUrl: patchInfo?.original_url || buildArchiveUrl(buildNumber, branch),
differentialUrl: null, differentialUrl: patchInfo?.patch_url || null,
checksum: null, checksum: patchInfo?.patch_hash || null,
sourceVersion: null, sourceVersion: patchInfo?.from ? `${patchInfo.from}.pwr` : (previousBuild > 0 ? `${previousBuild}.pwr` : null),
isDifferential: false, isDifferential: !!patchInfo?.proper_patch,
releaseNotes: null releaseNotes: patchInfo?.patch_note || null
}; };
} }
function canUseDifferentialUpdate() { function canUseDifferentialUpdate(currentVersion, targetDetails) {
// Differential updates are now handled via getUpdatePlan() if (!targetDetails) return false;
return false; if (!targetDetails.differentialUrl) return false;
if (!targetDetails.isDifferential) return false;
if (!currentVersion) return false;
const currentBuild = extractVersionNumber(currentVersion);
const expectedSource = extractVersionNumber(targetDetails.sourceVersion);
return currentBuild === expectedSource;
} }
function needsIntermediatePatches(currentVersion, targetVersion) { function needsIntermediatePatches(currentVersion, targetVersion) {
if (!currentVersion) return []; if (!currentVersion) return [];
const current = extractVersionNumber(currentVersion); const current = extractVersionNumber(currentVersion);
const target = extractVersionNumber(targetVersion); const target = extractVersionNumber(targetVersion);
if (current >= target) return [];
return [targetVersion]; const intermediates = [];
for (let i = current + 1; i <= target; i++) {
intermediates.push(`${i}.pwr`);
}
return intermediates;
} }
async function computeFileChecksum(filePath) { async function computeFileChecksum(filePath) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const hash = crypto.createHash('sha256'); const hash = crypto.createHash('sha256');
const stream = fs.createReadStream(filePath); const stream = fs.createReadStream(filePath);
stream.on('data', data => hash.update(data)); stream.on('data', data => hash.update(data));
stream.on('end', () => resolve(hash.digest('hex'))); stream.on('end', () => resolve(hash.digest('hex')));
stream.on('error', reject); stream.on('error', reject);
@@ -503,6 +290,7 @@ async function computeFileChecksum(filePath) {
async function validateChecksum(filePath, expectedChecksum) { async function validateChecksum(filePath, expectedChecksum) {
if (!expectedChecksum) return true; if (!expectedChecksum) return true;
const actualChecksum = await computeFileChecksum(filePath); const actualChecksum = await computeFileChecksum(filePath);
return actualChecksum === expectedChecksum; return actualChecksum === expectedChecksum;
} }
@@ -511,7 +299,7 @@ function getInstalledClientVersion() {
try { try {
const { loadVersionClient } = require('../core/config'); const { loadVersionClient } = require('../core/config');
return loadVersionClient(); return loadVersionClient();
} catch { } catch (err) {
return null; return null;
} }
} }
@@ -527,13 +315,8 @@ module.exports = {
computeFileChecksum, computeFileChecksum,
validateChecksum, validateChecksum,
getInstalledClientVersion, getInstalledClientVersion,
fetchMirrorManifest, fetchNewAPI,
getPWRUrl, getLatestVersionFromNewAPI,
getPWRUrlFromNewAPI, getPWRUrlFromNewAPI,
getUpdatePlan, extractVersionNumber
extractVersionNumber,
getPlatformPatches,
findOptimalPatchPath,
getPatchesBaseUrl,
getAllMirrorUrls
}; };

View File

@@ -9,9 +9,7 @@ const MAX_DOMAIN_LENGTH = 16;
// DualAuth ByteBuddy Agent (runtime class transformation, no JAR modification) // DualAuth ByteBuddy Agent (runtime class transformation, no JAR modification)
const DUALAUTH_AGENT_URL = 'https://github.com/sanasol/hytale-auth-server/releases/latest/download/dualauth-agent.jar'; const DUALAUTH_AGENT_URL = 'https://github.com/sanasol/hytale-auth-server/releases/latest/download/dualauth-agent.jar';
const DUALAUTH_AGENT_VERSION_API = 'https://api.github.com/repos/sanasol/hytale-auth-server/releases/latest';
const DUALAUTH_AGENT_FILENAME = 'dualauth-agent.jar'; const DUALAUTH_AGENT_FILENAME = 'dualauth-agent.jar';
const DUALAUTH_AGENT_VERSION_FILE = 'dualauth-agent.version';
function getTargetDomain() { function getTargetDomain() {
if (process.env.HYTALE_AUTH_DOMAIN) { if (process.env.HYTALE_AUTH_DOMAIN) {
@@ -513,70 +511,30 @@ class ClientPatcher {
*/ */
async ensureAgentAvailable(serverDir, progressCallback) { async ensureAgentAvailable(serverDir, progressCallback) {
const agentPath = this.getAgentPath(serverDir); const agentPath = this.getAgentPath(serverDir);
const versionPath = path.join(serverDir, DUALAUTH_AGENT_VERSION_FILE);
console.log('=== DualAuth Agent (ByteBuddy) ==='); console.log('=== DualAuth Agent (ByteBuddy) ===');
console.log(`Target: ${agentPath}`); console.log(`Target: ${agentPath}`);
// Check local version and whether file exists // Check if agent already exists and is valid
let localVersion = null;
let agentExists = false;
if (fs.existsSync(agentPath)) { if (fs.existsSync(agentPath)) {
try { try {
const stats = fs.statSync(agentPath); const stats = fs.statSync(agentPath);
if (stats.size > 1024) { if (stats.size > 1024) {
agentExists = true; console.log(`DualAuth Agent present (${(stats.size / 1024).toFixed(0)} KB)`);
if (fs.existsSync(versionPath)) { if (progressCallback) progressCallback('DualAuth Agent ready', 100);
localVersion = fs.readFileSync(versionPath, 'utf8').trim(); return { success: true, agentPath, alreadyExists: true };
} }
} else { // File exists but too small - corrupt, re-download
console.log('Agent file appears corrupt, re-downloading...'); console.log('Agent file appears corrupt, re-downloading...');
fs.unlinkSync(agentPath); fs.unlinkSync(agentPath);
}
} catch (e) { } catch (e) {
console.warn('Could not check agent file:', e.message); console.warn('Could not check agent file:', e.message);
} }
} }
// Check for updates from GitHub
let remoteVersion = null;
let needsDownload = !agentExists;
if (agentExists) {
try {
if (progressCallback) progressCallback('Checking for agent updates...', 5);
const axios = require('axios');
const resp = await axios.get(DUALAUTH_AGENT_VERSION_API, {
timeout: 5000,
headers: { 'Accept': 'application/vnd.github.v3+json' }
});
remoteVersion = resp.data.tag_name; // e.g. "v1.1.10"
if (localVersion && localVersion === remoteVersion) {
console.log(`DualAuth Agent up to date (${localVersion})`);
if (progressCallback) progressCallback('DualAuth Agent ready', 100);
return { success: true, agentPath, alreadyExists: true, version: localVersion };
}
console.log(`Agent update available: ${localVersion || 'unknown'}${remoteVersion}`);
needsDownload = true;
} catch (e) {
// GitHub API failed - use existing agent if available
console.warn(`Could not check for updates: ${e.message}`);
if (agentExists) {
console.log(`Using existing agent (${localVersion || 'unknown version'})`);
if (progressCallback) progressCallback('DualAuth Agent ready', 100);
return { success: true, agentPath, alreadyExists: true, version: localVersion };
}
}
}
if (!needsDownload) {
if (progressCallback) progressCallback('DualAuth Agent ready', 100);
return { success: true, agentPath, alreadyExists: true, version: localVersion };
}
// Download agent from GitHub releases // Download agent from GitHub releases
const action = agentExists ? 'Updating' : 'Downloading'; if (progressCallback) progressCallback('Downloading DualAuth Agent...', 20);
if (progressCallback) progressCallback(`${action} DualAuth Agent...`, 20); console.log(`Downloading from: ${DUALAUTH_AGENT_URL}`);
console.log(`${action} from: ${DUALAUTH_AGENT_URL}`);
try { try {
// Ensure server directory exists // Ensure server directory exists
@@ -590,7 +548,7 @@ class ClientPatcher {
const stream = await smartDownloadStream(DUALAUTH_AGENT_URL, (chunk, downloadedBytes, total) => { const stream = await smartDownloadStream(DUALAUTH_AGENT_URL, (chunk, downloadedBytes, total) => {
if (progressCallback && total) { if (progressCallback && total) {
const percent = 20 + Math.floor((downloadedBytes / total) * 70); const percent = 20 + Math.floor((downloadedBytes / total) * 70);
progressCallback(`${action} agent... ${(downloadedBytes / 1024).toFixed(0)} KB`, percent); progressCallback(`Downloading agent... ${(downloadedBytes / 1024).toFixed(0)} KB`, percent);
} }
}); });
@@ -617,13 +575,9 @@ class ClientPatcher {
} }
fs.renameSync(tmpPath, agentPath); fs.renameSync(tmpPath, agentPath);
// Save version console.log(`DualAuth Agent downloaded (${(stats.size / 1024).toFixed(0)} KB)`);
const version = remoteVersion || 'unknown';
fs.writeFileSync(versionPath, version, 'utf8');
console.log(`DualAuth Agent ${agentExists ? 'updated' : 'downloaded'} (${(stats.size / 1024).toFixed(0)} KB, ${version})`);
if (progressCallback) progressCallback('DualAuth Agent ready', 100); if (progressCallback) progressCallback('DualAuth Agent ready', 100);
return { success: true, agentPath, updated: agentExists, version }; return { success: true, agentPath };
} catch (downloadError) { } catch (downloadError) {
console.error(`Failed to download DualAuth Agent: ${downloadError.message}`); console.error(`Failed to download DualAuth Agent: ${downloadError.message}`);
@@ -632,11 +586,6 @@ class ClientPatcher {
if (fs.existsSync(tmpPath)) { if (fs.existsSync(tmpPath)) {
try { fs.unlinkSync(tmpPath); } catch (e) { /* ignore */ } try { fs.unlinkSync(tmpPath); } catch (e) { /* ignore */ }
} }
// If we had an existing agent, still use it
if (agentExists) {
console.log('Using existing agent despite update failure');
return { success: true, agentPath, alreadyExists: true, version: localVersion };
}
return { success: false, error: downloadError.message }; return { success: false, error: downloadError.message };
} }
} }

View File

@@ -1,405 +0,0 @@
/**
* Localhost/Local Development Code Backup
*
* This file contains code removed from clientPatcher.js that was used for local development.
* To re-enable local dev mode, merge this code back into clientPatcher.js.
*
* Backed up: 2026-01-28
*/
// =============================================================================
// LOCAL PATCHER PATHS (was in ensurePatcherDownloaded method)
// =============================================================================
// Check for local patcher (for local development)
const localPatcherPaths = [
path.join(__dirname, '..', '..', '..', 'hytale-auth-server', 'patcher', 'DualAuthPatcher.java'),
path.join(__dirname, '..', '..', '..', '..', 'hytale-auth-server', 'patcher', 'DualAuthPatcher.java'),
'/Users/sanasol/code/pterodactyl-hytale/hytale-auth-server/patcher/DualAuthPatcher.java'
];
// Check if we should use local patcher (localhost domain = local dev)
const domain = getTargetDomain();
const isLocalDev = domain.startsWith('localhost') || domain.startsWith('127.0.0.1');
if (isLocalDev) {
for (const localPath of localPatcherPaths) {
if (fs.existsSync(localPath)) {
console.log(`Using local DualAuthPatcher: ${localPath}`);
// Always copy fresh for local dev to pick up changes
fs.copyFileSync(localPath, patcherJava);
// Delete compiled class to force recompile
const patcherClass = path.join(patcherDir, 'DualAuthPatcher.class');
if (fs.existsSync(patcherClass)) {
fs.unlinkSync(patcherClass);
}
return;
}
}
console.log('Local patcher not found, falling back to download...');
}
// =============================================================================
// LEGACY SERVER PATCHER (was patchServerLegacy method)
// =============================================================================
/**
* Legacy server patcher (simple domain replacement, no dual auth)
* Use patchServer() for full dual auth support
*/
async patchServerLegacy(serverPath, progressCallback) {
const newDomain = this.getNewDomain();
const strategy = this.getDomainStrategy(newDomain);
console.log('=== Legacy Server Patcher ===');
console.log(`Target: ${serverPath}`);
console.log(`Domain: ${newDomain} (${newDomain.length} chars)`);
if (!fs.existsSync(serverPath)) {
return { success: false, error: `Server JAR not found: ${serverPath}` };
}
if (progressCallback) progressCallback('Patching server...', 20);
console.log('Opening server JAR...');
const zip = new AdmZip(serverPath);
const entries = zip.getEntries();
let totalCount = 0;
const oldUtf8 = this.stringToUtf8(ORIGINAL_DOMAIN);
for (const entry of entries) {
const name = entry.entryName;
if (name.endsWith('.class') || name.endsWith('.properties') ||
name.endsWith('.json') || name.endsWith('.xml') || name.endsWith('.yml')) {
const data = entry.getData();
if (data.includes(oldUtf8)) {
const { buffer: patchedData, count } = this.findAndReplaceDomainUtf8(data, ORIGINAL_DOMAIN, strategy.mainDomain);
if (count > 0) {
zip.updateFile(entry.entryName, patchedData);
totalCount += count;
}
}
}
}
if (totalCount > 0) {
zip.writeZip(serverPath);
}
if (progressCallback) progressCallback('Complete', 100);
return { success: true, patchCount: totalCount };
}
// =============================================================================
// DUALAUTHPATCHER FLOW FOR NON-STANDARD DOMAINS (was in patchServer method)
// =============================================================================
// For non-standard domains, use DualAuthPatcher for proper bytecode patching
const isStandardDomain = newDomain === 'auth.sanasol.ws' || newDomain === 'sanasol.ws';
if (!isStandardDomain) {
console.log(`Non-standard domain "${newDomain}" - using DualAuthPatcher`);
// Find Java
const java = this.findJava();
if (!java) {
console.error('Java not found - cannot run DualAuthPatcher');
console.error('Please install Java or use the bundled JRE');
return { success: false, error: 'Java not found for DualAuthPatcher' };
}
console.log(` Using Java: ${java}`);
// Setup patcher directory
const patcherDir = path.join(path.dirname(serverPath), '.patcher');
const libDir = path.join(patcherDir, 'lib');
try {
// Download patcher and libraries
if (progressCallback) progressCallback('Downloading DualAuthPatcher...', 20);
await this.ensurePatcherDownloaded(patcherDir);
if (progressCallback) progressCallback('Downloading ASM libraries...', 30);
await this.ensureAsmLibraries(libDir);
// Compile patcher
if (progressCallback) progressCallback('Compiling patcher...', 40);
const compileResult = await this.compileDualAuthPatcher(java, patcherDir, libDir);
if (!compileResult.success) {
return { success: false, error: compileResult.error };
}
// Build classpath
const classpath = [
patcherDir,
path.join(libDir, 'asm-9.6.jar'),
path.join(libDir, 'asm-tree-9.6.jar'),
path.join(libDir, 'asm-util-9.6.jar')
].join(process.platform === 'win32' ? ';' : ':');
// Run DualAuthPatcher with custom domain
if (progressCallback) progressCallback('Running DualAuthPatcher...', 60);
console.log(` Running DualAuthPatcher with domain: ${newDomain}`);
const patchResult = await this.runDualAuthPatcher(java, classpath, serverPath, newDomain);
if (patchResult.success) {
// Mark as patched
fs.writeFileSync(patchFlagFile, JSON.stringify({
domain: newDomain,
patchedAt: new Date().toISOString(),
patcher: 'DualAuthPatcher',
output: patchResult.stdout
}, null, 2));
if (progressCallback) progressCallback('Server patching complete', 100);
console.log('=== Server Patching Complete (DualAuthPatcher) ===');
return { success: true, patchCount: 1 };
} else {
console.error('DualAuthPatcher failed:', patchResult.error);
return { success: false, error: patchResult.error };
}
} catch (err) {
console.error('Failed to run DualAuthPatcher:', err.message);
return { success: false, error: err.message };
}
}
// =============================================================================
// HELPER METHODS FOR DUALAUTHPATCHER (keep if re-enabling non-standard domains)
// =============================================================================
/**
* Find Java executable - uses bundled JRE first (same as game uses)
* Falls back to system Java if bundled not available
*/
findJava() {
// 1. Try bundled JRE first (comes with the game)
try {
const bundled = getBundledJavaPath(JRE_DIR);
if (bundled && fs.existsSync(bundled)) {
console.log(`Using bundled Java: ${bundled}`);
return bundled;
}
} catch (e) {
// Bundled not available
}
// 2. Try javaManager's getJavaExec (handles all fallbacks)
try {
const javaExec = getJavaExec(JRE_DIR);
if (javaExec && fs.existsSync(javaExec)) {
console.log(`Using Java from javaManager: ${javaExec}`);
return javaExec;
}
} catch (e) {
// Not available
}
// 3. Check JAVA_HOME
if (process.env.JAVA_HOME) {
const javaHome = process.env.JAVA_HOME;
const javaBin = path.join(javaHome, 'bin', process.platform === 'win32' ? 'java.exe' : 'java');
if (fs.existsSync(javaBin)) {
console.log(`Using Java from JAVA_HOME: ${javaBin}`);
return javaBin;
}
}
// 4. Try 'java' from PATH
try {
execSync('java -version 2>&1', { encoding: 'utf8' });
console.log('Using Java from PATH');
return 'java';
} catch (e) {
// Not in PATH
}
return null;
}
/**
* Get DualAuthPatcher - downloads from GitHub
*/
async ensurePatcherDownloaded(patcherDir) {
const patcherJava = path.join(patcherDir, 'DualAuthPatcher.java');
const patcherUrl = 'https://raw.githubusercontent.com/sanasol/hytale-auth-server/master/patcher/DualAuthPatcher.java';
if (!fs.existsSync(patcherDir)) {
fs.mkdirSync(patcherDir, { recursive: true });
}
if (!fs.existsSync(patcherJava)) {
console.log('Downloading DualAuthPatcher from hytale-auth-server...');
try {
const https = require('https');
await new Promise((resolve, reject) => {
const file = fs.createWriteStream(patcherJava);
https.get(patcherUrl, (response) => {
if (response.statusCode === 302 || response.statusCode === 301) {
https.get(response.headers.location, (redirectResponse) => {
redirectResponse.pipe(file);
file.on('finish', () => {
file.close();
resolve();
});
}).on('error', reject);
} else {
response.pipe(file);
file.on('finish', () => {
file.close();
resolve();
});
}
}).on('error', (err) => {
fs.unlink(patcherJava, () => {});
reject(err);
});
});
console.log(' Downloaded DualAuthPatcher.java');
} catch (e) {
console.error(` Failed to download DualAuthPatcher: ${e.message}`);
throw e;
}
}
}
/**
* Download ASM libraries if not present
*/
async ensureAsmLibraries(libDir) {
if (!fs.existsSync(libDir)) {
fs.mkdirSync(libDir, { recursive: true });
}
const libs = [
{ name: 'asm-9.6.jar', url: 'https://repo1.maven.org/maven2/org/ow2/asm/asm/9.6/asm-9.6.jar' },
{ name: 'asm-tree-9.6.jar', url: 'https://repo1.maven.org/maven2/org/ow2/asm/asm-tree/9.6/asm-tree-9.6.jar' },
{ name: 'asm-util-9.6.jar', url: 'https://repo1.maven.org/maven2/org/ow2/asm/asm-util/9.6/asm-util-9.6.jar' }
];
for (const lib of libs) {
const libPath = path.join(libDir, lib.name);
if (!fs.existsSync(libPath)) {
console.log(`Downloading ${lib.name}...`);
try {
const https = require('https');
await new Promise((resolve, reject) => {
const file = fs.createWriteStream(libPath);
https.get(lib.url, (response) => {
response.pipe(file);
file.on('finish', () => {
file.close();
resolve();
});
}).on('error', (err) => {
fs.unlink(libPath, () => {});
reject(err);
});
});
console.log(` Downloaded ${lib.name}`);
} catch (e) {
console.error(` Failed to download ${lib.name}: ${e.message}`);
throw e;
}
}
}
}
/**
* Compile DualAuthPatcher if needed
*/
async compileDualAuthPatcher(java, patcherDir, libDir) {
const patcherClass = path.join(patcherDir, 'DualAuthPatcher.class');
const patcherJava = path.join(patcherDir, 'DualAuthPatcher.java');
if (fs.existsSync(patcherClass)) {
const classTime = fs.statSync(patcherClass).mtime;
const javaTime = fs.statSync(patcherJava).mtime;
if (classTime > javaTime) {
console.log('DualAuthPatcher already compiled');
return { success: true };
}
}
console.log('Compiling DualAuthPatcher...');
const javac = java.replace(/java(\.exe)?$/, 'javac$1');
const classpath = [
path.join(libDir, 'asm-9.6.jar'),
path.join(libDir, 'asm-tree-9.6.jar'),
path.join(libDir, 'asm-util-9.6.jar')
].join(process.platform === 'win32' ? ';' : ':');
try {
const execOptions = {
stdio: 'pipe',
cwd: patcherDir,
env: { ...process.env }
};
if (process.platform === 'win32') {
const systemRoot = process.env.SystemRoot || 'C:\\WINDOWS';
const systemPath = `${systemRoot}\\system32;${systemRoot};${systemRoot}\\System32\\Wbem`;
execOptions.env.PATH = execOptions.env.PATH
? `${systemPath};${execOptions.env.PATH}`
: systemPath;
execOptions.shell = true;
}
execSync(`"${javac}" -cp "${classpath}" -d "${patcherDir}" "${patcherJava}"`, execOptions);
console.log(' Compilation successful');
return { success: true };
} catch (e) {
const error = `Failed to compile DualAuthPatcher: ${e.message}`;
console.error(error);
if (e.stderr) console.error(e.stderr.toString());
return { success: false, error };
}
}
/**
* Run DualAuthPatcher on the server JAR
*/
async runDualAuthPatcher(java, classpath, serverPath, domain) {
return new Promise((resolve) => {
const args = ['-cp', classpath, 'DualAuthPatcher', serverPath];
const env = { ...process.env, HYTALE_AUTH_DOMAIN: domain };
console.log(`Running: java ${args.join(' ')}`);
console.log(` HYTALE_AUTH_DOMAIN=${domain}`);
const proc = spawn(java, args, { env, stdio: ['pipe', 'pipe', 'pipe'] });
let stdout = '';
let stderr = '';
proc.stdout.on('data', (data) => {
const str = data.toString();
stdout += str;
console.log(str.trim());
});
proc.stderr.on('data', (data) => {
const str = data.toString();
stderr += str;
console.error(str.trim());
});
proc.on('close', (code) => {
if (code === 0) {
resolve({ success: true, stdout });
} else {
resolve({ success: false, error: `Patcher exited with code ${code}: ${stderr}` });
}
});
proc.on('error', (err) => {
resolve({ success: false, error: `Failed to run patcher: ${err.message}` });
});
});
}

View File

@@ -1,238 +0,0 @@
const fs = require('fs');
const path = require('path');
const os = require('os');
const zlib = require('zlib');
const logger = require('../logger');
const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB per file
/**
* Get the HytaleSaves directory (game client logs)
*/
function getHytaleSavesDir() {
const home = os.homedir();
if (process.platform === 'win32') {
return path.join(home, 'AppData', 'Local', 'HytaleSaves');
} else if (process.platform === 'darwin') {
return path.join(home, 'Library', 'Application Support', 'HytaleSaves');
} else {
return path.join(home, '.hytalesaves');
}
}
/**
* Read a log file, capping at MAX_FILE_SIZE (keeps tail/most recent lines)
*/
function readLogFile(filePath) {
try {
const stats = fs.statSync(filePath);
if (stats.size <= MAX_FILE_SIZE) {
return fs.readFileSync(filePath, 'utf8');
}
// Read only the last MAX_FILE_SIZE bytes
const fd = fs.openSync(filePath, 'r');
const buffer = Buffer.alloc(MAX_FILE_SIZE);
fs.readSync(fd, buffer, 0, MAX_FILE_SIZE, stats.size - MAX_FILE_SIZE);
fs.closeSync(fd);
const content = buffer.toString('utf8');
// Skip first partial line
const firstNewline = content.indexOf('\n');
const trimmed = firstNewline >= 0 ? content.substring(firstNewline + 1) : content;
return `[... truncated ${stats.size - MAX_FILE_SIZE} bytes ...]\n` + trimmed;
} catch (err) {
return `[Error reading file: ${err.message}]`;
}
}
/**
* Get files matching a date pattern from a directory
*/
function getFilesForDate(dir, dateStr, pattern) {
if (!fs.existsSync(dir)) return [];
try {
return fs.readdirSync(dir)
.filter(f => f.includes(dateStr) && (pattern ? pattern.test(f) : true))
.map(f => ({
name: f,
path: path.join(dir, f),
mtime: fs.statSync(path.join(dir, f)).mtime
}))
.sort((a, b) => b.mtime - a.mtime);
} catch (err) {
return [];
}
}
// ============================================================================
// ZIP BUILDER (pure Node.js, no dependencies)
// ============================================================================
/**
* CRC32 lookup table
*/
const crc32Table = new Uint32Array(256);
for (let i = 0; i < 256; i++) {
let c = i;
for (let j = 0; j < 8; j++) {
c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
}
crc32Table[i] = c;
}
function crc32(buf) {
let crc = 0xFFFFFFFF;
for (let i = 0; i < buf.length; i++) {
crc = crc32Table[(crc ^ buf[i]) & 0xFF] ^ (crc >>> 8);
}
return (crc ^ 0xFFFFFFFF) >>> 0;
}
/**
* Create a ZIP file from an array of {name, content} entries
* Uses DEFLATE compression via built-in zlib
*/
function createZipBuffer(files) {
const localHeaders = [];
const centralEntries = [];
let offset = 0;
for (const file of files) {
const nameBytes = Buffer.from(file.name, 'utf8');
const contentBytes = Buffer.from(file.content, 'utf8');
const compressed = zlib.deflateRawSync(contentBytes);
const crcVal = crc32(contentBytes);
// Local file header (30 bytes + filename)
const local = Buffer.alloc(30);
local.writeUInt32LE(0x04034b50, 0); // signature
local.writeUInt16LE(20, 4); // version needed
local.writeUInt16LE(0, 6); // flags
local.writeUInt16LE(8, 8); // compression: DEFLATE
local.writeUInt16LE(0, 10); // mod time
local.writeUInt16LE(0, 12); // mod date
local.writeUInt32LE(crcVal, 14); // crc32
local.writeUInt32LE(compressed.length, 18); // compressed size
local.writeUInt32LE(contentBytes.length, 22); // uncompressed size
local.writeUInt16LE(nameBytes.length, 26); // filename length
local.writeUInt16LE(0, 28); // extra field length
localHeaders.push(local, nameBytes, compressed);
// Central directory entry (46 bytes + filename)
const central = Buffer.alloc(46);
central.writeUInt32LE(0x02014b50, 0); // signature
central.writeUInt16LE(20, 4); // version made by
central.writeUInt16LE(20, 6); // version needed
central.writeUInt16LE(0, 8); // flags
central.writeUInt16LE(8, 10); // compression
central.writeUInt16LE(0, 12); // mod time
central.writeUInt16LE(0, 14); // mod date
central.writeUInt32LE(crcVal, 16); // crc32
central.writeUInt32LE(compressed.length, 20); // compressed size
central.writeUInt32LE(contentBytes.length, 24); // uncompressed size
central.writeUInt16LE(nameBytes.length, 28); // filename length
central.writeUInt16LE(0, 30); // extra field length
central.writeUInt16LE(0, 32); // comment length
central.writeUInt16LE(0, 34); // disk number
central.writeUInt16LE(0, 36); // internal attrs
central.writeUInt32LE(0, 38); // external attrs
central.writeUInt32LE(offset, 42); // local header offset
centralEntries.push(central, nameBytes);
offset += 30 + nameBytes.length + compressed.length;
}
const centralDirBuf = Buffer.concat(centralEntries);
// End of central directory (22 bytes)
const eocd = Buffer.alloc(22);
eocd.writeUInt32LE(0x06054b50, 0); // signature
eocd.writeUInt16LE(0, 4); // disk number
eocd.writeUInt16LE(0, 6); // cd disk number
eocd.writeUInt16LE(files.length, 8); // entries on disk
eocd.writeUInt16LE(files.length, 10); // total entries
eocd.writeUInt32LE(centralDirBuf.length, 12); // cd size
eocd.writeUInt32LE(offset, 16); // cd offset
eocd.writeUInt16LE(0, 20); // comment length
return Buffer.concat([...localHeaders, centralDirBuf, eocd]);
}
// ============================================================================
/**
* Collect all relevant logs for submission
* Returns { files: [{name, content}], meta: {username, platform, version} }
*/
function collectLogs() {
const files = [];
const today = new Date();
const todayStr = today.toISOString().split('T')[0]; // YYYY-MM-DD
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
const yesterdayStr = yesterday.toISOString().split('T')[0];
// 1. Launcher logs
const launcherLogDir = logger.getLogDirectory();
if (launcherLogDir && fs.existsSync(launcherLogDir)) {
// Today's launcher logs
const todayLogs = getFilesForDate(launcherLogDir, todayStr, /^launcher-.*\.log$/);
for (const f of todayLogs) {
files.push({ name: `launcher/${f.name}`, content: readLogFile(f.path) });
}
// Most recent from yesterday (just one)
const yesterdayLogs = getFilesForDate(launcherLogDir, yesterdayStr, /^launcher-.*\.log$/);
if (yesterdayLogs.length > 0) {
files.push({ name: `launcher/${yesterdayLogs[0].name}`, content: readLogFile(yesterdayLogs[0].path) });
}
}
// 2. Game client logs
const savesDir = getHytaleSavesDir();
const clientLogDir = path.join(savesDir, 'Logs');
if (fs.existsSync(clientLogDir)) {
const clientLogs = getFilesForDate(clientLogDir, todayStr, /client\.log$/);
for (const f of clientLogs) {
files.push({ name: `client/${f.name}`, content: readLogFile(f.path) });
}
}
// 3. Config snapshot
const appDir = logger.getAppDir ? logger.getAppDir() : logger.getInstallPath();
const configPath = path.join(appDir, 'config.json');
if (fs.existsSync(configPath)) {
try {
const configContent = fs.readFileSync(configPath, 'utf8');
files.push({ name: 'config.json', content: configContent });
} catch (err) {
files.push({ name: 'config.json', content: `[Error reading config: ${err.message}]` });
}
}
// Build metadata
const { app } = require('electron');
let username = 'unknown';
try {
const configFile = path.join(appDir, 'config.json');
if (fs.existsSync(configFile)) {
const cfg = JSON.parse(fs.readFileSync(configFile, 'utf8'));
username = cfg.username || cfg.playerName || 'unknown';
}
} catch (err) {}
return {
files,
meta: {
username,
platform: `${process.platform}-${process.arch}`,
version: app.getVersion()
}
};
}
module.exports = { collectLogs, createZipBuffer };

View File

@@ -164,14 +164,8 @@ function detectGpuLinux() {
const isAmd = lowerLine.includes('amd') || lowerLine.includes('radeon') || vendorId === '1002'; const isAmd = lowerLine.includes('amd') || lowerLine.includes('radeon') || vendorId === '1002';
const isIntel = lowerLine.includes('intel') || vendorId === '8086'; const isIntel = lowerLine.includes('intel') || vendorId === '8086';
// Intel Arc discrete GPU detection // Intel Arc detection
// Discrete Arc cards have specific model numbers: A310, A380, A580, A750, A770, B570, B580 const isIntelArc = isIntel && (lowerName.includes('arc') || lowerName.includes('a770') || lowerName.includes('a750') || lowerName.includes('a380'));
// Integrated "Intel Arc Graphics" (Meteor Lake, Lunar Lake, Arrow Lake) have NO model suffix
// and sit on bus 00 (slot 00:02.0) — these are iGPUs, not dGPUs
const pciSlot = line.split(' ')[0] || '';
const pciBus = parseInt(pciSlot.split(':')[0], 16) || 0;
const hasArcModelNumber = isIntel && /\b[ab]\d{3}\b/i.test(lowerName);
const isIntelArc = isIntel && (hasArcModelNumber || (lowerName.includes('arc') && pciBus > 0));
let vendor = 'unknown'; let vendor = 'unknown';
if (isNvidia) vendor = 'nvidia'; if (isNvidia) vendor = 'nvidia';

View File

@@ -1,2 +1,3 @@
provider: generic provider: github
url: https://git.sanhost.net/sanasol/hytale-f2p/releases/download/latest owner: amiayweb # Change to your own GitHub username
repo: Hytale-F2P

View File

@@ -1,732 +0,0 @@
# Hytale Client Binary Analysis
CSC_LINK="/Users/sanasol/Downloads/Certificates-hytale.p12" CSC_KEY_PASSWORD="YieocpBVP68Rih*" APPLE_ID="sanasol2008rs@gmail.com" APPLE_APP_SPECIFIC_PASSWORD="ihah-lbta-movj-iqni" APPLE_TEAM_ID="9WVL8YG95H" npm run build:mac
CSC_LINK="/Users/sanasol/Downloads/Certificates-hytale.p12" CSC_KEY_PASSWORD="YieocpBVP68Rih*" APPLE_ID="sanasol2008rs@gmail.com" APPLE_APP_SPECIFIC_PASSWORD="ihah-lbta-movj-iqni" APPLE_TEAM_ID="9WVL8YG95H" npx electron-builder --mac --arm64
password ihah-lbta-movj-iqni
team id 9WVL8YG95H
cert pass YieocpBVP68Rih*
## Overview
This document contains a comprehensive analysis of the HytaleClient binary, documenting all discovered URLs, API endpoints, service domains, patchable strings, and internal functionality.
**Binary Analyzed:** `HytaleClient` (macOS .NET AOT compiled)
**Analysis Date:** 2026-01-27
**String Encoding:** UTF-16LE (Windows .NET string format)
---
## Table of Contents
1. [Service URLs](#1-service-urls)
2. [API Endpoints](#2-api-endpoints)
3. [World Tools & Builder Tools](#3-world-tools--builder-tools)
4. [External Service URLs](#4-external-service-urls)
5. [Patchable Strings](#5-patchable-strings)
6. [Sentry Error Tracking](#6-sentry-error-tracking)
7. [Internal Class References](#7-internal-class-references)
8. [Binary Offsets Reference](#8-binary-offsets-reference)
9. [Implementation Notes](#9-implementation-notes)
---
## 1. Service URLs
### 1.1 Primary Hytale Services
The client connects to four main service subdomains:
| Service | URL Pattern | Purpose | Status |
|---------|-------------|---------|--------|
| **Sessions** | `https://sessions.{domain}` | Authentication, JWT tokens, session management | Implemented in auth-server |
| **Account Data** | `https://account-data.{domain}` | Player profiles, skins, account information | Implemented in auth-server |
| **Telemetry** | `https://telemetry.{domain}` | Analytics, error reporting, usage statistics | Implemented (accepts/discards) |
| **Tools** | `https://tools.{domain}` | Asset editor, prefab management, world tools | **Not implemented** |
### 1.2 URL Construction
The client constructs URLs by combining:
1. Protocol: `https://`
2. Subdomain: `sessions.`, `account-data.`, `telemetry.`, `tools.`
3. Base domain: `hytale.com`
**Example:** `https://` + `sessions.` + `hytale.com` = `https://sessions.hytale.com`
The client patcher replaces these components to redirect traffic to the F2P auth server.
### 1.3 F2P Domain Routing
For F2P mode, all subdomains route to a single endpoint:
- `sessions.{f2p_domain}``https://{f2p_domain}`
- `account-data.{f2p_domain}``https://{f2p_domain}`
- `telemetry.{f2p_domain}``https://{f2p_domain}`
- `tools.{f2p_domain}``https://{f2p_domain}`
---
## 2. API Endpoints
### 2.1 Session Management Endpoints
#### POST /game-session/new
Create a new game session.
**Request:**
```json
{
"clientVersion": "string",
"platform": "string"
}
```
**Response:**
```json
{
"token": "JWT token",
"refreshToken": "refresh token",
"expiresIn": 36000
}
```
#### POST /game-session/refresh
Refresh an existing session token.
**Request:**
```json
{
"refreshToken": "string"
}
```
**Response:**
```json
{
"token": "new JWT token",
"refreshToken": "new refresh token",
"expiresIn": 36000
}
```
#### POST /game-session/child
Create a child session (for server connections).
**Request:**
```json
{
"parentToken": "string",
"audience": "server identifier"
}
```
#### DELETE /game-session
Notify server of session end (player disconnect).
### 2.2 Server Join Endpoints
#### POST /server-join/auth-grant
Request authorization grant for connecting to a game server.
**Request:**
```json
{
"serverAddress": "string",
"serverPort": number
}
```
**Response:**
```json
{
"grant": "authorization grant string"
}
```
#### POST /server-join/auth-token
Exchange authorization grant for server-specific token with certificate binding.
**Request:**
```json
{
"grant": "authorization grant",
"clientCertHash": "SHA256 hash of client certificate"
}
```
**Response:**
```json
{
"token": "server-specific JWT with cnf claim"
}
```
### 2.3 Account Endpoints
#### GET /my-account/game-profile
Get the player's game profile.
**Response:**
```json
{
"uuid": "player UUID",
"username": "display name",
"createdAt": "timestamp"
}
```
#### POST /my-account/game-profile
Update the player's game profile.
**Request:**
```json
{
"username": "new display name"
}
```
#### GET /my-account/cosmetics
Get list of unlocked cosmetics for the player.
**Response:**
```json
{
"cosmetics": [
{
"id": "cosmetic_id",
"category": "category_name",
"unlockedAt": "timestamp"
}
]
}
```
#### POST /my-account/skin
Save player's skin/character customization preferences.
**Request:**
```json
{
"skinTone": "SkinTone_01",
"bodyType": "Default",
"parts": {
"haircut": "Haircut_ShortMessy.Blue",
"eyes": "Eyes_Default.Green",
"eyebrows": "Eyebrows_Default",
"face": "Face_Default"
}
}
```
### 2.4 JWKS Endpoint
#### GET /.well-known/jwks.json
Get JSON Web Key Set for JWT verification.
**Response:**
```json
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"x": "base64url-encoded-public-key",
"kid": "key-id",
"use": "sig",
"alg": "EdDSA"
}
]
}
```
### 2.5 Profile Lookup Endpoints
#### GET /profile/uuid/{uuid}
Lookup player profile by UUID.
#### GET /profile/username/{username}
Lookup player profile by username (server-scoped).
---
## 3. World Tools & Builder Tools
### 3.1 World Tools (worldtools.*)
These are in-game tools for world creation and editing in builder/creative mode.
| Tool | Command | Description |
|------|---------|-------------|
| **Change Model** | `worldtools.changeModel` | Change the model of an entity in the world |
| **Import Image** | `worldtools.importImage` | Import image files into the world as textures |
| **Import OBJ** | `worldtools.importObj` | Import 3D OBJ model files into the world |
| **Instance** | `worldtools.instance` | Manage world instances and copies |
| **Play Sound** | `worldtools.playSound` | Play sound effects in the world |
| **Prefab Editor** | `worldtools.prefabEditor` | Open the prefab editor interface |
| **Prefab List** | `worldtools.prefabList` | List and manage saved prefabs |
| **Spawn Entity** | `worldtools.spawnEntity` | Spawn entities at specified locations |
| **Spawn Particle** | `worldtools.spawnParticle` | Spawn particle effects |
| **Tint Chunk** | `worldtools.tintChunk` | Apply color tinting to world chunks |
### 3.2 Builder Tools (buildertools.*)
Additional tools for the asset editor and builder mode.
| Tool | Class | Description |
|------|-------|-------------|
| **Image Import** | `buildertools.imageimport.ImageImportPage` | UI page for importing images |
| **OBJ Import** | `buildertools.objimport.ObjImportPage` | UI page for importing OBJ models |
| **Prefab Editor** | `buildertools.prefabeditor.ui.PrefabEditorLoadSettings` | Prefab editor with load/save |
| **Prefab List** | `buildertools.prefablist.PrefabPage` | Prefab listing and management |
### 3.3 Machinima Tool
- **Purpose:** In-game cinematic/video recording tool
- **Access:** Available via hotbar slot
- **Message:** "Hotbar is full. Clear a slot to receive the Machinima tool."
### 3.4 Asset Editor
The client includes an asset editor with these features:
- `AssetEditorDownload` - Download assets from tools service
- `assetEditor.exportModal` - Export modal for assets
- `assetEditor.fileSaveState` - File save state management
- `assetEditor.property.tooltip` - Property tooltips
### 3.5 tools.hytale.com API Requirements
To fully support builder mode, the tools service would need:
```
POST /assets/upload
- Upload asset files (images, models, sounds)
- Returns asset ID/URL
GET /assets/{assetId}
- Download asset by ID
- Returns asset binary data
POST /prefabs/save
- Save prefab definition
- Returns prefab ID
GET /prefabs/{prefabId}
- Load prefab by ID
- Returns prefab JSON
GET /prefabs/list
- List user's saved prefabs
- Returns array of prefab metadata
DELETE /prefabs/{prefabId}
- Delete a prefab
```
**Note:** The game functions without tools.hytale.com - it's only needed for cloud-based asset sharing in builder mode.
---
## 4. External Service URLs
### 4.1 Hytale Official URLs
| URL | Purpose | Patchable |
|-----|---------|-----------|
| `https://store.hytale.com/?upgrade=` | In-game store for purchases | Yes |
| `https://hytale.com/help/joining-friends` | Help documentation | Yes |
| `https://discord.gg/hytale` | Official Discord invite | Yes |
### 4.2 Third-Party Service URLs
| URL | Purpose | Notes |
|-----|---------|-------|
| `https://blockbench.net/downloads` | Blockbench download page | 3D model editor |
| `https://blockbench.net/plugins/hytale_plugin` | Hytale Blockbench plugin | For asset creation |
| `https://docs.sentry.io/platforms/dotnet/*` | Sentry documentation | Error tracking docs |
| `https://aka.ms/*` | Microsoft .NET documentation | Runtime docs |
| `https://learn.microsoft.com/*` | Microsoft Learn | .NET API docs |
| `https://go.microsoft.com/*` | Microsoft redirects | Various docs |
### 4.3 Graphics/Rendering References
| URL | Purpose |
|-----|---------|
| `https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)` | GLSL interface blocks |
| `https://www.khronos.org/opengl/wiki/Sampler_(GLSL)` | GLSL texture samplers |
| `https://www.shadertoy.com/view/Xd23Dh` | Shader reference |
| `https://www.shadertoy.com/view/ltlSRj` | Shader reference |
| `https://aras-p.info/texts/CompactNormalStorage.html` | Normal map compression |
| `https://mynameismjp.wordpress.com/2009/03/10/reconstructing-position-from-depth/` | Depth reconstruction |
| `https://briansharpe.files.wordpress.com/2018/07/moment-transparency-supp-av.pdf` | Transparency rendering |
| `https://www.pmavridis.com/research/fbcompression/` | Frame buffer compression |
| `https://jcgt.org/published/0002/02/09/` | Graphics technique |
| `https://jcgt.org/published/0006/01/03/` | Graphics technique |
| `https://graphics.cs.williams.edu/papers/CSSM/` | Graphics paper |
| `http://www.humus.name/Articles/Persson_LowLevelThinking.pdf` | Low-level graphics |
### 4.4 GitHub References
| URL | Purpose |
|-----|---------|
| `https://github.com/NLog/NLog.git` | Logging framework |
| `https://github.com/Noesis/Managed/tree/master/Src/Noesis/Core` | NoesisGUI core |
| `https://github.com/Noesis/Managed/tree/master/Src/NoesisApp/Core` | NoesisGUI app |
| `https://github.com/dotnet/dotnet` | .NET runtime |
| `https://github.com/ektrah/nsec.git` | NSec cryptography |
| `https://github.com/getsentry/sentry-dotnet` | Sentry .NET SDK |
---
## 5. Patchable Strings
### 5.1 Domain Strings
| Original | Replacement | Purpose |
|----------|-------------|---------|
| `hytale.com` | `{f2p_domain}` | Base domain (4-16 chars) |
| `sessions.` | Stripped or replaced | Session service subdomain |
| `account-data.` | Stripped or replaced | Account service subdomain |
| `telemetry.` | Stripped or replaced | Telemetry subdomain |
| `tools.` | Stripped or replaced | Tools service subdomain |
### 5.2 URL Strings
| Original | Can Replace With | Notes |
|----------|------------------|-------|
| `https://store.hytale.com/?upgrade=` | Custom store URL | In-game purchases |
| `https://discord.gg/hytale` | Custom Discord | Community link |
| `https://hytale.com/help/joining-friends` | Custom help docs | Help system |
| `sentry.hytale.com` | Own Sentry or disable | Error tracking |
### 5.3 String Encoding Details
**.NET UTF-16LE Format:**
- Each character is 2 bytes (little-endian)
- Example: "hytale" = `68 00 79 00 74 00 61 00 6c 00 65 00`
- Strings are length-prefixed in the binary
**Length Prefix Format:**
- 1 byte for strings < 128 chars
- 2 bytes (varint) for longer strings
- Followed by UTF-16LE character data
### 5.4 Current Patcher Behavior
The `clientPatcher.js` patches:
1. `sessions.hytale.com``{f2p_domain}` (single endpoint)
2. `account-data.hytale.com``{f2p_domain}`
3. `telemetry.hytale.com``{f2p_domain}`
**Not currently patched:**
- `tools.hytale.com` (builder mode assets)
- `store.hytale.com` (in-game store)
- `sentry.hytale.com` (error tracking)
---
## 6. Sentry Error Tracking
### 6.1 Sentry Configuration
**DSN Found in Binary:**
```
https://ca900df42fcf57d4dd8401a86ddd7da2@sentry.hytale.com/
```
**DSN Components:**
- Protocol: `https`
- Public Key: `ca900df42fcf57d4dd8401a86ddd7da2`
- Host: `sentry.hytale.com`
- Project ID: (after trailing slash)
### 6.2 Sentry Integration
The client uses the official Sentry .NET SDK:
- Package: `sentry-dotnet`
- Documentation refs found in binary
### 6.3 Patching Options
**Option 1: Disable Sentry**
- Replace DSN with invalid string
- Errors won't be reported
**Option 2: Redirect to Own Sentry**
- Replace `sentry.hytale.com` with own Sentry host
- Requires same-length domain or binary patching
**Option 3: Leave As-Is**
- Errors still report to Hypixel
- May expose F2P server information
### 6.4 Sentry Environment Variables
Found configuration references:
- `SENTRY_DSN` - DSN override
- `SENTRY_ENVIRONMENT` - Environment name
- Docs: `https://docs.sentry.io/platforms/dotnet/configuration/environments`
---
## 7. Internal Class References
### 7.1 Package Structure
```
com.hypixel.hytale/
├── builtin/
│ ├── buildertools/
│ │ ├── imageimport/
│ │ │ └── ImageImportPage
│ │ ├── objimport/
│ │ │ └── ObjImportPage
│ │ ├── prefabeditor/
│ │ │ └── ui/PrefabEditorLoadSettings
│ │ └── prefablist/
│ │ └── PrefabPage
│ ├── instances/
│ │ └── page/InstanceListPage
│ └── model/
│ └── pages/ChangeModelPage
├── server/
│ └── core/
│ └── asset/
│ └── type/
│ └── particle/
│ └── pages/ParticleSpawn*
└── Creation/
└── navigation/
├── buildertools/
└── worldtools/
├── changeModel
├── importImage
├── importObj
├── instance
├── playSound
├── prefabEditor
├── prefabList
├── spawnEntity
├── spawnParticle
└── tintChunk
```
### 7.2 UI Components
| Component | Path | Purpose |
|-----------|------|---------|
| GameLoading | `/GameLoading.u` | Loading screen |
| GamePageNavigation | `/GamePageNavigation.u` | Main navigation |
| ServerButton | `/ServerButton.u` | Server list button |
| ServerModal | `/ServerModal.u` | Server details modal |
| ServersPage | `/Servers/ServersPage.u` | Server browser |
| DirectConnectPopup | `/Servers/DirectConnectPopup.u` | Direct connect dialog |
| EditServerPopup | `/Servers/EditServerPopup.u` | Edit server dialog |
| JoinViaCodePopup | `/Servers/JoinViaCodePopup.u` | Join via code dialog |
| MinigamesPage | `/Minigames/MinigamesPage.u` | Minigames browser |
### 7.3 Configuration Files
| File | Purpose |
|------|---------|
| `/GameplayConfigs/Default.json` | Default gameplay settings |
| `hytale_plugin.js` | Blockbench plugin script |
| `hytale_assets` | Asset reference |
---
## 8. Binary Offsets Reference
### 8.1 URL String Offsets (macOS binary)
| Offset | Content | Length |
|--------|---------|--------|
| `0x1bf0098` | `https://account-data` | ~21 chars |
| `0x1bf00c9` | `https://aka.ms/dotnet-warnings/{0}` | ~35 chars |
| `0x1bf0114` | `https://blockbench.net/downloads` | ~33 chars |
| `0x1bf015d` | `https://blockbench.net/plugins/hytale_plugin` | ~45 chars |
| `0x1bf01bc` | `https://...@sentry.hytale.com/` | ~60 chars |
| `0x1bf023b` | `https://discord.gg/hytale` | ~26 chars |
| `0x1bf0274` | `https://hytale.com/help/joining-friends` | ~40 chars |
| `0x1bf02c9` | `https://sessions` | ~17 chars |
| `0x1bf02f2` | `https://store.hytale.com/?upgrade=` | ~35 chars |
| `0x1bf033d` | `https://telemetry` | ~18 chars |
| `0x1bf0368` | `https://tools` | ~14 chars |
### 8.2 API Endpoint Offsets
| Offset | Endpoint |
|--------|----------|
| `0x1b115d2` | `/game-session/child` |
| `0x1b115ff` | `/game-session/refresh` |
| `0x1b117c2` | `/server-join/auth-grant` |
| `0x1b117f7` | `/server-join/auth-token` |
| `0x1b11689` | `/my-account/cosmetics` |
| `0x1b116ba` | `/my-account/game-profile` |
| `0x1b116f1` | `/my-account/skin` |
| `0x1b10d8c` | `/.well-known/jwks.json` |
### 8.3 Notes on Offsets
- Offsets are for the macOS binary
- Windows/Linux binaries will have different offsets
- Offsets may change between game versions
- Always verify offsets before patching
---
## 9. Implementation Notes
### 9.1 Current Auth Server Implementation
The auth server (`hytale-auth-server`) currently implements:
**Fully Implemented:**
- [x] `/game-session/new` - Session creation
- [x] `/game-session/refresh` - Token refresh
- [x] `/server-join/auth-grant` - Auth grants
- [x] `/server-join/auth-token` - Token exchange with cert binding
- [x] `/my-account/cosmetics` - Cosmetic list
- [x] `/my-account/game-profile` - Profile get/update
- [x] `/my-account/skin` - Skin save
- [x] `/.well-known/jwks.json` - JWKS endpoint
- [x] `/profile/uuid/{uuid}` - UUID lookup
- [x] `/profile/username/{username}` - Username lookup
- [x] Telemetry endpoints (accept and discard)
**Not Implemented:**
- [ ] `tools.hytale.com` API (asset upload/download)
- [ ] Prefab cloud storage
- [ ] Asset sharing between players
### 9.2 Tools Service Implementation (Future)
If implementing tools.hytale.com functionality:
```javascript
// Suggested endpoints for auth-server
// Asset upload
app.post('/tools/assets/upload', async (req, res) => {
// Handle multipart file upload
// Store in local filesystem or S3
// Return asset ID
});
// Asset download
app.get('/tools/assets/:assetId', async (req, res) => {
// Retrieve asset by ID
// Stream file to client
});
// Prefab operations
app.post('/tools/prefabs', async (req, res) => {
// Save prefab JSON
// Associate with user
});
app.get('/tools/prefabs/:prefabId', async (req, res) => {
// Get prefab by ID
});
app.get('/tools/prefabs', async (req, res) => {
// List user's prefabs
});
app.delete('/tools/prefabs/:prefabId', async (req, res) => {
// Delete prefab
});
```
### 9.3 Patching Recommendations
**Essential (Already Done):**
1. Patch `sessions.hytale.com` → F2P domain
2. Patch `account-data.hytale.com` → F2P domain
3. Patch `telemetry.hytale.com` → F2P domain (or disable)
**Optional Enhancements:**
1. Patch `tools.hytale.com` → F2P domain (if implementing tools API)
2. Patch `sentry.hytale.com` → Own Sentry or disable
3. Patch `discord.gg/hytale` → Community Discord
4. Patch `store.hytale.com` → Custom store (if applicable)
**Not Recommended to Patch:**
- Blockbench URLs (useful for modding)
- Microsoft documentation URLs
- Graphics reference URLs
### 9.4 Security Considerations
1. **Sentry DSN Exposure**
- Current: Errors report to Hypixel's Sentry
- Risk: May expose F2P server details
- Recommendation: Disable or redirect
2. **Telemetry Data**
- Current: Accepted but discarded
- Alternative: Log for analytics
- Risk: Privacy concerns
3. **Asset Upload (if implemented)**
- Validate file types
- Limit file sizes
- Scan for malicious content
- Rate limit uploads
---
## Appendix A: String Extraction Commands
### Extract UTF-16LE Strings
```bash
python3 << 'EOF'
with open("HytaleClient", "rb") as f:
data = f.read()
pattern = b'h\x00t\x00t\x00p\x00s\x00:\x00/\x00/\x00'
idx = 0
while True:
idx = data.find(pattern, idx)
if idx == -1:
break
end = idx
chars = []
while end < len(data) - 1:
char = data[end] | (data[end+1] << 8)
if 0x20 <= char <= 0x7e:
chars.append(chr(char))
end += 2
else:
break
print(f"{hex(idx)}: {''.join(chars)}")
idx += 2
EOF
```
### Search for Specific Pattern
```bash
xxd HytaleClient | grep "h.y.t.a.l.e"
```
### Extract Context Around Offset
```bash
dd if=HytaleClient bs=1 skip=$((0x1bf0000)) count=2048 | xxd
```
---
## Appendix B: Version History
| Date | Changes |
|------|---------|
| 2026-01-27 | Initial analysis of macOS client binary |
---
## Appendix C: Related Documentation
- `CLAUDE.md` - Project overview and architecture
- `DUAL_AUTH_FLOW.md` - Dual authentication flow diagrams
- `STEAMDECK_CRASH_INVESTIGATION.md` - libzstd crash fix
- `PLAYER_PASSWORD_FEATURE.md` - Planned password authentication
- `backend/utils/clientPatcher.js` - Client patcher implementation

View File

@@ -1,113 +0,0 @@
# Singleplayer Server Crash: fastutil ClassNotFoundException
## Status: Open (user-specific, Feb 24 2026)
## Symptom
Singleplayer server crashes immediately after DualAuth Agent installs successfully:
```
Exception in thread "main" java.lang.NoClassDefFoundError: it/unimi/dsi/fastutil/objects/ObjectArrayList
at com.hypixel.hytale.plugin.early.EarlyPluginLoader.<clinit>(EarlyPluginLoader.java:34)
at com.hypixel.hytale.Main.main(Main.java:36)
Caused by: java.lang.ClassNotFoundException: it.unimi.dsi.fastutil.objects.ObjectArrayList
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)
```
Server exits with code 1. Multiplayer works fine for the same user.
## Affected User
- Discord: ヅ𝚃 JAYED !
- Platform: Windows (standard x86_64, NOT ARM)
- Reproduces 100% on singleplayer, every attempt
- Other users (including macOS/Linux) are NOT affected
## What Works
- Java wrapper correctly strips `-XX:+UseCompactObjectHeaders`
- Java wrapper correctly injects `--disable-sentry`
- DualAuth Agent v1.1.12 installs successfully (STATIC mode)
- Multiplayer connections work fine
- Repair and reinstall did NOT fix the issue
## Root Cause Analysis
`fastutil` (`it.unimi.dsi.fastutil`) should be bundled inside `HytaleServer.jar` (fat JAR). The `ClassNotFoundException` means the JVM's app classloader cannot find it despite it being in the JAR.
### Ruled Out
- **Wrapper issue**: Wrapper is working correctly (confirmed in logs)
- **UseCompactObjectHeaders**: Server also has `-XX:+IgnoreUnrecognizedVMOptions`, so unrecognized flags don't crash it
- **DualAuth Agent**: Works for all other users; agent installs successfully before the crash
- **Corrupted game files**: Repair/reinstall didn't help
- **ARM64/Parallels**: User is on standard Windows, not ARM
### Likely Causes (user-specific)
1. **Antivirus interference** — Windows Defender or third-party AV blocking Java from reading classes out of JAR files, especially with `-javaagent` active
2. **Corrupted/incompatible JRE** — bundled JRE might be broken on their system
3. **File locking** — another process holding HytaleServer.jar open
## Debugging Steps (ask user)
1. **Does official Hytale singleplayer work?** (without F2P launcher)
- Yes → something about our launch setup
- No → their system/JRE issue
2. **Check antivirus** — add game directory to Windows Defender exclusions:
- Settings → Windows Security → Virus & threat protection → Exclusions
- Add their HytaleF2P install folder
3. **Verify fastutil is in the JAR**:
```cmd
jar tf "D:\path\to\Server\HytaleServer.jar" | findstr fastutil
```
- If output shows fastutil classes → JAR is fine, classloader issue
- If no output → JAR is incomplete/corrupt (different from other users)
4. **Try without DualAuth agent** — rename `dualauth-agent.jar` in Server/ folder, retry singleplayer
- If works → agent's classloader manipulation breaks fastutil on their setup
- If still fails → unrelated to agent
5. **Check JRE version** — have them run:
```cmd
"D:\path\to\jre\latest\bin\java.exe" -version
```
## Update (Feb 24): `-XX:+UseCompactObjectHeaders` stripping removed from defaults
Stripping this flag did NOT fix the issue. The server already has `-XX:+IgnoreUnrecognizedVMOptions` so unrecognized flags are harmless. The flag was removed from default `stripFlags` in `backend/core/config.js`.
## Using the Java Wrapper to Strip JVM Flags
If a user needs to strip a specific JVM flag (e.g., for debugging or compatibility), they can do it via the launcher UI:
1. Open **Settings** → scroll to **Java Wrapper Configuration**
2. Under **JVM Flags to Remove**, type the flag (e.g. `-XX:+UseCompactObjectHeaders`) and click **Add**
3. The flag will be stripped from all JVM invocations at launch time
4. To inject custom arguments, use the **Arguments to Inject** section (with optional "Server Only" condition)
5. **Restore Defaults** resets to empty strip flags + `--disable-sentry` (server only)
The wrapper generates platform-specific scripts at launch time:
- **Windows**: `java-wrapper.bat` in `jre/latest/bin/`
- **macOS/Linux**: `java-wrapper` shell script in the same directory
Config is stored in `config.json` under `javaWrapperConfig`:
```json
{
"javaWrapperConfig": {
"stripFlags": ["-XX:+SomeFlag"],
"injectArgs": [
{ "arg": "--some-arg", "condition": "server" },
{ "arg": "--other-arg", "condition": "always" }
]
}
}
```
## Related
- Java wrapper config: `backend/core/config.js` (stripFlags / injectArgs)
- DualAuth Agent: v1.1.12, package `ws.sanasol.dualauth`
- Game version at time of report: `2026.02.19-1a311a592`

View File

@@ -1,48 +0,0 @@
# Client Crash: RAM Pressure on Low-End Hardware
## Status: Resolved (Feb 24, 2026)
## Symptom
Game launches but crashes with exit code 1 after ~32 seconds. Launcher logs show stale `java.exe(HytaleServer)` killed on every relaunch. Earlier sessions showed game running for ~4 minutes before `System.OutOfMemoryException`.
## Affected User
- Discord: KULVIN
- Hardware: Intel UHD 620 + NVIDIA MX150 (2GB VRAM), low-end laptop
- 22 mods installed
- Platform: Windows x64
## Root Cause
RAM pressure from background apps + 22 mods. The game's .NET client ran out of memory, and the embedded singleplayer server JVM (no `-Xmx` cap) competed for the same limited RAM.
## Timeline
1. Morning session: Game ran ~4 minutes, then `System.OutOfMemoryException` crashed client, server JVM crashed with `EXCEPTION_ACCESS_VIOLATION`
2. Evening sessions: Game started crashing in ~32 seconds (system still degraded from earlier OOM)
3. Server JVM orphaned every time (cleaned up by `killGameProcesses()` on next launch)
## Resolution
User fixed by:
1. Closing background applications to free RAM
2. Reinstalling the game
## Additional Issues Found
- `WeaponStatsViewer` mod left a directory (not a .jar) in `HytaleSaves\Mods\`, causing EPERM on every mod sync
- Stale AOT cache (`HytaleServer.aot`) cleaned up automatically by launcher
## Debugging Steps (for similar cases)
1. Check RAM usage in Task Manager before launching
2. Windows Event Viewer (Win+R → `eventvwr.msc` → Application) for crash module details
3. Try with all mods disabled
4. Reboot to clear degraded memory state
5. Close background apps (browsers, Discord, etc.)
## Recommendations
- Low-end hardware: reduce mod count (10 or fewer)
- Consider adding `-Xmx` cap to singleplayer server JVM to prevent unbounded memory growth

View File

@@ -1,138 +0,0 @@
# Update System Fixes Summary
## Overview
This document summarizes the fixes made to the launcher auto-update system to improve UX and fix macOS-specific issues.
## Issues Fixed
### 1. Duplicate Update Popups
**Problem:** Two different update UI files (`update.js` and `updater.js`) were both listening for update events, causing duplicate popups to appear.
**Solution:**
- Disabled `updater.js` in `index.html` (commented out the script tag)
- Now only `update.js` handles all update UI with improved features
### 2. Missing Skip Button
**Problem:** Users were soft-locked on the update screen with no way to dismiss it, especially problematic on macOS where auto-install often fails.
**Solution:**
- Added "Skip for now (not recommended)" button to update popup
- Skip button appears:
- After 30 seconds (timeout fallback)
- Immediately on any update error
- After install attempt fails (5 second timeout)
- Always visible once download completes
### 3. macOS Auto-Install Failure
**Problem:** `quitAndInstall()` silently fails on unsigned macOS apps, leaving users stuck.
**Solution:**
- Detect macOS in `main.js` and send `autoInstallSupported: false` flag
- On macOS, show "Download Manually (Recommended)" as primary action
- "Try Install & Restart" shown as secondary option
- Added force quit fallback in `install-update` handler for macOS
- Clear messaging: "Update downloaded but auto-install may not work on macOS"
### 4. Missing IPC Handlers
**Problem:** `open-download-page` IPC handler was not registered, causing errors when clicking manual download.
**Solution:**
- Added `open-download-page` handler in `main.js` that opens GitHub releases page
- Added `quitAndInstallUpdate` alias in `preload.js` for compatibility
### 5. Interface Blocking Not Removed
**Problem:** `unblockInterface()` method was called but never defined, leaving the UI blurred after closing popup.
**Solution:**
- Added complete `unblockInterface()` method that:
- Removes `interface-blocked` class from main content
- Removes `no-select` class from body
- Properly removes event listeners using stored bound function references
### 6. Breathing Animation on Downloaded State
**Problem:** The pulse/breathing animation continued after download completed, which felt inappropriate for a "ready to install" state.
**Solution:**
- Remove `update-popup-pulse` class in `showUpdateDownloaded()` method
### 7. Player Name Not Synced on First Install
**Problem:** Player name entered during installation wasn't synced to settings page input.
**Solution:**
- In `install.js`, sync player name to both `playerName` and `settingsPlayerName` inputs after installation completes
## Files Modified
### `GUI/index.html`
- Commented out `updater.js` script tag (duplicate update UI)
### `GUI/js/update.js`
- Removed legacy `onUpdatePopup` listener
- Added `closeUpdatePopup()` method
- Added `unblockInterface()` method
- Added skip button to popup HTML
- Added skip button visibility logic (30s timeout, on error, after download)
- Added macOS detection and alternative UI (manual download as primary)
- Removed pulse animation when download completes
- Added console logging for debugging
- Added extra DOM check to prevent duplicate popups
- Fixed manual download button to show "Opened in browser" and close popup
### `main.js`
- Changed `autoUpdater.autoDownload` from `false` to `true`
- Added macOS error handling with `requiresManualDownload` flag
- Added `autoInstallSupported` flag to `update-downloaded` event
- Added `open-download-page` IPC handler
- Enhanced `install-update` handler with macOS force quit fallback
### `preload.js`
- Added `quitAndInstallUpdate` alias for `install-update` IPC
### `GUI/js/install.js`
- Sync player name to `settingsPlayerName` input after installation
### `backend/utils/clientPatcher.js`
- Removed server patching code (server uses pre-patched JAR from CDN)
- Simplified to client-only patching
- Removed unused imports: `crypto`, `AdmZip`, `execSync`, `spawn`, `getJavaExec`, `getBundledJavaPath`, `JRE_DIR`
- Removed unused methods: `stringToUtf8()`, `findAndReplaceDomainUtf8()`
- Cleaned up comments and documentation
- Localhost/local dev code moved to `clientPatcher.localhost.js.bak` for reference
## Testing
To test the update popup manually, you can temporarily add this debug code to `update.js` init():
```javascript
// DEBUG: Simulate update available popup after 2 seconds
setTimeout(() => {
this.showUpdatePopup({
currentVersion: '2.0.0',
newVersion: '2.1.0',
releaseNotes: 'Debug test update'
});
// Simulate download complete after 3 more seconds
setTimeout(() => {
this.showUpdateDownloaded({
version: '2.1.0',
platform: 'darwin',
autoInstallSupported: false // Simulate macOS
});
}, 3000);
}, 2000);
```
## Platform-Specific Behavior
### Windows/Linux
- Auto-download enabled
- "Install & Restart" as primary action
- Skip button available as fallback
### macOS
- Auto-download enabled (download works, install doesn't)
- "Download Manually (Recommended)" as primary action
- "Try Install & Restart" as secondary option
- Skip button always visible after download
- Force quit fallback if quitAndInstall fails

View File

@@ -1,21 +0,0 @@
FROM node:20-alpine
# Install openssl for SSL certificate generation
RUN apk add --no-cache openssl
WORKDIR /app
# Copy package files from hytale-auth-server
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source files
COPY src ./src
COPY assets ./assets
EXPOSE 3000
# Use debug wrapper as entry point (will fall back to normal app.js if DEBUG_MODE!=true)
CMD ["node", "src/debug-wrapper.js"]

View File

@@ -1,252 +0,0 @@
# Hytale F2P Local Development Environment
Local development setup for researching and testing the Hytale client. Uses the **full hytale-auth-server** with a debug wrapper for capturing all requests.
## Quick Start
```bash
# 1. Start the local auth server
cd local-dev
chmod +x start.sh
./start.sh
# 2. In another terminal, start the launcher
cd ..
HYTALE_AUTH_DOMAIN=localhost:3000 npm start
```
That's it! The auth server will capture all requests for analysis.
## What's Included
### Full Auth Server with Debug Wrapper
- Uses the complete `hytale-auth-server` (not a minimal implementation)
- All endpoints work exactly as in production
- Debug wrapper captures ALL requests with full details
- Web-based debug dashboard at `/debug`
- Requests logged to `data/auth/debug-requests.jsonl`
- Unknown endpoints captured for research
### Services
- **Auth Server** (port 3000) - Full hytale-auth-server with debug
- **Kvrocks** (port 6666) - Redis-compatible storage for sessions
## Debug Dashboard
Access at `http://localhost:3000/debug`
Features:
- Real-time request viewer with auto-refresh
- Filter by subdomain (sessions, account-data, telemetry, tools)
- View full request/response details (headers, body, timing)
- Subdomain summary statistics
- Color-coded by request type and status
## Directory Structure
```
local-dev/
├── docker-compose.yml # Docker services config
├── Dockerfile.debug # Dockerfile using debug entry point
├── debug-wrapper.js # Debug wrapper for auth server
├── start.sh # Start script
├── README.md # This file
└── data/
├── auth/
│ ├── jwt_keys.json # Generated JWT keys
│ └── debug-requests.jsonl # Request log
└── kvrocks/ # Redis data
```
## Endpoints
### Debug Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/debug` | GET | Web-based debug dashboard |
| `/debug/requests` | GET | JSON list of captured requests |
| `/debug/requests?subdomain=X` | GET | Filter by subdomain |
| `/debug/subdomains` | GET | Request summary by subdomain |
| `/debug/requests` | DELETE | Clear captured requests |
### Auth Server Endpoints
All standard hytale-auth-server endpoints work:
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/health` | GET | Health check |
| `/.well-known/jwks.json` | GET | JWT public keys |
| `/game-session/new` | POST | Create session |
| `/game-session/refresh` | POST | Refresh token |
| `/game-session/child` | POST | Child session |
| `/game-session` | DELETE | End session |
| `/server-join/auth-grant` | POST | Auth grant |
| `/server-join/auth-token` | POST | Token exchange |
| `/my-account/game-profile` | GET/POST | Profile |
| `/my-account/cosmetics` | GET | Cosmetics |
| `/my-account/skin` | POST | Save skin |
| `/profile/uuid/:uuid` | GET | Profile lookup |
| `/profile/username/:username` | GET | Username lookup |
| `/avatar/:uuid` | GET | Avatar viewer |
| `/customizer/:uuid` | GET | Avatar customizer |
| `/admin` | GET | Admin dashboard (password: localdev) |
### Catch-All
Unknown endpoints return a debug response with tokens for research.
## Researching tools.hytale.com
The `tools.` subdomain is used for cloud-based asset management in builder/editor modes.
### How to Trigger Tools API
**Method 1: Builder Mode**
1. Start local dev environment
2. Launch game
3. Look for Creative/Builder mode in game menu
4. Use asset import/export features
5. Check debug dashboard for captured `tools.` requests
**Method 2: Server Configuration**
Builder mode may need server-side enablement:
- Server config for `builderToolsEnabled`
- Game mode settings
- Player permissions
**Method 3: Console Commands**
Try these worldtools commands in-game:
```
worldtools.changeModel
worldtools.importImage
worldtools.importObj
worldtools.prefabEditor
worldtools.prefabList
worldtools.spawnEntity
```
### Expected tools.hytale.com Endpoints
Based on binary analysis:
```
POST /assets/upload - Upload asset files
GET /assets/{id} - Download asset by ID
POST /prefabs - Save prefab
GET /prefabs/{id} - Load prefab
GET /prefabs - List prefabs
```
## Commands
### Start Services
```bash
./start.sh
```
### View Logs
```bash
docker compose logs -f auth-server
```
### Stop Services
```bash
docker compose down
```
### View Captured Requests (CLI)
```bash
# All requests
curl http://localhost:3000/debug/requests | jq
# Filter by subdomain
curl "http://localhost:3000/debug/requests?subdomain=tools" | jq
# Subdomain summary
curl http://localhost:3000/debug/subdomains | jq
# From log file
cat data/auth/debug-requests.jsonl | jq -s '.'
```
### Clear Request Log
```bash
curl -X DELETE http://localhost:3000/debug/requests
```
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `HYTALE_AUTH_DOMAIN` | - | Set to `localhost:3000` for local dev |
| `PORT` | `3000` | Auth server port |
| `DATA_DIR` | `/app/data` | Data directory |
| `DEBUG_MODE` | `true` | Enable debug features |
| `ADMIN_PASSWORD` | `localdev` | Admin dashboard password |
## Troubleshooting
### Client won't connect
1. Verify auth server is running: `curl http://localhost:3000/health`
2. Check environment variable: `echo $HYTALE_AUTH_DOMAIN`
3. Check Docker logs: `docker compose logs auth-server`
### JWT/Token errors
1. Check JWKS endpoint: `curl http://localhost:3000/.well-known/jwks.json`
2. Ensure `data/auth/jwt_keys.json` exists
3. Try restarting: `docker compose restart auth-server`
### Port 3000 in use
Change the port in docker-compose.yml:
```yaml
ports:
- "3001:3000"
```
Then use `HYTALE_AUTH_DOMAIN=localhost:3001`
### Server won't start
Check for build errors:
```bash
docker compose logs auth-server
docker compose build --no-cache auth-server
```
## Request Log Format
Each line in `debug-requests.jsonl` is a JSON object:
```json
{
"timestamp": "2026-01-27T12:00:00.000Z",
"method": "POST",
"path": "/game-session/new",
"host": "localhost:3000",
"subdomain": null,
"headers": {"content-type": "application/json", ...},
"query": {},
"body": {"uuid": "...", "name": "Player"},
"response": {
"statusCode": 200,
"duration": 15,
"body": {"identityToken": "...", ...}
}
}
```
## How It Works
1. **Docker Compose** starts Kvrocks (Redis) and the auth server
2. **Dockerfile.debug** builds the auth server with `debug-wrapper.js` as entry point
3. **debug-wrapper.js** wraps the original server:
- Intercepts all requests before they reach handlers
- Captures request details (headers, body, timing)
- Intercepts responses to capture response body
- Stores everything in memory and log file
- Adds `/debug/*` routes for the dashboard
4. When `DEBUG_MODE=true`:
- All requests are logged to console with colors
- Debug dashboard available at `/debug`
- Unknown endpoints return debug info with valid tokens
5. When `DEBUG_MODE=false`:
- Server runs normally (no debug overhead)
- Same as production auth server

View File

@@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDJTCCAg2gAwIBAgIUPhxbMZRuz3jUe6EzJf6yh9dazkcwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDEyNzIxMzU0OVoXDTI3MDEy
NzIxMzU0OVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAzkqaar2i/RFFkmXjcVC6G8Dvf9vYTMDJXq47qVmE0nDb
MsGSZCk4f5a9aG8BIEK/LN1csaGo3C7995R4+tqhEmFvRjHBsY2eJNeamHTEZsiw
UqWC98/Mq2EdT/mRVWks00XQAR0yobnPz7nphmxDv3LxddHfvYv1Va010UHO4SrW
1twNvM5UwCT2r5shpyzJAqF8U35RmaWSVzkEN7OMZbcg7aYsumdW2jSn539MKpAC
Eyr8pRXoDqGh4zp2lEuPryKtPyh2ljNmpYIQYaXQWlyjXDsHbs9U0sHFP2fw6k1X
sp4dUcPYzyt7WC6XfbUq2Ynz2no2sOJV1hruKY1niwIDAQABo28wbTAdBgNVHQ4E
FgQU/RBh6UbVrO9qkFVNmo+5AnM6mMgwHwYDVR0jBBgwFoAU/RBh6UbVrO9qkFVN
mo+5AnM6mMgwDwYDVR0TAQH/BAUwAwEB/zAaBgNVHREEEzARgglsb2NhbGhvc3SH
BH8AAAEwDQYJKoZIhvcNAQELBQADggEBAIr5gJ2gb079dF/tHeyqx6BxCg9+hsUg
38Fsxde0a6lEjSkntla+p20Bl2mdeWEeKFw5wc1mnMQkEJjixEhE5iA6gdswvpgf
07bYFRddCUYLrkQjWbo6u7UrkOeJfgZdHFTihMPguZ2tEPcMXitY7Ct24JRC3RRZ
BKCp88ix+ns61vAJfgdBZPbrtM0Ky2fIS+m9fWmKHvxag2/TOB67/avFzBkYXVgV
Xy79xxcICgIUELAQ8Hz0lXYfW/k+QnSAAtgLRxGh3eONaA/0Ij9sqzSnQzLM5fLd
omUKx+p2gzCpprSZGfV221ZtiAm6IvwKpQJMyM2YSbdCJfYAC0RjVyQ=
-----END CERTIFICATE-----

View File

@@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDOSppqvaL9EUWS
ZeNxULobwO9/29hMwMlerjupWYTScNsywZJkKTh/lr1obwEgQr8s3VyxoajcLv33
lHj62qESYW9GMcGxjZ4k15qYdMRmyLBSpYL3z8yrYR1P+ZFVaSzTRdABHTKhuc/P
uemGbEO/cvF10d+9i/VVrTXRQc7hKtbW3A28zlTAJPavmyGnLMkCoXxTflGZpZJX
OQQ3s4xltyDtpiy6Z1baNKfnf0wqkAITKvylFegOoaHjOnaUS4+vIq0/KHaWM2al
ghBhpdBaXKNcOwduz1TSwcU/Z/DqTVeynh1Rw9jPK3tYLpd9tSrZifPaejaw4lXW
Gu4pjWeLAgMBAAECggEAOXySsYItJGamw5g/HHnJkyhd1XyXNzRWKVtWZuf6WoZ2
nxtQRzcxdmS0XaDpaGsRSVhal2mcW9eAkHjAie9ZCX07fA0rk+YKFuw6OZf4j0gH
0tAqwhIXT/7dI0dB19JaWnnO8DCJxoW4QoPlbr9G1dgbL6EPv4t8D6cYIzs6goZk
LZcj5niASmkPgNoSi6yiBTcvOUEalJm3b+u5BMKL5jaodmo0pcBRa+w2jPdrFB9X
9s2ZdztLXdn2JVxS7N0IN9KNJzOhip/0FMiv9MAxQ+rSxsNgaZRKQ5iI8nzCRRv8
VDI6FW4+hUFIcieeEqzfORJRmtj2N99AzVZMOomkkQKBgQD3rkRiIB7Q5mFIoRRN
YWiTOlGT3o4tYRWBE/p6eFEBTxPlvbezKyZ5OdwsUVMVbWe2ZqAUjUgQQ53VtRZq
3VAQbsvsm08IbY9Zrj2zSqhfhm/pyT63fqWqdqHMmJfXCkL1tZJWaijyzmsUA5sO
j7zGJtxdb7a0DFD0WYhFQ9nbeQKBgQDVOHES0+UCQsq7XEmGhYBJNnVFtV8bCy8q
HuGBX1f+M+mE4DDuNo067EFKlEfiMTrMIKQT/3WNim6YpdCcMIrA/roCmG2cqjmQ
gkuEItxU1WGbO3VvK2eN9lG5uiWjg5P/5F4Y6U+DdBqGmNqvH3dvQP1AyEeqYnDv
YGkXFygWIwKBgQDUV0k1PwhsXDanR8HaHVrEbkkmFrWZ3hPLl880VBZOovcSDbaC
GspfP+Ws8QPj6OnzjMRNGlrf5rhYUWoosBhGHlciQHxfY150qlcncSgszVsA+ZGV
SzTIkfBhMalrqNaDROlywIzerW1LuVOkBkL3NrXSPUZL0gtNkbysdWE/MQKBgFLB
IUHJc+y4t66YVwEa93ty43k2t77rTFbwvV1U//XteAjWaqdKDO59m6mye2PS75si
YAxS7fENdXdRg/Ha9T+Kne878e8IMmdf6qdSUGmsl8GEBkQreHmkzHtlQA6ClwKO
Q+cvRmkiutjaoqWtdNF9S83E7eu3YVXG+YK4vho5AoGBANt5fp1In+BLcToV+MoK
gf6gNfwq6N3as5V4/JsB5Uem53dNE1E37rKNZqpeKHgBfrRi4FcfXNQFtNsk1Hty
tLLuNJtt6kTFKuurccCtxvexU7oItR7Jph6U7pWjKV9zcoReX817QZQeVjq2ZYAx
bGmSCyK9LMAI/TGYwu9G2DMA
-----END PRIVATE KEY-----

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +0,0 @@
{
"privateKey": "MC4CAQAwBQYDK2VwBCIEID/rEs35asZTitPu/73KBIoI4mb3dzH4QoBElz8IX29x",
"publicKey": "MCowBQYDK2VwAyEAg1HEi1XUdUZAYc+/9ioijFkkGdulbxT3eVpM+i8Mdr4=",
"createdAt": "2026-01-27T23:22:58.667Z"
}

View File

@@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIUHQdNViKmIqxQLoob3FV9wsVi7EYwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDEyNzIyMDA0MFoXDTI3MDEy
NzIyMDA0MFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAtt+6Fk7s1tunvn1rCB4HislzRl38OCHw8OS6LcJQgqat
qJMtfm/UQd2i97URmX6/RAimAEKAPd2njZXhtZ5pyGkURW9K99PhKx1yW7YPVnDs
bkyC3TvIFG6kBtUqwdawSHigUMKaOUMlDnHLlrobIoxGt1EplyPXRykLfzWO37Wo
3WNp/3tXcSvL7Osq/82f6jJhyTlo6UngUInMXpm3bq/k2ikEsIge7yJUqoLJwdCL
lIkm+Q0jI+p0YSiA+b17iLJBpvQHKkhLASKfK78lggZdcZJIc0J7Oqflzy3mG9e2
qqeSSCUKEpvvh95ep5kk5AkP0wYlgVO9bh+y9MEA9wIDAQABo1MwUTAdBgNVHQ4E
FgQUeK7lS9jHG9wkYTasyg14fNLo3bQwHwYDVR0jBBgwFoAUeK7lS9jHG9wkYTas
yg14fNLo3bQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAk4p1
wxBa1j156zE6ZAQAZT9axh+nuHjA6JyKPO82npOrjjMRDgkvvmvEjMQDL+YSwngD
RA5sJGwKiU9boaTYQEuM2uaqlNzBENv3GpWzkbfzKbqcgOOA/WFmXbogDjkknlee
/jgirTl1hM8Kih0avDjKLwRvSSAUZB2Z0tjpK1ZdLp7L8jIGzYdke0Qt4wDyucxS
C1BIPe8r12cSnNeP9krbdj7+P3YEWi9UgZWKAY8HbSjZSzXZvVlxcUO4ViSvF+lh
kGfAg22af46U0TKtZB2X++xO7aNrcRbqKNSEpibG510BN9ym4P/D1+QhFcz5guco
tvenNmwdgwY9viSnIw==
-----END CERTIFICATE-----

View File

@@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC237oWTuzW26e+
fWsIHgeKyXNGXfw4IfDw5LotwlCCpq2oky1+b9RB3aL3tRGZfr9ECKYAQoA93aeN
leG1nmnIaRRFb0r30+ErHXJbtg9WcOxuTILdO8gUbqQG1SrB1rBIeKBQwpo5QyUO
ccuWuhsijEa3USmXI9dHKQt/NY7ftajdY2n/e1dxK8vs6yr/zZ/qMmHJOWjpSeBQ
icxembdur+TaKQSwiB7vIlSqgsnB0IuUiSb5DSMj6nRhKID5vXuIskGm9AcqSEsB
Ip8rvyWCBl1xkkhzQns6p+XPLeYb17aqp5JIJQoSm++H3l6nmSTkCQ/TBiWBU71u
H7L0wQD3AgMBAAECggEANC/zjHM4jnY/0BI9jaL4NwiXP4EJvcEd37j+Upmt3SMQ
0tcxd/AU7DkCTVtcaufrUFCBgvh3XXBYZQTdVTWgbYNjOA9zHvdVvjaYkIiLpvjx
1+Y4wEbTqdULNTH3EjsgsoXOBk4KsgWx2QXrehehL8JFWgIs8sdVI6cYc1SG8dsK
ekWB14zwaA4bBMejpGtDV0q2/bgECq6R9vU9pMD46LXkjHli+xSnYdwrimMlckg/
VX94Tedi23mdEmxZBZ+m3fhzrSqs8C0JmTezGEO7awDl3NnAhK/t1cSfLz0o1YJv
1SOC/JSLET4rMDMHxRlUfhM5CI9NeSXS3n4HWKcBRQKBgQD3drS+sZ/pqUxjUbv+
LPUCLHar6ax0ge9bw3nS6LPRyp1uqmYiVRfruEWWZnCGIU+ST3zokZrroQA5jGPG
DB5PLsopqoE217G2514WEW8hZzW3395VtZqphA+o9SCmIA1logEYdN+ZDG6KQtiS
Ml1BdRTuyrUAC4QG4PNkb8xmSwKBgQC9LqTP7rU7p8RttN/f6q9kJCjsVrfj8rIs
fSPzYhVDHSNwJufwMjqLJxmNtrg0yIZfPfXKki8yX5ms28xh7d2je61rBBajI/lY
8cYsgP7bebruvClb4Rgyi9//bACWaEca/hQUte/8OG37wCSvotWMe0wZst1vLXrY
4i1Qo3YUhQKBgCIy3H2yDHh9NhpPtFxHGEEJpgjSDUw4nLygwhg8ooUurB0cHWtH
OTwRRkSnuYs/1UBSDeASJ0fDA0XwueUnzZSB0dx6PMs4ec3eIamJFUOXgNv9azuL
Emm033kpDy8GJPPTtLUNic0b47bl0Ao8PZkLNi5eAy+TZ9aPhfNuY+ALAoGBAILT
Y8QrlocHkK4xO/R0LhA5dVdi7M4lApQDgj4IIR4FY4FmVlTj11ptkp1Os3pFBa+N
gJEyoJLcS8CfS2qZfQFWQOnVrPXqpb+diucx2YzbVKtN0ego9HvYAPJ4tLtOp4WC
GU7tNmWcfGpxSK1xFO6OQWUwLTN6Jw8e8ilmsPylAoGBAK89GUz02qJrdbN8TMsI
vXBVpJX27wjFFwLHcsQ09CJ1Fppa8tZrqO/BtYTPhOncsmiOSjuFCnh6d3meEUHQ
BSYSjQWtJ+pilEMp2EiO55s4k/iFhKUeRsCjrTSGor21HqgRNaxsEfoieNWWWFv+
rcDAyW1WdJaydbVfXisAtzOL
-----END PRIVATE KEY-----

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1 +0,0 @@
MANIFEST-000035

View File

@@ -1 +0,0 @@
7a0fd32b-70d3-447e-90d3-74fc57ad6651

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,391 +0,0 @@
2026/01/27-22:00:38.559438 1 RocksDB version: 10.6.2
2026/01/27-22:00:38.559502 1 Git sha 0
2026/01/27-22:00:38.559503 1 Compile date 2025-11-08 14:59:16
2026/01/27-22:00:38.559504 1 DB SUMMARY
2026/01/27-22:00:38.559511 1 Host name (Env): 50e4104391a6
2026/01/27-22:00:38.559513 1 DB Session ID: Y90KK2H3WYG203E6436J
2026/01/27-22:00:38.559609 1 CURRENT file: CURRENT
2026/01/27-22:00:38.559610 1 IDENTITY file: IDENTITY
2026/01/27-22:00:38.559618 1 MANIFEST file: MANIFEST-000011 size: 751 Bytes
2026/01/27-22:00:38.559619 1 SST files in /data/db dir, Total Num: 0, files:
2026/01/27-22:00:38.559620 1 Write Ahead Log file in /data/db: 000010.log size: 1329 ;
2026/01/27-22:00:38.559621 1 Options.error_if_exists: 0
2026/01/27-22:00:38.559621 1 Options.create_if_missing: 1
2026/01/27-22:00:38.559622 1 Options.paranoid_checks: 1
2026/01/27-22:00:38.559623 1 Options.flush_verify_memtable_count: 1
2026/01/27-22:00:38.559623 1 Options.compaction_verify_record_count: 1
2026/01/27-22:00:38.559624 1 Options.track_and_verify_wals_in_manifest: 0
2026/01/27-22:00:38.559624 1 Options.track_and_verify_wals: 0
2026/01/27-22:00:38.559625 1 Options.verify_sst_unique_id_in_manifest: 1
2026/01/27-22:00:38.559625 1 Options.env: 0xffffa561d0c0
2026/01/27-22:00:38.559626 1 Options.fs: PosixFileSystem
2026/01/27-22:00:38.559627 1 Options.info_log: 0xffffa570f600
2026/01/27-22:00:38.559627 1 Options.max_file_opening_threads: 16
2026/01/27-22:00:38.559628 1 Options.statistics: 0xffffa56135b0
2026/01/27-22:00:38.559628 1 Options.statistics stats level: 3
2026/01/27-22:00:38.559629 1 Options.use_fsync: 0
2026/01/27-22:00:38.559629 1 Options.max_log_file_size: 268435456
2026/01/27-22:00:38.559630 1 Options.max_manifest_file_size: 67108864
2026/01/27-22:00:38.559630 1 Options.log_file_time_to_roll: 0
2026/01/27-22:00:38.559631 1 Options.keep_log_file_num: 12
2026/01/27-22:00:38.559631 1 Options.recycle_log_file_num: 0
2026/01/27-22:00:38.559632 1 Options.allow_fallocate: 1
2026/01/27-22:00:38.559632 1 Options.allow_mmap_reads: 0
2026/01/27-22:00:38.559633 1 Options.allow_mmap_writes: 0
2026/01/27-22:00:38.559633 1 Options.use_direct_reads: 0
2026/01/27-22:00:38.559634 1 Options.use_direct_io_for_flush_and_compaction: 0
2026/01/27-22:00:38.559634 1 Options.create_missing_column_families: 1
2026/01/27-22:00:38.559634 1 Options.db_log_dir:
2026/01/27-22:00:38.559635 1 Options.wal_dir:
2026/01/27-22:00:38.559636 1 Options.table_cache_numshardbits: 6
2026/01/27-22:00:38.559636 1 Options.WAL_ttl_seconds: 10800
2026/01/27-22:00:38.559637 1 Options.WAL_size_limit_MB: 16384
2026/01/27-22:00:38.559637 1 Options.max_write_batch_group_size_bytes: 1048576
2026/01/27-22:00:38.559638 1 Options.manifest_preallocation_size: 4194304
2026/01/27-22:00:38.559638 1 Options.is_fd_close_on_exec: 1
2026/01/27-22:00:38.559639 1 Options.advise_random_on_open: 1
2026/01/27-22:00:38.559639 1 Options.db_write_buffer_size: 0
2026/01/27-22:00:38.559640 1 Options.write_buffer_manager: 0xffffa566bfc0
2026/01/27-22:00:38.559640 1 Options.use_adaptive_mutex: 0
2026/01/27-22:00:38.559640 1 Options.rate_limiter: 0xffffa5636000
2026/01/27-22:00:38.559641 1 Options.sst_file_manager.rate_bytes_per_sec: 0
2026/01/27-22:00:38.559642 1 Options.wal_recovery_mode: 2
2026/01/27-22:00:38.559642 1 Options.enable_thread_tracking: 0
2026/01/27-22:00:38.559643 1 Options.enable_pipelined_write: 0
2026/01/27-22:00:38.559643 1 Options.unordered_write: 0
2026/01/27-22:00:38.559644 1 Options.allow_concurrent_memtable_write: 1
2026/01/27-22:00:38.559644 1 Options.enable_write_thread_adaptive_yield: 1
2026/01/27-22:00:38.559645 1 Options.write_thread_max_yield_usec: 100
2026/01/27-22:00:38.559645 1 Options.write_thread_slow_yield_usec: 3
2026/01/27-22:00:38.559646 1 Options.row_cache: None
2026/01/27-22:00:38.559646 1 Options.wal_filter: None
2026/01/27-22:00:38.559647 1 Options.avoid_flush_during_recovery: 0
2026/01/27-22:00:38.559647 1 Options.allow_ingest_behind: 0
2026/01/27-22:00:38.559648 1 Options.two_write_queues: 0
2026/01/27-22:00:38.559648 1 Options.manual_wal_flush: 0
2026/01/27-22:00:38.559648 1 Options.wal_compression: 0
2026/01/27-22:00:38.559649 1 Options.background_close_inactive_wals: 0
2026/01/27-22:00:38.559649 1 Options.atomic_flush: 0
2026/01/27-22:00:38.559650 1 Options.avoid_unnecessary_blocking_io: 1
2026/01/27-22:00:38.559650 1 Options.prefix_seek_opt_in_only: 0
2026/01/27-22:00:38.559651 1 Options.persist_stats_to_disk: 0
2026/01/27-22:00:38.559651 1 Options.write_dbid_to_manifest: 1
2026/01/27-22:00:38.559652 1 Options.write_identity_file: 1
2026/01/27-22:00:38.559652 1 Options.log_readahead_size: 0
2026/01/27-22:00:38.559653 1 Options.file_checksum_gen_factory: Unknown
2026/01/27-22:00:38.559653 1 Options.best_efforts_recovery: 0
2026/01/27-22:00:38.559654 1 Options.max_bgerror_resume_count: 2147483647
2026/01/27-22:00:38.559654 1 Options.bgerror_resume_retry_interval: 1000000
2026/01/27-22:00:38.559655 1 Options.allow_data_in_errors: 0
2026/01/27-22:00:38.559655 1 Options.db_host_id: __hostname__
2026/01/27-22:00:38.559656 1 Options.enforce_single_del_contracts: true
2026/01/27-22:00:38.559657 1 Options.metadata_write_temperature: kUnknown
2026/01/27-22:00:38.559657 1 Options.wal_write_temperature: kUnknown
2026/01/27-22:00:38.559658 1 Options.max_background_jobs: 4
2026/01/27-22:00:38.559658 1 Options.max_background_compactions: -1
2026/01/27-22:00:38.559659 1 Options.max_subcompactions: 2
2026/01/27-22:00:38.559659 1 Options.avoid_flush_during_shutdown: 0
2026/01/27-22:00:38.559660 1 Options.writable_file_max_buffer_size: 1048576
2026/01/27-22:00:38.559661 1 Options.delayed_write_rate : 536870912000
2026/01/27-22:00:38.559661 1 Options.max_total_wal_size: 536870912
2026/01/27-22:00:38.559662 1 Options.delete_obsolete_files_period_micros: 21600000000
2026/01/27-22:00:38.559663 1 Options.stats_dump_period_sec: 0
2026/01/27-22:00:38.559663 1 Options.stats_persist_period_sec: 600
2026/01/27-22:00:38.559664 1 Options.stats_history_buffer_size: 1048576
2026/01/27-22:00:38.559664 1 Options.max_open_files: 8096
2026/01/27-22:00:38.559665 1 Options.bytes_per_sync: 1048576
2026/01/27-22:00:38.559665 1 Options.wal_bytes_per_sync: 0
2026/01/27-22:00:38.559666 1 Options.strict_bytes_per_sync: 0
2026/01/27-22:00:38.559666 1 Options.compaction_readahead_size: 2097152
2026/01/27-22:00:38.559667 1 Options.max_background_flushes: -1
2026/01/27-22:00:38.559667 1 Options.daily_offpeak_time_utc:
2026/01/27-22:00:38.559668 1 Compression algorithms supported:
2026/01/27-22:00:38.559669 1 kCustomCompressionFE supported: 0
2026/01/27-22:00:38.559669 1 kCustomCompressionFC supported: 0
2026/01/27-22:00:38.559670 1 kCustomCompressionF8 supported: 0
2026/01/27-22:00:38.559670 1 kCustomCompressionF7 supported: 0
2026/01/27-22:00:38.559671 1 kCustomCompressionB2 supported: 0
2026/01/27-22:00:38.559671 1 kLZ4Compression supported: 1
2026/01/27-22:00:38.559672 1 kCustomCompression88 supported: 0
2026/01/27-22:00:38.559673 1 kCustomCompressionD8 supported: 0
2026/01/27-22:00:38.559673 1 kCustomCompression9F supported: 0
2026/01/27-22:00:38.559674 1 kCustomCompressionD6 supported: 0
2026/01/27-22:00:38.559674 1 kCustomCompressionA9 supported: 0
2026/01/27-22:00:38.559675 1 kCustomCompressionEC supported: 0
2026/01/27-22:00:38.559675 1 kCustomCompressionA3 supported: 0
2026/01/27-22:00:38.559676 1 kCustomCompressionCB supported: 0
2026/01/27-22:00:38.559676 1 kCustomCompression90 supported: 0
2026/01/27-22:00:38.559677 1 kCustomCompressionA0 supported: 0
2026/01/27-22:00:38.559677 1 kCustomCompressionC6 supported: 0
2026/01/27-22:00:38.559678 1 kCustomCompression9D supported: 0
2026/01/27-22:00:38.559678 1 kCustomCompression8B supported: 0
2026/01/27-22:00:38.559679 1 kCustomCompressionA8 supported: 0
2026/01/27-22:00:38.559679 1 kCustomCompression8D supported: 0
2026/01/27-22:00:38.559680 1 kCustomCompression97 supported: 0
2026/01/27-22:00:38.559680 1 kCustomCompression98 supported: 0
2026/01/27-22:00:38.559681 1 kCustomCompressionAC supported: 0
2026/01/27-22:00:38.559681 1 kCustomCompressionE9 supported: 0
2026/01/27-22:00:38.559682 1 kCustomCompression96 supported: 0
2026/01/27-22:00:38.559682 1 kCustomCompressionB1 supported: 0
2026/01/27-22:00:38.559683 1 kCustomCompression95 supported: 0
2026/01/27-22:00:38.559683 1 kCustomCompression84 supported: 0
2026/01/27-22:00:38.559684 1 kCustomCompression91 supported: 0
2026/01/27-22:00:38.559686 1 kCustomCompressionAB supported: 0
2026/01/27-22:00:38.559687 1 kCustomCompressionB3 supported: 0
2026/01/27-22:00:38.559687 1 kCustomCompression81 supported: 0
2026/01/27-22:00:38.559688 1 kCustomCompressionDC supported: 0
2026/01/27-22:00:38.559689 1 kBZip2Compression supported: 0
2026/01/27-22:00:38.559689 1 kCustomCompressionBB supported: 0
2026/01/27-22:00:38.559690 1 kCustomCompression9C supported: 0
2026/01/27-22:00:38.559690 1 kCustomCompressionC9 supported: 0
2026/01/27-22:00:38.559691 1 kCustomCompressionCC supported: 0
2026/01/27-22:00:38.559691 1 kCustomCompression92 supported: 0
2026/01/27-22:00:38.559692 1 kCustomCompressionB9 supported: 0
2026/01/27-22:00:38.559694 1 kCustomCompression8F supported: 0
2026/01/27-22:00:38.559695 1 kCustomCompression8A supported: 0
2026/01/27-22:00:38.559695 1 kCustomCompression9B supported: 0
2026/01/27-22:00:38.559696 1 kZSTD supported: 1
2026/01/27-22:00:38.559696 1 kCustomCompressionAA supported: 0
2026/01/27-22:00:38.559697 1 kCustomCompressionA2 supported: 0
2026/01/27-22:00:38.559697 1 kZlibCompression supported: 1
2026/01/27-22:00:38.559698 1 kXpressCompression supported: 0
2026/01/27-22:00:38.559699 1 kCustomCompressionFD supported: 0
2026/01/27-22:00:38.559699 1 kCustomCompressionE2 supported: 0
2026/01/27-22:00:38.559700 1 kLZ4HCCompression supported: 1
2026/01/27-22:00:38.559700 1 kCustomCompressionA6 supported: 0
2026/01/27-22:00:38.559701 1 kCustomCompression85 supported: 0
2026/01/27-22:00:38.559701 1 kCustomCompressionA4 supported: 0
2026/01/27-22:00:38.559702 1 kCustomCompression86 supported: 0
2026/01/27-22:00:38.559702 1 kCustomCompression83 supported: 0
2026/01/27-22:00:38.559703 1 kCustomCompression87 supported: 0
2026/01/27-22:00:38.559703 1 kCustomCompression89 supported: 0
2026/01/27-22:00:38.559704 1 kCustomCompression8C supported: 0
2026/01/27-22:00:38.559704 1 kCustomCompressionDB supported: 0
2026/01/27-22:00:38.559705 1 kCustomCompressionF3 supported: 0
2026/01/27-22:00:38.559705 1 kCustomCompressionE6 supported: 0
2026/01/27-22:00:38.559705 1 kCustomCompression8E supported: 0
2026/01/27-22:00:38.559706 1 kCustomCompressionDA supported: 0
2026/01/27-22:00:38.559706 1 kCustomCompression93 supported: 0
2026/01/27-22:00:38.559707 1 kCustomCompression94 supported: 0
2026/01/27-22:00:38.559709 1 kCustomCompression9E supported: 0
2026/01/27-22:00:38.559710 1 kCustomCompressionB4 supported: 0
2026/01/27-22:00:38.559710 1 kCustomCompressionFB supported: 0
2026/01/27-22:00:38.559711 1 kCustomCompressionB5 supported: 0
2026/01/27-22:00:38.559711 1 kCustomCompressionD5 supported: 0
2026/01/27-22:00:38.559712 1 kCustomCompressionB8 supported: 0
2026/01/27-22:00:38.559712 1 kCustomCompressionD1 supported: 0
2026/01/27-22:00:38.559713 1 kCustomCompressionBA supported: 0
2026/01/27-22:00:38.559713 1 kCustomCompressionBC supported: 0
2026/01/27-22:00:38.559714 1 kCustomCompressionCE supported: 0
2026/01/27-22:00:38.559714 1 kCustomCompressionBD supported: 0
2026/01/27-22:00:38.559715 1 kCustomCompressionC4 supported: 0
2026/01/27-22:00:38.559715 1 kCustomCompression9A supported: 0
2026/01/27-22:00:38.559716 1 kCustomCompression99 supported: 0
2026/01/27-22:00:38.559716 1 kCustomCompressionBE supported: 0
2026/01/27-22:00:38.559717 1 kCustomCompressionE5 supported: 0
2026/01/27-22:00:38.559717 1 kCustomCompressionD9 supported: 0
2026/01/27-22:00:38.559718 1 kCustomCompressionC1 supported: 0
2026/01/27-22:00:38.559718 1 kCustomCompressionC5 supported: 0
2026/01/27-22:00:38.559719 1 kCustomCompressionC2 supported: 0
2026/01/27-22:00:38.559719 1 kCustomCompressionA5 supported: 0
2026/01/27-22:00:38.559720 1 kCustomCompressionC7 supported: 0
2026/01/27-22:00:38.559720 1 kCustomCompressionBF supported: 0
2026/01/27-22:00:38.559721 1 kCustomCompressionE8 supported: 0
2026/01/27-22:00:38.559721 1 kCustomCompressionC8 supported: 0
2026/01/27-22:00:38.559722 1 kCustomCompressionAF supported: 0
2026/01/27-22:00:38.559722 1 kCustomCompressionCA supported: 0
2026/01/27-22:00:38.559723 1 kCustomCompressionCD supported: 0
2026/01/27-22:00:38.559723 1 kCustomCompressionC0 supported: 0
2026/01/27-22:00:38.559724 1 kCustomCompressionCF supported: 0
2026/01/27-22:00:38.559724 1 kCustomCompressionF9 supported: 0
2026/01/27-22:00:38.559724 1 kCustomCompressionD0 supported: 0
2026/01/27-22:00:38.559725 1 kCustomCompressionD2 supported: 0
2026/01/27-22:00:38.559726 1 kCustomCompressionAD supported: 0
2026/01/27-22:00:38.559726 1 kCustomCompressionD3 supported: 0
2026/01/27-22:00:38.559727 1 kCustomCompressionD4 supported: 0
2026/01/27-22:00:38.559727 1 kCustomCompressionD7 supported: 0
2026/01/27-22:00:38.559728 1 kCustomCompression82 supported: 0
2026/01/27-22:00:38.559728 1 kCustomCompressionDD supported: 0
2026/01/27-22:00:38.559729 1 kCustomCompressionC3 supported: 0
2026/01/27-22:00:38.559729 1 kCustomCompressionEE supported: 0
2026/01/27-22:00:38.559730 1 kCustomCompressionDE supported: 0
2026/01/27-22:00:38.559730 1 kCustomCompressionDF supported: 0
2026/01/27-22:00:38.559731 1 kCustomCompressionA7 supported: 0
2026/01/27-22:00:38.559731 1 kCustomCompressionE0 supported: 0
2026/01/27-22:00:38.559732 1 kCustomCompressionF1 supported: 0
2026/01/27-22:00:38.559732 1 kCustomCompressionE1 supported: 0
2026/01/27-22:00:38.559733 1 kCustomCompressionF5 supported: 0
2026/01/27-22:00:38.559733 1 kCustomCompression80 supported: 0
2026/01/27-22:00:38.559734 1 kCustomCompressionE3 supported: 0
2026/01/27-22:00:38.559734 1 kCustomCompressionE4 supported: 0
2026/01/27-22:00:38.559735 1 kCustomCompressionB0 supported: 0
2026/01/27-22:00:38.559735 1 kCustomCompressionEA supported: 0
2026/01/27-22:00:38.559736 1 kCustomCompressionFA supported: 0
2026/01/27-22:00:38.559736 1 kCustomCompressionE7 supported: 0
2026/01/27-22:00:38.559737 1 kCustomCompressionAE supported: 0
2026/01/27-22:00:38.559737 1 kCustomCompressionEB supported: 0
2026/01/27-22:00:38.559738 1 kCustomCompressionED supported: 0
2026/01/27-22:00:38.559738 1 kCustomCompressionB6 supported: 0
2026/01/27-22:00:38.559739 1 kCustomCompressionEF supported: 0
2026/01/27-22:00:38.559739 1 kCustomCompressionF0 supported: 0
2026/01/27-22:00:38.559740 1 kCustomCompressionB7 supported: 0
2026/01/27-22:00:38.559740 1 kCustomCompressionF2 supported: 0
2026/01/27-22:00:38.559741 1 kCustomCompressionA1 supported: 0
2026/01/27-22:00:38.559741 1 kCustomCompressionF4 supported: 0
2026/01/27-22:00:38.559742 1 kSnappyCompression supported: 1
2026/01/27-22:00:38.559742 1 kCustomCompressionF6 supported: 0
2026/01/27-22:00:38.559743 1 Fast CRC32 supported: Supported on Arm64
2026/01/27-22:00:38.559744 1 DMutex implementation: pthread_mutex_t
2026/01/27-22:00:38.559744 1 Jemalloc supported: 1
2026/01/27-22:00:38.560596 1 [db/version_set.cc:6190] Recovering from manifest file: /data/db/MANIFEST-000011
2026/01/27-22:00:38.560798 1 [db/column_family.cc:693] --------------- Options for column family [default]:
2026/01/27-22:00:38.560800 1 Options.comparator: leveldb.BytewiseComparator
2026/01/27-22:00:38.560801 1 Options.merge_operator: None
2026/01/27-22:00:38.560802 1 Options.compaction_filter: None
2026/01/27-22:00:38.560802 1 Options.compaction_filter_factory: None
2026/01/27-22:00:38.560803 1 Options.sst_partitioner_factory: None
2026/01/27-22:00:38.560803 1 Options.memtable_factory: SkipListFactory
2026/01/27-22:00:38.560804 1 Options.table_factory: BlockBasedTable
2026/01/27-22:00:38.560817 1 table_factory options: flush_block_policy_factory: FlushBlockBySizePolicyFactory (0xffffa56d15a0)
cache_index_and_filter_blocks: 0
cache_index_and_filter_blocks_with_high_priority: 1
pin_l0_filter_and_index_blocks_in_cache: 0
pin_top_level_index_and_filter: 1
index_type: 0
data_block_index_type: 0
index_shortening: 1
data_block_hash_table_util_ratio: 0.750000
checksum: 4
no_block_cache: 0
block_cache: 0xffffa566bd90
block_cache_name: LRUCache
block_cache_options:
capacity : 33554432
num_shard_bits : 6
strict_capacity_limit : 0
memory_allocator : None
high_pri_pool_ratio: 0.500
low_pri_pool_ratio: 0.000
persistent_cache: (nil)
block_size: 4096
block_size_deviation: 10
block_restart_interval: 16
index_block_restart_interval: 1
metadata_block_size: 4096
partition_filters: 0
use_delta_encoding: 1
filter_policy: nullptr
user_defined_index_factory: nullptr
fail_if_no_udi_on_open: 0
whole_key_filtering: 1
verify_compression: 0
read_amp_bytes_per_bit: 0
format_version: 6
enable_index_compression: 1
block_align: 0
max_auto_readahead_size: 262144
prepopulate_block_cache: 0
initial_auto_readahead_size: 8192
num_file_reads_for_auto_readahead: 2
2026/01/27-22:00:38.560818 1 Options.write_buffer_size: 67108864
2026/01/27-22:00:38.560819 1 Options.max_write_buffer_number: 4
2026/01/27-22:00:38.560820 1 Options.compression[0]: NoCompression
2026/01/27-22:00:38.560820 1 Options.compression[1]: NoCompression
2026/01/27-22:00:38.560821 1 Options.compression[2]: Snappy
2026/01/27-22:00:38.560822 1 Options.compression[3]: Snappy
2026/01/27-22:00:38.560822 1 Options.compression[4]: Snappy
2026/01/27-22:00:38.560823 1 Options.compression[5]: Snappy
2026/01/27-22:00:38.560823 1 Options.compression[6]: Snappy
2026/01/27-22:00:38.560824 1 Options.bottommost_compression: Disabled
2026/01/27-22:00:38.560824 1 Options.prefix_extractor: nullptr
2026/01/27-22:00:38.560825 1 Options.memtable_insert_with_hint_prefix_extractor: nullptr
2026/01/27-22:00:38.560825 1 Options.num_levels: 7
2026/01/27-22:00:38.560826 1 Options.min_write_buffer_number_to_merge: 1
2026/01/27-22:00:38.560826 1 Options.max_write_buffer_size_to_maintain: 0
2026/01/27-22:00:38.560827 1 Options.bottommost_compression_opts.window_bits: -14
2026/01/27-22:00:38.560827 1 Options.bottommost_compression_opts.level: 32767
2026/01/27-22:00:38.560828 1 Options.bottommost_compression_opts.strategy: 0
2026/01/27-22:00:38.560828 1 Options.bottommost_compression_opts.max_dict_bytes: 0
2026/01/27-22:00:38.560829 1 Options.bottommost_compression_opts.zstd_max_train_bytes: 0
2026/01/27-22:00:38.560829 1 Options.bottommost_compression_opts.parallel_threads: 1
2026/01/27-22:00:38.560830 1 Options.bottommost_compression_opts.enabled: false
2026/01/27-22:00:38.560830 1 Options.bottommost_compression_opts.max_dict_buffer_bytes: 0
2026/01/27-22:00:38.560831 1 Options.bottommost_compression_opts.use_zstd_dict_trainer: true
2026/01/27-22:00:38.560831 1 Options.compression_opts.window_bits: -14
2026/01/27-22:00:38.560832 1 Options.compression_opts.level: 32767
2026/01/27-22:00:38.560832 1 Options.compression_opts.strategy: 0
2026/01/27-22:00:38.560833 1 Options.compression_opts.max_dict_bytes: 0
2026/01/27-22:00:38.560833 1 Options.compression_opts.zstd_max_train_bytes: 0
2026/01/27-22:00:38.560834 1 Options.compression_opts.use_zstd_dict_trainer: true
2026/01/27-22:00:38.560834 1 Options.compression_opts.parallel_threads: 1
2026/01/27-22:00:38.560835 1 Options.compression_opts.enabled: false
2026/01/27-22:00:38.560835 1 Options.compression_opts.max_dict_buffer_bytes: 0
2026/01/27-22:00:38.560836 1 Options.level0_file_num_compaction_trigger: 4
2026/01/27-22:00:38.560836 1 Options.level0_slowdown_writes_trigger: 20
2026/01/27-22:00:38.560837 1 Options.level0_stop_writes_trigger: 40
2026/01/27-22:00:38.560837 1 Options.target_file_size_base: 134217728
2026/01/27-22:00:38.560838 1 Options.target_file_size_multiplier: 1
2026/01/27-22:00:38.560838 1 Options.max_bytes_for_level_base: 268435456
2026/01/27-22:00:38.560838 1 Options.level_compaction_dynamic_level_bytes: 1
2026/01/27-22:00:38.560840 1 Options.max_bytes_for_level_multiplier: 10.000000
2026/01/27-22:00:38.560840 1 Options.max_bytes_for_level_multiplier_addtl[0]: 1
2026/01/27-22:00:38.560841 1 Options.max_bytes_for_level_multiplier_addtl[1]: 1
2026/01/27-22:00:38.560841 1 Options.max_bytes_for_level_multiplier_addtl[2]: 1
2026/01/27-22:00:38.560842 1 Options.max_bytes_for_level_multiplier_addtl[3]: 1
2026/01/27-22:00:38.560842 1 Options.max_bytes_for_level_multiplier_addtl[4]: 1
2026/01/27-22:00:38.560843 1 Options.max_bytes_for_level_multiplier_addtl[5]: 1
2026/01/27-22:00:38.560843 1 Options.max_bytes_for_level_multiplier_addtl[6]: 1
2026/01/27-22:00:38.560844 1 Options.max_sequential_skip_in_iterations: 8
2026/01/27-22:00:38.560844 1 Options.memtable_op_scan_flush_trigger: 0
2026/01/27-22:00:38.560844 1 Options.memtable_avg_op_scan_flush_trigger: 0
2026/01/27-22:00:38.560845 1 Options.max_compaction_bytes: 3355443200
2026/01/27-22:00:38.560845 1 Options.arena_block_size: 1048576
2026/01/27-22:00:38.560846 1 Options.soft_pending_compaction_bytes_limit: 68719476736
2026/01/27-22:00:38.560846 1 Options.hard_pending_compaction_bytes_limit: 274877906944
2026/01/27-22:00:38.560847 1 Options.disable_auto_compactions: 0
2026/01/27-22:00:38.560848 1 Options.compaction_style: kCompactionStyleLevel
2026/01/27-22:00:38.560849 1 Options.compaction_pri: kMinOverlappingRatio
2026/01/27-22:00:38.560849 1 Options.compaction_options_universal.size_ratio: 1
2026/01/27-22:00:38.560850 1 Options.compaction_options_universal.min_merge_width: 2
2026/01/27-22:00:38.560850 1 Options.compaction_options_universal.max_merge_width: 4294967295
2026/01/27-22:00:38.560851 1 Options.compaction_options_universal.max_size_amplification_percent: 200
2026/01/27-22:00:38.560851 1 Options.compaction_options_universal.compression_size_percent: -1
2026/01/27-22:00:38.560852 1 Options.compaction_options_universal.stop_style: kCompactionStopStyleTotalSize
2026/01/27-22:00:38.560852 1 Options.compaction_options_universal.max_read_amp: -1
2026/01/27-22:00:38.560853 1 Options.compaction_options_universal.reduce_file_locking: 0
2026/01/27-22:00:38.560853 1 Options.compaction_options_fifo.max_table_files_size: 1073741824
2026/01/27-22:00:38.560854 1 Options.compaction_options_fifo.allow_compaction: 0
2026/01/27-22:00:38.560855 1 Options.table_properties_collectors:
2026/01/27-22:00:38.560856 1 Options.inplace_update_support: 0
2026/01/27-22:00:38.560857 1 Options.inplace_update_num_locks: 10000
2026/01/27-22:00:38.560857 1 Options.memtable_prefix_bloom_size_ratio: 0.000000
2026/01/27-22:00:38.560858 1 Options.memtable_whole_key_filtering: 0
2026/01/27-22:00:38.560858 1 Options.memtable_huge_page_size: 0
2026/01/27-22:00:38.560859 1 Options.bloom_locality: 0
2026/01/27-22:00:38.560859 1 Options.max_successive_merges: 0
2026/01/27-22:00:38.560860 1 Options.strict_max_successive_merges: 0
2026/01/27-22:00:38.560860 1 Options.optimize_filters_for_hits: 0
2026/01/27-22:00:38.560861 1 Options.paranoid_file_checks: 0
2026/01/27-22:00:38.560861 1 Options.force_consistency_checks: 1
2026/01/27-22:00:38.560862 1 Options.report_bg_io_stats: 0
2026/01/27-22:00:38.560862 1 Options.disallow_memtable_writes: 0
2026/01/27-22:00:38.560863 1 Options.ttl: 2592000
2026/01/27-22:00:38.560863 1 Options.periodic_compaction_seconds: 0
2026/01/27-22:00:38.560864 1 Options.default_temperature: kUnknown
2026/01/27-22:00:38.560864 1 Options.preclude_last_level_data_seconds: 0
2026/01/27-22:00:38.560865 1 Options.preserve_internal_time_seconds: 0
2026/01/27-22:00:38.560865 1 Options.enable_blob_files: false
2026/01/27-22:00:38.560866 1 Options.min_blob_size: 0
2026/01/27-22:00:38.560866 1 Options.blob_file_size: 268435456
2026/01/27-22:00:38.560867 1 Options.blob_compression_type: NoCompression
2026/01/27-22:00:38.560867 1 Options.enable_blob_garbage_collection: false
2026/01/27-22:00:38.560879 1 Options.blob_garbage_collection_age_cutoff: 0.250000
2026/01/27-22:00:38.560880 1 Options.blob_garbage_collection_force_threshold: 1.000000
2026/01/27-22:00:38.560880 1 Options.blob_compaction_readahead_size: 0
2026/01/27-22:00:38.560881 1 Options.blob_file_starting_level: 0
2026/01/27-22:00:38.560881 1 Options.experimental_mempurge_threshold: 0.000000
2026/01/27-22:00:38.560882 1 Options.memtable_max_range_deletions: 0
2026/01/27-22:00:38.560882 1 Options.cf_allow_ingest_behind: false
2026/01/27-22:00:38.561145 1 [WARN] [db/db_impl/db_impl_open.cc:2688] DB::Open() failed: Invalid argument: Column families not opened: index, search, stream, propagate, pubsub, zset_score, metadata
2026/01/27-22:00:38.561190 1 [db/db_impl/db_impl.cc:467] Shutdown: canceling all background work
2026/01/27-22:00:38.561240 1 [db/db_impl/db_impl.cc:681] Shutdown complete

File diff suppressed because it is too large Load Diff

View File

@@ -1,391 +0,0 @@
2026/01/27-22:05:20.858109 1 RocksDB version: 10.6.2
2026/01/27-22:05:20.858266 1 Git sha 0
2026/01/27-22:05:20.858267 1 Compile date 2025-11-08 14:59:16
2026/01/27-22:05:20.858268 1 DB SUMMARY
2026/01/27-22:05:20.858269 1 Host name (Env): 83baffb0044b
2026/01/27-22:05:20.858270 1 DB Session ID: KLAN4OO9759DRR73WKX0
2026/01/27-22:05:20.858345 1 CURRENT file: CURRENT
2026/01/27-22:05:20.858346 1 IDENTITY file: IDENTITY
2026/01/27-22:05:20.858357 1 MANIFEST file: MANIFEST-000016 size: 941 Bytes
2026/01/27-22:05:20.858358 1 SST files in /data/db dir, Total Num: 1, files: 000014.sst
2026/01/27-22:05:20.858359 1 Write Ahead Log file in /data/db: 000015.log size: 0 ;
2026/01/27-22:05:20.858360 1 Options.error_if_exists: 0
2026/01/27-22:05:20.858361 1 Options.create_if_missing: 1
2026/01/27-22:05:20.858361 1 Options.paranoid_checks: 1
2026/01/27-22:05:20.858362 1 Options.flush_verify_memtable_count: 1
2026/01/27-22:05:20.858362 1 Options.compaction_verify_record_count: 1
2026/01/27-22:05:20.858363 1 Options.track_and_verify_wals_in_manifest: 0
2026/01/27-22:05:20.858363 1 Options.track_and_verify_wals: 0
2026/01/27-22:05:20.858364 1 Options.verify_sst_unique_id_in_manifest: 1
2026/01/27-22:05:20.858364 1 Options.env: 0xffff8221d0c0
2026/01/27-22:05:20.858365 1 Options.fs: PosixFileSystem
2026/01/27-22:05:20.858366 1 Options.info_log: 0xffff8230f600
2026/01/27-22:05:20.858366 1 Options.max_file_opening_threads: 16
2026/01/27-22:05:20.858366 1 Options.statistics: 0xffff822135b0
2026/01/27-22:05:20.858367 1 Options.statistics stats level: 3
2026/01/27-22:05:20.858367 1 Options.use_fsync: 0
2026/01/27-22:05:20.858368 1 Options.max_log_file_size: 268435456
2026/01/27-22:05:20.858369 1 Options.max_manifest_file_size: 67108864
2026/01/27-22:05:20.858369 1 Options.log_file_time_to_roll: 0
2026/01/27-22:05:20.858370 1 Options.keep_log_file_num: 12
2026/01/27-22:05:20.858370 1 Options.recycle_log_file_num: 0
2026/01/27-22:05:20.858371 1 Options.allow_fallocate: 1
2026/01/27-22:05:20.858371 1 Options.allow_mmap_reads: 0
2026/01/27-22:05:20.858371 1 Options.allow_mmap_writes: 0
2026/01/27-22:05:20.858372 1 Options.use_direct_reads: 0
2026/01/27-22:05:20.858372 1 Options.use_direct_io_for_flush_and_compaction: 0
2026/01/27-22:05:20.858373 1 Options.create_missing_column_families: 1
2026/01/27-22:05:20.858373 1 Options.db_log_dir:
2026/01/27-22:05:20.858374 1 Options.wal_dir:
2026/01/27-22:05:20.858374 1 Options.table_cache_numshardbits: 6
2026/01/27-22:05:20.858375 1 Options.WAL_ttl_seconds: 10800
2026/01/27-22:05:20.858375 1 Options.WAL_size_limit_MB: 16384
2026/01/27-22:05:20.858376 1 Options.max_write_batch_group_size_bytes: 1048576
2026/01/27-22:05:20.858376 1 Options.manifest_preallocation_size: 4194304
2026/01/27-22:05:20.858377 1 Options.is_fd_close_on_exec: 1
2026/01/27-22:05:20.858377 1 Options.advise_random_on_open: 1
2026/01/27-22:05:20.858378 1 Options.db_write_buffer_size: 0
2026/01/27-22:05:20.858378 1 Options.write_buffer_manager: 0xffff8226bfc0
2026/01/27-22:05:20.858379 1 Options.use_adaptive_mutex: 0
2026/01/27-22:05:20.858379 1 Options.rate_limiter: 0xffff82236000
2026/01/27-22:05:20.858380 1 Options.sst_file_manager.rate_bytes_per_sec: 0
2026/01/27-22:05:20.858380 1 Options.wal_recovery_mode: 2
2026/01/27-22:05:20.858381 1 Options.enable_thread_tracking: 0
2026/01/27-22:05:20.858381 1 Options.enable_pipelined_write: 0
2026/01/27-22:05:20.858382 1 Options.unordered_write: 0
2026/01/27-22:05:20.858382 1 Options.allow_concurrent_memtable_write: 1
2026/01/27-22:05:20.858383 1 Options.enable_write_thread_adaptive_yield: 1
2026/01/27-22:05:20.858383 1 Options.write_thread_max_yield_usec: 100
2026/01/27-22:05:20.858384 1 Options.write_thread_slow_yield_usec: 3
2026/01/27-22:05:20.858384 1 Options.row_cache: None
2026/01/27-22:05:20.858384 1 Options.wal_filter: None
2026/01/27-22:05:20.858385 1 Options.avoid_flush_during_recovery: 0
2026/01/27-22:05:20.858385 1 Options.allow_ingest_behind: 0
2026/01/27-22:05:20.858386 1 Options.two_write_queues: 0
2026/01/27-22:05:20.858386 1 Options.manual_wal_flush: 0
2026/01/27-22:05:20.858387 1 Options.wal_compression: 0
2026/01/27-22:05:20.858387 1 Options.background_close_inactive_wals: 0
2026/01/27-22:05:20.858388 1 Options.atomic_flush: 0
2026/01/27-22:05:20.858388 1 Options.avoid_unnecessary_blocking_io: 1
2026/01/27-22:05:20.858389 1 Options.prefix_seek_opt_in_only: 0
2026/01/27-22:05:20.858389 1 Options.persist_stats_to_disk: 0
2026/01/27-22:05:20.858390 1 Options.write_dbid_to_manifest: 1
2026/01/27-22:05:20.858390 1 Options.write_identity_file: 1
2026/01/27-22:05:20.858391 1 Options.log_readahead_size: 0
2026/01/27-22:05:20.858391 1 Options.file_checksum_gen_factory: Unknown
2026/01/27-22:05:20.858392 1 Options.best_efforts_recovery: 0
2026/01/27-22:05:20.858392 1 Options.max_bgerror_resume_count: 2147483647
2026/01/27-22:05:20.858393 1 Options.bgerror_resume_retry_interval: 1000000
2026/01/27-22:05:20.858393 1 Options.allow_data_in_errors: 0
2026/01/27-22:05:20.858394 1 Options.db_host_id: __hostname__
2026/01/27-22:05:20.858394 1 Options.enforce_single_del_contracts: true
2026/01/27-22:05:20.858395 1 Options.metadata_write_temperature: kUnknown
2026/01/27-22:05:20.858396 1 Options.wal_write_temperature: kUnknown
2026/01/27-22:05:20.858396 1 Options.max_background_jobs: 4
2026/01/27-22:05:20.858396 1 Options.max_background_compactions: -1
2026/01/27-22:05:20.858397 1 Options.max_subcompactions: 2
2026/01/27-22:05:20.858397 1 Options.avoid_flush_during_shutdown: 0
2026/01/27-22:05:20.858398 1 Options.writable_file_max_buffer_size: 1048576
2026/01/27-22:05:20.858399 1 Options.delayed_write_rate : 536870912000
2026/01/27-22:05:20.858399 1 Options.max_total_wal_size: 536870912
2026/01/27-22:05:20.858400 1 Options.delete_obsolete_files_period_micros: 21600000000
2026/01/27-22:05:20.858400 1 Options.stats_dump_period_sec: 0
2026/01/27-22:05:20.858401 1 Options.stats_persist_period_sec: 600
2026/01/27-22:05:20.858401 1 Options.stats_history_buffer_size: 1048576
2026/01/27-22:05:20.858402 1 Options.max_open_files: 8096
2026/01/27-22:05:20.858402 1 Options.bytes_per_sync: 1048576
2026/01/27-22:05:20.858403 1 Options.wal_bytes_per_sync: 0
2026/01/27-22:05:20.858403 1 Options.strict_bytes_per_sync: 0
2026/01/27-22:05:20.858404 1 Options.compaction_readahead_size: 2097152
2026/01/27-22:05:20.858405 1 Options.max_background_flushes: -1
2026/01/27-22:05:20.858405 1 Options.daily_offpeak_time_utc:
2026/01/27-22:05:20.858406 1 Compression algorithms supported:
2026/01/27-22:05:20.858406 1 kCustomCompressionFE supported: 0
2026/01/27-22:05:20.858407 1 kCustomCompressionFC supported: 0
2026/01/27-22:05:20.858407 1 kCustomCompressionF8 supported: 0
2026/01/27-22:05:20.858408 1 kCustomCompressionF7 supported: 0
2026/01/27-22:05:20.858408 1 kCustomCompressionB2 supported: 0
2026/01/27-22:05:20.858409 1 kLZ4Compression supported: 1
2026/01/27-22:05:20.858410 1 kCustomCompression88 supported: 0
2026/01/27-22:05:20.858410 1 kCustomCompressionD8 supported: 0
2026/01/27-22:05:20.858411 1 kCustomCompression9F supported: 0
2026/01/27-22:05:20.858411 1 kCustomCompressionD6 supported: 0
2026/01/27-22:05:20.858412 1 kCustomCompressionA9 supported: 0
2026/01/27-22:05:20.858412 1 kCustomCompressionEC supported: 0
2026/01/27-22:05:20.858413 1 kCustomCompressionA3 supported: 0
2026/01/27-22:05:20.858413 1 kCustomCompressionCB supported: 0
2026/01/27-22:05:20.858414 1 kCustomCompression90 supported: 0
2026/01/27-22:05:20.858414 1 kCustomCompressionA0 supported: 0
2026/01/27-22:05:20.858415 1 kCustomCompressionC6 supported: 0
2026/01/27-22:05:20.858415 1 kCustomCompression9D supported: 0
2026/01/27-22:05:20.858415 1 kCustomCompression8B supported: 0
2026/01/27-22:05:20.858416 1 kCustomCompressionA8 supported: 0
2026/01/27-22:05:20.858416 1 kCustomCompression8D supported: 0
2026/01/27-22:05:20.858417 1 kCustomCompression97 supported: 0
2026/01/27-22:05:20.858417 1 kCustomCompression98 supported: 0
2026/01/27-22:05:20.858418 1 kCustomCompressionAC supported: 0
2026/01/27-22:05:20.858418 1 kCustomCompressionE9 supported: 0
2026/01/27-22:05:20.858419 1 kCustomCompression96 supported: 0
2026/01/27-22:05:20.858419 1 kCustomCompressionB1 supported: 0
2026/01/27-22:05:20.858420 1 kCustomCompression95 supported: 0
2026/01/27-22:05:20.858420 1 kCustomCompression84 supported: 0
2026/01/27-22:05:20.858421 1 kCustomCompression91 supported: 0
2026/01/27-22:05:20.858421 1 kCustomCompressionAB supported: 0
2026/01/27-22:05:20.858422 1 kCustomCompressionB3 supported: 0
2026/01/27-22:05:20.858422 1 kCustomCompression81 supported: 0
2026/01/27-22:05:20.858423 1 kCustomCompressionDC supported: 0
2026/01/27-22:05:20.858423 1 kBZip2Compression supported: 0
2026/01/27-22:05:20.858424 1 kCustomCompressionBB supported: 0
2026/01/27-22:05:20.858424 1 kCustomCompression9C supported: 0
2026/01/27-22:05:20.858425 1 kCustomCompressionC9 supported: 0
2026/01/27-22:05:20.858425 1 kCustomCompressionCC supported: 0
2026/01/27-22:05:20.858426 1 kCustomCompression92 supported: 0
2026/01/27-22:05:20.858426 1 kCustomCompressionB9 supported: 0
2026/01/27-22:05:20.858427 1 kCustomCompression8F supported: 0
2026/01/27-22:05:20.858427 1 kCustomCompression8A supported: 0
2026/01/27-22:05:20.858427 1 kCustomCompression9B supported: 0
2026/01/27-22:05:20.858428 1 kZSTD supported: 1
2026/01/27-22:05:20.858428 1 kCustomCompressionAA supported: 0
2026/01/27-22:05:20.858429 1 kCustomCompressionA2 supported: 0
2026/01/27-22:05:20.858429 1 kZlibCompression supported: 1
2026/01/27-22:05:20.858430 1 kXpressCompression supported: 0
2026/01/27-22:05:20.858431 1 kCustomCompressionFD supported: 0
2026/01/27-22:05:20.858431 1 kCustomCompressionE2 supported: 0
2026/01/27-22:05:20.858432 1 kLZ4HCCompression supported: 1
2026/01/27-22:05:20.858432 1 kCustomCompressionA6 supported: 0
2026/01/27-22:05:20.858433 1 kCustomCompression85 supported: 0
2026/01/27-22:05:20.858433 1 kCustomCompressionA4 supported: 0
2026/01/27-22:05:20.858434 1 kCustomCompression86 supported: 0
2026/01/27-22:05:20.858434 1 kCustomCompression83 supported: 0
2026/01/27-22:05:20.858434 1 kCustomCompression87 supported: 0
2026/01/27-22:05:20.858435 1 kCustomCompression89 supported: 0
2026/01/27-22:05:20.858435 1 kCustomCompression8C supported: 0
2026/01/27-22:05:20.858436 1 kCustomCompressionDB supported: 0
2026/01/27-22:05:20.858436 1 kCustomCompressionF3 supported: 0
2026/01/27-22:05:20.858437 1 kCustomCompressionE6 supported: 0
2026/01/27-22:05:20.858437 1 kCustomCompression8E supported: 0
2026/01/27-22:05:20.858438 1 kCustomCompressionDA supported: 0
2026/01/27-22:05:20.858438 1 kCustomCompression93 supported: 0
2026/01/27-22:05:20.858439 1 kCustomCompression94 supported: 0
2026/01/27-22:05:20.858440 1 kCustomCompression9E supported: 0
2026/01/27-22:05:20.858441 1 kCustomCompressionB4 supported: 0
2026/01/27-22:05:20.858441 1 kCustomCompressionFB supported: 0
2026/01/27-22:05:20.858442 1 kCustomCompressionB5 supported: 0
2026/01/27-22:05:20.858442 1 kCustomCompressionD5 supported: 0
2026/01/27-22:05:20.858443 1 kCustomCompressionB8 supported: 0
2026/01/27-22:05:20.858443 1 kCustomCompressionD1 supported: 0
2026/01/27-22:05:20.858444 1 kCustomCompressionBA supported: 0
2026/01/27-22:05:20.858444 1 kCustomCompressionBC supported: 0
2026/01/27-22:05:20.858445 1 kCustomCompressionCE supported: 0
2026/01/27-22:05:20.858445 1 kCustomCompressionBD supported: 0
2026/01/27-22:05:20.858445 1 kCustomCompressionC4 supported: 0
2026/01/27-22:05:20.858446 1 kCustomCompression9A supported: 0
2026/01/27-22:05:20.858446 1 kCustomCompression99 supported: 0
2026/01/27-22:05:20.858447 1 kCustomCompressionBE supported: 0
2026/01/27-22:05:20.858447 1 kCustomCompressionE5 supported: 0
2026/01/27-22:05:20.858448 1 kCustomCompressionD9 supported: 0
2026/01/27-22:05:20.858448 1 kCustomCompressionC1 supported: 0
2026/01/27-22:05:20.858449 1 kCustomCompressionC5 supported: 0
2026/01/27-22:05:20.858449 1 kCustomCompressionC2 supported: 0
2026/01/27-22:05:20.858450 1 kCustomCompressionA5 supported: 0
2026/01/27-22:05:20.858450 1 kCustomCompressionC7 supported: 0
2026/01/27-22:05:20.858451 1 kCustomCompressionBF supported: 0
2026/01/27-22:05:20.858451 1 kCustomCompressionE8 supported: 0
2026/01/27-22:05:20.858452 1 kCustomCompressionC8 supported: 0
2026/01/27-22:05:20.858452 1 kCustomCompressionAF supported: 0
2026/01/27-22:05:20.858453 1 kCustomCompressionCA supported: 0
2026/01/27-22:05:20.858453 1 kCustomCompressionCD supported: 0
2026/01/27-22:05:20.858454 1 kCustomCompressionC0 supported: 0
2026/01/27-22:05:20.858454 1 kCustomCompressionCF supported: 0
2026/01/27-22:05:20.858455 1 kCustomCompressionF9 supported: 0
2026/01/27-22:05:20.858455 1 kCustomCompressionD0 supported: 0
2026/01/27-22:05:20.858456 1 kCustomCompressionD2 supported: 0
2026/01/27-22:05:20.858456 1 kCustomCompressionAD supported: 0
2026/01/27-22:05:20.858457 1 kCustomCompressionD3 supported: 0
2026/01/27-22:05:20.858457 1 kCustomCompressionD4 supported: 0
2026/01/27-22:05:20.858458 1 kCustomCompressionD7 supported: 0
2026/01/27-22:05:20.858458 1 kCustomCompression82 supported: 0
2026/01/27-22:05:20.858459 1 kCustomCompressionDD supported: 0
2026/01/27-22:05:20.858459 1 kCustomCompressionC3 supported: 0
2026/01/27-22:05:20.858459 1 kCustomCompressionEE supported: 0
2026/01/27-22:05:20.858460 1 kCustomCompressionDE supported: 0
2026/01/27-22:05:20.858460 1 kCustomCompressionDF supported: 0
2026/01/27-22:05:20.858461 1 kCustomCompressionA7 supported: 0
2026/01/27-22:05:20.858461 1 kCustomCompressionE0 supported: 0
2026/01/27-22:05:20.858462 1 kCustomCompressionF1 supported: 0
2026/01/27-22:05:20.858462 1 kCustomCompressionE1 supported: 0
2026/01/27-22:05:20.858463 1 kCustomCompressionF5 supported: 0
2026/01/27-22:05:20.858463 1 kCustomCompression80 supported: 0
2026/01/27-22:05:20.858464 1 kCustomCompressionE3 supported: 0
2026/01/27-22:05:20.858464 1 kCustomCompressionE4 supported: 0
2026/01/27-22:05:20.858465 1 kCustomCompressionB0 supported: 0
2026/01/27-22:05:20.858465 1 kCustomCompressionEA supported: 0
2026/01/27-22:05:20.858466 1 kCustomCompressionFA supported: 0
2026/01/27-22:05:20.858466 1 kCustomCompressionE7 supported: 0
2026/01/27-22:05:20.858467 1 kCustomCompressionAE supported: 0
2026/01/27-22:05:20.858467 1 kCustomCompressionEB supported: 0
2026/01/27-22:05:20.858468 1 kCustomCompressionED supported: 0
2026/01/27-22:05:20.858468 1 kCustomCompressionB6 supported: 0
2026/01/27-22:05:20.858469 1 kCustomCompressionEF supported: 0
2026/01/27-22:05:20.858469 1 kCustomCompressionF0 supported: 0
2026/01/27-22:05:20.858470 1 kCustomCompressionB7 supported: 0
2026/01/27-22:05:20.858470 1 kCustomCompressionF2 supported: 0
2026/01/27-22:05:20.858470 1 kCustomCompressionA1 supported: 0
2026/01/27-22:05:20.858471 1 kCustomCompressionF4 supported: 0
2026/01/27-22:05:20.858472 1 kSnappyCompression supported: 1
2026/01/27-22:05:20.858472 1 kCustomCompressionF6 supported: 0
2026/01/27-22:05:20.858476 1 Fast CRC32 supported: Supported on Arm64
2026/01/27-22:05:20.858477 1 DMutex implementation: pthread_mutex_t
2026/01/27-22:05:20.858477 1 Jemalloc supported: 1
2026/01/27-22:05:20.858838 1 [db/version_set.cc:6190] Recovering from manifest file: /data/db/MANIFEST-000016
2026/01/27-22:05:20.858987 1 [db/column_family.cc:693] --------------- Options for column family [default]:
2026/01/27-22:05:20.858989 1 Options.comparator: leveldb.BytewiseComparator
2026/01/27-22:05:20.858990 1 Options.merge_operator: None
2026/01/27-22:05:20.858990 1 Options.compaction_filter: None
2026/01/27-22:05:20.858991 1 Options.compaction_filter_factory: None
2026/01/27-22:05:20.858991 1 Options.sst_partitioner_factory: None
2026/01/27-22:05:20.858992 1 Options.memtable_factory: SkipListFactory
2026/01/27-22:05:20.858992 1 Options.table_factory: BlockBasedTable
2026/01/27-22:05:20.859006 1 table_factory options: flush_block_policy_factory: FlushBlockBySizePolicyFactory (0xffff822d15a0)
cache_index_and_filter_blocks: 0
cache_index_and_filter_blocks_with_high_priority: 1
pin_l0_filter_and_index_blocks_in_cache: 0
pin_top_level_index_and_filter: 1
index_type: 0
data_block_index_type: 0
index_shortening: 1
data_block_hash_table_util_ratio: 0.750000
checksum: 4
no_block_cache: 0
block_cache: 0xffff8226bd90
block_cache_name: LRUCache
block_cache_options:
capacity : 33554432
num_shard_bits : 6
strict_capacity_limit : 0
memory_allocator : None
high_pri_pool_ratio: 0.500
low_pri_pool_ratio: 0.000
persistent_cache: (nil)
block_size: 4096
block_size_deviation: 10
block_restart_interval: 16
index_block_restart_interval: 1
metadata_block_size: 4096
partition_filters: 0
use_delta_encoding: 1
filter_policy: nullptr
user_defined_index_factory: nullptr
fail_if_no_udi_on_open: 0
whole_key_filtering: 1
verify_compression: 0
read_amp_bytes_per_bit: 0
format_version: 6
enable_index_compression: 1
block_align: 0
max_auto_readahead_size: 262144
prepopulate_block_cache: 0
initial_auto_readahead_size: 8192
num_file_reads_for_auto_readahead: 2
2026/01/27-22:05:20.859009 1 Options.write_buffer_size: 67108864
2026/01/27-22:05:20.859009 1 Options.max_write_buffer_number: 4
2026/01/27-22:05:20.859010 1 Options.compression[0]: NoCompression
2026/01/27-22:05:20.859011 1 Options.compression[1]: NoCompression
2026/01/27-22:05:20.859011 1 Options.compression[2]: Snappy
2026/01/27-22:05:20.859012 1 Options.compression[3]: Snappy
2026/01/27-22:05:20.859012 1 Options.compression[4]: Snappy
2026/01/27-22:05:20.859013 1 Options.compression[5]: Snappy
2026/01/27-22:05:20.859013 1 Options.compression[6]: Snappy
2026/01/27-22:05:20.859014 1 Options.bottommost_compression: Disabled
2026/01/27-22:05:20.859015 1 Options.prefix_extractor: nullptr
2026/01/27-22:05:20.859015 1 Options.memtable_insert_with_hint_prefix_extractor: nullptr
2026/01/27-22:05:20.859016 1 Options.num_levels: 7
2026/01/27-22:05:20.859016 1 Options.min_write_buffer_number_to_merge: 1
2026/01/27-22:05:20.859017 1 Options.max_write_buffer_size_to_maintain: 0
2026/01/27-22:05:20.859017 1 Options.bottommost_compression_opts.window_bits: -14
2026/01/27-22:05:20.859018 1 Options.bottommost_compression_opts.level: 32767
2026/01/27-22:05:20.859018 1 Options.bottommost_compression_opts.strategy: 0
2026/01/27-22:05:20.859019 1 Options.bottommost_compression_opts.max_dict_bytes: 0
2026/01/27-22:05:20.859019 1 Options.bottommost_compression_opts.zstd_max_train_bytes: 0
2026/01/27-22:05:20.859020 1 Options.bottommost_compression_opts.parallel_threads: 1
2026/01/27-22:05:20.859020 1 Options.bottommost_compression_opts.enabled: false
2026/01/27-22:05:20.859021 1 Options.bottommost_compression_opts.max_dict_buffer_bytes: 0
2026/01/27-22:05:20.859021 1 Options.bottommost_compression_opts.use_zstd_dict_trainer: true
2026/01/27-22:05:20.859022 1 Options.compression_opts.window_bits: -14
2026/01/27-22:05:20.859022 1 Options.compression_opts.level: 32767
2026/01/27-22:05:20.859023 1 Options.compression_opts.strategy: 0
2026/01/27-22:05:20.859023 1 Options.compression_opts.max_dict_bytes: 0
2026/01/27-22:05:20.859024 1 Options.compression_opts.zstd_max_train_bytes: 0
2026/01/27-22:05:20.859024 1 Options.compression_opts.use_zstd_dict_trainer: true
2026/01/27-22:05:20.859025 1 Options.compression_opts.parallel_threads: 1
2026/01/27-22:05:20.859025 1 Options.compression_opts.enabled: false
2026/01/27-22:05:20.859026 1 Options.compression_opts.max_dict_buffer_bytes: 0
2026/01/27-22:05:20.859026 1 Options.level0_file_num_compaction_trigger: 4
2026/01/27-22:05:20.859026 1 Options.level0_slowdown_writes_trigger: 20
2026/01/27-22:05:20.859027 1 Options.level0_stop_writes_trigger: 40
2026/01/27-22:05:20.859027 1 Options.target_file_size_base: 134217728
2026/01/27-22:05:20.859028 1 Options.target_file_size_multiplier: 1
2026/01/27-22:05:20.859028 1 Options.max_bytes_for_level_base: 268435456
2026/01/27-22:05:20.859029 1 Options.level_compaction_dynamic_level_bytes: 1
2026/01/27-22:05:20.859030 1 Options.max_bytes_for_level_multiplier: 10.000000
2026/01/27-22:05:20.859030 1 Options.max_bytes_for_level_multiplier_addtl[0]: 1
2026/01/27-22:05:20.859031 1 Options.max_bytes_for_level_multiplier_addtl[1]: 1
2026/01/27-22:05:20.859031 1 Options.max_bytes_for_level_multiplier_addtl[2]: 1
2026/01/27-22:05:20.859032 1 Options.max_bytes_for_level_multiplier_addtl[3]: 1
2026/01/27-22:05:20.859032 1 Options.max_bytes_for_level_multiplier_addtl[4]: 1
2026/01/27-22:05:20.859033 1 Options.max_bytes_for_level_multiplier_addtl[5]: 1
2026/01/27-22:05:20.859033 1 Options.max_bytes_for_level_multiplier_addtl[6]: 1
2026/01/27-22:05:20.859034 1 Options.max_sequential_skip_in_iterations: 8
2026/01/27-22:05:20.859034 1 Options.memtable_op_scan_flush_trigger: 0
2026/01/27-22:05:20.859035 1 Options.memtable_avg_op_scan_flush_trigger: 0
2026/01/27-22:05:20.859035 1 Options.max_compaction_bytes: 3355443200
2026/01/27-22:05:20.859036 1 Options.arena_block_size: 1048576
2026/01/27-22:05:20.859036 1 Options.soft_pending_compaction_bytes_limit: 68719476736
2026/01/27-22:05:20.859037 1 Options.hard_pending_compaction_bytes_limit: 274877906944
2026/01/27-22:05:20.859037 1 Options.disable_auto_compactions: 0
2026/01/27-22:05:20.859038 1 Options.compaction_style: kCompactionStyleLevel
2026/01/27-22:05:20.859038 1 Options.compaction_pri: kMinOverlappingRatio
2026/01/27-22:05:20.859039 1 Options.compaction_options_universal.size_ratio: 1
2026/01/27-22:05:20.859039 1 Options.compaction_options_universal.min_merge_width: 2
2026/01/27-22:05:20.859040 1 Options.compaction_options_universal.max_merge_width: 4294967295
2026/01/27-22:05:20.859040 1 Options.compaction_options_universal.max_size_amplification_percent: 200
2026/01/27-22:05:20.859041 1 Options.compaction_options_universal.compression_size_percent: -1
2026/01/27-22:05:20.859042 1 Options.compaction_options_universal.stop_style: kCompactionStopStyleTotalSize
2026/01/27-22:05:20.859042 1 Options.compaction_options_universal.max_read_amp: -1
2026/01/27-22:05:20.859043 1 Options.compaction_options_universal.reduce_file_locking: 0
2026/01/27-22:05:20.859043 1 Options.compaction_options_fifo.max_table_files_size: 1073741824
2026/01/27-22:05:20.859043 1 Options.compaction_options_fifo.allow_compaction: 0
2026/01/27-22:05:20.859044 1 Options.table_properties_collectors:
2026/01/27-22:05:20.859045 1 Options.inplace_update_support: 0
2026/01/27-22:05:20.859046 1 Options.inplace_update_num_locks: 10000
2026/01/27-22:05:20.859046 1 Options.memtable_prefix_bloom_size_ratio: 0.000000
2026/01/27-22:05:20.859049 1 Options.memtable_whole_key_filtering: 0
2026/01/27-22:05:20.859049 1 Options.memtable_huge_page_size: 0
2026/01/27-22:05:20.859050 1 Options.bloom_locality: 0
2026/01/27-22:05:20.859050 1 Options.max_successive_merges: 0
2026/01/27-22:05:20.859051 1 Options.strict_max_successive_merges: 0
2026/01/27-22:05:20.859051 1 Options.optimize_filters_for_hits: 0
2026/01/27-22:05:20.859052 1 Options.paranoid_file_checks: 0
2026/01/27-22:05:20.859052 1 Options.force_consistency_checks: 1
2026/01/27-22:05:20.859053 1 Options.report_bg_io_stats: 0
2026/01/27-22:05:20.859053 1 Options.disallow_memtable_writes: 0
2026/01/27-22:05:20.859054 1 Options.ttl: 2592000
2026/01/27-22:05:20.859054 1 Options.periodic_compaction_seconds: 0
2026/01/27-22:05:20.859055 1 Options.default_temperature: kUnknown
2026/01/27-22:05:20.859055 1 Options.preclude_last_level_data_seconds: 0
2026/01/27-22:05:20.859056 1 Options.preserve_internal_time_seconds: 0
2026/01/27-22:05:20.859056 1 Options.enable_blob_files: false
2026/01/27-22:05:20.859057 1 Options.min_blob_size: 0
2026/01/27-22:05:20.859058 1 Options.blob_file_size: 268435456
2026/01/27-22:05:20.859058 1 Options.blob_compression_type: NoCompression
2026/01/27-22:05:20.859059 1 Options.enable_blob_garbage_collection: false
2026/01/27-22:05:20.859059 1 Options.blob_garbage_collection_age_cutoff: 0.250000
2026/01/27-22:05:20.859060 1 Options.blob_garbage_collection_force_threshold: 1.000000
2026/01/27-22:05:20.859060 1 Options.blob_compaction_readahead_size: 0
2026/01/27-22:05:20.859061 1 Options.blob_file_starting_level: 0
2026/01/27-22:05:20.859061 1 Options.experimental_mempurge_threshold: 0.000000
2026/01/27-22:05:20.859062 1 Options.memtable_max_range_deletions: 0
2026/01/27-22:05:20.859062 1 Options.cf_allow_ingest_behind: false
2026/01/27-22:05:20.859382 1 [WARN] [db/db_impl/db_impl_open.cc:2688] DB::Open() failed: Invalid argument: Column families not opened: index, search, stream, propagate, pubsub, zset_score, metadata
2026/01/27-22:05:20.859417 1 [db/db_impl/db_impl.cc:467] Shutdown: canceling all background work
2026/01/27-22:05:20.859456 1 [db/db_impl/db_impl.cc:681] Shutdown complete

File diff suppressed because it is too large Load Diff

View File

@@ -1,391 +0,0 @@
2026/01/27-22:29:25.240257 1 RocksDB version: 10.6.2
2026/01/27-22:29:25.240319 1 Git sha 0
2026/01/27-22:29:25.240320 1 Compile date 2025-11-08 14:59:16
2026/01/27-22:29:25.240321 1 DB SUMMARY
2026/01/27-22:29:25.240322 1 Host name (Env): 5872b7ec3319
2026/01/27-22:29:25.240323 1 DB Session ID: NHD5FTT50V3BVJ0V5F19
2026/01/27-22:29:25.240426 1 CURRENT file: CURRENT
2026/01/27-22:29:25.240428 1 IDENTITY file: IDENTITY
2026/01/27-22:29:25.240430 1 MANIFEST file: MANIFEST-000020 size: 941 Bytes
2026/01/27-22:29:25.240432 1 SST files in /data/db dir, Total Num: 1, files: 000014.sst
2026/01/27-22:29:25.240433 1 Write Ahead Log file in /data/db: 000019.log size: 101968 ;
2026/01/27-22:29:25.240437 1 Options.error_if_exists: 0
2026/01/27-22:29:25.240437 1 Options.create_if_missing: 1
2026/01/27-22:29:25.240438 1 Options.paranoid_checks: 1
2026/01/27-22:29:25.240438 1 Options.flush_verify_memtable_count: 1
2026/01/27-22:29:25.240439 1 Options.compaction_verify_record_count: 1
2026/01/27-22:29:25.240440 1 Options.track_and_verify_wals_in_manifest: 0
2026/01/27-22:29:25.240440 1 Options.track_and_verify_wals: 0
2026/01/27-22:29:25.240441 1 Options.verify_sst_unique_id_in_manifest: 1
2026/01/27-22:29:25.240441 1 Options.env: 0xffff8981d0c0
2026/01/27-22:29:25.240442 1 Options.fs: PosixFileSystem
2026/01/27-22:29:25.240442 1 Options.info_log: 0xffff8990f600
2026/01/27-22:29:25.240443 1 Options.max_file_opening_threads: 16
2026/01/27-22:29:25.240443 1 Options.statistics: 0xffff898135b0
2026/01/27-22:29:25.240444 1 Options.statistics stats level: 3
2026/01/27-22:29:25.240444 1 Options.use_fsync: 0
2026/01/27-22:29:25.240445 1 Options.max_log_file_size: 268435456
2026/01/27-22:29:25.240446 1 Options.max_manifest_file_size: 67108864
2026/01/27-22:29:25.240448 1 Options.log_file_time_to_roll: 0
2026/01/27-22:29:25.240449 1 Options.keep_log_file_num: 12
2026/01/27-22:29:25.240450 1 Options.recycle_log_file_num: 0
2026/01/27-22:29:25.240450 1 Options.allow_fallocate: 1
2026/01/27-22:29:25.240451 1 Options.allow_mmap_reads: 0
2026/01/27-22:29:25.240451 1 Options.allow_mmap_writes: 0
2026/01/27-22:29:25.240452 1 Options.use_direct_reads: 0
2026/01/27-22:29:25.240453 1 Options.use_direct_io_for_flush_and_compaction: 0
2026/01/27-22:29:25.240453 1 Options.create_missing_column_families: 1
2026/01/27-22:29:25.240454 1 Options.db_log_dir:
2026/01/27-22:29:25.240455 1 Options.wal_dir:
2026/01/27-22:29:25.240455 1 Options.table_cache_numshardbits: 6
2026/01/27-22:29:25.240456 1 Options.WAL_ttl_seconds: 10800
2026/01/27-22:29:25.240456 1 Options.WAL_size_limit_MB: 16384
2026/01/27-22:29:25.240457 1 Options.max_write_batch_group_size_bytes: 1048576
2026/01/27-22:29:25.240458 1 Options.manifest_preallocation_size: 4194304
2026/01/27-22:29:25.240458 1 Options.is_fd_close_on_exec: 1
2026/01/27-22:29:25.240459 1 Options.advise_random_on_open: 1
2026/01/27-22:29:25.240460 1 Options.db_write_buffer_size: 0
2026/01/27-22:29:25.240460 1 Options.write_buffer_manager: 0xffff8986bfc0
2026/01/27-22:29:25.240461 1 Options.use_adaptive_mutex: 0
2026/01/27-22:29:25.240461 1 Options.rate_limiter: 0xffff89836000
2026/01/27-22:29:25.240462 1 Options.sst_file_manager.rate_bytes_per_sec: 0
2026/01/27-22:29:25.240465 1 Options.wal_recovery_mode: 2
2026/01/27-22:29:25.240466 1 Options.enable_thread_tracking: 0
2026/01/27-22:29:25.240467 1 Options.enable_pipelined_write: 0
2026/01/27-22:29:25.240468 1 Options.unordered_write: 0
2026/01/27-22:29:25.240468 1 Options.allow_concurrent_memtable_write: 1
2026/01/27-22:29:25.240469 1 Options.enable_write_thread_adaptive_yield: 1
2026/01/27-22:29:25.240469 1 Options.write_thread_max_yield_usec: 100
2026/01/27-22:29:25.240470 1 Options.write_thread_slow_yield_usec: 3
2026/01/27-22:29:25.240471 1 Options.row_cache: None
2026/01/27-22:29:25.240471 1 Options.wal_filter: None
2026/01/27-22:29:25.240472 1 Options.avoid_flush_during_recovery: 0
2026/01/27-22:29:25.240472 1 Options.allow_ingest_behind: 0
2026/01/27-22:29:25.240473 1 Options.two_write_queues: 0
2026/01/27-22:29:25.240474 1 Options.manual_wal_flush: 0
2026/01/27-22:29:25.240474 1 Options.wal_compression: 0
2026/01/27-22:29:25.240475 1 Options.background_close_inactive_wals: 0
2026/01/27-22:29:25.240475 1 Options.atomic_flush: 0
2026/01/27-22:29:25.240476 1 Options.avoid_unnecessary_blocking_io: 1
2026/01/27-22:29:25.240476 1 Options.prefix_seek_opt_in_only: 0
2026/01/27-22:29:25.240477 1 Options.persist_stats_to_disk: 0
2026/01/27-22:29:25.240480 1 Options.write_dbid_to_manifest: 1
2026/01/27-22:29:25.240481 1 Options.write_identity_file: 1
2026/01/27-22:29:25.240481 1 Options.log_readahead_size: 0
2026/01/27-22:29:25.240482 1 Options.file_checksum_gen_factory: Unknown
2026/01/27-22:29:25.240483 1 Options.best_efforts_recovery: 0
2026/01/27-22:29:25.240483 1 Options.max_bgerror_resume_count: 2147483647
2026/01/27-22:29:25.240484 1 Options.bgerror_resume_retry_interval: 1000000
2026/01/27-22:29:25.240484 1 Options.allow_data_in_errors: 0
2026/01/27-22:29:25.240485 1 Options.db_host_id: __hostname__
2026/01/27-22:29:25.240485 1 Options.enforce_single_del_contracts: true
2026/01/27-22:29:25.240486 1 Options.metadata_write_temperature: kUnknown
2026/01/27-22:29:25.240487 1 Options.wal_write_temperature: kUnknown
2026/01/27-22:29:25.240487 1 Options.max_background_jobs: 4
2026/01/27-22:29:25.240488 1 Options.max_background_compactions: -1
2026/01/27-22:29:25.240488 1 Options.max_subcompactions: 2
2026/01/27-22:29:25.240489 1 Options.avoid_flush_during_shutdown: 0
2026/01/27-22:29:25.240489 1 Options.writable_file_max_buffer_size: 1048576
2026/01/27-22:29:25.240492 1 Options.delayed_write_rate : 536870912000
2026/01/27-22:29:25.240493 1 Options.max_total_wal_size: 536870912
2026/01/27-22:29:25.240494 1 Options.delete_obsolete_files_period_micros: 21600000000
2026/01/27-22:29:25.240494 1 Options.stats_dump_period_sec: 0
2026/01/27-22:29:25.240495 1 Options.stats_persist_period_sec: 600
2026/01/27-22:29:25.240495 1 Options.stats_history_buffer_size: 1048576
2026/01/27-22:29:25.240496 1 Options.max_open_files: 8096
2026/01/27-22:29:25.240496 1 Options.bytes_per_sync: 1048576
2026/01/27-22:29:25.240497 1 Options.wal_bytes_per_sync: 0
2026/01/27-22:29:25.240498 1 Options.strict_bytes_per_sync: 0
2026/01/27-22:29:25.240498 1 Options.compaction_readahead_size: 2097152
2026/01/27-22:29:25.240499 1 Options.max_background_flushes: -1
2026/01/27-22:29:25.240499 1 Options.daily_offpeak_time_utc:
2026/01/27-22:29:25.240500 1 Compression algorithms supported:
2026/01/27-22:29:25.240500 1 kCustomCompressionFE supported: 0
2026/01/27-22:29:25.240501 1 kCustomCompressionFC supported: 0
2026/01/27-22:29:25.240502 1 kCustomCompressionF8 supported: 0
2026/01/27-22:29:25.240502 1 kCustomCompressionF7 supported: 0
2026/01/27-22:29:25.240503 1 kCustomCompressionB2 supported: 0
2026/01/27-22:29:25.240503 1 kLZ4Compression supported: 1
2026/01/27-22:29:25.240506 1 kCustomCompression88 supported: 0
2026/01/27-22:29:25.240506 1 kCustomCompressionD8 supported: 0
2026/01/27-22:29:25.240507 1 kCustomCompression9F supported: 0
2026/01/27-22:29:25.240507 1 kCustomCompressionD6 supported: 0
2026/01/27-22:29:25.240508 1 kCustomCompressionA9 supported: 0
2026/01/27-22:29:25.240508 1 kCustomCompressionEC supported: 0
2026/01/27-22:29:25.240509 1 kCustomCompressionA3 supported: 0
2026/01/27-22:29:25.240509 1 kCustomCompressionCB supported: 0
2026/01/27-22:29:25.240510 1 kCustomCompression90 supported: 0
2026/01/27-22:29:25.240510 1 kCustomCompressionA0 supported: 0
2026/01/27-22:29:25.240511 1 kCustomCompressionC6 supported: 0
2026/01/27-22:29:25.240513 1 kCustomCompression9D supported: 0
2026/01/27-22:29:25.240514 1 kCustomCompression8B supported: 0
2026/01/27-22:29:25.240514 1 kCustomCompressionA8 supported: 0
2026/01/27-22:29:25.240515 1 kCustomCompression8D supported: 0
2026/01/27-22:29:25.240515 1 kCustomCompression97 supported: 0
2026/01/27-22:29:25.240516 1 kCustomCompression98 supported: 0
2026/01/27-22:29:25.240516 1 kCustomCompressionAC supported: 0
2026/01/27-22:29:25.240517 1 kCustomCompressionE9 supported: 0
2026/01/27-22:29:25.240517 1 kCustomCompression96 supported: 0
2026/01/27-22:29:25.240518 1 kCustomCompressionB1 supported: 0
2026/01/27-22:29:25.240518 1 kCustomCompression95 supported: 0
2026/01/27-22:29:25.240519 1 kCustomCompression84 supported: 0
2026/01/27-22:29:25.240519 1 kCustomCompression91 supported: 0
2026/01/27-22:29:25.240520 1 kCustomCompressionAB supported: 0
2026/01/27-22:29:25.240520 1 kCustomCompressionB3 supported: 0
2026/01/27-22:29:25.240521 1 kCustomCompression81 supported: 0
2026/01/27-22:29:25.240521 1 kCustomCompressionDC supported: 0
2026/01/27-22:29:25.240522 1 kBZip2Compression supported: 0
2026/01/27-22:29:25.240523 1 kCustomCompressionBB supported: 0
2026/01/27-22:29:25.240523 1 kCustomCompression9C supported: 0
2026/01/27-22:29:25.240524 1 kCustomCompressionC9 supported: 0
2026/01/27-22:29:25.240525 1 kCustomCompressionCC supported: 0
2026/01/27-22:29:25.240525 1 kCustomCompression92 supported: 0
2026/01/27-22:29:25.240526 1 kCustomCompressionB9 supported: 0
2026/01/27-22:29:25.240526 1 kCustomCompression8F supported: 0
2026/01/27-22:29:25.240527 1 kCustomCompression8A supported: 0
2026/01/27-22:29:25.240528 1 kCustomCompression9B supported: 0
2026/01/27-22:29:25.240528 1 kZSTD supported: 1
2026/01/27-22:29:25.240529 1 kCustomCompressionAA supported: 0
2026/01/27-22:29:25.240529 1 kCustomCompressionA2 supported: 0
2026/01/27-22:29:25.240530 1 kZlibCompression supported: 1
2026/01/27-22:29:25.240531 1 kXpressCompression supported: 0
2026/01/27-22:29:25.240532 1 kCustomCompressionFD supported: 0
2026/01/27-22:29:25.240532 1 kCustomCompressionE2 supported: 0
2026/01/27-22:29:25.240533 1 kLZ4HCCompression supported: 1
2026/01/27-22:29:25.240534 1 kCustomCompressionA6 supported: 0
2026/01/27-22:29:25.240534 1 kCustomCompression85 supported: 0
2026/01/27-22:29:25.240535 1 kCustomCompressionA4 supported: 0
2026/01/27-22:29:25.240536 1 kCustomCompression86 supported: 0
2026/01/27-22:29:25.240536 1 kCustomCompression83 supported: 0
2026/01/27-22:29:25.240537 1 kCustomCompression87 supported: 0
2026/01/27-22:29:25.240537 1 kCustomCompression89 supported: 0
2026/01/27-22:29:25.240538 1 kCustomCompression8C supported: 0
2026/01/27-22:29:25.240539 1 kCustomCompressionDB supported: 0
2026/01/27-22:29:25.240539 1 kCustomCompressionF3 supported: 0
2026/01/27-22:29:25.240540 1 kCustomCompressionE6 supported: 0
2026/01/27-22:29:25.240541 1 kCustomCompression8E supported: 0
2026/01/27-22:29:25.240541 1 kCustomCompressionDA supported: 0
2026/01/27-22:29:25.240542 1 kCustomCompression93 supported: 0
2026/01/27-22:29:25.240542 1 kCustomCompression94 supported: 0
2026/01/27-22:29:25.240545 1 kCustomCompression9E supported: 0
2026/01/27-22:29:25.240545 1 kCustomCompressionB4 supported: 0
2026/01/27-22:29:25.240546 1 kCustomCompressionFB supported: 0
2026/01/27-22:29:25.240547 1 kCustomCompressionB5 supported: 0
2026/01/27-22:29:25.240547 1 kCustomCompressionD5 supported: 0
2026/01/27-22:29:25.240548 1 kCustomCompressionB8 supported: 0
2026/01/27-22:29:25.240548 1 kCustomCompressionD1 supported: 0
2026/01/27-22:29:25.240549 1 kCustomCompressionBA supported: 0
2026/01/27-22:29:25.240550 1 kCustomCompressionBC supported: 0
2026/01/27-22:29:25.240550 1 kCustomCompressionCE supported: 0
2026/01/27-22:29:25.240551 1 kCustomCompressionBD supported: 0
2026/01/27-22:29:25.240551 1 kCustomCompressionC4 supported: 0
2026/01/27-22:29:25.240552 1 kCustomCompression9A supported: 0
2026/01/27-22:29:25.240553 1 kCustomCompression99 supported: 0
2026/01/27-22:29:25.240553 1 kCustomCompressionBE supported: 0
2026/01/27-22:29:25.240554 1 kCustomCompressionE5 supported: 0
2026/01/27-22:29:25.240554 1 kCustomCompressionD9 supported: 0
2026/01/27-22:29:25.240555 1 kCustomCompressionC1 supported: 0
2026/01/27-22:29:25.240555 1 kCustomCompressionC5 supported: 0
2026/01/27-22:29:25.240556 1 kCustomCompressionC2 supported: 0
2026/01/27-22:29:25.240557 1 kCustomCompressionA5 supported: 0
2026/01/27-22:29:25.240557 1 kCustomCompressionC7 supported: 0
2026/01/27-22:29:25.240558 1 kCustomCompressionBF supported: 0
2026/01/27-22:29:25.240558 1 kCustomCompressionE8 supported: 0
2026/01/27-22:29:25.240559 1 kCustomCompressionC8 supported: 0
2026/01/27-22:29:25.240559 1 kCustomCompressionAF supported: 0
2026/01/27-22:29:25.240560 1 kCustomCompressionCA supported: 0
2026/01/27-22:29:25.240561 1 kCustomCompressionCD supported: 0
2026/01/27-22:29:25.240561 1 kCustomCompressionC0 supported: 0
2026/01/27-22:29:25.240562 1 kCustomCompressionCF supported: 0
2026/01/27-22:29:25.240562 1 kCustomCompressionF9 supported: 0
2026/01/27-22:29:25.240563 1 kCustomCompressionD0 supported: 0
2026/01/27-22:29:25.240564 1 kCustomCompressionD2 supported: 0
2026/01/27-22:29:25.240564 1 kCustomCompressionAD supported: 0
2026/01/27-22:29:25.240565 1 kCustomCompressionD3 supported: 0
2026/01/27-22:29:25.240566 1 kCustomCompressionD4 supported: 0
2026/01/27-22:29:25.240566 1 kCustomCompressionD7 supported: 0
2026/01/27-22:29:25.240567 1 kCustomCompression82 supported: 0
2026/01/27-22:29:25.240567 1 kCustomCompressionDD supported: 0
2026/01/27-22:29:25.240568 1 kCustomCompressionC3 supported: 0
2026/01/27-22:29:25.240568 1 kCustomCompressionEE supported: 0
2026/01/27-22:29:25.240569 1 kCustomCompressionDE supported: 0
2026/01/27-22:29:25.240569 1 kCustomCompressionDF supported: 0
2026/01/27-22:29:25.240570 1 kCustomCompressionA7 supported: 0
2026/01/27-22:29:25.240570 1 kCustomCompressionE0 supported: 0
2026/01/27-22:29:25.240570 1 kCustomCompressionF1 supported: 0
2026/01/27-22:29:25.240571 1 kCustomCompressionE1 supported: 0
2026/01/27-22:29:25.240571 1 kCustomCompressionF5 supported: 0
2026/01/27-22:29:25.240572 1 kCustomCompression80 supported: 0
2026/01/27-22:29:25.240572 1 kCustomCompressionE3 supported: 0
2026/01/27-22:29:25.240573 1 kCustomCompressionE4 supported: 0
2026/01/27-22:29:25.240573 1 kCustomCompressionB0 supported: 0
2026/01/27-22:29:25.240574 1 kCustomCompressionEA supported: 0
2026/01/27-22:29:25.240574 1 kCustomCompressionFA supported: 0
2026/01/27-22:29:25.240575 1 kCustomCompressionE7 supported: 0
2026/01/27-22:29:25.240575 1 kCustomCompressionAE supported: 0
2026/01/27-22:29:25.240576 1 kCustomCompressionEB supported: 0
2026/01/27-22:29:25.240576 1 kCustomCompressionED supported: 0
2026/01/27-22:29:25.240577 1 kCustomCompressionB6 supported: 0
2026/01/27-22:29:25.240579 1 kCustomCompressionEF supported: 0
2026/01/27-22:29:25.240580 1 kCustomCompressionF0 supported: 0
2026/01/27-22:29:25.240580 1 kCustomCompressionB7 supported: 0
2026/01/27-22:29:25.240581 1 kCustomCompressionF2 supported: 0
2026/01/27-22:29:25.240581 1 kCustomCompressionA1 supported: 0
2026/01/27-22:29:25.240582 1 kCustomCompressionF4 supported: 0
2026/01/27-22:29:25.240582 1 kSnappyCompression supported: 1
2026/01/27-22:29:25.240583 1 kCustomCompressionF6 supported: 0
2026/01/27-22:29:25.240584 1 Fast CRC32 supported: Supported on Arm64
2026/01/27-22:29:25.240584 1 DMutex implementation: pthread_mutex_t
2026/01/27-22:29:25.240585 1 Jemalloc supported: 1
2026/01/27-22:29:25.241055 1 [db/version_set.cc:6190] Recovering from manifest file: /data/db/MANIFEST-000020
2026/01/27-22:29:25.241240 1 [db/column_family.cc:693] --------------- Options for column family [default]:
2026/01/27-22:29:25.241243 1 Options.comparator: leveldb.BytewiseComparator
2026/01/27-22:29:25.241244 1 Options.merge_operator: None
2026/01/27-22:29:25.241245 1 Options.compaction_filter: None
2026/01/27-22:29:25.241246 1 Options.compaction_filter_factory: None
2026/01/27-22:29:25.241246 1 Options.sst_partitioner_factory: None
2026/01/27-22:29:25.241247 1 Options.memtable_factory: SkipListFactory
2026/01/27-22:29:25.241248 1 Options.table_factory: BlockBasedTable
2026/01/27-22:29:25.241278 1 table_factory options: flush_block_policy_factory: FlushBlockBySizePolicyFactory (0xffff898d15a0)
cache_index_and_filter_blocks: 0
cache_index_and_filter_blocks_with_high_priority: 1
pin_l0_filter_and_index_blocks_in_cache: 0
pin_top_level_index_and_filter: 1
index_type: 0
data_block_index_type: 0
index_shortening: 1
data_block_hash_table_util_ratio: 0.750000
checksum: 4
no_block_cache: 0
block_cache: 0xffff8986bd90
block_cache_name: LRUCache
block_cache_options:
capacity : 33554432
num_shard_bits : 6
strict_capacity_limit : 0
memory_allocator : None
high_pri_pool_ratio: 0.500
low_pri_pool_ratio: 0.000
persistent_cache: (nil)
block_size: 4096
block_size_deviation: 10
block_restart_interval: 16
index_block_restart_interval: 1
metadata_block_size: 4096
partition_filters: 0
use_delta_encoding: 1
filter_policy: nullptr
user_defined_index_factory: nullptr
fail_if_no_udi_on_open: 0
whole_key_filtering: 1
verify_compression: 0
read_amp_bytes_per_bit: 0
format_version: 6
enable_index_compression: 1
block_align: 0
max_auto_readahead_size: 262144
prepopulate_block_cache: 0
initial_auto_readahead_size: 8192
num_file_reads_for_auto_readahead: 2
2026/01/27-22:29:25.241289 1 Options.write_buffer_size: 67108864
2026/01/27-22:29:25.241289 1 Options.max_write_buffer_number: 4
2026/01/27-22:29:25.241291 1 Options.compression[0]: NoCompression
2026/01/27-22:29:25.241291 1 Options.compression[1]: NoCompression
2026/01/27-22:29:25.241292 1 Options.compression[2]: Snappy
2026/01/27-22:29:25.241292 1 Options.compression[3]: Snappy
2026/01/27-22:29:25.241293 1 Options.compression[4]: Snappy
2026/01/27-22:29:25.241293 1 Options.compression[5]: Snappy
2026/01/27-22:29:25.241294 1 Options.compression[6]: Snappy
2026/01/27-22:29:25.241294 1 Options.bottommost_compression: Disabled
2026/01/27-22:29:25.241295 1 Options.prefix_extractor: nullptr
2026/01/27-22:29:25.241295 1 Options.memtable_insert_with_hint_prefix_extractor: nullptr
2026/01/27-22:29:25.241296 1 Options.num_levels: 7
2026/01/27-22:29:25.241296 1 Options.min_write_buffer_number_to_merge: 1
2026/01/27-22:29:25.241297 1 Options.max_write_buffer_size_to_maintain: 0
2026/01/27-22:29:25.241297 1 Options.bottommost_compression_opts.window_bits: -14
2026/01/27-22:29:25.241298 1 Options.bottommost_compression_opts.level: 32767
2026/01/27-22:29:25.241298 1 Options.bottommost_compression_opts.strategy: 0
2026/01/27-22:29:25.241299 1 Options.bottommost_compression_opts.max_dict_bytes: 0
2026/01/27-22:29:25.241299 1 Options.bottommost_compression_opts.zstd_max_train_bytes: 0
2026/01/27-22:29:25.241300 1 Options.bottommost_compression_opts.parallel_threads: 1
2026/01/27-22:29:25.241301 1 Options.bottommost_compression_opts.enabled: false
2026/01/27-22:29:25.241301 1 Options.bottommost_compression_opts.max_dict_buffer_bytes: 0
2026/01/27-22:29:25.241302 1 Options.bottommost_compression_opts.use_zstd_dict_trainer: true
2026/01/27-22:29:25.241302 1 Options.compression_opts.window_bits: -14
2026/01/27-22:29:25.241303 1 Options.compression_opts.level: 32767
2026/01/27-22:29:25.241303 1 Options.compression_opts.strategy: 0
2026/01/27-22:29:25.241304 1 Options.compression_opts.max_dict_bytes: 0
2026/01/27-22:29:25.241304 1 Options.compression_opts.zstd_max_train_bytes: 0
2026/01/27-22:29:25.241304 1 Options.compression_opts.use_zstd_dict_trainer: true
2026/01/27-22:29:25.241305 1 Options.compression_opts.parallel_threads: 1
2026/01/27-22:29:25.241305 1 Options.compression_opts.enabled: false
2026/01/27-22:29:25.241306 1 Options.compression_opts.max_dict_buffer_bytes: 0
2026/01/27-22:29:25.241306 1 Options.level0_file_num_compaction_trigger: 4
2026/01/27-22:29:25.241307 1 Options.level0_slowdown_writes_trigger: 20
2026/01/27-22:29:25.241307 1 Options.level0_stop_writes_trigger: 40
2026/01/27-22:29:25.241308 1 Options.target_file_size_base: 134217728
2026/01/27-22:29:25.241308 1 Options.target_file_size_multiplier: 1
2026/01/27-22:29:25.241309 1 Options.max_bytes_for_level_base: 268435456
2026/01/27-22:29:25.241309 1 Options.level_compaction_dynamic_level_bytes: 1
2026/01/27-22:29:25.241310 1 Options.max_bytes_for_level_multiplier: 10.000000
2026/01/27-22:29:25.241311 1 Options.max_bytes_for_level_multiplier_addtl[0]: 1
2026/01/27-22:29:25.241311 1 Options.max_bytes_for_level_multiplier_addtl[1]: 1
2026/01/27-22:29:25.241312 1 Options.max_bytes_for_level_multiplier_addtl[2]: 1
2026/01/27-22:29:25.241312 1 Options.max_bytes_for_level_multiplier_addtl[3]: 1
2026/01/27-22:29:25.241313 1 Options.max_bytes_for_level_multiplier_addtl[4]: 1
2026/01/27-22:29:25.241313 1 Options.max_bytes_for_level_multiplier_addtl[5]: 1
2026/01/27-22:29:25.241314 1 Options.max_bytes_for_level_multiplier_addtl[6]: 1
2026/01/27-22:29:25.241314 1 Options.max_sequential_skip_in_iterations: 8
2026/01/27-22:29:25.241315 1 Options.memtable_op_scan_flush_trigger: 0
2026/01/27-22:29:25.241315 1 Options.memtable_avg_op_scan_flush_trigger: 0
2026/01/27-22:29:25.241316 1 Options.max_compaction_bytes: 3355443200
2026/01/27-22:29:25.241316 1 Options.arena_block_size: 1048576
2026/01/27-22:29:25.241317 1 Options.soft_pending_compaction_bytes_limit: 68719476736
2026/01/27-22:29:25.241317 1 Options.hard_pending_compaction_bytes_limit: 274877906944
2026/01/27-22:29:25.241318 1 Options.disable_auto_compactions: 0
2026/01/27-22:29:25.241318 1 Options.compaction_style: kCompactionStyleLevel
2026/01/27-22:29:25.241319 1 Options.compaction_pri: kMinOverlappingRatio
2026/01/27-22:29:25.241319 1 Options.compaction_options_universal.size_ratio: 1
2026/01/27-22:29:25.241320 1 Options.compaction_options_universal.min_merge_width: 2
2026/01/27-22:29:25.241320 1 Options.compaction_options_universal.max_merge_width: 4294967295
2026/01/27-22:29:25.241321 1 Options.compaction_options_universal.max_size_amplification_percent: 200
2026/01/27-22:29:25.241322 1 Options.compaction_options_universal.compression_size_percent: -1
2026/01/27-22:29:25.241322 1 Options.compaction_options_universal.stop_style: kCompactionStopStyleTotalSize
2026/01/27-22:29:25.241323 1 Options.compaction_options_universal.max_read_amp: -1
2026/01/27-22:29:25.241323 1 Options.compaction_options_universal.reduce_file_locking: 0
2026/01/27-22:29:25.241324 1 Options.compaction_options_fifo.max_table_files_size: 1073741824
2026/01/27-22:29:25.241324 1 Options.compaction_options_fifo.allow_compaction: 0
2026/01/27-22:29:25.241326 1 Options.table_properties_collectors:
2026/01/27-22:29:25.241327 1 Options.inplace_update_support: 0
2026/01/27-22:29:25.241327 1 Options.inplace_update_num_locks: 10000
2026/01/27-22:29:25.241328 1 Options.memtable_prefix_bloom_size_ratio: 0.000000
2026/01/27-22:29:25.241329 1 Options.memtable_whole_key_filtering: 0
2026/01/27-22:29:25.241329 1 Options.memtable_huge_page_size: 0
2026/01/27-22:29:25.241329 1 Options.bloom_locality: 0
2026/01/27-22:29:25.241330 1 Options.max_successive_merges: 0
2026/01/27-22:29:25.241330 1 Options.strict_max_successive_merges: 0
2026/01/27-22:29:25.241331 1 Options.optimize_filters_for_hits: 0
2026/01/27-22:29:25.241331 1 Options.paranoid_file_checks: 0
2026/01/27-22:29:25.241332 1 Options.force_consistency_checks: 1
2026/01/27-22:29:25.241332 1 Options.report_bg_io_stats: 0
2026/01/27-22:29:25.241333 1 Options.disallow_memtable_writes: 0
2026/01/27-22:29:25.241333 1 Options.ttl: 2592000
2026/01/27-22:29:25.241334 1 Options.periodic_compaction_seconds: 0
2026/01/27-22:29:25.241334 1 Options.default_temperature: kUnknown
2026/01/27-22:29:25.241335 1 Options.preclude_last_level_data_seconds: 0
2026/01/27-22:29:25.241335 1 Options.preserve_internal_time_seconds: 0
2026/01/27-22:29:25.241336 1 Options.enable_blob_files: false
2026/01/27-22:29:25.241336 1 Options.min_blob_size: 0
2026/01/27-22:29:25.241337 1 Options.blob_file_size: 268435456
2026/01/27-22:29:25.241337 1 Options.blob_compression_type: NoCompression
2026/01/27-22:29:25.241338 1 Options.enable_blob_garbage_collection: false
2026/01/27-22:29:25.241338 1 Options.blob_garbage_collection_age_cutoff: 0.250000
2026/01/27-22:29:25.241339 1 Options.blob_garbage_collection_force_threshold: 1.000000
2026/01/27-22:29:25.241340 1 Options.blob_compaction_readahead_size: 0
2026/01/27-22:29:25.241340 1 Options.blob_file_starting_level: 0
2026/01/27-22:29:25.241341 1 Options.experimental_mempurge_threshold: 0.000000
2026/01/27-22:29:25.241341 1 Options.memtable_max_range_deletions: 0
2026/01/27-22:29:25.241342 1 Options.cf_allow_ingest_behind: false
2026/01/27-22:29:25.242680 1 [WARN] [db/db_impl/db_impl_open.cc:2688] DB::Open() failed: Invalid argument: Column families not opened: index, search, stream, propagate, pubsub, zset_score, metadata
2026/01/27-22:29:25.242737 1 [db/db_impl/db_impl.cc:467] Shutdown: canceling all background work
2026/01/27-22:29:25.242782 1 [db/db_impl/db_impl.cc:681] Shutdown complete

File diff suppressed because it is too large Load Diff

View File

@@ -1,391 +0,0 @@
2026/01/27-22:29:39.168333 1 RocksDB version: 10.6.2
2026/01/27-22:29:39.168394 1 Git sha 0
2026/01/27-22:29:39.168395 1 Compile date 2025-11-08 14:59:16
2026/01/27-22:29:39.168396 1 DB SUMMARY
2026/01/27-22:29:39.168397 1 Host name (Env): db98b7caa3d9
2026/01/27-22:29:39.168398 1 DB Session ID: M5A8FG11TXBDE6GONB6Y
2026/01/27-22:29:39.168483 1 CURRENT file: CURRENT
2026/01/27-22:29:39.168484 1 IDENTITY file: IDENTITY
2026/01/27-22:29:39.168485 1 MANIFEST file: MANIFEST-000025 size: 1092 Bytes
2026/01/27-22:29:39.168487 1 SST files in /data/db dir, Total Num: 2, files: 000014.sst 000023.sst
2026/01/27-22:29:39.168487 1 Write Ahead Log file in /data/db: 000024.log size: 0 ;
2026/01/27-22:29:39.168488 1 Options.error_if_exists: 0
2026/01/27-22:29:39.168489 1 Options.create_if_missing: 1
2026/01/27-22:29:39.168489 1 Options.paranoid_checks: 1
2026/01/27-22:29:39.168490 1 Options.flush_verify_memtable_count: 1
2026/01/27-22:29:39.168490 1 Options.compaction_verify_record_count: 1
2026/01/27-22:29:39.168491 1 Options.track_and_verify_wals_in_manifest: 0
2026/01/27-22:29:39.168491 1 Options.track_and_verify_wals: 0
2026/01/27-22:29:39.168492 1 Options.verify_sst_unique_id_in_manifest: 1
2026/01/27-22:29:39.168492 1 Options.env: 0xffff9a21d0c0
2026/01/27-22:29:39.168493 1 Options.fs: PosixFileSystem
2026/01/27-22:29:39.168493 1 Options.info_log: 0xffff9a30f600
2026/01/27-22:29:39.168494 1 Options.max_file_opening_threads: 16
2026/01/27-22:29:39.168494 1 Options.statistics: 0xffff9a2135b0
2026/01/27-22:29:39.168495 1 Options.statistics stats level: 3
2026/01/27-22:29:39.168495 1 Options.use_fsync: 0
2026/01/27-22:29:39.168496 1 Options.max_log_file_size: 268435456
2026/01/27-22:29:39.168496 1 Options.max_manifest_file_size: 67108864
2026/01/27-22:29:39.168497 1 Options.log_file_time_to_roll: 0
2026/01/27-22:29:39.168497 1 Options.keep_log_file_num: 12
2026/01/27-22:29:39.168498 1 Options.recycle_log_file_num: 0
2026/01/27-22:29:39.168498 1 Options.allow_fallocate: 1
2026/01/27-22:29:39.168499 1 Options.allow_mmap_reads: 0
2026/01/27-22:29:39.168499 1 Options.allow_mmap_writes: 0
2026/01/27-22:29:39.168500 1 Options.use_direct_reads: 0
2026/01/27-22:29:39.168500 1 Options.use_direct_io_for_flush_and_compaction: 0
2026/01/27-22:29:39.168501 1 Options.create_missing_column_families: 1
2026/01/27-22:29:39.168501 1 Options.db_log_dir:
2026/01/27-22:29:39.168502 1 Options.wal_dir:
2026/01/27-22:29:39.168502 1 Options.table_cache_numshardbits: 6
2026/01/27-22:29:39.168503 1 Options.WAL_ttl_seconds: 10800
2026/01/27-22:29:39.168503 1 Options.WAL_size_limit_MB: 16384
2026/01/27-22:29:39.168504 1 Options.max_write_batch_group_size_bytes: 1048576
2026/01/27-22:29:39.168504 1 Options.manifest_preallocation_size: 4194304
2026/01/27-22:29:39.168505 1 Options.is_fd_close_on_exec: 1
2026/01/27-22:29:39.168505 1 Options.advise_random_on_open: 1
2026/01/27-22:29:39.168506 1 Options.db_write_buffer_size: 0
2026/01/27-22:29:39.168506 1 Options.write_buffer_manager: 0xffff9a26bfc0
2026/01/27-22:29:39.168507 1 Options.use_adaptive_mutex: 0
2026/01/27-22:29:39.168507 1 Options.rate_limiter: 0xffff9a236000
2026/01/27-22:29:39.168508 1 Options.sst_file_manager.rate_bytes_per_sec: 0
2026/01/27-22:29:39.168508 1 Options.wal_recovery_mode: 2
2026/01/27-22:29:39.168509 1 Options.enable_thread_tracking: 0
2026/01/27-22:29:39.168509 1 Options.enable_pipelined_write: 0
2026/01/27-22:29:39.168509 1 Options.unordered_write: 0
2026/01/27-22:29:39.168510 1 Options.allow_concurrent_memtable_write: 1
2026/01/27-22:29:39.168510 1 Options.enable_write_thread_adaptive_yield: 1
2026/01/27-22:29:39.168511 1 Options.write_thread_max_yield_usec: 100
2026/01/27-22:29:39.168511 1 Options.write_thread_slow_yield_usec: 3
2026/01/27-22:29:39.168512 1 Options.row_cache: None
2026/01/27-22:29:39.168512 1 Options.wal_filter: None
2026/01/27-22:29:39.168513 1 Options.avoid_flush_during_recovery: 0
2026/01/27-22:29:39.168513 1 Options.allow_ingest_behind: 0
2026/01/27-22:29:39.168514 1 Options.two_write_queues: 0
2026/01/27-22:29:39.168514 1 Options.manual_wal_flush: 0
2026/01/27-22:29:39.168515 1 Options.wal_compression: 0
2026/01/27-22:29:39.168515 1 Options.background_close_inactive_wals: 0
2026/01/27-22:29:39.168516 1 Options.atomic_flush: 0
2026/01/27-22:29:39.168516 1 Options.avoid_unnecessary_blocking_io: 1
2026/01/27-22:29:39.168516 1 Options.prefix_seek_opt_in_only: 0
2026/01/27-22:29:39.168517 1 Options.persist_stats_to_disk: 0
2026/01/27-22:29:39.168517 1 Options.write_dbid_to_manifest: 1
2026/01/27-22:29:39.168518 1 Options.write_identity_file: 1
2026/01/27-22:29:39.168518 1 Options.log_readahead_size: 0
2026/01/27-22:29:39.168519 1 Options.file_checksum_gen_factory: Unknown
2026/01/27-22:29:39.168519 1 Options.best_efforts_recovery: 0
2026/01/27-22:29:39.168520 1 Options.max_bgerror_resume_count: 2147483647
2026/01/27-22:29:39.168520 1 Options.bgerror_resume_retry_interval: 1000000
2026/01/27-22:29:39.168521 1 Options.allow_data_in_errors: 0
2026/01/27-22:29:39.168521 1 Options.db_host_id: __hostname__
2026/01/27-22:29:39.168522 1 Options.enforce_single_del_contracts: true
2026/01/27-22:29:39.168523 1 Options.metadata_write_temperature: kUnknown
2026/01/27-22:29:39.168523 1 Options.wal_write_temperature: kUnknown
2026/01/27-22:29:39.168523 1 Options.max_background_jobs: 4
2026/01/27-22:29:39.168524 1 Options.max_background_compactions: -1
2026/01/27-22:29:39.168524 1 Options.max_subcompactions: 2
2026/01/27-22:29:39.168525 1 Options.avoid_flush_during_shutdown: 0
2026/01/27-22:29:39.168525 1 Options.writable_file_max_buffer_size: 1048576
2026/01/27-22:29:39.168526 1 Options.delayed_write_rate : 536870912000
2026/01/27-22:29:39.168527 1 Options.max_total_wal_size: 536870912
2026/01/27-22:29:39.168528 1 Options.delete_obsolete_files_period_micros: 21600000000
2026/01/27-22:29:39.168528 1 Options.stats_dump_period_sec: 0
2026/01/27-22:29:39.168528 1 Options.stats_persist_period_sec: 600
2026/01/27-22:29:39.168529 1 Options.stats_history_buffer_size: 1048576
2026/01/27-22:29:39.168530 1 Options.max_open_files: 8096
2026/01/27-22:29:39.168530 1 Options.bytes_per_sync: 1048576
2026/01/27-22:29:39.168531 1 Options.wal_bytes_per_sync: 0
2026/01/27-22:29:39.168531 1 Options.strict_bytes_per_sync: 0
2026/01/27-22:29:39.168532 1 Options.compaction_readahead_size: 2097152
2026/01/27-22:29:39.168532 1 Options.max_background_flushes: -1
2026/01/27-22:29:39.168533 1 Options.daily_offpeak_time_utc:
2026/01/27-22:29:39.168533 1 Compression algorithms supported:
2026/01/27-22:29:39.168534 1 kCustomCompressionFE supported: 0
2026/01/27-22:29:39.168534 1 kCustomCompressionFC supported: 0
2026/01/27-22:29:39.168535 1 kCustomCompressionF8 supported: 0
2026/01/27-22:29:39.168535 1 kCustomCompressionF7 supported: 0
2026/01/27-22:29:39.168536 1 kCustomCompressionB2 supported: 0
2026/01/27-22:29:39.168537 1 kLZ4Compression supported: 1
2026/01/27-22:29:39.168537 1 kCustomCompression88 supported: 0
2026/01/27-22:29:39.168538 1 kCustomCompressionD8 supported: 0
2026/01/27-22:29:39.168538 1 kCustomCompression9F supported: 0
2026/01/27-22:29:39.168539 1 kCustomCompressionD6 supported: 0
2026/01/27-22:29:39.168539 1 kCustomCompressionA9 supported: 0
2026/01/27-22:29:39.168540 1 kCustomCompressionEC supported: 0
2026/01/27-22:29:39.168540 1 kCustomCompressionA3 supported: 0
2026/01/27-22:29:39.168541 1 kCustomCompressionCB supported: 0
2026/01/27-22:29:39.168541 1 kCustomCompression90 supported: 0
2026/01/27-22:29:39.168542 1 kCustomCompressionA0 supported: 0
2026/01/27-22:29:39.168542 1 kCustomCompressionC6 supported: 0
2026/01/27-22:29:39.168543 1 kCustomCompression9D supported: 0
2026/01/27-22:29:39.168543 1 kCustomCompression8B supported: 0
2026/01/27-22:29:39.168544 1 kCustomCompressionA8 supported: 0
2026/01/27-22:29:39.168544 1 kCustomCompression8D supported: 0
2026/01/27-22:29:39.168545 1 kCustomCompression97 supported: 0
2026/01/27-22:29:39.168545 1 kCustomCompression98 supported: 0
2026/01/27-22:29:39.168546 1 kCustomCompressionAC supported: 0
2026/01/27-22:29:39.168546 1 kCustomCompressionE9 supported: 0
2026/01/27-22:29:39.168547 1 kCustomCompression96 supported: 0
2026/01/27-22:29:39.168547 1 kCustomCompressionB1 supported: 0
2026/01/27-22:29:39.168548 1 kCustomCompression95 supported: 0
2026/01/27-22:29:39.168548 1 kCustomCompression84 supported: 0
2026/01/27-22:29:39.168549 1 kCustomCompression91 supported: 0
2026/01/27-22:29:39.168549 1 kCustomCompressionAB supported: 0
2026/01/27-22:29:39.168550 1 kCustomCompressionB3 supported: 0
2026/01/27-22:29:39.168550 1 kCustomCompression81 supported: 0
2026/01/27-22:29:39.168551 1 kCustomCompressionDC supported: 0
2026/01/27-22:29:39.168551 1 kBZip2Compression supported: 0
2026/01/27-22:29:39.168552 1 kCustomCompressionBB supported: 0
2026/01/27-22:29:39.168552 1 kCustomCompression9C supported: 0
2026/01/27-22:29:39.168553 1 kCustomCompressionC9 supported: 0
2026/01/27-22:29:39.168553 1 kCustomCompressionCC supported: 0
2026/01/27-22:29:39.168554 1 kCustomCompression92 supported: 0
2026/01/27-22:29:39.168554 1 kCustomCompressionB9 supported: 0
2026/01/27-22:29:39.168554 1 kCustomCompression8F supported: 0
2026/01/27-22:29:39.168555 1 kCustomCompression8A supported: 0
2026/01/27-22:29:39.168555 1 kCustomCompression9B supported: 0
2026/01/27-22:29:39.168556 1 kZSTD supported: 1
2026/01/27-22:29:39.168556 1 kCustomCompressionAA supported: 0
2026/01/27-22:29:39.168557 1 kCustomCompressionA2 supported: 0
2026/01/27-22:29:39.168558 1 kZlibCompression supported: 1
2026/01/27-22:29:39.168558 1 kXpressCompression supported: 0
2026/01/27-22:29:39.168559 1 kCustomCompressionFD supported: 0
2026/01/27-22:29:39.168559 1 kCustomCompressionE2 supported: 0
2026/01/27-22:29:39.168560 1 kLZ4HCCompression supported: 1
2026/01/27-22:29:39.168560 1 kCustomCompressionA6 supported: 0
2026/01/27-22:29:39.168561 1 kCustomCompression85 supported: 0
2026/01/27-22:29:39.168561 1 kCustomCompressionA4 supported: 0
2026/01/27-22:29:39.168562 1 kCustomCompression86 supported: 0
2026/01/27-22:29:39.168562 1 kCustomCompression83 supported: 0
2026/01/27-22:29:39.168562 1 kCustomCompression87 supported: 0
2026/01/27-22:29:39.168563 1 kCustomCompression89 supported: 0
2026/01/27-22:29:39.168563 1 kCustomCompression8C supported: 0
2026/01/27-22:29:39.168564 1 kCustomCompressionDB supported: 0
2026/01/27-22:29:39.168564 1 kCustomCompressionF3 supported: 0
2026/01/27-22:29:39.168565 1 kCustomCompressionE6 supported: 0
2026/01/27-22:29:39.168565 1 kCustomCompression8E supported: 0
2026/01/27-22:29:39.168566 1 kCustomCompressionDA supported: 0
2026/01/27-22:29:39.168566 1 kCustomCompression93 supported: 0
2026/01/27-22:29:39.168567 1 kCustomCompression94 supported: 0
2026/01/27-22:29:39.168570 1 kCustomCompression9E supported: 0
2026/01/27-22:29:39.168570 1 kCustomCompressionB4 supported: 0
2026/01/27-22:29:39.168571 1 kCustomCompressionFB supported: 0
2026/01/27-22:29:39.168571 1 kCustomCompressionB5 supported: 0
2026/01/27-22:29:39.168572 1 kCustomCompressionD5 supported: 0
2026/01/27-22:29:39.168572 1 kCustomCompressionB8 supported: 0
2026/01/27-22:29:39.168572 1 kCustomCompressionD1 supported: 0
2026/01/27-22:29:39.168573 1 kCustomCompressionBA supported: 0
2026/01/27-22:29:39.168573 1 kCustomCompressionBC supported: 0
2026/01/27-22:29:39.168574 1 kCustomCompressionCE supported: 0
2026/01/27-22:29:39.168574 1 kCustomCompressionBD supported: 0
2026/01/27-22:29:39.168575 1 kCustomCompressionC4 supported: 0
2026/01/27-22:29:39.168575 1 kCustomCompression9A supported: 0
2026/01/27-22:29:39.168577 1 kCustomCompression99 supported: 0
2026/01/27-22:29:39.168578 1 kCustomCompressionBE supported: 0
2026/01/27-22:29:39.168578 1 kCustomCompressionE5 supported: 0
2026/01/27-22:29:39.168579 1 kCustomCompressionD9 supported: 0
2026/01/27-22:29:39.168579 1 kCustomCompressionC1 supported: 0
2026/01/27-22:29:39.168580 1 kCustomCompressionC5 supported: 0
2026/01/27-22:29:39.168580 1 kCustomCompressionC2 supported: 0
2026/01/27-22:29:39.168581 1 kCustomCompressionA5 supported: 0
2026/01/27-22:29:39.168581 1 kCustomCompressionC7 supported: 0
2026/01/27-22:29:39.168581 1 kCustomCompressionBF supported: 0
2026/01/27-22:29:39.168582 1 kCustomCompressionE8 supported: 0
2026/01/27-22:29:39.168582 1 kCustomCompressionC8 supported: 0
2026/01/27-22:29:39.168583 1 kCustomCompressionAF supported: 0
2026/01/27-22:29:39.168583 1 kCustomCompressionCA supported: 0
2026/01/27-22:29:39.168584 1 kCustomCompressionCD supported: 0
2026/01/27-22:29:39.168584 1 kCustomCompressionC0 supported: 0
2026/01/27-22:29:39.168585 1 kCustomCompressionCF supported: 0
2026/01/27-22:29:39.168585 1 kCustomCompressionF9 supported: 0
2026/01/27-22:29:39.168586 1 kCustomCompressionD0 supported: 0
2026/01/27-22:29:39.168586 1 kCustomCompressionD2 supported: 0
2026/01/27-22:29:39.168587 1 kCustomCompressionAD supported: 0
2026/01/27-22:29:39.168587 1 kCustomCompressionD3 supported: 0
2026/01/27-22:29:39.168588 1 kCustomCompressionD4 supported: 0
2026/01/27-22:29:39.168588 1 kCustomCompressionD7 supported: 0
2026/01/27-22:29:39.168589 1 kCustomCompression82 supported: 0
2026/01/27-22:29:39.168589 1 kCustomCompressionDD supported: 0
2026/01/27-22:29:39.168590 1 kCustomCompressionC3 supported: 0
2026/01/27-22:29:39.168590 1 kCustomCompressionEE supported: 0
2026/01/27-22:29:39.168591 1 kCustomCompressionDE supported: 0
2026/01/27-22:29:39.168591 1 kCustomCompressionDF supported: 0
2026/01/27-22:29:39.168592 1 kCustomCompressionA7 supported: 0
2026/01/27-22:29:39.168592 1 kCustomCompressionE0 supported: 0
2026/01/27-22:29:39.168593 1 kCustomCompressionF1 supported: 0
2026/01/27-22:29:39.168593 1 kCustomCompressionE1 supported: 0
2026/01/27-22:29:39.168594 1 kCustomCompressionF5 supported: 0
2026/01/27-22:29:39.168594 1 kCustomCompression80 supported: 0
2026/01/27-22:29:39.168595 1 kCustomCompressionE3 supported: 0
2026/01/27-22:29:39.168595 1 kCustomCompressionE4 supported: 0
2026/01/27-22:29:39.168596 1 kCustomCompressionB0 supported: 0
2026/01/27-22:29:39.168596 1 kCustomCompressionEA supported: 0
2026/01/27-22:29:39.168597 1 kCustomCompressionFA supported: 0
2026/01/27-22:29:39.168597 1 kCustomCompressionE7 supported: 0
2026/01/27-22:29:39.168598 1 kCustomCompressionAE supported: 0
2026/01/27-22:29:39.168598 1 kCustomCompressionEB supported: 0
2026/01/27-22:29:39.168598 1 kCustomCompressionED supported: 0
2026/01/27-22:29:39.168599 1 kCustomCompressionB6 supported: 0
2026/01/27-22:29:39.168599 1 kCustomCompressionEF supported: 0
2026/01/27-22:29:39.168600 1 kCustomCompressionF0 supported: 0
2026/01/27-22:29:39.168600 1 kCustomCompressionB7 supported: 0
2026/01/27-22:29:39.168601 1 kCustomCompressionF2 supported: 0
2026/01/27-22:29:39.168601 1 kCustomCompressionA1 supported: 0
2026/01/27-22:29:39.168602 1 kCustomCompressionF4 supported: 0
2026/01/27-22:29:39.168602 1 kSnappyCompression supported: 1
2026/01/27-22:29:39.168603 1 kCustomCompressionF6 supported: 0
2026/01/27-22:29:39.168604 1 Fast CRC32 supported: Supported on Arm64
2026/01/27-22:29:39.168605 1 DMutex implementation: pthread_mutex_t
2026/01/27-22:29:39.168605 1 Jemalloc supported: 1
2026/01/27-22:29:39.168746 1 [db/version_set.cc:6190] Recovering from manifest file: /data/db/MANIFEST-000025
2026/01/27-22:29:39.168844 1 [db/column_family.cc:693] --------------- Options for column family [default]:
2026/01/27-22:29:39.168846 1 Options.comparator: leveldb.BytewiseComparator
2026/01/27-22:29:39.168846 1 Options.merge_operator: None
2026/01/27-22:29:39.168847 1 Options.compaction_filter: None
2026/01/27-22:29:39.168847 1 Options.compaction_filter_factory: None
2026/01/27-22:29:39.168848 1 Options.sst_partitioner_factory: None
2026/01/27-22:29:39.168848 1 Options.memtable_factory: SkipListFactory
2026/01/27-22:29:39.168849 1 Options.table_factory: BlockBasedTable
2026/01/27-22:29:39.168863 1 table_factory options: flush_block_policy_factory: FlushBlockBySizePolicyFactory (0xffff9a2d15a0)
cache_index_and_filter_blocks: 0
cache_index_and_filter_blocks_with_high_priority: 1
pin_l0_filter_and_index_blocks_in_cache: 0
pin_top_level_index_and_filter: 1
index_type: 0
data_block_index_type: 0
index_shortening: 1
data_block_hash_table_util_ratio: 0.750000
checksum: 4
no_block_cache: 0
block_cache: 0xffff9a26bd90
block_cache_name: LRUCache
block_cache_options:
capacity : 33554432
num_shard_bits : 6
strict_capacity_limit : 0
memory_allocator : None
high_pri_pool_ratio: 0.500
low_pri_pool_ratio: 0.000
persistent_cache: (nil)
block_size: 4096
block_size_deviation: 10
block_restart_interval: 16
index_block_restart_interval: 1
metadata_block_size: 4096
partition_filters: 0
use_delta_encoding: 1
filter_policy: nullptr
user_defined_index_factory: nullptr
fail_if_no_udi_on_open: 0
whole_key_filtering: 1
verify_compression: 0
read_amp_bytes_per_bit: 0
format_version: 6
enable_index_compression: 1
block_align: 0
max_auto_readahead_size: 262144
prepopulate_block_cache: 0
initial_auto_readahead_size: 8192
num_file_reads_for_auto_readahead: 2
2026/01/27-22:29:39.168867 1 Options.write_buffer_size: 67108864
2026/01/27-22:29:39.168873 1 Options.max_write_buffer_number: 4
2026/01/27-22:29:39.168874 1 Options.compression[0]: NoCompression
2026/01/27-22:29:39.168875 1 Options.compression[1]: NoCompression
2026/01/27-22:29:39.168875 1 Options.compression[2]: Snappy
2026/01/27-22:29:39.168876 1 Options.compression[3]: Snappy
2026/01/27-22:29:39.168876 1 Options.compression[4]: Snappy
2026/01/27-22:29:39.168877 1 Options.compression[5]: Snappy
2026/01/27-22:29:39.168877 1 Options.compression[6]: Snappy
2026/01/27-22:29:39.168878 1 Options.bottommost_compression: Disabled
2026/01/27-22:29:39.168878 1 Options.prefix_extractor: nullptr
2026/01/27-22:29:39.168879 1 Options.memtable_insert_with_hint_prefix_extractor: nullptr
2026/01/27-22:29:39.168879 1 Options.num_levels: 7
2026/01/27-22:29:39.168880 1 Options.min_write_buffer_number_to_merge: 1
2026/01/27-22:29:39.168880 1 Options.max_write_buffer_size_to_maintain: 0
2026/01/27-22:29:39.168881 1 Options.bottommost_compression_opts.window_bits: -14
2026/01/27-22:29:39.168881 1 Options.bottommost_compression_opts.level: 32767
2026/01/27-22:29:39.168882 1 Options.bottommost_compression_opts.strategy: 0
2026/01/27-22:29:39.168882 1 Options.bottommost_compression_opts.max_dict_bytes: 0
2026/01/27-22:29:39.168883 1 Options.bottommost_compression_opts.zstd_max_train_bytes: 0
2026/01/27-22:29:39.168883 1 Options.bottommost_compression_opts.parallel_threads: 1
2026/01/27-22:29:39.168884 1 Options.bottommost_compression_opts.enabled: false
2026/01/27-22:29:39.168884 1 Options.bottommost_compression_opts.max_dict_buffer_bytes: 0
2026/01/27-22:29:39.168885 1 Options.bottommost_compression_opts.use_zstd_dict_trainer: true
2026/01/27-22:29:39.168885 1 Options.compression_opts.window_bits: -14
2026/01/27-22:29:39.168886 1 Options.compression_opts.level: 32767
2026/01/27-22:29:39.168886 1 Options.compression_opts.strategy: 0
2026/01/27-22:29:39.168887 1 Options.compression_opts.max_dict_bytes: 0
2026/01/27-22:29:39.168887 1 Options.compression_opts.zstd_max_train_bytes: 0
2026/01/27-22:29:39.168887 1 Options.compression_opts.use_zstd_dict_trainer: true
2026/01/27-22:29:39.168888 1 Options.compression_opts.parallel_threads: 1
2026/01/27-22:29:39.168888 1 Options.compression_opts.enabled: false
2026/01/27-22:29:39.168889 1 Options.compression_opts.max_dict_buffer_bytes: 0
2026/01/27-22:29:39.168889 1 Options.level0_file_num_compaction_trigger: 4
2026/01/27-22:29:39.168890 1 Options.level0_slowdown_writes_trigger: 20
2026/01/27-22:29:39.168890 1 Options.level0_stop_writes_trigger: 40
2026/01/27-22:29:39.168891 1 Options.target_file_size_base: 134217728
2026/01/27-22:29:39.168891 1 Options.target_file_size_multiplier: 1
2026/01/27-22:29:39.168892 1 Options.max_bytes_for_level_base: 268435456
2026/01/27-22:29:39.168892 1 Options.level_compaction_dynamic_level_bytes: 1
2026/01/27-22:29:39.168893 1 Options.max_bytes_for_level_multiplier: 10.000000
2026/01/27-22:29:39.168894 1 Options.max_bytes_for_level_multiplier_addtl[0]: 1
2026/01/27-22:29:39.168895 1 Options.max_bytes_for_level_multiplier_addtl[1]: 1
2026/01/27-22:29:39.168895 1 Options.max_bytes_for_level_multiplier_addtl[2]: 1
2026/01/27-22:29:39.168895 1 Options.max_bytes_for_level_multiplier_addtl[3]: 1
2026/01/27-22:29:39.168896 1 Options.max_bytes_for_level_multiplier_addtl[4]: 1
2026/01/27-22:29:39.168896 1 Options.max_bytes_for_level_multiplier_addtl[5]: 1
2026/01/27-22:29:39.168897 1 Options.max_bytes_for_level_multiplier_addtl[6]: 1
2026/01/27-22:29:39.168897 1 Options.max_sequential_skip_in_iterations: 8
2026/01/27-22:29:39.168898 1 Options.memtable_op_scan_flush_trigger: 0
2026/01/27-22:29:39.168898 1 Options.memtable_avg_op_scan_flush_trigger: 0
2026/01/27-22:29:39.168899 1 Options.max_compaction_bytes: 3355443200
2026/01/27-22:29:39.168899 1 Options.arena_block_size: 1048576
2026/01/27-22:29:39.168900 1 Options.soft_pending_compaction_bytes_limit: 68719476736
2026/01/27-22:29:39.168900 1 Options.hard_pending_compaction_bytes_limit: 274877906944
2026/01/27-22:29:39.168901 1 Options.disable_auto_compactions: 0
2026/01/27-22:29:39.168901 1 Options.compaction_style: kCompactionStyleLevel
2026/01/27-22:29:39.168902 1 Options.compaction_pri: kMinOverlappingRatio
2026/01/27-22:29:39.168902 1 Options.compaction_options_universal.size_ratio: 1
2026/01/27-22:29:39.168903 1 Options.compaction_options_universal.min_merge_width: 2
2026/01/27-22:29:39.168903 1 Options.compaction_options_universal.max_merge_width: 4294967295
2026/01/27-22:29:39.168904 1 Options.compaction_options_universal.max_size_amplification_percent: 200
2026/01/27-22:29:39.168904 1 Options.compaction_options_universal.compression_size_percent: -1
2026/01/27-22:29:39.168905 1 Options.compaction_options_universal.stop_style: kCompactionStopStyleTotalSize
2026/01/27-22:29:39.168905 1 Options.compaction_options_universal.max_read_amp: -1
2026/01/27-22:29:39.168906 1 Options.compaction_options_universal.reduce_file_locking: 0
2026/01/27-22:29:39.168906 1 Options.compaction_options_fifo.max_table_files_size: 1073741824
2026/01/27-22:29:39.168907 1 Options.compaction_options_fifo.allow_compaction: 0
2026/01/27-22:29:39.168908 1 Options.table_properties_collectors:
2026/01/27-22:29:39.168908 1 Options.inplace_update_support: 0
2026/01/27-22:29:39.168909 1 Options.inplace_update_num_locks: 10000
2026/01/27-22:29:39.168910 1 Options.memtable_prefix_bloom_size_ratio: 0.000000
2026/01/27-22:29:39.168911 1 Options.memtable_whole_key_filtering: 0
2026/01/27-22:29:39.168911 1 Options.memtable_huge_page_size: 0
2026/01/27-22:29:39.168911 1 Options.bloom_locality: 0
2026/01/27-22:29:39.168912 1 Options.max_successive_merges: 0
2026/01/27-22:29:39.168912 1 Options.strict_max_successive_merges: 0
2026/01/27-22:29:39.168913 1 Options.optimize_filters_for_hits: 0
2026/01/27-22:29:39.168913 1 Options.paranoid_file_checks: 0
2026/01/27-22:29:39.168914 1 Options.force_consistency_checks: 1
2026/01/27-22:29:39.168914 1 Options.report_bg_io_stats: 0
2026/01/27-22:29:39.168915 1 Options.disallow_memtable_writes: 0
2026/01/27-22:29:39.168915 1 Options.ttl: 2592000
2026/01/27-22:29:39.168916 1 Options.periodic_compaction_seconds: 0
2026/01/27-22:29:39.168916 1 Options.default_temperature: kUnknown
2026/01/27-22:29:39.168917 1 Options.preclude_last_level_data_seconds: 0
2026/01/27-22:29:39.168917 1 Options.preserve_internal_time_seconds: 0
2026/01/27-22:29:39.168918 1 Options.enable_blob_files: false
2026/01/27-22:29:39.168918 1 Options.min_blob_size: 0
2026/01/27-22:29:39.168919 1 Options.blob_file_size: 268435456
2026/01/27-22:29:39.168919 1 Options.blob_compression_type: NoCompression
2026/01/27-22:29:39.168920 1 Options.enable_blob_garbage_collection: false
2026/01/27-22:29:39.168920 1 Options.blob_garbage_collection_age_cutoff: 0.250000
2026/01/27-22:29:39.168921 1 Options.blob_garbage_collection_force_threshold: 1.000000
2026/01/27-22:29:39.168921 1 Options.blob_compaction_readahead_size: 0
2026/01/27-22:29:39.168922 1 Options.blob_file_starting_level: 0
2026/01/27-22:29:39.168922 1 Options.experimental_mempurge_threshold: 0.000000
2026/01/27-22:29:39.168923 1 Options.memtable_max_range_deletions: 0
2026/01/27-22:29:39.168923 1 Options.cf_allow_ingest_behind: false
2026/01/27-22:29:39.168977 1 [WARN] [db/db_impl/db_impl_open.cc:2688] DB::Open() failed: Invalid argument: Column families not opened: index, search, stream, propagate, pubsub, zset_score, metadata
2026/01/27-22:29:39.169006 1 [db/db_impl/db_impl.cc:467] Shutdown: canceling all background work
2026/01/27-22:29:39.169031 1 [db/db_impl/db_impl.cc:681] Shutdown complete

File diff suppressed because it is too large Load Diff

View File

@@ -1,391 +0,0 @@
2026/01/27-23:22:58.502039 1 RocksDB version: 10.6.2
2026/01/27-23:22:58.502469 1 Git sha 0
2026/01/27-23:22:58.502470 1 Compile date 2025-11-08 14:59:16
2026/01/27-23:22:58.502472 1 DB SUMMARY
2026/01/27-23:22:58.502473 1 Host name (Env): aae070ac4a88
2026/01/27-23:22:58.502474 1 DB Session ID: SI1XGUJ3JZXKO7QMB6X0
2026/01/27-23:22:58.502586 1 CURRENT file: CURRENT
2026/01/27-23:22:58.502587 1 IDENTITY file: IDENTITY
2026/01/27-23:22:58.502589 1 MANIFEST file: MANIFEST-000029 size: 1104 Bytes
2026/01/27-23:22:58.502590 1 SST files in /data/db dir, Total Num: 2, files: 000014.sst 000023.sst
2026/01/27-23:22:58.502591 1 Write Ahead Log file in /data/db: 000028.log size: 86377 ;
2026/01/27-23:22:58.502592 1 Options.error_if_exists: 0
2026/01/27-23:22:58.502593 1 Options.create_if_missing: 1
2026/01/27-23:22:58.502593 1 Options.paranoid_checks: 1
2026/01/27-23:22:58.502594 1 Options.flush_verify_memtable_count: 1
2026/01/27-23:22:58.502595 1 Options.compaction_verify_record_count: 1
2026/01/27-23:22:58.502595 1 Options.track_and_verify_wals_in_manifest: 0
2026/01/27-23:22:58.502596 1 Options.track_and_verify_wals: 0
2026/01/27-23:22:58.502596 1 Options.verify_sst_unique_id_in_manifest: 1
2026/01/27-23:22:58.502597 1 Options.env: 0xffff9341d0c0
2026/01/27-23:22:58.502598 1 Options.fs: PosixFileSystem
2026/01/27-23:22:58.502599 1 Options.info_log: 0xffff9350f600
2026/01/27-23:22:58.502599 1 Options.max_file_opening_threads: 16
2026/01/27-23:22:58.502600 1 Options.statistics: 0xffff934135b0
2026/01/27-23:22:58.502600 1 Options.statistics stats level: 3
2026/01/27-23:22:58.502601 1 Options.use_fsync: 0
2026/01/27-23:22:58.502602 1 Options.max_log_file_size: 268435456
2026/01/27-23:22:58.502602 1 Options.max_manifest_file_size: 67108864
2026/01/27-23:22:58.502603 1 Options.log_file_time_to_roll: 0
2026/01/27-23:22:58.502603 1 Options.keep_log_file_num: 12
2026/01/27-23:22:58.502604 1 Options.recycle_log_file_num: 0
2026/01/27-23:22:58.502605 1 Options.allow_fallocate: 1
2026/01/27-23:22:58.502605 1 Options.allow_mmap_reads: 0
2026/01/27-23:22:58.502606 1 Options.allow_mmap_writes: 0
2026/01/27-23:22:58.502606 1 Options.use_direct_reads: 0
2026/01/27-23:22:58.502607 1 Options.use_direct_io_for_flush_and_compaction: 0
2026/01/27-23:22:58.502607 1 Options.create_missing_column_families: 1
2026/01/27-23:22:58.502608 1 Options.db_log_dir:
2026/01/27-23:22:58.502609 1 Options.wal_dir:
2026/01/27-23:22:58.502609 1 Options.table_cache_numshardbits: 6
2026/01/27-23:22:58.502610 1 Options.WAL_ttl_seconds: 10800
2026/01/27-23:22:58.502611 1 Options.WAL_size_limit_MB: 16384
2026/01/27-23:22:58.502611 1 Options.max_write_batch_group_size_bytes: 1048576
2026/01/27-23:22:58.502612 1 Options.manifest_preallocation_size: 4194304
2026/01/27-23:22:58.502612 1 Options.is_fd_close_on_exec: 1
2026/01/27-23:22:58.502613 1 Options.advise_random_on_open: 1
2026/01/27-23:22:58.502614 1 Options.db_write_buffer_size: 0
2026/01/27-23:22:58.502614 1 Options.write_buffer_manager: 0xffff9346bfc0
2026/01/27-23:22:58.502615 1 Options.use_adaptive_mutex: 0
2026/01/27-23:22:58.502615 1 Options.rate_limiter: 0xffff93436000
2026/01/27-23:22:58.502616 1 Options.sst_file_manager.rate_bytes_per_sec: 0
2026/01/27-23:22:58.502617 1 Options.wal_recovery_mode: 2
2026/01/27-23:22:58.502617 1 Options.enable_thread_tracking: 0
2026/01/27-23:22:58.502618 1 Options.enable_pipelined_write: 0
2026/01/27-23:22:58.502618 1 Options.unordered_write: 0
2026/01/27-23:22:58.502619 1 Options.allow_concurrent_memtable_write: 1
2026/01/27-23:22:58.502619 1 Options.enable_write_thread_adaptive_yield: 1
2026/01/27-23:22:58.502620 1 Options.write_thread_max_yield_usec: 100
2026/01/27-23:22:58.502621 1 Options.write_thread_slow_yield_usec: 3
2026/01/27-23:22:58.502621 1 Options.row_cache: None
2026/01/27-23:22:58.502622 1 Options.wal_filter: None
2026/01/27-23:22:58.502622 1 Options.avoid_flush_during_recovery: 0
2026/01/27-23:22:58.502623 1 Options.allow_ingest_behind: 0
2026/01/27-23:22:58.502623 1 Options.two_write_queues: 0
2026/01/27-23:22:58.502624 1 Options.manual_wal_flush: 0
2026/01/27-23:22:58.502625 1 Options.wal_compression: 0
2026/01/27-23:22:58.502625 1 Options.background_close_inactive_wals: 0
2026/01/27-23:22:58.502626 1 Options.atomic_flush: 0
2026/01/27-23:22:58.502626 1 Options.avoid_unnecessary_blocking_io: 1
2026/01/27-23:22:58.502627 1 Options.prefix_seek_opt_in_only: 0
2026/01/27-23:22:58.502628 1 Options.persist_stats_to_disk: 0
2026/01/27-23:22:58.502628 1 Options.write_dbid_to_manifest: 1
2026/01/27-23:22:58.502629 1 Options.write_identity_file: 1
2026/01/27-23:22:58.502629 1 Options.log_readahead_size: 0
2026/01/27-23:22:58.502630 1 Options.file_checksum_gen_factory: Unknown
2026/01/27-23:22:58.502630 1 Options.best_efforts_recovery: 0
2026/01/27-23:22:58.502631 1 Options.max_bgerror_resume_count: 2147483647
2026/01/27-23:22:58.502632 1 Options.bgerror_resume_retry_interval: 1000000
2026/01/27-23:22:58.502632 1 Options.allow_data_in_errors: 0
2026/01/27-23:22:58.502633 1 Options.db_host_id: __hostname__
2026/01/27-23:22:58.502633 1 Options.enforce_single_del_contracts: true
2026/01/27-23:22:58.502634 1 Options.metadata_write_temperature: kUnknown
2026/01/27-23:22:58.502635 1 Options.wal_write_temperature: kUnknown
2026/01/27-23:22:58.502635 1 Options.max_background_jobs: 4
2026/01/27-23:22:58.502636 1 Options.max_background_compactions: -1
2026/01/27-23:22:58.502637 1 Options.max_subcompactions: 2
2026/01/27-23:22:58.502637 1 Options.avoid_flush_during_shutdown: 0
2026/01/27-23:22:58.502638 1 Options.writable_file_max_buffer_size: 1048576
2026/01/27-23:22:58.502638 1 Options.delayed_write_rate : 536870912000
2026/01/27-23:22:58.502640 1 Options.max_total_wal_size: 536870912
2026/01/27-23:22:58.502640 1 Options.delete_obsolete_files_period_micros: 21600000000
2026/01/27-23:22:58.502641 1 Options.stats_dump_period_sec: 0
2026/01/27-23:22:58.502642 1 Options.stats_persist_period_sec: 600
2026/01/27-23:22:58.502643 1 Options.stats_history_buffer_size: 1048576
2026/01/27-23:22:58.502644 1 Options.max_open_files: 8096
2026/01/27-23:22:58.502644 1 Options.bytes_per_sync: 1048576
2026/01/27-23:22:58.502645 1 Options.wal_bytes_per_sync: 0
2026/01/27-23:22:58.502645 1 Options.strict_bytes_per_sync: 0
2026/01/27-23:22:58.502646 1 Options.compaction_readahead_size: 2097152
2026/01/27-23:22:58.502646 1 Options.max_background_flushes: -1
2026/01/27-23:22:58.502647 1 Options.daily_offpeak_time_utc:
2026/01/27-23:22:58.502647 1 Compression algorithms supported:
2026/01/27-23:22:58.502648 1 kCustomCompressionFE supported: 0
2026/01/27-23:22:58.502649 1 kCustomCompressionFC supported: 0
2026/01/27-23:22:58.502649 1 kCustomCompressionF8 supported: 0
2026/01/27-23:22:58.502650 1 kCustomCompressionF7 supported: 0
2026/01/27-23:22:58.502651 1 kCustomCompressionB2 supported: 0
2026/01/27-23:22:58.502651 1 kLZ4Compression supported: 1
2026/01/27-23:22:58.502652 1 kCustomCompression88 supported: 0
2026/01/27-23:22:58.502653 1 kCustomCompressionD8 supported: 0
2026/01/27-23:22:58.502653 1 kCustomCompression9F supported: 0
2026/01/27-23:22:58.502654 1 kCustomCompressionD6 supported: 0
2026/01/27-23:22:58.502654 1 kCustomCompressionA9 supported: 0
2026/01/27-23:22:58.502655 1 kCustomCompressionEC supported: 0
2026/01/27-23:22:58.502655 1 kCustomCompressionA3 supported: 0
2026/01/27-23:22:58.502656 1 kCustomCompressionCB supported: 0
2026/01/27-23:22:58.502657 1 kCustomCompression90 supported: 0
2026/01/27-23:22:58.502657 1 kCustomCompressionA0 supported: 0
2026/01/27-23:22:58.502658 1 kCustomCompressionC6 supported: 0
2026/01/27-23:22:58.502659 1 kCustomCompression9D supported: 0
2026/01/27-23:22:58.502659 1 kCustomCompression8B supported: 0
2026/01/27-23:22:58.502660 1 kCustomCompressionA8 supported: 0
2026/01/27-23:22:58.502660 1 kCustomCompression8D supported: 0
2026/01/27-23:22:58.502661 1 kCustomCompression97 supported: 0
2026/01/27-23:22:58.502661 1 kCustomCompression98 supported: 0
2026/01/27-23:22:58.502662 1 kCustomCompressionAC supported: 0
2026/01/27-23:22:58.502662 1 kCustomCompressionE9 supported: 0
2026/01/27-23:22:58.502663 1 kCustomCompression96 supported: 0
2026/01/27-23:22:58.502663 1 kCustomCompressionB1 supported: 0
2026/01/27-23:22:58.502664 1 kCustomCompression95 supported: 0
2026/01/27-23:22:58.502664 1 kCustomCompression84 supported: 0
2026/01/27-23:22:58.502665 1 kCustomCompression91 supported: 0
2026/01/27-23:22:58.502665 1 kCustomCompressionAB supported: 0
2026/01/27-23:22:58.502666 1 kCustomCompressionB3 supported: 0
2026/01/27-23:22:58.502666 1 kCustomCompression81 supported: 0
2026/01/27-23:22:58.502667 1 kCustomCompressionDC supported: 0
2026/01/27-23:22:58.502667 1 kBZip2Compression supported: 0
2026/01/27-23:22:58.502668 1 kCustomCompressionBB supported: 0
2026/01/27-23:22:58.502668 1 kCustomCompression9C supported: 0
2026/01/27-23:22:58.502668 1 kCustomCompressionC9 supported: 0
2026/01/27-23:22:58.502669 1 kCustomCompressionCC supported: 0
2026/01/27-23:22:58.502669 1 kCustomCompression92 supported: 0
2026/01/27-23:22:58.502670 1 kCustomCompressionB9 supported: 0
2026/01/27-23:22:58.502670 1 kCustomCompression8F supported: 0
2026/01/27-23:22:58.502671 1 kCustomCompression8A supported: 0
2026/01/27-23:22:58.502671 1 kCustomCompression9B supported: 0
2026/01/27-23:22:58.502671 1 kZSTD supported: 1
2026/01/27-23:22:58.502672 1 kCustomCompressionAA supported: 0
2026/01/27-23:22:58.502672 1 kCustomCompressionA2 supported: 0
2026/01/27-23:22:58.502673 1 kZlibCompression supported: 1
2026/01/27-23:22:58.502673 1 kXpressCompression supported: 0
2026/01/27-23:22:58.502674 1 kCustomCompressionFD supported: 0
2026/01/27-23:22:58.502674 1 kCustomCompressionE2 supported: 0
2026/01/27-23:22:58.502675 1 kLZ4HCCompression supported: 1
2026/01/27-23:22:58.502675 1 kCustomCompressionA6 supported: 0
2026/01/27-23:22:58.502676 1 kCustomCompression85 supported: 0
2026/01/27-23:22:58.502676 1 kCustomCompressionA4 supported: 0
2026/01/27-23:22:58.502676 1 kCustomCompression86 supported: 0
2026/01/27-23:22:58.502677 1 kCustomCompression83 supported: 0
2026/01/27-23:22:58.502677 1 kCustomCompression87 supported: 0
2026/01/27-23:22:58.502678 1 kCustomCompression89 supported: 0
2026/01/27-23:22:58.502678 1 kCustomCompression8C supported: 0
2026/01/27-23:22:58.502679 1 kCustomCompressionDB supported: 0
2026/01/27-23:22:58.502679 1 kCustomCompressionF3 supported: 0
2026/01/27-23:22:58.502679 1 kCustomCompressionE6 supported: 0
2026/01/27-23:22:58.502680 1 kCustomCompression8E supported: 0
2026/01/27-23:22:58.502680 1 kCustomCompressionDA supported: 0
2026/01/27-23:22:58.502681 1 kCustomCompression93 supported: 0
2026/01/27-23:22:58.502681 1 kCustomCompression94 supported: 0
2026/01/27-23:22:58.502685 1 kCustomCompression9E supported: 0
2026/01/27-23:22:58.502685 1 kCustomCompressionB4 supported: 0
2026/01/27-23:22:58.502686 1 kCustomCompressionFB supported: 0
2026/01/27-23:22:58.502686 1 kCustomCompressionB5 supported: 0
2026/01/27-23:22:58.502686 1 kCustomCompressionD5 supported: 0
2026/01/27-23:22:58.502687 1 kCustomCompressionB8 supported: 0
2026/01/27-23:22:58.502687 1 kCustomCompressionD1 supported: 0
2026/01/27-23:22:58.502688 1 kCustomCompressionBA supported: 0
2026/01/27-23:22:58.502688 1 kCustomCompressionBC supported: 0
2026/01/27-23:22:58.502689 1 kCustomCompressionCE supported: 0
2026/01/27-23:22:58.502689 1 kCustomCompressionBD supported: 0
2026/01/27-23:22:58.502689 1 kCustomCompressionC4 supported: 0
2026/01/27-23:22:58.502690 1 kCustomCompression9A supported: 0
2026/01/27-23:22:58.502690 1 kCustomCompression99 supported: 0
2026/01/27-23:22:58.502691 1 kCustomCompressionBE supported: 0
2026/01/27-23:22:58.502691 1 kCustomCompressionE5 supported: 0
2026/01/27-23:22:58.502692 1 kCustomCompressionD9 supported: 0
2026/01/27-23:22:58.502692 1 kCustomCompressionC1 supported: 0
2026/01/27-23:22:58.502693 1 kCustomCompressionC5 supported: 0
2026/01/27-23:22:58.502694 1 kCustomCompressionC2 supported: 0
2026/01/27-23:22:58.502694 1 kCustomCompressionA5 supported: 0
2026/01/27-23:22:58.502695 1 kCustomCompressionC7 supported: 0
2026/01/27-23:22:58.502695 1 kCustomCompressionBF supported: 0
2026/01/27-23:22:58.502696 1 kCustomCompressionE8 supported: 0
2026/01/27-23:22:58.502696 1 kCustomCompressionC8 supported: 0
2026/01/27-23:22:58.502697 1 kCustomCompressionAF supported: 0
2026/01/27-23:22:58.502698 1 kCustomCompressionCA supported: 0
2026/01/27-23:22:58.502698 1 kCustomCompressionCD supported: 0
2026/01/27-23:22:58.502699 1 kCustomCompressionC0 supported: 0
2026/01/27-23:22:58.502699 1 kCustomCompressionCF supported: 0
2026/01/27-23:22:58.502700 1 kCustomCompressionF9 supported: 0
2026/01/27-23:22:58.502700 1 kCustomCompressionD0 supported: 0
2026/01/27-23:22:58.502701 1 kCustomCompressionD2 supported: 0
2026/01/27-23:22:58.502701 1 kCustomCompressionAD supported: 0
2026/01/27-23:22:58.502702 1 kCustomCompressionD3 supported: 0
2026/01/27-23:22:58.502703 1 kCustomCompressionD4 supported: 0
2026/01/27-23:22:58.502703 1 kCustomCompressionD7 supported: 0
2026/01/27-23:22:58.502704 1 kCustomCompression82 supported: 0
2026/01/27-23:22:58.502704 1 kCustomCompressionDD supported: 0
2026/01/27-23:22:58.502705 1 kCustomCompressionC3 supported: 0
2026/01/27-23:22:58.502705 1 kCustomCompressionEE supported: 0
2026/01/27-23:22:58.502706 1 kCustomCompressionDE supported: 0
2026/01/27-23:22:58.502706 1 kCustomCompressionDF supported: 0
2026/01/27-23:22:58.502707 1 kCustomCompressionA7 supported: 0
2026/01/27-23:22:58.502707 1 kCustomCompressionE0 supported: 0
2026/01/27-23:22:58.502708 1 kCustomCompressionF1 supported: 0
2026/01/27-23:22:58.502708 1 kCustomCompressionE1 supported: 0
2026/01/27-23:22:58.502709 1 kCustomCompressionF5 supported: 0
2026/01/27-23:22:58.502709 1 kCustomCompression80 supported: 0
2026/01/27-23:22:58.502710 1 kCustomCompressionE3 supported: 0
2026/01/27-23:22:58.502711 1 kCustomCompressionE4 supported: 0
2026/01/27-23:22:58.502711 1 kCustomCompressionB0 supported: 0
2026/01/27-23:22:58.502712 1 kCustomCompressionEA supported: 0
2026/01/27-23:22:58.502712 1 kCustomCompressionFA supported: 0
2026/01/27-23:22:58.502713 1 kCustomCompressionE7 supported: 0
2026/01/27-23:22:58.502713 1 kCustomCompressionAE supported: 0
2026/01/27-23:22:58.502714 1 kCustomCompressionEB supported: 0
2026/01/27-23:22:58.502714 1 kCustomCompressionED supported: 0
2026/01/27-23:22:58.502715 1 kCustomCompressionB6 supported: 0
2026/01/27-23:22:58.502715 1 kCustomCompressionEF supported: 0
2026/01/27-23:22:58.502716 1 kCustomCompressionF0 supported: 0
2026/01/27-23:22:58.502716 1 kCustomCompressionB7 supported: 0
2026/01/27-23:22:58.502717 1 kCustomCompressionF2 supported: 0
2026/01/27-23:22:58.502717 1 kCustomCompressionA1 supported: 0
2026/01/27-23:22:58.502718 1 kCustomCompressionF4 supported: 0
2026/01/27-23:22:58.502719 1 kSnappyCompression supported: 1
2026/01/27-23:22:58.502719 1 kCustomCompressionF6 supported: 0
2026/01/27-23:22:58.502720 1 Fast CRC32 supported: Supported on Arm64
2026/01/27-23:22:58.502721 1 DMutex implementation: pthread_mutex_t
2026/01/27-23:22:58.502722 1 Jemalloc supported: 1
2026/01/27-23:22:58.503054 1 [db/version_set.cc:6190] Recovering from manifest file: /data/db/MANIFEST-000029
2026/01/27-23:22:58.503246 1 [db/column_family.cc:693] --------------- Options for column family [default]:
2026/01/27-23:22:58.503249 1 Options.comparator: leveldb.BytewiseComparator
2026/01/27-23:22:58.503250 1 Options.merge_operator: None
2026/01/27-23:22:58.503250 1 Options.compaction_filter: None
2026/01/27-23:22:58.503251 1 Options.compaction_filter_factory: None
2026/01/27-23:22:58.503251 1 Options.sst_partitioner_factory: None
2026/01/27-23:22:58.503252 1 Options.memtable_factory: SkipListFactory
2026/01/27-23:22:58.503253 1 Options.table_factory: BlockBasedTable
2026/01/27-23:22:58.503273 1 table_factory options: flush_block_policy_factory: FlushBlockBySizePolicyFactory (0xffff934d15a0)
cache_index_and_filter_blocks: 0
cache_index_and_filter_blocks_with_high_priority: 1
pin_l0_filter_and_index_blocks_in_cache: 0
pin_top_level_index_and_filter: 1
index_type: 0
data_block_index_type: 0
index_shortening: 1
data_block_hash_table_util_ratio: 0.750000
checksum: 4
no_block_cache: 0
block_cache: 0xffff9346bd90
block_cache_name: LRUCache
block_cache_options:
capacity : 33554432
num_shard_bits : 6
strict_capacity_limit : 0
memory_allocator : None
high_pri_pool_ratio: 0.500
low_pri_pool_ratio: 0.000
persistent_cache: (nil)
block_size: 4096
block_size_deviation: 10
block_restart_interval: 16
index_block_restart_interval: 1
metadata_block_size: 4096
partition_filters: 0
use_delta_encoding: 1
filter_policy: nullptr
user_defined_index_factory: nullptr
fail_if_no_udi_on_open: 0
whole_key_filtering: 1
verify_compression: 0
read_amp_bytes_per_bit: 0
format_version: 6
enable_index_compression: 1
block_align: 0
max_auto_readahead_size: 262144
prepopulate_block_cache: 0
initial_auto_readahead_size: 8192
num_file_reads_for_auto_readahead: 2
2026/01/27-23:22:58.503278 1 Options.write_buffer_size: 67108864
2026/01/27-23:22:58.503279 1 Options.max_write_buffer_number: 4
2026/01/27-23:22:58.503280 1 Options.compression[0]: NoCompression
2026/01/27-23:22:58.503281 1 Options.compression[1]: NoCompression
2026/01/27-23:22:58.503281 1 Options.compression[2]: Snappy
2026/01/27-23:22:58.503282 1 Options.compression[3]: Snappy
2026/01/27-23:22:58.503282 1 Options.compression[4]: Snappy
2026/01/27-23:22:58.503283 1 Options.compression[5]: Snappy
2026/01/27-23:22:58.503284 1 Options.compression[6]: Snappy
2026/01/27-23:22:58.503284 1 Options.bottommost_compression: Disabled
2026/01/27-23:22:58.503285 1 Options.prefix_extractor: nullptr
2026/01/27-23:22:58.503286 1 Options.memtable_insert_with_hint_prefix_extractor: nullptr
2026/01/27-23:22:58.503286 1 Options.num_levels: 7
2026/01/27-23:22:58.503287 1 Options.min_write_buffer_number_to_merge: 1
2026/01/27-23:22:58.503287 1 Options.max_write_buffer_size_to_maintain: 0
2026/01/27-23:22:58.503288 1 Options.bottommost_compression_opts.window_bits: -14
2026/01/27-23:22:58.503289 1 Options.bottommost_compression_opts.level: 32767
2026/01/27-23:22:58.503290 1 Options.bottommost_compression_opts.strategy: 0
2026/01/27-23:22:58.503290 1 Options.bottommost_compression_opts.max_dict_bytes: 0
2026/01/27-23:22:58.503291 1 Options.bottommost_compression_opts.zstd_max_train_bytes: 0
2026/01/27-23:22:58.503292 1 Options.bottommost_compression_opts.parallel_threads: 1
2026/01/27-23:22:58.503292 1 Options.bottommost_compression_opts.enabled: false
2026/01/27-23:22:58.503293 1 Options.bottommost_compression_opts.max_dict_buffer_bytes: 0
2026/01/27-23:22:58.503294 1 Options.bottommost_compression_opts.use_zstd_dict_trainer: true
2026/01/27-23:22:58.503294 1 Options.compression_opts.window_bits: -14
2026/01/27-23:22:58.503295 1 Options.compression_opts.level: 32767
2026/01/27-23:22:58.503296 1 Options.compression_opts.strategy: 0
2026/01/27-23:22:58.503296 1 Options.compression_opts.max_dict_bytes: 0
2026/01/27-23:22:58.503297 1 Options.compression_opts.zstd_max_train_bytes: 0
2026/01/27-23:22:58.503297 1 Options.compression_opts.use_zstd_dict_trainer: true
2026/01/27-23:22:58.503298 1 Options.compression_opts.parallel_threads: 1
2026/01/27-23:22:58.503299 1 Options.compression_opts.enabled: false
2026/01/27-23:22:58.503299 1 Options.compression_opts.max_dict_buffer_bytes: 0
2026/01/27-23:22:58.503300 1 Options.level0_file_num_compaction_trigger: 4
2026/01/27-23:22:58.503300 1 Options.level0_slowdown_writes_trigger: 20
2026/01/27-23:22:58.503301 1 Options.level0_stop_writes_trigger: 40
2026/01/27-23:22:58.503301 1 Options.target_file_size_base: 134217728
2026/01/27-23:22:58.503302 1 Options.target_file_size_multiplier: 1
2026/01/27-23:22:58.503303 1 Options.max_bytes_for_level_base: 268435456
2026/01/27-23:22:58.503303 1 Options.level_compaction_dynamic_level_bytes: 1
2026/01/27-23:22:58.503304 1 Options.max_bytes_for_level_multiplier: 10.000000
2026/01/27-23:22:58.503305 1 Options.max_bytes_for_level_multiplier_addtl[0]: 1
2026/01/27-23:22:58.503306 1 Options.max_bytes_for_level_multiplier_addtl[1]: 1
2026/01/27-23:22:58.503307 1 Options.max_bytes_for_level_multiplier_addtl[2]: 1
2026/01/27-23:22:58.503307 1 Options.max_bytes_for_level_multiplier_addtl[3]: 1
2026/01/27-23:22:58.503308 1 Options.max_bytes_for_level_multiplier_addtl[4]: 1
2026/01/27-23:22:58.503308 1 Options.max_bytes_for_level_multiplier_addtl[5]: 1
2026/01/27-23:22:58.503309 1 Options.max_bytes_for_level_multiplier_addtl[6]: 1
2026/01/27-23:22:58.503309 1 Options.max_sequential_skip_in_iterations: 8
2026/01/27-23:22:58.503310 1 Options.memtable_op_scan_flush_trigger: 0
2026/01/27-23:22:58.503310 1 Options.memtable_avg_op_scan_flush_trigger: 0
2026/01/27-23:22:58.503311 1 Options.max_compaction_bytes: 3355443200
2026/01/27-23:22:58.503311 1 Options.arena_block_size: 1048576
2026/01/27-23:22:58.503312 1 Options.soft_pending_compaction_bytes_limit: 68719476736
2026/01/27-23:22:58.503312 1 Options.hard_pending_compaction_bytes_limit: 274877906944
2026/01/27-23:22:58.503313 1 Options.disable_auto_compactions: 0
2026/01/27-23:22:58.503314 1 Options.compaction_style: kCompactionStyleLevel
2026/01/27-23:22:58.503314 1 Options.compaction_pri: kMinOverlappingRatio
2026/01/27-23:22:58.503315 1 Options.compaction_options_universal.size_ratio: 1
2026/01/27-23:22:58.503315 1 Options.compaction_options_universal.min_merge_width: 2
2026/01/27-23:22:58.503316 1 Options.compaction_options_universal.max_merge_width: 4294967295
2026/01/27-23:22:58.503316 1 Options.compaction_options_universal.max_size_amplification_percent: 200
2026/01/27-23:22:58.503317 1 Options.compaction_options_universal.compression_size_percent: -1
2026/01/27-23:22:58.503318 1 Options.compaction_options_universal.stop_style: kCompactionStopStyleTotalSize
2026/01/27-23:22:58.503318 1 Options.compaction_options_universal.max_read_amp: -1
2026/01/27-23:22:58.503319 1 Options.compaction_options_universal.reduce_file_locking: 0
2026/01/27-23:22:58.503319 1 Options.compaction_options_fifo.max_table_files_size: 1073741824
2026/01/27-23:22:58.503320 1 Options.compaction_options_fifo.allow_compaction: 0
2026/01/27-23:22:58.503321 1 Options.table_properties_collectors:
2026/01/27-23:22:58.503321 1 Options.inplace_update_support: 0
2026/01/27-23:22:58.503322 1 Options.inplace_update_num_locks: 10000
2026/01/27-23:22:58.503323 1 Options.memtable_prefix_bloom_size_ratio: 0.000000
2026/01/27-23:22:58.503323 1 Options.memtable_whole_key_filtering: 0
2026/01/27-23:22:58.503324 1 Options.memtable_huge_page_size: 0
2026/01/27-23:22:58.503324 1 Options.bloom_locality: 0
2026/01/27-23:22:58.503325 1 Options.max_successive_merges: 0
2026/01/27-23:22:58.503325 1 Options.strict_max_successive_merges: 0
2026/01/27-23:22:58.503326 1 Options.optimize_filters_for_hits: 0
2026/01/27-23:22:58.503326 1 Options.paranoid_file_checks: 0
2026/01/27-23:22:58.503327 1 Options.force_consistency_checks: 1
2026/01/27-23:22:58.503327 1 Options.report_bg_io_stats: 0
2026/01/27-23:22:58.503328 1 Options.disallow_memtable_writes: 0
2026/01/27-23:22:58.503328 1 Options.ttl: 2592000
2026/01/27-23:22:58.503329 1 Options.periodic_compaction_seconds: 0
2026/01/27-23:22:58.503329 1 Options.default_temperature: kUnknown
2026/01/27-23:22:58.503330 1 Options.preclude_last_level_data_seconds: 0
2026/01/27-23:22:58.503330 1 Options.preserve_internal_time_seconds: 0
2026/01/27-23:22:58.503331 1 Options.enable_blob_files: false
2026/01/27-23:22:58.503331 1 Options.min_blob_size: 0
2026/01/27-23:22:58.503332 1 Options.blob_file_size: 268435456
2026/01/27-23:22:58.503332 1 Options.blob_compression_type: NoCompression
2026/01/27-23:22:58.503333 1 Options.enable_blob_garbage_collection: false
2026/01/27-23:22:58.503333 1 Options.blob_garbage_collection_age_cutoff: 0.250000
2026/01/27-23:22:58.503334 1 Options.blob_garbage_collection_force_threshold: 1.000000
2026/01/27-23:22:58.503335 1 Options.blob_compaction_readahead_size: 0
2026/01/27-23:22:58.503335 1 Options.blob_file_starting_level: 0
2026/01/27-23:22:58.503336 1 Options.experimental_mempurge_threshold: 0.000000
2026/01/27-23:22:58.503336 1 Options.memtable_max_range_deletions: 0
2026/01/27-23:22:58.503337 1 Options.cf_allow_ingest_behind: false
2026/01/27-23:22:58.503427 1 [WARN] [db/db_impl/db_impl_open.cc:2688] DB::Open() failed: Invalid argument: Column families not opened: index, search, stream, propagate, pubsub, zset_score, metadata
2026/01/27-23:22:58.503463 1 [db/db_impl/db_impl.cc:467] Shutdown: canceling all background work
2026/01/27-23:22:58.503492 1 [db/db_impl/db_impl.cc:681] Shutdown complete

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,143 +0,0 @@
[2026-01-27T21:55:32.811461+00:00][I][main.cc:168] kvrocks version 2.14.0 (commit a71eb42f)
[2026-01-27T21:55:32.841671+00:00][I][storage.cc:407] [storage] Success to load the data from disk: 12 ms
[2026-01-27T21:55:32.843628+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T21:55:32.843938+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T21:55:32.844064+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T21:55:32.844141+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T21:55:32.844201+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T21:55:32.844268+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T21:55:32.844338+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T21:55:32.844401+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T21:55:32.844625+00:00][I][worker.cc:596] [worker] Thread #281473273094752 started
[2026-01-27T21:55:32.844653+00:00][I][worker.cc:596] [worker] Thread #281473281548896 started
[2026-01-27T21:55:32.844671+00:00][I][worker.cc:596] [worker] Thread #281473290003040 started
[2026-01-27T21:55:32.844708+00:00][I][worker.cc:596] [worker] Thread #281473366090336 started
[2026-01-27T21:55:32.844742+00:00][I][worker.cc:596] [worker] Thread #281473357636192 started
[2026-01-27T21:55:32.844787+00:00][I][worker.cc:596] [worker] Thread #281473349182048 started
[2026-01-27T21:55:32.844825+00:00][I][worker.cc:596] [worker] Thread #281473340727904 started
[2026-01-27T21:55:32.844861+00:00][I][worker.cc:596] [worker] Thread #281473332273760 started
[2026-01-27T21:55:32.844960+00:00][I][server.cc:245] [server] Ready to accept connections
[2026-01-27T21:56:34.369240+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: pubsub
[2026-01-27T21:56:34.377799+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: pubsub finished, result: OK
[2026-01-27T21:56:34.378156+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: propagate
[2026-01-27T21:56:34.378349+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: propagate finished, result: OK
[2026-01-27T21:59:36.368485+00:00][I][main.cc:53] Signal Terminated (15) received, stopping the server
[2026-01-27T22:00:38.556809+00:00][I][main.cc:168] kvrocks version 2.14.0 (commit a71eb42f)
[2026-01-27T22:00:38.571773+00:00][I][event_listener.cc:187] [event_listener/table_file_created] column family: metadata, file path: /data/db/000014.sst, file size: 2427, job_id: 1, reason: recovery, status: OK
[2026-01-27T22:00:38.579255+00:00][I][storage.cc:407] [storage] Success to load the data from disk: 17 ms
[2026-01-27T22:00:38.581394+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:00:38.581635+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:00:38.581731+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:00:38.581905+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:00:38.582059+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:00:38.582166+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:00:38.582287+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:00:38.582369+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:00:38.582500+00:00][I][worker.cc:596] [worker] Thread #281473264837216 started
[2026-01-27T22:00:38.582607+00:00][I][worker.cc:596] [worker] Thread #281473273291360 started
[2026-01-27T22:00:38.582648+00:00][I][worker.cc:596] [worker] Thread #281473281745504 started
[2026-01-27T22:00:38.582698+00:00][I][worker.cc:596] [worker] Thread #281473336730208 started
[2026-01-27T22:00:38.582731+00:00][I][worker.cc:596] [worker] Thread #281473328276064 started
[2026-01-27T22:00:38.582767+00:00][I][worker.cc:596] [worker] Thread #281473319821920 started
[2026-01-27T22:00:38.582805+00:00][I][worker.cc:596] [worker] Thread #281473311367776 started
[2026-01-27T22:00:38.582859+00:00][I][worker.cc:596] [worker] Thread #281473302913632 started
[2026-01-27T22:00:38.582953+00:00][I][server.cc:245] [server] Ready to accept connections
[2026-01-27T22:01:40.207616+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: pubsub
[2026-01-27T22:01:40.208939+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: pubsub finished, result: OK
[2026-01-27T22:01:40.209035+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: propagate
[2026-01-27T22:01:40.209124+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: propagate finished, result: OK
[2026-01-27T22:04:28.316771+00:00][I][main.cc:53] Signal Terminated (15) received, stopping the server
[2026-01-27T22:05:20.855690+00:00][I][main.cc:168] kvrocks version 2.14.0 (commit a71eb42f)
[2026-01-27T22:05:20.871111+00:00][I][storage.cc:407] [storage] Success to load the data from disk: 11 ms
[2026-01-27T22:05:20.872974+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:05:20.873484+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:05:20.873699+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:05:20.874015+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:05:20.874074+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:05:20.874129+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:05:20.874184+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:05:20.874242+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:05:20.874356+00:00][I][worker.cc:596] [worker] Thread #281472658825824 started
[2026-01-27T22:05:20.874371+00:00][I][worker.cc:596] [worker] Thread #281472667279968 started
[2026-01-27T22:05:20.874386+00:00][I][worker.cc:596] [worker] Thread #281472675734112 started
[2026-01-27T22:05:20.874415+00:00][I][worker.cc:596] [worker] Thread #281472739041888 started
[2026-01-27T22:05:20.874436+00:00][I][worker.cc:596] [worker] Thread #281472726458976 started
[2026-01-27T22:05:20.874450+00:00][I][worker.cc:596] [worker] Thread #281472718004832 started
[2026-01-27T22:05:20.874464+00:00][I][worker.cc:596] [worker] Thread #281472709550688 started
[2026-01-27T22:05:20.874485+00:00][I][worker.cc:596] [worker] Thread #281472701096544 started
[2026-01-27T22:05:20.874543+00:00][I][server.cc:245] [server] Ready to accept connections
[2026-01-27T22:06:22.367592+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: pubsub
[2026-01-27T22:06:22.370793+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: pubsub finished, result: OK
[2026-01-27T22:06:22.371106+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: propagate
[2026-01-27T22:06:22.371313+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: propagate finished, result: OK
[2026-01-27T22:29:24.678219+00:00][I][main.cc:53] Signal Terminated (15) received, stopping the server
[2026-01-27T22:29:25.238671+00:00][I][main.cc:168] kvrocks version 2.14.0 (commit a71eb42f)
[2026-01-27T22:29:25.251917+00:00][I][event_listener.cc:187] [event_listener/table_file_created] column family: metadata, file path: /data/db/000023.sst, file size: 2476, job_id: 1, reason: recovery, status: OK
[2026-01-27T22:29:25.259557+00:00][I][storage.cc:407] [storage] Success to load the data from disk: 16 ms
[2026-01-27T22:29:25.262110+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:25.262274+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:25.262372+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:25.262449+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:25.262518+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:25.262618+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:25.262684+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:25.262746+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:25.262900+00:00][I][worker.cc:596] [worker] Thread #281472793043552 started
[2026-01-27T22:29:25.262932+00:00][I][worker.cc:596] [worker] Thread #281472801497696 started
[2026-01-27T22:29:25.262956+00:00][I][worker.cc:596] [worker] Thread #281472809951840 started
[2026-01-27T22:29:25.262989+00:00][I][worker.cc:596] [worker] Thread #281472873259616 started
[2026-01-27T22:29:25.263017+00:00][I][worker.cc:596] [worker] Thread #281472860676704 started
[2026-01-27T22:29:25.263051+00:00][I][worker.cc:596] [worker] Thread #281472852222560 started
[2026-01-27T22:29:25.263086+00:00][I][worker.cc:596] [worker] Thread #281472843768416 started
[2026-01-27T22:29:25.263115+00:00][I][worker.cc:596] [worker] Thread #281472835314272 started
[2026-01-27T22:29:25.263201+00:00][I][server.cc:245] [server] Ready to accept connections
[2026-01-27T22:29:38.668808+00:00][I][main.cc:53] Signal Terminated (15) received, stopping the server
[2026-01-27T22:29:39.166069+00:00][I][main.cc:168] kvrocks version 2.14.0 (commit a71eb42f)
[2026-01-27T22:29:39.182475+00:00][I][storage.cc:407] [storage] Success to load the data from disk: 13 ms
[2026-01-27T22:29:39.183886+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:39.184001+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:39.184110+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:39.184542+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:39.184647+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:39.184722+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:39.184822+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:39.184900+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T22:29:39.185008+00:00][I][worker.cc:596] [worker] Thread #281473065673312 started
[2026-01-27T22:29:39.185045+00:00][I][worker.cc:596] [worker] Thread #281473074127456 started
[2026-01-27T22:29:39.185068+00:00][I][worker.cc:596] [worker] Thread #281473082581600 started
[2026-01-27T22:29:39.185101+00:00][I][worker.cc:596] [worker] Thread #281473147986528 started
[2026-01-27T22:29:39.185127+00:00][I][worker.cc:596] [worker] Thread #281473133306464 started
[2026-01-27T22:29:39.185153+00:00][I][worker.cc:596] [worker] Thread #281473124852320 started
[2026-01-27T22:29:39.185193+00:00][I][worker.cc:596] [worker] Thread #281473116398176 started
[2026-01-27T22:29:39.185221+00:00][I][worker.cc:596] [worker] Thread #281473107944032 started
[2026-01-27T22:29:39.185302+00:00][I][server.cc:245] [server] Ready to accept connections
[2026-01-27T22:30:40.725701+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: pubsub
[2026-01-27T22:30:40.726451+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: pubsub finished, result: OK
[2026-01-27T22:30:40.726546+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: propagate
[2026-01-27T22:30:40.726624+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: propagate finished, result: OK
[2026-01-27T23:22:53.541798+00:00][I][main.cc:53] Signal Terminated (15) received, stopping the server
[2026-01-27T23:22:58.500203+00:00][I][main.cc:168] kvrocks version 2.14.0 (commit a71eb42f)
[2026-01-27T23:22:58.511777+00:00][I][event_listener.cc:187] [event_listener/table_file_created] column family: default, file path: /data/db/000032.sst, file size: 2104, job_id: 1, reason: recovery, status: OK
[2026-01-27T23:22:58.513628+00:00][I][event_listener.cc:187] [event_listener/table_file_created] column family: metadata, file path: /data/db/000033.sst, file size: 6606, job_id: 1, reason: recovery, status: OK
[2026-01-27T23:22:58.525506+00:00][I][storage.cc:407] [storage] Success to load the data from disk: 21 ms
[2026-01-27T23:22:58.527352+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T23:22:58.527574+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T23:22:58.527688+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T23:22:58.527767+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T23:22:58.527848+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T23:22:58.527949+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T23:22:58.528244+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T23:22:58.528341+00:00][I][worker.cc:76] [worker] Listening on: 0.0.0.0:6666
[2026-01-27T23:22:58.528525+00:00][I][worker.cc:596] [worker] Thread #281472916578912 started
[2026-01-27T23:22:58.528585+00:00][I][worker.cc:596] [worker] Thread #281472958849632 started
[2026-01-27T23:22:58.528618+00:00][I][worker.cc:596] [worker] Thread #281472967303776 started
[2026-01-27T23:22:58.528648+00:00][I][worker.cc:596] [worker] Thread #281473036837472 started
[2026-01-27T23:22:58.528952+00:00][I][worker.cc:596] [worker] Thread #281473022157408 started
[2026-01-27T23:22:58.528990+00:00][I][worker.cc:596] [worker] Thread #281473009574496 started
[2026-01-27T23:22:58.529021+00:00][I][worker.cc:596] [worker] Thread #281473001120352 started
[2026-01-27T23:22:58.529044+00:00][I][worker.cc:596] [worker] Thread #281472992666208 started
[2026-01-27T23:22:58.529123+00:00][I][server.cc:245] [server] Ready to accept connections
[2026-01-27T23:24:00.045680+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: pubsub
[2026-01-27T23:24:00.046224+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: pubsub finished, result: OK
[2026-01-27T23:24:00.046312+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: propagate
[2026-01-27T23:24:00.046352+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: propagate finished, result: OK

View File

@@ -1,4 +0,0 @@
[2026-01-28T00:00:56.433217+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: pubsub
[2026-01-28T00:00:56.435397+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: pubsub finished, result: OK
[2026-01-28T00:00:56.435483+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: propagate
[2026-01-28T00:00:56.435537+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: propagate finished, result: OK

View File

@@ -1,4 +0,0 @@
[2026-01-29T00:00:32.264105+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: pubsub
[2026-01-29T00:00:32.270121+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: pubsub finished, result: OK
[2026-01-29T00:00:32.270189+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: propagate
[2026-01-29T00:00:32.270251+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: propagate finished, result: OK

View File

@@ -1,5 +0,0 @@
[2026-01-30T00:00:53.731511+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: pubsub
[2026-01-30T00:00:53.732305+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: pubsub finished, result: OK
[2026-01-30T00:00:53.732942+00:00][I][compaction_checker.cc:35] [compaction checker] Start to compact the column family: propagate
[2026-01-30T00:00:53.733051+00:00][I][compaction_checker.cc:38] [compaction checker] Compact the column family: propagate finished, result: OK
[2026-01-30T00:17:42.821542+00:00][I][main.cc:53] Signal Terminated (15) received, stopping the server

View File

@@ -1,169 +0,0 @@
/**
* Debug Middleware for Local Development
* Captures and logs all requests for research
*/
const fs = require('fs');
const path = require('path');
const DATA_DIR = process.env.DATA_DIR || '/app/data';
const LOG_FILE = path.join(DATA_DIR, 'debug-requests.jsonl');
const capturedRequests = [];
const MAX_CAPTURED = 500;
function extractSubdomain(host) {
if (!host) return null;
const hostname = host.split(':')[0];
const match = hostname.match(/^(sessions|account-data|telemetry|tools|oauth\.accounts|accounts)\./);
return match ? match[1] : null;
}
/**
* Debug middleware - logs requests (skip internal paths)
*/
function debugMiddleware(req, res, next) {
// Skip debug/health/admin endpoints
if (req.path.startsWith('/debug') || req.path === '/health' || req.path === '/favicon.ico') {
return next();
}
const startTime = Date.now();
const subdomain = extractSubdomain(req.headers.host);
// Color logging
const colors = { reset: '\x1b[0m', green: '\x1b[32m', blue: '\x1b[34m', yellow: '\x1b[33m', magenta: '\x1b[35m', cyan: '\x1b[36m', red: '\x1b[31m' };
const subColor = { 'sessions': colors.green, 'account-data': colors.blue, 'telemetry': colors.yellow, 'tools': colors.magenta }[subdomain] || colors.cyan;
console.log(`${subColor}[${subdomain || 'main'}]${colors.reset} ${req.method} ${req.url}`);
// Capture response
const origSend = res.send.bind(res);
const origJson = res.json.bind(res);
let responseBody = null;
res.send = function(body) {
responseBody = typeof body === 'string' && body.length > 2000 ? body.substring(0, 2000) + '...' : body;
return origSend(body);
};
res.json = function(body) {
try {
const str = JSON.stringify(body);
responseBody = str.length > 2000 ? { _truncated: true, _len: str.length } : body;
} catch (e) {
responseBody = { _error: 'serialize failed' };
}
return origJson(body);
};
res.on('finish', () => {
const duration = Date.now() - startTime;
const statusColor = res.statusCode >= 400 ? colors.red : colors.green;
console.log(` ${statusColor}-> ${res.statusCode}${colors.reset} (${duration}ms)`);
const entry = {
timestamp: new Date().toISOString(),
method: req.method,
path: req.path,
host: req.headers.host,
subdomain,
query: req.query,
body: req.body,
response: { statusCode: res.statusCode, duration, body: responseBody }
};
capturedRequests.unshift(entry);
if (capturedRequests.length > MAX_CAPTURED) capturedRequests.pop();
try { fs.appendFileSync(LOG_FILE, JSON.stringify(entry) + '\n'); } catch (e) {}
});
next();
}
/**
* Setup debug routes
*/
function setupDebugRoutes(app) {
// Debug dashboard
app.get('/debug', (req, res) => {
res.send(`<!DOCTYPE html>
<html><head><title>Debug</title>
<style>
body{font-family:monospace;background:#1a1a2e;color:#eee;padding:20px}
h1{color:#00d4ff}.section{background:#16213e;padding:15px;margin:10px 0;border-radius:8px}
.req{background:#0f3460;padding:8px;margin:4px 0;border-radius:4px;font-size:12px}
.GET{color:#4ade80}.POST{color:#60a5fa}.DELETE{color:#f87171}
.sub{color:#c084fc;font-weight:bold}pre{background:#0a0a1a;padding:8px;overflow-x:auto;font-size:11px}
button{background:#00d4ff;color:#000;border:none;padding:6px 12px;cursor:pointer;margin:4px;border-radius:4px}
select{padding:6px;background:#0f3460;border:1px solid #00d4ff;color:#fff;border-radius:4px}
details summary{cursor:pointer;color:#60a5fa}
</style></head><body>
<h1>Debug Dashboard</h1>
<div class="section"><button onclick="refresh()">Refresh</button><button onclick="clearReqs()">Clear</button>
<select id="filter" onchange="refresh()"><option value="">All</option><option value="sessions">sessions</option>
<option value="account-data">account-data</option><option value="telemetry">telemetry</option><option value="tools">tools</option></select>
<span id="count"></span></div>
<div class="section"><h2>Subdomains</h2><div id="subs"></div></div>
<div class="section"><h2>Requests</h2><div id="reqs" style="max-height:500px;overflow-y:auto"></div></div>
<script>
async function refresh(){
const f=document.getElementById('filter').value;
const r=await fetch('/debug/requests'+(f?'?subdomain='+f:''));
const d=await r.json();
document.getElementById('count').textContent=d.total+' total';
document.getElementById('reqs').innerHTML=d.requests.map(r=>\`<div class="req">
<span class="\${r.method}">\${r.method}</span> <span class="sub">\${r.subdomain||'main'}</span> \${r.path}
<span style="color:\${r.response?.statusCode<400?'#4ade80':'#f87171'}">\${r.response?.statusCode}</span>
<details><summary>Details</summary><pre>\${JSON.stringify(r,null,2)}</pre></details></div>\`).join('');
const s=await fetch('/debug/subdomains');
const sd=await s.json();
document.getElementById('subs').innerHTML=Object.entries(sd).map(([k,v])=>'<span class="sub">'+k+'</span>: '+v.count).join(' | ');
}
async function clearReqs(){await fetch('/debug/requests',{method:'DELETE'});refresh();}
setInterval(refresh,5000);refresh();
</script></body></html>`);
});
app.get('/debug/requests', (req, res) => {
let filtered = capturedRequests;
if (req.query.subdomain) filtered = filtered.filter(r => r.subdomain === req.query.subdomain);
res.json({ total: capturedRequests.length, filtered: filtered.length, requests: filtered.slice(0, 100) });
});
app.get('/debug/subdomains', (req, res) => {
const summary = {};
for (const r of capturedRequests) {
const sub = r.subdomain || 'main';
if (!summary[sub]) summary[sub] = { count: 0 };
summary[sub].count++;
}
res.json(summary);
});
app.delete('/debug/requests', (req, res) => {
capturedRequests.length = 0;
res.json({ cleared: true });
});
// Catch-all for unknown endpoints (must be registered last)
app.all('*', (req, res, next) => {
// Only catch truly unknown paths
if (res.headersSent) return;
const subdomain = extractSubdomain(req.headers.host);
console.log(`\x1b[31m[UNKNOWN]\x1b[0m ${req.method} ${req.path} (subdomain: ${subdomain})`);
res.status(200).json({
debug: true,
message: 'Unknown endpoint captured',
method: req.method,
path: req.path,
subdomain,
headers: req.headers,
body: req.body
});
});
}
module.exports = { debugMiddleware, setupDebugRoutes, capturedRequests };

View File

@@ -1,800 +0,0 @@
/**
* Debug Wrapper Entry Point
* Wraps the existing hytale-auth-server with debug capabilities
*/
const http = require('http');
const https = require('https');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// Only load debug functionality if DEBUG_MODE is enabled
const DEBUG_MODE = process.env.DEBUG_MODE === 'true';
const DATA_DIR = process.env.DATA_DIR || '/app/data';
const LOG_FILE = path.join(DATA_DIR, 'debug-requests.jsonl');
const SSL_DIR = path.join(DATA_DIR, 'ssl');
const SSL_KEY = path.join(SSL_DIR, 'server.key');
const SSL_CERT = path.join(SSL_DIR, 'server.crt');
const HTTPS_PORT = parseInt(process.env.HTTPS_PORT || '3443');
/**
* Generate self-signed SSL certificates if they don't exist
*/
function ensureSSLCertificates() {
if (!fs.existsSync(SSL_DIR)) {
fs.mkdirSync(SSL_DIR, { recursive: true });
}
if (!fs.existsSync(SSL_KEY) || !fs.existsSync(SSL_CERT)) {
console.log('Generating self-signed SSL certificates...');
try {
execSync(`openssl req -x509 -newkey rsa:2048 -keyout "${SSL_KEY}" -out "${SSL_CERT}" -days 365 -nodes -subj "/CN=localhost" 2>/dev/null`);
console.log('SSL certificates generated successfully');
} catch (err) {
console.error('Failed to generate SSL certificates:', err.message);
console.error('HTTPS will not be available. Install openssl or provide certificates manually.');
return null;
}
}
try {
return {
key: fs.readFileSync(SSL_KEY),
cert: fs.readFileSync(SSL_CERT)
};
} catch (err) {
console.error('Failed to read SSL certificates:', err.message);
return null;
}
}
// In-memory request storage for debug dashboard
const capturedRequests = [];
const MAX_CAPTURED = 500;
// Color codes for terminal output
const colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
blue: '\x1b[34m',
yellow: '\x1b[33m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
red: '\x1b[31m'
};
function extractSubdomain(host) {
if (!host) return null;
const hostname = host.split(':')[0];
const match = hostname.match(/^(sessions|account-data|telemetry|tools|oauth\.accounts|accounts)\./);
return match ? match[1] : null;
}
function getSubdomainColor(subdomain) {
const subColors = {
'sessions': colors.green,
'account-data': colors.blue,
'telemetry': colors.yellow,
'tools': colors.magenta
};
return subColors[subdomain] || colors.cyan;
}
/**
* Serve the debug dashboard HTML
*/
function serveDebugDashboard(res) {
const html = `<!DOCTYPE html>
<html><head><title>Debug Dashboard - Local Dev</title>
<style>
body{font-family:monospace;background:#1a1a2e;color:#eee;padding:20px;margin:0}
h1{color:#00d4ff;margin-bottom:10px}
.info{color:#888;font-size:12px;margin-bottom:20px}
.section{background:#16213e;padding:15px;margin:10px 0;border-radius:8px}
.req{background:#0f3460;padding:10px;margin:5px 0;border-radius:4px;font-size:12px;border-left:3px solid #00d4ff}
.req.error{border-left-color:#f87171}
.GET{color:#4ade80}.POST{color:#60a5fa}.DELETE{color:#f87171}.PUT{color:#fbbf24}
.sub{color:#c084fc;font-weight:bold}
.status{padding:2px 6px;border-radius:3px;font-size:11px}
.status.ok{background:#166534;color:#4ade80}
.status.error{background:#7f1d1d;color:#f87171}
pre{background:#0a0a1a;padding:10px;overflow-x:auto;font-size:11px;border-radius:4px;max-height:300px;overflow-y:auto}
button{background:#00d4ff;color:#000;border:none;padding:8px 16px;cursor:pointer;margin:4px;border-radius:4px;font-weight:bold}
button:hover{background:#00b8e6}
button.danger{background:#dc2626}
button.danger:hover{background:#b91c1c}
button.paused{background:#fbbf24;color:#000}
select{padding:8px;background:#0f3460;border:1px solid #00d4ff;color:#fff;border-radius:4px}
details summary{cursor:pointer;color:#60a5fa;padding:5px 0}
.timestamp{color:#666;font-size:10px}
.duration{color:#fbbf24}
.path{color:#fff}
#count{margin-left:10px;color:#4ade80}
#pauseStatus{margin-left:10px;color:#fbbf24;font-weight:bold}
.controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
</style></head><body>
<h1>Debug Dashboard - Local Development</h1>
<div class="info">Capturing all requests to auth server for research purposes</div>
<div class="section">
<div class="controls">
<button onclick="refresh()">Refresh</button>
<button id="pauseBtn" onclick="togglePause()">Pause</button>
<button class="danger" onclick="clearReqs()">Clear All</button>
<select id="filter" onchange="refresh()">
<option value="">All Subdomains</option>
<option value="sessions">sessions</option>
<option value="account-data">account-data</option>
<option value="telemetry">telemetry</option>
<option value="tools">tools</option>
<option value="main">main (no subdomain)</option>
</select>
<span id="count"></span>
<span id="pauseStatus"></span>
</div>
</div>
<div class="section">
<h2>Subdomain Summary</h2>
<div id="subs"></div>
</div>
<div class="section">
<h2>Recent Requests</h2>
<div id="reqs" style="max-height:600px;overflow-y:auto"></div>
</div>
<script>
let isPaused = false;
let refreshInterval = null;
async function refresh() {
const f = document.getElementById('filter').value;
const r = await fetch('/debug/requests' + (f ? '?subdomain=' + f : ''));
const d = await r.json();
document.getElementById('count').textContent = d.total + ' total, ' + d.filtered + ' shown';
document.getElementById('reqs').innerHTML = d.requests.map((r, i) => {
const statusClass = r.response?.statusCode >= 400 ? 'error' : 'ok';
const reqClass = r.response?.statusCode >= 400 ? 'error' : '';
return \`<div class="req \${reqClass}">
<div>
<span class="timestamp">\${r.timestamp}</span>
<span class="\${r.method}">\${r.method}</span>
<span class="sub">\${r.subdomain || 'main'}</span>
<span class="path">\${r.path}</span>
<span class="status \${statusClass}">\${r.response?.statusCode || '?'}</span>
<span class="duration">\${r.response?.duration || 0}ms</span>
</div>
<details ontoggle="handleDetailsToggle(this)">
<summary>Request Details</summary>
<pre>\${JSON.stringify({
host: r.host,
query: r.query,
body: r.body,
headers: r.headers
}, null, 2)}</pre>
</details>
<details ontoggle="handleDetailsToggle(this)">
<summary>Response</summary>
<pre>\${JSON.stringify(r.response, null, 2)}</pre>
</details>
</div>\`;
}).join('');
const s = await fetch('/debug/subdomains');
const sd = await s.json();
document.getElementById('subs').innerHTML = Object.entries(sd)
.map(([k, v]) => '<span class="sub">' + k + '</span>: ' + v.count)
.join(' | ') || '<em>No requests yet</em>';
}
function handleDetailsToggle(el) {
// Auto-pause when any details block is opened
if (el.open && !isPaused) {
togglePause();
}
}
function togglePause() {
isPaused = !isPaused;
const btn = document.getElementById('pauseBtn');
const status = document.getElementById('pauseStatus');
if (isPaused) {
btn.textContent = 'Resume';
btn.classList.add('paused');
status.textContent = '(auto-refresh paused)';
if (refreshInterval) {
clearInterval(refreshInterval);
refreshInterval = null;
}
} else {
btn.textContent = 'Pause';
btn.classList.remove('paused');
status.textContent = '';
refreshInterval = setInterval(autoRefresh, 3000);
refresh();
}
}
function autoRefresh() {
if (!isPaused) {
refresh();
}
}
async function clearReqs() {
if (confirm('Clear all captured requests?')) {
await fetch('/debug/requests', { method: 'DELETE' });
refresh();
}
}
refreshInterval = setInterval(autoRefresh, 3000);
refresh();
</script>
</body></html>`;
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(html);
}
/**
* Handle debug API endpoints
*/
function handleDebugApi(req, res, urlPath, url) {
if (urlPath === '/debug' || urlPath === '/debug/') {
serveDebugDashboard(res);
return true;
}
if (urlPath === '/debug/requests') {
if (req.method === 'DELETE') {
capturedRequests.length = 0;
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ cleared: true }));
return true;
}
let filtered = capturedRequests;
const subdomain = url.searchParams.get('subdomain');
if (subdomain) {
if (subdomain === 'main') {
filtered = filtered.filter(r => !r.subdomain);
} else {
filtered = filtered.filter(r => r.subdomain === subdomain);
}
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
total: capturedRequests.length,
filtered: filtered.length,
requests: filtered.slice(0, 100)
}));
return true;
}
if (urlPath === '/debug/subdomains') {
const summary = {};
for (const r of capturedRequests) {
const sub = r.subdomain || 'main';
if (!summary[sub]) summary[sub] = { count: 0 };
summary[sub].count++;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(summary));
return true;
}
return false;
}
/**
* Capture request/response for debugging
*/
function captureRequest(req, res, url, startTime) {
const subdomain = extractSubdomain(req.headers.host);
const originalEnd = res.end.bind(res);
let responseBody = null;
// Intercept res.end to capture response
res.end = function(chunk, encoding, callback) {
if (chunk) {
try {
const str = chunk.toString();
if (str.length > 2000) {
responseBody = { _truncated: true, _length: str.length, _preview: str.substring(0, 500) };
} else {
try {
responseBody = JSON.parse(str);
} catch {
responseBody = str;
}
}
} catch (e) {
responseBody = { _error: 'Could not capture response' };
}
}
const duration = Date.now() - startTime;
// Log with colors
const subColor = getSubdomainColor(subdomain);
const statusColor = res.statusCode >= 400 ? colors.red : colors.green;
console.log(`${subColor}[${subdomain || 'main'}]${colors.reset} ${req.method} ${url.pathname} ${statusColor}${res.statusCode}${colors.reset} (${duration}ms)`);
// Store in memory
const entry = {
timestamp: new Date().toISOString(),
method: req.method,
path: url.pathname,
host: req.headers.host,
subdomain,
query: Object.fromEntries(url.searchParams),
headers: sanitizeHeaders(req.headers),
response: {
statusCode: res.statusCode,
duration,
body: responseBody
}
};
capturedRequests.unshift(entry);
if (capturedRequests.length > MAX_CAPTURED) {
capturedRequests.pop();
}
// Also write to file
try {
fs.appendFileSync(LOG_FILE, JSON.stringify(entry) + '\n');
} catch (e) {
// Ignore file write errors
}
return originalEnd(chunk, encoding, callback);
};
}
/**
* Sanitize headers for logging (remove sensitive data)
*/
function sanitizeHeaders(headers) {
const sanitized = { ...headers };
// Keep Authorization header but might want to truncate in future
return sanitized;
}
/**
* Main entry point - wraps the existing server
*/
async function main() {
if (DEBUG_MODE) {
console.log('\x1b[33m=== DEBUG MODE ENABLED ===\x1b[0m');
console.log(`Debug dashboard: http://localhost:${process.env.PORT || 3000}/debug`);
console.log(`Request log: ${LOG_FILE}`);
console.log('');
}
// Load the original app module
const app = require('./app');
// If not debug mode, just run normally
if (!DEBUG_MODE) {
app.run();
return;
}
// In debug mode, we need to wrap the request handler
// Re-implement the server startup with debug wrapper
const config = require('./config');
const { connect: connectRedis, isConnected } = require('./services/redis');
const assets = require('./services/assets');
console.log('=== Hytale Auth Server (Debug Mode) ===');
console.log(`Domain: ${config.domain}`);
console.log(`Data directory: ${config.dataDir}`);
// Pre-load cosmetics
assets.preloadCosmetics();
// Connect to Redis
await connectRedis();
// Create the original request handler reference
const originalHandler = require('./app').handleRequest || null;
// Since handleRequest is not exported, we need a different approach
// Load all the modules manually
const middleware = require('./middleware');
const routes = require('./routes');
const { sendJson } = require('./utils/response');
const auth = require('./services/auth');
const crypto = require('crypto');
// Recreate the request handler with debug wrapping
async function debugWrappedHandler(req, res) {
const startTime = Date.now();
const url = new URL(req.url, `http://${req.headers.host}`);
const urlPath = url.pathname;
// Skip debug endpoints from capture (but still handle them)
if (urlPath.startsWith('/debug')) {
if (handleDebugApi(req, res, urlPath, url)) {
return;
}
}
// Skip favicon and health from debug capture
const skipCapture = urlPath === '/favicon.ico' || urlPath === '/health';
// Capture the request/response if not skipped
if (!skipCapture) {
captureRequest(req, res, url, startTime);
}
// Now delegate to original handling logic
// CORS headers
middleware.corsHeaders(res);
if (req.method === 'OPTIONS') {
middleware.handleOptions(req, res);
return;
}
// Handle binary uploads
const headCacheMatch = urlPath.match(/^\/avatar\/([^/]+)\/head-cache$/);
if (headCacheMatch && req.method === 'POST') {
routes.avatar.handleAvatarRoutes(req, res, urlPath, {});
return;
}
// Parse JSON body
const body = await middleware.parseBody(req);
// Store body for debug capture
if (!skipCapture && capturedRequests.length > 0 && !capturedRequests[0].body) {
capturedRequests[0].body = body;
}
// Extract user context
const { uuid, name, tokenScope } = middleware.extractUserContext(body, req.headers);
// Route the request (copied from app.js to avoid modification)
await routeRequestDebug(req, res, url, urlPath, body, uuid, name, tokenScope, middleware, routes, sendJson, auth, crypto);
}
// Create HTTP server with debug wrapper
const server = http.createServer(debugWrappedHandler);
server.listen(config.port, '0.0.0.0', () => {
console.log(`HTTP Server running on port ${config.port}`);
console.log(`Redis: ${isConnected() ? 'connected' : 'NOT CONNECTED'}`);
console.log(`\n\x1b[36mDebug Dashboard: http://localhost:${config.port}/debug\x1b[0m`);
});
// Create HTTPS server if certificates are available
const sslOptions = ensureSSLCertificates();
if (sslOptions) {
const httpsServer = https.createServer(sslOptions, debugWrappedHandler);
httpsServer.listen(HTTPS_PORT, '0.0.0.0', () => {
console.log(`\x1b[32mHTTPS Server running on port ${HTTPS_PORT}\x1b[0m`);
console.log(`\x1b[33mNote: Self-signed cert - add to keychain or use NODE_TLS_REJECT_UNAUTHORIZED=0\x1b[0m\n`);
});
}
}
/**
* Route request - copied from app.js to avoid modifying original
*/
async function routeRequestDebug(req, res, url, urlPath, body, uuid, name, tokenScope, middleware, routes, sendJson, auth, crypto) {
const headers = req.headers;
// Avatar viewer routes
if (urlPath.startsWith('/avatar/')) {
await routes.avatar.handleAvatarRoutes(req, res, urlPath, body);
return;
}
// Customizer route
if (urlPath.startsWith('/customizer')) {
routes.avatar.handleCustomizerRoute(req, res, urlPath);
return;
}
// Cosmetics list API
if (urlPath === '/cosmetics/list') {
routes.assets.handleCosmeticsList(req, res);
return;
}
// Single cosmetic item data API
if (urlPath.startsWith('/cosmetics/item/')) {
routes.assets.handleCosmeticItem(req, res, urlPath);
return;
}
// Static assets route
if (urlPath.startsWith('/assets/')) {
routes.assets.handleStaticAssets(req, res, urlPath);
return;
}
// Asset extraction route
if (urlPath.startsWith('/asset/')) {
routes.assets.handleAssetRoute(req, res, urlPath);
return;
}
// Download route
if (urlPath.startsWith('/download/')) {
routes.assets.handleDownload(req, res, urlPath);
return;
}
// Health check
if (urlPath === '/health' || urlPath === '/') {
routes.health.handleHealth(req, res);
return;
}
// Favicon
if (urlPath === '/favicon.ico') {
res.writeHead(204);
res.end();
return;
}
// JWKS endpoint
if (urlPath === '/.well-known/jwks.json' || urlPath === '/jwks.json') {
routes.health.handleJwks(req, res);
return;
}
// Server auto-auth
if (urlPath === '/server/auto-auth') {
routes.server.handleServerAutoAuth(req, res, body);
return;
}
// Server game profiles
if (urlPath === '/server/game-profiles' || urlPath === '/game-profiles') {
routes.server.handleServerGameProfiles(req, res, headers);
return;
}
// OAuth device authorization
if (urlPath === '/oauth2/device/auth') {
routes.server.handleOAuthDeviceAuth(req, res, body);
return;
}
// OAuth device verification
if (urlPath === '/oauth2/device/verify') {
const query = Object.fromEntries(url.searchParams);
routes.server.handleOAuthDeviceVerify(req, res, query);
return;
}
// OAuth token endpoint
if (urlPath === '/oauth2/token') {
routes.server.handleOAuthToken(req, res, body);
return;
}
// Game session endpoints
if (urlPath === '/game-session/new') {
routes.session.handleGameSessionNew(req, res, body, uuid, name);
return;
}
if (urlPath === '/game-session/refresh') {
await routes.session.handleGameSessionRefresh(req, res, body, uuid, name, headers);
return;
}
if (urlPath === '/game-session/child' || urlPath.includes('/game-session/child')) {
routes.session.handleGameSessionChild(req, res, body, uuid, name);
return;
}
// Authorization grant
if (urlPath === '/game-session/authorize' || urlPath.includes('/authorize') || urlPath.includes('/auth-grant')) {
routes.session.handleAuthorizationGrant(req, res, body, uuid, name, headers);
return;
}
// Token exchange
if (urlPath === '/server-join/auth-token' || urlPath === '/game-session/exchange' || urlPath.includes('/auth-token')) {
routes.session.handleTokenExchange(req, res, body, uuid, name, headers);
return;
}
// Session/Auth endpoints
if ((urlPath.includes('/session') || urlPath.includes('/child')) && !urlPath.startsWith('/admin')) {
routes.session.handleSession(req, res, body, uuid, name);
return;
}
if (urlPath.includes('/auth')) {
routes.session.handleAuth(req, res, body, uuid, name);
return;
}
if (urlPath.includes('/token')) {
routes.session.handleToken(req, res, body, uuid, name);
return;
}
if (urlPath.includes('/validate') || urlPath.includes('/verify')) {
routes.session.handleValidate(req, res, body, uuid, name);
return;
}
if (urlPath.includes('/refresh')) {
routes.session.handleRefresh(req, res, body, uuid, name);
return;
}
// Account data endpoints
if (urlPath === '/my-account/game-profile' || urlPath.includes('/game-profile')) {
await routes.account.handleGameProfile(req, res, body, uuid, name);
return;
}
if (urlPath === '/my-account/skin') {
await routes.account.handleSkin(req, res, body, uuid, name, routes.avatar.invalidateHeadCache);
return;
}
// Account-data skin endpoint
if (urlPath.startsWith('/account-data/skin/')) {
const skinUuid = urlPath.replace('/account-data/skin/', '');
await routes.account.handleSkin(req, res, body, skinUuid, name, routes.avatar.invalidateHeadCache);
return;
}
if (urlPath === '/my-account/cosmetics' || urlPath.includes('/my-account/cosmetics')) {
routes.account.handleCosmetics(req, res, body, uuid, name);
return;
}
if (urlPath === '/my-account/get-launcher-data') {
routes.account.handleLauncherData(req, res, body, uuid, name);
return;
}
if (urlPath === '/my-account/get-profiles') {
routes.account.handleGetProfiles(req, res, body, uuid, name);
return;
}
// Bug reports and feedback
if (urlPath === '/bugs/create' || urlPath === '/feedback/create') {
res.writeHead(204);
res.end();
return;
}
// Game session delete
if (urlPath === '/game-session' && req.method === 'DELETE') {
await routes.session.handleGameSessionDelete(req, res, headers);
return;
}
// Admin login
if (urlPath === '/admin/login' && req.method === 'POST') {
await routes.admin.handleAdminLogin(req, res, body);
return;
}
// Admin verify
if (urlPath === '/admin/verify') {
const token = headers['x-admin-token'] || url.searchParams.get('token');
await routes.admin.handleAdminVerify(req, res, token);
return;
}
// Admin dashboard
if (urlPath === '/admin' || urlPath === '/admin/') {
routes.admin.handleAdminDashboard(req, res);
return;
}
// Test page for head embed
if (urlPath === '/test/head') {
routes.avatar.handleTestHeadPage(req, res);
return;
}
// Protected admin routes
if (urlPath.startsWith('/admin/')) {
const validToken = await middleware.verifyAdminAuth(headers);
if (!validToken) {
sendJson(res, 401, { error: 'Unauthorized. Please login at /admin' });
return;
}
}
// Admin API endpoints
if (urlPath === '/admin/sessions' || urlPath === '/sessions/active') {
await routes.admin.handleActiveSessions(req, res);
return;
}
if (urlPath === '/admin/stats') {
await routes.admin.handleAdminStats(req, res);
return;
}
if (urlPath.startsWith('/admin/servers')) {
await routes.admin.handleAdminServers(req, res, url);
return;
}
if (urlPath === '/admin/search') {
await routes.admin.handlePlayerSearch(req, res, url);
return;
}
if (urlPath === '/admin/prerender-queue') {
await routes.admin.handlePrerenderQueue(req, res);
return;
}
// Profile lookup by UUID
if (urlPath.startsWith('/profile/uuid/')) {
const lookupUuid = urlPath.replace('/profile/uuid/', '');
await routes.account.handleProfileLookupByUuid(req, res, lookupUuid, headers);
return;
}
// Profile lookup by username
if (urlPath.startsWith('/profile/username/')) {
const lookupUsername = decodeURIComponent(urlPath.replace('/profile/username/', ''));
await routes.account.handleProfileLookupByUsername(req, res, lookupUsername, headers);
return;
}
// Profile endpoint
if (urlPath.includes('/profile') || urlPath.includes('/user') || urlPath.includes('/me')) {
routes.account.handleProfile(req, res, body, uuid, name);
return;
}
// Cosmetics endpoint
if (urlPath.includes('/cosmetic') || urlPath.includes('/unlocked') || urlPath.includes('/inventory')) {
routes.account.handleCosmetics(req, res, body, uuid, name);
return;
}
// Telemetry endpoint
if (urlPath.includes('/telemetry') || urlPath.includes('/analytics') || urlPath.includes('/event')) {
sendJson(res, 200, { success: true, received: true });
return;
}
// Catch-all for unknown endpoints - important for research!
console.log(`\x1b[31m[UNKNOWN ENDPOINT]\x1b[0m ${req.method} ${urlPath}`);
const requestHost = req.headers.host;
const authGrant = auth.generateAuthorizationGrant(uuid, name, crypto.randomUUID(), null, requestHost);
const accessToken = auth.generateIdentityToken(uuid, name, null, ['game.base'], requestHost);
sendJson(res, 200, {
debug: true,
message: 'Unknown endpoint captured for research',
endpoint: urlPath,
method: req.method,
identityToken: accessToken,
sessionToken: auth.generateSessionToken(uuid, requestHost),
authorizationGrant: authGrant,
accessToken: accessToken,
tokenType: 'Bearer',
user: { uuid, name, premium: true }
});
}
main().catch(err => {
console.error('Failed to start debug server:', err);
process.exit(1);
});

View File

@@ -1,43 +0,0 @@
services:
# Redis/Kvrocks for session storage
kvrocks:
image: apache/kvrocks:latest
command: --bind 0.0.0.0 --dir /data
ports:
- "6666:6666"
volumes:
- ./data/kvrocks:/data
# Use the real hytale-auth-server with debug wrapper
auth-server:
build:
context: ../../hytale-auth-server
dockerfile: ../Hytale-F2P/local-dev/Dockerfile.debug
ports:
- "3000:3000"
- "3443:3443"
environment:
- PORT=3000
- HTTPS_PORT=3443
- DOMAIN=localhost:3443
- USE_TLS=false
- DATA_DIR=/app/data
- REDIS_URL=redis://kvrocks:6666
- ADMIN_PASSWORD=localdev
- DEBUG_MODE=true
- KEY_ID=2025-10-01-sanasol
volumes:
- ./data/auth:/app/data
# Mount debug wrapper as entry point
- ./debug-wrapper.js:/app/src/debug-wrapper.js:ro
depends_on:
- kvrocks
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]
interval: 5s
timeout: 3s
retries: 10
networks:
default:
name: hytale-local-dev

View File

@@ -1,88 +0,0 @@
#!/bin/bash
# Hytale F2P Local Development - Start Script
# Usage: ./start.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
# Colors
GREEN='\033[0;32m'
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
echo ""
echo -e "${CYAN}========================================"
echo "Hytale F2P Local Dev Environment"
echo -e "========================================${NC}"
echo ""
# Create data directories
mkdir -p "$SCRIPT_DIR/data/auth" "$SCRIPT_DIR/data/kvrocks"
# Start Docker services
echo -e "${GREEN}Building and starting services...${NC}"
cd "$SCRIPT_DIR"
# Stop existing
docker compose down 2>/dev/null || true
# Build and start
docker compose up -d --build
# Wait for ready
echo -n "Waiting for server"
for i in {1..30}; do
if curl -s http://localhost:3000/health > /dev/null 2>&1; then
echo ""
echo -e "${GREEN}Server ready!${NC}"
break
fi
if [ $i -eq 30 ]; then
echo ""
echo -e "${RED}Server failed to start! Check logs:${NC}"
docker compose logs auth-server
exit 1
fi
echo -n "."
sleep 1
done
echo ""
echo -e "${CYAN}========================================"
echo "Services Running"
echo -e "========================================${NC}"
echo ""
docker compose ps
echo ""
echo -e "${CYAN}Endpoints:${NC}"
echo -e " Auth Server HTTP: ${GREEN}http://localhost:3000${NC}"
echo -e " Auth Server HTTPS: ${GREEN}https://localhost:3443${NC}"
echo -e " Debug Dashboard: ${GREEN}http://localhost:3000/debug${NC}"
echo -e " Admin Dashboard: ${GREEN}http://localhost:3000/admin${NC} (password: localdev)"
echo -e " JWKS: ${GREEN}http://localhost:3000/.well-known/jwks.json${NC}"
echo -e " Kvrocks: localhost:6666"
echo ""
echo -e "${CYAN}========================================"
echo "Using with Launcher"
echo -e "========================================${NC}"
echo ""
echo -e "Start launcher with HTTPS (recommended):"
echo -e " ${GREEN}cd $PROJECT_ROOT${NC}"
echo -e " ${GREEN}HYTALE_AUTH_DOMAIN=localhost:3443 NODE_TLS_REJECT_UNAUTHORIZED=0 npm start${NC}"
echo ""
echo -e "${YELLOW}First time SSL setup (macOS):${NC}"
echo -e " Add cert to keychain to avoid NODE_TLS_REJECT_UNAUTHORIZED:"
echo -e " ${GREEN}sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain data/auth/ssl/server.crt${NC}"
echo ""
echo -e "${CYAN}========================================"
echo "Useful Commands"
echo -e "========================================${NC}"
echo ""
echo -e "View logs: ${GREEN}docker compose logs -f auth-server${NC}"
echo -e "Stop services: ${GREEN}docker compose down${NC}"
echo -e "Restart: ${GREEN}docker compose restart auth-server${NC}"
echo -e "Check request log: ${GREEN}cat data/auth/debug-requests.jsonl | jq${NC}"
echo ""

159
main.js
View File

@@ -3,7 +3,7 @@ require('dotenv').config({ path: path.join(__dirname, '.env') });
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron'); const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
const { autoUpdater } = require('electron-updater'); const { autoUpdater } = require('electron-updater');
const fs = require('fs'); const fs = require('fs');
const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, saveLauncherHardwareAcceleration, loadLauncherHardwareAcceleration, saveAllowMultiInstance, loadAllowMultiInstance, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched, loadConfig, saveConfig, checkLaunchReady } = require('./backend/launcher'); const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, saveLauncherHardwareAcceleration, loadLauncherHardwareAcceleration, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched, loadConfig, saveConfig, checkLaunchReady } = require('./backend/launcher');
const { retryPWRDownload } = require('./backend/managers/gameManager'); const { retryPWRDownload } = require('./backend/managers/gameManager');
const { migrateUserDataToCentralized } = require('./backend/utils/userDataMigration'); const { migrateUserDataToCentralized } = require('./backend/utils/userDataMigration');
@@ -23,9 +23,8 @@ const profileManager = require('./backend/managers/profileManager');
logger.interceptConsole(); logger.interceptConsole();
// Single instance lock (skip if multi-instance mode is enabled) // Single instance lock
const multiInstanceEnabled = loadAllowMultiInstance(); const gotTheLock = app.requestSingleInstanceLock();
const gotTheLock = multiInstanceEnabled || app.requestSingleInstanceLock();
if (!gotTheLock) { if (!gotTheLock) {
console.log('Another instance is already running. Quitting...'); console.log('Another instance is already running. Quitting...');
@@ -85,13 +84,12 @@ function setDiscordActivity() {
largeImageText: 'Hytale F2P Launcher', largeImageText: 'Hytale F2P Launcher',
buttons: [ buttons: [
{ {
label: 'Download', label: 'GitHub',
url: 'https://git.sanhost.net/sanasol/hytale-f2p/releases' url: 'https://github.com/amiayweb/Hytale-F2P'
}, },
{ {
label: 'Community', label: 'Discord',
// url: 'https://discord.gg/Fhbb9Yk5WW' url: 'https://discord.gg/hf2pdc'
url: 'https://chat.sanhost.net/invite/Tfz4jCK4'
} }
] ]
}); });
@@ -741,15 +739,6 @@ ipcMain.handle('load-close-launcher', () => {
return loadCloseLauncherOnStart(); return loadCloseLauncherOnStart();
}); });
ipcMain.handle('save-allow-multi-instance', (event, enabled) => {
saveAllowMultiInstance(enabled);
return { success: true };
});
ipcMain.handle('load-allow-multi-instance', () => {
return loadAllowMultiInstance();
});
ipcMain.handle('save-launcher-hw-accel', (event, enabled) => { ipcMain.handle('save-launcher-hw-accel', (event, enabled) => {
saveLauncherHardwareAcceleration(enabled); saveLauncherHardwareAcceleration(enabled);
return { success: true }; return { success: true };
@@ -975,8 +964,8 @@ ipcMain.handle('open-external', async (event, url) => {
ipcMain.handle('open-download-page', async () => { ipcMain.handle('open-download-page', async () => {
try { try {
// Open Forgejo releases page for manual download // Open GitHub releases page for manual download
await shell.openExternal('https://git.sanhost.net/sanasol/hytale-f2p/releases/latest'); await shell.openExternal('https://github.com/amiayweb/Hytale-F2P/releases/latest');
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
console.error('Failed to open download page:', error); console.error('Failed to open download page:', error);
@@ -1079,7 +1068,7 @@ ipcMain.handle('load-settings', async () => {
} }
}); });
const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod, getModFiles, getCurrentUuid, getAllUuidMappings, setUuidForUser, generateNewUuid, deleteUuidForUser, resetCurrentUserUuid } = require('./backend/launcher'); const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod, getCurrentUuid, getAllUuidMappings, setUuidForUser, generateNewUuid, deleteUuidForUser, resetCurrentUserUuid } = require('./backend/launcher');
const os = require('os'); const os = require('os');
ipcMain.handle('get-local-app-data', async () => { ipcMain.handle('get-local-app-data', async () => {
@@ -1129,15 +1118,6 @@ ipcMain.handle('download-mod', async (event, modInfo) => {
} }
}); });
ipcMain.handle('get-mod-files', async (event, modId) => {
try {
return await getModFiles(modId);
} catch (error) {
console.error('Error getting mod files:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('uninstall-mod', async (event, modId, modsPath) => { ipcMain.handle('uninstall-mod', async (event, modId, modsPath) => {
try { try {
return await uninstallMod(modId, modsPath); return await uninstallMod(modId, modsPath);
@@ -1427,83 +1407,6 @@ ipcMain.handle('get-recent-logs', async (event, maxLines = 100) => {
ipcMain.handle('send-logs', async () => {
try {
const https = require('https');
const http = require('http');
const { collectLogs, createZipBuffer } = require('./backend/utils/logCollector');
const { files, meta } = collectLogs();
if (files.length === 0) {
return { success: false, error: 'No log files found' };
}
// Create ZIP with individual log files
const zipBuffer = createZipBuffer(files);
// Get auth server URL from core config
const { getAuthServerUrl } = require('./backend/core/config');
const authUrl = getAuthServerUrl();
// Build file names list
const fileNames = files.map(f => f.name).join(',');
return await new Promise((resolve) => {
const url = new URL(authUrl + '/logs/submit');
const transport = url.protocol === 'https:' ? https : http;
const options = {
hostname: url.hostname,
port: url.port || (url.protocol === 'https:' ? 443 : 80),
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/zip',
'Content-Length': zipBuffer.length,
'X-Log-Username': meta.username || 'unknown',
'X-Log-Platform': meta.platform || 'unknown',
'X-Log-Version': meta.version || 'unknown',
'X-Log-File-Count': String(files.length),
'X-Log-Files': fileNames
},
timeout: 30000
};
const req = transport.request(options, (res) => {
let body = '';
res.on('data', (chunk) => body += chunk);
res.on('end', () => {
try {
const data = JSON.parse(body);
if (res.statusCode === 200) {
resolve({ success: true, id: data.id, message: data.message });
} else {
resolve({ success: false, error: data.error || `Server error ${res.statusCode}` });
}
} catch (e) {
resolve({ success: false, error: `Invalid response: ${res.statusCode}` });
}
});
});
req.on('error', (err) => {
resolve({ success: false, error: err.message });
});
req.on('timeout', () => {
req.destroy();
resolve({ success: false, error: 'Request timed out' });
});
req.write(zipBuffer);
req.end();
});
} catch (error) {
console.error('Error sending logs:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('open-logs-folder', async () => { ipcMain.handle('open-logs-folder', async () => {
try { try {
const logDir = logger.getLogDirectory(); const logDir = logger.getLogDirectory();
@@ -1559,45 +1462,3 @@ ipcMain.handle('profile-update', async (event, id, updates) => {
} }
}); });
// Java Wrapper Config IPC
ipcMain.handle('load-wrapper-config', () => {
const { loadWrapperConfig } = require('./backend/launcher');
return loadWrapperConfig();
});
ipcMain.handle('save-wrapper-config', (event, config) => {
try {
const { saveWrapperConfig } = require('./backend/launcher');
saveWrapperConfig(config);
return { success: true };
} catch (error) {
console.error('Error saving wrapper config:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('reset-wrapper-config', () => {
try {
const { resetWrapperConfig } = require('./backend/launcher');
const config = resetWrapperConfig();
return { success: true, config };
} catch (error) {
console.error('Error resetting wrapper config:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('get-default-wrapper-config', () => {
const { getDefaultWrapperConfig } = require('./backend/launcher');
return getDefaultWrapperConfig();
});
ipcMain.handle('preview-wrapper-script', (event, config, platform) => {
const { generateWrapperScript } = require('./backend/launcher');
return generateWrapperScript(config || require('./backend/launcher').loadWrapperConfig(), platform || process.platform, '/path/to/java');
});
ipcMain.handle('get-current-platform', () => {
return process.platform;
});

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "hytale-f2p-launcher", "name": "hytale-f2p-launcher",
"version": "2.4.2", "version": "2.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hytale-f2p-launcher", "name": "hytale-f2p-launcher",
"version": "2.4.2", "version": "2.2.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"adm-zip": "^0.5.10", "adm-zip": "^0.5.10",

View File

@@ -1,8 +1,8 @@
{ {
"name": "hytale-f2p-launcher", "name": "hytale-f2p-launcher",
"version": "2.4.5", "version": "2.2.2",
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support", "description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
"homepage": "https://git.sanhost.net/sanasol/hytale-f2p", "homepage": "https://github.com/amiayweb/Hytale-F2P",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"start": "electron .", "start": "electron .",
@@ -26,6 +26,7 @@
"electron", "electron",
"auto-update", "auto-update",
"mod-manager" "mod-manager"
], ],
"maintainers": [ "maintainers": [
{ {
@@ -117,8 +118,9 @@
"createStartMenuShortcut": true "createStartMenuShortcut": true
}, },
"publish": { "publish": {
"provider": "generic", "provider": "github",
"url": "https://git.sanhost.net/sanasol/hytale-f2p/releases/download/latest" "owner": "amiayweb",
"repo": "Hytale-F2P"
} }
} }
} }

View File

@@ -20,8 +20,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
loadLanguage: () => ipcRenderer.invoke('load-language'), loadLanguage: () => ipcRenderer.invoke('load-language'),
saveCloseLauncher: (enabled) => ipcRenderer.invoke('save-close-launcher', enabled), saveCloseLauncher: (enabled) => ipcRenderer.invoke('save-close-launcher', enabled),
loadCloseLauncher: () => ipcRenderer.invoke('load-close-launcher'), loadCloseLauncher: () => ipcRenderer.invoke('load-close-launcher'),
saveAllowMultiInstance: (enabled) => ipcRenderer.invoke('save-allow-multi-instance', enabled),
loadAllowMultiInstance: () => ipcRenderer.invoke('load-allow-multi-instance'),
loadConfig: () => ipcRenderer.invoke('load-config'), loadConfig: () => ipcRenderer.invoke('load-config'),
saveConfig: (configUpdate) => ipcRenderer.invoke('save-config', configUpdate), saveConfig: (configUpdate) => ipcRenderer.invoke('save-config', configUpdate),
@@ -47,7 +45,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath), loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath),
downloadMod: (modInfo) => ipcRenderer.invoke('download-mod', modInfo), downloadMod: (modInfo) => ipcRenderer.invoke('download-mod', modInfo),
uninstallMod: (modId, modsPath) => ipcRenderer.invoke('uninstall-mod', modId, modsPath), uninstallMod: (modId, modsPath) => ipcRenderer.invoke('uninstall-mod', modId, modsPath),
getModFiles: (modId) => ipcRenderer.invoke('get-mod-files', modId),
toggleMod: (modId, modsPath) => ipcRenderer.invoke('toggle-mod', modId, modsPath), toggleMod: (modId, modsPath) => ipcRenderer.invoke('toggle-mod', modId, modsPath),
selectModFiles: () => ipcRenderer.invoke('select-mod-files'), selectModFiles: () => ipcRenderer.invoke('select-mod-files'),
copyModFile: (sourcePath, modsPath) => ipcRenderer.invoke('copy-mod-file', sourcePath, modsPath), copyModFile: (sourcePath, modsPath) => ipcRenderer.invoke('copy-mod-file', sourcePath, modsPath),
@@ -97,7 +94,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
getLogDirectory: () => ipcRenderer.invoke('get-log-directory'), getLogDirectory: () => ipcRenderer.invoke('get-log-directory'),
openLogsFolder: () => ipcRenderer.invoke('open-logs-folder'), openLogsFolder: () => ipcRenderer.invoke('open-logs-folder'),
getRecentLogs: (maxLines) => ipcRenderer.invoke('get-recent-logs', maxLines), getRecentLogs: (maxLines) => ipcRenderer.invoke('get-recent-logs', maxLines),
sendLogs: () => ipcRenderer.invoke('send-logs'),
// UUID Management methods // UUID Management methods
getCurrentUuid: () => ipcRenderer.invoke('get-current-uuid'), getCurrentUuid: () => ipcRenderer.invoke('get-current-uuid'),
@@ -107,14 +103,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
deleteUuidForUser: (username) => ipcRenderer.invoke('delete-uuid-for-user', username), deleteUuidForUser: (username) => ipcRenderer.invoke('delete-uuid-for-user', username),
resetCurrentUserUuid: () => ipcRenderer.invoke('reset-current-user-uuid'), resetCurrentUserUuid: () => ipcRenderer.invoke('reset-current-user-uuid'),
// Java Wrapper Config API
loadWrapperConfig: () => ipcRenderer.invoke('load-wrapper-config'),
saveWrapperConfig: (config) => ipcRenderer.invoke('save-wrapper-config', config),
resetWrapperConfig: () => ipcRenderer.invoke('reset-wrapper-config'),
getDefaultWrapperConfig: () => ipcRenderer.invoke('get-default-wrapper-config'),
previewWrapperScript: (config, platform) => ipcRenderer.invoke('preview-wrapper-script', config, platform),
getCurrentPlatform: () => ipcRenderer.invoke('get-current-platform'),
// Profile API // Profile API
profile: { profile: {
create: (name) => ipcRenderer.invoke('profile-create', name), create: (name) => ipcRenderer.invoke('profile-create', name),

View File

@@ -1,134 +0,0 @@
# Hytale F2P - Dedicated Server
Host your own Hytale server. The scripts handle everything automatically.
## Prerequisites
- **Java 25+** — [Windows installer](https://download.oracle.com/java/25/latest/jdk-25_windows-x64_bin.exe) | [Other platforms](https://adoptium.net/)
- If you have the F2P launcher installed, its bundled Java will be used automatically
- **Internet connection** for first launch (downloads ~3.5 GB of game files)
- If you have the F2P launcher installed, game files are copied locally (no download needed)
## Video Guide
[![Video Guide](https://img.youtube.com/vi/KvuXLH7SKvI/maxresdefault.jpg)](https://youtu.be/KvuXLH7SKvI)
## Quick Start
### Free Hosted Server (no PC required)
Use [play.hosting](https://play.hosting) to get a free Hytale server with F2P support:
1. Register at [play.hosting](https://play.hosting)
2. Create a **Hytale** server
3. Start the server once and wait for it to fully load
4. Go to **Files** → open the `mods` folder
5. Click **New****File via URL**
6. Paste: `https://github.com/sanasol/hytale-auth-server/releases/latest/download/dualauth-agent.jar`
7. Click **Query** and download the file
8. Go to **Console** and **Restart** the server
Done — your F2P server is ready to join.
### Windows (self-hosted)
1. Download `start.bat` to an empty folder
2. Double-click `start.bat`
3. Done — server starts on port **5520**
### Linux / macOS (self-hosted)
```bash
mkdir hytale-server && cd hytale-server
curl -O https://git.sanhost.net/sanasol/hytale-f2p/raw/branch/develop/server/start.sh
chmod +x start.sh
./start.sh
```
## What the scripts do
1. Search for the F2P launcher install (default paths + custom `installPath` from config)
2. Use bundled Java from the launcher, or fall back to system Java (25+ required)
3. Copy game files from the launcher install if available
4. Download missing files: `HytaleServer.jar` (~150 MB), `Assets.zip` (~3.3 GB), `dualauth-agent.jar` (~5 MB)
5. Check for updates on every launch (server, assets, and agent)
6. Generate a persistent server ID
7. Fetch authentication tokens
8. Start the server with dual-auth support
## Connecting
- **Same PC**: Connect to `localhost:5520` or `127.0.0.1:5520`
- **LAN**: Connect to your local IP (e.g. `192.168.1.x:5520`)
- **Internet**: Forward port `5520` (TCP + UDP) on your router, friends connect to your public IP
### No public IP? Use playit.gg (recommended)
If you're behind CGNAT or can't port forward, [playit.gg](https://playit.gg) gives you a public address for free:
1. Go to [playit.gg](https://playit.gg) and create an account
2. Download and run the playit agent
3. Create a tunnel — select **Hytale** as the game type, local port `5520`
4. Share the generated address with friends (e.g. `something.joinplayit.gg:12345`)
### Other options
- [Radmin VPN](https://www.radmin-vpn.com/) — virtual LAN, all players must install it
- [ZeroTier](https://www.zerotier.com/) — same idea, create a network, friends join and connect via VPN IP
## Configuration
Set environment variables before running the script:
| Variable | Default | Description |
|----------|---------|-------------|
| `SERVER_NAME` | `My Hytale Server` | Server name shown in listings |
| `BIND_ADDRESS` | `0.0.0.0:5520` | IP and port to listen on |
| `JVM_XMX` | *(Java default)* | Max memory (e.g. `4G`, `8G`) |
| `JVM_XMS` | *(Java default)* | Initial memory |
| `AUTH_MODE` | `authenticated` | Auth mode (`authenticated` or `none`) |
| `HYTALE_AUTH_DOMAIN` | `auth.sanasol.ws` | Auth server domain |
| `DOWNLOAD_BASE` | `https://download.sanasol.ws/download` | File download URL |
**Example (Linux):**
```bash
SERVER_NAME="Epic Server" JVM_XMX=4G ./start.sh
```
**Example (Windows):**
```cmd
set SERVER_NAME=Epic Server
set JVM_XMX=4G
start.bat
```
## Files created
```
your-folder/
├── start.sh / start.bat # Startup script
├── HytaleServer.jar # Game server (auto-downloaded)
├── Assets.zip # Game assets (auto-downloaded)
├── dualauth-agent.jar # Auth agent (auto-downloaded)
├── .server-id # Persistent server UUID
├── .versions/ # Version tracking for auto-updates
├── Server/ # Server data (created by server)
│ ├── config.json
│ └── worlds/
└── UserData/ # Player saves
```
## Troubleshooting
| Problem | Solution |
|---------|----------|
| `java not found` | Install the F2P launcher (includes Java) or install Java 25+ from [adoptium.net](https://adoptium.net/) |
| Download fails | Check internet connection. Files can be downloaded manually from `https://download.sanasol.ws/download/` |
| Port already in use | Change port: `BIND_ADDRESS=0.0.0.0:5521 ./start.sh` |
| Out of memory | Set more RAM: `JVM_XMX=4G ./start.sh` |
| Friends can't connect | Forward port 5520 (TCP+UDP) on your router, or use [playit.gg](https://playit.gg) if you can't port forward |
## Community
Need help? Join the community: TG Group: https://t.me/sanhostnet | TG Channel: https://t.me/hf2p_og | Chat: https://chat.sanhost.net/invite/Tfz4jCK4
<!-- Discord: https://discord.gg/Fhbb9Yk5WW -->

View File

@@ -1,441 +0,0 @@
@echo off
setlocal enabledelayedexpansion
:: ============================================================
:: Hytale F2P Dedicated Server - One-Click Starter
:: ============================================================
:: Just double-click this file to start your server!
::
:: The script will:
:: 1. Look for game files and Java in your F2P launcher install
:: 2. Auto-download anything missing
:: 3. Auto-update server, assets, and agent on each launch
:: 4. Fetch auth tokens and start the server
:: ============================================================
:: Configuration (edit these or set as environment variables)
if not defined HYTALE_AUTH_DOMAIN set "HYTALE_AUTH_DOMAIN=auth.sanasol.ws"
if not defined AUTH_SERVER set "AUTH_SERVER=https://%HYTALE_AUTH_DOMAIN%"
if not defined SERVER_NAME set "SERVER_NAME=My Hytale Server"
if not defined ASSETS_PATH set "ASSETS_PATH=.\Assets.zip"
if not defined BIND_ADDRESS set "BIND_ADDRESS=0.0.0.0:5520"
if not defined AUTH_MODE set "AUTH_MODE=authenticated"
if not defined DOWNLOAD_BASE set "DOWNLOAD_BASE=https://download.sanasol.ws/download"
:: File names
set "AGENT_JAR=dualauth-agent.jar"
set "SERVER_JAR=HytaleServer.jar"
set "AGENT_URL=https://github.com/sanasol/hytale-auth-server/releases/latest/download/dualauth-agent.jar"
set "AGENT_VERSION_API=https://api.github.com/repos/sanasol/hytale-auth-server/releases/latest"
set "VERSION_DIR=.versions"
echo ============================================================
echo Hytale F2P Dedicated Server
echo ============================================================
echo.
:: --- Prerequisite Checks ---
where curl >nul 2>&1
if errorlevel 1 (
echo [ERROR] curl is required but not found
echo [ERROR] curl comes with Windows 10+. Update Windows or install curl.
pause
exit /b 1
)
if not exist "%VERSION_DIR%" mkdir "%VERSION_DIR%"
:: --- Find Local F2P Launcher Install ---
set "F2P_DIR="
set "F2P_BASE=%USERPROFILE%\AppData\Local\HytaleF2P"
set "F2P_CONFIG=%F2P_BASE%\config.json"
set "JAVA_CMD=java"
:: Check config.json for custom installPath
set "F2P_CUSTOM_BASE="
if exist "%F2P_CONFIG%" (
for /f "delims=" %%p in ('powershell -Command "try { $c = Get-Content '%F2P_CONFIG%' | ConvertFrom-Json; if ($c.installPath) { $c.installPath.Trim() + '\HytaleF2P' } } catch {}" 2^>nul') do (
set "F2P_CUSTOM_BASE=%%p"
)
)
:: Search for game files: custom path first, then default
if defined F2P_CUSTOM_BASE (
if exist "!F2P_CUSTOM_BASE!\release\package\game\latest" (
set "F2P_DIR=!F2P_CUSTOM_BASE!\release\package\game\latest"
) else if exist "!F2P_CUSTOM_BASE!\pre-release\package\game\latest" (
set "F2P_DIR=!F2P_CUSTOM_BASE!\pre-release\package\game\latest"
)
)
if not defined F2P_DIR (
if exist "%F2P_BASE%\release\package\game\latest" (
set "F2P_DIR=%F2P_BASE%\release\package\game\latest"
) else if exist "%F2P_BASE%\pre-release\package\game\latest" (
set "F2P_DIR=%F2P_BASE%\pre-release\package\game\latest"
)
)
:: --- Find Java from F2P launcher ---
:: Check config.json for custom javaPath
if exist "%F2P_CONFIG%" (
for /f "delims=" %%j in ('powershell -Command "try { $c = Get-Content '%F2P_CONFIG%' | ConvertFrom-Json; if ($c.javaPath -and (Test-Path $c.javaPath)) { $c.javaPath.Trim() } } catch {}" 2^>nul') do (
set "JAVA_CMD=%%j"
echo [INFO] Found Java in F2P config: %%j
)
)
:: Check bundled JRE if no custom javaPath found
if "!JAVA_CMD!"=="java" (
set "F2P_JRE_BASE="
if defined F2P_CUSTOM_BASE (
if exist "!F2P_CUSTOM_BASE!\release\package\jre\latest\bin\java.exe" (
set "F2P_JRE_BASE=!F2P_CUSTOM_BASE!\release\package\jre\latest"
) else if exist "!F2P_CUSTOM_BASE!\pre-release\package\jre\latest\bin\java.exe" (
set "F2P_JRE_BASE=!F2P_CUSTOM_BASE!\pre-release\package\jre\latest"
)
)
if not defined F2P_JRE_BASE (
if exist "%F2P_BASE%\release\package\jre\latest\bin\java.exe" (
set "F2P_JRE_BASE=%F2P_BASE%\release\package\jre\latest"
) else if exist "%F2P_BASE%\pre-release\package\jre\latest\bin\java.exe" (
set "F2P_JRE_BASE=%F2P_BASE%\pre-release\package\jre\latest"
)
)
if defined F2P_JRE_BASE (
set "JAVA_CMD=!F2P_JRE_BASE!\bin\java.exe"
echo [INFO] Found Java in F2P launcher: !JAVA_CMD!
)
)
:: Verify java exists
"!JAVA_CMD!" -version >nul 2>&1
if errorlevel 1 (
where java >nul 2>&1
if errorlevel 1 (
echo [ERROR] Java is not installed and no F2P launcher JRE found
echo.
echo Options:
echo 1. Install the F2P launcher first ^(it includes Java^)
echo 2. Download Java 25: https://download.oracle.com/java/25/latest/jdk-25_windows-x64_bin.exe
echo.
pause
exit /b 1
)
set "JAVA_CMD=java"
)
:: Check Java version
for /f "tokens=3 delims= " %%v in ('"!JAVA_CMD!" -version 2^>^&1 ^| findstr /i "version"') do (
set "JAVA_VER_RAW=%%~v"
)
if defined JAVA_VER_RAW (
for /f "tokens=1 delims=." %%m in ("!JAVA_VER_RAW!") do set "JAVA_MAJOR=%%m"
)
echo [INFO] Java: !JAVA_VER_RAW! ^(!JAVA_CMD!^)
if defined JAVA_MAJOR (
if !JAVA_MAJOR! LSS 25 (
echo [ERROR] Java !JAVA_MAJOR! detected. Java 25+ is REQUIRED.
echo The DualAuth agent requires Java 25 ^(class file version 69^).
echo.
echo Download Java 25: https://download.oracle.com/java/25/latest/jdk-25_windows-x64_bin.exe
echo.
pause
exit /b 1
)
)
:: --- Copy game files from F2P install ---
if defined F2P_DIR (
echo [INFO] Found F2P launcher game files: !F2P_DIR!
if not exist "%SERVER_JAR%" (
if exist "!F2P_DIR!\Server\HytaleServer.jar" (
echo [INFO] Found HytaleServer.jar in F2P launcher
echo [INFO] Copying from: !F2P_DIR!\Server\HytaleServer.jar
copy "!F2P_DIR!\Server\HytaleServer.jar" "%SERVER_JAR%" >nul
echo [INFO] Copied successfully
)
)
if not exist "%ASSETS_PATH%" (
if exist "!F2P_DIR!\Assets.zip" (
echo [INFO] Found Assets.zip in F2P launcher
echo [INFO] Copying from: !F2P_DIR!\Assets.zip
copy "!F2P_DIR!\Assets.zip" "%ASSETS_PATH%" >nul
echo [INFO] Copied successfully
)
)
echo.
) else (
echo [INFO] No F2P launcher install found, will download files
echo.
)
:: --- Download / Update HytaleServer.jar ---
set "JAR_URL=%DOWNLOAD_BASE%/HytaleServer.jar"
set "JAR_VERSION_FILE=%VERSION_DIR%\HytaleServer.jar.version"
if not exist "%SERVER_JAR%" (
echo [INFO] HytaleServer.jar not found, downloading...
echo [INFO] Expected size: ~150 MB
curl -fL --progress-bar -o "%SERVER_JAR%.tmp" "%JAR_URL%" --connect-timeout 15 --max-time 3600
if exist "%SERVER_JAR%.tmp" (
move /y "%SERVER_JAR%.tmp" "%SERVER_JAR%" >nul
echo [INFO] HytaleServer.jar downloaded
for /f "delims=" %%h in ('powershell -Command "try { (Invoke-WebRequest -Uri '%JAR_URL%' -Method Head -UseBasicParsing).Headers['ETag'] } catch {}" 2^>nul') do (
echo %%h>"%JAR_VERSION_FILE%"
)
) else (
echo [ERROR] Failed to download HytaleServer.jar
echo [ERROR] Check your internet connection
pause
exit /b 1
)
) else (
echo [INFO] Checking for HytaleServer.jar updates...
set "LOCAL_JAR_VER="
if exist "%JAR_VERSION_FILE%" set /p LOCAL_JAR_VER=<"%JAR_VERSION_FILE%"
set "REMOTE_JAR_VER="
for /f "delims=" %%h in ('powershell -Command "try { (Invoke-WebRequest -Uri '%JAR_URL%' -Method Head -UseBasicParsing -TimeoutSec 10).Headers['ETag'] } catch { '' }" 2^>nul') do (
set "REMOTE_JAR_VER=%%h"
)
if defined REMOTE_JAR_VER (
if "!LOCAL_JAR_VER!"=="!REMOTE_JAR_VER!" (
echo [INFO] HytaleServer.jar is up to date
) else (
echo [INFO] HytaleServer.jar update available, downloading...
curl -fL --progress-bar -o "%SERVER_JAR%.tmp" "%JAR_URL%" --connect-timeout 15 --max-time 3600
if exist "%SERVER_JAR%.tmp" (
move /y "%SERVER_JAR%.tmp" "%SERVER_JAR%" >nul
echo !REMOTE_JAR_VER!>"%JAR_VERSION_FILE%"
echo [INFO] HytaleServer.jar updated
) else (
echo [WARN] Update failed, using existing HytaleServer.jar
)
)
) else (
echo [INFO] Could not check for updates, using existing HytaleServer.jar
)
)
:: --- Download / Update Assets.zip ---
set "ASSETS_URL=%DOWNLOAD_BASE%/Assets.zip"
set "ASSETS_VERSION_FILE=%VERSION_DIR%\Assets.zip.version"
if not exist "%ASSETS_PATH%" (
echo [INFO] Assets.zip not found, downloading...
echo [INFO] Expected size: ~3.3 GB - this will take a while
curl -fL --progress-bar -o "%ASSETS_PATH%.tmp" "%ASSETS_URL%" --connect-timeout 15 --max-time 7200
if exist "%ASSETS_PATH%.tmp" (
move /y "%ASSETS_PATH%.tmp" "%ASSETS_PATH%" >nul
echo [INFO] Assets.zip downloaded
for /f "delims=" %%h in ('powershell -Command "try { (Invoke-WebRequest -Uri '%ASSETS_URL%' -Method Head -UseBasicParsing).Headers['ETag'] } catch {}" 2^>nul') do (
echo %%h>"%ASSETS_VERSION_FILE%"
)
) else (
echo [ERROR] Failed to download Assets.zip
echo [ERROR] Check your internet connection
pause
exit /b 1
)
) else (
echo [INFO] Checking for Assets.zip updates...
set "LOCAL_ASSETS_VER="
if exist "%ASSETS_VERSION_FILE%" set /p LOCAL_ASSETS_VER=<"%ASSETS_VERSION_FILE%"
set "REMOTE_ASSETS_VER="
for /f "delims=" %%h in ('powershell -Command "try { (Invoke-WebRequest -Uri '%ASSETS_URL%' -Method Head -UseBasicParsing -TimeoutSec 10).Headers['ETag'] } catch { '' }" 2^>nul') do (
set "REMOTE_ASSETS_VER=%%h"
)
if defined REMOTE_ASSETS_VER (
if "!LOCAL_ASSETS_VER!"=="!REMOTE_ASSETS_VER!" (
echo [INFO] Assets.zip is up to date
) else (
echo [INFO] Assets.zip update available, downloading...
echo [INFO] This is a large file ^(~3.3 GB^), please be patient
curl -fL --progress-bar -o "%ASSETS_PATH%.tmp" "%ASSETS_URL%" --connect-timeout 15 --max-time 7200
if exist "%ASSETS_PATH%.tmp" (
move /y "%ASSETS_PATH%.tmp" "%ASSETS_PATH%" >nul
echo !REMOTE_ASSETS_VER!>"%ASSETS_VERSION_FILE%"
echo [INFO] Assets.zip updated
) else (
echo [WARN] Update failed, using existing Assets.zip
)
)
) else (
echo [INFO] Could not check for updates, using existing Assets.zip
)
)
:: --- Download / Update DualAuth Agent ---
set "AGENT_VERSION_FILE=%VERSION_DIR%\dualauth-agent.jar.version"
if not exist "%AGENT_JAR%" (
echo [INFO] Downloading DualAuth Agent...
curl -fL -# -o "%AGENT_JAR%.tmp" "%AGENT_URL%" --connect-timeout 15 --max-time 120
if exist "%AGENT_JAR%.tmp" (
move /y "%AGENT_JAR%.tmp" "%AGENT_JAR%" >nul
echo [INFO] DualAuth Agent downloaded
for /f "delims=" %%v in ('powershell -Command "try { $r = Invoke-RestMethod -Uri '%AGENT_VERSION_API%' -TimeoutSec 10; $r.tag_name } catch { '' }" 2^>nul') do (
echo %%v>"%AGENT_VERSION_FILE%"
)
) else (
echo [ERROR] Failed to download DualAuth Agent
echo [ERROR] Download manually: %AGENT_URL%
pause
exit /b 1
)
) else (
echo [INFO] Checking for DualAuth Agent updates...
set "LOCAL_AGENT_VER="
if exist "%AGENT_VERSION_FILE%" set /p LOCAL_AGENT_VER=<"%AGENT_VERSION_FILE%"
set "REMOTE_AGENT_VER="
for /f "delims=" %%v in ('powershell -Command "try { $r = Invoke-RestMethod -Uri '%AGENT_VERSION_API%' -TimeoutSec 10; $r.tag_name } catch { '' }" 2^>nul') do (
set "REMOTE_AGENT_VER=%%v"
)
if defined REMOTE_AGENT_VER (
if "!LOCAL_AGENT_VER!"=="!REMOTE_AGENT_VER!" (
echo [INFO] DualAuth Agent up to date ^(!LOCAL_AGENT_VER!^)
) else (
echo [INFO] Agent update: !LOCAL_AGENT_VER! -^> !REMOTE_AGENT_VER!
curl -fL -# -o "%AGENT_JAR%.tmp" "%AGENT_URL%" --connect-timeout 15 --max-time 120
if exist "%AGENT_JAR%.tmp" (
move /y "%AGENT_JAR%.tmp" "%AGENT_JAR%" >nul
echo !REMOTE_AGENT_VER!>"%AGENT_VERSION_FILE%"
echo [INFO] DualAuth Agent updated
) else (
echo [WARN] Agent update failed, using existing
)
)
) else (
echo [INFO] Could not check agent updates, using existing
)
)
:: --- Final Checks ---
if not exist "%SERVER_JAR%" (
echo [ERROR] HytaleServer.jar not found
pause
exit /b 1
)
if not exist "%ASSETS_PATH%" (
echo [ERROR] Assets.zip not found
pause
exit /b 1
)
if not exist "%AGENT_JAR%" (
echo [ERROR] dualauth-agent.jar not found
pause
exit /b 1
)
:: --- Generate or Load Server ID ---
set "SERVER_ID_FILE=.server-id"
if exist "%SERVER_ID_FILE%" (
set /p SERVER_ID=<"%SERVER_ID_FILE%"
echo [INFO] Server ID: !SERVER_ID!
) else (
for /f "delims=" %%i in ('powershell -Command "[guid]::NewGuid().ToString()"') do set "SERVER_ID=%%i"
echo !SERVER_ID!>"%SERVER_ID_FILE%"
echo [INFO] Generated server ID: !SERVER_ID!
)
:: --- Fetch Server Tokens ---
echo.
echo [INFO] Fetching server tokens from %AUTH_SERVER%...
set "TEMP_RESPONSE=%TEMP%\hytale_auth_%RANDOM%.json"
curl -s -X POST "%AUTH_SERVER%/server/auto-auth" ^
-H "Content-Type: application/json" ^
-d "{\"server_id\": \"!SERVER_ID!\", \"server_name\": \"%SERVER_NAME%\"}" ^
--connect-timeout 10 ^
--max-time 30 ^
-o "%TEMP_RESPONSE%" 2>nul
if errorlevel 1 (
echo [ERROR] Failed to connect to auth server at %AUTH_SERVER%
del "%TEMP_RESPONSE%" 2>nul
pause
exit /b 1
)
findstr /C:"sessionToken" "%TEMP_RESPONSE%" >nul 2>&1
if errorlevel 1 (
echo [ERROR] Invalid response from auth server:
type "%TEMP_RESPONSE%"
del "%TEMP_RESPONSE%" 2>nul
pause
exit /b 1
)
:: Extract tokens using PowerShell
for /f "delims=" %%i in ('powershell -Command "$j = Get-Content '%TEMP_RESPONSE%' | ConvertFrom-Json; $j.sessionToken"') do set "SESSION_TOKEN=%%i"
for /f "delims=" %%i in ('powershell -Command "$j = Get-Content '%TEMP_RESPONSE%' | ConvertFrom-Json; $j.identityToken"') do set "IDENTITY_TOKEN=%%i"
del "%TEMP_RESPONSE%" 2>nul
if "!SESSION_TOKEN!"=="" (
echo [ERROR] Could not extract session token from response
pause
exit /b 1
)
if "!IDENTITY_TOKEN!"=="" (
echo [ERROR] Could not extract identity token from response
pause
exit /b 1
)
echo [INFO] Tokens received successfully
:: --- Start Server ---
set "JAVA_ARGS="
if defined JVM_XMS set "JAVA_ARGS=!JAVA_ARGS! -Xms%JVM_XMS%"
if defined JVM_XMX set "JAVA_ARGS=!JAVA_ARGS! -Xmx%JVM_XMX%"
echo.
echo ============================================================
echo Starting Hytale Server
echo Name: %SERVER_NAME%
echo Bind: %BIND_ADDRESS%
echo Java: !JAVA_CMD!
echo Agent: %AGENT_JAR%
echo ============================================================
echo.
"!JAVA_CMD!" %JAVA_ARGS% -javaagent:"%AGENT_JAR%" -jar "%SERVER_JAR%" ^
--assets "%ASSETS_PATH%" ^
--bind "%BIND_ADDRESS%" ^
--auth-mode "%AUTH_MODE%" ^
--disable-sentry ^
--session-token "!SESSION_TOKEN!" ^
--identity-token "!IDENTITY_TOKEN!" ^
%*
echo.
echo ============================================================
echo Server stopped. Exit code: %ERRORLEVEL%
echo ============================================================
pause
endlocal

View File

@@ -1,516 +0,0 @@
#!/bin/bash
# ============================================================
# Hytale F2P Dedicated Server - One-Click Starter
# ============================================================
# Just run: ./start.sh
#
# The script will:
# 1. Look for game files in your F2P launcher install
# 2. Auto-download anything missing
# 3. Auto-update server, assets, and agent on each launch
# 4. Fetch auth tokens and start the server
# ============================================================
set -e
# Configuration (edit these or set as environment variables)
HYTALE_AUTH_DOMAIN="${HYTALE_AUTH_DOMAIN:-auth.sanasol.ws}"
AUTH_SERVER="${AUTH_SERVER:-https://$HYTALE_AUTH_DOMAIN}"
SERVER_NAME="${SERVER_NAME:-My Hytale Server}"
BIND_ADDRESS="${BIND_ADDRESS:-0.0.0.0:5520}"
AUTH_MODE="${AUTH_MODE:-authenticated}"
# Download URLs
DOWNLOAD_BASE="${DOWNLOAD_BASE:-https://download.sanasol.ws/download}"
AGENT_URL="https://github.com/sanasol/hytale-auth-server/releases/latest/download/dualauth-agent.jar"
AGENT_VERSION_API="https://api.github.com/repos/sanasol/hytale-auth-server/releases/latest"
# File names (in current directory)
AGENT_JAR="dualauth-agent.jar"
SERVER_JAR="HytaleServer.jar"
ASSETS_FILE="Assets.zip"
ASSETS_PATH="${ASSETS_PATH:-./Assets.zip}"
VERSION_DIR=".versions"
echo "============================================================"
echo " Hytale F2P Dedicated Server"
echo "============================================================"
echo ""
# --- Prerequisite Checks ---
if ! command -v curl &>/dev/null; then
echo "[ERROR] curl is required but not found"
echo " Install: sudo apt install curl"
exit 1
fi
mkdir -p "$VERSION_DIR"
# --- Find Local F2P Launcher Install ---
get_f2p_default_dir() {
case "$(uname -s)" in
Darwin) echo "$HOME/Library/Application Support/HytaleF2P" ;;
Linux) echo "$HOME/.hytalef2p" ;;
*) return 1 ;;
esac
}
# Read a JSON string field from config.json using available tools
read_config_field() {
local config_file="$1" field="$2"
if [ ! -f "$config_file" ]; then return 1; fi
if command -v python3 &>/dev/null; then
python3 -c "
import json
try:
c = json.load(open('$config_file'))
v = c.get('$field', '').strip()
if v: print(v)
except: pass
" 2>/dev/null
elif command -v jq &>/dev/null; then
jq -r ".$field // empty" "$config_file" 2>/dev/null
else
grep -o "\"$field\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "$config_file" 2>/dev/null | cut -d'"' -f4
fi
}
find_f2p_install() {
local default_app_dir
default_app_dir=$(get_f2p_default_dir) || return 1
local search_dirs=()
# Check config.json for custom installPath
local config_file="$default_app_dir/config.json"
local custom_path
custom_path=$(read_config_field "$config_file" "installPath")
if [ -n "$custom_path" ]; then
local custom_f2p="$custom_path/HytaleF2P"
if [ -d "$custom_f2p" ]; then
search_dirs+=("$custom_f2p")
fi
fi
# Always also check default location
search_dirs+=("$default_app_dir")
for base in "${search_dirs[@]}"; do
for branch in "release" "pre-release"; do
local game_dir="$base/$branch/package/game/latest"
if [ -d "$game_dir" ]; then
echo "$game_dir"
return 0
fi
done
done
return 1
}
# Find bundled JRE from F2P launcher install
find_f2p_java() {
local default_app_dir
default_app_dir=$(get_f2p_default_dir) || return 1
local search_dirs=()
# Check config.json for custom javaPath first
local config_file="$default_app_dir/config.json"
local custom_java
custom_java=$(read_config_field "$config_file" "javaPath")
if [ -n "$custom_java" ] && [ -x "$custom_java" ]; then
echo "$custom_java"
return 0
fi
# Check custom installPath
local custom_path
custom_path=$(read_config_field "$config_file" "installPath")
if [ -n "$custom_path" ]; then
local custom_f2p="$custom_path/HytaleF2P"
[ -d "$custom_f2p" ] && search_dirs+=("$custom_f2p")
fi
search_dirs+=("$default_app_dir")
for base in "${search_dirs[@]}"; do
for branch in "release" "pre-release"; do
local jre_dir="$base/$branch/package/jre/latest"
# Standard path
if [ -x "$jre_dir/bin/java" ]; then
echo "$jre_dir/bin/java"
return 0
fi
# macOS bundle path
if [ -x "$jre_dir/Contents/Home/bin/java" ]; then
echo "$jre_dir/Contents/Home/bin/java"
return 0
fi
done
done
return 1
}
copy_from_f2p() {
local file="$1" local_path="$2" f2p_path="$3"
if [ -f "$local_path" ]; then
return 1 # Already exists locally
fi
if [ -f "$f2p_path" ]; then
echo "[INFO] Found $file in F2P launcher install"
echo "[INFO] Copying from: $f2p_path"
cp "$f2p_path" "$local_path"
echo "[INFO] Copied successfully"
return 0
fi
return 1
}
# --- Detect Java ---
JAVA_CMD="java"
# Try F2P bundled JRE first
F2P_JAVA=$(find_f2p_java 2>/dev/null) || true
if [ -n "$F2P_JAVA" ]; then
echo "[INFO] Found Java in F2P launcher: $F2P_JAVA"
JAVA_CMD="$F2P_JAVA"
elif ! command -v java &>/dev/null; then
echo "[ERROR] Java is not installed and no F2P launcher JRE found"
echo ""
echo " Options:"
echo " 1. Install the F2P launcher first (it includes Java)"
echo " 2. Install Java 25+ manually:"
echo " Windows: https://download.oracle.com/java/25/latest/jdk-25_windows-x64_bin.exe"
echo " macOS: brew install openjdk"
echo " Ubuntu/Debian: sudo apt install openjdk-25-jre"
echo ""
exit 1
fi
# Check Java version
JAVA_FULL=$("$JAVA_CMD" -version 2>&1 | head -1)
JAVA_VER=$(echo "$JAVA_FULL" | grep -oP '(?<=")\d+' 2>/dev/null || echo "$JAVA_FULL" | sed 's/.*"\([0-9]*\).*/\1/')
echo "[INFO] Java: $JAVA_FULL"
if [ -n "$JAVA_VER" ] && [ "$JAVA_VER" -lt 25 ] 2>/dev/null; then
echo "[ERROR] Java $JAVA_VER detected. Java 25+ is REQUIRED."
echo " The DualAuth agent requires Java 25 (class file version 69)."
echo ""
if [ -n "$F2P_JAVA" ]; then
echo " Your F2P launcher JRE is outdated. Update the launcher to get a newer Java."
else
echo " Install Java 25+:"
echo " Windows: https://download.oracle.com/java/25/latest/jdk-25_windows-x64_bin.exe"
echo " macOS: brew install openjdk"
echo " Ubuntu/Debian: sudo apt install openjdk-25-jre"
fi
echo ""
exit 1
fi
# --- Find F2P Game Files ---
F2P_DIR=""
if F2P_DIR=$(find_f2p_install 2>/dev/null); then
echo "[INFO] Found F2P launcher game files: $F2P_DIR"
# Try to copy HytaleServer.jar from F2P install
copy_from_f2p "HytaleServer.jar" "$SERVER_JAR" "$F2P_DIR/Server/HytaleServer.jar" || true
# Try to copy Assets.zip from F2P install
copy_from_f2p "Assets.zip" "$ASSETS_PATH" "$F2P_DIR/Assets.zip" || true
echo ""
else
echo "[INFO] No F2P launcher install found, will download files"
echo ""
fi
# --- Download / Update Functions ---
get_remote_version() {
local url="$1"
local headers
headers=$(curl -sI -L "$url" --connect-timeout 10 --max-time 15 2>/dev/null | tr -d '\r')
local etag
etag=$(echo "$headers" | grep -i "^etag:" | tail -1 | sed 's/^[^:]*: *//' | tr -d '"')
if [ -n "$etag" ]; then printf '%s' "$etag"; return 0; fi
local lastmod
lastmod=$(echo "$headers" | grep -i "^last-modified:" | tail -1 | sed 's/^[^:]*: *//')
if [ -n "$lastmod" ]; then printf '%s' "$lastmod"; return 0; fi
local length
length=$(echo "$headers" | grep -i "^content-length:" | tail -1 | sed 's/^[^:]*: *//')
if [ -n "$length" ]; then printf 'size:%s' "$length"; return 0; fi
return 1
}
needs_update() {
local url="$1" dest="$2" name="$3"
local version_file="${VERSION_DIR}/${name}.version"
if [ ! -f "$dest" ]; then
echo "[INFO] $name not found, will download"
return 0
fi
echo "[INFO] Checking for $name updates..."
local remote_version
remote_version=$(get_remote_version "$url" 2>/dev/null) || true
if [ -z "$remote_version" ]; then
echo "[INFO] Could not check for updates, using existing $name"
return 1
fi
local local_version=""
[ -f "$version_file" ] && local_version=$(cat "$version_file" 2>/dev/null)
if [ "$remote_version" = "$local_version" ]; then
echo "[INFO] $name is up to date"
return 1
fi
if [ -n "$local_version" ]; then
echo "[INFO] $name update available"
fi
return 0
}
save_version() {
local url="$1" name="$2"
local version_file="${VERSION_DIR}/${name}.version"
local ver
ver=$(get_remote_version "$url" 2>/dev/null) || true
[ -n "$ver" ] && printf '%s\n' "$ver" > "$version_file"
}
download_file() {
local url="$1" dest="$2" name="$3" expected_mb="${4:-0}"
local tmp="${dest}.tmp"
echo "[INFO] Downloading $name..."
[ "$expected_mb" -gt 0 ] 2>/dev/null && echo "[INFO] Expected size: ~${expected_mb} MB"
for attempt in 1 2 3; do
rm -f "$tmp" 2>/dev/null || true
if [ "$expected_mb" -gt 50 ] 2>/dev/null; then
curl -fL --progress-bar -o "$tmp" "$url" --connect-timeout 15 --max-time 3600 2>&1
else
curl -fL -# -o "$tmp" "$url" --connect-timeout 15 --max-time 300 2>&1
fi
if [ $? -eq 0 ] && [ -f "$tmp" ]; then
local size
size=$(stat -c%s "$tmp" 2>/dev/null || stat -f%z "$tmp" 2>/dev/null || echo 0)
if [ "$size" -gt 1000 ]; then
mv -f "$tmp" "$dest"
local mb=$((size / 1024 / 1024))
echo "[INFO] $name downloaded (${mb} MB)"
return 0
fi
echo "[WARN] $name download too small (${size} bytes), retrying..."
fi
echo "[WARN] Download attempt $attempt failed, retrying..."
rm -f "$tmp" 2>/dev/null || true
sleep 2
done
echo "[ERROR] Failed to download $name after 3 attempts"
rm -f "$tmp" 2>/dev/null || true
return 1
}
# --- Download / Update Server Files ---
# HytaleServer.jar
JAR_URL="${DOWNLOAD_BASE}/HytaleServer.jar"
if needs_update "$JAR_URL" "$SERVER_JAR" "HytaleServer.jar"; then
if download_file "$JAR_URL" "$SERVER_JAR" "HytaleServer.jar" "150"; then
save_version "$JAR_URL" "HytaleServer.jar"
else
if [ ! -f "$SERVER_JAR" ]; then
echo "[ERROR] HytaleServer.jar is required. Check your internet connection."
exit 1
fi
echo "[WARN] Update failed, using existing HytaleServer.jar"
fi
fi
# Assets.zip
ASSETS_URL="${DOWNLOAD_BASE}/Assets.zip"
if needs_update "$ASSETS_URL" "$ASSETS_PATH" "Assets.zip"; then
echo "[INFO] Assets.zip is large (~3.3 GB), this may take a while..."
if download_file "$ASSETS_URL" "$ASSETS_PATH" "Assets.zip" "3300"; then
save_version "$ASSETS_URL" "Assets.zip"
else
if [ ! -f "$ASSETS_PATH" ]; then
echo "[ERROR] Assets.zip is required. Check your internet connection."
exit 1
fi
echo "[WARN] Update failed, using existing Assets.zip"
fi
fi
# DualAuth Agent (uses GitHub releases API for version tracking)
check_agent_update() {
if [ -f "$AGENT_JAR" ]; then
local agent_size
agent_size=$(stat -c%s "$AGENT_JAR" 2>/dev/null || stat -f%z "$AGENT_JAR" 2>/dev/null || echo 0)
if [ "$agent_size" -lt 10000 ]; then
echo "[WARN] Agent JAR seems corrupt (${agent_size} bytes), re-downloading..."
rm -f "$AGENT_JAR"
return 0
fi
local version_file="${VERSION_DIR}/${AGENT_JAR}.version"
local local_version=""
[ -f "$version_file" ] && local_version=$(cat "$version_file" 2>/dev/null)
echo "[INFO] Checking for DualAuth Agent updates..."
local remote_version
remote_version=$(curl -sf "$AGENT_VERSION_API" --connect-timeout 5 --max-time 10 2>/dev/null | grep -o '"tag_name":"[^"]*"' | cut -d'"' -f4)
if [ -n "$remote_version" ]; then
if [ "$local_version" = "$remote_version" ]; then
echo "[INFO] DualAuth Agent up to date ($local_version)"
return 1
else
echo "[INFO] Agent update: ${local_version:-unknown} -> $remote_version"
return 0
fi
else
echo "[INFO] Could not check agent updates, using existing"
return 1
fi
else
return 0
fi
}
AGENT_REMOTE_VERSION=""
if check_agent_update; then
echo "[INFO] Downloading DualAuth Agent..."
if curl -fL -# -o "${AGENT_JAR}.tmp" "$AGENT_URL" --connect-timeout 15 --max-time 120 2>&1 && [ -f "${AGENT_JAR}.tmp" ]; then
dl_size=$(stat -c%s "${AGENT_JAR}.tmp" 2>/dev/null || stat -f%z "${AGENT_JAR}.tmp" 2>/dev/null || echo 0)
if [ "$dl_size" -gt 10000 ]; then
mv -f "${AGENT_JAR}.tmp" "$AGENT_JAR"
AGENT_REMOTE_VERSION=$(curl -sf "$AGENT_VERSION_API" --connect-timeout 5 --max-time 10 2>/dev/null | grep -o '"tag_name":"[^"]*"' | cut -d'"' -f4)
[ -n "$AGENT_REMOTE_VERSION" ] && printf '%s\n' "$AGENT_REMOTE_VERSION" > "${VERSION_DIR}/${AGENT_JAR}.version"
echo "[INFO] DualAuth Agent ready (${AGENT_REMOTE_VERSION:-latest})"
else
echo "[WARN] Downloaded agent too small, discarding"
rm -f "${AGENT_JAR}.tmp"
fi
else
rm -f "${AGENT_JAR}.tmp" 2>/dev/null
if [ -f "$AGENT_JAR" ]; then
echo "[WARN] Agent update failed, using existing"
else
echo "[ERROR] Failed to download DualAuth Agent"
echo "[ERROR] Download manually: $AGENT_URL"
exit 1
fi
fi
fi
# --- Final Checks ---
for required in "$SERVER_JAR" "$ASSETS_PATH" "$AGENT_JAR"; do
if [ ! -f "$required" ]; then
echo "[ERROR] Required file missing: $required"
exit 1
fi
done
# --- Generate Server ID ---
SERVER_ID_FILE=".server-id"
if [ -f "$SERVER_ID_FILE" ]; then
SERVER_ID=$(cat "$SERVER_ID_FILE")
echo "[INFO] Server ID: $SERVER_ID"
else
SERVER_ID=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen 2>/dev/null || python3 -c "import uuid; print(uuid.uuid4())" 2>/dev/null)
if [ -z "$SERVER_ID" ]; then
echo "[ERROR] Could not generate UUID. Install uuidgen or python3."
exit 1
fi
printf '%s' "$SERVER_ID" > "$SERVER_ID_FILE"
echo "[INFO] Generated server ID: $SERVER_ID"
fi
# --- Fetch Tokens ---
echo ""
echo "[INFO] Fetching server tokens from $AUTH_SERVER..."
TEMP_RESPONSE=$(mktemp)
curl -s -X POST "$AUTH_SERVER/server/auto-auth" \
-H "Content-Type: application/json" \
-d "{\"server_id\": \"$SERVER_ID\", \"server_name\": \"$SERVER_NAME\"}" \
--connect-timeout 10 \
--max-time 30 \
-o "$TEMP_RESPONSE"
if [ $? -ne 0 ]; then
echo "[ERROR] Failed to connect to auth server at $AUTH_SERVER"
rm -f "$TEMP_RESPONSE"
exit 1
fi
if ! grep -q "sessionToken" "$TEMP_RESPONSE" 2>/dev/null; then
echo "[ERROR] Invalid response from auth server:"
cat "$TEMP_RESPONSE"
rm -f "$TEMP_RESPONSE"
exit 1
fi
# Extract tokens (python3 > jq > grep fallback)
if command -v python3 &>/dev/null; then
SESSION_TOKEN=$(python3 -c "import json,sys; print(json.load(open('$TEMP_RESPONSE'))['sessionToken'])")
IDENTITY_TOKEN=$(python3 -c "import json,sys; print(json.load(open('$TEMP_RESPONSE'))['identityToken'])")
elif command -v jq &>/dev/null; then
SESSION_TOKEN=$(jq -r '.sessionToken' "$TEMP_RESPONSE")
IDENTITY_TOKEN=$(jq -r '.identityToken' "$TEMP_RESPONSE")
else
SESSION_TOKEN=$(grep -o '"sessionToken":"[^"]*"' "$TEMP_RESPONSE" | cut -d'"' -f4)
IDENTITY_TOKEN=$(grep -o '"identityToken":"[^"]*"' "$TEMP_RESPONSE" | cut -d'"' -f4)
fi
rm -f "$TEMP_RESPONSE"
if [ -z "$SESSION_TOKEN" ] || [ -z "$IDENTITY_TOKEN" ]; then
echo "[ERROR] Could not extract tokens from auth server response"
exit 1
fi
echo "[INFO] Tokens received successfully"
# --- Start Server ---
JAVA_ARGS=""
[ -n "${JVM_XMS:-}" ] && JAVA_ARGS="$JAVA_ARGS -Xms$JVM_XMS"
[ -n "${JVM_XMX:-}" ] && JAVA_ARGS="$JAVA_ARGS -Xmx$JVM_XMX"
echo ""
echo "============================================================"
echo " Starting Hytale Server"
echo " Name: $SERVER_NAME"
echo " Bind: $BIND_ADDRESS"
echo " Agent: $AGENT_JAR"
echo "============================================================"
echo ""
exec "$JAVA_CMD" $JAVA_ARGS -javaagent:"$AGENT_JAR" -jar "$SERVER_JAR" \
--assets "$ASSETS_PATH" \
--bind "$BIND_ADDRESS" \
--auth-mode "$AUTH_MODE" \
--disable-sentry \
--session-token "$SESSION_TOKEN" \
--identity-token "$IDENTITY_TOKEN" \
"$@"

View File

@@ -1,523 +0,0 @@
#!/usr/bin/env node
/**
* UUID Persistence Tests
*
* Simulates the exact conditions that caused character data loss:
* - Config file corruption during updates
* - File locks making config temporarily unreadable
* - Username re-entry after config wipe
*
* Run: node test-uuid-persistence.js
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
// Use a temp directory so we don't mess with real config
const TEST_DIR = path.join(os.tmpdir(), 'hytale-uuid-test-' + Date.now());
const CONFIG_FILE = path.join(TEST_DIR, 'config.json');
const CONFIG_BACKUP = path.join(TEST_DIR, 'config.json.bak');
const CONFIG_TEMP = path.join(TEST_DIR, 'config.json.tmp');
const UUID_STORE_FILE = path.join(TEST_DIR, 'uuid-store.json');
// Track test results
let passed = 0;
let failed = 0;
const failures = [];
function assert(condition, message) {
if (condition) {
passed++;
console.log(`${message}`);
} else {
failed++;
failures.push(message);
console.log(` ✗ FAIL: ${message}`);
}
}
function assertEqual(actual, expected, message) {
if (actual === expected) {
passed++;
console.log(`${message}`);
} else {
failed++;
failures.push(`${message} (expected: ${expected}, got: ${actual})`);
console.log(` ✗ FAIL: ${message} (expected: "${expected}", got: "${actual}")`);
}
}
function cleanup() {
try {
if (fs.existsSync(TEST_DIR)) {
fs.rmSync(TEST_DIR, { recursive: true });
}
} catch (e) {}
}
function setup() {
cleanup();
fs.mkdirSync(TEST_DIR, { recursive: true });
}
// ============================================================================
// Inline the config functions so we can override paths
// (We can't require config.js directly because it uses hardcoded getAppDir())
// ============================================================================
function validateConfig(config) {
if (!config || typeof config !== 'object') return false;
if (config.userUuids !== undefined && typeof config.userUuids !== 'object') return false;
if (config.username !== undefined && (typeof config.username !== 'string')) return false;
return true;
}
function loadConfig() {
try {
if (fs.existsSync(CONFIG_FILE)) {
const data = fs.readFileSync(CONFIG_FILE, 'utf8');
if (data.trim()) {
const config = JSON.parse(data);
if (validateConfig(config)) return config;
console.warn('[Config] Primary config invalid structure, trying backup...');
}
}
} catch (err) {
console.error('[Config] Failed to load primary config:', err.message);
}
try {
if (fs.existsSync(CONFIG_BACKUP)) {
const data = fs.readFileSync(CONFIG_BACKUP, 'utf8');
if (data.trim()) {
const config = JSON.parse(data);
if (validateConfig(config)) {
console.log('[Config] Recovered from backup successfully');
try { fs.writeFileSync(CONFIG_FILE, data, 'utf8'); } catch (e) {}
return config;
}
}
}
} catch (err) {}
return {};
}
function saveConfig(update) {
const maxRetries = 3;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
if (!fs.existsSync(TEST_DIR)) fs.mkdirSync(TEST_DIR, { recursive: true });
const currentConfig = loadConfig();
// SAFETY CHECK: refuse to save if file exists but loaded empty
if (Object.keys(currentConfig).length === 0 && fs.existsSync(CONFIG_FILE)) {
const fileSize = fs.statSync(CONFIG_FILE).size;
if (fileSize > 2) {
console.error(`[Config] REFUSING to save — loaded empty but file exists (${fileSize} bytes). Retrying...`);
const delay = attempt * 50; // shorter delay for tests
const start = Date.now();
while (Date.now() - start < delay) {}
continue;
}
}
const newConfig = { ...currentConfig, ...update };
const data = JSON.stringify(newConfig, null, 2);
fs.writeFileSync(CONFIG_TEMP, data, 'utf8');
const verification = JSON.parse(fs.readFileSync(CONFIG_TEMP, 'utf8'));
if (!validateConfig(verification)) throw new Error('Validation failed');
if (fs.existsSync(CONFIG_FILE)) {
try {
const currentData = fs.readFileSync(CONFIG_FILE, 'utf8');
if (currentData.trim()) fs.writeFileSync(CONFIG_BACKUP, currentData, 'utf8');
} catch (e) {}
}
fs.renameSync(CONFIG_TEMP, CONFIG_FILE);
return true;
} catch (err) {
try { if (fs.existsSync(CONFIG_TEMP)) fs.unlinkSync(CONFIG_TEMP); } catch (e) {}
if (attempt >= maxRetries) throw err;
}
}
}
function loadUuidStore() {
try {
if (fs.existsSync(UUID_STORE_FILE)) {
const data = fs.readFileSync(UUID_STORE_FILE, 'utf8');
if (data.trim()) return JSON.parse(data);
}
} catch (err) {}
return {};
}
function saveUuidStore(store) {
const tmpFile = UUID_STORE_FILE + '.tmp';
fs.writeFileSync(tmpFile, JSON.stringify(store, null, 2), 'utf8');
fs.renameSync(tmpFile, UUID_STORE_FILE);
}
function migrateUuidStoreIfNeeded() {
if (fs.existsSync(UUID_STORE_FILE)) return;
const config = loadConfig();
if (config.userUuids && Object.keys(config.userUuids).length > 0) {
console.log('[UUID Store] Migrating', Object.keys(config.userUuids).length, 'UUIDs');
saveUuidStore(config.userUuids);
}
}
function getUuidForUser(username) {
const { v4: uuidv4 } = require('uuid');
if (!username || !username.trim()) throw new Error('Username required');
const displayName = username.trim();
const normalizedLookup = displayName.toLowerCase();
migrateUuidStoreIfNeeded();
// 1. Check UUID store (source of truth)
const uuidStore = loadUuidStore();
const storeKey = Object.keys(uuidStore).find(k => k.toLowerCase() === normalizedLookup);
if (storeKey) {
const existingUuid = uuidStore[storeKey];
if (storeKey !== displayName) {
delete uuidStore[storeKey];
uuidStore[displayName] = existingUuid;
saveUuidStore(uuidStore);
}
// Sync to config (non-critical)
try {
const config = loadConfig();
const configUuids = config.userUuids || {};
const configKey = Object.keys(configUuids).find(k => k.toLowerCase() === normalizedLookup);
if (!configKey || configUuids[configKey] !== existingUuid) {
if (configKey) delete configUuids[configKey];
configUuids[displayName] = existingUuid;
saveConfig({ userUuids: configUuids });
}
} catch (e) {}
return existingUuid;
}
// 2. Fallback: check config.json
const config = loadConfig();
const userUuids = config.userUuids || {};
const configKey = Object.keys(userUuids).find(k => k.toLowerCase() === normalizedLookup);
if (configKey) {
const recoveredUuid = userUuids[configKey];
uuidStore[displayName] = recoveredUuid;
saveUuidStore(uuidStore);
return recoveredUuid;
}
// 3. New user — generate UUID
const newUuid = uuidv4();
uuidStore[displayName] = newUuid;
saveUuidStore(uuidStore);
userUuids[displayName] = newUuid;
saveConfig({ userUuids });
return newUuid;
}
// ============================================================================
// OLD CODE (before fix) — for comparison testing
// ============================================================================
function getUuidForUser_OLD(username) {
const { v4: uuidv4 } = require('uuid');
if (!username || !username.trim()) throw new Error('Username required');
const displayName = username.trim();
const normalizedLookup = displayName.toLowerCase();
const config = loadConfig();
const userUuids = config.userUuids || {};
const existingKey = Object.keys(userUuids).find(k => k.toLowerCase() === normalizedLookup);
if (existingKey) {
return userUuids[existingKey];
}
// New user
const newUuid = uuidv4();
userUuids[displayName] = newUuid;
saveConfig({ userUuids });
return newUuid;
}
function saveConfig_OLD(update) {
// OLD saveConfig without safety check
if (!fs.existsSync(TEST_DIR)) fs.mkdirSync(TEST_DIR, { recursive: true });
const currentConfig = loadConfig();
// NO SAFETY CHECK — this is the bug
const newConfig = { ...currentConfig, ...update };
fs.writeFileSync(CONFIG_FILE, JSON.stringify(newConfig, null, 2), 'utf8');
return true;
}
// ============================================================================
// TESTS
// ============================================================================
console.log('\n' + '='.repeat(70));
console.log('UUID PERSISTENCE TESTS — Simulating update corruption scenarios');
console.log('='.repeat(70));
// --------------------------------------------------------------------------
// TEST 1: Normal flow — UUID stays consistent
// --------------------------------------------------------------------------
console.log('\n--- Test 1: Normal flow — UUID stays consistent ---');
setup();
const uuid1 = getUuidForUser('SpecialK');
const uuid2 = getUuidForUser('SpecialK');
const uuid3 = getUuidForUser('specialk'); // case insensitive
assertEqual(uuid1, uuid2, 'Same username returns same UUID');
assertEqual(uuid1, uuid3, 'Case-insensitive lookup returns same UUID');
assert(uuid1.match(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i), 'UUID is valid v4 format');
// --------------------------------------------------------------------------
// TEST 2: Simulate update corruption (THE BUG) — old code
// --------------------------------------------------------------------------
console.log('\n--- Test 2: OLD CODE — Config wipe during update loses UUID ---');
setup();
// Setup: player has UUID
const oldConfig = { username: 'SpecialK', userUuids: { 'SpecialK': 'aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee' }, hasLaunchedBefore: true };
fs.writeFileSync(CONFIG_FILE, JSON.stringify(oldConfig, null, 2), 'utf8');
const uuidBefore = getUuidForUser_OLD('SpecialK');
assertEqual(uuidBefore, 'aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee', 'UUID correct before corruption');
// Simulate: config.json gets corrupted (loadConfig returns {} because file locked)
// This simulates what happens when saveConfig reads an empty/locked file
fs.writeFileSync(CONFIG_FILE, '', 'utf8'); // Simulate corruption: empty file
// Old saveConfig behavior: reads empty, merges with update, saves
// This wipes userUuids
saveConfig_OLD({ hasLaunchedBefore: true });
const configAfterCorruption = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
assert(!configAfterCorruption.userUuids, 'OLD CODE: userUuids wiped after corruption');
assert(!configAfterCorruption.username, 'OLD CODE: username wiped after corruption');
// Player re-enters name, gets NEW UUID (character data lost!)
const uuidAfterOld = getUuidForUser_OLD('SpecialK');
assert(uuidAfterOld !== uuidBefore, 'OLD CODE: UUID changed after corruption (BUG!)');
// --------------------------------------------------------------------------
// TEST 3: NEW CODE — Config wipe during update, UUID survives via uuid-store
// --------------------------------------------------------------------------
console.log('\n--- Test 3: NEW CODE — Config wipe + UUID survives via uuid-store ---');
setup();
// Setup: player has UUID (stored in both config.json AND uuid-store.json)
const initialConfig = { username: 'SpecialK', userUuids: { 'SpecialK': 'aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee' }, hasLaunchedBefore: true };
fs.writeFileSync(CONFIG_FILE, JSON.stringify(initialConfig, null, 2), 'utf8');
// First call migrates to uuid-store
const uuidFirst = getUuidForUser('SpecialK');
assertEqual(uuidFirst, 'aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee', 'UUID correct before corruption');
assert(fs.existsSync(UUID_STORE_FILE), 'uuid-store.json created');
// Simulate: config.json gets wiped (same as the update bug)
fs.writeFileSync(CONFIG_FILE, '{}', 'utf8');
// Verify config is empty
const wipedConfig = loadConfig();
assert(!wipedConfig.userUuids || Object.keys(wipedConfig.userUuids).length === 0, 'Config wiped — no userUuids');
assert(!wipedConfig.username, 'Config wiped — no username');
// Player re-enters same name → UUID recovered from uuid-store!
const uuidAfterNew = getUuidForUser('SpecialK');
assertEqual(uuidAfterNew, 'aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee', 'NEW CODE: UUID preserved after config wipe!');
// --------------------------------------------------------------------------
// TEST 4: saveConfig safety check — refuses to overwrite good data with empty
// --------------------------------------------------------------------------
console.log('\n--- Test 4: saveConfig safety check — blocks destructive writes ---');
setup();
// Setup: valid config file with data
const goodConfig = { username: 'SpecialK', userUuids: { 'SpecialK': 'aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee' }, hasLaunchedBefore: true, installPath: 'C:\\Games\\Hytale' };
fs.writeFileSync(CONFIG_FILE, JSON.stringify(goodConfig, null, 2), 'utf8');
// Make the file temporarily unreadable by writing garbage (simulates file lock/corruption)
const originalContent = fs.readFileSync(CONFIG_FILE, 'utf8');
fs.writeFileSync(CONFIG_FILE, 'NOT VALID JSON!!!', 'utf8');
// Try to save — should refuse because file exists but can't be parsed
let saveThrew = false;
try {
saveConfig({ someNewField: true });
} catch (e) {
saveThrew = true;
}
// The file should still have the garbage (not overwritten with { someNewField: true })
const afterContent = fs.readFileSync(CONFIG_FILE, 'utf8');
// Restore original for backup recovery test
fs.writeFileSync(CONFIG_FILE, JSON.stringify(goodConfig, null, 2), 'utf8');
// Note: with invalid JSON, loadConfig returns {} and safety check triggers
// The save may eventually succeed on retry if the file becomes readable
// What matters is that it doesn't blindly overwrite
assert(afterContent !== '{\n "someNewField": true\n}', 'Safety check prevented blind overwrite of corrupted file');
// --------------------------------------------------------------------------
// TEST 5: Backup recovery — config.json corrupted, recovered from .bak
// --------------------------------------------------------------------------
console.log('\n--- Test 5: Backup recovery — auto-recover from .bak ---');
setup();
// Create config and backup
const validConfig = { username: 'SpecialK', userUuids: { 'SpecialK': 'aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee' } };
fs.writeFileSync(CONFIG_BACKUP, JSON.stringify(validConfig, null, 2), 'utf8');
fs.writeFileSync(CONFIG_FILE, 'CORRUPTED', 'utf8');
const recovered = loadConfig();
assertEqual(recovered.username, 'SpecialK', 'Username recovered from backup');
assert(recovered.userUuids && recovered.userUuids['SpecialK'] === 'aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee', 'UUID recovered from backup');
// --------------------------------------------------------------------------
// TEST 6: Full update simulation — the exact scenario from player report
// --------------------------------------------------------------------------
console.log('\n--- Test 6: Full update simulation (player report scenario) ---');
setup();
// Step 1: Player installs v2.3.4, sets username, plays game
console.log(' Step 1: Player sets up profile...');
saveConfig({ username: 'Special K', hasLaunchedBefore: true });
const originalUuid = getUuidForUser('Special K');
console.log(` Original UUID: ${originalUuid}`);
// Step 2: v2.3.5 auto-update — new app launches
console.log(' Step 2: Simulating v2.3.5 update...');
// Simulate the 3 saveConfig calls that happen during startup
// But first, simulate config being temporarily locked (returns empty)
const preUpdateContent = fs.readFileSync(CONFIG_FILE, 'utf8');
fs.writeFileSync(CONFIG_FILE, '', 'utf8'); // Simulate: file empty during write (race condition)
// These are the 3 calls from: profileManager.init, migrateUserDataToCentralized, handleFirstLaunchCheck
// With our safety check, they should NOT wipe the data
try { saveConfig({ hasLaunchedBefore: true }); } catch (e) { /* expected — safety check blocks it */ }
// Simulate file becomes readable again (antivirus releases lock)
fs.writeFileSync(CONFIG_FILE, preUpdateContent, 'utf8');
// Step 3: Player re-enters username (because UI might show empty)
console.log(' Step 3: Player re-enters username...');
const postUpdateUuid = getUuidForUser('Special K');
console.log(` Post-update UUID: ${postUpdateUuid}`);
assertEqual(postUpdateUuid, originalUuid, 'UUID survived the full update cycle!');
// --------------------------------------------------------------------------
// TEST 7: Multiple users — UUIDs stay independent
// --------------------------------------------------------------------------
console.log('\n--- Test 7: Multiple users — UUIDs stay independent ---');
setup();
const uuidAlice = getUuidForUser('Alice');
const uuidBob = getUuidForUser('Bob');
const uuidCharlie = getUuidForUser('Charlie');
assert(uuidAlice !== uuidBob, 'Alice and Bob have different UUIDs');
assert(uuidBob !== uuidCharlie, 'Bob and Charlie have different UUIDs');
// Wipe config, all should survive
fs.writeFileSync(CONFIG_FILE, '{}', 'utf8');
assertEqual(getUuidForUser('Alice'), uuidAlice, 'Alice UUID survived config wipe');
assertEqual(getUuidForUser('Bob'), uuidBob, 'Bob UUID survived config wipe');
assertEqual(getUuidForUser('Charlie'), uuidCharlie, 'Charlie UUID survived config wipe');
// --------------------------------------------------------------------------
// TEST 8: UUID store deleted — recovery from config.json
// --------------------------------------------------------------------------
console.log('\n--- Test 8: UUID store deleted — recovery from config.json ---');
setup();
// Create UUID via normal flow (saves to both stores)
const uuidOriginal = getUuidForUser('TestPlayer');
// Delete uuid-store.json (simulates user manually deleting it or disk issue)
fs.unlinkSync(UUID_STORE_FILE);
assert(!fs.existsSync(UUID_STORE_FILE), 'uuid-store.json deleted');
// UUID should be recovered from config.json
const uuidRecovered = getUuidForUser('TestPlayer');
assertEqual(uuidRecovered, uuidOriginal, 'UUID recovered from config.json after uuid-store deletion');
assert(fs.existsSync(UUID_STORE_FILE), 'uuid-store.json recreated after recovery');
// --------------------------------------------------------------------------
// TEST 9: Both stores deleted — new UUID generated (fresh install)
// --------------------------------------------------------------------------
console.log('\n--- Test 9: Both stores deleted — new UUID (fresh install) ---');
setup();
const uuidFresh = getUuidForUser('NewPlayer');
// Delete both
fs.unlinkSync(UUID_STORE_FILE);
fs.unlinkSync(CONFIG_FILE);
const uuidAfterWipe = getUuidForUser('NewPlayer');
assert(uuidAfterWipe !== uuidFresh, 'New UUID generated when both stores are gone (expected for true fresh install)');
// --------------------------------------------------------------------------
// TEST 10: Worst case — config.json wiped AND uuid-store.json exists
// Simulates the EXACT player-reported scenario with new code
// --------------------------------------------------------------------------
console.log('\n--- Test 10: Exact player scenario with new code ---');
setup();
// Player has been playing for a while
saveConfig({
username: 'Special K',
hasLaunchedBefore: true,
installPath: 'C:\\Games\\Hytale',
version_client: '2026.02.19-1a311a592',
version_branch: 'release',
userUuids: { 'Special K': '11111111-2222-4333-9444-555555555555' }
});
// First call creates uuid-store.json
const originalUuid10 = getUuidForUser('Special K');
assertEqual(originalUuid10, '11111111-2222-4333-9444-555555555555', 'Original UUID loaded');
// BOOM: Update happens, config.json completely wiped
fs.writeFileSync(CONFIG_FILE, '{}', 'utf8');
// Username lost — player has to re-enter
const loadedUsername = loadConfig().username;
assert(!loadedUsername, 'Username is gone from config (simulating what player saw)');
// Player types "Special K" again in settings
saveConfig({ username: 'Special K' });
// Player clicks Play — getUuidForUser called
const recoveredUuid10 = getUuidForUser('Special K');
assertEqual(recoveredUuid10, '11111111-2222-4333-9444-555555555555', 'UUID recovered — character data preserved!');
// ============================================================================
// RESULTS
// ============================================================================
console.log('\n' + '='.repeat(70));
console.log(`RESULTS: ${passed} passed, ${failed} failed`);
if (failed > 0) {
console.log('\nFailures:');
failures.forEach(f => console.log(`${f}`));
}
console.log('='.repeat(70));
cleanup();
process.exit(failed > 0 ? 1 : 0);

View File

@@ -1,19 +0,0 @@
@echo off
setlocal EnableDelayedExpansion
REM Java wrapper for Windows - strips UseCompactObjectHeaders, adds --disable-sentry
REM Uses %* string replacement instead of for-loop to preserve = signs in JVM flags
set "REAL_JAVA=%~dp0java-original.exe"
set "ARGS=%*"
REM Strip -XX:+UseCompactObjectHeaders
set "ARGS=!ARGS:-XX:+UseCompactObjectHeaders=!"
REM Check if running HytaleServer.jar and add --disable-sentry
echo !ARGS! | findstr /i "HytaleServer.jar" >nul 2>&1
if !ERRORLEVEL!==0 (
"%REAL_JAVA%" !ARGS! --disable-sentry
) else (
"%REAL_JAVA%" !ARGS!
)
exit /b !ERRORLEVEL!

53
test.md
View File

@@ -1,53 +0,0 @@
# Steam Deck Debug Commands
## GDB Stack Trace
Run patched game under gdb to get crash location:
```bash
cd ~/.hytalef2p/release/package/game/latest
gdb -ex "run --app-dir /home/deck/.hytalef2p/release/package/game/latest --java-exec /home/deck/.hytalef2p/release/package/jre/latest/bin/java --auth-mode authenticated --uuid c500dc3e-2791-4df6-be1e-5574600339c0 --name Player --identity-token eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjUtMTAtMDEtc2FuYXNvbCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjNTAwZGMzZS0yNzkxLTRkZjYtYmUxZS01NTc0NjAwMzM5YzAiLCJuYW1lIjoiUGxheWVyIiwidXNlcm5hbWUiOiJQbGF5ZXIiLCJwcm9maWxlIjp7InVzZXJuYW1lIjoiUGxheWVyIn0sImVudGl0bGVtZW50cyI6WyJnYW1lLmJhc2UiXSwic2NvcGUiOiJoeXRhbGU6c2VydmVyIGh5dGFsZTpjbGllbnQiLCJpYXQiOjE3Njk0NzkzMjgsImV4cCI6MTc2OTUxNTMyOCwiaXNzIjoiaHR0cHM6Ly9zZXNzaW9ucy5zYW5hc29sLndzIiwianRpIjoiYTAxYjI1MDItZWE1Mi00NTM2LWI3ODQtN2RjNzljMjlkZjM0In0.BtV846L9_rWRINFvDqrtg1ZJhIVQRNsrN550UULN9j5yhMxP1TJx4bDxS2bkpRtxVmkdcA_xhQWapRSlFywZAA --session-token eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjUtMTAtMDEtc2FuYXNvbCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjNTAwZGMzZS0yNzkxLTRkZjYtYmUxZS01NTc0NjAwMzM5YzAiLCJzY29wZSI6Imh5dGFsZTpzZXJ2ZXIiLCJpYXQiOjE3Njk0NzkzMjgsImV4cCI6MTc2OTUxNTMyOCwiaXNzIjoiaHR0cHM6Ly9zZXNzaW9ucy5zYW5hc29sLndzIiwianRpIjoiMzI0M2EwZTAtMjU2Mi00MzQ1LTljNGMtZWUyZDA5NDQ1NWI0In0.38ae2bCyAtjI0egmuy0v7u6caIi9hJGJFVeoeSvckQmaZMf-KaeHu4k5cF17d9eKXUxfEK_Ahjb1PTvIQhKtAA --user-dir /home/deck/.hytalesaves" ./Client/HytaleClient
```
After crash, in gdb prompt:
```
bt
bt full
info registers
quit
```
## Hex Dump Commands
Search for full hytale.com UTF-16LE:
```bash
xxd ~/.hytalef2p/release/package/game/latest/Client/HytaleClient.original | grep "6800 7900 7400 6100 6c00 6500 2e00 6300 6f00 6d00"
```
Search for partial matches:
```bash
xxd ~/.hytalef2p/release/package/game/latest/Client/HytaleClient.original | grep -i "h.y.t" | head -10
```
## Test Different Patch Modes
Restore and test:
```bash
# Restore original
cp ~/.hytalef2p/release/package/game/latest/Client/HytaleClient.original ~/.hytalef2p/release/package/game/latest/Client/HytaleClient
rm ~/.hytalef2p/release/package/game/latest/Client/HytaleClient.patched_custom
# Test UTF-16LE mode with same-length domain
HYTALE_PATCH_MODE=utf16le HYTALE_AUTH_DOMAIN=sanasol.ws HYTALE_SKIP_SENTRY_PATCH=1 HYTALE_SKIP_SUBDOMAIN_PATCH=1 npm start
# Test length-prefixed mode (default)
HYTALE_AUTH_DOMAIN=sanasol.ws HYTALE_SKIP_SENTRY_PATCH=1 HYTALE_SKIP_SUBDOMAIN_PATCH=1 npm start
```
## Binary Validation
```bash
file ~/.hytalef2p/release/package/game/latest/Client/HytaleClient
ldd ~/.hytalef2p/release/package/game/latest/Client/HytaleClient
```