Files
hytale-f2p-mirror/docs/PATCH_CDN_INFRASTRUCTURE.md
sanasol 0dafb17c7b v2.3.1: CDN redirect gateway, fix token username bug
- Migrate patch downloads to auth server redirect gateway (302 -> CDN)
  Allows instant CDN switching via admin panel without launcher update
- Fix identity token "Player" username mismatch on fresh install
  Add token username verification with retry in fetchAuthTokens
- Refactor versionManager to use mirror manifest via auth.sanasol.ws/patches
- Add optimal patch routing (BFS) for differential updates
- Add PATCH_CDN_INFRASTRUCTURE.md documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 14:36:09 +01:00

7.1 KiB

Patch CDN Infrastructure & Game Update System

Overview

The F2P launcher downloads game patches through a CDN redirect gateway hosted on the auth server. This allows instant CDN switching (e.g., for DMCA takedowns) without releasing a new launcher version.

Architecture

Launcher --> GET auth.sanasol.ws/patches/manifest.json
          --> 302 --> mega.io/.../manifest.json

Launcher --> GET auth.sanasol.ws/patches/windows/amd64/release/0_to_11.pwr
          --> 302 --> mega.io/.../windows/amd64/release/0_to_11.pwr

The auth server acts as a pure redirect gateway (302). No bandwidth is consumed on the auth server - all actual file transfers happen directly between the launcher and the CDN.

URLs

URL Purpose
https://auth.sanasol.ws/patches/* Redirect gateway (302 -> CDN)
https://auth.sanasol.ws/patches/manifest.json Patch manifest (redirects to CDN)
https://auth.sanasol.ws/admin/page/settings Admin panel to change CDN URL
https://auth.sanasol.ws/admin/api/settings/patches-cdn API to GET/POST CDN base URL

Default CDN (MEGA S4)

Base URL: https://s3.g.s4.mega.io/kcvismkrtfcalgwxzsazbq46l72dwsypqaham/hytale/patches

Changing CDN (DMCA response)

  1. Go to https://auth.sanasol.ws/admin/page/settings
  2. Find "Patches CDN Base URL" section
  3. Change URL to new CDN (e.g., https://new-cdn.example.com/patches)
  4. Click "Save" - all launcher requests instantly redirect to new CDN
  5. No launcher update needed

Manifest Format

The manifest is a JSON file listing all available patch files:

{
  "updated": "2026-02-20T13:20:09.776Z",
  "files": {
    "windows/amd64/release/0_to_11.pwr": { "size": 1618804736 },
    "windows/amd64/release/10_to_11.pwr": { "size": 62914560 },
    "darwin/arm64/release/0_to_11.pwr": { "size": 1617100800 },
    "server/release": { "version": "2026.02.19-1a311a592", "size": 1509949440, "sha256": "..." },
    ...
  }
}

Key Structure

File keys follow the pattern: {os}/{arch}/{branch}/{from}_to_{to}.pwr

  • OS: windows, linux, darwin
  • Arch: amd64, arm64
  • Branch: release, pre-release
  • Patch: {from}_to_{to}.pwr (e.g., 0_to_11.pwr for full install, 10_to_11.pwr for differential)

Server builds use: server/{branch} with version, size, sha256 fields.

Game Update Process

1. Version Check

Launcher calls: getLatestClientVersion(branch)
  -> Fetches manifest from auth.sanasol.ws/patches/manifest.json
  -> Finds highest build number for current platform/branch
  -> Returns "v{buildNumber}" (e.g., "v11")

2. Update Plan (Optimal Patch Routing)

Launcher calls: getUpdatePlan(currentBuild, targetBuild, branch)
  -> Fetches manifest
  -> Finds available patches for platform
  -> Uses BFS to find optimal path (minimizes total download size)
  -> Example: build 5 -> 11 might use: 5->10 (148MB) + 10->11 (60MB)
    instead of: 0->11 (1.5GB)

3. Download & Apply

For each step in the update plan:
  1. Download .pwr file from auth.sanasol.ws/patches/{key}
     (redirects to CDN, supports resume via Range headers)
  2. Apply patch using butler tool:
     butler apply --staging-dir <staging> <pwr_file> <game_dir>
  3. Save version after each step

4. Fresh Install

For first-time installs (currentBuild = 0):

  • Downloads 0_to_{target}.pwr (full install, ~1.5GB)
  • Applies with butler to create the full game directory

5. Differential Update

For existing installations:

  • Finds optimal patch chain (e.g., 10_to_11.pwr at ~60MB)
  • Applies incrementally, saving progress after each step
  • Falls back to full install if no patch path found

Mirror Sync Script

The mirror script (scripts/hytale-mirror.js) downloads patches from the official Hytale API and uploads to MEGA S4.

Usage

cd scripts
node hytale-mirror.js download   # Download patches locally
node hytale-mirror.js upload     # Upload to MEGA S4 via rclone
node hytale-mirror.js sync       # Download + Upload in one step

What It Does

  1. Discovery: Calls Hytale API to find available patches for all platforms
  2. Download: Downloads .pwr files to scripts/mirror/ directory
  3. Manifest Generation: Creates manifest.json with file sizes (no local paths)
  4. Upload: Uses rclone to sync to MEGA S4

SOCKS5 Proxy

  • API discovery calls use SOCKS5 proxy rotation (for rate limiting)
  • File downloads do NOT use proxy (too slow for large files)
  • Proxy list in proxies.json (auto-refreshed from proxy service)

Prerequisites

  • rclone configured with megas4 remote pointing to MEGA S4
  • Node.js 20+
  • Network access to Hytale API endpoints

Launcher Configuration

Environment Variables

Variable Default Description
HYTALE_AUTH_DOMAIN auth.sanasol.ws Auth domain (used for patch redirects)

Key Files

File Description
backend/services/versionManager.js Manifest fetching, version checking, update planning
backend/managers/differentialUpdateManager.js Download orchestration, butler integration
backend/utils/fileManager.js File download with retry, resume, stall detection
backend/managers/gameLauncher.js Game launch with token fetch, patching, signing

Constants (versionManager.js)

const AUTH_DOMAIN = process.env.HYTALE_AUTH_DOMAIN || 'auth.sanasol.ws';
const MIRROR_BASE_URL = `https://${AUTH_DOMAIN}/patches`;
const MIRROR_MANIFEST_URL = `${MIRROR_BASE_URL}/manifest.json`;
const MANIFEST_CACHE_DURATION = 60000; // 1 minute cache
const FALLBACK_LATEST_BUILD = 11; // If manifest unreachable

Auth Server Implementation

Routes

GET /patches/*  -> handlePatchRedirect()
  - Extracts path after /patches/
  - Reads CDN base URL from Redis settings
  - Returns 302 redirect to {baseUrl}/{path}
  - Tracks download metrics

GET /admin/api/settings/patches-cdn -> getPatchesCdnBaseUrl()
POST /admin/api/settings/patches-cdn -> setPatchesCdnBaseUrl()

Redis Storage

settings:global -> { patchesCdnBaseUrl: "https://s3.g.s4.mega.io/..." }
metrics:downloads -> { "patch:manifest.json": count, ... }

Troubleshooting

"Invalid manifest structure" error

  • Check manifest.json is valid JSON with files object
  • Verify CDN is accessible: curl -sL https://auth.sanasol.ws/patches/manifest.json | python3 -m json.tool
  • Check admin settings for correct CDN URL

0-byte downloads

  • Verify redirect works: curl -sI https://auth.sanasol.ws/patches/darwin/arm64/release/0_to_11.pwr
  • Should show 302 with Location header
  • Test actual download: curl -sL -o /dev/null -w "%{size_download}" -r 0-1023 <url>

Manifest has local paths

  • Regenerate manifest: node scripts/hytale-mirror.js download (re-scans files)
  • Re-upload: node scripts/hytale-mirror.js upload
  • Verify: entries should only have { size: <bytes> }, no path field

CDN switch not taking effect

  • Check Redis: CDN URL stored in settings:global
  • Verify via API: curl https://auth.sanasol.ws/admin/api/settings/patches-cdn
  • Manifest is cached for 1 minute in launcher - wait or restart