mirror of
https://github.com/amiayweb/Hytale-F2P.git
synced 2026-02-26 17:11:45 -03:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b53c7f89d | ||
|
|
ff5acb5278 | ||
|
|
5d75ca80aa | ||
|
|
281aa6fcde | ||
|
|
158d0af820 | ||
|
|
503f304704 | ||
|
|
234e1e1008 | ||
|
|
d002e831cd | ||
|
|
048f2040f1 | ||
|
|
b05aeef66d | ||
|
|
30265549cf | ||
|
|
5a3efba1d6 | ||
|
|
479f24e86f | ||
|
|
611b7a7084 | ||
|
|
e472435927 | ||
|
|
f40d0105df | ||
|
|
96db9adf68 | ||
|
|
a0f49f126c | ||
|
|
f8333c09cd | ||
|
|
bcf0326763 | ||
|
|
0a5c3db710 | ||
|
|
905a9d754c | ||
|
|
99f032e9ab | ||
|
|
5e3506a9ac | ||
|
|
2671a59f38 | ||
|
|
8e9af9c768 |
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -3,7 +3,7 @@ name: Build and Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
@@ -18,13 +18,19 @@ jobs:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
- run: npm ci
|
||||
- run: npx electron-builder --linux --publish never
|
||||
|
||||
- name: Build Linux Packages
|
||||
run: |
|
||||
npx electron-builder --linux --x64 --arm64 --publish never
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-builds
|
||||
path: |
|
||||
dist/*.AppImage
|
||||
dist/*.deb
|
||||
dist/*.rpm
|
||||
dist/*.pacman
|
||||
dist/latest.yml
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
@@ -41,6 +47,7 @@ jobs:
|
||||
name: windows-builds
|
||||
path: |
|
||||
dist/*.exe
|
||||
dist/latest.yml
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
@@ -63,7 +70,10 @@ jobs:
|
||||
release:
|
||||
needs: [build-linux, build-windows, build-macos]
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
|
||||
if: |
|
||||
startsWith(github.ref, 'refs/tags/v') ||
|
||||
github.ref == 'refs/heads/release' ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -77,17 +87,22 @@ jobs:
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R artifacts
|
||||
|
||||
- name: Get version from package.json
|
||||
id: pkg_version
|
||||
run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ github.ref_type == 'tag' && github.ref_name || format('manual-{0}', github.sha) }}
|
||||
name: ${{ github.ref_type == 'tag' && github.ref_name || format('Manual build {0}', github.sha) }}
|
||||
# If it's a tag, use the tag.
|
||||
tag_name: ${{ github.ref_type == 'tag' && github.ref_name || format('v{0}.r{1}', steps.pkg_version.outputs.VERSION, github.run_number) }}
|
||||
# If it's the 'release' branch, use 'v2.0.2-beta.r42'
|
||||
name: ${{ github.ref_type == 'tag' && github.ref_name || format('v{0}-beta.r{1})', steps.pkg_version.outputs.VERSION, github.run_number) }}
|
||||
files: |
|
||||
artifacts/linux-builds/**/*
|
||||
artifacts/windows-builds/**/*
|
||||
artifacts/macos-builds/**/*
|
||||
generate_release_notes: true
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
|
||||
draft: true
|
||||
# DYNAMIC FLAGS: Mark as pre-release ONLY IF it's NOT a tag (meaning it's a branch push)
|
||||
prerelease: ${{ github.ref_type != 'tag' }}
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,4 +1,11 @@
|
||||
dist/*
|
||||
node_modules/*
|
||||
package-lock.json
|
||||
bun.lock
|
||||
|
||||
# Build artifacts
|
||||
src/
|
||||
pkg/
|
||||
|
||||
# Package files
|
||||
*.tar.zst
|
||||
*.zst
|
||||
179
GUI/index.html
179
GUI/index.html
@@ -55,6 +55,10 @@
|
||||
<i class="fas fa-user"></i>
|
||||
<span class="nav-tooltip">Skins</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="logs" id="openLogsBtn" onclick="openLogs()">
|
||||
<i class="fas fa-terminal"></i>
|
||||
<span class="nav-tooltip">Logs</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -279,61 +283,6 @@
|
||||
</div>
|
||||
|
||||
<div class="settings-content">
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">
|
||||
<i class="fas fa-coffee"></i>
|
||||
Java Runtime
|
||||
</h3>
|
||||
|
||||
<div class="settings-option">
|
||||
<label class="settings-checkbox">
|
||||
<input type="checkbox" id="customJavaCheck" />
|
||||
<span class="checkmark"></span>
|
||||
<div class="checkbox-content">
|
||||
<div class="checkbox-title">Use Custom Java Path</div>
|
||||
<div class="checkbox-description">Override the bundled Java runtime with
|
||||
your own installation</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="customJavaOptions" class="custom-java-options" style="display: none;">
|
||||
<div class="settings-input-group">
|
||||
<label class="settings-input-label">Java Executable Path</label>
|
||||
<div class="settings-input-with-button">
|
||||
<input type="text" id="customJavaPath" class="settings-input"
|
||||
placeholder="Select Java path..." readonly />
|
||||
<button id="browseJavaBtn" class="settings-browse-btn">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
Browse
|
||||
</button>
|
||||
</div>
|
||||
<p class="settings-hint">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
Select the Java installation folder (supports Windows, Mac, Linux)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">
|
||||
<i class="fab fa-discord"></i>
|
||||
Discord Integration
|
||||
</h3>
|
||||
|
||||
<div class="settings-option">
|
||||
<label class="settings-checkbox">
|
||||
<input type="checkbox" id="discordRPCCheck" checked />
|
||||
<span class="checkmark"></span>
|
||||
<div class="checkbox-content">
|
||||
<div class="checkbox-title">Enable Discord Rich Presence</div>
|
||||
<div class="checkbox-description">Show your launcher activity on Discord
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">
|
||||
@@ -365,6 +314,41 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-option">
|
||||
<div class="settings-button-group">
|
||||
<button id="repairGameBtn" class="settings-action-btn"
|
||||
onclick="repairGame()">
|
||||
<i class="fas fa-tools"></i>
|
||||
<div class="btn-content">
|
||||
<div class="btn-title">Repair Game</div>
|
||||
<div class="btn-description">Reinstall game files (preserves data)
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="settings-input-group">
|
||||
<label class="settings-input-label">GPU Preference</label>
|
||||
<div class="segmented-control">
|
||||
<input type="radio" id="gpu-auto" name="gpuPreference" value="auto"
|
||||
checked>
|
||||
<label for="gpu-auto">Auto</label>
|
||||
<input type="radio" id="gpu-integrated" name="gpuPreference"
|
||||
value="integrated">
|
||||
<label for="gpu-integrated">Integrated</label>
|
||||
<input type="radio" id="gpu-dedicated" name="gpuPreference"
|
||||
value="dedicated">
|
||||
<label for="gpu-dedicated">Dedicated</label>
|
||||
</div>
|
||||
<p class="settings-hint">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
Select your preferred GPU (Linux: affects DRI_PRIME)
|
||||
</p>
|
||||
<div id="gpu-detection-info" class="gpu-detection-info"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
@@ -400,12 +384,70 @@
|
||||
<i class="fas fa-list"></i>
|
||||
<div class="btn-content">
|
||||
<div class="btn-title">Manage All UUIDs</div>
|
||||
<div class="btn-description">View and manage all player UUIDs</div>
|
||||
<div class="btn-description">View and manage all player UUIDs
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">
|
||||
<i class="fab fa-discord"></i>
|
||||
Discord Integration
|
||||
</h3>
|
||||
|
||||
<div class="settings-option">
|
||||
<label class="settings-checkbox">
|
||||
<input type="checkbox" id="discordRPCCheck" checked />
|
||||
<span class="checkmark"></span>
|
||||
<div class="checkbox-content">
|
||||
<div class="checkbox-title">Enable Discord Rich Presence</div>
|
||||
<div class="checkbox-description">Show your launcher activity on Discord
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">
|
||||
<i class="fas fa-coffee"></i>
|
||||
Java Runtime
|
||||
</h3>
|
||||
|
||||
<div class="settings-option">
|
||||
<label class="settings-checkbox">
|
||||
<input type="checkbox" id="customJavaCheck" />
|
||||
<span class="checkmark"></span>
|
||||
<div class="checkbox-content">
|
||||
<div class="checkbox-title">Use Custom Java Path</div>
|
||||
<div class="checkbox-description">Override the bundled Java runtime with
|
||||
your own installation</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="customJavaOptions" class="custom-java-options" style="display: none;">
|
||||
<div class="settings-input-group">
|
||||
<label class="settings-input-label">Java Executable Path</label>
|
||||
<div class="settings-input-with-button">
|
||||
<input type="text" id="customJavaPath" class="settings-input"
|
||||
placeholder="Select Java path..." readonly />
|
||||
<button id="browseJavaBtn" class="settings-browse-btn">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
Browse
|
||||
</button>
|
||||
</div>
|
||||
<p class="settings-hint">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
Select the Java installation folder (supports Windows, Mac, Linux)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -417,6 +459,30 @@
|
||||
<p>Skin customization coming soon...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="logs-page" class="page">
|
||||
<div class="logs-container">
|
||||
<div class="logs-header">
|
||||
<h2 class="logs-title">
|
||||
<i class="fas fa-terminal"></i>
|
||||
SYSTEM LOGS
|
||||
</h2>
|
||||
<div class="logs-actions">
|
||||
<button class="logs-action-btn" onclick="copyLogs()">
|
||||
<i class="fas fa-copy"></i> Copy
|
||||
</button>
|
||||
<button class="logs-action-btn" onclick="refreshLogs()">
|
||||
<i class="fas fa-sync-alt"></i> Refresh
|
||||
</button>
|
||||
<button class="logs-action-btn" onclick="openLogsFolder()">
|
||||
<i class="fas fa-folder-open"></i> Open Folder
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="logsTerminal" class="logs-terminal">
|
||||
<div class="text-gray-500 text-center mt-10">Loading logs...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
@@ -665,4 +731,5 @@
|
||||
<script type="module" src="js/update.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
||||
@@ -23,14 +23,6 @@ export function setupLauncher() {
|
||||
javaPathInput.addEventListener('change', saveJavaPath);
|
||||
}
|
||||
|
||||
if (window.electronAPI && window.electronAPI.onProgressUpdate) {
|
||||
window.electronAPI.onProgressUpdate((data) => {
|
||||
if (!isDownloading) return;
|
||||
if (window.LauncherUI) {
|
||||
window.LauncherUI.updateProgress(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (window.electronAPI && window.electronAPI.onProgressUpdate) {
|
||||
window.electronAPI.onProgressUpdate((data) => {
|
||||
if (!isDownloading) return;
|
||||
@@ -212,6 +204,15 @@ export async function launch() {
|
||||
javaPath = window.SettingsAPI.getCurrentJavaPath();
|
||||
}
|
||||
|
||||
let gpuPreference = 'auto';
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.loadGpuPreference) {
|
||||
gpuPreference = await window.electronAPI.loadGpuPreference();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading GPU preference:', error);
|
||||
}
|
||||
|
||||
if (window.LauncherUI) window.LauncherUI.showProgress();
|
||||
isDownloading = true;
|
||||
if (playBtn) {
|
||||
@@ -223,7 +224,7 @@ export async function launch() {
|
||||
if (window.LauncherUI) window.LauncherUI.updateProgress({ message: 'Starting game...' });
|
||||
|
||||
if (window.electronAPI && window.electronAPI.launchGame) {
|
||||
const result = await window.electronAPI.launchGame(playerName, javaPath, '');
|
||||
const result = await window.electronAPI.launchGame(playerName, javaPath, '', gpuPreference);
|
||||
|
||||
isDownloading = false;
|
||||
|
||||
@@ -436,6 +437,49 @@ async function performUninstall() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function repairGame() {
|
||||
showCustomConfirm(
|
||||
'Are you sure you want to repair Hytale? This will reinstall the game files but keep your data (saves, screenshots, etc.).',
|
||||
'Repair Game',
|
||||
async () => {
|
||||
await performRepair();
|
||||
},
|
||||
null,
|
||||
'Repair',
|
||||
'Cancel'
|
||||
);
|
||||
}
|
||||
|
||||
async function performRepair() {
|
||||
if (window.LauncherUI) window.LauncherUI.showProgress();
|
||||
if (window.LauncherUI) window.LauncherUI.updateProgress({ message: 'Repairing game...' });
|
||||
isDownloading = true;
|
||||
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.repairGame) {
|
||||
const result = await window.electronAPI.repairGame();
|
||||
|
||||
if (result.success) {
|
||||
if (window.LauncherUI) {
|
||||
window.LauncherUI.updateProgress({ message: 'Game repaired successfully!' });
|
||||
setTimeout(() => {
|
||||
window.LauncherUI.hideProgress();
|
||||
}, 2000);
|
||||
}
|
||||
} else {
|
||||
throw new Error(result.error || 'Repair failed');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (window.LauncherUI) {
|
||||
window.LauncherUI.updateProgress({ message: `Repair failed: ${error.message}` });
|
||||
setTimeout(() => window.LauncherUI.hideProgress(), 3000);
|
||||
}
|
||||
} finally {
|
||||
isDownloading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function resetPlayButton() {
|
||||
isDownloading = false;
|
||||
if (playBtn) {
|
||||
@@ -528,5 +572,59 @@ async function loadCustomJavaPath() {
|
||||
|
||||
window.launch = launch;
|
||||
window.uninstallGame = uninstallGame;
|
||||
window.repairGame = repairGame;
|
||||
|
||||
window.openLogs = async () => {
|
||||
if (window.LauncherUI) {
|
||||
window.LauncherUI.showPage('logs-page');
|
||||
window.LauncherUI.setActiveNav('logs');
|
||||
}
|
||||
await refreshLogs();
|
||||
};
|
||||
|
||||
window.openLogsFolder = async () => {
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.openLogsFolder) {
|
||||
await window.electronAPI.openLogsFolder();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to open logs folder:', error);
|
||||
}
|
||||
};
|
||||
|
||||
window.refreshLogs = async () => {
|
||||
const terminal = document.getElementById('logsTerminal');
|
||||
if (!terminal) return;
|
||||
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.getRecentLogs) {
|
||||
// Fetch up to MAX_LOG_LINES lines
|
||||
const logs = await window.electronAPI.getRecentLogs(MAX_LOG_LINES);
|
||||
if (logs) {
|
||||
// Formatting for colors could be done here if needed
|
||||
terminal.textContent = logs;
|
||||
terminal.scrollTop = terminal.scrollHeight;
|
||||
} else {
|
||||
terminal.textContent = 'No logs available.';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
terminal.textContent = 'Error loading logs: ' + error.message;
|
||||
}
|
||||
};
|
||||
|
||||
window.copyLogs = () => {
|
||||
const terminal = document.getElementById('logsTerminal');
|
||||
if (terminal) {
|
||||
navigator.clipboard.writeText(terminal.textContent)
|
||||
.then(() => alert('Logs copied to clipboard!'))
|
||||
.catch(err => console.error('Failed to copy logs:', err));
|
||||
}
|
||||
};
|
||||
|
||||
window.repairGame = repairGame;
|
||||
|
||||
// Constants
|
||||
const MAX_LOG_LINES = 500;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', setupLauncher);
|
||||
|
||||
96
GUI/js/logs.js
Normal file
96
GUI/js/logs.js
Normal file
@@ -0,0 +1,96 @@
|
||||
|
||||
// Logs Page Logic
|
||||
|
||||
async function loadLogs() {
|
||||
const terminal = document.getElementById('logsTerminal');
|
||||
if (!terminal) return;
|
||||
|
||||
terminal.innerHTML = '<div class="text-gray-500 text-center mt-10">Loading logs...</div>';
|
||||
|
||||
try {
|
||||
const logs = await window.electronAPI.getRecentLogs(500); // Fetch last 500 lines
|
||||
|
||||
if (logs) {
|
||||
// Escape HTML to prevent XSS and preserve format
|
||||
const safeLogs = logs.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
|
||||
terminal.innerHTML = `<pre class="logs-content">${safeLogs}</pre>`;
|
||||
|
||||
// Auto scroll to bottom
|
||||
terminal.scrollTop = terminal.scrollHeight;
|
||||
} else {
|
||||
terminal.innerHTML = '<div class="text-gray-500 text-center mt-10">No logs found.</div>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load logs:', error);
|
||||
terminal.innerHTML = `<div class="text-red-500 text-center mt-10">Error loading logs: ${error.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshLogs() {
|
||||
const btn = document.querySelector('button[onclick="refreshLogs()"] i');
|
||||
if (btn) btn.classList.add('fa-spin');
|
||||
|
||||
await loadLogs();
|
||||
|
||||
if (btn) setTimeout(() => btn.classList.remove('fa-spin'), 500);
|
||||
}
|
||||
|
||||
async function copyLogs() {
|
||||
const terminal = document.getElementById('logsTerminal');
|
||||
if (!terminal) return;
|
||||
|
||||
const content = terminal.innerText;
|
||||
if (!content) return;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(content);
|
||||
|
||||
const btn = document.querySelector('button[onclick="copyLogs()"]');
|
||||
const originalText = btn.innerHTML;
|
||||
|
||||
btn.innerHTML = '<i class="fas fa-check"></i> Copied!';
|
||||
setTimeout(() => {
|
||||
btn.innerHTML = originalText;
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error('Failed to copy logs:', err);
|
||||
}
|
||||
}
|
||||
|
||||
async function openLogsFolder() {
|
||||
await window.electronAPI.openLogsFolder();
|
||||
}
|
||||
|
||||
function openLogs() {
|
||||
// Navigation is handled by sidebar logic, but we can trigger a refresh
|
||||
window.LauncherUI.showPage('logs-page');
|
||||
window.LauncherUI.setActiveNav('logs');
|
||||
refreshLogs();
|
||||
}
|
||||
|
||||
// Expose functions globally
|
||||
window.refreshLogs = refreshLogs;
|
||||
window.copyLogs = copyLogs;
|
||||
window.openLogsFolder = openLogsFolder;
|
||||
window.openLogs = openLogs;
|
||||
|
||||
// Auto-load logs when the page becomes active
|
||||
const logsObserver = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.target.classList.contains('active') && mutation.target.id === 'logs-page') {
|
||||
loadLogs();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const logsPage = document.getElementById('logs-page');
|
||||
if (logsPage) {
|
||||
logsObserver.observe(logsPage, { attributes: true, attributeFilter: ['class'] });
|
||||
}
|
||||
});
|
||||
@@ -6,8 +6,9 @@ import './mods.js';
|
||||
import './players.js';
|
||||
import './chat.js';
|
||||
import './settings.js';
|
||||
import './logs.js';
|
||||
|
||||
window.closeDiscordNotification = function() {
|
||||
window.closeDiscordNotification = function () {
|
||||
const notification = document.getElementById('discordNotification');
|
||||
if (notification) {
|
||||
notification.classList.add('hidden');
|
||||
@@ -32,7 +33,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
const originalClose = window.closeDiscordNotification;
|
||||
window.closeDiscordNotification = function() {
|
||||
window.closeDiscordNotification = function () {
|
||||
localStorage.setItem('discordNotificationDismissed', 'true');
|
||||
originalClose();
|
||||
};
|
||||
@@ -5,6 +5,7 @@ let customJavaPath;
|
||||
let browseJavaBtn;
|
||||
let settingsPlayerName;
|
||||
let discordRPCCheck;
|
||||
let gpuPreferenceRadios;
|
||||
|
||||
// UUID Management elements
|
||||
let currentUuidDisplay;
|
||||
@@ -142,6 +143,7 @@ function showCustomConfirm(message, title = 'Confirm Action', onConfirm, onCance
|
||||
document.addEventListener('keydown', handleEscape);
|
||||
}
|
||||
|
||||
|
||||
export function initSettings() {
|
||||
setupSettingsElements();
|
||||
loadAllSettings();
|
||||
@@ -154,6 +156,7 @@ function setupSettingsElements() {
|
||||
browseJavaBtn = document.getElementById('browseJavaBtn');
|
||||
settingsPlayerName = document.getElementById('settingsPlayerName');
|
||||
discordRPCCheck = document.getElementById('discordRPCCheck');
|
||||
gpuPreferenceRadios = document.querySelectorAll('input[name="gpuPreference"]');
|
||||
|
||||
// UUID Management elements
|
||||
currentUuidDisplay = document.getElementById('currentUuid');
|
||||
@@ -226,6 +229,15 @@ function setupSettingsElements() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (gpuPreferenceRadios) {
|
||||
gpuPreferenceRadios.forEach(radio => {
|
||||
radio.addEventListener('change', async () => {
|
||||
await saveGpuPreference();
|
||||
await updateGpuLabel();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toggleCustomJava() {
|
||||
@@ -361,11 +373,85 @@ async function loadPlayerName() {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveGpuPreference() {
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.saveGpuPreference && gpuPreferenceRadios) {
|
||||
const gpuPreference = Array.from(gpuPreferenceRadios).find(radio => radio.checked)?.value || 'auto';
|
||||
await window.electronAPI.saveGpuPreference(gpuPreference);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving GPU preference:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateGpuLabel() {
|
||||
const detectionInfo = document.getElementById('gpu-detection-info');
|
||||
if (!detectionInfo) return;
|
||||
|
||||
if (gpuPreferenceRadios) {
|
||||
const checked = Array.from(gpuPreferenceRadios).find(radio => radio.checked);
|
||||
if (checked) {
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.getDetectedGpu) {
|
||||
const detected = await window.electronAPI.getDetectedGpu();
|
||||
if (checked.value === 'auto') {
|
||||
if (detected.dedicatedName) {
|
||||
detectionInfo.textContent = `dGPU detected, using ${detected.dedicatedName}`;
|
||||
} else {
|
||||
detectionInfo.textContent = `dGPU not detected, using iGPU (${detected.integratedName}) instead`;
|
||||
}
|
||||
detectionInfo.style.display = 'block';
|
||||
} else if (checked.value === 'integrated') {
|
||||
detectionInfo.textContent = `Detected: ${detected.integratedName}`;
|
||||
detectionInfo.style.display = 'block';
|
||||
} else if (checked.value === 'dedicated') {
|
||||
if (detected.dedicatedName) {
|
||||
detectionInfo.textContent = `Detected: ${detected.dedicatedName}`;
|
||||
} else {
|
||||
detectionInfo.textContent = `No dedicated GPU detected`;
|
||||
}
|
||||
detectionInfo.style.display = 'block';
|
||||
} else {
|
||||
detectionInfo.style.display = 'none';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting detected GPU:', error);
|
||||
detectionInfo.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
detectionInfo.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
detectionInfo.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGpuPreference() {
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.loadGpuPreference && gpuPreferenceRadios) {
|
||||
const savedPreference = await window.electronAPI.loadGpuPreference();
|
||||
if (savedPreference) {
|
||||
for (const radio of gpuPreferenceRadios) {
|
||||
if (radio.value === savedPreference) {
|
||||
radio.checked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
await updateGpuLabel();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading GPU preference:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAllSettings() {
|
||||
await loadCustomJavaPath();
|
||||
await loadPlayerName();
|
||||
await loadCurrentUuid();
|
||||
await loadDiscordRPC();
|
||||
await loadGpuPreference();
|
||||
}
|
||||
|
||||
async function openGameLocation() {
|
||||
|
||||
152
GUI/style.css
152
GUI/style.css
@@ -795,6 +795,96 @@ body {
|
||||
}
|
||||
|
||||
|
||||
.custom-java-hint i {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
/* Logs Page Styles */
|
||||
.logs-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 1rem 2rem 2rem;
|
||||
}
|
||||
|
||||
.logs-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.logs-title {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.logs-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.logs-action-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 6px;
|
||||
color: #d1d5db;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.logs-action-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.logs-terminal {
|
||||
flex: 1;
|
||||
background: #0d1117;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
color: #e5e7eb;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.5);
|
||||
|
||||
/* Custom scrollbar */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(147, 51, 234, 0.3) transparent;
|
||||
}
|
||||
|
||||
.log-line {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.log-line.error {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.log-line.warn {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.log-line.info {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
|
||||
.news-section {
|
||||
padding: 1rem 2rem;
|
||||
flex: 1;
|
||||
@@ -1005,6 +1095,7 @@ body {
|
||||
transform: translateY(0.5rem);
|
||||
transition: all 0.3s ease 0.1s;
|
||||
display: -webkit-box;
|
||||
line-clamp: 2;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
@@ -1888,6 +1979,7 @@ body {
|
||||
line-height: 1.5;
|
||||
margin: 0.75rem 0;
|
||||
display: -webkit-box;
|
||||
line-clamp: 3;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
@@ -2675,6 +2767,7 @@ body {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
line-clamp: 2;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
@@ -3095,6 +3188,7 @@ body {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
line-clamp: 2;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
word-break: break-word;
|
||||
@@ -3758,7 +3852,7 @@ body {
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
margin-bottom: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -4047,6 +4141,14 @@ body {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.gpu-detection-info {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-weight: 500;
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
#settings-page {
|
||||
opacity: 0;
|
||||
@@ -4078,7 +4180,53 @@ body {
|
||||
background: linear-gradient(135deg, #a855f7, #60a5fa);
|
||||
}
|
||||
|
||||
|
||||
.segmented-control {
|
||||
display: flex;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(10px);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.segmented-control input[type="radio"] {
|
||||
display: none;
|
||||
}
|
||||
.segmented-control label {
|
||||
flex: 1;
|
||||
padding: 0.75rem 1rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
color: #d1d5db;
|
||||
font-size: 0.875rem;
|
||||
background: transparent;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-weight: 500;
|
||||
}
|
||||
.segmented-control label:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
.segmented-control input[type="radio"]:checked + label {
|
||||
background: linear-gradient(135deg, #9333ea, #3b82f6);
|
||||
color: white;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.segmented-control label:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
}
|
||||
.segmented-control input[type="radio"]:checked + label:hover {
|
||||
background: linear-gradient(135deg, #7c3aed, #2563eb);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
.segmented-control:focus-within {
|
||||
border-color: rgba(147, 51, 234, 0.3);
|
||||
box-shadow: 0 0 20px rgba(147, 51, 234, 0.1);
|
||||
}
|
||||
|
||||
|
||||
#update-popup-overlay {
|
||||
|
||||
9
PKGBUILD
9
PKGBUILD
@@ -1,19 +1,20 @@
|
||||
# Maintainer: Terromur <terromuroz@proton.me>
|
||||
# Maintainer: Fazri Gading <fazrigading@gmail.com>
|
||||
pkgname=Hytale-F2P-git
|
||||
_pkgname=Hytale-F2P
|
||||
pkgver=2.0.2.r90.g21f8527
|
||||
pkgver=2.0.2b.r120.gb05aeef
|
||||
pkgrel=1
|
||||
pkgdesc="HyLauncher - unofficial Hytale Launcher for free to play gamers"
|
||||
pkgdesc="Hytale-F2P - unofficial Hytale Launcher for free to play with multiplayer support"
|
||||
arch=('x86_64')
|
||||
url="https://github.com/amiayweb/Hytale-F2P"
|
||||
license=('custom')
|
||||
makedepends=('npm')
|
||||
makedepends=('npm' 'git' 'rpm-tools' 'libxcrypt-compat')
|
||||
source=("git+$url.git" "Hytale-F2P.desktop")
|
||||
sha256sums=('SKIP' '8c78a6931fade2b0501122980dc238e042b9f6f0292b5ca74c391d7b3c1543c0')
|
||||
|
||||
pkgver() {
|
||||
cd "$_pkgname"
|
||||
printf "2.0.2.r%s.g%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
|
||||
printf "2.0.2b.r%s.g%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
|
||||
}
|
||||
|
||||
build() {
|
||||
|
||||
12
README.md
12
README.md
@@ -78,7 +78,17 @@ See [BUILD.md](BUILD.md) for comprehensive build instructions.
|
||||
|
||||
## 📋 Changelog
|
||||
|
||||
### 🆕 v2.0.2 *(Latest)*
|
||||
### 🆕 v2.0.ba *(Minor Update: Performance & Utilities)*
|
||||
[TODO] add features list here
|
||||
|
||||
### 🆕 v2.0.2a *(Minor Update)*
|
||||
- 🧑🚀 **Profiles System** — Added proper profile management: create, switch, and delete profiles. Each profile now has its own **isolated mod list**.
|
||||
- 🔒 **Mod Isolation** — Fixed ModManager so mods are **strictly scoped to the active profile**. Browsing and installing now only affects the selected profile.
|
||||
- 🚨 **Critical Path Fix** — Resolved a macOS bug where mods were being saved to a Windows path (`~/AppData/Local`) instead of `~/Library/Application Support`. Mods now save to the **correct location** and load properly in-game.
|
||||
- 🛡️ **Stability Improvements** — Added an **auto-sync step before every launch** to ensure the physical mods folder always matches the active profile.
|
||||
- 🎨 **UI Enhancements** — Added a **profile selector dropdown** and a **profile management modal**.
|
||||
|
||||
### 🆕 v2.0.2
|
||||
- 🎮 **Discord RPC Integration** - Added Discord Rich Presence with toggle in settings (enabled by default)
|
||||
- 🌐 **Cross-Platform Multiplayer** - Added multiplayer patch support for Windows, Linux, and macOS
|
||||
- 🎨 **Chat Improvements** - Simplified chat color system
|
||||
|
||||
@@ -277,6 +277,15 @@ function loadChatColor() {
|
||||
return config.chatColor || '#3498db';
|
||||
}
|
||||
|
||||
function saveGpuPreference(gpuPreference) {
|
||||
saveConfig({ gpuPreference: gpuPreference || 'auto' });
|
||||
}
|
||||
|
||||
function loadGpuPreference() {
|
||||
const config = loadConfig();
|
||||
return config.gpuPreference || 'auto';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadConfig,
|
||||
saveConfig,
|
||||
@@ -308,5 +317,8 @@ module.exports = {
|
||||
setUuidForUser,
|
||||
generateNewUuid,
|
||||
deleteUuidForUser,
|
||||
resetCurrentUserUuid
|
||||
resetCurrentUserUuid,
|
||||
// GPU Preference exports
|
||||
saveGpuPreference,
|
||||
loadGpuPreference
|
||||
};
|
||||
|
||||
@@ -20,14 +20,16 @@ const {
|
||||
getUuidForUser,
|
||||
isFirstLaunch,
|
||||
markAsLaunched,
|
||||
CONFIG_FILE,
|
||||
// UUID Management
|
||||
getCurrentUuid,
|
||||
getAllUuidMappings,
|
||||
setUuidForUser,
|
||||
generateNewUuid,
|
||||
deleteUuidForUser,
|
||||
resetCurrentUserUuid
|
||||
resetCurrentUserUuid,
|
||||
// GPU Preference
|
||||
saveGpuPreference,
|
||||
loadGpuPreference
|
||||
} = require('./core/config');
|
||||
|
||||
const { getResolvedAppDir, getModsPath } = require('./core/paths');
|
||||
@@ -38,7 +40,8 @@ const {
|
||||
installGame,
|
||||
uninstallGame,
|
||||
updateGameFiles,
|
||||
checkExistingGameInstallation
|
||||
checkExistingGameInstallation,
|
||||
repairGame
|
||||
} = require('./managers/gameManager');
|
||||
|
||||
const {
|
||||
@@ -77,6 +80,9 @@ const {
|
||||
handleFirstLaunchCheck
|
||||
} = require('./services/firstLaunch');
|
||||
|
||||
// Utils
|
||||
const { detectGpu } = require('./utils/platformUtils');
|
||||
|
||||
// Re-export all functions to maintain backward compatibility
|
||||
module.exports = {
|
||||
// Game launch functions
|
||||
@@ -88,6 +94,7 @@ module.exports = {
|
||||
isGameInstalled,
|
||||
uninstallGame,
|
||||
updateGameFiles,
|
||||
repairGame,
|
||||
|
||||
// User configuration functions
|
||||
saveUsername,
|
||||
@@ -111,6 +118,11 @@ module.exports = {
|
||||
saveDiscordRPC,
|
||||
loadDiscordRPC,
|
||||
|
||||
// GPU Preference functions
|
||||
saveGpuPreference,
|
||||
loadGpuPreference,
|
||||
detectGpu,
|
||||
|
||||
// Version functions
|
||||
getInstalledClientVersion,
|
||||
getLatestClientVersion,
|
||||
|
||||
@@ -6,7 +6,7 @@ const { promisify } = require('util');
|
||||
const { spawn } = require('child_process');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const { getResolvedAppDir, findClientPath } = require('../core/paths');
|
||||
const { setupWaylandEnvironment } = require('../utils/platformUtils');
|
||||
const { setupWaylandEnvironment, setupGpuEnvironment } = require('../utils/platformUtils');
|
||||
const { saveUsername, saveInstallPath, loadJavaPath, getUuidForUser, getAuthServerUrl, getAuthDomain } = require('../core/config');
|
||||
const { resolveJavaPath, getJavaExec, getBundledJavaPath, detectSystemJava, JAVA_EXECUTABLE } = require('./javaManager');
|
||||
const { getInstalledClientVersion, getLatestClientVersion } = require('../services/versionManager');
|
||||
@@ -101,7 +101,7 @@ function generateLocalTokens(uuid, name) {
|
||||
};
|
||||
}
|
||||
|
||||
async function launchGame(playerName = 'Player', progressCallback, javaPathOverride, installPathOverride) {
|
||||
async function launchGame(playerName = 'Player', progressCallback, javaPathOverride, installPathOverride, gpuPreference = 'auto') {
|
||||
const customAppDir = getResolvedAppDir(installPathOverride);
|
||||
const customGameDir = path.join(customAppDir, 'release', 'package', 'game', 'latest');
|
||||
const customJreDir = path.join(customAppDir, 'release', 'package', 'jre', 'latest');
|
||||
@@ -281,6 +281,9 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
|
||||
const waylandEnv = setupWaylandEnvironment();
|
||||
Object.assign(env, waylandEnv);
|
||||
|
||||
const gpuEnv = setupGpuEnvironment(gpuPreference);
|
||||
Object.assign(env, gpuEnv);
|
||||
|
||||
try {
|
||||
let spawnOptions = {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
@@ -352,7 +355,7 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
|
||||
}
|
||||
}
|
||||
|
||||
async function launchGameWithVersionCheck(playerName = 'Player', progressCallback, javaPathOverride, installPathOverride) {
|
||||
async function launchGameWithVersionCheck(playerName = 'Player', progressCallback, javaPathOverride, installPathOverride, gpuPreference = 'auto') {
|
||||
try {
|
||||
if (progressCallback) {
|
||||
progressCallback('Checking for updates...', 0, null, null, null);
|
||||
@@ -403,7 +406,7 @@ async function launchGameWithVersionCheck(playerName = 'Player', progressCallbac
|
||||
progressCallback('Launching game...', 80, null, null, null);
|
||||
}
|
||||
|
||||
return await launchGame(playerName, progressCallback, javaPathOverride, installPathOverride);
|
||||
return await launchGame(playerName, progressCallback, javaPathOverride, installPathOverride, gpuPreference);
|
||||
} catch (error) {
|
||||
console.error('Error in version check and launch:', error);
|
||||
if (progressCallback) {
|
||||
|
||||
@@ -387,6 +387,102 @@ function checkExistingGameInstallation() {
|
||||
}
|
||||
}
|
||||
|
||||
async function repairGame(progressCallback) {
|
||||
const appDir = getResolvedAppDir();
|
||||
const gameDir = path.join(appDir, 'release', 'package', 'game', 'latest');
|
||||
|
||||
// Check if game exists
|
||||
if (!fs.existsSync(gameDir)) {
|
||||
throw new Error('Game directory not found. Cannot repair.');
|
||||
}
|
||||
|
||||
// Locate UserData
|
||||
const userDataPath = findUserDataRecursive(gameDir);
|
||||
let userDataBackup = null;
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback('Backing up user data...', 10, null, null, null);
|
||||
}
|
||||
|
||||
// Backup UserData
|
||||
if (userDataPath && fs.existsSync(userDataPath)) {
|
||||
userDataBackup = path.join(appDir, 'UserData_backup_repair_' + Date.now());
|
||||
console.log(`Backing up UserData during repair from ${userDataPath} to ${userDataBackup}`);
|
||||
|
||||
// Copy function
|
||||
function copyRecursive(src, dest) {
|
||||
const stat = fs.statSync(src);
|
||||
if (stat.isDirectory()) {
|
||||
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
||||
fs.readdirSync(src).forEach(child => copyRecursive(path.join(src, child), path.join(dest, child)));
|
||||
} else {
|
||||
fs.copyFileSync(src, dest);
|
||||
}
|
||||
}
|
||||
|
||||
copyRecursive(userDataPath, userDataBackup);
|
||||
}
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback('Removing old game files...', 30, null, null, null);
|
||||
}
|
||||
|
||||
// Delete Game and Cache Directory
|
||||
console.log('Removing corrupted game files...');
|
||||
fs.rmSync(gameDir, { recursive: true, force: true });
|
||||
|
||||
const cacheDir = path.join(appDir, 'cache');
|
||||
if (fs.existsSync(cacheDir)) {
|
||||
console.log('Clearing cache directory...');
|
||||
fs.rmSync(cacheDir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
console.log('Reinstalling game files...');
|
||||
|
||||
// Passing null/undefined for overrides to use defaults/saved configs
|
||||
// installGame calls progressCallback internally
|
||||
await installGame('Player', progressCallback);
|
||||
|
||||
// Restore UserData
|
||||
if (userDataBackup && fs.existsSync(userDataBackup)) {
|
||||
if (progressCallback) {
|
||||
progressCallback('Restoring user data...', 90, null, null, null);
|
||||
}
|
||||
|
||||
// installGame creates: path.join(customGameDir, 'Client', 'UserData')
|
||||
const newGameDir = path.join(appDir, 'release', 'package', 'game', 'latest');
|
||||
const newUserDataPath = path.join(newGameDir, 'Client', 'UserData');
|
||||
|
||||
if (!fs.existsSync(newUserDataPath)) {
|
||||
fs.mkdirSync(newUserDataPath, { recursive: true });
|
||||
}
|
||||
|
||||
console.log(`Restoring UserData to ${newUserDataPath}`);
|
||||
|
||||
function copyRecursive(src, dest) {
|
||||
const stat = fs.statSync(src);
|
||||
if (stat.isDirectory()) {
|
||||
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
||||
fs.readdirSync(src).forEach(child => copyRecursive(path.join(src, child), path.join(dest, child)));
|
||||
} else {
|
||||
fs.copyFileSync(src, dest);
|
||||
}
|
||||
}
|
||||
|
||||
copyRecursive(userDataBackup, newUserDataPath);
|
||||
|
||||
// Cleanup Backup
|
||||
console.log('Cleaning up repair backup...');
|
||||
fs.rmSync(userDataBackup, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback('Repair completed successfully!', 100, null, null, null);
|
||||
}
|
||||
|
||||
return { success: true, repaired: true };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
downloadPWR,
|
||||
applyPWR,
|
||||
@@ -394,5 +490,9 @@ module.exports = {
|
||||
isGameInstalled,
|
||||
installGame,
|
||||
uninstallGame,
|
||||
checkExistingGameInstallation
|
||||
isGameInstalled,
|
||||
installGame,
|
||||
uninstallGame,
|
||||
checkExistingGameInstallation,
|
||||
repairGame
|
||||
};
|
||||
|
||||
@@ -138,6 +138,7 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 3) {
|
||||
|
||||
if (!isRetryable || attempt === maxRetries - 1) {
|
||||
console.error(`Non-retryable error or max retries reached: ${error.code || error.message}`);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,9 +65,100 @@ function setupWaylandEnvironment() {
|
||||
return envVars;
|
||||
}
|
||||
|
||||
function detectGpu() {
|
||||
if (process.platform !== 'linux') {
|
||||
return { mode: 'integrated', vendor: 'intel', integratedName: 'Unknown', dedicatedName: null };
|
||||
}
|
||||
|
||||
try {
|
||||
const output = execSync('lspci -nn | grep \'VGA\\|3D\'', { encoding: 'utf8' });
|
||||
// console.log('GPU detection raw output:', output);
|
||||
const lines = output.split('\n').filter(line => line.trim());
|
||||
// console.log('GPU detection parsed lines:', lines);
|
||||
|
||||
let integratedName = null;
|
||||
let dedicatedName = null;
|
||||
let hasNvidia = false;
|
||||
let hasAmd = false;
|
||||
|
||||
for (const line of lines) {
|
||||
// console.log('Checking line:', line);
|
||||
if (line.includes('VGA') || line.includes('3D')) {
|
||||
// console.log('Line contains VGA or 3D');
|
||||
|
||||
const match = line.match(/\[([^\]]+)\]/g);
|
||||
let modelName = null;
|
||||
if (match && match.length >= 2) {
|
||||
modelName = match[1].slice(1, -1);
|
||||
}
|
||||
|
||||
if (line.includes('10de:') || line.toLowerCase().includes('nvidia')) {
|
||||
hasNvidia = true;
|
||||
dedicatedName = "NVIDIA " + modelName || 'NVIDIA GPU';
|
||||
console.log('Detected NVIDIA GPU:', dedicatedName);
|
||||
} else if (line.includes('1002:') || line.toLowerCase().includes('amd') || line.toLowerCase().includes('radeon')) {
|
||||
hasAmd = true;
|
||||
dedicatedName = "AMD " + modelName || 'AMD GPU';
|
||||
console.log('Detected AMD GPU:', dedicatedName);
|
||||
} else if (line.includes('8086:') || line.toLowerCase().includes('intel')) {
|
||||
integratedName = "Intel " + modelName || 'Intel GPU';
|
||||
console.log('Detected Intel GPU:', integratedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// console.log('hasNvidia:', hasNvidia, 'hasAmd:', hasAmd, 'integratedName:', integratedName, 'dedicatedName:', dedicatedName);
|
||||
|
||||
if (hasNvidia) {
|
||||
return { mode: 'dedicated', vendor: 'nvidia', integratedName: integratedName || 'Intel GPU', dedicatedName };
|
||||
} else if (hasAmd) {
|
||||
return { mode: 'dedicated', vendor: 'amd', integratedName: integratedName || 'Intel GPU', dedicatedName };
|
||||
} else {
|
||||
return { mode: 'integrated', vendor: 'intel', integratedName: integratedName || 'Intel GPU', dedicatedName: null };
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('GPU detection failed, falling back to integrated:', error.message);
|
||||
return { mode: 'integrated', vendor: 'intel', integratedName: 'Unknown', dedicatedName: null };
|
||||
}
|
||||
}
|
||||
|
||||
function setupGpuEnvironment(gpuPreference) {
|
||||
if (process.platform !== 'linux') {
|
||||
return {};
|
||||
}
|
||||
|
||||
let finalPreference = gpuPreference;
|
||||
let detected = detectGpu();
|
||||
|
||||
if (gpuPreference === 'auto') {
|
||||
finalPreference = detected.mode;
|
||||
console.log(`Auto-detected GPU: ${detected.vendor} (${detected.mode})`);
|
||||
}
|
||||
|
||||
console.log('Preferred GPU set to:', finalPreference);
|
||||
|
||||
const envVars = {};
|
||||
|
||||
if (finalPreference === 'dedicated') {
|
||||
envVars.DRI_PRIME = '1';
|
||||
if (detected.vendor === 'nvidia') {
|
||||
envVars.__NV_PRIME_RENDER_OFFLOAD = '1';
|
||||
envVars.__GLX_VENDOR_LIBRARY_NAME = 'nvidia';
|
||||
envVars.__GL_SHADER_DISK_CACHE = '1';
|
||||
envVars.__GL_SHADER_DISK_CACHE_PATH = '/tmp';
|
||||
}
|
||||
console.log('GPU environment variables:', envVars);
|
||||
} else {
|
||||
console.log('Using integrated GPU, no environment variables set');
|
||||
}
|
||||
return envVars;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getOS,
|
||||
getArch,
|
||||
isWaylandSession,
|
||||
setupWaylandEnvironment
|
||||
setupWaylandEnvironment,
|
||||
detectGpu,
|
||||
setupGpuEnvironment
|
||||
};
|
||||
|
||||
98
main.js
98
main.js
@@ -1,7 +1,7 @@
|
||||
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveChatUsername, loadChatUsername, saveChatColor, loadChatColor, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, isGameInstalled, uninstallGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched } = require('./backend/launcher');
|
||||
const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveChatUsername, loadChatUsername, saveChatColor, loadChatColor, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched } = require('./backend/launcher');
|
||||
const UpdateManager = require('./backend/updateManager');
|
||||
const logger = require('./backend/logger');
|
||||
const profileManager = require('./backend/managers/profileManager');
|
||||
@@ -120,8 +120,6 @@ function createWindow() {
|
||||
mainWindow.webContents.send('show-update-popup', updateInfo);
|
||||
}
|
||||
}, 3000);
|
||||
//mainWindow.webContents.openDevTools();
|
||||
|
||||
|
||||
mainWindow.webContents.on('devtools-opened', () => {
|
||||
mainWindow.webContents.closeDevTools();
|
||||
@@ -161,6 +159,19 @@ app.whenReady().then(async () => {
|
||||
console.log('Node.js version:', process.versions.node);
|
||||
console.log('Log directory:', logger.getLogDirectory());
|
||||
|
||||
try {
|
||||
const { loadGpuPreference, detectGpu } = require('./backend/launcher');
|
||||
const savedPreference = loadGpuPreference();
|
||||
if (savedPreference === 'auto') {
|
||||
global.detectedGpu = detectGpu(); // if 'auto' selected = preload GPU detection
|
||||
console.log('GPU auto-detection completed on startup:', global.detectedGpu);
|
||||
} else {
|
||||
console.log('GPU preference is manual, skipping auto-detection');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to preload GPU detection:', error.message);
|
||||
global.detectedGpu = { mode: 'integrated', vendor: 'intel' };
|
||||
}
|
||||
|
||||
|
||||
// Initialize Profile Manager (runs migration if needed)
|
||||
@@ -282,7 +293,7 @@ app.on('window-all-closed', () => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath) => {
|
||||
ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, gpuPreference) => {
|
||||
try {
|
||||
const progressCallback = (message, percent, speed, downloaded, total) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
@@ -297,7 +308,7 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath) =
|
||||
}
|
||||
};
|
||||
|
||||
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath);
|
||||
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
@@ -456,13 +467,35 @@ ipcMain.handle('is-game-installed', async () => {
|
||||
ipcMain.handle('uninstall-game', async () => {
|
||||
try {
|
||||
await uninstallGame();
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Uninstall error:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('repair-game', async () => {
|
||||
try {
|
||||
const progressCallback = (message, percent, speed, downloaded, total) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
const data = {
|
||||
message: message || null,
|
||||
percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null,
|
||||
speed: speed !== null && speed !== undefined ? speed : null,
|
||||
downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null,
|
||||
total: total !== null && total !== undefined ? total : null
|
||||
};
|
||||
mainWindow.webContents.send('progress-update', data);
|
||||
}
|
||||
};
|
||||
|
||||
const result = await repairGame(progressCallback);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Repair error:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-hytale-news', async () => {
|
||||
try {
|
||||
const news = await getHytaleNews();
|
||||
@@ -710,6 +743,35 @@ ipcMain.handle('get-update-info', async () => {
|
||||
return updateManager.getUpdateInfo();
|
||||
});
|
||||
|
||||
ipcMain.handle('get-gpu-info', () => {
|
||||
try {
|
||||
return app.getGPUInfo('complete');
|
||||
} catch (error) {
|
||||
console.error('Error getting GPU info:', error);
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('save-gpu-preference', (event, gpuPreference) => {
|
||||
const { saveGpuPreference } = require('./backend/launcher');
|
||||
saveGpuPreference(gpuPreference);
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
ipcMain.handle('load-gpu-preference', () => {
|
||||
const { loadGpuPreference } = require('./backend/launcher');
|
||||
return loadGpuPreference();
|
||||
});
|
||||
|
||||
ipcMain.handle('get-detected-gpu', () => {
|
||||
if (global.detectedGpu) {
|
||||
return global.detectedGpu;
|
||||
}
|
||||
const { detectGpu } = require('./backend/launcher');
|
||||
global.detectedGpu = detectGpu();
|
||||
return global.detectedGpu;
|
||||
});
|
||||
|
||||
ipcMain.handle('window-close', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.close();
|
||||
@@ -808,7 +870,14 @@ ipcMain.handle('get-recent-logs', async (event, maxLines = 100) => {
|
||||
const content = fs.readFileSync(latestLogFile, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
return lines.slice(-maxLines).join('\n');
|
||||
let result = lines.slice(-maxLines).join('\n');
|
||||
|
||||
if (lines.length > maxLines) {
|
||||
const truncatedMsg = `\n--- ⚠️ LOG TRUNCATED: Showing last ${maxLines} lines of ${lines.length}. Open Logs Folder for full history ---\n\n`;
|
||||
return result + truncatedMsg;
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error reading logs:', error);
|
||||
return null;
|
||||
@@ -816,6 +885,21 @@ ipcMain.handle('get-recent-logs', async (event, maxLines = 100) => {
|
||||
});
|
||||
|
||||
|
||||
|
||||
ipcMain.handle('open-logs-folder', async () => {
|
||||
try {
|
||||
const logDir = logger.getLogDirectory();
|
||||
if (logDir && fs.existsSync(logDir)) {
|
||||
await shell.openPath(logDir);
|
||||
return { success: true };
|
||||
}
|
||||
return { success: false, error: 'Logs directory not found' };
|
||||
} catch (error) {
|
||||
console.error('Error opening logs folder:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// Profile Management IPC
|
||||
ipcMain.handle('profile-create', async (event, name) => {
|
||||
try {
|
||||
|
||||
48
package-lock.json
generated
48
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "hytale-f2p-launcher",
|
||||
"version": "2.0.1",
|
||||
"name": "hytale-f2p-launcherv2",
|
||||
"version": "2.0.2b",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hytale-f2p-launcher",
|
||||
"version": "2.0.1",
|
||||
"name": "hytale-f2p-launcherv2",
|
||||
"version": "2.0.2b",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.5.10",
|
||||
@@ -3459,15 +3459,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
@@ -4504,6 +4506,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/temp": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz",
|
||||
@@ -4568,20 +4582,6 @@
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/temp/node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-async-pool": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz",
|
||||
|
||||
66
package.json
66
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hytale-f2p-launcherv2",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.2b",
|
||||
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
|
||||
"homepage": "https://github.com/amiayweb/Hytale-F2P",
|
||||
"main": "main.js",
|
||||
@@ -23,6 +23,17 @@
|
||||
"auto-update",
|
||||
"mod-manager",
|
||||
"chat"
|
||||
],
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Terromur",
|
||||
"url": "https://github.com/Terromur"
|
||||
},
|
||||
{
|
||||
"name": "Fari Gading",
|
||||
"email": "fazrigading@gmail.com",
|
||||
"url": "https://github.com/fazrigading"
|
||||
}
|
||||
],
|
||||
"author": {
|
||||
"name": "AMIAY",
|
||||
@@ -45,7 +56,8 @@
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.hytalef2p.launcher",
|
||||
"productName": "Hytale F2P",
|
||||
"productName": "Hytale F2P Launcher",
|
||||
"executableName": "hytale-f2p-launcherv2",
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
},
|
||||
@@ -58,54 +70,25 @@
|
||||
],
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "portable",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
{ "target": "nsis", "arch": ["x64", "arm64"] },
|
||||
{ "target": "portable", "arch": ["x64"] }
|
||||
],
|
||||
"icon": "icon.ico"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "deb",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
{ "target": "AppImage", "arch": ["x64", "arm64"] },
|
||||
{ "target": "deb", "arch": ["x64", "arm64"] },
|
||||
{ "target": "rpm", "arch": ["x64", "arm64"] },
|
||||
{ "target": "pacman", "arch": ["x64", "arm64"] }
|
||||
],
|
||||
"icon": "build/icon.png",
|
||||
"category": "Game",
|
||||
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support"
|
||||
"category": "Game"
|
||||
},
|
||||
"mac": {
|
||||
"target": [
|
||||
{
|
||||
"target": "dmg",
|
||||
"arch": [
|
||||
"universal"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
"universal"
|
||||
]
|
||||
}
|
||||
{ "target": "dmg", "arch": ["universal"] },
|
||||
{ "target": "zip", "arch": ["universal"] }
|
||||
],
|
||||
"icon": "build/icon.icns",
|
||||
"category": "public.app-category.games"
|
||||
@@ -118,6 +101,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
launchGame: (playerName, javaPath, installPath) => ipcRenderer.invoke('launch-game', playerName, javaPath, installPath),
|
||||
launchGame: (playerName, javaPath, installPath, gpuPreference) => ipcRenderer.invoke('launch-game', playerName, javaPath, installPath, gpuPreference),
|
||||
installGame: (playerName, javaPath, installPath) => ipcRenderer.invoke('install-game', playerName, javaPath, installPath),
|
||||
closeWindow: () => ipcRenderer.invoke('window-close'),
|
||||
minimizeWindow: () => ipcRenderer.invoke('window-minimize'),
|
||||
@@ -21,6 +21,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
browseJavaPath: () => ipcRenderer.invoke('browse-java-path'),
|
||||
isGameInstalled: () => ipcRenderer.invoke('is-game-installed'),
|
||||
uninstallGame: () => ipcRenderer.invoke('uninstall-game'),
|
||||
repairGame: () => ipcRenderer.invoke('repair-game'),
|
||||
getHytaleNews: () => ipcRenderer.invoke('get-hytale-news'),
|
||||
openExternal: (url) => ipcRenderer.invoke('open-external', url),
|
||||
openExternalLink: (url) => ipcRenderer.invoke('openExternalLink', url),
|
||||
@@ -49,6 +50,11 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
ipcRenderer.on('show-update-popup', (event, data) => callback(data));
|
||||
},
|
||||
|
||||
getGpuInfo: () => ipcRenderer.invoke('get-gpu-info'),
|
||||
saveGpuPreference: (gpuPreference) => ipcRenderer.invoke('save-gpu-preference', gpuPreference),
|
||||
loadGpuPreference: () => ipcRenderer.invoke('load-gpu-preference'),
|
||||
getDetectedGpu: () => ipcRenderer.invoke('get-detected-gpu'),
|
||||
|
||||
acceptFirstLaunchUpdate: (existingGame) => ipcRenderer.invoke('accept-first-launch-update', existingGame),
|
||||
markAsLaunched: () => ipcRenderer.invoke('mark-as-launched'),
|
||||
onFirstLaunchUpdate: (callback) => {
|
||||
@@ -65,6 +71,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
},
|
||||
|
||||
getLogDirectory: () => ipcRenderer.invoke('get-log-directory'),
|
||||
openLogsFolder: () => ipcRenderer.invoke('open-logs-folder'),
|
||||
getRecentLogs: (maxLines) => ipcRenderer.invoke('get-recent-logs', maxLines),
|
||||
|
||||
// UUID Management methods
|
||||
|
||||
Reference in New Issue
Block a user