Merge branch 'develop' of https://github.com/amiayweb/Hytale-F2P into develop

This commit is contained in:
Fazri Gading
2026-01-25 22:38:10 +08:00
15 changed files with 475 additions and 495 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

@@ -1770,252 +1770,252 @@ body {
animation: shimmer 2s infinite; animation: shimmer 2s infinite;
} }
@keyframes shimmer { @keyframes shimmer {
0% { 0% {
left: -100%; left: -100%;
} }
100% { 100% {
left: 100%; left: 100%;
} }
} }
/* Progress Error and Retry Styles */ /* Progress Error and Retry Styles */
.progress-error-container { .progress-error-container {
margin-top: 0.75rem; margin-top: 0.75rem;
padding-top: 0.75rem; padding-top: 0.75rem;
border-top: 1px solid rgba(239, 68, 68, 0.3); border-top: 1px solid rgba(239, 68, 68, 0.3);
animation: errorSlideIn 0.3s ease-out; animation: errorSlideIn 0.3s ease-out;
} }
@keyframes errorSlideIn { @keyframes errorSlideIn {
from { from {
opacity: 0; opacity: 0;
transform: translateY(-10px); transform: translateY(-10px);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
} }
} }
.progress-error-message { .progress-error-message {
color: #f87171; color: #f87171;
font-family: 'JetBrains Mono', monospace; font-family: 'JetBrains Mono', monospace;
font-size: 0.75rem; font-size: 0.75rem;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
text-shadow: 0 0 8px rgba(248, 113, 113, 0.4); text-shadow: 0 0 8px rgba(248, 113, 113, 0.4);
line-height: 1.4; line-height: 1.4;
} }
.progress-retry-section { .progress-retry-section {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.75rem;
} }
.progress-retry-buttons { .progress-retry-buttons {
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem;
flex-wrap: wrap; flex-wrap: wrap;
} }
.progress-retry-info { .progress-retry-info {
color: #fbbf24; color: #fbbf24;
font-family: 'JetBrains Mono', monospace; font-family: 'JetBrains Mono', monospace;
font-size: 0.7rem; font-size: 0.7rem;
flex: 1; flex: 1;
} }
.progress-retry-btn { .progress-retry-btn {
background: linear-gradient(135deg, #dc2626, #ef4444); background: linear-gradient(135deg, #dc2626, #ef4444);
color: white; color: white;
border: none; border: none;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
border-radius: 6px; border-radius: 6px;
font-family: 'JetBrains Mono', monospace; font-family: 'JetBrains Mono', monospace;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
box-shadow: 0 2px 8px rgba(220, 38, 38, 0.3); box-shadow: 0 2px 8px rgba(220, 38, 38, 0.3);
white-space: nowrap; white-space: nowrap;
min-width: 120px; min-width: 120px;
} }
.progress-retry-btn:hover { .progress-retry-btn:hover {
background: linear-gradient(135deg, #b91c1c, #dc2626); background: linear-gradient(135deg, #b91c1c, #dc2626);
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.4); box-shadow: 0 4px 12px rgba(220, 38, 38, 0.4);
} }
.progress-retry-btn:active { .progress-retry-btn:active {
transform: translateY(0); transform: translateY(0);
box-shadow: 0 2px 6px rgba(220, 38, 38, 0.3); box-shadow: 0 2px 6px rgba(220, 38, 38, 0.3);
} }
.progress-retry-btn:disabled { .progress-retry-btn:disabled {
background: linear-gradient(135deg, #4b5563, #6b7280); background: linear-gradient(135deg, #4b5563, #6b7280);
cursor: not-allowed; cursor: not-allowed;
transform: none; transform: none;
box-shadow: none; box-shadow: none;
opacity: 0.6; opacity: 0.6;
} }
/* Progress overlay error state */ /* Progress overlay error state */
.progress-overlay.error-state { .progress-overlay.error-state {
border-color: rgba(239, 68, 68, 0.5); border-color: rgba(239, 68, 68, 0.5);
box-shadow: box-shadow:
0 4px 16px rgba(0, 0, 0, 0.5), 0 4px 16px rgba(0, 0, 0, 0.5),
0 0 30px rgba(239, 68, 68, 0.2), 0 0 30px rgba(239, 68, 68, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.05); inset 0 1px 0 rgba(255, 255, 255, 0.05);
} }
.progress-overlay.error-state #progressBarFill { .progress-overlay.error-state #progressBarFill {
background: linear-gradient(90deg, #dc2626, #ef4444); background: linear-gradient(90deg, #dc2626, #ef4444);
animation: errorPulse 1.5s ease-in-out infinite; animation: errorPulse 1.5s ease-in-out infinite;
} }
@keyframes errorPulse { @keyframes errorPulse {
0%, 100% { 0%, 100% {
opacity: 0.8; opacity: 0.8;
} }
50% { 50% {
opacity: 1; opacity: 1;
} }
} }
/* Error type specific styling */ /* Error type specific styling */
.progress-error-container.error-network { .progress-error-container.error-network {
border-top-color: rgba(59, 130, 246, 0.5); border-top-color: rgba(59, 130, 246, 0.5);
} }
.progress-error-container.error-network .progress-error-message { .progress-error-container.error-network .progress-error-message {
color: #60a5fa; color: #60a5fa;
text-shadow: 0 0 8px rgba(96, 165, 250, 0.4); text-shadow: 0 0 8px rgba(96, 165, 250, 0.4);
} }
.progress-error-container.error-stall { .progress-error-container.error-stall {
border-top-color: rgba(245, 158, 11, 0.5); border-top-color: rgba(245, 158, 11, 0.5);
} }
.progress-error-container.error-stall .progress-error-message { .progress-error-container.error-stall .progress-error-message {
color: #fbbf24; color: #fbbf24;
text-shadow: 0 0 8px rgba(251, 191, 36, 0.4); text-shadow: 0 0 8px rgba(251, 191, 36, 0.4);
} }
.progress-error-container.error-file { .progress-error-container.error-file {
border-top-color: rgba(239, 68, 68, 0.5); border-top-color: rgba(239, 68, 68, 0.5);
} }
.progress-error-container.error-file .progress-error-message { .progress-error-container.error-file .progress-error-message {
color: #f87171; color: #f87171;
text-shadow: 0 0 8px rgba(248, 113, 113, 0.4); text-shadow: 0 0 8px rgba(248, 113, 113, 0.4);
} }
.progress-error-container.error-permission { .progress-error-container.error-permission {
border-top-color: rgba(168, 85, 247, 0.5); border-top-color: rgba(168, 85, 247, 0.5);
} }
.progress-error-container.error-permission .progress-error-message { .progress-error-container.error-permission .progress-error-message {
color: #a855f7; color: #a855f7;
text-shadow: 0 0 8px rgba(168, 85, 247, 0.4); text-shadow: 0 0 8px rgba(168, 85, 247, 0.4);
} }
.progress-error-container.error-server { .progress-error-container.error-server {
border-top-color: rgba(236, 72, 153, 0.5); border-top-color: rgba(236, 72, 153, 0.5);
} }
.progress-error-container.error-server .progress-error-message { .progress-error-container.error-server .progress-error-message {
color: #ec4899; color: #ec4899;
text-shadow: 0 0 8px rgba(236, 72, 153, 0.4); text-shadow: 0 0 8px rgba(236, 72, 153, 0.4);
} }
.progress-error-container.error-corruption { .progress-error-container.error-corruption {
border-top-color: rgba(220, 38, 38, 0.8); border-top-color: rgba(220, 38, 38, 0.8);
} }
.progress-error-container.error-corruption .progress-error-message { .progress-error-container.error-corruption .progress-error-message {
color: #dc2626; color: #dc2626;
text-shadow: 0 0 8px rgba(220, 38, 38, 0.6); text-shadow: 0 0 8px rgba(220, 38, 38, 0.6);
font-weight: 600; font-weight: 600;
} }
.progress-error-container.error-butler { .progress-error-container.error-butler {
border-top-color: rgba(245, 158, 11, 0.5); border-top-color: rgba(245, 158, 11, 0.5);
} }
.progress-error-container.error-butler .progress-error-message { .progress-error-container.error-butler .progress-error-message {
color: #f59e0b; color: #f59e0b;
text-shadow: 0 0 8px rgba(245, 158, 11, 0.4); text-shadow: 0 0 8px rgba(245, 158, 11, 0.4);
} }
.progress-error-container.error-space { .progress-error-container.error-space {
border-top-color: rgba(168, 85, 247, 0.5); border-top-color: rgba(168, 85, 247, 0.5);
} }
.progress-error-container.error-space .progress-error-message { .progress-error-container.error-space .progress-error-message {
color: #a855f7; color: #a855f7;
text-shadow: 0 0 8px rgba(168, 85, 247, 0.4); text-shadow: 0 0 8px rgba(168, 85, 247, 0.4);
} }
.progress-error-container.error-conflict { .progress-error-container.error-conflict {
border-top-color: rgba(6, 182, 212, 0.5); border-top-color: rgba(6, 182, 212, 0.5);
} }
.progress-error-container.error-conflict .progress-error-message { .progress-error-container.error-conflict .progress-error-message {
color: #06b6d4; color: #06b6d4;
text-shadow: 0 0 8px rgba(6, 182, 212, 0.4); text-shadow: 0 0 8px rgba(6, 182, 212, 0.4);
} }
/* Connection quality indicators */ /* Connection quality indicators */
.progress-details { .progress-details {
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.progress-details #progressSize { .progress-details #progressSize {
transition: color 0.5s ease; transition: color 0.5s ease;
} }
/* Enhanced retry button states */ /* Enhanced retry button states */
.progress-retry-btn.retrying { .progress-retry-btn.retrying {
background: linear-gradient(135deg, #059669, #10b981); background: linear-gradient(135deg, #059669, #10b981);
animation: retryingPulse 1s ease-in-out infinite; animation: retryingPulse 1s ease-in-out infinite;
} }
@keyframes retryingPulse { @keyframes retryingPulse {
0%, 100% { 0%, 100% {
transform: scale(1); transform: scale(1);
box-shadow: 0 2px 8px rgba(5, 150, 105, 0.3); box-shadow: 0 2px 8px rgba(5, 150, 105, 0.3);
} }
50% { 50% {
transform: scale(1.05); transform: scale(1.05);
box-shadow: 0 4px 12px rgba(5, 150, 105, 0.4); box-shadow: 0 4px 12px rgba(5, 150, 105, 0.4);
} }
} }
/* Network status indicator (optional future enhancement) */ /* Network status indicator (optional future enhancement) */
.network-status { .network-status {
position: absolute; position: absolute;
top: 5px; top: 5px;
right: 5px; right: 5px;
width: 8px; width: 8px;
height: 8px; height: 8px;
border-radius: 50%; border-radius: 50%;
background: #10b981; background: #10b981;
box-shadow: 0 0 6px rgba(16, 185, 129, 0.6); box-shadow: 0 0 6px rgba(16, 185, 129, 0.6);
} }
.network-status.poor { .network-status.poor {
background: #f87171; background: #f87171;
box-shadow: 0 0 6px rgba(248, 113, 113, 0.6); box-shadow: 0 0 6px rgba(248, 113, 113, 0.6);
} }
.network-status.fair { .network-status.fair {
background: #fbbf24; background: #fbbf24;
box-shadow: 0 0 6px rgba(251, 191, 36, 0.6); box-shadow: 0 0 6px rgba(251, 191, 36, 0.6);
} }
.progress-bar-fill { .progress-bar-fill {
height: 100%; height: 100%;
@@ -5990,167 +5990,167 @@ select.settings-input option {
to { to {
opacity: 1; opacity: 1;
} }
} }
/* Launcher Update Modal Styles */ /* Launcher Update Modal Styles */
.update-modal-overlay { .update-modal-overlay {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: rgba(0, 0, 0, 0.85); background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 100000; z-index: 100000;
animation: fadeIn 0.3s ease; animation: fadeIn 0.3s ease;
} }
.update-modal { .update-modal {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
border: 2px solid rgba(147, 51, 234, 0.3); border: 2px solid rgba(147, 51, 234, 0.3);
border-radius: 16px; border-radius: 16px;
padding: 2rem; padding: 2rem;
max-width: 500px; max-width: 500px;
width: 90%; width: 90%;
box-shadow: 0 20px 60px rgba(147, 51, 234, 0.3); box-shadow: 0 20px 60px rgba(147, 51, 234, 0.3);
animation: slideUp 0.3s ease; animation: slideUp 0.3s ease;
} }
@keyframes slideUp { @keyframes slideUp {
from { from {
transform: translateY(30px); transform: translateY(30px);
opacity: 0; opacity: 0;
} }
to { to {
transform: translateY(0); transform: translateY(0);
opacity: 1; opacity: 1;
} }
} }
.update-header { .update-header {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
color: #9333ea; color: #9333ea;
} }
.update-header i { .update-header i {
font-size: 2rem; font-size: 2rem;
} }
.update-header h2 { .update-header h2 {
margin: 0; margin: 0;
font-size: 1.5rem; font-size: 1.5rem;
color: #fff; color: #fff;
} }
.update-content { .update-content {
color: #e0e0e0; color: #e0e0e0;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
.update-version { .update-version {
font-size: 1.2rem; font-size: 1.2rem;
font-weight: 600; font-weight: 600;
color: #9333ea; color: #9333ea;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.current-version { .current-version {
color: #888; color: #888;
font-size: 0.9rem; font-size: 0.9rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.release-notes { .release-notes {
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
border-left: 3px solid #9333ea; border-left: 3px solid #9333ea;
padding: 1rem; padding: 1rem;
border-radius: 8px; border-radius: 8px;
max-height: 200px; max-height: 200px;
overflow-y: auto; overflow-y: auto;
font-size: 0.9rem; font-size: 0.9rem;
line-height: 1.6; line-height: 1.6;
} }
.update-progress { .update-progress {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
.progress-bar-container { .progress-bar-container {
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
border-radius: 10px; border-radius: 10px;
height: 20px; height: 20px;
overflow: hidden; overflow: hidden;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.progress-bar { .progress-bar {
height: 100%; height: 100%;
background: linear-gradient(90deg, #9333ea 0%, #7c3aed 100%); background: linear-gradient(90deg, #9333ea 0%, #7c3aed 100%);
width: 0%; width: 0%;
transition: width 0.3s ease; transition: width 0.3s ease;
border-radius: 10px; border-radius: 10px;
} }
.progress-text { .progress-text {
color: #aaa; color: #aaa;
font-size: 0.9rem; font-size: 0.9rem;
text-align: center; text-align: center;
margin: 0; margin: 0;
} }
.update-note { .update-note {
background: rgba(147, 51, 234, 0.1); background: rgba(147, 51, 234, 0.1);
border: 1px solid rgba(147, 51, 234, 0.3); border: 1px solid rgba(147, 51, 234, 0.3);
padding: 0.75rem; padding: 0.75rem;
border-radius: 8px; border-radius: 8px;
font-size: 0.9rem; font-size: 0.9rem;
margin-top: 1rem; margin-top: 1rem;
} }
.update-actions { .update-actions {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
justify-content: flex-end; justify-content: flex-end;
} }
.update-actions button { .update-actions button {
padding: 0.75rem 1.5rem; padding: 0.75rem 1.5rem;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
} }
.update-actions .btn-primary { .update-actions .btn-primary {
background: linear-gradient(135deg, #9333ea 0%, #7c3aed 100%); background: linear-gradient(135deg, #9333ea 0%, #7c3aed 100%);
color: white; color: white;
} }
.update-actions .btn-primary:hover:not(:disabled) { .update-actions .btn-primary:hover:not(:disabled) {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(147, 51, 234, 0.4); box-shadow: 0 5px 15px rgba(147, 51, 234, 0.4);
} }
.update-actions .btn-secondary { .update-actions .btn-secondary {
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
color: #e0e0e0; color: #e0e0e0;
border: 1px solid rgba(255, 255, 255, 0.2); border: 1px solid rgba(255, 255, 255, 0.2);
} }
.update-actions .btn-secondary:hover:not(:disabled) { .update-actions .btn-secondary:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.15); background: rgba(255, 255, 255, 0.15);
} }
.update-actions button:disabled { .update-actions button:disabled {
opacity: 0.5; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;
} }

View File

@@ -29,5 +29,5 @@ package() {
mkdir -p "$pkgdir/opt/$_pkgname" mkdir -p "$pkgdir/opt/$_pkgname"
cp -r "$_pkgname/dist/linux-unpacked/"* "$pkgdir/opt/$_pkgname" cp -r "$_pkgname/dist/linux-unpacked/"* "$pkgdir/opt/$_pkgname"
install -Dm644 "$_pkgname.desktop" "$pkgdir/usr/share/applications/$_pkgname.desktop" install -Dm644 "$_pkgname.desktop" "$pkgdir/usr/share/applications/$_pkgname.desktop"
install -Dm644 "$_pkgname/icon.png" "$pkgdir/usr/share/icons/hicolor/512x512/apps/$_pkgname.png" install -Dm644 "$_pkgname/GUI/icon.png" "$pkgdir/usr/share/icons/hicolor/256x256/apps/$_pkgname.png"
} }

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,8 +340,7 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) {
}); });
}); });
// Si on arrive ici, le téléchargement a réussi return dest;
return;
} catch (error) { } catch (error) {
lastError = error; lastError = error;
@@ -368,13 +363,16 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) {
// If file is substantial size (> 1.5GB), treat as success and break // If file is substantial size (> 1.5GB), treat as success and break
if (sizeInMB >= 1500) { if (sizeInMB >= 1500) {
console.log('File appears to be complete despite error, treating as success'); console.log('File appears to be complete despite error, treating as success');
return; // Exit the retry loop successfully return dest; // Exit the retry loop successfully
} }
} }
// 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

@@ -48,6 +48,7 @@
"adm-zip": "^0.5.10", "adm-zip": "^0.5.10",
"axios": "^1.6.0", "axios": "^1.6.0",
"discord-rpc": "^4.0.1", "discord-rpc": "^4.0.1",
"dotenv": "^17.2.3",
"electron-updater": "^6.7.3", "electron-updater": "^6.7.3",
"fs-extra": "^11.3.3", "fs-extra": "^11.3.3",
"tar": "^6.2.1", "tar": "^6.2.1",
@@ -68,7 +69,8 @@
"preload.js", "preload.js",
"backend/**/*", "backend/**/*",
"GUI/**/*", "GUI/**/*",
"package.json" "package.json",
".env"
], ],
"win": { "win": {
"target": [ "target": [
@@ -80,7 +82,7 @@
] ]
} }
], ],
"icon": "build/icon.ico" "icon": "icon.ico"
}, },
"linux": { "linux": {
"target": [ "target": [

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),
@@ -129,5 +130,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
}, },
onUpdateDownloaded: (callback) => { onUpdateDownloaded: (callback) => {
ipcRenderer.on('update-downloaded', (event, data) => callback(data)); ipcRenderer.on('update-downloaded', (event, data) => callback(data));
},
onUpdateError: (callback) => {
ipcRenderer.on('update-error', (event, data) => callback(data));
} }
}); });