mirror of
https://git.sanhost.net/sanasol/hytale-f2p
synced 2026-02-26 10:31:47 -03:00
Compare commits
6 Commits
v2.4.0
...
5170f453ea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5170f453ea | ||
|
|
db3b2fc966 | ||
|
|
2f5820e850 | ||
|
|
4abb455e0f | ||
|
|
d5828463f9 | ||
|
|
0d15659dc0 |
@@ -430,8 +430,70 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3 class="settings-section-title">
|
||||||
|
<i class="fas fa-scroll"></i>
|
||||||
|
<span data-i18n="settings.wrapperConfig">Java Wrapper Configuration</span>
|
||||||
|
</h3>
|
||||||
|
<p class="settings-hint" style="margin-bottom: 12px;">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
<span data-i18n="settings.wrapperConfigHint">Configure how the Java wrapper handles JVM flags and arguments at launch time.</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Strip Flags -->
|
||||||
|
<label class="settings-label" style="margin-bottom: 6px;">
|
||||||
|
<span data-i18n="settings.wrapperStripFlags">JVM Flags to Remove</span>
|
||||||
|
</label>
|
||||||
|
<div id="wrapperStripFlagsList" class="wrapper-items-list"></div>
|
||||||
|
<div style="display: flex; gap: 6px; margin-top: 6px;">
|
||||||
|
<input type="text" id="wrapperAddFlagInput" class="settings-input" style="flex:1;"
|
||||||
|
data-i18n-placeholder="settings.wrapperAddFlagPlaceholder" placeholder="e.g. -XX:+SomeFlag" spellcheck="false">
|
||||||
|
<button id="wrapperAddFlagBtn" class="settings-browse-btn">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
<span data-i18n="settings.wrapperAdd">Add</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Inject Args -->
|
||||||
|
<label class="settings-label" style="margin-top: 16px; margin-bottom: 6px;">
|
||||||
|
<span data-i18n="settings.wrapperInjectArgs">Arguments to Inject</span>
|
||||||
|
</label>
|
||||||
|
<div id="wrapperInjectArgsList" class="wrapper-items-list"></div>
|
||||||
|
<div style="display: flex; gap: 6px; margin-top: 6px;">
|
||||||
|
<input type="text" id="wrapperAddArgInput" class="settings-input" style="flex:1;"
|
||||||
|
data-i18n-placeholder="settings.wrapperAddArgPlaceholder" placeholder="e.g. --some-flag" spellcheck="false">
|
||||||
|
<select id="wrapperAddArgCondition" class="wrapper-condition-select">
|
||||||
|
<option value="server" data-i18n="settings.wrapperConditionServer">Server Only</option>
|
||||||
|
<option value="always" data-i18n="settings.wrapperConditionAlways">Always</option>
|
||||||
|
</select>
|
||||||
|
<button id="wrapperAddArgBtn" class="settings-browse-btn">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
<span data-i18n="settings.wrapperAdd">Add</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Restore Defaults -->
|
||||||
|
<div style="margin-top: 12px;">
|
||||||
|
<button id="wrapperRestoreDefaultsBtn" class="settings-browse-btn" style="background: rgba(239, 68, 68, 0.2); border-color: rgba(239, 68, 68, 0.3);">
|
||||||
|
<i class="fas fa-undo"></i>
|
||||||
|
<span data-i18n="settings.wrapperRestoreDefaults">Restore Defaults</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Script Preview (collapsible) -->
|
||||||
|
<div style="margin-top: 12px;">
|
||||||
|
<button id="wrapperPreviewToggle" class="wrapper-preview-toggle">
|
||||||
|
<i class="fas fa-chevron-right" id="wrapperPreviewChevron"></i>
|
||||||
|
<span data-i18n="settings.wrapperAdvancedPreview">Advanced: Script Preview</span>
|
||||||
|
</button>
|
||||||
|
<div id="wrapperPreviewContainer" style="display: none; margin-top: 8px;">
|
||||||
|
<pre id="wrapperPreviewContent" class="wrapper-preview-content"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-column">
|
<div class="settings-column">
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3 class="settings-section-title">
|
<h3 class="settings-section-title">
|
||||||
@@ -601,7 +663,11 @@
|
|||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mods-modal-body">
|
<div class="mods-modal-body" style="padding-top: 0;">
|
||||||
|
<div class="mods-search-container" style="margin: 1.5rem; margin-bottom: 1rem;">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
<input type="text" id="myModsSearch" placeholder="Search installed mods..." class="mods-search" />
|
||||||
|
</div>
|
||||||
<div id="installedModsList" class="installed-mods-list">
|
<div id="installedModsList" class="installed-mods-list">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -805,6 +871,25 @@
|
|||||||
<script src="js/featured.js"></script>
|
<script src="js/featured.js"></script>
|
||||||
<script type="module" src="js/settings.js"></script>
|
<script type="module" src="js/settings.js"></script>
|
||||||
<script type="module" src="js/update.js"></script>
|
<script type="module" src="js/update.js"></script>
|
||||||
|
|
||||||
|
<!-- Version Selection Modal (Isolated Container) -->
|
||||||
|
<div id="versionSelectModal" class="modal-overlay" style="display: none; position: fixed; inset: 0; z-index: 9999; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(5px); align-items: center; justify-content: center;">
|
||||||
|
<div class="glass-panel" style="width: 100%; max-width: 600px; max-height: 80vh; display: flex; flex-direction: column; border-radius: 12px; overflow: hidden; margin: 20px;">
|
||||||
|
<div class="modal-header" style="display: flex; justify-content: space-between; align-items: center; padding: 1.5rem; border-bottom: 1px solid rgba(255, 255, 255, 0.1);">
|
||||||
|
<h3 style="margin: 0; font-size: 1.25rem;">Select Version</h3>
|
||||||
|
<button id="closeVersionModal" class="modal-close" style="background: none; border: none; color: #a0a0a0; font-size: 1.25rem; cursor: pointer;"><i class="fas fa-times"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" style="padding: 1.5rem; overflow-y: auto;">
|
||||||
|
<div id="versionList" class="version-list-container">
|
||||||
|
<div class="loading-versions" style="display: flex; flex-direction: column; align-items: center; gap: 1rem; color: #a0a0a0;">
|
||||||
|
<i class="fas fa-spinner fa-spin fa-2x"></i>
|
||||||
|
<span>Loading versions...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- updater.js disabled - using update.js instead which has skip button and macOS handling -->
|
<!-- updater.js disabled - using update.js instead which has skip button and macOS handling -->
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
184
GUI/js/mods.js
184
GUI/js/mods.js
@@ -49,6 +49,18 @@ function setupModsEventListeners() {
|
|||||||
closeModalBtn.addEventListener('click', closeMyModsModal);
|
closeModalBtn.addEventListener('click', closeMyModsModal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const myModsSearchInput = document.getElementById('myModsSearch');
|
||||||
|
if (myModsSearchInput) {
|
||||||
|
let myModsSearchTimeout;
|
||||||
|
myModsSearchInput.addEventListener('input', (e) => {
|
||||||
|
const query = e.target.value.toLowerCase().trim();
|
||||||
|
clearTimeout(myModsSearchTimeout);
|
||||||
|
myModsSearchTimeout = setTimeout(() => {
|
||||||
|
filterInstalledMods(query);
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const modal = document.getElementById('myModsModal');
|
const modal = document.getElementById('myModsModal');
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.addEventListener('click', (e) => {
|
modal.addEventListener('click', (e) => {
|
||||||
@@ -78,12 +90,30 @@ function setupModsEventListeners() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const browseContainer = document.getElementById('browseModsList');
|
||||||
|
if (browseContainer) {
|
||||||
|
browseContainer.addEventListener('click', (e) => {
|
||||||
|
const installBtn = e.target.closest('[data-install-mod-id]');
|
||||||
|
if (installBtn) {
|
||||||
|
const modId = installBtn.getAttribute('data-install-mod-id');
|
||||||
|
const mod = browseMods.find(m => m.id == modId);
|
||||||
|
if (mod) {
|
||||||
|
openVersionSelectModal(mod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openMyModsModal() {
|
function openMyModsModal() {
|
||||||
const modal = document.getElementById('myModsModal');
|
const modal = document.getElementById('myModsModal');
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.classList.add('active');
|
modal.classList.add('active');
|
||||||
|
const searchInput = document.getElementById('myModsSearch');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.value = '';
|
||||||
|
}
|
||||||
loadInstalledMods();
|
loadInstalledMods();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,6 +122,10 @@ function closeMyModsModal() {
|
|||||||
const modal = document.getElementById('myModsModal');
|
const modal = document.getElementById('myModsModal');
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.classList.remove('active');
|
modal.classList.remove('active');
|
||||||
|
const searchInput = document.getElementById('myModsSearch');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.value = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,19 +147,39 @@ async function loadInstalledMods() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function filterInstalledMods(query) {
|
||||||
|
if (!query || query === '') {
|
||||||
|
displayInstalledMods(installedMods);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filtered = installedMods.filter(mod => {
|
||||||
|
const nameMatch = mod.name?.toLowerCase().includes(query);
|
||||||
|
const fileNameMatch = mod.fileName?.toLowerCase().includes(query);
|
||||||
|
const descriptionMatch = mod.description?.toLowerCase().includes(query);
|
||||||
|
const authorMatch = mod.author?.toLowerCase().includes(query);
|
||||||
|
return nameMatch || fileNameMatch || descriptionMatch || authorMatch;
|
||||||
|
});
|
||||||
|
|
||||||
|
displayInstalledMods(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
function displayInstalledMods(mods) {
|
function displayInstalledMods(mods) {
|
||||||
const modsContainer = document.getElementById('installedModsList');
|
const modsContainer = document.getElementById('installedModsList');
|
||||||
if (!modsContainer) return;
|
if (!modsContainer) return;
|
||||||
|
|
||||||
if (mods.length === 0) {
|
if (mods.length === 0) {
|
||||||
|
const searchInput = document.getElementById('myModsSearch');
|
||||||
|
const isSearching = searchInput && searchInput.value.trim() !== '';
|
||||||
|
|
||||||
modsContainer.innerHTML = `
|
modsContainer.innerHTML = `
|
||||||
<div class=\"empty-installed-mods\">
|
<div class=\"empty-installed-mods\">
|
||||||
<i class=\"fas fa-box-open\"></i>
|
<i class=\"fas fa-${isSearching ? 'search' : 'box-open'}\"></i>
|
||||||
<h4 data-i18n="mods.noModsInstalled">No Mods Installed</h4>
|
<h4 data-i18n="${isSearching ? 'mods.noModsFound' : 'mods.noModsInstalled'}">${isSearching ? 'No Mods Found' : 'No Mods Installed'}</h4>
|
||||||
<p data-i18n="mods.noModsInstalledDesc">Add mods from CurseForge or import local files</p>
|
<p data-i18n="${isSearching ? 'mods.noModsFoundDesc' : 'mods.noModsInstalledDesc'}">${isSearching ? 'Try a different search term' : 'Add mods from CurseForge or import local files'}</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
if (window.i18n) {
|
if (window.i18n && !isSearching) {
|
||||||
const container = modsContainer.querySelector('.empty-installed-mods');
|
const container = modsContainer.querySelector('.empty-installed-mods');
|
||||||
container.querySelector('h4').textContent = window.i18n.t('mods.noModsInstalled');
|
container.querySelector('h4').textContent = window.i18n.t('mods.noModsInstalled');
|
||||||
container.querySelector('p').textContent = window.i18n.t('mods.noModsInstalledDesc');
|
container.querySelector('p').textContent = window.i18n.t('mods.noModsInstalledDesc');
|
||||||
@@ -165,7 +219,7 @@ function createInstalledModCard(mod) {
|
|||||||
<div class="installed-mod-info">
|
<div class="installed-mod-info">
|
||||||
<div class="installed-mod-header">
|
<div class="installed-mod-header">
|
||||||
<h4 class="installed-mod-name">${mod.name}</h4>
|
<h4 class="installed-mod-name">${mod.name}</h4>
|
||||||
<span class="installed-mod-version">v${mod.version}</span>
|
<span class="installed-mod-version">${mod.fileName || 'v' + mod.version}</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="installed-mod-description">${mod.description || (window.i18n ? window.i18n.t('mods.noDescription') : 'No description available')}</p>
|
<p class="installed-mod-description">${mod.description || (window.i18n ? window.i18n.t('mods.noDescription') : 'No description available')}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -295,13 +349,6 @@ function displayBrowseMods(mods) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
browseContainer.innerHTML = mods.map(mod => createBrowseModCard(mod)).join('');
|
browseContainer.innerHTML = mods.map(mod => createBrowseModCard(mod)).join('');
|
||||||
|
|
||||||
mods.forEach(mod => {
|
|
||||||
const installBtn = document.getElementById(`install-${mod.id}`);
|
|
||||||
if (installBtn) {
|
|
||||||
installBtn.addEventListener('click', () => downloadAndInstallMod(mod));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBrowseModCard(mod) {
|
function createBrowseModCard(mod) {
|
||||||
@@ -350,12 +397,12 @@ function createBrowseModCard(mod) {
|
|||||||
${window.i18n ? window.i18n.t('mods.view') : 'VIEW'}
|
${window.i18n ? window.i18n.t('mods.view') : 'VIEW'}
|
||||||
</button>
|
</button>
|
||||||
${!isInstalled ?
|
${!isInstalled ?
|
||||||
`<button id="install-${mod.id}" class="mod-btn-toggle bg-primary text-black hover:bg-primary/80">
|
`<button data-install-mod-id=\"${mod.id}\" class=\"mod-btn-toggle bg-primary text-black hover:bg-primary/80\">
|
||||||
<i class="fas fa-download"></i>
|
<i class=\"fas fa-download\"></i>
|
||||||
${window.i18n ? window.i18n.t('mods.install') : 'INSTALL'}
|
${window.i18n ? window.i18n.t('mods.install') : 'INSTALL'}
|
||||||
</button>` :
|
</button>` :
|
||||||
`<button class="mod-btn-toggle bg-white/10 text-white" disabled>
|
`<button class=\"mod-btn-toggle bg-white/10 text-white\" disabled>
|
||||||
<i class="fas fa-check"></i>
|
<i class=\"fas fa-check\"></i>
|
||||||
${window.i18n ? window.i18n.t('mods.installed') : 'INSTALLED'}
|
${window.i18n ? window.i18n.t('mods.installed') : 'INSTALLED'}
|
||||||
</button>`
|
</button>`
|
||||||
}
|
}
|
||||||
@@ -364,6 +411,104 @@ function createBrowseModCard(mod) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currentSelectedMod = null;
|
||||||
|
|
||||||
|
function openVersionSelectModal(mod) {
|
||||||
|
currentSelectedMod = mod;
|
||||||
|
const modal = document.getElementById('versionSelectModal');
|
||||||
|
const closeBtn = document.getElementById('closeVersionModal');
|
||||||
|
const versionList = document.getElementById('versionList');
|
||||||
|
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
modal.classList.add('active');
|
||||||
|
|
||||||
|
const closeHandler = () => {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
setTimeout(() => {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}, 300);
|
||||||
|
currentSelectedMod = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (closeBtn) {
|
||||||
|
closeBtn.onclick = closeHandler;
|
||||||
|
}
|
||||||
|
modal.onclick = (e) => {
|
||||||
|
if (e.target === modal) closeHandler();
|
||||||
|
};
|
||||||
|
|
||||||
|
loadModVersions(mod.id, versionList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadModVersions(modId, container) {
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="loading-versions">
|
||||||
|
<i class="fas fa-spinner fa-spin fa-2x" style="margin-bottom: 10px; display: block;"></i>
|
||||||
|
<span>Loading versions...</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const versions = await window.electronAPI.getModFiles(modId);
|
||||||
|
|
||||||
|
if (!versions || versions.length === 0) {
|
||||||
|
container.innerHTML = `<div class="p-4 text-center text-gray-400" style="padding: 2rem;">No versions found for this mod.</div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort versions by date desc (API returns desc but ensure)
|
||||||
|
versions.sort((a, b) => new Date(b.fileDate) - new Date(a.fileDate));
|
||||||
|
|
||||||
|
container.innerHTML = versions.map(file => `
|
||||||
|
<div class="version-item">
|
||||||
|
<div class="version-info">
|
||||||
|
<div class="version-name">${file.displayName}</div>
|
||||||
|
<div class="version-meta">
|
||||||
|
<span><i class="fas fa-calendar"></i> ${new Date(file.fileDate).toLocaleDateString()}</span>
|
||||||
|
<span><i class="fas fa-download"></i> ${formatNumber(file.downloadCount)}</span>
|
||||||
|
<span><i class="fas fa-file-archive"></i> ${(file.fileLength / 1024 / 1024).toFixed(2)} MB</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="version-actions">
|
||||||
|
<button class="btn-install" data-file-id="${file.id}">
|
||||||
|
Install
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
// Add event listeners securely
|
||||||
|
container.querySelectorAll('.btn-install').forEach((btn, index) => {
|
||||||
|
const file = versions[index]; // Map index to file data
|
||||||
|
btn.onclick = () => installVersion(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading versions:', error);
|
||||||
|
container.innerHTML = `<div class="p-4 text-center text-red-400" style="padding: 2rem;">Error loading versions.<br><small>${error.message}</small></div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installVersion(file) {
|
||||||
|
if (!currentSelectedMod) return;
|
||||||
|
|
||||||
|
const modal = document.getElementById('versionSelectModal');
|
||||||
|
modal.style.display = 'none';
|
||||||
|
|
||||||
|
const modInfo = {
|
||||||
|
...currentSelectedMod,
|
||||||
|
fileId: file.id,
|
||||||
|
downloadUrl: file.downloadUrl,
|
||||||
|
fileName: file.fileName,
|
||||||
|
fileSize: file.fileLength
|
||||||
|
};
|
||||||
|
|
||||||
|
await downloadAndInstallMod(modInfo);
|
||||||
|
currentSelectedMod = null;
|
||||||
|
}
|
||||||
|
|
||||||
async function downloadAndInstallMod(modInfo) {
|
async function downloadAndInstallMod(modInfo) {
|
||||||
try {
|
try {
|
||||||
const downloadMsg = window.i18n ? window.i18n.t('notifications.modsDownloading').replace('{name}', modInfo.name) : `Downloading ${modInfo.name}...`;
|
const downloadMsg = window.i18n ? window.i18n.t('notifications.modsDownloading').replace('{name}', modInfo.name) : `Downloading ${modInfo.name}...`;
|
||||||
@@ -762,7 +907,10 @@ window.modsManager = {
|
|||||||
closeMyModsModal,
|
closeMyModsModal,
|
||||||
viewModPage,
|
viewModPage,
|
||||||
loadInstalledMods,
|
loadInstalledMods,
|
||||||
loadBrowseMods
|
loadBrowseMods,
|
||||||
|
openVersionSelectModal
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', initModsManager);
|
// Remove auto-init since we are now calling it from script.js explicitly
|
||||||
|
// which guarantees order and environment readiness
|
||||||
|
// document.addEventListener('DOMContentLoaded', initModsManager);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import './ui.js';
|
|||||||
import './install.js';
|
import './install.js';
|
||||||
import './launcher.js';
|
import './launcher.js';
|
||||||
import './news.js';
|
import './news.js';
|
||||||
import './mods.js';
|
import { initModsManager } from './mods.js';
|
||||||
import './players.js';
|
import './players.js';
|
||||||
import './settings.js';
|
import './settings.js';
|
||||||
import './logs.js';
|
import './logs.js';
|
||||||
@@ -15,6 +15,12 @@ let i18nInitialized = false;
|
|||||||
|
|
||||||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||||
updateLanguageSelector();
|
updateLanguageSelector();
|
||||||
|
initModsManager();
|
||||||
|
} else {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
updateLanguageSelector();
|
||||||
|
initModsManager();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@@ -569,6 +569,7 @@ async function loadAllSettings() {
|
|||||||
await loadLauncherHwAccel();
|
await loadLauncherHwAccel();
|
||||||
await loadGpuPreference();
|
await loadGpuPreference();
|
||||||
await loadVersionBranch();
|
await loadVersionBranch();
|
||||||
|
await loadWrapperConfigUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1254,3 +1255,235 @@ async function loadVersionBranch() {
|
|||||||
return 'release';
|
return 'release';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Java Wrapper Configuration UI ===
|
||||||
|
|
||||||
|
let _wrapperConfig = null;
|
||||||
|
let _wrapperPreviewOpen = false;
|
||||||
|
|
||||||
|
async function loadWrapperConfigUI() {
|
||||||
|
try {
|
||||||
|
if (!window.electronAPI || !window.electronAPI.loadWrapperConfig) return;
|
||||||
|
|
||||||
|
_wrapperConfig = await window.electronAPI.loadWrapperConfig();
|
||||||
|
renderStripFlagsList();
|
||||||
|
renderInjectArgsList();
|
||||||
|
setupWrapperEventListeners();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading wrapper config UI:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStripFlagsList() {
|
||||||
|
const container = document.getElementById('wrapperStripFlagsList');
|
||||||
|
if (!container || !_wrapperConfig) return;
|
||||||
|
|
||||||
|
if (_wrapperConfig.stripFlags.length === 0) {
|
||||||
|
container.innerHTML = '<div class="wrapper-items-empty">No flags configured</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = '';
|
||||||
|
_wrapperConfig.stripFlags.forEach((flag, index) => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'wrapper-item';
|
||||||
|
item.innerHTML = `
|
||||||
|
<span class="wrapper-item-text">${escapeHtml(flag)}</span>
|
||||||
|
<button class="wrapper-item-delete" data-index="${index}" title="Remove">
|
||||||
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
item.querySelector('.wrapper-item-delete').addEventListener('click', () => removeStripFlag(index));
|
||||||
|
container.appendChild(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderInjectArgsList() {
|
||||||
|
const container = document.getElementById('wrapperInjectArgsList');
|
||||||
|
if (!container || !_wrapperConfig) return;
|
||||||
|
|
||||||
|
if (_wrapperConfig.injectArgs.length === 0) {
|
||||||
|
container.innerHTML = '<div class="wrapper-items-empty">No arguments configured</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = '';
|
||||||
|
_wrapperConfig.injectArgs.forEach((entry, index) => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'wrapper-item';
|
||||||
|
|
||||||
|
const serverLabel = window.i18n ? window.i18n.t('settings.wrapperConditionServer') : 'Server Only';
|
||||||
|
const alwaysLabel = window.i18n ? window.i18n.t('settings.wrapperConditionAlways') : 'Always';
|
||||||
|
|
||||||
|
item.innerHTML = `
|
||||||
|
<span class="wrapper-item-text">${escapeHtml(entry.arg)}</span>
|
||||||
|
<div class="wrapper-item-condition">
|
||||||
|
<select data-index="${index}">
|
||||||
|
<option value="server"${entry.condition === 'server' ? ' selected' : ''}>${serverLabel}</option>
|
||||||
|
<option value="always"${entry.condition === 'always' ? ' selected' : ''}>${alwaysLabel}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button class="wrapper-item-delete" data-index="${index}" title="Remove">
|
||||||
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
item.querySelector('select').addEventListener('change', (e) => updateArgCondition(index, e.target.value));
|
||||||
|
item.querySelector('.wrapper-item-delete').addEventListener('click', () => removeInjectArg(index));
|
||||||
|
container.appendChild(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addStripFlag() {
|
||||||
|
const input = document.getElementById('wrapperAddFlagInput');
|
||||||
|
if (!input || !_wrapperConfig) return;
|
||||||
|
|
||||||
|
const flag = input.value.trim();
|
||||||
|
if (!flag) return;
|
||||||
|
|
||||||
|
if (_wrapperConfig.stripFlags.includes(flag)) {
|
||||||
|
const msg = window.i18n ? window.i18n.t('notifications.wrapperFlagExists') : 'This flag is already in the list';
|
||||||
|
showNotification(msg, 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_wrapperConfig.stripFlags.push(flag);
|
||||||
|
input.value = '';
|
||||||
|
renderStripFlagsList();
|
||||||
|
await saveWrapperConfigToBackend();
|
||||||
|
await updateWrapperPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeStripFlag(index) {
|
||||||
|
if (!_wrapperConfig) return;
|
||||||
|
_wrapperConfig.stripFlags.splice(index, 1);
|
||||||
|
renderStripFlagsList();
|
||||||
|
await saveWrapperConfigToBackend();
|
||||||
|
await updateWrapperPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addInjectArg() {
|
||||||
|
const input = document.getElementById('wrapperAddArgInput');
|
||||||
|
const condSelect = document.getElementById('wrapperAddArgCondition');
|
||||||
|
if (!input || !condSelect || !_wrapperConfig) return;
|
||||||
|
|
||||||
|
const arg = input.value.trim();
|
||||||
|
if (!arg) return;
|
||||||
|
|
||||||
|
const exists = _wrapperConfig.injectArgs.some(e => e.arg === arg);
|
||||||
|
if (exists) {
|
||||||
|
const msg = window.i18n ? window.i18n.t('notifications.wrapperArgExists') : 'This argument is already in the list';
|
||||||
|
showNotification(msg, 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_wrapperConfig.injectArgs.push({ arg, condition: condSelect.value });
|
||||||
|
input.value = '';
|
||||||
|
renderInjectArgsList();
|
||||||
|
await saveWrapperConfigToBackend();
|
||||||
|
await updateWrapperPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeInjectArg(index) {
|
||||||
|
if (!_wrapperConfig) return;
|
||||||
|
_wrapperConfig.injectArgs.splice(index, 1);
|
||||||
|
renderInjectArgsList();
|
||||||
|
await saveWrapperConfigToBackend();
|
||||||
|
await updateWrapperPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateArgCondition(index, condition) {
|
||||||
|
if (!_wrapperConfig || !_wrapperConfig.injectArgs[index]) return;
|
||||||
|
_wrapperConfig.injectArgs[index].condition = condition;
|
||||||
|
await saveWrapperConfigToBackend();
|
||||||
|
await updateWrapperPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveWrapperConfigToBackend() {
|
||||||
|
try {
|
||||||
|
const result = await window.electronAPI.saveWrapperConfig(_wrapperConfig);
|
||||||
|
if (!result || !result.success) {
|
||||||
|
throw new Error(result?.error || 'Save failed');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving wrapper config:', error);
|
||||||
|
const msg = window.i18n ? window.i18n.t('notifications.wrapperConfigSaveFailed') : 'Failed to save wrapper configuration';
|
||||||
|
showNotification(msg, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupWrapperEventListeners() {
|
||||||
|
const addFlagBtn = document.getElementById('wrapperAddFlagBtn');
|
||||||
|
const addFlagInput = document.getElementById('wrapperAddFlagInput');
|
||||||
|
const addArgBtn = document.getElementById('wrapperAddArgBtn');
|
||||||
|
const addArgInput = document.getElementById('wrapperAddArgInput');
|
||||||
|
const restoreBtn = document.getElementById('wrapperRestoreDefaultsBtn');
|
||||||
|
const previewToggle = document.getElementById('wrapperPreviewToggle');
|
||||||
|
|
||||||
|
if (addFlagBtn) addFlagBtn.addEventListener('click', addStripFlag);
|
||||||
|
if (addFlagInput) addFlagInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') addStripFlag(); });
|
||||||
|
if (addArgBtn) addArgBtn.addEventListener('click', addInjectArg);
|
||||||
|
if (addArgInput) addArgInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') addInjectArg(); });
|
||||||
|
|
||||||
|
if (restoreBtn) {
|
||||||
|
restoreBtn.addEventListener('click', () => {
|
||||||
|
const message = window.i18n ? window.i18n.t('confirm.resetWrapperMessage') : 'Are you sure you want to restore defaults? Your custom changes will be lost.';
|
||||||
|
const title = window.i18n ? window.i18n.t('confirm.resetWrapperTitle') : 'Restore Defaults';
|
||||||
|
|
||||||
|
showCustomConfirm(message, title, async () => {
|
||||||
|
try {
|
||||||
|
const result = await window.electronAPI.resetWrapperConfig();
|
||||||
|
if (result && result.success) {
|
||||||
|
_wrapperConfig = result.config;
|
||||||
|
renderStripFlagsList();
|
||||||
|
renderInjectArgsList();
|
||||||
|
await updateWrapperPreview();
|
||||||
|
const msg = window.i18n ? window.i18n.t('notifications.wrapperConfigReset') : 'Wrapper configuration restored to defaults';
|
||||||
|
showNotification(msg, 'success');
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.error || 'Reset failed');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error resetting wrapper config:', error);
|
||||||
|
const msg = window.i18n ? window.i18n.t('notifications.wrapperConfigResetFailed') : 'Failed to restore wrapper configuration';
|
||||||
|
showNotification(msg, 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previewToggle) {
|
||||||
|
previewToggle.addEventListener('click', toggleWrapperPreview);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleWrapperPreview() {
|
||||||
|
const container = document.getElementById('wrapperPreviewContainer');
|
||||||
|
const chevron = document.getElementById('wrapperPreviewChevron');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
_wrapperPreviewOpen = !_wrapperPreviewOpen;
|
||||||
|
|
||||||
|
if (_wrapperPreviewOpen) {
|
||||||
|
container.style.display = 'block';
|
||||||
|
if (chevron) chevron.classList.add('expanded');
|
||||||
|
await updateWrapperPreview();
|
||||||
|
} else {
|
||||||
|
container.style.display = 'none';
|
||||||
|
if (chevron) chevron.classList.remove('expanded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateWrapperPreview() {
|
||||||
|
if (!_wrapperPreviewOpen || !_wrapperConfig) return;
|
||||||
|
|
||||||
|
const previewEl = document.getElementById('wrapperPreviewContent');
|
||||||
|
if (!previewEl) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const platform = await window.electronAPI.getCurrentPlatform();
|
||||||
|
const script = await window.electronAPI.previewWrapperScript(_wrapperConfig, platform);
|
||||||
|
previewEl.textContent = script;
|
||||||
|
} catch (error) {
|
||||||
|
previewEl.textContent = 'Error generating preview: ' + error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -147,7 +147,18 @@
|
|||||||
"branchSwitching": "Switching to {branch}...",
|
"branchSwitching": "Switching to {branch}...",
|
||||||
"branchSwitched": "Switched to {branch} successfully!",
|
"branchSwitched": "Switched to {branch} successfully!",
|
||||||
"installRequired": "Installation Required",
|
"installRequired": "Installation Required",
|
||||||
"branchInstallConfirm": "The game will be installed for the {branch} branch. Continue?"
|
"branchInstallConfirm": "The game will be installed for the {branch} branch. Continue?",
|
||||||
|
"wrapperConfig": "Java Wrapper Configuration",
|
||||||
|
"wrapperConfigHint": "Configure how the Java wrapper handles JVM flags and arguments at launch time.",
|
||||||
|
"wrapperStripFlags": "JVM Flags to Remove",
|
||||||
|
"wrapperInjectArgs": "Arguments to Inject",
|
||||||
|
"wrapperAddFlagPlaceholder": "e.g. -XX:+SomeFlag",
|
||||||
|
"wrapperAddArgPlaceholder": "e.g. --some-flag",
|
||||||
|
"wrapperAdd": "Add",
|
||||||
|
"wrapperConditionServer": "Server Only",
|
||||||
|
"wrapperConditionAlways": "Always",
|
||||||
|
"wrapperRestoreDefaults": "Restore Defaults",
|
||||||
|
"wrapperAdvancedPreview": "Advanced: Script Preview"
|
||||||
},
|
},
|
||||||
"uuid": {
|
"uuid": {
|
||||||
"modalTitle": "UUID Management",
|
"modalTitle": "UUID Management",
|
||||||
@@ -221,7 +232,13 @@
|
|||||||
"noUsername": "No username configured. Please save your username first.",
|
"noUsername": "No username configured. Please save your username first.",
|
||||||
"switchUsernameSuccess": "Switched to \"{username}\" successfully!",
|
"switchUsernameSuccess": "Switched to \"{username}\" successfully!",
|
||||||
"switchUsernameFailed": "Failed to switch username",
|
"switchUsernameFailed": "Failed to switch username",
|
||||||
"playerNameTooLong": "Player name must be 16 characters or less"
|
"playerNameTooLong": "Player name must be 16 characters or less",
|
||||||
|
"wrapperConfigSaved": "Wrapper configuration saved",
|
||||||
|
"wrapperConfigSaveFailed": "Failed to save wrapper configuration",
|
||||||
|
"wrapperConfigReset": "Wrapper configuration restored to defaults",
|
||||||
|
"wrapperConfigResetFailed": "Failed to restore wrapper configuration",
|
||||||
|
"wrapperFlagExists": "This flag is already in the list",
|
||||||
|
"wrapperArgExists": "This argument is already in the list"
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"defaultTitle": "Confirm action",
|
"defaultTitle": "Confirm action",
|
||||||
@@ -239,7 +256,9 @@
|
|||||||
"uninstallGameButton": "Uninstall",
|
"uninstallGameButton": "Uninstall",
|
||||||
"switchUsernameTitle": "Switch Identity",
|
"switchUsernameTitle": "Switch Identity",
|
||||||
"switchUsernameMessage": "Switch to username \"{username}\"? This will change your current player identity.",
|
"switchUsernameMessage": "Switch to username \"{username}\"? This will change your current player identity.",
|
||||||
"switchUsernameButton": "Switch"
|
"switchUsernameButton": "Switch",
|
||||||
|
"resetWrapperTitle": "Restore Defaults",
|
||||||
|
"resetWrapperMessage": "Are you sure you want to restore the default wrapper configuration? Your custom changes will be lost."
|
||||||
},
|
},
|
||||||
"progress": {
|
"progress": {
|
||||||
"initializing": "Initializing...",
|
"initializing": "Initializing...",
|
||||||
|
|||||||
216
GUI/style.css
216
GUI/style.css
@@ -4829,6 +4829,140 @@ select.settings-input option {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wrapper-items-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-item:hover {
|
||||||
|
border-color: rgba(147, 51, 234, 0.3);
|
||||||
|
background: rgba(147, 51, 234, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-item-text {
|
||||||
|
font-family: 'JetBrains Mono', 'Courier New', monospace;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #e5e7eb;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-item-condition select {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #e5e7eb;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 4px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-item-condition select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: rgba(147, 51, 234, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-item-delete {
|
||||||
|
padding: 4px 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-item-delete:hover {
|
||||||
|
background: rgba(239, 68, 68, 0.2);
|
||||||
|
border-color: rgba(239, 68, 68, 0.4);
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-items-empty {
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-condition-select {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #e5e7eb;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
padding: 6px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-condition-select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: rgba(147, 51, 234, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-preview-toggle {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
padding: 4px 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-preview-toggle:hover {
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-preview-toggle i {
|
||||||
|
transition: transform 0.2s;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-preview-toggle i.expanded {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-preview-content {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
font-family: 'JetBrains Mono', 'Courier New', monospace;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 12px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: pre;
|
||||||
|
tab-size: 4;
|
||||||
|
}
|
||||||
|
|
||||||
.settings-hint {
|
.settings-hint {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
@@ -6476,3 +6610,85 @@ input[type="text"].uuid-input,
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Version Selection Styles */
|
||||||
|
.version-list-container::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-list-container::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-meta {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #a0a0a0;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-meta span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-meta i {
|
||||||
|
font-size: 0.8em;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-actions .btn-install {
|
||||||
|
padding: 0.5rem 1.25rem;
|
||||||
|
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-actions .btn-install:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 6px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
background: linear-gradient(135deg, #4f93f6, #3b82f6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-versions {
|
||||||
|
padding: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
@@ -858,6 +858,212 @@ function checkLaunchReady() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// JAVA WRAPPER CONFIGURATION (Structured)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
const DEFAULT_WRAPPER_CONFIG = {
|
||||||
|
stripFlags: ['-XX:+UseCompactObjectHeaders'],
|
||||||
|
injectArgs: [
|
||||||
|
{ arg: '--disable-sentry', condition: 'server' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
function getDefaultWrapperConfig() {
|
||||||
|
return JSON.parse(JSON.stringify(DEFAULT_WRAPPER_CONFIG));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadWrapperConfig() {
|
||||||
|
const config = loadConfig();
|
||||||
|
if (config.javaWrapperConfig && typeof config.javaWrapperConfig === 'object') {
|
||||||
|
const wc = config.javaWrapperConfig;
|
||||||
|
if (Array.isArray(wc.stripFlags) && Array.isArray(wc.injectArgs)) {
|
||||||
|
const loaded = JSON.parse(JSON.stringify(wc));
|
||||||
|
// Normalize entries: ensure every injectArg has a valid condition
|
||||||
|
for (const entry of loaded.injectArgs) {
|
||||||
|
if (!['server', 'always'].includes(entry.condition)) {
|
||||||
|
entry.condition = 'always';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getDefaultWrapperConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveWrapperConfig(wrapperConfig) {
|
||||||
|
if (!wrapperConfig || typeof wrapperConfig !== 'object') {
|
||||||
|
throw new Error('Invalid wrapper config');
|
||||||
|
}
|
||||||
|
if (!Array.isArray(wrapperConfig.stripFlags) || !Array.isArray(wrapperConfig.injectArgs)) {
|
||||||
|
throw new Error('Invalid wrapper config structure');
|
||||||
|
}
|
||||||
|
// Validate injectArgs entries
|
||||||
|
for (const entry of wrapperConfig.injectArgs) {
|
||||||
|
if (!entry.arg || typeof entry.arg !== 'string') {
|
||||||
|
throw new Error('Each inject arg must have a string "arg" property');
|
||||||
|
}
|
||||||
|
if (!['server', 'always'].includes(entry.condition)) {
|
||||||
|
throw new Error('Inject arg condition must be "server" or "always"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
saveConfig({ javaWrapperConfig: wrapperConfig });
|
||||||
|
console.log('[Config] Wrapper config saved');
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetWrapperConfig() {
|
||||||
|
const config = loadConfig();
|
||||||
|
delete config.javaWrapperConfig;
|
||||||
|
delete config.javaWrapperScripts; // Clean up legacy key if present
|
||||||
|
|
||||||
|
// Write the cleaned config using the same atomic pattern as saveConfig.
|
||||||
|
// We cannot use saveConfig() here because it merges (spread) which cannot remove keys.
|
||||||
|
const data = JSON.stringify(config, null, 2);
|
||||||
|
fs.writeFileSync(CONFIG_TEMP, data, 'utf8');
|
||||||
|
if (fs.existsSync(CONFIG_FILE)) {
|
||||||
|
fs.copyFileSync(CONFIG_FILE, CONFIG_BACKUP);
|
||||||
|
}
|
||||||
|
fs.renameSync(CONFIG_TEMP, CONFIG_FILE);
|
||||||
|
console.log('[Config] Wrapper config reset to default');
|
||||||
|
return getDefaultWrapperConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a platform-specific wrapper script from structured config
|
||||||
|
* @param {Object} config - { stripFlags: string[], injectArgs: {arg, condition}[] }
|
||||||
|
* @param {string} platform - 'darwin', 'win32', or 'linux'
|
||||||
|
* @param {string|null} javaBin - Path to real java binary (required for darwin/linux)
|
||||||
|
* @returns {string} Generated script content
|
||||||
|
*/
|
||||||
|
function generateWrapperScript(config, platform, javaBin) {
|
||||||
|
const { stripFlags, injectArgs } = config;
|
||||||
|
const alwaysArgs = injectArgs.filter(a => a.condition === 'always');
|
||||||
|
const serverArgs = injectArgs.filter(a => a.condition === 'server');
|
||||||
|
|
||||||
|
if (platform === 'win32') {
|
||||||
|
return _generateWindowsWrapper(stripFlags, alwaysArgs, serverArgs);
|
||||||
|
} else {
|
||||||
|
return _generateUnixWrapper(stripFlags, alwaysArgs, serverArgs, javaBin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _generateUnixWrapper(stripFlags, alwaysArgs, serverArgs, javaBin) {
|
||||||
|
const lines = [
|
||||||
|
'#!/bin/bash',
|
||||||
|
'# Java wrapper - generated by HytaleF2P launcher',
|
||||||
|
`REAL_JAVA="${javaBin || '${JAVA_BIN}'}"`,
|
||||||
|
'ARGS=("$@")',
|
||||||
|
''
|
||||||
|
];
|
||||||
|
|
||||||
|
// Strip flags
|
||||||
|
if (stripFlags.length > 0) {
|
||||||
|
lines.push('# Strip JVM flags');
|
||||||
|
lines.push('FILTERED_ARGS=()');
|
||||||
|
lines.push('for arg in "${ARGS[@]}"; do');
|
||||||
|
lines.push(' case "$arg" in');
|
||||||
|
for (const flag of stripFlags) {
|
||||||
|
lines.push(` "${flag}") echo "[Wrapper] Stripped: $arg" ;;`);
|
||||||
|
}
|
||||||
|
lines.push(' *) FILTERED_ARGS+=("$arg") ;;');
|
||||||
|
lines.push(' esac');
|
||||||
|
lines.push('done');
|
||||||
|
} else {
|
||||||
|
lines.push('FILTERED_ARGS=("${ARGS[@]}")');
|
||||||
|
}
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
// Always-inject args
|
||||||
|
if (alwaysArgs.length > 0) {
|
||||||
|
lines.push('# Inject args (always)');
|
||||||
|
for (const a of alwaysArgs) {
|
||||||
|
lines.push(`FILTERED_ARGS+=("${a.arg}")`);
|
||||||
|
lines.push(`echo "[Wrapper] Injected ${a.arg}"`);
|
||||||
|
}
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server-conditional args (appended after HytaleServer.jar if present)
|
||||||
|
if (serverArgs.length > 0) {
|
||||||
|
lines.push('# Inject args (server only)');
|
||||||
|
lines.push('IS_SERVER=false');
|
||||||
|
lines.push('for arg in "${FILTERED_ARGS[@]}"; do');
|
||||||
|
lines.push(' if [[ "$arg" == *"HytaleServer.jar"* ]]; then');
|
||||||
|
lines.push(' IS_SERVER=true');
|
||||||
|
lines.push(' break');
|
||||||
|
lines.push(' fi');
|
||||||
|
lines.push('done');
|
||||||
|
lines.push('if [ "$IS_SERVER" = true ]; then');
|
||||||
|
for (const a of serverArgs) {
|
||||||
|
lines.push(` FILTERED_ARGS+=("${a.arg}")`);
|
||||||
|
lines.push(` echo "[Wrapper] Injected ${a.arg}"`);
|
||||||
|
}
|
||||||
|
lines.push('fi');
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('echo "[Wrapper] Executing: $REAL_JAVA ${FILTERED_ARGS[*]}"');
|
||||||
|
lines.push('exec "$REAL_JAVA" "${FILTERED_ARGS[@]}"');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function _generateWindowsWrapper(stripFlags, alwaysArgs, serverArgs) {
|
||||||
|
const lines = [
|
||||||
|
'@echo off',
|
||||||
|
'setlocal EnableDelayedExpansion',
|
||||||
|
'',
|
||||||
|
'REM Java wrapper - generated by HytaleF2P launcher',
|
||||||
|
'set "REAL_JAVA=%~dp0java-original.exe"',
|
||||||
|
'set "ARGS=%*"',
|
||||||
|
''
|
||||||
|
];
|
||||||
|
|
||||||
|
// Strip flags using string replacement
|
||||||
|
if (stripFlags.length > 0) {
|
||||||
|
lines.push('REM Strip JVM flags');
|
||||||
|
for (const flag of stripFlags) {
|
||||||
|
lines.push(`set "ARGS=!ARGS:${flag}=!"`);
|
||||||
|
}
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always-inject args
|
||||||
|
const alwaysExtra = alwaysArgs.map(a => a.arg).join(' ');
|
||||||
|
|
||||||
|
// Server-conditional args
|
||||||
|
if (serverArgs.length > 0) {
|
||||||
|
const serverExtra = serverArgs.map(a => a.arg).join(' ');
|
||||||
|
lines.push('REM Check if running HytaleServer.jar and inject server args');
|
||||||
|
lines.push('echo !ARGS! | findstr /i "HytaleServer.jar" >nul 2>&1');
|
||||||
|
lines.push('if "!ERRORLEVEL!"=="0" (');
|
||||||
|
if (alwaysExtra) {
|
||||||
|
lines.push(` echo [Wrapper] Injected ${alwaysExtra} ${serverExtra}`);
|
||||||
|
lines.push(` "%REAL_JAVA%" !ARGS! ${alwaysExtra} ${serverExtra}`);
|
||||||
|
} else {
|
||||||
|
lines.push(` echo [Wrapper] Injected ${serverExtra}`);
|
||||||
|
lines.push(` "%REAL_JAVA%" !ARGS! ${serverExtra}`);
|
||||||
|
}
|
||||||
|
lines.push(') else (');
|
||||||
|
if (alwaysExtra) {
|
||||||
|
lines.push(` "%REAL_JAVA%" !ARGS! ${alwaysExtra}`);
|
||||||
|
} else {
|
||||||
|
lines.push(' "%REAL_JAVA%" !ARGS!');
|
||||||
|
}
|
||||||
|
lines.push(')');
|
||||||
|
} else if (alwaysExtra) {
|
||||||
|
lines.push(`"%REAL_JAVA%" !ARGS! ${alwaysExtra}`);
|
||||||
|
} else {
|
||||||
|
lines.push('"%REAL_JAVA%" !ARGS!');
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('exit /b !ERRORLEVEL!');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
return lines.join('\r\n');
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// EXPORTS
|
// EXPORTS
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -924,6 +1130,13 @@ module.exports = {
|
|||||||
saveVersionBranch,
|
saveVersionBranch,
|
||||||
loadVersionBranch,
|
loadVersionBranch,
|
||||||
|
|
||||||
|
// Java Wrapper Config
|
||||||
|
getDefaultWrapperConfig,
|
||||||
|
loadWrapperConfig,
|
||||||
|
saveWrapperConfig,
|
||||||
|
resetWrapperConfig,
|
||||||
|
generateWrapperScript,
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
CONFIG_FILE,
|
CONFIG_FILE,
|
||||||
UUID_STORE_FILE
|
UUID_STORE_FILE
|
||||||
|
|||||||
@@ -45,7 +45,13 @@ const {
|
|||||||
saveVersionClient,
|
saveVersionClient,
|
||||||
loadVersionClient,
|
loadVersionClient,
|
||||||
saveVersionBranch,
|
saveVersionBranch,
|
||||||
loadVersionBranch
|
loadVersionBranch,
|
||||||
|
// Java Wrapper Config
|
||||||
|
getDefaultWrapperConfig,
|
||||||
|
loadWrapperConfig,
|
||||||
|
saveWrapperConfig,
|
||||||
|
resetWrapperConfig,
|
||||||
|
generateWrapperScript
|
||||||
} = require('./core/config');
|
} = require('./core/config');
|
||||||
|
|
||||||
const { getResolvedAppDir, getModsPath } = require('./core/paths');
|
const { getResolvedAppDir, getModsPath } = require('./core/paths');
|
||||||
@@ -78,7 +84,8 @@ const {
|
|||||||
loadInstalledMods,
|
loadInstalledMods,
|
||||||
downloadMod,
|
downloadMod,
|
||||||
uninstallMod,
|
uninstallMod,
|
||||||
toggleMod
|
toggleMod,
|
||||||
|
getModFiles
|
||||||
} = require('./managers/modManager');
|
} = require('./managers/modManager');
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
@@ -181,6 +188,7 @@ module.exports = {
|
|||||||
downloadMod,
|
downloadMod,
|
||||||
uninstallMod,
|
uninstallMod,
|
||||||
toggleMod,
|
toggleMod,
|
||||||
|
getModFiles,
|
||||||
saveModsToConfig,
|
saveModsToConfig,
|
||||||
loadModsFromConfig,
|
loadModsFromConfig,
|
||||||
|
|
||||||
@@ -197,6 +205,13 @@ module.exports = {
|
|||||||
proposeGameUpdate,
|
proposeGameUpdate,
|
||||||
handleFirstLaunchCheck,
|
handleFirstLaunchCheck,
|
||||||
|
|
||||||
|
// Java Wrapper Config functions
|
||||||
|
getDefaultWrapperConfig,
|
||||||
|
loadWrapperConfig,
|
||||||
|
saveWrapperConfig,
|
||||||
|
resetWrapperConfig,
|
||||||
|
generateWrapperScript,
|
||||||
|
|
||||||
// Path functions
|
// Path functions
|
||||||
getResolvedAppDir
|
getResolvedAppDir
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ const {
|
|||||||
saveVersionClient,
|
saveVersionClient,
|
||||||
loadUsername,
|
loadUsername,
|
||||||
hasUsername,
|
hasUsername,
|
||||||
checkLaunchReady
|
checkLaunchReady,
|
||||||
|
loadWrapperConfig,
|
||||||
|
generateWrapperScript
|
||||||
} = require('../core/config');
|
} = require('../core/config');
|
||||||
const { resolveJavaPath, getJavaExec, getBundledJavaPath, detectSystemJava, JAVA_EXECUTABLE } = require('./javaManager');
|
const { resolveJavaPath, getJavaExec, getBundledJavaPath, detectSystemJava, JAVA_EXECUTABLE } = require('./javaManager');
|
||||||
const { getLatestClientVersion } = require('../services/versionManager');
|
const { getLatestClientVersion } = require('../services/versionManager');
|
||||||
@@ -328,23 +330,13 @@ async function launchGame(playerNameOverride = null, progressCallback, javaPathO
|
|||||||
console.log('Signed server binaries (after patching)');
|
console.log('Signed server binaries (after patching)');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create java wrapper (must be signed on macOS)
|
||||||
if (javaBin && fs.existsSync(javaBin)) {
|
if (javaBin && fs.existsSync(javaBin)) {
|
||||||
const javaWrapperPath = path.join(path.dirname(javaBin), 'java-wrapper');
|
const javaWrapperPath = path.join(path.dirname(javaBin), 'java-wrapper');
|
||||||
const wrapperScript = `#!/bin/bash
|
const wrapperScript = generateWrapperScript(loadWrapperConfig(), 'darwin', javaBin);
|
||||||
# Java wrapper for macOS - adds --disable-sentry to fix Sentry hang issue
|
|
||||||
REAL_JAVA="${javaBin}"
|
|
||||||
ARGS=("$@")
|
|
||||||
for i in "\${!ARGS[@]}"; do
|
|
||||||
if [[ "\${ARGS[$i]}" == *"HytaleServer.jar"* ]]; then
|
|
||||||
ARGS=("\${ARGS[@]:0:$((i+1))}" "--disable-sentry" "\${ARGS[@]:$((i+1))}")
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
exec "$REAL_JAVA" "\${ARGS[@]}"
|
|
||||||
`;
|
|
||||||
fs.writeFileSync(javaWrapperPath, wrapperScript, { mode: 0o755 });
|
fs.writeFileSync(javaWrapperPath, wrapperScript, { mode: 0o755 });
|
||||||
await signPath(javaWrapperPath, false);
|
await signPath(javaWrapperPath, false);
|
||||||
console.log('Created java wrapper with --disable-sentry fix');
|
console.log('Created java wrapper from config template');
|
||||||
javaBin = javaWrapperPath;
|
javaBin = javaWrapperPath;
|
||||||
}
|
}
|
||||||
} catch (signError) {
|
} catch (signError) {
|
||||||
@@ -353,6 +345,40 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Windows: Create java wrapper to strip/inject JVM flags per wrapper config
|
||||||
|
if (process.platform === 'win32' && javaBin && fs.existsSync(javaBin)) {
|
||||||
|
try {
|
||||||
|
const javaDir = path.dirname(javaBin);
|
||||||
|
const javaOriginal = path.join(javaDir, 'java-original.exe');
|
||||||
|
const javaWrapperPath = path.join(javaDir, 'java-wrapper.bat');
|
||||||
|
|
||||||
|
if (!fs.existsSync(javaOriginal)) {
|
||||||
|
fs.copyFileSync(javaBin, javaOriginal);
|
||||||
|
console.log('Backed up java.exe as java-original.exe');
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapperScript = generateWrapperScript(loadWrapperConfig(), 'win32', null);
|
||||||
|
fs.writeFileSync(javaWrapperPath, wrapperScript);
|
||||||
|
console.log('Created Windows java wrapper from config template');
|
||||||
|
javaBin = javaWrapperPath;
|
||||||
|
} catch (wrapperError) {
|
||||||
|
console.log('Notice: Windows java wrapper creation failed:', wrapperError.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linux: Create java wrapper to strip/inject JVM flags per wrapper config
|
||||||
|
if (process.platform === 'linux' && javaBin && fs.existsSync(javaBin)) {
|
||||||
|
try {
|
||||||
|
const javaWrapperPath = path.join(path.dirname(javaBin), 'java-wrapper');
|
||||||
|
const wrapperScript = generateWrapperScript(loadWrapperConfig(), 'linux', javaBin);
|
||||||
|
fs.writeFileSync(javaWrapperPath, wrapperScript, { mode: 0o755 });
|
||||||
|
console.log('Created Linux java wrapper from config template');
|
||||||
|
javaBin = javaWrapperPath;
|
||||||
|
} catch (wrapperError) {
|
||||||
|
console.log('Notice: Linux java wrapper creation failed:', wrapperError.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
'--app-dir', gameLatest,
|
'--app-dir', gameLatest,
|
||||||
'--java-exec', javaBin,
|
'--java-exec', javaBin,
|
||||||
|
|||||||
@@ -285,6 +285,27 @@ async function toggleMod(modId, modsPath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function getModFiles(modId) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`https://api.curseforge.com/v1/mods/${modId}/files`, {
|
||||||
|
headers: {
|
||||||
|
'x-api-key': API_KEY,
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
sortOrder: 'desc'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching mod files:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function syncModsForCurrentProfile() {
|
async function syncModsForCurrentProfile() {
|
||||||
try {
|
try {
|
||||||
const activeProfile = profileManager.getActiveProfile();
|
const activeProfile = profileManager.getActiveProfile();
|
||||||
@@ -455,5 +476,6 @@ module.exports = {
|
|||||||
syncModsForCurrentProfile,
|
syncModsForCurrentProfile,
|
||||||
generateModId,
|
generateModId,
|
||||||
extractModName,
|
extractModName,
|
||||||
extractVersion
|
extractVersion,
|
||||||
};
|
getModFiles
|
||||||
|
};
|
||||||
53
main.js
53
main.js
@@ -1068,7 +1068,7 @@ ipcMain.handle('load-settings', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod, getCurrentUuid, getAllUuidMappings, setUuidForUser, generateNewUuid, deleteUuidForUser, resetCurrentUserUuid } = require('./backend/launcher');
|
const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod, getModFiles, getCurrentUuid, getAllUuidMappings, setUuidForUser, generateNewUuid, deleteUuidForUser, resetCurrentUserUuid } = require('./backend/launcher');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
|
|
||||||
ipcMain.handle('get-local-app-data', async () => {
|
ipcMain.handle('get-local-app-data', async () => {
|
||||||
@@ -1118,6 +1118,15 @@ ipcMain.handle('download-mod', async (event, modInfo) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-mod-files', async (event, modId) => {
|
||||||
|
try {
|
||||||
|
return await getModFiles(modId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting mod files:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('uninstall-mod', async (event, modId, modsPath) => {
|
ipcMain.handle('uninstall-mod', async (event, modId, modsPath) => {
|
||||||
try {
|
try {
|
||||||
return await uninstallMod(modId, modsPath);
|
return await uninstallMod(modId, modsPath);
|
||||||
@@ -1539,3 +1548,45 @@ ipcMain.handle('profile-update', async (event, id, updates) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Java Wrapper Config IPC
|
||||||
|
ipcMain.handle('load-wrapper-config', () => {
|
||||||
|
const { loadWrapperConfig } = require('./backend/launcher');
|
||||||
|
return loadWrapperConfig();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('save-wrapper-config', (event, config) => {
|
||||||
|
try {
|
||||||
|
const { saveWrapperConfig } = require('./backend/launcher');
|
||||||
|
saveWrapperConfig(config);
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving wrapper config:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('reset-wrapper-config', () => {
|
||||||
|
try {
|
||||||
|
const { resetWrapperConfig } = require('./backend/launcher');
|
||||||
|
const config = resetWrapperConfig();
|
||||||
|
return { success: true, config };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error resetting wrapper config:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-default-wrapper-config', () => {
|
||||||
|
const { getDefaultWrapperConfig } = require('./backend/launcher');
|
||||||
|
return getDefaultWrapperConfig();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('preview-wrapper-script', (event, config, platform) => {
|
||||||
|
const { generateWrapperScript } = require('./backend/launcher');
|
||||||
|
return generateWrapperScript(config || require('./backend/launcher').loadWrapperConfig(), platform || process.platform, '/path/to/java');
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-current-platform', () => {
|
||||||
|
return process.platform;
|
||||||
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hytale-f2p-launcher",
|
"name": "hytale-f2p-launcher",
|
||||||
"version": "2.4.0",
|
"version": "2.4.1",
|
||||||
"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",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath),
|
loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath),
|
||||||
downloadMod: (modInfo) => ipcRenderer.invoke('download-mod', modInfo),
|
downloadMod: (modInfo) => ipcRenderer.invoke('download-mod', modInfo),
|
||||||
uninstallMod: (modId, modsPath) => ipcRenderer.invoke('uninstall-mod', modId, modsPath),
|
uninstallMod: (modId, modsPath) => ipcRenderer.invoke('uninstall-mod', modId, modsPath),
|
||||||
|
getModFiles: (modId) => ipcRenderer.invoke('get-mod-files', modId),
|
||||||
toggleMod: (modId, modsPath) => ipcRenderer.invoke('toggle-mod', modId, modsPath),
|
toggleMod: (modId, modsPath) => ipcRenderer.invoke('toggle-mod', modId, modsPath),
|
||||||
selectModFiles: () => ipcRenderer.invoke('select-mod-files'),
|
selectModFiles: () => ipcRenderer.invoke('select-mod-files'),
|
||||||
copyModFile: (sourcePath, modsPath) => ipcRenderer.invoke('copy-mod-file', sourcePath, modsPath),
|
copyModFile: (sourcePath, modsPath) => ipcRenderer.invoke('copy-mod-file', sourcePath, modsPath),
|
||||||
@@ -104,6 +105,14 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
deleteUuidForUser: (username) => ipcRenderer.invoke('delete-uuid-for-user', username),
|
deleteUuidForUser: (username) => ipcRenderer.invoke('delete-uuid-for-user', username),
|
||||||
resetCurrentUserUuid: () => ipcRenderer.invoke('reset-current-user-uuid'),
|
resetCurrentUserUuid: () => ipcRenderer.invoke('reset-current-user-uuid'),
|
||||||
|
|
||||||
|
// Java Wrapper Config API
|
||||||
|
loadWrapperConfig: () => ipcRenderer.invoke('load-wrapper-config'),
|
||||||
|
saveWrapperConfig: (config) => ipcRenderer.invoke('save-wrapper-config', config),
|
||||||
|
resetWrapperConfig: () => ipcRenderer.invoke('reset-wrapper-config'),
|
||||||
|
getDefaultWrapperConfig: () => ipcRenderer.invoke('get-default-wrapper-config'),
|
||||||
|
previewWrapperScript: (config, platform) => ipcRenderer.invoke('preview-wrapper-script', config, platform),
|
||||||
|
getCurrentPlatform: () => ipcRenderer.invoke('get-current-platform'),
|
||||||
|
|
||||||
// Profile API
|
// Profile API
|
||||||
profile: {
|
profile: {
|
||||||
create: (name) => ipcRenderer.invoke('profile-create', name),
|
create: (name) => ipcRenderer.invoke('profile-create', name),
|
||||||
|
|||||||
Reference in New Issue
Block a user