diff --git a/backend/core/testConfig.js b/backend/core/testConfig.js index e6e9687..0229dda 100644 --- a/backend/core/testConfig.js +++ b/backend/core/testConfig.js @@ -1,5 +1,5 @@ const FORCE_CLEAN_INSTALL_VERSION = false; -const CLEAN_INSTALL_TEST_VERSION = '4.pwr'; +const CLEAN_INSTALL_TEST_VERSION = 'v4'; module.exports = { FORCE_CLEAN_INSTALL_VERSION, diff --git a/backend/managers/gameManager.js b/backend/managers/gameManager.js index 98753d3..c60e912 100644 --- a/backend/managers/gameManager.js +++ b/backend/managers/gameManager.js @@ -64,7 +64,7 @@ async function safeRemoveDirectory(dirPath, maxRetries = 3) { } } -async function downloadPWR(branch = 'release', fileName = '7.pwr', progressCallback, cacheDir = CACHE_DIR, manualRetry = false) { +async function downloadPWR(branch = 'release', fileName = 'v8', progressCallback, cacheDir = CACHE_DIR, manualRetry = false) { const osName = getOS(); const arch = getArch(); @@ -72,8 +72,23 @@ async function downloadPWR(branch = 'release', fileName = '7.pwr', progressCallb throw new Error('Hytale x86_64 Intel Mac Support has not been released yet. Please check back later.'); } - const url = `https://game-patches.hytale.com/patches/${osName}/${arch}/${branch}/0/${fileName}`; - const dest = path.join(cacheDir, `${branch}_${fileName}`); + const { getPWRUrlFromNewAPI } = require('../services/versionManager'); + + let url; + let isUsingNewAPI = false; + + try { + console.log(`[DownloadPWR] Fetching URL from new API for branch: ${branch}, version: ${fileName}`); + url = await getPWRUrlFromNewAPI(branch, fileName); + isUsingNewAPI = true; + console.log(`[DownloadPWR] Using new API URL: ${url}`); + } catch (error) { + console.error(`[DownloadPWR] Failed to get URL from new API: ${error.message}`); + console.log(`[DownloadPWR] Falling back to old URL format`); + url = `https://game-patches.hytale.com/patches/${osName}/${arch}/${branch}/0/${fileName}.pwr`; + } + + const dest = path.join(cacheDir, `${branch}_${fileName}.pwr`); // Check if file exists and validate it if (fs.existsSync(dest) && !manualRetry) { @@ -93,7 +108,7 @@ async function downloadPWR(branch = 'release', fileName = '7.pwr', progressCallb } } - console.log('Fetching PWR patch file:', url); + console.log(`Fetching PWR patch file from ${isUsingNewAPI ? 'NEW API' : 'old API'}:`, url); try { if (manualRetry) { diff --git a/backend/services/versionManager.js b/backend/services/versionManager.js index f739122..1b42b9b 100644 --- a/backend/services/versionManager.js +++ b/backend/services/versionManager.js @@ -6,32 +6,186 @@ const { smartRequest } = require('../utils/proxyClient'); const BASE_PATCH_URL = 'https://game-patches.hytale.com/patches'; const MANIFEST_API = 'https://files.hytalef2p.com/api/patch_manifest'; +const NEW_API_URL = 'https://thecute.cloud/ShipOfYarn/api.php'; -async function getLatestClientVersion(branch = 'release') { +let apiCache = null; +let apiCacheTime = 0; +const API_CACHE_DURATION = 60000; // 1 minute + +async function fetchNewAPI() { + const now = Date.now(); + + if (apiCache && (now - apiCacheTime) < API_CACHE_DURATION) { + console.log('[NewAPI] Using cached API data'); + return apiCache; + } + try { - console.log(`Fetching latest client version from API (branch: ${branch})...`); - const response = await smartRequest(`https://files.hytalef2p.com/api/version_client?branch=${branch}`, { - timeout: 40000, + console.log('[NewAPI] Fetching from:', NEW_API_URL); + const response = await axios.get(NEW_API_URL, { + timeout: 15000, headers: { 'User-Agent': 'Hytale-F2P-Launcher' } }); - - if (response.data && response.data.client_version) { - const version = response.data.client_version; - console.log(`Latest client version for ${branch}: ${version}`); - return version; + + if (response.data && response.data.hytale) { + apiCache = response.data; + apiCacheTime = now; + console.log('[NewAPI] API data fetched and cached successfully'); + return response.data; } else { - console.log('Warning: Invalid API response, falling back to latest known version (7.pwr)'); - return '7.pwr'; + throw new Error('Invalid API response structure'); } } catch (error) { - console.error('Error fetching client version:', error.message); - console.log('Warning: API unavailable, falling back to latest known version (7.pwr)'); - return '7.pwr'; + console.error('[NewAPI] Error fetching API:', error.message); + if (apiCache) { + console.log('[NewAPI] Using expired cache due to error'); + return apiCache; + } + throw error; } } +async function getLatestVersionFromNewAPI(branch = 'release') { + try { + const apiData = await fetchNewAPI(); + const osName = getOS(); + const arch = getArch(); + + let osKey = osName; + if (osName === 'darwin') { + osKey = 'mac'; + } + + const branchData = apiData.hytale[branch]; + if (!branchData || !branchData[osKey]) { + throw new Error(`No data found for branch: ${branch}, OS: ${osKey}`); + } + + 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; + }); + + const latestVersionNumber = Math.max(...versionNumbers); + console.log(`[NewAPI] Latest version number: ${latestVersionNumber} for branch ${branch}`); + + return `v${latestVersionNumber}`; + } catch (error) { + console.error('[NewAPI] Error getting latest version:', error.message); + throw error; + } +} + +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; + if (osName === 'windows') { + fileName = `${version}-windows-amd64.pwr`; + } else if (osName === 'linux') { + fileName = `${version}-linux-amd64.pwr`; + } else if (osName === 'darwin') { + fileName = `${version}-darwin-arm64.pwr`; + } + + const branchData = apiData.hytale[branch]; + if (!branchData || !branchData[osKey]) { + throw new Error(`No data found for branch: ${branch}, OS: ${osKey}`); + } + + 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') { + try { + console.log(`[NewAPI] Fetching latest client version from new API (branch: ${branch})...`); + + // Utiliser la nouvelle API + const latestVersion = await getLatestVersionFromNewAPI(branch); + console.log(`[NewAPI] Latest client version for ${branch}: ${latestVersion}`); + return latestVersion; + + } catch (error) { + console.error('[NewAPI] Error fetching client version from new API:', error.message); + console.log('[NewAPI] Falling back to old API...'); + + // Fallback vers l'ancienne API si la nouvelle échoue + try { + const response = await smartRequest(`https://files.hytalef2p.com/api/version_client?branch=${branch}`, { + timeout: 40000, + headers: { + 'User-Agent': 'Hytale-F2P-Launcher' + } + }); + + if (response.data && response.data.client_version) { + const version = response.data.client_version; + console.log(`Latest client version for ${branch} (old API): ${version}`); + return version; + } else { + 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'; + } + } +} + +// Fonction utilitaire pour extraire le numéro de version +// Supporte les formats: "7.pwr", "v8", "v8-windows-amd64.pwr", etc. +function extractVersionNumber(version) { + if (!version) return 0; + + // Nouveau format: "v8" ou "v8-xxx.pwr" + const vMatch = version.match(/v(\d+)/); + if (vMatch) { + return parseInt(vMatch[1]); + } + + // Ancien format: "7.pwr" + const pwrMatch = version.match(/(\d+)\.pwr/); + if (pwrMatch) { + return parseInt(pwrMatch[1]); + } + + // Fallback: essayer de parser directement + const num = parseInt(version); + return isNaN(num) ? 0 : num; +} + function buildArchiveUrl(buildNumber, branch = 'release') { const os = getOS(); const arch = getArch(); @@ -50,7 +204,7 @@ async function checkArchiveExists(buildNumber, branch = 'release') { async function discoverAvailableVersions(latestKnown, branch = 'release', maxProbe = 50) { const available = []; - const latest = parseInt(latestKnown.replace('.pwr', '')); + const latest = extractVersionNumber(latestKnown); for (let i = latest; i >= Math.max(1, latest - maxProbe); i--) { const exists = await checkArchiveExists(i, branch); @@ -77,7 +231,7 @@ async function fetchPatchManifest(branch = 'release') { } async function extractVersionDetails(targetVersion, branch = 'release') { - const buildNumber = parseInt(targetVersion.replace('.pwr', '')); + const buildNumber = extractVersionNumber(targetVersion); const previousBuild = buildNumber - 1; const manifest = await fetchPatchManifest(branch); @@ -103,8 +257,8 @@ function canUseDifferentialUpdate(currentVersion, targetDetails) { if (!currentVersion) return false; - const currentBuild = parseInt(currentVersion.replace('.pwr', '')); - const expectedSource = parseInt(targetDetails.sourceVersion?.replace('.pwr', '') || '0'); + const currentBuild = extractVersionNumber(currentVersion); + const expectedSource = extractVersionNumber(targetDetails.sourceVersion); return currentBuild === expectedSource; } @@ -112,8 +266,8 @@ function canUseDifferentialUpdate(currentVersion, targetDetails) { function needsIntermediatePatches(currentVersion, targetVersion) { if (!currentVersion) return []; - const current = parseInt(currentVersion.replace('.pwr', '')); - const target = parseInt(targetVersion.replace('.pwr', '')); + const current = extractVersionNumber(currentVersion); + const target = extractVersionNumber(targetVersion); const intermediates = []; for (let i = current + 1; i <= target; i++) { @@ -160,5 +314,9 @@ module.exports = { needsIntermediatePatches, computeFileChecksum, validateChecksum, - getInstalledClientVersion + getInstalledClientVersion, + fetchNewAPI, + getLatestVersionFromNewAPI, + getPWRUrlFromNewAPI, + extractVersionNumber }; diff --git a/main.js b/main.js index 39975e4..265052f 100644 --- a/main.js +++ b/main.js @@ -627,7 +627,7 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath, console.log('[Main] Processing Butler error with retry context'); errorData.retryData = { branch: error.branch || 'release', - fileName: error.fileName || '7.pwr', + fileName: error.fileName || 'v8', cacheDir: error.cacheDir }; errorData.canRetry = error.canRetry !== false; @@ -647,7 +647,7 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath, console.log('[Main] Processing generic error, creating default retry data'); errorData.retryData = { branch: 'release', - fileName: '7.pwr' + fileName: 'v8' }; // For generic errors, assume it's retryable unless specified errorData.canRetry = error.canRetry !== false; @@ -887,7 +887,7 @@ ipcMain.handle('retry-download', async (event, retryData) => { console.log('[IPC] Invalid retry data, using PWR defaults'); retryData = { branch: 'release', - fileName: '7.pwr' + fileName: 'v8' }; } @@ -921,7 +921,7 @@ ipcMain.handle('retry-download', async (event, retryData) => { } : { branch: retryData?.branch || 'release', - fileName: retryData?.fileName || '7.pwr', + fileName: retryData?.fileName || 'v8', cacheDir: retryData?.cacheDir };