Compare commits

..

1 Commits
v2.4.7 ... main

Author SHA1 Message Date
sanasol
57056e5b7a v2.4.8: UI improvements, update popup fixes, per-profile branch tracking
- Fix auto-update popup: indeterminate progress fallback when no download events, show 100% on complete
- Remove macOS auto-update warning (app is now signed)
- Disable update popup pulse animation
- Remove news tab and news section from home screen
- Center play section vertically, add community links with colored icons
- Add game version + branch display on play page (from manifest)
- Add last played timestamp tracking
- Version badge links to git.sanhost.net releases
- Profiles now store version_branch and version_client per-configuration
- Profile switch restores branch/version and refreshes settings UI
- DevTools enabled in dev mode (electron . --dev)
- Reorder community links: Chat, Discord, TG Channel, TG Group, Source

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 21:47:23 +01:00
11 changed files with 337 additions and 146 deletions

View File

@@ -44,10 +44,6 @@
<i class="fas fa-box"></i>
<span class="nav-tooltip" data-i18n="nav.mods">Mods</span>
</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">
<i class="fas fa-cog"></i>
<span class="nav-tooltip" data-i18n="nav.settings">Settings</span>
@@ -219,36 +215,38 @@
<span data-i18n="play.playButton">PLAY HYTALE</span>
</button>
<div style="display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 12px; font-size: 12px;">
<span style="color: #93a3b8;">Telegram:</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'">
<i class="fas fa-users"></i> Group
<div id="game-info-bar" class="game-info-bar">
<span id="game-version-info" class="game-info-item game-info-loading">loading...</span>
<span class="game-info-sep"></span>
<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>
<span style="color: #4b5563;">|</span>
<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-telegram"></i> Channel
<a href="#" class="community-link" onclick="window.electronAPI?.openExternal('https://discord.gg/8WVU24XshK'); return false;" title="Discord">
<i class="fab fa-discord" style="color: #5865F2;"></i>
<span>Discord</span>
</a>
<span style="color: #4b5563;">|</span>
<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="fas fa-comments"></i> Community Chat
<a href="#" class="community-link" onclick="window.electronAPI?.openExternal('https://t.me/hf2p_og'); return false;" title="Telegram Channel">
<i class="fab fa-telegram" style="color: #26A5E4;"></i>
<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>
</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 id="featured-page" class="page">
@@ -301,15 +299,6 @@
</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 class="settings-container">
@@ -950,34 +939,34 @@
</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>
<span id="launcherVersion"></span>
</div>
<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">
<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
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>
<span class="mx-2">|</span>
<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>,
<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>,
<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>,
<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>,
<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>,
<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>,
<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>,
<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>
</span>
</div>

View File

@@ -169,6 +169,16 @@ window.switchProfile = async (id) => {
// Refresh UI
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
if (window.modsManager) {
if (window.modsManager.loadInstalledMods) await window.modsManager.loadInstalledMods();

View File

@@ -1,7 +1,6 @@
import './ui.js';
import './install.js';
import './launcher.js';
import './news.js';
import { initModsManager } from './mods.js';
import './players.js';
import './settings.js';

View File

@@ -1924,6 +1924,11 @@ async function switchBranch(newBranch) {
await loadVersionBranch();
console.log('[Settings] Radio buttons updated after branch switch');
// Refresh game info bar on play page
if (window.LauncherUI?.loadGameInfoBar) {
window.LauncherUI.loadGameInfoBar();
}
setTimeout(() => {
if (window.LauncherUI) {
window.LauncherUI.hideProgress();

View File

@@ -138,6 +138,7 @@ function showLauncherOrInstall(isInstalled) {
if (gameTitle) gameTitle.style.display = '';
showPage('play-page');
setActiveNav('play');
loadGameInfoBar();
} else {
if (launcher) launcher.style.display = 'none';
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 = {
showPage,
setActiveNav,
showLauncherOrInstall,
showProgress,
hideProgress,
updateProgress
updateProgress,
loadGameInfoBar
};
// Make installation effects globally available

View File

@@ -16,6 +16,8 @@ class ClientUpdateManager {
});
window.electronAPI.onUpdateDownloadProgress((progress) => {
console.log('📊 download-progress event:', progress);
this.downloadProgressReceived = true;
this.updateDownloadProgress(progress);
});
@@ -49,7 +51,7 @@ class ClientUpdateManager {
const popupHTML = `
<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-icon">
<i class="fas fa-download"></i>
@@ -118,11 +120,29 @@ class ClientUpdateManager {
this.blockInterface();
// Show progress container immediately (auto-download is enabled)
this.downloadProgressReceived = false;
const progressContainer = document.getElementById('update-progress-container');
if (progressContainer) {
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');
if (installBtn) {
installBtn.addEventListener('click', async (e) => {
@@ -248,6 +268,10 @@ class ClientUpdateManager {
const progressSize = document.getElementById('update-progress-size');
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);
progressBar.style.width = `${percent}%`;
@@ -273,57 +297,35 @@ class ClientUpdateManager {
showUpdateDownloaded(updateInfo) {
const statusText = document.getElementById('update-status-text');
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 installBtn = document.getElementById('update-install-btn');
const downloadBtn = document.getElementById('update-download-btn');
const skipBtn = document.getElementById('update-skip-btn');
const footerText = document.getElementById('update-footer-text');
const popupContainer = document.querySelector('.update-popup-container');
// Remove breathing/pulse animation when download is complete
if (popupContainer) {
popupContainer.classList.remove('update-popup-pulse');
// Stop indeterminate animation and show 100%
if (progressBar) {
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) {
progressContainer.style.display = 'none';
}
// 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 (footerText) {
footerText.textContent = 'Click to install the update:';
}
if (buttonsContainer) {
@@ -338,7 +340,7 @@ class ClientUpdateManager {
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) {
@@ -366,9 +368,6 @@ class ClientUpdateManager {
if (errorMessage && errorText) {
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;
errorMessage.style.display = 'block';
}

View File

@@ -646,12 +646,18 @@ body {
}
#play-page {
display: flex;
flex-direction: column;
}
.play-section {
padding: 2rem;
display: flex;
justify-content: center;
align-items: center;
min-height: 200px;
flex: 1;
min-height: 0;
}
.play-content {
@@ -663,6 +669,62 @@ body {
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 {
font-size: 2rem;
font-weight: 700;
@@ -5260,19 +5322,15 @@ select.settings-input option {
}
.update-popup-pulse {
animation: updatePulse 2s ease-in-out infinite !important;
}
@keyframes updatePulse {
0%,
100% {
transform: scale(1);
@keyframes indeterminateProgress {
0% {
opacity: 0.4;
}
50% {
transform: scale(1.02);
opacity: 0.8;
}
100% {
opacity: 0.4;
}
}

View File

@@ -1,10 +1,14 @@
const path = require('path');
const fs = require('fs');
const { v4: uuidv4 } = require('uuid');
const {
loadConfig,
saveConfig,
getModsPath
const {
loadConfig,
saveConfig,
getModsPath,
loadVersionBranch,
saveVersionBranch,
loadVersionClient,
saveVersionClient
} = require('../core/config');
// Lazy-load modManager to avoid circular deps, or keep imports structured.
@@ -39,11 +43,13 @@ class ProfileManager {
name: 'Default',
created: new Date().toISOString(),
lastUsed: new Date().toISOString(),
// settings specific to this profile
// If global settings existed, we copy them here
mods: config.installedMods || [], // Legacy mods are now part of default profile
javaPath: config.javaPath || '',
versionBranch: config.version_branch || 'release',
versionClient: config.version_client || null,
gameOptions: {
minMemory: '1G',
maxMemory: '4G',
@@ -73,13 +79,16 @@ class ProfileManager {
const config = loadConfig();
const id = uuidv4();
// New profiles inherit the current branch/version
const newProfile = {
id,
name: name.trim(),
created: new Date().toISOString(),
lastUsed: null,
mods: [], // Start with no mods enabled
javaPath: '',
javaPath: '',
versionBranch: loadVersionBranch(),
versionClient: loadVersionClient(),
gameOptions: {
minMemory: '1G',
maxMemory: '4G',
@@ -128,20 +137,35 @@ class ProfileManager {
}
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();
saveConfig({
saveConfig({
activeProfileId: id,
profiles: config.profiles
profiles: config.profiles
});
// 2. Trigger Mod Sync
// We need to require this here to ensure it uses the *newly saved* active profile ID
// 2. Restore branch/version from the new profile
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');
await syncModsForCurrentProfile();
return config.profiles[id];
return newProfile;
}
deleteProfile(id) {
@@ -177,7 +201,7 @@ class ProfileManager {
}
// Safety checks on updates
const allowedFields = ['name', 'javaPath', 'gameOptions', 'mods'];
const allowedFields = ['name', 'javaPath', 'gameOptions', 'mods', 'versionBranch', 'versionClient'];
const sanitizedUpdates = {};
Object.keys(updates).forEach(key => {

89
main.js
View File

@@ -193,7 +193,7 @@ function createWindow() {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
devTools: false,
devTools: process.argv.includes('--dev'),
webSecurity: true
}
});
@@ -251,7 +251,7 @@ function createWindow() {
mainWindow.webContents.send('update-error', {
message: err.message,
isMacSigningError: isMacSigningError,
requiresManualDownload: isMacSigningError || process.platform === 'darwin'
requiresManualDownload: isMacSigningError
});
}
});
@@ -272,9 +272,7 @@ function createWindow() {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('update-downloaded', {
version: info.version,
platform: process.platform,
// macOS auto-install often fails on unsigned apps
autoInstallSupported: process.platform !== 'darwin'
platform: process.platform
});
}
});
@@ -286,9 +284,12 @@ function createWindow() {
});
}, 3000);
mainWindow.webContents.on('devtools-opened', () => {
mainWindow.webContents.closeDevTools();
});
const isDev = process.argv.includes('--dev');
if (!isDev) {
mainWindow.webContents.on('devtools-opened', () => {
mainWindow.webContents.closeDevTools();
});
}
mainWindow.webContents.on('before-input-event', (event, input) => {
// Allow standard copy/paste/cut/select-all shortcuts
@@ -301,18 +302,20 @@ function createWindow() {
return; // Don't block these
}
// Block devtools shortcuts
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() === 'c') {
event.preventDefault();
}
if (input.key === 'F12') {
event.preventDefault();
// Block devtools shortcuts (except in dev mode)
if (!isDev) {
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() === 'c') {
event.preventDefault();
}
if (input.key === 'F12') {
event.preventDefault();
}
}
if (input.key === 'F5') {
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);
if (result.success && result.launched) {
// Save last played timestamp
try { saveConfig({ last_played: Date.now() }); } catch (e) { /* ignore */ }
const closeOnStart = loadCloseLauncherOnStart();
if (closeOnStart) {
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 });
if (result.success && result.launched) {
try { saveConfig({ last_played: Date.now() }); } catch (e) { /* ignore */ }
const closeOnStart = loadCloseLauncherOnStart();
if (closeOnStart) {
setTimeout(() => { app.quit(); }, 1000);
@@ -1366,6 +1373,11 @@ ipcMain.handle('get-detected-gpu', () => {
ipcMain.handle('save-version-branch', (event, branch) => {
const { saveVersionBranch } = require('./backend/launcher');
saveVersionBranch(branch);
// Sync to active profile
const activeProfile = profileManager.getActiveProfile();
if (activeProfile) {
profileManager.updateProfile(activeProfile.id, { versionBranch: branch });
}
return { success: true };
});
@@ -1379,6 +1391,43 @@ ipcMain.handle('load-version-client', () => {
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', () => {
app.quit();
});

View File

@@ -1,6 +1,6 @@
{
"name": "hytale-f2p-launcher",
"version": "2.4.7",
"version": "2.4.8",
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
"homepage": "https://git.sanhost.net/sanasol/hytale-f2p",
"main": "main.js",

View File

@@ -79,6 +79,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
saveVersionBranch: (branch) => ipcRenderer.invoke('save-version-branch', branch),
loadVersionBranch: () => ipcRenderer.invoke('load-version-branch'),
loadVersionClient: () => ipcRenderer.invoke('load-version-client'),
getGameInfo: () => ipcRenderer.invoke('get-game-info'),
acceptFirstLaunchUpdate: (existingGame) => ipcRenderer.invoke('accept-first-launch-update', existingGame),
markAsLaunched: () => ipcRenderer.invoke('mark-as-launched'),