diff --git a/backend/managers/gameManager.js b/backend/managers/gameManager.js index 1790353..465da2f 100644 --- a/backend/managers/gameManager.js +++ b/backend/managers/gameManager.js @@ -85,8 +85,9 @@ async function downloadPWR(branch = 'release', fileName = 'v8', progressCallback console.log(`[DownloadPWR] Mirror URL: ${url}`); } catch (error) { console.error(`[DownloadPWR] Failed to get mirror URL: ${error.message}`); - const { MIRROR_BASE_URL } = require('../services/versionManager'); - url = `${MIRROR_BASE_URL}/${osName}/${arch}/${branch}/0_to_${extractVersionNumber(fileName)}.pwr`; + const { getPatchesBaseUrl } = require('../services/versionManager'); + const baseUrl = await getPatchesBaseUrl(); + url = `${baseUrl}/${osName}/${arch}/${branch}/0_to_${extractVersionNumber(fileName)}.pwr`; console.log(`[DownloadPWR] Fallback URL: ${url}`); } } diff --git a/backend/services/versionManager.js b/backend/services/versionManager.js index e846add..15f6f3a 100644 --- a/backend/services/versionManager.js +++ b/backend/services/versionManager.js @@ -3,20 +3,56 @@ const crypto = require('crypto'); const fs = require('fs'); const { getOS, getArch } = require('../utils/platformUtils'); -// Patches CDN via auth server redirect gateway (allows instant CDN switching) +// Patches base URL fetched dynamically from auth server 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 PATCHES_CONFIG_URL = `https://${AUTH_DOMAIN}/api/patches-config`; // Fallback: latest known build number if manifest is unreachable 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 /** - * Fetch the mirror manifest from MEGA S4 + * Fetch patches base URL from auth server config endpoint + */ +async function getPatchesBaseUrl() { + const now = Date.now(); + if (patchesBaseUrl && (now - patchesConfigTime) < PATCHES_CONFIG_CACHE_DURATION) { + return patchesBaseUrl; + } + + try { + console.log('[Mirror] Fetching patches config from:', PATCHES_CONFIG_URL); + const response = await axios.get(PATCHES_CONFIG_URL, { + timeout: 10000, + headers: { 'User-Agent': 'Hytale-F2P-Launcher' } + }); + + if (response.data && response.data.patches_url) { + patchesBaseUrl = response.data.patches_url.replace(/\/+$/, ''); + patchesConfigTime = now; + console.log('[Mirror] Patches base URL:', patchesBaseUrl); + return patchesBaseUrl; + } + throw new Error('Invalid patches config'); + } catch (error) { + console.error('[Mirror] Error fetching patches config:', error.message); + if (patchesBaseUrl) { + console.log('[Mirror] Using cached patches URL:', patchesBaseUrl); + return patchesBaseUrl; + } + throw error; + } +} + +/** + * Fetch the mirror manifest */ async function fetchMirrorManifest() { const now = Date.now(); @@ -26,9 +62,12 @@ async function fetchMirrorManifest() { return manifestCache; } + const baseUrl = await getPatchesBaseUrl(); + const manifestUrl = `${baseUrl}/manifest.json`; + try { - console.log('[Mirror] Fetching manifest from:', MIRROR_MANIFEST_URL); - const response = await axios.get(MIRROR_MANIFEST_URL, { + console.log('[Mirror] Fetching manifest from:', manifestUrl); + const response = await axios.get(manifestUrl, { timeout: 15000, headers: { 'User-Agent': 'Hytale-F2P-Launcher' } }); @@ -82,9 +121,10 @@ function getPlatformPatches(manifest, branch = 'release') { * Find optimal patch path using BFS with download size minimization * Returns array of { from, to, url, size, key } steps, or null if no path found */ -function findOptimalPatchPath(currentBuild, targetBuild, patches) { +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] = []; @@ -118,7 +158,7 @@ function findOptimalPatchPath(currentBuild, targetBuild, patches) { path: [...path, { from: edge.from, to: edge.to, - url: `${MIRROR_BASE_URL}/${edge.key}`, + url: `${baseUrl}/${edge.key}`, size: edge.size, key: edge.key }], @@ -139,7 +179,7 @@ async function getUpdatePlan(currentBuild, targetBuild, branch = 'release') { const patches = getPlatformPatches(manifest, branch); // Try optimal path - const steps = findOptimalPatchPath(currentBuild, targetBuild, patches); + const steps = await findOptimalPatchPath(currentBuild, targetBuild, patches); if (steps && steps.length > 0) { const totalSize = steps.reduce((sum, s) => sum + s.size, 0); @@ -150,10 +190,11 @@ async function getUpdatePlan(currentBuild, targetBuild, branch = 'release') { // Fallback: full install 0 -> target const fullPatch = patches.find(p => p.from === 0 && p.to === targetBuild); if (fullPatch) { + const baseUrl = await getPatchesBaseUrl(); const step = { from: 0, to: targetBuild, - url: `${MIRROR_BASE_URL}/${fullPatch.key}`, + url: `${baseUrl}/${fullPatch.key}`, size: fullPatch.size, key: fullPatch.key }; @@ -200,7 +241,8 @@ async function getPWRUrl(branch = 'release', version = 'v11') { const fullPatch = patches.find(p => p.from === 0 && p.to === targetBuild); if (fullPatch) { - const url = `${MIRROR_BASE_URL}/${fullPatch.key}`; + const baseUrl = await getPatchesBaseUrl(); + const url = `${baseUrl}/${fullPatch.key}`; console.log(`[Mirror] PWR URL: ${url}`); return url; } @@ -216,7 +258,8 @@ async function getPWRUrl(branch = 'release', version = 'v11') { } // Construct mirror URL (will work if patch was uploaded but manifest is stale) - return `${MIRROR_BASE_URL}/${os}/${arch}/${branch}/0_to_${targetBuild}.pwr`; + const baseUrl = await getPatchesBaseUrl(); + return `${baseUrl}/${os}/${arch}/${branch}/0_to_${targetBuild}.pwr`; } // Backward-compatible alias @@ -240,14 +283,15 @@ function extractVersionNumber(version) { return isNaN(num) ? 0 : num; } -function buildArchiveUrl(buildNumber, branch = 'release') { +async function buildArchiveUrl(buildNumber, branch = 'release') { + const baseUrl = await getPatchesBaseUrl(); const os = getOS(); const arch = getArch(); - return `${MIRROR_BASE_URL}/${os}/${arch}/${branch}/0_to_${buildNumber}.pwr`; + return `${baseUrl}/${os}/${arch}/${branch}/0_to_${buildNumber}.pwr`; } async function checkArchiveExists(buildNumber, branch = 'release') { - const url = buildArchiveUrl(buildNumber, branch); + const url = await buildArchiveUrl(buildNumber, branch); try { const response = await axios.head(url, { timeout: 10000 }); return response.status === 200; @@ -269,7 +313,7 @@ async function discoverAvailableVersions(latestKnown, branch = 'release') { async function extractVersionDetails(targetVersion, branch = 'release') { const buildNumber = extractVersionNumber(targetVersion); - const fullUrl = buildArchiveUrl(buildNumber, branch); + const fullUrl = await buildArchiveUrl(buildNumber, branch); return { version: targetVersion, @@ -340,5 +384,5 @@ module.exports = { extractVersionNumber, getPlatformPatches, findOptimalPatchPath, - MIRROR_BASE_URL + getPatchesBaseUrl }; diff --git a/package.json b/package.json index 6246448..8df035e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hytale-f2p-launcher", - "version": "2.3.3", + "version": "2.3.4", "description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support", "homepage": "https://github.com/amiayweb/Hytale-F2P", "main": "main.js",