This commit is contained in:
AMIAY
2026-01-25 13:31:08 +01:00
parent f07e4a2004
commit b11b78f7dc
13 changed files with 465 additions and 490 deletions

View File

@@ -635,20 +635,6 @@
</div> </div>
</div> </div>
<!-- Installation effects overlay -->
<div id="installationEffects" class="installation-effects" style="display: none;">
<div class="space-effects">
<div class="warp-line"></div>
<div class="warp-line"></div>
<div class="warp-line"></div>
<div class="warp-line"></div>
<div class="warp-line"></div>
<div class="warp-line"></div>
<div class="warp-line"></div>
<div class="warp-line"></div>
</div>
</div>
<div id="chatUsernameModal" class="chat-username-modal" style="display: none;"> <div id="chatUsernameModal" class="chat-username-modal" style="display: none;">
<div class="chat-username-modal-content"> <div class="chat-username-modal-content">
<div class="chat-username-modal-header"> <div class="chat-username-modal-header">

View File

@@ -1,5 +1,5 @@
let CF_API_KEY = "$2a$10$bqk254NMZOWVTzLVJCcxEOmhcyUujKxA5xk.kQCN9q0KNYFJd5b32"; let API_KEY = "$2a$10$bqk254NMZOWVTzLVJCcxEOmhcyUujKxA5xk.kQCN9q0KNYFJd5b32";
const CURSEFORGE_API = 'https://api.curseforge.com/v1'; const CURSEFORGE_API = 'https://api.curseforge.com/v1';
const HYTALE_GAME_ID = 70216; const HYTALE_GAME_ID = 70216;
@@ -12,7 +12,9 @@ let modsTotalPages = 1;
export async function initModsManager() { export async function initModsManager() {
try { try {
console.log('Loaded API Key:', CF_API_KEY ? 'Yes' : 'No'); if (window.electronAPI && window.electronAPI.getEnvVar) {
console.log('Loaded API Key:', API_KEY ? 'Yes' : 'No');
}
} catch (err) { } catch (err) {
console.error('Failed to load API Key:', err); console.error('Failed to load API Key:', err);
} }
@@ -194,7 +196,7 @@ async function loadBrowseMods() {
browseContainer.innerHTML = `<div class="loading-mods"><div class="loading-spinner"></div><span>${window.i18n ? window.i18n.t('mods.loadingMods') : 'Loading mods from CurseForge...'}</span></div>`; browseContainer.innerHTML = `<div class="loading-mods"><div class="loading-spinner"></div><span>${window.i18n ? window.i18n.t('mods.loadingMods') : 'Loading mods from CurseForge...'}</span></div>`;
try { try {
if (!CF_API_KEY || CF_API_KEY.length < 10) { if (!API_KEY || API_KEY.length < 10) {
browseContainer.innerHTML = ` browseContainer.innerHTML = `
<div class=\"empty-browse-mods\"> <div class=\"empty-browse-mods\">
<i class=\"fas fa-key\"></i> <i class=\"fas fa-key\"></i>
@@ -221,7 +223,7 @@ async function loadBrowseMods() {
const response = await fetch(url, { const response = await fetch(url, {
headers: { headers: {
'x-api-key': CF_API_KEY, 'x-api-key': API_KEY,
'Accept': 'application/json' 'Accept': 'application/json'
} }
}); });

View File

@@ -207,11 +207,6 @@ function setupSettingsElements() {
} }
if (closeLauncherCheck) {
closeLauncherCheck.addEventListener('change', saveCloseLauncher);
}
// UUID event listeners // UUID event listeners
if (copyUuidBtn) { if (copyUuidBtn) {
copyUuidBtn.addEventListener('click', copyCurrentUuid); copyUuidBtn.addEventListener('click', copyCurrentUuid);
@@ -397,31 +392,6 @@ async function loadCloseLauncher() {
} }
async function saveCloseLauncher() {
try {
if (window.electronAPI && window.electronAPI.saveCloseLauncher && closeLauncherCheck) {
const enabled = closeLauncherCheck.checked;
await window.electronAPI.saveCloseLauncher(enabled);
}
} catch (error) {
console.error('Error saving close launcher setting:', error);
}
}
async function loadCloseLauncher() {
try {
if (window.electronAPI && window.electronAPI.loadCloseLauncher) {
const enabled = await window.electronAPI.loadCloseLauncher();
if (closeLauncherCheck) {
closeLauncherCheck.checked = enabled;
}
}
} catch (error) {
console.error('Error loading close launcher setting:', error);
}
}
async function savePlayerName() { async function savePlayerName() {
try { try {
if (!window.electronAPI || !settingsPlayerName) return; if (!window.electronAPI || !settingsPlayerName) return;

View File

@@ -638,9 +638,6 @@ function setupUI() {
// Setup retry button // Setup retry button
setupRetryButton(); setupRetryButton();
// Setup draggable progress bar
setupProgressDrag();
lockPlayButton(true); lockPlayButton(true);
setTimeout(() => { setTimeout(() => {
@@ -741,6 +738,7 @@ window.LauncherUI = {
// Make installation effects globally available // Make installation effects globally available
// Draggable progress bar functionality // Draggable progress bar functionality
function setupProgressDrag() { function setupProgressDrag() {
if (!progressOverlay) return; if (!progressOverlay) return;

View File

@@ -15,12 +15,6 @@ class AppUpdater {
} }
setupAutoUpdater() { setupAutoUpdater() {
// Enable dev mode for testing (reads dev-app-update.yml)
// Only enable in development, not in production builds
if (process.env.NODE_ENV === 'development' || !app.isPackaged) {
autoUpdater.forceDevUpdateConfig = true;
console.log('Dev update mode enabled - using dev-app-update.yml');
}
// Configure logger for electron-updater // Configure logger for electron-updater
// Create a compatible logger interface // Create a compatible logger interface

View File

@@ -406,5 +406,6 @@ module.exports = {
getJavaDetection, getJavaDetection,
downloadJRE, downloadJRE,
extractJRE, extractJRE,
retryJREDownload,
JAVA_EXECUTABLE JAVA_EXECUTABLE
}; };

View File

@@ -6,7 +6,7 @@ const { getModsPath, getProfilesDir } = require('../core/paths');
const { saveModsToConfig, loadModsFromConfig } = require('../core/config'); const { saveModsToConfig, loadModsFromConfig } = require('../core/config');
const profileManager = require('./profileManager'); const profileManager = require('./profileManager');
const CF_API_KEY = "$2a$10$bqk254NMZOWVTzLVJCcxEOmhcyUujKxA5xk.kQCN9q0KNYFJd5b32"; const API_KEY = "$2a$10$bqk254NMZOWVTzLVJCcxEOmhcyUujKxA5xk.kQCN9q0KNYFJd5b32";
/** /**
* Get the physical mods path for a specific profile. * Get the physical mods path for a specific profile.
@@ -120,7 +120,7 @@ async function downloadMod(modInfo) {
if (!downloadUrl && modInfo.fileId && modInfo.modId) { if (!downloadUrl && modInfo.fileId && modInfo.modId) {
const response = await axios.get(`https://api.curseforge.com/v1/mods/${modInfo.modId || modInfo.curseForgeId}/files/${modInfo.fileId || modInfo.curseForgeFileId}`, { const response = await axios.get(`https://api.curseforge.com/v1/mods/${modInfo.modId || modInfo.curseForgeId}/files/${modInfo.fileId || modInfo.curseForgeFileId}`, {
headers: { headers: {
'x-api-key': modInfo.apiKey || CF_API_KEY, 'x-api-key': modInfo.apiKey || API_KEY,
'Accept': 'application/json' 'Accept': 'application/json'
} }
}); });
@@ -393,7 +393,7 @@ async function syncModsForCurrentProfile() {
...mod, ...mod,
modId: mod.curseForgeId, modId: mod.curseForgeId,
fileId: mod.curseForgeFileId || mod.fileId, fileId: mod.curseForgeFileId || mod.fileId,
apiKey: CF_API_KEY apiKey: API_KEY
}); });
} catch (err) { } catch (err) {
console.error(`[ModManager] Auto-repair failed for "${mod.name}": ${err.message}`); console.error(`[ModManager] Auto-repair failed for "${mod.name}": ${err.message}`);

View File

@@ -103,40 +103,36 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) {
} }
const headers = { const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
'Accept': '*/*', 'Accept': '*/*'
'Accept-Language': 'en-US,en;q=0.9',
'Referer': 'https://launcher.hytale.com/',
'Connection': 'keep-alive'
}; };
// Add Range header for resume capability // Add Range header ONLY if resuming (startByte > 0)
if (startByte > 0) { if (startByte > 0) {
headers['Range'] = `bytes=${startByte}-`; headers['Range'] = `bytes=${startByte}-`;
console.log(`Adding Range header: bytes=${startByte}-`);
} else {
console.log('Fresh download, no Range header');
} }
const response = await axios({ const response = await axios({
method: 'GET', method: 'GET',
url: url, url: url,
responseType: 'stream', responseType: 'stream',
timeout: 60000, // 60 secondes timeout timeout: 60000,
signal: controller.signal, signal: controller.signal,
headers: headers, headers: headers,
// Configuration Axios pour la robustesse réseau
validateStatus: function (status) { validateStatus: function (status) {
// Accept both 200 (full download) and 206 (partial content for resume)
return (status >= 200 && status < 300) || status === 206; return (status >= 200 && status < 300) || status === 206;
}, },
// Retry configuration
maxRedirects: 5, maxRedirects: 5,
// Network resilience family: 4
family: 4 // Force IPv4
}); });
const contentLength = response.headers['content-length']; const contentLength = response.headers['content-length'];
const totalSize = contentLength ? parseInt(contentLength, 10) + startByte : 0; // Adjust for resume const totalSize = contentLength ? parseInt(contentLength, 10) + startByte : 0;
let downloaded = startByte; // Start with existing bytes let downloaded = startByte;
lastProgressTime = Date.now(); // Update time after response received lastProgressTime = Date.now();
const startTime = Date.now(); const startTime = Date.now();
// Check network status before attempting download // Check network status before attempting download
@@ -344,7 +340,6 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) {
}); });
}); });
// Si on arrive ici, le téléchargement a réussi
return; return;
} catch (error) { } catch (error) {
@@ -375,6 +370,9 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) {
// Enhanced file cleanup with validation // Enhanced file cleanup with validation
if (fs.existsSync(dest)) { if (fs.existsSync(dest)) {
try { try {
// HTTP 416 = Range Not Satisfiable, delete corrupted partial file
const isRangeError = error.message && error.message.includes('416');
// Check if file is corrupted (small or invalid) or if error is non-resumable // Check if file is corrupted (small or invalid) or if error is non-resumable
const partialStats = fs.statSync(dest); const partialStats = fs.statSync(dest);
const isResumableError = error.message && ( const isResumableError = error.message && (
@@ -387,13 +385,14 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) {
// Check if download appears to be complete (close to expected PWR size) // Check if download appears to be complete (close to expected PWR size)
const isPossiblyComplete = partialStats.size >= 1500 * 1024 * 1024; // >= 1.5GB const isPossiblyComplete = partialStats.size >= 1500 * 1024 * 1024; // >= 1.5GB
if (partialStats.size < 1024 * 1024 || (!isResumableError && !isPossiblyComplete)) { if (isRangeError || partialStats.size < 1024 * 1024 || (!isResumableError && !isPossiblyComplete)) {
// Delete if file is too small OR error is non-resumable AND not possibly complete // Delete if HTTP 416 OR file is too small OR error is non-resumable AND not possibly complete
console.log(`[Cleanup] Removing PWR file (${!isResumableError && !isPossiblyComplete ? 'non-resumable error' : 'too small'}): ${(partialStats.size / 1024 / 1024).toFixed(2)} MB`); const reason = isRangeError ? 'HTTP 416 range error' : (!isResumableError && !isPossiblyComplete ? 'non-resumable error' : 'too small');
console.log(`[Cleanup] Removing file (${reason}): ${(partialStats.size / 1024 / 1024).toFixed(2)} MB`);
fs.unlinkSync(dest); fs.unlinkSync(dest);
} else { } else {
// Keep the file for resume on resumable errors or if possibly complete // Keep the file for resume on resumable errors or if possibly complete
console.log(`[Resume] Keeping PWR file (${isPossiblyComplete ? 'possibly complete' : 'for resume'}): ${(partialStats.size / 1024 / 1024).toFixed(2)} MB`); console.log(`[Resume] Keeping file (${isPossiblyComplete ? 'possibly complete' : 'for resume'}): ${(partialStats.size / 1024 / 1024).toFixed(2)} MB`);
} }
} catch (cleanupError) { } catch (cleanupError) {
console.warn('Could not handle partial file:', cleanupError.message); console.warn('Could not handle partial file:', cleanupError.message);
@@ -554,6 +553,17 @@ async function retryDownload(url, dest, progressCallback, previousError = null)
fs.mkdirSync(destDir, { recursive: true }); fs.mkdirSync(destDir, { recursive: true });
} }
// CRITICAL: Delete partial file before manual retry to avoid HTTP 416
if (fs.existsSync(dest)) {
try {
const stats = fs.statSync(dest);
console.log(`[Retry] Deleting partial file before retry: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
fs.unlinkSync(dest);
} catch (err) {
console.warn('Could not delete partial file:', err.message);
}
}
try { try {
await downloadFile(url, dest, progressCallback, additionalRetries); await downloadFile(url, dest, progressCallback, additionalRetries);
console.log('Manual retry successful'); console.log('Manual retry successful');

BIN
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

15
main.js
View File

@@ -1,4 +1,5 @@
const path = require('path'); const path = require('path');
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');
@@ -446,6 +447,13 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, g
ipcMain.handle('install-game', async (event, playerName, javaPath, installPath, branch) => { ipcMain.handle('install-game', async (event, playerName, javaPath, installPath, branch) => {
try { try {
console.log(`[IPC] install-game called with parameters:`);
console.log(` - playerName: ${playerName}`);
console.log(` - javaPath: ${javaPath}`);
console.log(` - installPath: ${installPath}`);
console.log(` - branch: ${branch}`);
console.log(`[IPC] branch type: ${typeof branch}, value: ${JSON.stringify(branch)}`);
// Signal installation start // Signal installation start
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('installation-start'); mainWindow.webContents.send('installation-start');
@@ -625,6 +633,7 @@ ipcMain.handle('load-close-launcher', () => {
}); });
ipcMain.handle('select-install-path', async () => { ipcMain.handle('select-install-path', async () => {
const result = await dialog.showOpenDialog(mainWindow, { const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openDirectory'], properties: ['openDirectory'],
title: 'Select Installation Folder' title: 'Select Installation Folder'
@@ -742,8 +751,8 @@ ipcMain.handle('retry-download', async (event, retryData) => {
console.log('[IPC] Full JRE retry data:', JSON.stringify(retryData, null, 2)); console.log('[IPC] Full JRE retry data:', JSON.stringify(retryData, null, 2));
const { retryJREDownload } = require('./backend/managers/javaManager'); const { retryJREDownload } = require('./backend/managers/javaManager');
await retryJREDownload(retryData.jreUrl, jreCacheFile, progressCallback);
const jreCacheFile = path.join(retryData.cacheDir, retryData.fileName); const jreCacheFile = path.join(retryData.cacheDir, retryData.fileName);
await retryJREDownload(retryData.jreUrl, jreCacheFile, progressCallback);
return { success: true }; return { success: true };
} }
@@ -930,6 +939,10 @@ ipcMain.handle('get-local-app-data', async () => {
return process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'); return process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
}); });
ipcMain.handle('get-env-var', async (event, key) => {
return process.env[key];
});
ipcMain.handle('get-user-id', async () => { ipcMain.handle('get-user-id', async () => {
try { try {
const { getOrCreatePlayerId } = require('./backend/launcher'); const { getOrCreatePlayerId } = require('./backend/launcher');

View File

@@ -35,6 +35,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
openGameLocation: () => ipcRenderer.invoke('open-game-location'), openGameLocation: () => ipcRenderer.invoke('open-game-location'),
saveSettings: (settings) => ipcRenderer.invoke('save-settings', settings), saveSettings: (settings) => ipcRenderer.invoke('save-settings', settings),
loadSettings: () => ipcRenderer.invoke('load-settings'), loadSettings: () => ipcRenderer.invoke('load-settings'),
getEnvVar: (key) => ipcRenderer.invoke('get-env-var', key),
getLocalAppData: () => ipcRenderer.invoke('get-local-app-data'), getLocalAppData: () => ipcRenderer.invoke('get-local-app-data'),
getModsPath: () => ipcRenderer.invoke('get-mods-path'), getModsPath: () => ipcRenderer.invoke('get-mods-path'),
loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath), loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath),