# 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: ```json { "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 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 ```bash 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) ```javascript 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 ` ### 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: }`, 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