Compare commits

...

5 Commits

Author SHA1 Message Date
sanasol
e1a3f919a2 Fix JRE flatten failing silently on Windows EPERM
When extracting the bundled JRE, flattenJREDir renames files from
the nested jdk subdirectory up one level. On Windows this fails with
EPERM when antivirus or file indexing holds handles open, leaving
the JRE nested and unfindable — causing "Server failed to boot".

- Fall back to copy+delete when rename gets EPERM/EACCES/EBUSY
- getBundledJavaPath checks nested JRE subdirs as last resort
2026-02-26 23:39:43 +01:00
sanasol
e3fe1b6a10 Delete server/commit-msg.txt 2026-02-26 19:45:57 +00:00
sanasol
7042188696 fix: use HTTP for uploads, add debug output
- Switch upload URL to HTTP (port 3000) to bypass expired SSL cert
- Add HTTP status code and response body to upload output for debugging
- Upload URL kept in secret (FORGEJO_UPLOAD_URL), not exposed in workflow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 20:29:26 +01:00
sanasol
c067efbcea fix: add -k to curl uploads to bypass expired IP cert
The Let's Encrypt certificate on the direct IP (208.69.78.130) expired
and HTTP-01 challenge renewal is failing due to port 80 being blocked.
Adding -k skips cert verification while keeping TLS encryption.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 20:20:55 +01:00
sanasol
b83b728d21 v2.4.4: Rename Profiles to Configurations, add Identity Switcher
- Rename "Profiles" to "Configurations" in all UI text and 11 locale files
- Add identity switcher dropdown in header (green accent, fa-id-badge icon)
- Quick-switch player identity without opening Settings
- "Manage" action opens UUID Management modal
- Header tooltips explaining what each dropdown does
- Config dropdown icon changed from fa-user-circle to fa-sliders-h
- Global Escape key handler for closing modals and dropdowns
- Fix identity selector not clickable (missing -webkit-app-region: no-drag)
- Sync header identity name after all identity-changing operations
- XSS protection in identity list rendering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 20:01:40 +01:00
20 changed files with 929 additions and 210 deletions

View File

@@ -52,12 +52,20 @@ jobs:
run: |
RELEASE_ID=$(curl -s "${FORGEJO_API}/repos/${GITHUB_REPOSITORY}/releases/tags/${{ github.ref_name }}" \
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')
echo "Release ID: ${RELEASE_ID}"
echo "Upload URL: ${FORGEJO_UPLOAD}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets"
for file in dist/*.exe dist/*.exe.blockmap dist/latest.yml; do
[ -f "$file" ] || continue
echo "Uploading $file..."
curl -s --max-time 600 -X POST "${FORGEJO_UPLOAD}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=$(basename $file)" \
echo "Uploading $file ($(stat -c%s "$file" 2>/dev/null || stat -f%z "$file") bytes)..."
HTTP_CODE=$(curl -w '%{http_code}' --max-time 600 -X POST "${FORGEJO_UPLOAD}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=$(basename $file)" \
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
-F "attachment=@${file}" || echo "Failed to upload $file"
-F "attachment=@${file}" -o /tmp/upload_response.txt 2>/tmp/upload_err.txt)
if [ "$HTTP_CODE" = "201" ]; then
echo "OK — uploaded $(basename $file)"
else
echo "FAILED (HTTP $HTTP_CODE): $(cat /tmp/upload_response.txt)"
echo "Curl stderr: $(cat /tmp/upload_err.txt)"
fi
done
build-macos:
@@ -83,12 +91,19 @@ jobs:
run: |
RELEASE_ID=$(curl -s "${FORGEJO_API}/repos/${GITHUB_REPOSITORY}/releases/tags/${{ github.ref_name }}" \
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')
echo "Release ID: ${RELEASE_ID}"
for file in dist/*.dmg dist/*.zip dist/*.blockmap dist/latest-mac.yml; do
[ -f "$file" ] || continue
echo "Uploading $file..."
curl -s --max-time 600 -X POST "${FORGEJO_UPLOAD}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=$(basename $file)" \
HTTP_CODE=$(curl -w '%{http_code}' --max-time 600 -X POST "${FORGEJO_UPLOAD}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=$(basename $file)" \
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
-F "attachment=@${file}" || echo "Failed to upload $file"
-F "attachment=@${file}" -o /tmp/upload_response.txt 2>/tmp/upload_err.txt)
if [ "$HTTP_CODE" = "201" ]; then
echo "OK — uploaded $(basename $file)"
else
echo "FAILED (HTTP $HTTP_CODE): $(cat /tmp/upload_response.txt)"
echo "Curl stderr: $(cat /tmp/upload_err.txt)"
fi
done
build-linux:
@@ -113,10 +128,17 @@ jobs:
run: |
RELEASE_ID=$(curl -s "${FORGEJO_API}/repos/${GITHUB_REPOSITORY}/releases/tags/${{ github.ref_name }}" \
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')
echo "Release ID: ${RELEASE_ID}"
for file in dist/*.AppImage dist/*.AppImage.blockmap dist/*.deb dist/*.rpm dist/*.pacman dist/latest-linux.yml; do
[ -f "$file" ] || continue
echo "Uploading $file..."
curl -s --max-time 600 -X POST "${FORGEJO_UPLOAD}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=$(basename $file)" \
echo "Uploading $file ($(stat -c%s "$file" 2>/dev/null || stat -f%z "$file") bytes)..."
HTTP_CODE=$(curl -w '%{http_code}' --max-time 600 -X POST "${FORGEJO_UPLOAD}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=$(basename $file)" \
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
-F "attachment=@${file}" || echo "Failed to upload $file"
-F "attachment=@${file}" -o /tmp/upload_response.txt 2>/tmp/upload_err.txt)
if [ "$HTTP_CODE" = "201" ]; then
echo "OK — uploaded $(basename $file)"
else
echo "FAILED (HTTP $HTTP_CODE): $(cat /tmp/upload_response.txt)"
echo "Curl stderr: $(cat /tmp/upload_err.txt)"
fi
done

View File

@@ -73,22 +73,40 @@
<span id="onlineCount" class="counter-value">0</span>
</div>
<div class="identity-selector" id="identitySelector">
<button class="identity-btn" onclick="toggleIdentityDropdown()">
<i class="fas fa-id-badge"></i>
<span id="currentIdentityName">Player</span>
<i class="fas fa-chevron-down"></i>
</button>
<div class="identity-dropdown" id="identityDropdown">
<div class="identity-list" id="identityList"></div>
<div class="identity-divider"></div>
<div class="identity-action" onclick="openIdentityManager()">
<i class="fas fa-fingerprint"></i>
<span data-i18n="header.manageIdentities">Manage</span>
</div>
</div>
<span class="header-tooltip" data-i18n="header.identityTooltip">Your player name &amp; UUID used in-game</span>
</div>
<div class="profile-selector" id="profileSelector">
<button class="profile-btn" onclick="toggleProfileDropdown()">
<i class="fas fa-user-circle"></i>
<i class="fas fa-sliders-h"></i>
<span id="currentProfileName">Default</span>
<i class="fas fa-chevron-down"></i>
</button>
<div class="profile-dropdown" id="profileDropdown">
<div class="profile-list" id="profileList">
<!-- Profiles populated by JS -->
<!-- Configurations populated by JS -->
</div>
<div class="profile-divider"></div>
<div class="profile-action" onclick="openProfileManager()">
<i class="fas fa-cog"></i>
<span data-i18n="header.manageProfiles">Manage Profiles</span>
<span data-i18n="header.manageProfiles">Manage</span>
</div>
</div>
<span class="header-tooltip" data-i18n="header.configTooltip">Game config: mods, Java &amp; memory settings</span>
</div>
<div class="window-controls">
@@ -736,28 +754,40 @@
</div>
<div class="uuid-modal-body">
<div class="uuid-current-section">
<h3 class="uuid-section-title" data-i18n="uuid.currentUserUUID">Current User UUID</h3>
<div class="uuid-current-display">
<input type="text" id="modalCurrentUuid" class="uuid-display-input" readonly />
<button id="modalCopyUuidBtn" class="uuid-action-btn copy-btn" title="Copy UUID">
<i class="fas fa-copy"></i>
</button>
<button id="modalRegenerateUuidBtn" class="uuid-action-btn regenerate-btn"
title="Generate New UUID">
<i class="fas fa-sync-alt"></i>
</button>
</div>
</div>
<div class="uuid-list-section">
<div class="uuid-list-header">
<h3 class="uuid-section-title" data-i18n="uuid.allPlayerUUIDs">All Player UUIDs</h3>
<button id="generateNewUuidBtn" class="uuid-generate-btn">
<button id="addIdentityBtn" class="uuid-generate-btn">
<i class="fas fa-plus"></i>
<span data-i18n="uuid.generateNew">Generate New UUID</span>
<span data-i18n="uuid.addIdentity">Add Identity</span>
</button>
</div>
<div id="uuidAddForm" class="uuid-add-form" style="display: none;">
<div class="uuid-add-form-row">
<input type="text" id="addIdentityUsername" class="uuid-input"
data-i18n-placeholder="uuid.usernamePlaceholder"
placeholder="Username" maxlength="16" />
</div>
<div class="uuid-add-form-row">
<input type="text" id="addIdentityUuid" class="uuid-input"
data-i18n-placeholder="uuid.customPlaceholder"
placeholder="UUID (auto-generated)" maxlength="36" />
<button id="addIdentityRegenerateBtn" class="uuid-action-btn regenerate-btn"
title="Generate new UUID">
<i class="fas fa-sync-alt"></i>
</button>
</div>
<div class="uuid-add-form-actions">
<button id="addIdentityConfirmBtn" class="uuid-set-btn">
<i class="fas fa-check"></i>
<span data-i18n="uuid.add">Add</span>
</button>
<button id="addIdentityCancelBtn" class="uuid-cancel-btn">
<i class="fas fa-times"></i>
<span data-i18n="uuid.cancel">Cancel</span>
</button>
</div>
</div>
<div id="uuidList" class="uuid-list">
<div class="uuid-loading">
<i class="fas fa-spinner fa-spin"></i>
@@ -766,21 +796,27 @@
</div>
</div>
<div class="uuid-custom-section">
<h3 class="uuid-section-title" data-i18n="uuid.setCustomUUID">Set Custom UUID</h3>
<div class="uuid-custom-form">
<input type="text" id="customUuidInput" class="uuid-input"
data-i18n-placeholder="uuid.customPlaceholder" maxlength="36" />
<button id="setCustomUuidBtn" class="uuid-set-btn">
<i class="fas fa-check"></i>
<span data-i18n="uuid.setUUID">Set UUID</span>
</button>
<div class="uuid-advanced-section">
<button id="uuidAdvancedToggle" class="uuid-advanced-toggle">
<i class="fas fa-chevron-right uuid-advanced-chevron"></i>
<span data-i18n="uuid.advanced">Advanced</span>
</button>
<div id="uuidAdvancedContent" class="uuid-advanced-content" style="display: none;">
<h3 class="uuid-section-title" data-i18n="uuid.setCustomUUID">Set Custom UUID</h3>
<div class="uuid-custom-form">
<input type="text" id="customUuidInput" class="uuid-input"
data-i18n-placeholder="uuid.customPlaceholder" maxlength="36" />
<button id="setCustomUuidBtn" class="uuid-set-btn">
<i class="fas fa-check"></i>
<span data-i18n="uuid.setUUID">Set UUID</span>
</button>
</div>
<p class="uuid-custom-hint">
<i class="fas fa-exclamation-triangle"></i>
<span data-i18n="uuid.warning">Warning: Setting a custom UUID will change your current player
identity</span>
</p>
</div>
<p class="uuid-custom-hint">
<i class="fas fa-exclamation-triangle"></i>
<span data-i18n="uuid.warning">Warning: Setting a custom UUID will change your current player
identity</span>
</p>
</div>
</div>
</div>
@@ -791,8 +827,8 @@
<div class="profile-modal-content">
<div class="profile-modal-header">
<h2 class="profile-modal-title">
<i class="fas fa-users-cog mr-2"></i>
<span data-i18n="profiles.modalTitle">Manage Profiles</span>
<i class="fas fa-sliders-h mr-2"></i>
<span data-i18n="configurations.modalTitle">Manage Configurations</span>
</h2>
<button class="modal-close-btn" onclick="closeProfileManager()">
<i class="fas fa-times"></i>
@@ -803,10 +839,10 @@
<!-- Populated by JS -->
</div>
<div class="profile-create-section">
<input type="text" id="newProfileName" data-i18n-placeholder="profiles.newProfilePlaceholder"
<input type="text" id="newProfileName" data-i18n-placeholder="configurations.newProfilePlaceholder"
class="profile-input" maxlength="20">
<button class="profile-create-btn" onclick="createNewProfile()">
<i class="fas fa-plus"></i> <span data-i18n="profiles.createProfile">Create Profile</span>
<i class="fas fa-plus"></i> <span data-i18n="configurations.createProfile">Create Configuration</span>
</button>
</div>
</div>

View File

@@ -35,13 +35,21 @@ export function setupLauncher() {
// Initial Profile Load
loadProfiles();
// Close dropdown on outside click
// Initial Identity Load
loadIdentities();
// Close dropdowns on outside click
document.addEventListener('click', (e) => {
const selector = document.getElementById('profileSelector');
if (selector && !selector.contains(e.target)) {
const profileSelector = document.getElementById('profileSelector');
if (profileSelector && !profileSelector.contains(e.target)) {
const dropdown = document.getElementById('profileDropdown');
if (dropdown) dropdown.classList.remove('show');
}
const identitySelector = document.getElementById('identitySelector');
if (identitySelector && !identitySelector.contains(e.target)) {
const dropdown = document.getElementById('identityDropdown');
if (dropdown) dropdown.classList.remove('show');
}
});
}
@@ -83,7 +91,7 @@ function renderProfileList(profiles, activeProfile) {
managerList.innerHTML = profiles.map(p => `
<div class="profile-manager-item ${p.id === activeProfile.id ? 'active' : ''}">
<div class="flex items-center gap-3">
<i class="fas fa-user-circle text-xl text-gray-400"></i>
<i class="fas fa-sliders-h text-xl text-gray-400"></i>
<div>
<div class="font-bold">${p.name}</div>
<div class="text-xs text-gray-500">ID: ${p.id.substring(0, 8)}...</div>
@@ -106,13 +114,6 @@ function updateCurrentProfileUI(profile) {
}
}
window.toggleProfileDropdown = () => {
const dropdown = document.getElementById('profileDropdown');
if (dropdown) {
dropdown.classList.toggle('show');
}
};
window.openProfileManager = () => {
const modal = document.getElementById('profileManagerModal');
if (modal) {
@@ -146,7 +147,7 @@ window.createNewProfile = async () => {
};
window.deleteProfile = async (id) => {
if (!confirm('Are you sure you want to delete this profile? parameters and mods configuration will be lost.')) return;
if (!confirm('Are you sure you want to delete this configuration? Mod settings will be lost.')) return;
try {
await window.electronAPI.profile.delete(id);
@@ -160,7 +161,7 @@ window.deleteProfile = async (id) => {
window.switchProfile = async (id) => {
try {
if (window.LauncherUI) window.LauncherUI.showProgress();
const switchingMsg = window.i18n ? window.i18n.t('progress.switchingProfile') : 'Switching Profile...';
const switchingMsg = window.i18n ? window.i18n.t('progress.switchingProfile') : 'Switching configuration...';
if (window.LauncherUI) window.LauncherUI.updateProgress({ message: switchingMsg });
await window.electronAPI.profile.activate(id);
@@ -179,7 +180,7 @@ window.switchProfile = async (id) => {
if (dropdown) dropdown.classList.remove('show');
if (window.LauncherUI) {
const switchedMsg = window.i18n ? window.i18n.t('progress.profileSwitched') : 'Profile Switched!';
const switchedMsg = window.i18n ? window.i18n.t('progress.profileSwitched') : 'Configuration switched!';
window.LauncherUI.updateProgress({ message: switchedMsg });
setTimeout(() => window.LauncherUI.hideProgress(), 1000);
}
@@ -676,6 +677,121 @@ async function loadCustomJavaPath() {
}
}
// ==========================================
// IDENTITY SWITCHER
// ==========================================
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
async function loadIdentities() {
try {
if (!window.electronAPI) return;
const nameEl = document.getElementById('currentIdentityName');
// Load current username
let currentUsername = 'Player';
if (window.electronAPI.loadUsername) {
const name = await window.electronAPI.loadUsername();
if (name) currentUsername = name;
}
if (nameEl) nameEl.textContent = currentUsername;
// Load all identities for dropdown
const list = document.getElementById('identityList');
if (!list || !window.electronAPI.getAllUuidMappings) return;
const mappings = await window.electronAPI.getAllUuidMappings();
renderIdentityList(mappings, currentUsername);
} catch (error) {
console.error('Failed to load identities:', error);
}
}
function renderIdentityList(mappings, currentUsername) {
const list = document.getElementById('identityList');
if (!list) return;
if (!mappings || mappings.length === 0) {
list.innerHTML = '<div class="identity-empty">No identities</div>';
return;
}
list.innerHTML = mappings.map(m => {
const safe = escapeHtml(m.username);
return `
<div class="identity-item ${m.username === currentUsername ? 'active' : ''}"
onclick="switchIdentity('${safe.replace(/'/g, "&#39;")}')">
<span>${safe}</span>
${m.username === currentUsername ? '<i class="fas fa-check ml-auto"></i>' : ''}
</div>
`;
}).join('');
}
window.toggleIdentityDropdown = () => {
const dropdown = document.getElementById('identityDropdown');
if (dropdown) {
dropdown.classList.toggle('show');
// Close profile dropdown
const profileDropdown = document.getElementById('profileDropdown');
if (profileDropdown) profileDropdown.classList.remove('show');
}
};
window.openIdentityManager = () => {
// Close dropdown
const dropdown = document.getElementById('identityDropdown');
if (dropdown) dropdown.classList.remove('show');
// Open UUID modal from settings
if (window.openUuidModal) {
window.openUuidModal();
}
};
window.switchIdentity = async (username) => {
try {
if (!window.electronAPI || !window.electronAPI.saveUsername) return;
const result = await window.electronAPI.saveUsername(username);
if (result && result.success === false) {
throw new Error(result.error || 'Failed to switch identity');
}
// Refresh identity dropdown
await loadIdentities();
// Close dropdown
const dropdown = document.getElementById('identityDropdown');
if (dropdown) dropdown.classList.remove('show');
// Update settings page username field and UUID display
const settingsInput = document.getElementById('settingsPlayerName');
if (settingsInput) settingsInput.value = username;
if (window.loadCurrentUuid) window.loadCurrentUuid();
} catch (error) {
console.error('Failed to switch identity:', error);
}
};
// Make loadIdentities available globally for settings.js to call
window.loadIdentities = loadIdentities;
window.toggleProfileDropdown = () => {
const dropdown = document.getElementById('profileDropdown');
if (dropdown) {
dropdown.classList.toggle('show');
// Close identity dropdown
const identityDropdown = document.getElementById('identityDropdown');
if (identityDropdown) identityDropdown.classList.remove('show');
}
};
window.launch = launch;
window.uninstallGame = uninstallGame;
window.repairGame = repairGame;

View File

@@ -18,10 +18,15 @@ let regenerateUuidBtn;
let manageUuidsBtn;
let uuidModal;
let uuidModalClose;
let modalCurrentUuid;
let modalCopyUuidBtn;
let modalRegenerateUuidBtn;
let generateNewUuidBtn;
let addIdentityBtn;
let uuidAddForm;
let addIdentityUsername;
let addIdentityUuid;
let addIdentityRegenerateBtn;
let addIdentityConfirmBtn;
let addIdentityCancelBtn;
let uuidAdvancedToggle;
let uuidAdvancedContent;
let uuidList;
let customUuidInput;
let setCustomUuidBtn;
@@ -180,10 +185,15 @@ function setupSettingsElements() {
manageUuidsBtn = document.getElementById('manageUuidsBtn');
uuidModal = document.getElementById('uuidModal');
uuidModalClose = document.getElementById('uuidModalClose');
modalCurrentUuid = document.getElementById('modalCurrentUuid');
modalCopyUuidBtn = document.getElementById('modalCopyUuidBtn');
modalRegenerateUuidBtn = document.getElementById('modalRegenerateUuidBtn');
generateNewUuidBtn = document.getElementById('generateNewUuidBtn');
addIdentityBtn = document.getElementById('addIdentityBtn');
uuidAddForm = document.getElementById('uuidAddForm');
addIdentityUsername = document.getElementById('addIdentityUsername');
addIdentityUuid = document.getElementById('addIdentityUuid');
addIdentityRegenerateBtn = document.getElementById('addIdentityRegenerateBtn');
addIdentityConfirmBtn = document.getElementById('addIdentityConfirmBtn');
addIdentityCancelBtn = document.getElementById('addIdentityCancelBtn');
uuidAdvancedToggle = document.getElementById('uuidAdvancedToggle');
uuidAdvancedContent = document.getElementById('uuidAdvancedContent');
uuidList = document.getElementById('uuidList');
customUuidInput = document.getElementById('customUuidInput');
setCustomUuidBtn = document.getElementById('setCustomUuidBtn');
@@ -230,16 +240,24 @@ function setupSettingsElements() {
uuidModalClose.addEventListener('click', closeUuidModal);
}
if (modalCopyUuidBtn) {
modalCopyUuidBtn.addEventListener('click', copyCurrentUuid);
if (addIdentityBtn) {
addIdentityBtn.addEventListener('click', showAddIdentityForm);
}
if (modalRegenerateUuidBtn) {
modalRegenerateUuidBtn.addEventListener('click', regenerateCurrentUuid);
if (addIdentityRegenerateBtn) {
addIdentityRegenerateBtn.addEventListener('click', regenerateAddIdentityUuid);
}
if (generateNewUuidBtn) {
generateNewUuidBtn.addEventListener('click', generateNewUuid);
if (addIdentityConfirmBtn) {
addIdentityConfirmBtn.addEventListener('click', confirmAddIdentity);
}
if (addIdentityCancelBtn) {
addIdentityCancelBtn.addEventListener('click', hideAddIdentityForm);
}
if (uuidAdvancedToggle) {
uuidAdvancedToggle.addEventListener('click', toggleAdvancedSection);
}
if (setCustomUuidBtn) {
@@ -467,6 +485,9 @@ async function savePlayerName() {
// Also refresh the UUID list to update which entry is marked as current
await loadAllUuids();
// Refresh header identity dropdown
if (window.loadIdentities) window.loadIdentities();
} catch (error) {
console.error('Error saving player name:', error);
const errorMsg = window.i18n ? window.i18n.t('notifications.playerNameSaveFailed') : 'Failed to save player name';
@@ -630,7 +651,6 @@ async function loadCurrentUuid() {
const uuid = await window.electronAPI.getCurrentUuid();
if (uuid) {
if (currentUuidDisplay) currentUuidDisplay.value = uuid;
if (modalCurrentUuid) modalCurrentUuid.value = uuid;
}
}
} catch (error) {
@@ -640,7 +660,7 @@ async function loadCurrentUuid() {
async function copyCurrentUuid() {
try {
const uuid = currentUuidDisplay ? currentUuidDisplay.value : modalCurrentUuid?.value;
const uuid = currentUuidDisplay ? currentUuidDisplay.value : null;
if (uuid && navigator.clipboard) {
await navigator.clipboard.writeText(uuid);
const msg = window.i18n ? window.i18n.t('notifications.uuidCopied') : 'UUID copied to clipboard!';
@@ -688,13 +708,13 @@ async function performRegenerateUuid() {
const result = await window.electronAPI.resetCurrentUserUuid();
if (result.success && result.uuid) {
if (currentUuidDisplay) currentUuidDisplay.value = result.uuid;
if (modalCurrentUuid) modalCurrentUuid.value = result.uuid;
const msg = window.i18n ? window.i18n.t('notifications.uuidGenerated') : 'New UUID generated successfully!';
showNotification(msg, 'success');
if (uuidModal && uuidModal.style.display !== 'none') {
await loadAllUuids();
}
if (window.loadIdentities) window.loadIdentities();
} else {
throw new Error(result.error || 'Failed to generate new UUID');
}
@@ -717,6 +737,10 @@ async function openUuidModal() {
}
}
// Expose globally so identity dropdown and Escape handler can use them
window.openUuidModal = openUuidModal;
window.loadCurrentUuid = loadCurrentUuid;
function closeUuidModal() {
if (uuidModal) {
uuidModal.classList.remove('active');
@@ -725,6 +749,7 @@ function closeUuidModal() {
}, 300);
}
}
window.closeUuidModal = closeUuidModal;
async function loadAllUuids() {
try {
@@ -769,6 +794,9 @@ async function loadAllUuids() {
<button class="uuid-item-btn copy" onclick="copyUuid('${mapping.uuid}')" title="Copy UUID">
<i class="fas fa-copy"></i>
</button>
${mapping.isCurrent ? `<button class="uuid-item-btn regenerate" onclick="regenerateUuidForUser('${escapeHtml(mapping.username)}')" title="Regenerate UUID">
<i class="fas fa-sync-alt"></i>
</button>` : ''}
${!mapping.isCurrent ? `<button class="uuid-item-btn delete" onclick="deleteUuid('${escapeHtml(mapping.username)}')" title="Delete UUID">
<i class="fas fa-trash"></i>
</button>` : ''}
@@ -791,23 +819,119 @@ async function loadAllUuids() {
}
}
async function generateNewUuid() {
async function showAddIdentityForm() {
if (!uuidAddForm) return;
uuidAddForm.style.display = 'block';
if (addIdentityUsername) {
addIdentityUsername.value = '';
addIdentityUsername.focus();
}
if (addIdentityUuid) {
try {
if (window.electronAPI && window.electronAPI.generateNewUuid) {
const newUuid = await window.electronAPI.generateNewUuid();
if (newUuid) addIdentityUuid.value = newUuid;
}
} catch (e) {
console.error('Error pre-generating UUID:', e);
}
}
if (addIdentityBtn) addIdentityBtn.style.display = 'none';
}
function hideAddIdentityForm() {
if (uuidAddForm) uuidAddForm.style.display = 'none';
if (addIdentityBtn) addIdentityBtn.style.display = '';
}
async function regenerateAddIdentityUuid() {
try {
if (window.electronAPI && window.electronAPI.generateNewUuid) {
const newUuid = await window.electronAPI.generateNewUuid();
if (newUuid) {
if (customUuidInput) customUuidInput.value = newUuid;
const msg = window.i18n ? window.i18n.t('notifications.uuidGeneratedShort') : 'New UUID generated!';
showNotification(msg, 'success');
if (newUuid && addIdentityUuid) {
addIdentityUuid.value = newUuid;
}
}
} catch (error) {
console.error('Error generating new UUID:', error);
const msg = window.i18n ? window.i18n.t('notifications.uuidGenerateFailed') : 'Failed to generate new UUID';
console.error('Error generating UUID:', error);
}
}
async function confirmAddIdentity() {
try {
const username = addIdentityUsername ? addIdentityUsername.value.trim() : '';
const uuid = addIdentityUuid ? addIdentityUuid.value.trim() : '';
if (!username) {
const msg = window.i18n ? window.i18n.t('notifications.playerNameRequired') : 'Please enter a username';
showNotification(msg, 'error');
return;
}
if (username.length > 16) {
const msg = window.i18n ? window.i18n.t('notifications.playerNameTooLong') : 'Username must be 16 characters or less';
showNotification(msg, 'error');
return;
}
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!uuid || !uuidRegex.test(uuid)) {
const msg = window.i18n ? window.i18n.t('notifications.uuidInvalidFormat') : 'Invalid UUID format';
showNotification(msg, 'error');
return;
}
if (window.electronAPI && window.electronAPI.setUuidForUser) {
const result = await window.electronAPI.setUuidForUser(username, uuid);
if (result.success) {
const msg = window.i18n ? window.i18n.t('notifications.identityAdded') : 'Identity added successfully!';
showNotification(msg, 'success');
hideAddIdentityForm();
await loadAllUuids();
if (window.loadIdentities) window.loadIdentities();
} else {
throw new Error(result.error || 'Failed to add identity');
}
}
} catch (error) {
console.error('Error adding identity:', error);
const msg = window.i18n ? window.i18n.t('notifications.identityAddFailed') : 'Failed to add identity';
showNotification(msg, 'error');
}
}
function toggleAdvancedSection() {
if (!uuidAdvancedContent || !uuidAdvancedToggle) return;
const isOpen = uuidAdvancedContent.style.display !== 'none';
uuidAdvancedContent.style.display = isOpen ? 'none' : 'block';
const chevron = uuidAdvancedToggle.querySelector('.uuid-advanced-chevron');
if (chevron) {
chevron.classList.toggle('open', !isOpen);
}
}
window.regenerateUuidForUser = async function (username) {
try {
const message = window.i18n ? window.i18n.t('confirm.regenerateUuidMessage') : 'Are you sure you want to generate a new UUID? This will change your player identity.';
const title = window.i18n ? window.i18n.t('confirm.regenerateUuidTitle') : 'Generate New UUID';
const confirmBtn = window.i18n ? window.i18n.t('confirm.regenerateUuidButton') : 'Generate';
const cancelBtn = window.i18n ? window.i18n.t('common.cancel') : 'Cancel';
showCustomConfirm(
message,
title,
async () => {
await performRegenerateUuid();
},
null,
confirmBtn,
cancelBtn
);
} catch (error) {
console.error('Error regenerating UUID:', error);
}
};
async function setCustomUuid() {
try {
if (!customUuidInput || !customUuidInput.value.trim()) {
@@ -865,13 +989,13 @@ async function performSetCustomUuid(uuid) {
if (result.success) {
if (currentUuidDisplay) currentUuidDisplay.value = uuid;
if (modalCurrentUuid) modalCurrentUuid.value = uuid;
if (customUuidInput) customUuidInput.value = '';
const msg = window.i18n ? window.i18n.t('notifications.uuidSetSuccess') : 'Custom UUID set successfully!';
showNotification(msg, 'success');
await loadAllUuids();
if (window.loadIdentities) window.loadIdentities();
} else {
throw new Error(result.error || 'Failed to set custom UUID');
}
@@ -950,6 +1074,9 @@ async function performSwitchToUsername(username) {
// Refresh the UUID list to show new "Current" badge
await loadAllUuids();
// Refresh header identity dropdown
if (window.loadIdentities) window.loadIdentities();
const msg = window.i18n
? window.i18n.t('notifications.switchUsernameSuccess').replace('{username}', username)
: `Switched to "${username}" successfully!`;
@@ -997,6 +1124,7 @@ async function performDeleteUuid(username) {
const msg = window.i18n ? window.i18n.t('notifications.uuidDeleteSuccess') : 'UUID deleted successfully!';
showNotification(msg, 'success');
await loadAllUuids();
if (window.loadIdentities) window.loadIdentities();
} else {
throw new Error(result.error || 'Failed to delete UUID');
}

View File

@@ -79,12 +79,18 @@ function setupWindowControls() {
const header = document.querySelector('.header');
const profileSelector = document.querySelector('.profile-selector');
const identitySelector = document.querySelector('.identity-selector');
if (profileSelector) {
profileSelector.style.pointerEvents = 'auto';
profileSelector.style.zIndex = '10000';
}
if (identitySelector) {
identitySelector.style.pointerEvents = 'auto';
identitySelector.style.zIndex = '10000';
}
if (windowControls) {
windowControls.style.pointerEvents = 'auto';
windowControls.style.zIndex = '10000';
@@ -98,6 +104,9 @@ function setupWindowControls() {
if (profileSelector) {
profileSelector.style.webkitAppRegion = 'no-drag';
}
if (identitySelector) {
identitySelector.style.webkitAppRegion = 'no-drag';
}
}
if (window.electronAPI) {
@@ -1109,4 +1118,50 @@ window.openDiscordExternal = function() {
window.toggleMaximize = toggleMaximize;
// Global Escape key handler for closing popups/modals/dropdowns
document.addEventListener('keydown', (e) => {
if (e.key !== 'Escape') return;
// Custom confirm dialogs handle their own Escape — skip if one is open
if (document.querySelector('.custom-confirm-modal')) return;
// Close modals (highest priority)
const profileModal = document.getElementById('profileManagerModal');
if (profileModal && profileModal.style.display !== 'none') {
if (window.closeProfileManager) window.closeProfileManager();
return;
}
const uuidModal = document.getElementById('uuidModal');
if (uuidModal && uuidModal.style.display !== 'none') {
if (window.closeUuidModal) window.closeUuidModal();
return;
}
const discordModal = document.getElementById('discordPopupModal');
if (discordModal && discordModal.style.display !== 'none') {
discordModal.style.display = 'none';
return;
}
const versionModal = document.getElementById('versionSelectModal');
if (versionModal && versionModal.style.display !== 'none') {
versionModal.style.display = 'none';
return;
}
// Close dropdowns (lower priority)
const identityDropdown = document.getElementById('identityDropdown');
if (identityDropdown && identityDropdown.classList.contains('show')) {
identityDropdown.classList.remove('show');
return;
}
const profileDropdown = document.getElementById('profileDropdown');
if (profileDropdown && profileDropdown.classList.contains('show')) {
profileDropdown.classList.remove('show');
return;
}
});
document.addEventListener('DOMContentLoaded', setupUI);

View File

@@ -8,7 +8,10 @@
},
"header": {
"playersLabel": "اللاعبون:",
"manageProfiles": "إدارة الملفات الشخصية",
"manageProfiles": "إدارة",
"manageIdentities": "إدارة",
"identityTooltip": "اسم اللاعب ومعرّف UUID المستخدمان في اللعبة",
"configTooltip": "إعدادات اللعبة: المودات، Java والذاكرة",
"defaultProfile": "الافتراضي"
},
"install": {
@@ -151,9 +154,12 @@
},
"uuid": {
"modalTitle": "إدارة UUID",
"currentUserUUID": "UUID المستخدم الحالي",
"allPlayerUUIDs": "جميع معرفات UUID للاعبين",
"generateNew": "إنشاء UUID جديد",
"addIdentity": "إضافة هوية",
"usernamePlaceholder": "اسم المستخدم",
"add": "إضافة",
"cancel": "إلغاء",
"advanced": "متقدم",
"loadingUUIDs": "جاري تحميل الـ UUIDs...",
"setCustomUUID": "تعيين UUID مخصص",
"customPlaceholder": "أدخل UUID مخصص (الصيغة: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +168,10 @@
"copyTooltip": "نسخ UUID",
"regenerateTooltip": "إنشاء UUID جديد"
},
"profiles": {
"modalTitle": "إدارة الملفات الشخصية",
"newProfilePlaceholder": "اسم الملف الشخصي الجديد",
"createProfile": "إنشاء ملف شخصي"
"configurations": {
"modalTitle": "إدارة التكوينات",
"newProfilePlaceholder": "اسم التكوين الجديد",
"createProfile": "إنشاء تكوين"
},
"discord": {
"notificationText": "انضم إلى مجتمعنا على ديسكورد!",
@@ -219,6 +225,8 @@
"hwAccelSaved": "تم حفظ إعداد تسريع الأجهزة",
"hwAccelSaveFailed": "فشل حفظ إعداد تسريع الأجهزة",
"noUsername": "لم يتم تهيئة اسم مستخدم. يرجى حفظ اسم المستخدم أولاً.",
"identityAdded": "تمت إضافة الهوية بنجاح!",
"identityAddFailed": "فشل في إضافة الهوية",
"switchUsernameSuccess": "تم التبديل إلى المستخدم \"{username}\" بنجاح!",
"switchUsernameFailed": "فشل تبديل اسم المستخدم",
"playerNameTooLong": "يجب أن يكون اسم اللاعب 16 حرفاً أو أقل"
@@ -247,8 +255,8 @@
"installing": "جاري التثبيت...",
"extracting": "جاري الاستخراج...",
"verifying": "جاري التحقق...",
"switchingProfile": "جاري تبديل الملف الشخصي...",
"profileSwitched": "تم تبديل الملف الشخصي!",
"switchingProfile": "جاري تبديل التكوين...",
"profileSwitched": "تم تبديل التكوين!",
"startingGame": "جاري بدء اللعبة...",
"launching": "جاري التشغيل...",
"uninstallingGame": "جاري إلغاء تثبيت اللعبة...",

View File

@@ -8,7 +8,10 @@
},
"header": {
"playersLabel": "Spieler:",
"manageProfiles": "Profile verwalten",
"manageProfiles": "Verwalten",
"manageIdentities": "Verwalten",
"identityTooltip": "Dein Spielername und UUID im Spiel",
"configTooltip": "Spielkonfiguration: Mods, Java- und Speichereinstellungen",
"defaultProfile": "Standard"
},
"install": {
@@ -151,9 +154,12 @@
},
"uuid": {
"modalTitle": "UUID-Verwaltung",
"currentUserUUID": "Aktuelle Benutzer-UUID",
"allPlayerUUIDs": "Alle Spieler-UUIDs",
"generateNew": "Neue UUID generieren",
"addIdentity": "Identität hinzufügen",
"usernamePlaceholder": "Benutzername",
"add": "Hinzufügen",
"cancel": "Abbrechen",
"advanced": "Erweitert",
"loadingUUIDs": "UUIDs werden geladen...",
"setCustomUUID": "Benutzerdefinierte UUID festlegen",
"customPlaceholder": "Benutzerdefinierte UUID eingeben (Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +168,10 @@
"copyTooltip": "UUID kopieren",
"regenerateTooltip": "Neue UUID generieren"
},
"profiles": {
"modalTitle": "Profile verwalten",
"newProfilePlaceholder": "Neuer Profilname",
"createProfile": "Profil erstellen"
"configurations": {
"modalTitle": "Konfigurationen verwalten",
"newProfilePlaceholder": "Neuer Konfigurationsname",
"createProfile": "Konfiguration erstellen"
},
"discord": {
"notificationText": "Tritt unserer Discord-Community bei!",
@@ -219,6 +225,8 @@
"hwAccelSaved": "Hardware-Beschleunigungseinstellung gespeichert",
"hwAccelSaveFailed": "Hardware-Beschleunigungseinstellung konnte nicht gespeichert werden",
"noUsername": "Kein Benutzername konfiguriert. Bitte speichere zuerst deinen Benutzernamen.",
"identityAdded": "Identität erfolgreich hinzugefügt!",
"identityAddFailed": "Fehler beim Hinzufügen der Identität",
"switchUsernameSuccess": "Erfolgreich zu \"{username}\" gewechselt!",
"switchUsernameFailed": "Benutzername konnte nicht gewechselt werden",
"playerNameTooLong": "Spielername darf maximal 16 Zeichen haben"
@@ -247,8 +255,8 @@
"installing": "Installiere...",
"extracting": "Entpacke...",
"verifying": "Überprüfe...",
"switchingProfile": "Profil wird gewechselt...",
"profileSwitched": "Profil gewechselt!",
"switchingProfile": "Konfiguration wird gewechselt...",
"profileSwitched": "Konfiguration gewechselt!",
"startingGame": "Spiel wird gestartet...",
"launching": "STARTET...",
"uninstallingGame": "Spiel wird deinstalliert...",

View File

@@ -8,7 +8,10 @@
},
"header": {
"playersLabel": "Players:",
"manageProfiles": "Manage Profiles",
"manageProfiles": "Manage",
"manageIdentities": "Manage",
"identityTooltip": "Your player name & UUID used in-game",
"configTooltip": "Game config: mods, Java & memory settings",
"defaultProfile": "Default"
},
"install": {
@@ -162,21 +165,24 @@
},
"uuid": {
"modalTitle": "UUID Management",
"currentUserUUID": "Current User UUID",
"allPlayerUUIDs": "All Player UUIDs",
"generateNew": "Generate New UUID",
"addIdentity": "Add Identity",
"usernamePlaceholder": "Username",
"add": "Add",
"cancel": "Cancel",
"advanced": "Advanced",
"loadingUUIDs": "Loading UUIDs...",
"setCustomUUID": "Set Custom UUID",
"setCustomUUID": "Set Custom UUID for Current User",
"customPlaceholder": "Enter custom UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
"setUUID": "Set UUID",
"warning": "Warning: Setting a custom UUID will change your current player identity",
"copyTooltip": "Copy UUID",
"regenerateTooltip": "Generate New UUID"
},
"profiles": {
"modalTitle": "Manage Profiles",
"newProfilePlaceholder": "New Profile Name",
"createProfile": "Create Profile"
"configurations": {
"modalTitle": "Manage Configurations",
"newProfilePlaceholder": "New Configuration Name",
"createProfile": "Create Configuration"
},
"discord": {
"notificationText": "Join our Discord community!",
@@ -230,6 +236,8 @@
"hwAccelSaved": "Hardware acceleration setting saved",
"hwAccelSaveFailed": "Failed to save hardware acceleration setting",
"noUsername": "No username configured. Please save your username first.",
"identityAdded": "Identity added successfully!",
"identityAddFailed": "Failed to add identity",
"switchUsernameSuccess": "Switched to \"{username}\" successfully!",
"switchUsernameFailed": "Failed to switch username",
"playerNameTooLong": "Player name must be 16 characters or less",
@@ -266,8 +274,8 @@
"installing": "Installing...",
"extracting": "Extracting...",
"verifying": "Verifying...",
"switchingProfile": "Switching profile...",
"profileSwitched": "Profile switched!",
"switchingProfile": "Switching configuration...",
"profileSwitched": "Configuration switched!",
"startingGame": "Starting game...",
"launching": "LAUNCHING...",
"uninstallingGame": "Uninstalling game...",

View File

@@ -8,7 +8,10 @@
},
"header": {
"playersLabel": "Jugadores:",
"manageProfiles": "Gestionar Perfiles",
"manageProfiles": "Gestionar",
"manageIdentities": "Gestionar",
"identityTooltip": "Tu nombre de jugador y UUID usados en el juego",
"configTooltip": "Configuración del juego: mods, Java y memoria",
"defaultProfile": "Predeterminado"
},
"install": {
@@ -151,9 +154,12 @@
},
"uuid": {
"modalTitle": "Gestión de UUID",
"currentUserUUID": "UUID del usuario actual",
"allPlayerUUIDs": "Todos los UUIDs de jugadores",
"generateNew": "Generar nuevo UUID",
"addIdentity": "Añadir identidad",
"usernamePlaceholder": "Nombre de usuario",
"add": "Añadir",
"cancel": "Cancelar",
"advanced": "Avanzado",
"loadingUUIDs": "Cargando UUIDs...",
"setCustomUUID": "Establecer UUID personalizado",
"customPlaceholder": "Ingresa un UUID personalizado (formato: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +168,10 @@
"copyTooltip": "Copiar UUID",
"regenerateTooltip": "Generar nuevo UUID"
},
"profiles": {
"modalTitle": "Gestionar perfiles",
"newProfilePlaceholder": "Nombre del nuevo perfil",
"createProfile": "Crear perfil"
"configurations": {
"modalTitle": "Gestionar Configuraciones",
"newProfilePlaceholder": "Nombre de la nueva configuración",
"createProfile": "Crear Configuración"
},
"discord": {
"notificationText": "¡Únete a nuestra comunidad de Discord!",
@@ -219,6 +225,8 @@
"hwAccelSaved": "Configuración de aceleración por hardware guardada",
"hwAccelSaveFailed": "Error al guardar la configuración de aceleración por hardware",
"noUsername": "No hay nombre de usuario configurado. Por favor, guarda tu nombre de usuario primero.",
"identityAdded": "¡Identidad añadida con éxito!",
"identityAddFailed": "Error al añadir identidad",
"switchUsernameSuccess": "¡Cambiado a \"{username}\" con éxito!",
"switchUsernameFailed": "Error al cambiar nombre de usuario",
"playerNameTooLong": "El nombre del jugador debe tener 16 caracteres o menos"
@@ -247,8 +255,8 @@
"installing": "Instalando...",
"extracting": "Extrayendo...",
"verifying": "Verificando...",
"switchingProfile": "Cambiando perfil...",
"profileSwitched": Perfil cambiado!",
"switchingProfile": "Cambiando configuración...",
"profileSwitched": Configuración cambiada!",
"startingGame": "Iniciando juego...",
"launching": "INICIANDO...",
"uninstallingGame": "Desinstalando juego...",

View File

@@ -8,7 +8,10 @@
},
"header": {
"playersLabel": "Joueurs:",
"manageProfiles": "Gérer les Profils",
"manageProfiles": "Gérer",
"manageIdentities": "Gérer",
"identityTooltip": "Votre nom de joueur et UUID utilisés en jeu",
"configTooltip": "Configuration du jeu : mods, Java et mémoire",
"defaultProfile": "Par défaut"
},
"install": {
@@ -151,9 +154,12 @@
},
"uuid": {
"modalTitle": "Gestion UUID",
"currentUserUUID": "UUID Utilisateur Actuel",
"allPlayerUUIDs": "Tous les UUIDs Joueurs",
"generateNew": "Générer Nouvel UUID",
"addIdentity": "Ajouter une identité",
"usernamePlaceholder": "Nom d'utilisateur",
"add": "Ajouter",
"cancel": "Annuler",
"advanced": "Avancé",
"loadingUUIDs": "Chargement des UUIDs...",
"setCustomUUID": "Définir UUID Personnalisé",
"customPlaceholder": "Entrez UUID personnalisé (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +168,10 @@
"copyTooltip": "Copier UUID",
"regenerateTooltip": "Générer Nouvel UUID"
},
"profiles": {
"modalTitle": "Gérer les Profils",
"newProfilePlaceholder": "Nom du Nouveau Profil",
"createProfile": "Créer un Profil"
"configurations": {
"modalTitle": "Gérer les Configurations",
"newProfilePlaceholder": "Nom de la Nouvelle Configuration",
"createProfile": "Créer une Configuration"
},
"discord": {
"notificationText": "Rejoignez notre communauté Discord!",
@@ -219,6 +225,8 @@
"hwAccelSaved": "Paramètre d'accélération matérielle sauvegardé",
"hwAccelSaveFailed": "Échec de la sauvegarde du paramètre d'accélération matérielle",
"noUsername": "Aucun nom d'utilisateur configuré. Veuillez d'abord enregistrer votre nom d'utilisateur.",
"identityAdded": "Identité ajoutée avec succès !",
"identityAddFailed": "Échec de l'ajout de l'identité",
"switchUsernameSuccess": "Basculé vers \"{username}\" avec succès!",
"switchUsernameFailed": "Échec du changement de nom d'utilisateur",
"playerNameTooLong": "Le nom du joueur doit comporter 16 caractères ou moins"
@@ -247,8 +255,8 @@
"installing": "Installation...",
"extracting": "Extraction...",
"verifying": "Vérification...",
"switchingProfile": "Changement de profil...",
"profileSwitched": "Profil changé!",
"switchingProfile": "Changement de configuration...",
"profileSwitched": "Configuration changée !",
"startingGame": "Démarrage du jeu...",
"launching": "LANCEMENT...",
"uninstallingGame": "Désinstallation du jeu...",

View File

@@ -8,7 +8,10 @@
},
"header": {
"playersLabel": "Pemain:",
"manageProfiles": "Kelola Profil",
"manageProfiles": "Kelola",
"manageIdentities": "Kelola",
"identityTooltip": "Nama pemain & UUID yang digunakan dalam game",
"configTooltip": "Konfigurasi game: mod, Java & pengaturan memori",
"defaultProfile": "Default"
},
"install": {
@@ -151,9 +154,12 @@
},
"uuid": {
"modalTitle": "Manajemen UUID",
"currentUserUUID": "UUID Pengguna Saat Ini",
"allPlayerUUIDs": "Semua UUID Pemain",
"generateNew": "Hasilkan UUID Baru",
"addIdentity": "Tambah Identitas",
"usernamePlaceholder": "Nama Pengguna",
"add": "Tambah",
"cancel": "Batal",
"advanced": "Lanjutan",
"loadingUUIDs": "Memuat UUID...",
"setCustomUUID": "Setel UUID Kustom",
"customPlaceholder": "Masukkan UUID kustom (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +168,10 @@
"copyTooltip": "Salin UUID",
"regenerateTooltip": "Hasilkan UUID Baru"
},
"profiles": {
"modalTitle": "Kelola Profil",
"newProfilePlaceholder": "Nama Profil Baru",
"createProfile": "Buat Profil"
"configurations": {
"modalTitle": "Kelola Konfigurasi",
"newProfilePlaceholder": "Nama Konfigurasi Baru",
"createProfile": "Buat Konfigurasi"
},
"discord": {
"notificationText": "Gabung komunitas Discord kami!",
@@ -219,6 +225,8 @@
"hwAccelSaved": "Pengaturan akselerasi perangkat keras disimpan",
"hwAccelSaveFailed": "Gagal menyimpan pengaturan akselerasi perangkat keras",
"noUsername": "Nama pengguna belum dikonfigurasi. Silakan simpan nama pengguna terlebih dahulu.",
"identityAdded": "Identitas berhasil ditambahkan!",
"identityAddFailed": "Gagal menambahkan identitas",
"switchUsernameSuccess": "Berhasil beralih ke \"{username}\"!",
"switchUsernameFailed": "Gagal beralih nama pengguna",
"playerNameTooLong": "Nama pemain harus 16 karakter atau kurang"
@@ -247,8 +255,8 @@
"installing": "Menginstal...",
"extracting": "Mengekstrak...",
"verifying": "Memverifikasi...",
"switchingProfile": "Beralih profil...",
"profileSwitched": "Profil dialihkan!",
"switchingProfile": "Beralih konfigurasi...",
"profileSwitched": "Konfigurasi dialihkan!",
"startingGame": "Memulai game...",
"launching": "MELUNCURKAN...",
"uninstallingGame": "Menghapus instalasi game...",

View File

@@ -8,7 +8,10 @@
},
"header": {
"playersLabel": "Graczy:",
"manageProfiles": "Zarządzaj Profilami",
"manageProfiles": "Zarządzaj",
"manageIdentities": "Zarządzaj",
"identityTooltip": "Twoja nazwa gracza i UUID używane w grze",
"configTooltip": "Konfiguracja gry: mody, Java i ustawienia pamięci",
"defaultProfile": "Domyślny"
},
"install": {
@@ -151,9 +154,12 @@
},
"uuid": {
"modalTitle": "Zarządzanie UUID",
"currentUserUUID": "Aktualny UUID użytkownika",
"allPlayerUUIDs": "Wszystkie identyfikatory UUID graczy",
"generateNew": "Wygeneruj nowy UUID",
"addIdentity": "Dodaj tożsamość",
"usernamePlaceholder": "Nazwa użytkownika",
"add": "Dodaj",
"cancel": "Anuluj",
"advanced": "Zaawansowane",
"loadingUUIDs": "Ładowanie UUID...",
"setCustomUUID": "Ustaw niestandardowy UUID",
"customPlaceholder": "Wprowadź niestandardowy UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +168,10 @@
"copyTooltip": "Kopiuj UUID",
"regenerateTooltip": "Wygeneruj nowy UUID"
},
"profiles": {
"modalTitle": "Zarządzaj Profilami",
"newProfilePlaceholder": "Nowa Nazwa Profilu",
"createProfile": "Utwórz Profil"
"configurations": {
"modalTitle": "Zarządzaj Konfiguracjami",
"newProfilePlaceholder": "Nazwa Nowej Konfiguracji",
"createProfile": "Utwórz Konfigurację"
},
"discord": {
"notificationText": "Dołącz do naszej społeczności Discord!",
@@ -219,6 +225,8 @@
"hwAccelSaved": "Zapisano ustawienie przyspieszenia sprzętowego",
"hwAccelSaveFailed": "Nie udało się zapisać ustawienia przyspieszenia sprzętowego",
"noUsername": "Nie skonfigurowano nazwy użytkownika. Najpierw zapisz swoją nazwę użytkownika.",
"identityAdded": "Tożsamość dodana pomyślnie!",
"identityAddFailed": "Nie udało się dodać tożsamości",
"switchUsernameSuccess": "Pomyślnie przełączono na \"{username}\"!",
"switchUsernameFailed": "Nie udało się przełączyć nazwy użytkownika",
"playerNameTooLong": "Nazwa gracza musi mieć 16 znaków lub mniej"
@@ -247,8 +255,8 @@
"installing": "Instalowanie...",
"extracting": "Ekstraktowanie...",
"verifying": "Weryfikowanie...",
"switchingProfile": "Przełączanie profilu...",
"profileSwitched": "Profil zmieniony!",
"switchingProfile": "Przełączanie konfiguracji...",
"profileSwitched": "Konfiguracja zmieniona!",
"startingGame": "Uruchamianie gry...",
"launching": "URUCHAMIANIE...",
"uninstallingGame": "Odinstalowywanie gry...",

View File

@@ -8,7 +8,10 @@
},
"header": {
"playersLabel": "Jogadores:",
"manageProfiles": "Gerenciar Perfis",
"manageProfiles": "Gerenciar",
"manageIdentities": "Gerenciar",
"identityTooltip": "Seu nome de jogador e UUID usados no jogo",
"configTooltip": "Configuração do jogo: mods, Java e memória",
"defaultProfile": "Padrão"
},
"install": {
@@ -151,9 +154,12 @@
},
"uuid": {
"modalTitle": "Gerenciamento de UUID",
"currentUserUUID": "UUID do usuário atual",
"allPlayerUUIDs": "Todos os UUIDs de jogadores",
"generateNew": "Gerar novo UUID",
"addIdentity": "Adicionar identidade",
"usernamePlaceholder": "Nome de usuário",
"add": "Adicionar",
"cancel": "Cancelar",
"advanced": "Avançado",
"loadingUUIDs": "Carregando UUIDs...",
"setCustomUUID": "Definir UUID personalizado",
"customPlaceholder": "Digite um UUID personalizado (formato: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +168,10 @@
"copyTooltip": "Copiar UUID",
"regenerateTooltip": "Gerar novo UUID"
},
"profiles": {
"modalTitle": "Gerenciar perfis",
"newProfilePlaceholder": "Nome do novo perfil",
"createProfile": "Criar perfil"
"configurations": {
"modalTitle": "Gerenciar Configurações",
"newProfilePlaceholder": "Nome da Nova Configuração",
"createProfile": "Criar Configuração"
},
"discord": {
"notificationText": "Junte-se à nossa comunidade do Discord!",
@@ -219,6 +225,8 @@
"hwAccelSaved": "Configuração de aceleração de hardware salva",
"hwAccelSaveFailed": "Falha ao salvar configuração de aceleração de hardware",
"noUsername": "Nenhum nome de usuário configurado. Por favor, salve seu nome de usuário primeiro.",
"identityAdded": "Identidade adicionada com sucesso!",
"identityAddFailed": "Falha ao adicionar identidade",
"switchUsernameSuccess": "Alterado para \"{username}\" com sucesso!",
"switchUsernameFailed": "Falha ao trocar nome de usuário",
"playerNameTooLong": "O nome do jogador deve ter 16 caracteres ou menos"
@@ -247,8 +255,8 @@
"installing": "Instalando...",
"extracting": "Extraindo...",
"verifying": "Verificando...",
"switchingProfile": "Alternando perfil...",
"profileSwitched": "Perfil alternado!",
"switchingProfile": "Alternando configuração...",
"profileSwitched": "Configuração alternada!",
"startingGame": "Iniciando jogo...",
"launching": "INICIANDO...",
"uninstallingGame": "Desinstalando jogo...",

View File

@@ -8,7 +8,10 @@
},
"header": {
"playersLabel": "Игроки:",
"manageProfiles": "Управлять профилями:",
"manageProfiles": "Управление",
"manageIdentities": "Управление",
"identityTooltip": "Ваш игровой ник и UUID, используемые в игре",
"configTooltip": "Конфигурация игры: моды, Java и настройки памяти",
"defaultProfile": "По умолчанию"
},
"install": {
@@ -151,9 +154,12 @@
},
"uuid": {
"modalTitle": "Управление UUID",
"currentUserUUID": "UUID текущего пользователя",
"allPlayerUUIDs": "UUID всех игроков",
"generateNew": "Сгенерировать новый UUID",
"addIdentity": "Добавить личность",
"usernamePlaceholder": "Имя пользователя",
"add": "Добавить",
"cancel": "Отмена",
"advanced": "Дополнительно",
"loadingUUIDs": "Загрузка UUID...",
"setCustomUUID": "Установить кастомный UUID",
"customPlaceholder": "Ввести кастомный UUID (форматы: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +168,10 @@
"copyTooltip": "Скопировать UUID",
"regenerateTooltip": "Сгенерировать новый UUID"
},
"profiles": {
"modalTitle": "Управление профилями",
"newProfilePlaceholder": "Новое имя профиля",
"createProfile": "Создать профиль"
"configurations": {
"modalTitle": "Управление конфигурациями",
"newProfilePlaceholder": "Название новой конфигурации",
"createProfile": "Создать конфигурацию"
},
"discord": {
"notificationText": "Присоединитесь к нашему сообществу в Discord!",
@@ -219,6 +225,8 @@
"hwAccelSaved": "Настройка аппаратного ускорения сохранена!",
"hwAccelSaveFailed": "Не удалось сохранить настройку аппаратного ускорения",
"noUsername": "Имя пользователя не настроено. Пожалуйста, сначала сохраните имя пользователя.",
"identityAdded": "Личность успешно добавлена!",
"identityAddFailed": "Не удалось добавить личность",
"switchUsernameSuccess": "Успешно переключено на \"{username}\"!",
"switchUsernameFailed": "Не удалось переключить имя пользователя",
"playerNameTooLong": "Имя игрока должно быть не более 16 символов"
@@ -247,8 +255,8 @@
"installing": "Установка...",
"extracting": "Извлечение...",
"verifying": "Проверка...",
"switchingProfile": "Смена профиля...",
"profileSwitched": "Профиль сменён!",
"switchingProfile": "Смена конфигурации...",
"profileSwitched": "Конфигурация изменена!",
"startingGame": "Запуск игры...",
"launching": "ЗАПУСК...",
"uninstallingGame": "Удаление игры...",

View File

@@ -8,7 +8,10 @@
},
"header": {
"playersLabel": "Spelare:",
"manageProfiles": "Hantera profiler",
"manageProfiles": "Hantera",
"manageIdentities": "Hantera",
"identityTooltip": "Ditt spelarnamn och UUID som används i spelet",
"configTooltip": "Spelkonfiguration: moddar, Java- och minnesinställningar",
"defaultProfile": "Standard"
},
"install": {
@@ -151,9 +154,12 @@
},
"uuid": {
"modalTitle": "UUID-hantering",
"currentUserUUID": "Nuvarande användar-UUID",
"allPlayerUUIDs": "Alla spelare-UUID:er",
"generateNew": "Generera ny UUID",
"addIdentity": "Lägg till identitet",
"usernamePlaceholder": "Användarnamn",
"add": "Lägg till",
"cancel": "Avbryt",
"advanced": "Avancerat",
"loadingUUIDs": "Laddar UUID:er...",
"setCustomUUID": "Ange anpassad UUID",
"customPlaceholder": "Ange anpassad UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +168,10 @@
"copyTooltip": "Kopiera UUID",
"regenerateTooltip": "Generera ny UUID"
},
"profiles": {
"modalTitle": "Hantera profiler",
"newProfilePlaceholder": "Nytt profilnamn",
"createProfile": "Skapa profil"
"configurations": {
"modalTitle": "Hantera konfigurationer",
"newProfilePlaceholder": "Nytt konfigurationsnamn",
"createProfile": "Skapa konfiguration"
},
"discord": {
"notificationText": "Gå med i vår Discord-gemenskap!",
@@ -219,6 +225,8 @@
"hwAccelSaved": "Hårdvaruaccelerationsinställning sparad",
"hwAccelSaveFailed": "Misslyckades med att spara hårdvaruaccelerationsinställning",
"noUsername": "Inget användarnamn konfigurerat. Vänligen spara ditt användarnamn först.",
"identityAdded": "Identitet tillagd!",
"identityAddFailed": "Kunde inte lägga till identitet",
"switchUsernameSuccess": "Bytte till \"{username}\" framgångsrikt!",
"switchUsernameFailed": "Misslyckades med att byta användarnamn",
"playerNameTooLong": "Spelarnamnet måste vara 16 tecken eller mindre"
@@ -247,8 +255,8 @@
"installing": "Installerar...",
"extracting": "Extraherar...",
"verifying": "Verifierar...",
"switchingProfile": "Byter profil...",
"profileSwitched": "Profil bytt!",
"switchingProfile": "Byter konfiguration...",
"profileSwitched": "Konfiguration bytt!",
"startingGame": "Startar spel...",
"launching": "STARTAR...",
"uninstallingGame": "Avinstallerar spel...",

View File

@@ -8,7 +8,10 @@
},
"header": {
"playersLabel": "Oyuncular:",
"manageProfiles": "Profilleri Yönet",
"manageProfiles": "Yönet",
"manageIdentities": "Yönet",
"identityTooltip": "Oyun içinde kullanılan oyuncu adınız ve UUID'niz",
"configTooltip": "Oyun yapılandırması: modlar, Java ve bellek ayarları",
"defaultProfile": "Varsayılan"
},
"install": {
@@ -151,9 +154,12 @@
},
"uuid": {
"modalTitle": "UUID Yönetimi",
"currentUserUUID": "Geçerli Kullanıcı UUID",
"allPlayerUUIDs": "Tüm Oyuncu UUID'leri",
"generateNew": "Yeni UUID Oluştur",
"addIdentity": "Kimlik Ekle",
"usernamePlaceholder": "Kullanıcı Adı",
"add": "Ekle",
"cancel": "İptal",
"advanced": "Gelişmiş",
"loadingUUIDs": "UUID'ler yükleniyor...",
"setCustomUUID": "Özel UUID Ayarla",
"customPlaceholder": "Özel UUID girin (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +168,10 @@
"copyTooltip": "UUID'yi Kopyala",
"regenerateTooltip": "Yeni UUID Oluştur"
},
"profiles": {
"modalTitle": "Profilleri Yönet",
"newProfilePlaceholder": "Yeni Profil Adı",
"createProfile": "Profil Oluştur"
"configurations": {
"modalTitle": "Yapılandırmaları Yönet",
"newProfilePlaceholder": "Yeni Yapılandırma Adı",
"createProfile": "Yapılandırma Oluştur"
},
"discord": {
"notificationText": "Discord topluluğumuza katılın!",
@@ -219,6 +225,8 @@
"hwAccelSaved": "Donanım hızlandırma ayarı kaydedildi",
"hwAccelSaveFailed": "Donanım hızlandırma ayarı kaydedilemedi",
"noUsername": "Kullanıcı adı yapılandırılmadı. Lütfen önce kullanıcı adınızı kaydedin.",
"identityAdded": "Kimlik başarıyla eklendi!",
"identityAddFailed": "Kimlik eklenemedi",
"switchUsernameSuccess": "\"{username}\" adına başarıyla geçildi!",
"switchUsernameFailed": "Kullanıcı adı değiştirilemedi",
"playerNameTooLong": "Oyuncu adı 16 karakter veya daha az olmalıdır"
@@ -247,8 +255,8 @@
"installing": "Kuruluyur...",
"extracting": "Ayıklanıyor...",
"verifying": "Doğrulanıyor...",
"switchingProfile": "Profil değiştiriliyor...",
"profileSwitched": "Profil değiştirildi!",
"switchingProfile": "Yapılandırma değiştiriliyor...",
"profileSwitched": "Yapılandırma değiştirildi!",
"startingGame": "Oyun başlatılıyor...",
"launching": "BAŞLATILIYOR...",
"uninstallingGame": "Oyun kaldırılıyor...",

View File

@@ -5575,9 +5575,8 @@ input[type="text"].uuid-input,
font-family: 'Space Grotesk', sans-serif;
}
.uuid-current-section,
.uuid-list-section,
.uuid-custom-section {
.uuid-advanced-section {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
@@ -5824,6 +5823,96 @@ input[type="text"].uuid-input,
color: #3b82f6;
}
.uuid-item-btn.regenerate:hover {
background: rgba(249, 115, 22, 0.2);
border-color: rgba(249, 115, 22, 0.4);
color: #f97316;
}
/* Add Identity Form */
.uuid-add-form {
background: rgba(147, 51, 234, 0.05);
border: 1px dashed rgba(147, 51, 234, 0.3);
border-radius: 8px;
padding: 1rem;
margin-bottom: 0.75rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.uuid-add-form-row {
display: flex;
align-items: stretch;
gap: 0.5rem;
}
.uuid-add-form-row .uuid-input {
flex: 1;
padding: 0.75rem 1rem;
border-radius: 8px;
}
.uuid-add-form-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
}
.uuid-cancel-btn {
padding: 0.75rem 1.25rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
color: rgba(255, 255, 255, 0.7);
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 0.5rem;
}
.uuid-cancel-btn:hover {
background: rgba(239, 68, 68, 0.15);
border-color: rgba(239, 68, 68, 0.3);
color: #ef4444;
}
/* Advanced Section */
.uuid-advanced-toggle {
display: flex;
align-items: center;
gap: 0.5rem;
background: none;
border: none;
color: rgba(255, 255, 255, 0.6);
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
padding: 0.5rem 0;
transition: color 0.3s ease;
font-family: 'Space Grotesk', sans-serif;
}
.uuid-advanced-toggle:hover {
color: rgba(255, 255, 255, 0.9);
}
.uuid-advanced-chevron {
font-size: 0.75rem;
transition: transform 0.3s ease;
}
.uuid-advanced-chevron.open {
transform: rotate(90deg);
}
.uuid-advanced-content {
margin-top: 1rem;
}
@media (max-width: 600px) {
.uuid-modal-content {
width: 95vw;
@@ -5835,8 +5924,8 @@ input[type="text"].uuid-input,
gap: 1.5rem;
}
.uuid-current-display,
.uuid-custom-form {
.uuid-custom-form,
.uuid-add-form-row {
flex-direction: column;
}
@@ -6213,6 +6302,155 @@ input[type="text"].uuid-input,
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
/* Header Tooltip Styles */
.header-tooltip {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%) translateY(4px);
font-size: 0.65rem;
font-weight: 500;
letter-spacing: 0.03em;
color: #9ca3af;
opacity: 0;
transition: opacity 0.2s ease;
pointer-events: none;
white-space: nowrap;
background: rgba(0, 0, 0, 0.85);
padding: 0.3rem 0.6rem;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.08);
z-index: 1;
}
.identity-selector:hover .header-tooltip,
.profile-selector:hover .header-tooltip {
opacity: 1;
}
.identity-dropdown.show ~ .header-tooltip,
.profile-dropdown.show ~ .header-tooltip {
opacity: 0 !important;
}
/* Identity Selector Styles */
.identity-selector {
position: relative;
pointer-events: auto;
margin-left: 1rem;
z-index: 9999 !important;
}
.identity-btn {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 1rem;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
color: white;
cursor: pointer;
font-family: 'Space Grotesk', sans-serif;
font-weight: 600;
transition: all 0.2s ease;
z-index: 100000 !important;
pointer-events: auto !important;
}
.identity-btn:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(34, 197, 94, 0.4);
}
.identity-btn i {
color: #22c55e;
}
.identity-dropdown {
position: absolute;
top: 100%;
left: 0;
margin-top: 0.5rem;
width: 200px;
background: rgba(20, 20, 20, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 0.5rem;
display: none;
flex-direction: column;
z-index: 2000;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
animation: fadeIn 0.1s ease;
}
.identity-dropdown.show {
display: flex;
}
.identity-list {
max-height: 200px;
overflow-y: auto;
}
.identity-item {
padding: 0.6rem 0.8rem;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
color: #ccc;
transition: all 0.2s;
}
.identity-item:hover {
background: rgba(255, 255, 255, 0.1);
color: white;
}
.identity-item.active {
background: rgba(34, 197, 94, 0.2);
color: white;
font-weight: bold;
}
.identity-item.active::before {
content: '\2022';
color: #22c55e;
font-size: 1.2rem;
}
.identity-divider {
height: 1px;
background: rgba(255, 255, 255, 0.1);
margin: 0.5rem 0;
}
.identity-action {
padding: 0.6rem 0.8rem;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
color: #22c55e;
font-weight: 600;
}
.identity-action:hover {
background: rgba(34, 197, 94, 0.1);
}
.identity-empty {
padding: 0.6rem 0.8rem;
color: #666;
font-size: 0.85rem;
text-align: center;
}
/* Profile Selector Styles */
.profile-selector {
position: relative;

View File

@@ -106,6 +106,23 @@ function getBundledJavaPath(jreDir = JRE_DIR) {
}
}
// Fallback: check for nested JRE directory (e.g. jdk-25.0.2+10-jre/bin/java)
// This happens when flattenJREDir fails due to EPERM/EACCES on Windows
try {
if (fs.existsSync(jreDir)) {
const entries = fs.readdirSync(jreDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && entry.name !== 'bin' && entry.name !== 'lib') {
const nestedCandidate = path.join(jreDir, entry.name, 'bin', JAVA_EXECUTABLE);
if (fs.existsSync(nestedCandidate)) {
console.log(`[JRE] Using nested Java path: ${nestedCandidate}`);
return nestedCandidate;
}
}
}
}
} catch (_) { /* ignore */ }
return null;
}
@@ -409,7 +426,7 @@ function extractTarGz(tarGzPath, dest) {
function flattenJREDir(jreLatest) {
try {
const entries = fs.readdirSync(jreLatest, { withFileTypes: true });
if (entries.length !== 1 || !entries[0].isDirectory()) {
return;
}
@@ -420,12 +437,48 @@ function flattenJREDir(jreLatest) {
for (const file of files) {
const oldPath = path.join(nested, file.name);
const newPath = path.join(jreLatest, file.name);
fs.renameSync(oldPath, newPath);
try {
fs.renameSync(oldPath, newPath);
} catch (renameErr) {
if (renameErr.code === 'EPERM' || renameErr.code === 'EACCES' || renameErr.code === 'EBUSY') {
console.log(`[JRE] Rename failed for ${file.name} (${renameErr.code}), using copy fallback`);
copyRecursiveSync(oldPath, newPath);
} else {
throw renameErr;
}
}
}
fs.rmSync(nested, { recursive: true, force: true });
try {
fs.rmSync(nested, { recursive: true, force: true });
} catch (rmErr) {
console.log('[JRE] Could not remove nested JRE dir (non-critical):', rmErr.message);
}
} catch (err) {
console.log('Notice: could not restructure Java directory:', err.message);
console.error('[JRE] Failed to restructure Java directory:', err.message);
// Last resort: check if java exists in a nested subdir and skip flatten
try {
const entries = fs.readdirSync(jreLatest, { withFileTypes: true });
const nestedDir = entries.find(e => e.isDirectory() && e.name !== 'bin' && e.name !== 'lib');
if (nestedDir) {
const nestedBin = path.join(jreLatest, nestedDir.name, 'bin', process.platform === 'win32' ? 'java.exe' : 'java');
if (fs.existsSync(nestedBin)) {
console.log(`[JRE] Java found in nested dir: ${nestedDir.name}, leaving structure as-is`);
}
}
} catch (_) { /* ignore */ }
}
}
function copyRecursiveSync(src, dest) {
const stat = fs.statSync(src);
if (stat.isDirectory()) {
fs.mkdirSync(dest, { recursive: true });
for (const child of fs.readdirSync(src)) {
copyRecursiveSync(path.join(src, child), path.join(dest, child));
}
} else {
fs.copyFileSync(src, dest);
}
}

View File

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

View File

@@ -1,17 +0,0 @@
## v2.4.0
### New Features
- **Send Logs** — One-click button to submit launcher & game logs to support. Collects launcher logs, game client logs, and config snapshot into a ZIP, uploads to server, and returns an 8-character ID to share with support ([320ca54](https://git.sanhost.net/sanasol/hytale-f2p/commit/320ca547585c67d0773dba262612db5026378f52), [19c8991](https://git.sanhost.net/sanasol/hytale-f2p/commit/19c8991a44641ebbf44eec73a0ecd9db05241c49))
- **Arabic (ar-SA) locale** with full RTL support (community contribution by @Yugurten) ([30929ee](https://git.sanhost.net/sanasol/hytale-f2p/commit/30929ee0da5a9c64e65869d6157bd705db3b80f0))
- **One-click dedicated server scripts** for self-hosting ([552ec42](https://git.sanhost.net/sanasol/hytale-f2p/commit/552ec42d6c7e1e7d1a2803d284019ccae963f41e))
### Bug Fixes
- Fix Intel Arc iGPU (Meteor Lake/Lunar Lake) on PCI bus 00 being misdetected as discrete GPU on dual-GPU Linux systems ([19c8991](https://git.sanhost.net/sanasol/hytale-f2p/commit/19c8991a44641ebbf44eec73a0ecd9db05241c49))
- Fix stalled game processes blocking launcher operations — automatic process cleanup on repair and relaunch ([e14d56e](https://git.sanhost.net/sanasol/hytale-f2p/commit/e14d56ef4846423c1fd172d88334cb76193ee741))
- Fix AOT cache crashes — stale cache cleared before game launch ([e14d56e](https://git.sanhost.net/sanasol/hytale-f2p/commit/e14d56ef4846423c1fd172d88334cb76193ee741))
- Fix Arabic RTL CSS syntax ([fb90277](https://git.sanhost.net/sanasol/hytale-f2p/commit/fb90277be9cf5f0b8a90195a7d089273b6be082b))
### Other
- Updated README with Forgejo URLs and server setup video ([a649bf1](https://git.sanhost.net/sanasol/hytale-f2p/commit/a649bf1fcc7cbb2cd0d9aa0160b07828a144b9dd), [66faa1b](https://git.sanhost.net/sanasol/hytale-f2p/commit/66faa1bb1e39575fecb462310af338d13b1cb183))
**Full changelog**: [v2.3.8...v2.4.0](https://git.sanhost.net/sanasol/hytale-f2p/compare/v2.3.8...v2.4.0)