mirror of
https://git.sanhost.net/sanasol/hytale-f2p.git
synced 2026-03-01 00:01:48 -03:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57056e5b7a | ||
|
|
fcf041be39 | ||
|
|
d53ac915f3 |
@@ -44,10 +44,6 @@
|
|||||||
<i class="fas fa-box"></i>
|
<i class="fas fa-box"></i>
|
||||||
<span class="nav-tooltip" data-i18n="nav.mods">Mods</span>
|
<span class="nav-tooltip" data-i18n="nav.mods">Mods</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-page="news">
|
|
||||||
<i class="fas fa-newspaper"></i>
|
|
||||||
<span class="nav-tooltip" data-i18n="nav.news">News</span>
|
|
||||||
</div>
|
|
||||||
<div class="nav-item" data-page="settings">
|
<div class="nav-item" data-page="settings">
|
||||||
<i class="fas fa-cog"></i>
|
<i class="fas fa-cog"></i>
|
||||||
<span class="nav-tooltip" data-i18n="nav.settings">Settings</span>
|
<span class="nav-tooltip" data-i18n="nav.settings">Settings</span>
|
||||||
@@ -219,36 +215,38 @@
|
|||||||
<span data-i18n="play.playButton">PLAY HYTALE</span>
|
<span data-i18n="play.playButton">PLAY HYTALE</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div style="display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 12px; font-size: 12px;">
|
<div id="game-info-bar" class="game-info-bar">
|
||||||
<span style="color: #93a3b8;">Telegram:</span>
|
<span id="game-version-info" class="game-info-item game-info-loading">loading...</span>
|
||||||
<a href="#" onclick="window.electronAPI?.openExternal('https://t.me/sanhostnet'); return false;" style="color: #93a3b8; text-decoration: none; display: flex; align-items: center; gap: 4px; transition: color 0.2s;" onmouseover="this.style.color='#60a5fa'" onmouseout="this.style.color='#93a3b8'">
|
<span class="game-info-sep"></span>
|
||||||
<i class="fas fa-users"></i> Group
|
<span id="game-branch-info" class="game-info-item game-info-loading">...</span>
|
||||||
|
<span id="game-last-played" class="game-info-item" style="display: none;"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="community-links">
|
||||||
|
<a href="#" class="community-link" onclick="openDiscordExternal(); return false;" title="Community Chat">
|
||||||
|
<i class="fas fa-comments" style="color: #22c55e;"></i>
|
||||||
|
<span>Chat</span>
|
||||||
</a>
|
</a>
|
||||||
<span style="color: #4b5563;">|</span>
|
<a href="#" class="community-link" onclick="window.electronAPI?.openExternal('https://discord.gg/8WVU24XshK'); return false;" title="Discord">
|
||||||
<a href="#" onclick="window.electronAPI?.openExternal('https://t.me/hf2p_og'); return false;" style="color: #93a3b8; text-decoration: none; display: flex; align-items: center; gap: 4px; transition: color 0.2s;" onmouseover="this.style.color='#60a5fa'" onmouseout="this.style.color='#93a3b8'">
|
<i class="fab fa-discord" style="color: #5865F2;"></i>
|
||||||
<i class="fab fa-telegram"></i> Channel
|
<span>Discord</span>
|
||||||
</a>
|
</a>
|
||||||
<span style="color: #4b5563;">|</span>
|
<a href="#" class="community-link" onclick="window.electronAPI?.openExternal('https://t.me/hf2p_og'); return false;" title="Telegram Channel">
|
||||||
<a href="#" onclick="openDiscordExternal(); return false;" style="color: #93a3b8; text-decoration: none; display: flex; align-items: center; gap: 4px; transition: color 0.2s;" onmouseover="this.style.color='#60a5fa'" onmouseout="this.style.color='#93a3b8'">
|
<i class="fab fa-telegram" style="color: #26A5E4;"></i>
|
||||||
<i class="fas fa-comments"></i> Community Chat
|
<span>Channel</span>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="community-link" onclick="window.electronAPI?.openExternal('https://t.me/sanhostnet'); return false;" title="Telegram Group">
|
||||||
|
<i class="fab fa-telegram" style="color: #26A5E4;"></i>
|
||||||
|
<span>Group</span>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="community-link" onclick="window.electronAPI?.openExternal('https://git.sanhost.net/sanasol/hytale-f2p'); return false;" title="Source Code">
|
||||||
|
<i class="fab fa-git-alt" style="color: #e2e8f0;"></i>
|
||||||
|
<span>Source</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="news-section">
|
|
||||||
<div class="news-header">
|
|
||||||
<h2 class="news-title">
|
|
||||||
<i class="fas fa-star mr-2"></i>
|
|
||||||
<span data-i18n="play.latestNews">LATEST NEWS</span>
|
|
||||||
</h2>
|
|
||||||
<button class="view-all-btn" onclick="navigateToPage('news')">
|
|
||||||
<span data-i18n="play.viewAll">VIEW ALL</span> <i
|
|
||||||
class="fas fa-arrow-right ml-1"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div id="newsGrid" class="news-grid-horizontal"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="featured-page" class="page">
|
<div id="featured-page" class="page">
|
||||||
@@ -301,15 +299,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="news-page" class="page">
|
|
||||||
<div class="news-header">
|
|
||||||
<h2 class="news-title">
|
|
||||||
<i class="fas fa-newspaper mr-2"></i>
|
|
||||||
<span data-i18n="news.title">ALL NEWS</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div id="allNewsGrid" class="news-grid-full"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="settings-page" class="page">
|
<div id="settings-page" class="page">
|
||||||
<div class="settings-container">
|
<div class="settings-container">
|
||||||
@@ -950,34 +939,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="version-display-bottom">
|
<div class="version-display-bottom" onclick="window.electronAPI?.openExternal('https://git.sanhost.net/sanasol/hytale-f2p/releases')" style="cursor: pointer;" title="View releases">
|
||||||
<i class="fas fa-code-branch"></i>
|
<i class="fas fa-code-branch"></i>
|
||||||
<span id="launcherVersion"></span>
|
<span id="launcherVersion"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="fixed bottom-0 left-0 right-0 z-50 bg-black/80 backdrop-blur-sm px-4 py-2">
|
<footer class="fixed bottom-0 left-0 right-0 z-50 bg-black/80 backdrop-blur-sm px-4 py-2">
|
||||||
<div class="flex items-center justify-center text-xs text-gray-400">
|
<div class="flex items-center justify-center text-xs text-gray-400">
|
||||||
<span>Made by <a href="https://github.com/amiayweb" target="_blank"
|
<span>Made by <a href="#" onclick="window.electronAPI?.openExternal('https://github.com/amiayweb'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@amiayweb</a> & <a
|
class="text-blue-400 hover:text-blue-300 transition-colors">@amiayweb</a> & <a
|
||||||
href="https://github.com/Relyz1993" target="_blank"
|
href="#" onclick="window.electronAPI?.openExternal('https://github.com/Relyz1993'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@Relyz</a></span>
|
class="text-blue-400 hover:text-blue-300 transition-colors">@Relyz</a></span>
|
||||||
<span class="mx-2">|</span>
|
<span class="mx-2">|</span>
|
||||||
<span>Contributors:
|
<span>Contributors:
|
||||||
<a href="https://github.com/chasem-dev" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/chasem-dev'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@chasem-dev</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@chasem-dev</a>,
|
||||||
<a href="https://github.com/crimera" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/crimera'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@crimera</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@crimera</a>,
|
||||||
<a href="https://github.com/sanasol" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/sanasol'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@sanasol</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@sanasol</a>,
|
||||||
<a href="https://github.com/Terromur" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/Terromur'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@terromur</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@terromur</a>,
|
||||||
<a href="https://github.com/ericiskoolbeans" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/ericiskoolbeans'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@ericiskoolbeans</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@ericiskoolbeans</a>,
|
||||||
<a href="https://github.com/fazrigading" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/fazrigading'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@fazrigading</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@fazrigading</a>,
|
||||||
<a href="https://github.com/Rahul-Sahani04" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/Rahul-Sahani04'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@Rahul-Sahani04</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@Rahul-Sahani04</a>,
|
||||||
<a href="https://github.com/xSamiVS" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/xSamiVS'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@xSamiVS</a>
|
class="text-blue-400 hover:text-blue-300 transition-colors">@xSamiVS</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -169,6 +169,16 @@ window.switchProfile = async (id) => {
|
|||||||
// Refresh UI
|
// Refresh UI
|
||||||
await loadProfiles();
|
await loadProfiles();
|
||||||
|
|
||||||
|
// Refresh branch radio buttons in settings
|
||||||
|
if (window.SettingsAPI?.reloadBranch) {
|
||||||
|
await window.SettingsAPI.reloadBranch();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh game info bar on play page
|
||||||
|
if (window.LauncherUI?.loadGameInfoBar) {
|
||||||
|
window.LauncherUI.loadGameInfoBar();
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh Mods
|
// Refresh Mods
|
||||||
if (window.modsManager) {
|
if (window.modsManager) {
|
||||||
if (window.modsManager.loadInstalledMods) await window.modsManager.loadInstalledMods();
|
if (window.modsManager.loadInstalledMods) await window.modsManager.loadInstalledMods();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import './ui.js';
|
import './ui.js';
|
||||||
import './install.js';
|
import './install.js';
|
||||||
import './launcher.js';
|
import './launcher.js';
|
||||||
import './news.js';
|
|
||||||
import { initModsManager } from './mods.js';
|
import { initModsManager } from './mods.js';
|
||||||
import './players.js';
|
import './players.js';
|
||||||
import './settings.js';
|
import './settings.js';
|
||||||
|
|||||||
@@ -1924,6 +1924,11 @@ async function switchBranch(newBranch) {
|
|||||||
await loadVersionBranch();
|
await loadVersionBranch();
|
||||||
console.log('[Settings] Radio buttons updated after branch switch');
|
console.log('[Settings] Radio buttons updated after branch switch');
|
||||||
|
|
||||||
|
// Refresh game info bar on play page
|
||||||
|
if (window.LauncherUI?.loadGameInfoBar) {
|
||||||
|
window.LauncherUI.loadGameInfoBar();
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (window.LauncherUI) {
|
if (window.LauncherUI) {
|
||||||
window.LauncherUI.hideProgress();
|
window.LauncherUI.hideProgress();
|
||||||
|
|||||||
59
GUI/js/ui.js
59
GUI/js/ui.js
@@ -138,6 +138,7 @@ function showLauncherOrInstall(isInstalled) {
|
|||||||
if (gameTitle) gameTitle.style.display = '';
|
if (gameTitle) gameTitle.style.display = '';
|
||||||
showPage('play-page');
|
showPage('play-page');
|
||||||
setActiveNav('play');
|
setActiveNav('play');
|
||||||
|
loadGameInfoBar();
|
||||||
} else {
|
} else {
|
||||||
if (launcher) launcher.style.display = 'none';
|
if (launcher) launcher.style.display = 'none';
|
||||||
if (install) {
|
if (install) {
|
||||||
@@ -738,13 +739,69 @@ async function checkGameInstallation() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadGameInfoBar() {
|
||||||
|
try {
|
||||||
|
const info = await window.electronAPI.getGameInfo();
|
||||||
|
const bar = document.getElementById('game-info-bar');
|
||||||
|
if (!bar) return;
|
||||||
|
|
||||||
|
const versionEl = document.getElementById('game-version-info');
|
||||||
|
const branchEl = document.getElementById('game-branch-info');
|
||||||
|
const lastPlayedEl = document.getElementById('game-last-played');
|
||||||
|
|
||||||
|
if (versionEl) {
|
||||||
|
versionEl.classList.remove('game-info-loading');
|
||||||
|
if (info.version) {
|
||||||
|
const display = info.readableVersion
|
||||||
|
? `${info.version} - ${info.readableVersion}`
|
||||||
|
: info.version;
|
||||||
|
versionEl.textContent = display;
|
||||||
|
} else {
|
||||||
|
versionEl.textContent = 'Not installed';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (branchEl) {
|
||||||
|
branchEl.classList.remove('game-info-loading');
|
||||||
|
const isPreRelease = info.branch === 'pre-release';
|
||||||
|
branchEl.textContent = isPreRelease ? 'Pre-Release' : 'Release';
|
||||||
|
branchEl.style.color = isPreRelease ? '#f59e0b' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastPlayedEl && info.lastPlayed) {
|
||||||
|
lastPlayedEl.style.display = '';
|
||||||
|
if (!lastPlayedEl.previousElementSibling?.classList?.contains('game-info-sep')) {
|
||||||
|
const sep = document.createElement('span');
|
||||||
|
sep.className = 'game-info-sep';
|
||||||
|
lastPlayedEl.before(sep);
|
||||||
|
}
|
||||||
|
lastPlayedEl.textContent = formatTimeAgo(info.lastPlayed);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Could not load game info:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTimeAgo(timestamp) {
|
||||||
|
const diff = Date.now() - timestamp;
|
||||||
|
const minutes = Math.floor(diff / 60000);
|
||||||
|
const hours = Math.floor(diff / 3600000);
|
||||||
|
const days = Math.floor(diff / 86400000);
|
||||||
|
|
||||||
|
if (minutes < 1) return 'Just played';
|
||||||
|
if (minutes < 60) return `Played ${minutes}m ago`;
|
||||||
|
if (hours < 24) return `Played ${hours}h ago`;
|
||||||
|
if (days < 30) return `Played ${days}d ago`;
|
||||||
|
return `Played ${Math.floor(days / 30)}mo ago`;
|
||||||
|
}
|
||||||
|
|
||||||
window.LauncherUI = {
|
window.LauncherUI = {
|
||||||
showPage,
|
showPage,
|
||||||
setActiveNav,
|
setActiveNav,
|
||||||
showLauncherOrInstall,
|
showLauncherOrInstall,
|
||||||
showProgress,
|
showProgress,
|
||||||
hideProgress,
|
hideProgress,
|
||||||
updateProgress
|
updateProgress,
|
||||||
|
loadGameInfoBar
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make installation effects globally available
|
// Make installation effects globally available
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class ClientUpdateManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.electronAPI.onUpdateDownloadProgress((progress) => {
|
window.electronAPI.onUpdateDownloadProgress((progress) => {
|
||||||
|
console.log('📊 download-progress event:', progress);
|
||||||
|
this.downloadProgressReceived = true;
|
||||||
this.updateDownloadProgress(progress);
|
this.updateDownloadProgress(progress);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@ class ClientUpdateManager {
|
|||||||
|
|
||||||
const popupHTML = `
|
const popupHTML = `
|
||||||
<div id="update-popup-overlay">
|
<div id="update-popup-overlay">
|
||||||
<div class="update-popup-container update-popup-pulse">
|
<div class="update-popup-container">
|
||||||
<div class="update-popup-header">
|
<div class="update-popup-header">
|
||||||
<div class="update-popup-icon">
|
<div class="update-popup-icon">
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
@@ -118,11 +120,29 @@ class ClientUpdateManager {
|
|||||||
this.blockInterface();
|
this.blockInterface();
|
||||||
|
|
||||||
// Show progress container immediately (auto-download is enabled)
|
// Show progress container immediately (auto-download is enabled)
|
||||||
|
this.downloadProgressReceived = false;
|
||||||
const progressContainer = document.getElementById('update-progress-container');
|
const progressContainer = document.getElementById('update-progress-container');
|
||||||
if (progressContainer) {
|
if (progressContainer) {
|
||||||
progressContainer.style.display = 'block';
|
progressContainer.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no progress events arrive within 2 seconds, show indeterminate animation
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.downloadProgressReceived && progressContainer) {
|
||||||
|
const bar = document.getElementById('update-progress-bar');
|
||||||
|
const speed = document.getElementById('update-progress-speed');
|
||||||
|
const percent = document.getElementById('update-progress-percent');
|
||||||
|
if (bar) {
|
||||||
|
bar.style.width = '100%';
|
||||||
|
bar.style.animation = 'indeterminateProgress 1.5s ease-in-out infinite';
|
||||||
|
bar.style.opacity = '0.7';
|
||||||
|
}
|
||||||
|
if (speed) speed.textContent = '';
|
||||||
|
if (percent) percent.textContent = 'Downloading...';
|
||||||
|
console.log('⏳ No download-progress events received, showing indeterminate progress');
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
const installBtn = document.getElementById('update-install-btn');
|
const installBtn = document.getElementById('update-install-btn');
|
||||||
if (installBtn) {
|
if (installBtn) {
|
||||||
installBtn.addEventListener('click', async (e) => {
|
installBtn.addEventListener('click', async (e) => {
|
||||||
@@ -248,6 +268,10 @@ class ClientUpdateManager {
|
|||||||
const progressSize = document.getElementById('update-progress-size');
|
const progressSize = document.getElementById('update-progress-size');
|
||||||
|
|
||||||
if (progressBar && progress) {
|
if (progressBar && progress) {
|
||||||
|
// Stop indeterminate animation if it was running
|
||||||
|
progressBar.style.animation = 'none';
|
||||||
|
progressBar.style.opacity = '1';
|
||||||
|
|
||||||
const percent = Math.round(progress.percent || 0);
|
const percent = Math.round(progress.percent || 0);
|
||||||
progressBar.style.width = `${percent}%`;
|
progressBar.style.width = `${percent}%`;
|
||||||
|
|
||||||
@@ -273,57 +297,35 @@ class ClientUpdateManager {
|
|||||||
showUpdateDownloaded(updateInfo) {
|
showUpdateDownloaded(updateInfo) {
|
||||||
const statusText = document.getElementById('update-status-text');
|
const statusText = document.getElementById('update-status-text');
|
||||||
const progressContainer = document.getElementById('update-progress-container');
|
const progressContainer = document.getElementById('update-progress-container');
|
||||||
|
const progressBar = document.getElementById('update-progress-bar');
|
||||||
|
const progressPercent = document.getElementById('update-progress-percent');
|
||||||
|
const progressSpeed = document.getElementById('update-progress-speed');
|
||||||
|
const progressSize = document.getElementById('update-progress-size');
|
||||||
const buttonsContainer = document.getElementById('update-buttons-container');
|
const buttonsContainer = document.getElementById('update-buttons-container');
|
||||||
const installBtn = document.getElementById('update-install-btn');
|
|
||||||
const downloadBtn = document.getElementById('update-download-btn');
|
|
||||||
const skipBtn = document.getElementById('update-skip-btn');
|
const skipBtn = document.getElementById('update-skip-btn');
|
||||||
const footerText = document.getElementById('update-footer-text');
|
const footerText = document.getElementById('update-footer-text');
|
||||||
const popupContainer = document.querySelector('.update-popup-container');
|
|
||||||
|
|
||||||
// Remove breathing/pulse animation when download is complete
|
// Stop indeterminate animation and show 100%
|
||||||
if (popupContainer) {
|
if (progressBar) {
|
||||||
popupContainer.classList.remove('update-popup-pulse');
|
progressBar.style.animation = 'none';
|
||||||
|
progressBar.style.opacity = '1';
|
||||||
|
progressBar.style.width = '100%';
|
||||||
|
}
|
||||||
|
if (progressPercent) progressPercent.textContent = '100%';
|
||||||
|
if (progressSpeed) progressSpeed.textContent = 'Complete';
|
||||||
|
if (progressContainer) progressContainer.style.display = 'block';
|
||||||
|
|
||||||
|
// Hide progress after a short delay so user sees 100%
|
||||||
|
setTimeout(() => {
|
||||||
|
if (progressContainer) progressContainer.style.display = 'none';
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
if (statusText) {
|
||||||
|
statusText.textContent = 'Update downloaded! Ready to install.';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progressContainer) {
|
if (footerText) {
|
||||||
progressContainer.style.display = 'none';
|
footerText.textContent = 'Click to install the update:';
|
||||||
}
|
|
||||||
|
|
||||||
// Use platform info from main process if available, fallback to browser detection
|
|
||||||
const autoInstallSupported = updateInfo.autoInstallSupported !== undefined
|
|
||||||
? updateInfo.autoInstallSupported
|
|
||||||
: navigator.platform.toUpperCase().indexOf('MAC') < 0;
|
|
||||||
|
|
||||||
if (!autoInstallSupported) {
|
|
||||||
// macOS: Show manual download as primary since auto-update doesn't work
|
|
||||||
if (statusText) {
|
|
||||||
statusText.textContent = 'Update downloaded but auto-install may not work on macOS.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (installBtn) {
|
|
||||||
// Still show install button but as secondary option
|
|
||||||
installBtn.classList.add('update-download-btn-secondary');
|
|
||||||
installBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Try Install & Restart';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloadBtn) {
|
|
||||||
// Make manual download primary
|
|
||||||
downloadBtn.classList.remove('update-download-btn-secondary');
|
|
||||||
downloadBtn.innerHTML = '<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>Download Manually (Recommended)';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (footerText) {
|
|
||||||
footerText.textContent = 'Auto-install often fails on macOS:';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Windows/Linux: Auto-install should work
|
|
||||||
if (statusText) {
|
|
||||||
statusText.textContent = 'Update downloaded! Ready to install.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (footerText) {
|
|
||||||
footerText.textContent = 'Click to install the update:';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buttonsContainer) {
|
if (buttonsContainer) {
|
||||||
@@ -338,7 +340,7 @@ class ClientUpdateManager {
|
|||||||
console.error('❌ Skip button not found in DOM!');
|
console.error('❌ Skip button not found in DOM!');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ Update downloaded, ready to install. autoInstallSupported:', autoInstallSupported);
|
console.log('✅ Update downloaded, ready to install');
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdateError(errorInfo) {
|
handleUpdateError(errorInfo) {
|
||||||
@@ -366,9 +368,6 @@ class ClientUpdateManager {
|
|||||||
|
|
||||||
if (errorMessage && errorText) {
|
if (errorMessage && errorText) {
|
||||||
let message = errorInfo.message || 'An error occurred during the update process.';
|
let message = errorInfo.message || 'An error occurred during the update process.';
|
||||||
if (errorInfo.isMacSigningError) {
|
|
||||||
message = 'Auto-update requires code signing. Please download manually.';
|
|
||||||
}
|
|
||||||
errorText.textContent = message;
|
errorText.textContent = message;
|
||||||
errorMessage.style.display = 'block';
|
errorMessage.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -646,12 +646,18 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#play-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.play-section {
|
.play-section {
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 200px;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.play-content {
|
.play-content {
|
||||||
@@ -663,6 +669,62 @@ body {
|
|||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.game-info-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-info-loading {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-info-sep {
|
||||||
|
width: 3px;
|
||||||
|
height: 3px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-links {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
padding: 0.4rem 0.75rem;
|
||||||
|
color: #9ca3af;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-link:hover {
|
||||||
|
color: #e2e8f0;
|
||||||
|
border-color: rgba(147, 51, 234, 0.4);
|
||||||
|
background: rgba(147, 51, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.play-title {
|
.play-title {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -5260,19 +5322,15 @@ select.settings-input option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.update-popup-pulse {
|
@keyframes indeterminateProgress {
|
||||||
animation: updatePulse 2s ease-in-out infinite !important;
|
0% {
|
||||||
}
|
opacity: 0.4;
|
||||||
|
|
||||||
@keyframes updatePulse {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
transform: scale(1.02);
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -445,6 +445,17 @@ async function launchGame(playerNameOverride = null, progressCallback, javaPathO
|
|||||||
|
|
||||||
const env = { ...process.env };
|
const env = { ...process.env };
|
||||||
|
|
||||||
|
// Linux: Add Client directory to LD_LIBRARY_PATH so the dynamic linker can find
|
||||||
|
// bundled native libraries (e.g. libSDL3_image.so.0). The .NET DllImport only tries
|
||||||
|
// bare names like "SDL3_image.so" which don't match versioned .so.0 files.
|
||||||
|
// LD_LIBRARY_PATH lets dlopen() find them via standard library resolution.
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
const clientDir = path.dirname(clientPath);
|
||||||
|
const existing = env.LD_LIBRARY_PATH || '';
|
||||||
|
env.LD_LIBRARY_PATH = existing ? `${clientDir}:${existing}` : clientDir;
|
||||||
|
console.log(`Linux: LD_LIBRARY_PATH includes ${clientDir}`);
|
||||||
|
}
|
||||||
|
|
||||||
const waylandEnv = setupWaylandEnvironment();
|
const waylandEnv = setupWaylandEnvironment();
|
||||||
Object.assign(env, waylandEnv);
|
Object.assign(env, waylandEnv);
|
||||||
const gpuEnv = setupGpuEnvironment(gpuPreference);
|
const gpuEnv = setupGpuEnvironment(gpuPreference);
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
const {
|
const {
|
||||||
loadConfig,
|
loadConfig,
|
||||||
saveConfig,
|
saveConfig,
|
||||||
getModsPath
|
getModsPath,
|
||||||
|
loadVersionBranch,
|
||||||
|
saveVersionBranch,
|
||||||
|
loadVersionClient,
|
||||||
|
saveVersionClient
|
||||||
} = require('../core/config');
|
} = require('../core/config');
|
||||||
|
|
||||||
// Lazy-load modManager to avoid circular deps, or keep imports structured.
|
// Lazy-load modManager to avoid circular deps, or keep imports structured.
|
||||||
@@ -39,11 +43,13 @@ class ProfileManager {
|
|||||||
name: 'Default',
|
name: 'Default',
|
||||||
created: new Date().toISOString(),
|
created: new Date().toISOString(),
|
||||||
lastUsed: new Date().toISOString(),
|
lastUsed: new Date().toISOString(),
|
||||||
|
|
||||||
// settings specific to this profile
|
// settings specific to this profile
|
||||||
// If global settings existed, we copy them here
|
// If global settings existed, we copy them here
|
||||||
mods: config.installedMods || [], // Legacy mods are now part of default profile
|
mods: config.installedMods || [], // Legacy mods are now part of default profile
|
||||||
javaPath: config.javaPath || '',
|
javaPath: config.javaPath || '',
|
||||||
|
versionBranch: config.version_branch || 'release',
|
||||||
|
versionClient: config.version_client || null,
|
||||||
gameOptions: {
|
gameOptions: {
|
||||||
minMemory: '1G',
|
minMemory: '1G',
|
||||||
maxMemory: '4G',
|
maxMemory: '4G',
|
||||||
@@ -73,13 +79,16 @@ class ProfileManager {
|
|||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const id = uuidv4();
|
const id = uuidv4();
|
||||||
|
|
||||||
|
// New profiles inherit the current branch/version
|
||||||
const newProfile = {
|
const newProfile = {
|
||||||
id,
|
id,
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
created: new Date().toISOString(),
|
created: new Date().toISOString(),
|
||||||
lastUsed: null,
|
lastUsed: null,
|
||||||
mods: [], // Start with no mods enabled
|
mods: [], // Start with no mods enabled
|
||||||
javaPath: '',
|
javaPath: '',
|
||||||
|
versionBranch: loadVersionBranch(),
|
||||||
|
versionClient: loadVersionClient(),
|
||||||
gameOptions: {
|
gameOptions: {
|
||||||
minMemory: '1G',
|
minMemory: '1G',
|
||||||
maxMemory: '4G',
|
maxMemory: '4G',
|
||||||
@@ -128,20 +137,35 @@ class ProfileManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[ProfileManager] Switching to profile: ${config.profiles[id].name} (${id})`);
|
console.log(`[ProfileManager] Switching to profile: ${config.profiles[id].name} (${id})`);
|
||||||
|
|
||||||
// 1. Update config first
|
// Save current branch/version to the outgoing profile
|
||||||
|
const oldId = config.activeProfileId;
|
||||||
|
if (oldId && config.profiles[oldId]) {
|
||||||
|
config.profiles[oldId].versionBranch = loadVersionBranch();
|
||||||
|
config.profiles[oldId].versionClient = loadVersionClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Update config
|
||||||
config.profiles[id].lastUsed = new Date().toISOString();
|
config.profiles[id].lastUsed = new Date().toISOString();
|
||||||
saveConfig({
|
saveConfig({
|
||||||
activeProfileId: id,
|
activeProfileId: id,
|
||||||
profiles: config.profiles
|
profiles: config.profiles
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Trigger Mod Sync
|
// 2. Restore branch/version from the new profile
|
||||||
// We need to require this here to ensure it uses the *newly saved* active profile ID
|
const newProfile = config.profiles[id];
|
||||||
|
if (newProfile.versionBranch) {
|
||||||
|
saveVersionBranch(newProfile.versionBranch);
|
||||||
|
}
|
||||||
|
if (newProfile.versionClient !== undefined) {
|
||||||
|
saveVersionClient(newProfile.versionClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Trigger Mod Sync
|
||||||
const { syncModsForCurrentProfile } = require('./modManager');
|
const { syncModsForCurrentProfile } = require('./modManager');
|
||||||
await syncModsForCurrentProfile();
|
await syncModsForCurrentProfile();
|
||||||
|
|
||||||
return config.profiles[id];
|
return newProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteProfile(id) {
|
deleteProfile(id) {
|
||||||
@@ -177,7 +201,7 @@ class ProfileManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Safety checks on updates
|
// Safety checks on updates
|
||||||
const allowedFields = ['name', 'javaPath', 'gameOptions', 'mods'];
|
const allowedFields = ['name', 'javaPath', 'gameOptions', 'mods', 'versionBranch', 'versionClient'];
|
||||||
const sanitizedUpdates = {};
|
const sanitizedUpdates = {};
|
||||||
|
|
||||||
Object.keys(updates).forEach(key => {
|
Object.keys(updates).forEach(key => {
|
||||||
|
|||||||
@@ -594,6 +594,10 @@ function setupGpuEnvironment(gpuPreference) {
|
|||||||
if (detected.vendor === 'nvidia') {
|
if (detected.vendor === 'nvidia') {
|
||||||
envVars.__NV_PRIME_RENDER_OFFLOAD = '1';
|
envVars.__NV_PRIME_RENDER_OFFLOAD = '1';
|
||||||
envVars.__GLX_VENDOR_LIBRARY_NAME = 'nvidia';
|
envVars.__GLX_VENDOR_LIBRARY_NAME = 'nvidia';
|
||||||
|
// Prevent Wayland explicit sync crashes on NVIDIA (Hyprland, etc.)
|
||||||
|
if (isWaylandSession()) {
|
||||||
|
envVars.__NV_DISABLE_EXPLICIT_SYNC = '1';
|
||||||
|
}
|
||||||
const nvidiaEglFile = '/usr/share/glvnd/egl_vendor.d/10_nvidia.json';
|
const nvidiaEglFile = '/usr/share/glvnd/egl_vendor.d/10_nvidia.json';
|
||||||
if (fs.existsSync(nvidiaEglFile)) {
|
if (fs.existsSync(nvidiaEglFile)) {
|
||||||
envVars.__EGL_VENDOR_LIBRARY_FILENAMES = nvidiaEglFile;
|
envVars.__EGL_VENDOR_LIBRARY_FILENAMES = nvidiaEglFile;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Singleplayer Server Crash: fastutil ClassNotFoundException
|
# Singleplayer Server Crash: fastutil ClassNotFoundException
|
||||||
|
|
||||||
## Status: Open — NO SOLUTION (Feb 24-27 2026)
|
## Status: Open — likely outdated HytaleServer.jar (Feb 24-28 2026)
|
||||||
|
|
||||||
## Symptom
|
## Symptom
|
||||||
|
|
||||||
@@ -20,9 +20,10 @@ Server exits with code 1. Multiplayer works fine for the same user.
|
|||||||
|
|
||||||
1. **ヅ𝚃 JAYED !** (Feb 24) — Windows x86_64, had AOT cache errors before fastutil crash
|
1. **ヅ𝚃 JAYED !** (Feb 24) — Windows x86_64, had AOT cache errors before fastutil crash
|
||||||
2. **Asentrix** (Feb 27) — Windows x86_64 (NT 10.0.26200.0), RTX 4060, Launcher v2.4.4, NO AOT cache errors
|
2. **Asentrix** (Feb 27) — Windows x86_64 (NT 10.0.26200.0), RTX 4060, Launcher v2.4.4, NO AOT cache errors
|
||||||
|
3. **7645754** (Feb 28) — Standalone server on localhost, **FIXED by updating HytaleServer.jar**
|
||||||
|
|
||||||
- Reproduces 100% on singleplayer, every attempt
|
- Reproduces 100% on singleplayer, every attempt (users 1-2)
|
||||||
- Multiplayer works fine for both users
|
- Multiplayer works fine for users 1-2
|
||||||
- macOS/Linux users are NOT affected
|
- macOS/Linux users are NOT affected
|
||||||
|
|
||||||
## Ruled Out (confirmed via debug builds)
|
## Ruled Out (confirmed via debug builds)
|
||||||
@@ -37,26 +38,30 @@ Server exits with code 1. Multiplayer works fine for the same user.
|
|||||||
| **ARM64/Parallels** | User is on standard Windows x86_64 | Not applicable. |
|
| **ARM64/Parallels** | User is on standard Windows x86_64 | Not applicable. |
|
||||||
| **AOT cache** | Asentrix has no AOT errors (JAYED did), both crash the same way | Not the root cause. |
|
| **AOT cache** | Asentrix has no AOT errors (JAYED did), both crash the same way | Not the root cause. |
|
||||||
|
|
||||||
|
## Key Finding: Outdated HytaleServer.jar (Feb 28)
|
||||||
|
|
||||||
|
User `7645754` had the **exact same error** on their standalone localhost server but NOT on their VPS. **Fixed by replacing `HytaleServer.jar` with the current version.** The old JAR used to work but stopped — likely the bundled JRE was updated and is now incompatible with older JAR versions.
|
||||||
|
|
||||||
|
This strongly suggests the root cause for F2P launcher users is also a **stale/mismatched `HytaleServer.jar`**. The launcher may report the correct version but the actual file on disk could be from an older download.
|
||||||
|
|
||||||
## What We Know
|
## What We Know
|
||||||
|
|
||||||
- `fastutil` is bundled inside `HytaleServer.jar` (fat/shaded JAR) — same JAR for all users
|
- `fastutil` is bundled inside `HytaleServer.jar` (fat/shaded JAR)
|
||||||
- JVM's `BuiltinClassLoader` cannot find `it.unimi.dsi.fastutil.objects.ObjectArrayList` despite it being in the JAR
|
- JVM's `BuiltinClassLoader` cannot find `it.unimi.dsi.fastutil.objects.ObjectArrayList` despite it being in the JAR
|
||||||
- Crash happens at `EarlyPluginLoader` static initializer (line 34) which imports `ObjectArrayList`
|
- Crash happens at `EarlyPluginLoader` static initializer (line 34) which imports `ObjectArrayList`
|
||||||
- The bundled JRE is identical for all users (downloaded by launcher)
|
- **Replacing `HytaleServer.jar` with a fresh copy fixes the issue** (confirmed by user 3)
|
||||||
- The issue is **not** caused by anything the F2P launcher adds — it's the vanilla server JVM failing to load its own classes
|
- The issue is NOT caused by the DualAuth agent or any launcher modification
|
||||||
|
|
||||||
## Remaining Theories
|
## Fix for Users
|
||||||
|
|
||||||
1. **Antivirus/security software** — Windows Defender or third-party AV intercepting JAR file reads. Real-time scanning + fat JAR = known conflict. **Untested** — user should try disabling AV temporarily.
|
### F2P Launcher users (Asentrix, JAYED)
|
||||||
2. **Windows Insider build** — Asentrix is on NT 10.0.26200.0 (Windows 11 Dev/Insider). Bleeding-edge Windows may have JVM compatibility issues.
|
1. **Delete the entire game folder**: `%LOCALAPPDATA%\HytaleF2P\release\package\game\`
|
||||||
3. **File locking** — Stalled `java.exe` processes holding `HytaleServer.jar` open (Asentrix had stalled processes killed at every launch).
|
2. Relaunch — launcher will re-download everything fresh
|
||||||
4. **Corrupted JRE on disk** — Despite being the same download, filesystem or AV may have corrupted specific JRE files on their system.
|
3. NOT just "repair" — full delete to ensure no stale files remain
|
||||||
|
|
||||||
## Next Steps to Try
|
### Standalone server users
|
||||||
|
1. Download fresh `HytaleServer.jar` from current game version
|
||||||
1. **Disable Windows Defender** temporarily — the only quick test left
|
2. Replace the old JAR file
|
||||||
2. **Delete bundled JRE** and let launcher re-download — rules out local JRE corruption
|
|
||||||
3. **Ask if official Hytale singleplayer works** — if it also crashes, it's their system (but F2P users may not have access)
|
|
||||||
|
|
||||||
## Update History
|
## Update History
|
||||||
|
|
||||||
@@ -70,6 +75,12 @@ User reported singleplayer crash. Initial investigation found AOT cache errors +
|
|||||||
- **Conclusion**: Neither the agent nor CDS is the cause. The JVM itself cannot load classes from the fat JAR on these specific Windows systems.
|
- **Conclusion**: Neither the agent nor CDS is the cause. The JVM itself cannot load classes from the fat JAR on these specific Windows systems.
|
||||||
- Note: wrapper `injectArgs` append AFTER `-jar`, so they cannot inject JVM flags — only `JAVA_TOOL_OPTIONS` works for JVM flags
|
- Note: wrapper `injectArgs` append AFTER `-jar`, so they cannot inject JVM flags — only `JAVA_TOOL_OPTIONS` works for JVM flags
|
||||||
|
|
||||||
|
### Feb 28: Third user (7645754) — FIXED by replacing HytaleServer.jar
|
||||||
|
- Standalone server user had same crash on localhost, VPS worked fine
|
||||||
|
- **Fixed by updating `HytaleServer.jar` to match VPS version**
|
||||||
|
- Root cause likely: outdated JAR incompatible with current/updated JRE
|
||||||
|
- For F2P launcher users: need to delete game folder and force fresh re-download
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
- Java wrapper config: `backend/core/config.js` (stripFlags / injectArgs)
|
- Java wrapper config: `backend/core/config.js` (stripFlags / injectArgs)
|
||||||
|
|||||||
89
main.js
89
main.js
@@ -193,7 +193,7 @@ function createWindow() {
|
|||||||
preload: path.join(__dirname, 'preload.js'),
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
devTools: false,
|
devTools: process.argv.includes('--dev'),
|
||||||
webSecurity: true
|
webSecurity: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -251,7 +251,7 @@ function createWindow() {
|
|||||||
mainWindow.webContents.send('update-error', {
|
mainWindow.webContents.send('update-error', {
|
||||||
message: err.message,
|
message: err.message,
|
||||||
isMacSigningError: isMacSigningError,
|
isMacSigningError: isMacSigningError,
|
||||||
requiresManualDownload: isMacSigningError || process.platform === 'darwin'
|
requiresManualDownload: isMacSigningError
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -272,9 +272,7 @@ function createWindow() {
|
|||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
mainWindow.webContents.send('update-downloaded', {
|
mainWindow.webContents.send('update-downloaded', {
|
||||||
version: info.version,
|
version: info.version,
|
||||||
platform: process.platform,
|
platform: process.platform
|
||||||
// macOS auto-install often fails on unsigned apps
|
|
||||||
autoInstallSupported: process.platform !== 'darwin'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -286,9 +284,12 @@ function createWindow() {
|
|||||||
});
|
});
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
mainWindow.webContents.on('devtools-opened', () => {
|
const isDev = process.argv.includes('--dev');
|
||||||
mainWindow.webContents.closeDevTools();
|
if (!isDev) {
|
||||||
});
|
mainWindow.webContents.on('devtools-opened', () => {
|
||||||
|
mainWindow.webContents.closeDevTools();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
mainWindow.webContents.on('before-input-event', (event, input) => {
|
mainWindow.webContents.on('before-input-event', (event, input) => {
|
||||||
// Allow standard copy/paste/cut/select-all shortcuts
|
// Allow standard copy/paste/cut/select-all shortcuts
|
||||||
@@ -301,18 +302,20 @@ function createWindow() {
|
|||||||
return; // Don't block these
|
return; // Don't block these
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block devtools shortcuts
|
// Block devtools shortcuts (except in dev mode)
|
||||||
if (input.control && input.shift && input.key.toLowerCase() === 'i') {
|
if (!isDev) {
|
||||||
event.preventDefault();
|
if (input.control && input.shift && input.key.toLowerCase() === 'i') {
|
||||||
}
|
event.preventDefault();
|
||||||
if (input.control && input.shift && input.key.toLowerCase() === 'j') {
|
}
|
||||||
event.preventDefault();
|
if (input.control && input.shift && input.key.toLowerCase() === 'j') {
|
||||||
}
|
event.preventDefault();
|
||||||
if (input.control && input.shift && input.key.toLowerCase() === 'c') {
|
}
|
||||||
event.preventDefault();
|
if (input.control && input.shift && input.key.toLowerCase() === 'c') {
|
||||||
}
|
event.preventDefault();
|
||||||
if (input.key === 'F12') {
|
}
|
||||||
event.preventDefault();
|
if (input.key === 'F12') {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (input.key === 'F5') {
|
if (input.key === 'F5') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -552,6 +555,9 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, g
|
|||||||
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference, null, launchOptions);
|
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference, null, launchOptions);
|
||||||
|
|
||||||
if (result.success && result.launched) {
|
if (result.success && result.launched) {
|
||||||
|
// Save last played timestamp
|
||||||
|
try { saveConfig({ last_played: Date.now() }); } catch (e) { /* ignore */ }
|
||||||
|
|
||||||
const closeOnStart = loadCloseLauncherOnStart();
|
const closeOnStart = loadCloseLauncherOnStart();
|
||||||
if (closeOnStart) {
|
if (closeOnStart) {
|
||||||
console.log('Close Launcher on start enabled, quitting application...');
|
console.log('Close Launcher on start enabled, quitting application...');
|
||||||
@@ -608,6 +614,7 @@ ipcMain.handle('launch-game-with-password', async (event, playerName, javaPath,
|
|||||||
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference, null, { password });
|
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference, null, { password });
|
||||||
|
|
||||||
if (result.success && result.launched) {
|
if (result.success && result.launched) {
|
||||||
|
try { saveConfig({ last_played: Date.now() }); } catch (e) { /* ignore */ }
|
||||||
const closeOnStart = loadCloseLauncherOnStart();
|
const closeOnStart = loadCloseLauncherOnStart();
|
||||||
if (closeOnStart) {
|
if (closeOnStart) {
|
||||||
setTimeout(() => { app.quit(); }, 1000);
|
setTimeout(() => { app.quit(); }, 1000);
|
||||||
@@ -1366,6 +1373,11 @@ ipcMain.handle('get-detected-gpu', () => {
|
|||||||
ipcMain.handle('save-version-branch', (event, branch) => {
|
ipcMain.handle('save-version-branch', (event, branch) => {
|
||||||
const { saveVersionBranch } = require('./backend/launcher');
|
const { saveVersionBranch } = require('./backend/launcher');
|
||||||
saveVersionBranch(branch);
|
saveVersionBranch(branch);
|
||||||
|
// Sync to active profile
|
||||||
|
const activeProfile = profileManager.getActiveProfile();
|
||||||
|
if (activeProfile) {
|
||||||
|
profileManager.updateProfile(activeProfile.id, { versionBranch: branch });
|
||||||
|
}
|
||||||
return { success: true };
|
return { success: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1379,6 +1391,43 @@ ipcMain.handle('load-version-client', () => {
|
|||||||
return loadVersionClient();
|
return loadVersionClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-game-info', async () => {
|
||||||
|
const { loadVersionClient, loadVersionBranch } = require('./backend/launcher');
|
||||||
|
const { fetchMirrorManifest } = require('./backend/services/versionManager');
|
||||||
|
const config = loadConfig();
|
||||||
|
const branch = loadVersionBranch();
|
||||||
|
|
||||||
|
let version = null;
|
||||||
|
let readableVersion = null;
|
||||||
|
try {
|
||||||
|
const manifest = await fetchMirrorManifest();
|
||||||
|
if (manifest?.versions?.[branch]) {
|
||||||
|
const branchVersions = manifest.versions[branch];
|
||||||
|
// Get the highest version number for the current branch
|
||||||
|
const nums = Object.keys(branchVersions).map(Number).filter(n => !isNaN(n));
|
||||||
|
if (nums.length > 0) {
|
||||||
|
const latest = Math.max(...nums).toString();
|
||||||
|
version = `v${latest}`;
|
||||||
|
readableVersion = branchVersions[latest]?.version || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Manifest fetch failed, fall back to stored version
|
||||||
|
version = loadVersionClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!version) {
|
||||||
|
version = loadVersionClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version,
|
||||||
|
readableVersion,
|
||||||
|
branch,
|
||||||
|
lastPlayed: config.last_played || null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('window-close', () => {
|
ipcMain.handle('window-close', () => {
|
||||||
app.quit();
|
app.quit();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hytale-f2p-launcher",
|
"name": "hytale-f2p-launcher",
|
||||||
"version": "2.4.6",
|
"version": "2.4.8",
|
||||||
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
|
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
|
||||||
"homepage": "https://git.sanhost.net/sanasol/hytale-f2p",
|
"homepage": "https://git.sanhost.net/sanasol/hytale-f2p",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
saveVersionBranch: (branch) => ipcRenderer.invoke('save-version-branch', branch),
|
saveVersionBranch: (branch) => ipcRenderer.invoke('save-version-branch', branch),
|
||||||
loadVersionBranch: () => ipcRenderer.invoke('load-version-branch'),
|
loadVersionBranch: () => ipcRenderer.invoke('load-version-branch'),
|
||||||
loadVersionClient: () => ipcRenderer.invoke('load-version-client'),
|
loadVersionClient: () => ipcRenderer.invoke('load-version-client'),
|
||||||
|
getGameInfo: () => ipcRenderer.invoke('get-game-info'),
|
||||||
|
|
||||||
acceptFirstLaunchUpdate: (existingGame) => ipcRenderer.invoke('accept-first-launch-update', existingGame),
|
acceptFirstLaunchUpdate: (existingGame) => ipcRenderer.invoke('accept-first-launch-update', existingGame),
|
||||||
markAsLaunched: () => ipcRenderer.invoke('mark-as-launched'),
|
markAsLaunched: () => ipcRenderer.invoke('mark-as-launched'),
|
||||||
|
|||||||
Reference in New Issue
Block a user