Compare commits

...

8 Commits

Author SHA1 Message Date
sanasol
43a2b6d004 debug: disable DualAuth agent to test fastutil classloader issue
Temporarily skip -javaagent injection to determine if agent's
appendToBootstrapClassLoaderSearch() causes the fastutil
ClassNotFoundException on affected Windows systems.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:47:46 +01:00
sanasol
ce6455314d debug: add -Xshare:off to JAVA_TOOL_OPTIONS to test fastutil classloader fix
Disables JVM Class Data Sharing when DualAuth agent is active.
May fix singleplayer crash (NoClassDefFoundError: fastutil) on some Windows systems
where appendToBootstrapClassLoaderSearch breaks CDS classloading.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:32:50 +01:00
sanasol
3abdd10cab v2.4.5: Add multi-instance setting for mod developers
Allow running multiple game clients simultaneously via a new
"Allow multiple game instances" toggle in Settings. When enabled,
skips the Electron single-instance lock and the pre-launch process
kill, so existing game instances stay alive.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 21:52:13 +01:00
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
26 changed files with 1075 additions and 253 deletions

View File

@@ -52,12 +52,20 @@ jobs:
run: | run: |
RELEASE_ID=$(curl -s "${FORGEJO_API}/repos/${GITHUB_REPOSITORY}/releases/tags/${{ github.ref_name }}" \ 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"])') -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 for file in dist/*.exe dist/*.exe.blockmap dist/latest.yml; do
[ -f "$file" ] || continue [ -f "$file" ] || continue
echo "Uploading $file..." echo "Uploading $file ($(stat -c%s "$file" 2>/dev/null || stat -f%z "$file") bytes)..."
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 }}" \ -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 done
build-macos: build-macos:
@@ -83,12 +91,19 @@ jobs:
run: | run: |
RELEASE_ID=$(curl -s "${FORGEJO_API}/repos/${GITHUB_REPOSITORY}/releases/tags/${{ github.ref_name }}" \ 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"])') -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 for file in dist/*.dmg dist/*.zip dist/*.blockmap dist/latest-mac.yml; do
[ -f "$file" ] || continue [ -f "$file" ] || continue
echo "Uploading $file..." 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 }}" \ -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 done
build-linux: build-linux:
@@ -113,10 +128,17 @@ jobs:
run: | run: |
RELEASE_ID=$(curl -s "${FORGEJO_API}/repos/${GITHUB_REPOSITORY}/releases/tags/${{ github.ref_name }}" \ 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"])') -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 for file in dist/*.AppImage dist/*.AppImage.blockmap dist/*.deb dist/*.rpm dist/*.pacman dist/latest-linux.yml; do
[ -f "$file" ] || continue [ -f "$file" ] || continue
echo "Uploading $file..." echo "Uploading $file ($(stat -c%s "$file" 2>/dev/null || stat -f%z "$file") bytes)..."
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 }}" \ -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 done

View File

@@ -73,22 +73,40 @@
<span id="onlineCount" class="counter-value">0</span> <span id="onlineCount" class="counter-value">0</span>
</div> </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"> <div class="profile-selector" id="profileSelector">
<button class="profile-btn" onclick="toggleProfileDropdown()"> <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> <span id="currentProfileName">Default</span>
<i class="fas fa-chevron-down"></i> <i class="fas fa-chevron-down"></i>
</button> </button>
<div class="profile-dropdown" id="profileDropdown"> <div class="profile-dropdown" id="profileDropdown">
<div class="profile-list" id="profileList"> <div class="profile-list" id="profileList">
<!-- Profiles populated by JS --> <!-- Configurations populated by JS -->
</div> </div>
<div class="profile-divider"></div> <div class="profile-divider"></div>
<div class="profile-action" onclick="openProfileManager()"> <div class="profile-action" onclick="openProfileManager()">
<i class="fas fa-cog"></i> <i class="fas fa-cog"></i>
<span data-i18n="header.manageProfiles">Manage Profiles</span> <span data-i18n="header.manageProfiles">Manage</span>
</div> </div>
</div> </div>
<span class="header-tooltip" data-i18n="header.configTooltip">Game config: mods, Java &amp; memory settings</span>
</div> </div>
<div class="window-controls"> <div class="window-controls">
@@ -574,6 +592,18 @@
</div> </div>
</label> </label>
</div> </div>
<div class="settings-option">
<label class="settings-checkbox">
<input type="checkbox" id="allowMultiInstanceCheck" />
<span class="checkmark"></span>
<div class="checkbox-content">
<div class="checkbox-title" data-i18n="settings.allowMultiInstance">Allow multiple game instances</div>
<div class="checkbox-description" data-i18n="settings.allowMultiInstanceDescription">
Allow running multiple game clients at the same time (useful for mod development)
</div>
</div>
</label>
</div>
<div class="settings-option"> <div class="settings-option">
<label class="settings-checkbox"> <label class="settings-checkbox">
<input type="checkbox" id="launcherHwAccelCheck" /> <input type="checkbox" id="launcherHwAccelCheck" />
@@ -736,28 +766,40 @@
</div> </div>
<div class="uuid-modal-body"> <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-section">
<div class="uuid-list-header"> <div class="uuid-list-header">
<h3 class="uuid-section-title" data-i18n="uuid.allPlayerUUIDs">All Player UUIDs</h3> <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> <i class="fas fa-plus"></i>
<span data-i18n="uuid.generateNew">Generate New UUID</span> <span data-i18n="uuid.addIdentity">Add Identity</span>
</button> </button>
</div> </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 id="uuidList" class="uuid-list">
<div class="uuid-loading"> <div class="uuid-loading">
<i class="fas fa-spinner fa-spin"></i> <i class="fas fa-spinner fa-spin"></i>
@@ -766,21 +808,27 @@
</div> </div>
</div> </div>
<div class="uuid-custom-section"> <div class="uuid-advanced-section">
<h3 class="uuid-section-title" data-i18n="uuid.setCustomUUID">Set Custom UUID</h3> <button id="uuidAdvancedToggle" class="uuid-advanced-toggle">
<div class="uuid-custom-form"> <i class="fas fa-chevron-right uuid-advanced-chevron"></i>
<input type="text" id="customUuidInput" class="uuid-input" <span data-i18n="uuid.advanced">Advanced</span>
data-i18n-placeholder="uuid.customPlaceholder" maxlength="36" /> </button>
<button id="setCustomUuidBtn" class="uuid-set-btn"> <div id="uuidAdvancedContent" class="uuid-advanced-content" style="display: none;">
<i class="fas fa-check"></i> <h3 class="uuid-section-title" data-i18n="uuid.setCustomUUID">Set Custom UUID</h3>
<span data-i18n="uuid.setUUID">Set UUID</span> <div class="uuid-custom-form">
</button> <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> </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> </div>
</div> </div>
@@ -791,8 +839,8 @@
<div class="profile-modal-content"> <div class="profile-modal-content">
<div class="profile-modal-header"> <div class="profile-modal-header">
<h2 class="profile-modal-title"> <h2 class="profile-modal-title">
<i class="fas fa-users-cog mr-2"></i> <i class="fas fa-sliders-h mr-2"></i>
<span data-i18n="profiles.modalTitle">Manage Profiles</span> <span data-i18n="configurations.modalTitle">Manage Configurations</span>
</h2> </h2>
<button class="modal-close-btn" onclick="closeProfileManager()"> <button class="modal-close-btn" onclick="closeProfileManager()">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
@@ -803,10 +851,10 @@
<!-- Populated by JS --> <!-- Populated by JS -->
</div> </div>
<div class="profile-create-section"> <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"> class="profile-input" maxlength="20">
<button class="profile-create-btn" onclick="createNewProfile()"> <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> </button>
</div> </div>
</div> </div>

View File

@@ -35,13 +35,21 @@ export function setupLauncher() {
// Initial Profile Load // Initial Profile Load
loadProfiles(); loadProfiles();
// Close dropdown on outside click // Initial Identity Load
loadIdentities();
// Close dropdowns on outside click
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
const selector = document.getElementById('profileSelector'); const profileSelector = document.getElementById('profileSelector');
if (selector && !selector.contains(e.target)) { if (profileSelector && !profileSelector.contains(e.target)) {
const dropdown = document.getElementById('profileDropdown'); const dropdown = document.getElementById('profileDropdown');
if (dropdown) dropdown.classList.remove('show'); 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 => ` managerList.innerHTML = profiles.map(p => `
<div class="profile-manager-item ${p.id === activeProfile.id ? 'active' : ''}"> <div class="profile-manager-item ${p.id === activeProfile.id ? 'active' : ''}">
<div class="flex items-center gap-3"> <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>
<div class="font-bold">${p.name}</div> <div class="font-bold">${p.name}</div>
<div class="text-xs text-gray-500">ID: ${p.id.substring(0, 8)}...</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 = () => { window.openProfileManager = () => {
const modal = document.getElementById('profileManagerModal'); const modal = document.getElementById('profileManagerModal');
if (modal) { if (modal) {
@@ -146,7 +147,7 @@ window.createNewProfile = async () => {
}; };
window.deleteProfile = async (id) => { 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 { try {
await window.electronAPI.profile.delete(id); await window.electronAPI.profile.delete(id);
@@ -160,7 +161,7 @@ window.deleteProfile = async (id) => {
window.switchProfile = async (id) => { window.switchProfile = async (id) => {
try { try {
if (window.LauncherUI) window.LauncherUI.showProgress(); 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 }); if (window.LauncherUI) window.LauncherUI.updateProgress({ message: switchingMsg });
await window.electronAPI.profile.activate(id); await window.electronAPI.profile.activate(id);
@@ -179,7 +180,7 @@ window.switchProfile = async (id) => {
if (dropdown) dropdown.classList.remove('show'); if (dropdown) dropdown.classList.remove('show');
if (window.LauncherUI) { 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 }); window.LauncherUI.updateProgress({ message: switchedMsg });
setTimeout(() => window.LauncherUI.hideProgress(), 1000); 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.launch = launch;
window.uninstallGame = uninstallGame; window.uninstallGame = uninstallGame;
window.repairGame = repairGame; window.repairGame = repairGame;

View File

@@ -6,6 +6,7 @@ let browseJavaBtn;
let settingsPlayerName; let settingsPlayerName;
let discordRPCCheck; let discordRPCCheck;
let closeLauncherCheck; let closeLauncherCheck;
let allowMultiInstanceCheck;
let launcherHwAccelCheck; let launcherHwAccelCheck;
let gpuPreferenceRadios; let gpuPreferenceRadios;
let gameBranchRadios; let gameBranchRadios;
@@ -18,10 +19,15 @@ let regenerateUuidBtn;
let manageUuidsBtn; let manageUuidsBtn;
let uuidModal; let uuidModal;
let uuidModalClose; let uuidModalClose;
let modalCurrentUuid; let addIdentityBtn;
let modalCopyUuidBtn; let uuidAddForm;
let modalRegenerateUuidBtn; let addIdentityUsername;
let generateNewUuidBtn; let addIdentityUuid;
let addIdentityRegenerateBtn;
let addIdentityConfirmBtn;
let addIdentityCancelBtn;
let uuidAdvancedToggle;
let uuidAdvancedContent;
let uuidList; let uuidList;
let customUuidInput; let customUuidInput;
let setCustomUuidBtn; let setCustomUuidBtn;
@@ -166,6 +172,7 @@ function setupSettingsElements() {
settingsPlayerName = document.getElementById('settingsPlayerName'); settingsPlayerName = document.getElementById('settingsPlayerName');
discordRPCCheck = document.getElementById('discordRPCCheck'); discordRPCCheck = document.getElementById('discordRPCCheck');
closeLauncherCheck = document.getElementById('closeLauncherCheck'); closeLauncherCheck = document.getElementById('closeLauncherCheck');
allowMultiInstanceCheck = document.getElementById('allowMultiInstanceCheck');
launcherHwAccelCheck = document.getElementById('launcherHwAccelCheck'); launcherHwAccelCheck = document.getElementById('launcherHwAccelCheck');
gpuPreferenceRadios = document.querySelectorAll('input[name="gpuPreference"]'); gpuPreferenceRadios = document.querySelectorAll('input[name="gpuPreference"]');
gameBranchRadios = document.querySelectorAll('input[name="gameBranch"]'); gameBranchRadios = document.querySelectorAll('input[name="gameBranch"]');
@@ -180,10 +187,15 @@ function setupSettingsElements() {
manageUuidsBtn = document.getElementById('manageUuidsBtn'); manageUuidsBtn = document.getElementById('manageUuidsBtn');
uuidModal = document.getElementById('uuidModal'); uuidModal = document.getElementById('uuidModal');
uuidModalClose = document.getElementById('uuidModalClose'); uuidModalClose = document.getElementById('uuidModalClose');
modalCurrentUuid = document.getElementById('modalCurrentUuid'); addIdentityBtn = document.getElementById('addIdentityBtn');
modalCopyUuidBtn = document.getElementById('modalCopyUuidBtn'); uuidAddForm = document.getElementById('uuidAddForm');
modalRegenerateUuidBtn = document.getElementById('modalRegenerateUuidBtn'); addIdentityUsername = document.getElementById('addIdentityUsername');
generateNewUuidBtn = document.getElementById('generateNewUuidBtn'); 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'); uuidList = document.getElementById('uuidList');
customUuidInput = document.getElementById('customUuidInput'); customUuidInput = document.getElementById('customUuidInput');
setCustomUuidBtn = document.getElementById('setCustomUuidBtn'); setCustomUuidBtn = document.getElementById('setCustomUuidBtn');
@@ -208,6 +220,10 @@ function setupSettingsElements() {
closeLauncherCheck.addEventListener('change', saveCloseLauncher); closeLauncherCheck.addEventListener('change', saveCloseLauncher);
} }
if (allowMultiInstanceCheck) {
allowMultiInstanceCheck.addEventListener('change', saveAllowMultiInstance);
}
if (launcherHwAccelCheck) { if (launcherHwAccelCheck) {
launcherHwAccelCheck.addEventListener('change', saveLauncherHwAccel); launcherHwAccelCheck.addEventListener('change', saveLauncherHwAccel);
} }
@@ -230,16 +246,24 @@ function setupSettingsElements() {
uuidModalClose.addEventListener('click', closeUuidModal); uuidModalClose.addEventListener('click', closeUuidModal);
} }
if (modalCopyUuidBtn) { if (addIdentityBtn) {
modalCopyUuidBtn.addEventListener('click', copyCurrentUuid); addIdentityBtn.addEventListener('click', showAddIdentityForm);
} }
if (modalRegenerateUuidBtn) { if (addIdentityRegenerateBtn) {
modalRegenerateUuidBtn.addEventListener('click', regenerateCurrentUuid); addIdentityRegenerateBtn.addEventListener('click', regenerateAddIdentityUuid);
} }
if (generateNewUuidBtn) { if (addIdentityConfirmBtn) {
generateNewUuidBtn.addEventListener('click', generateNewUuid); addIdentityConfirmBtn.addEventListener('click', confirmAddIdentity);
}
if (addIdentityCancelBtn) {
addIdentityCancelBtn.addEventListener('click', hideAddIdentityForm);
}
if (uuidAdvancedToggle) {
uuidAdvancedToggle.addEventListener('click', toggleAdvancedSection);
} }
if (setCustomUuidBtn) { if (setCustomUuidBtn) {
@@ -397,6 +421,30 @@ async function loadCloseLauncher() {
} }
} }
async function saveAllowMultiInstance() {
try {
if (window.electronAPI && window.electronAPI.saveAllowMultiInstance && allowMultiInstanceCheck) {
const enabled = allowMultiInstanceCheck.checked;
await window.electronAPI.saveAllowMultiInstance(enabled);
}
} catch (error) {
console.error('Error saving multi-instance setting:', error);
}
}
async function loadAllowMultiInstance() {
try {
if (window.electronAPI && window.electronAPI.loadAllowMultiInstance) {
const enabled = await window.electronAPI.loadAllowMultiInstance();
if (allowMultiInstanceCheck) {
allowMultiInstanceCheck.checked = enabled;
}
}
} catch (error) {
console.error('Error loading multi-instance setting:', error);
}
}
async function saveLauncherHwAccel() { async function saveLauncherHwAccel() {
try { try {
if (window.electronAPI && window.electronAPI.saveLauncherHardwareAcceleration && launcherHwAccelCheck) { if (window.electronAPI && window.electronAPI.saveLauncherHardwareAcceleration && launcherHwAccelCheck) {
@@ -467,6 +515,9 @@ async function savePlayerName() {
// Also refresh the UUID list to update which entry is marked as current // Also refresh the UUID list to update which entry is marked as current
await loadAllUuids(); await loadAllUuids();
// Refresh header identity dropdown
if (window.loadIdentities) window.loadIdentities();
} catch (error) { } catch (error) {
console.error('Error saving player name:', error); console.error('Error saving player name:', error);
const errorMsg = window.i18n ? window.i18n.t('notifications.playerNameSaveFailed') : 'Failed to save player name'; const errorMsg = window.i18n ? window.i18n.t('notifications.playerNameSaveFailed') : 'Failed to save player name';
@@ -566,6 +617,7 @@ async function loadAllSettings() {
await loadCurrentUuid(); await loadCurrentUuid();
await loadDiscordRPC(); await loadDiscordRPC();
await loadCloseLauncher(); await loadCloseLauncher();
await loadAllowMultiInstance();
await loadLauncherHwAccel(); await loadLauncherHwAccel();
await loadGpuPreference(); await loadGpuPreference();
await loadVersionBranch(); await loadVersionBranch();
@@ -630,7 +682,6 @@ async function loadCurrentUuid() {
const uuid = await window.electronAPI.getCurrentUuid(); const uuid = await window.electronAPI.getCurrentUuid();
if (uuid) { if (uuid) {
if (currentUuidDisplay) currentUuidDisplay.value = uuid; if (currentUuidDisplay) currentUuidDisplay.value = uuid;
if (modalCurrentUuid) modalCurrentUuid.value = uuid;
} }
} }
} catch (error) { } catch (error) {
@@ -640,7 +691,7 @@ async function loadCurrentUuid() {
async function copyCurrentUuid() { async function copyCurrentUuid() {
try { try {
const uuid = currentUuidDisplay ? currentUuidDisplay.value : modalCurrentUuid?.value; const uuid = currentUuidDisplay ? currentUuidDisplay.value : null;
if (uuid && navigator.clipboard) { if (uuid && navigator.clipboard) {
await navigator.clipboard.writeText(uuid); await navigator.clipboard.writeText(uuid);
const msg = window.i18n ? window.i18n.t('notifications.uuidCopied') : 'UUID copied to clipboard!'; const msg = window.i18n ? window.i18n.t('notifications.uuidCopied') : 'UUID copied to clipboard!';
@@ -688,13 +739,13 @@ async function performRegenerateUuid() {
const result = await window.electronAPI.resetCurrentUserUuid(); const result = await window.electronAPI.resetCurrentUserUuid();
if (result.success && result.uuid) { if (result.success && result.uuid) {
if (currentUuidDisplay) currentUuidDisplay.value = 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!'; const msg = window.i18n ? window.i18n.t('notifications.uuidGenerated') : 'New UUID generated successfully!';
showNotification(msg, 'success'); showNotification(msg, 'success');
if (uuidModal && uuidModal.style.display !== 'none') { if (uuidModal && uuidModal.style.display !== 'none') {
await loadAllUuids(); await loadAllUuids();
} }
if (window.loadIdentities) window.loadIdentities();
} else { } else {
throw new Error(result.error || 'Failed to generate new UUID'); throw new Error(result.error || 'Failed to generate new UUID');
} }
@@ -717,6 +768,10 @@ async function openUuidModal() {
} }
} }
// Expose globally so identity dropdown and Escape handler can use them
window.openUuidModal = openUuidModal;
window.loadCurrentUuid = loadCurrentUuid;
function closeUuidModal() { function closeUuidModal() {
if (uuidModal) { if (uuidModal) {
uuidModal.classList.remove('active'); uuidModal.classList.remove('active');
@@ -725,6 +780,7 @@ function closeUuidModal() {
}, 300); }, 300);
} }
} }
window.closeUuidModal = closeUuidModal;
async function loadAllUuids() { async function loadAllUuids() {
try { try {
@@ -769,6 +825,9 @@ async function loadAllUuids() {
<button class="uuid-item-btn copy" onclick="copyUuid('${mapping.uuid}')" title="Copy UUID"> <button class="uuid-item-btn copy" onclick="copyUuid('${mapping.uuid}')" title="Copy UUID">
<i class="fas fa-copy"></i> <i class="fas fa-copy"></i>
</button> </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"> ${!mapping.isCurrent ? `<button class="uuid-item-btn delete" onclick="deleteUuid('${escapeHtml(mapping.username)}')" title="Delete UUID">
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>
</button>` : ''} </button>` : ''}
@@ -791,23 +850,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 { try {
if (window.electronAPI && window.electronAPI.generateNewUuid) { if (window.electronAPI && window.electronAPI.generateNewUuid) {
const newUuid = await window.electronAPI.generateNewUuid(); const newUuid = await window.electronAPI.generateNewUuid();
if (newUuid) { if (newUuid && addIdentityUuid) {
if (customUuidInput) customUuidInput.value = newUuid; addIdentityUuid.value = newUuid;
const msg = window.i18n ? window.i18n.t('notifications.uuidGeneratedShort') : 'New UUID generated!';
showNotification(msg, 'success');
} }
} }
} catch (error) { } catch (error) {
console.error('Error generating new UUID:', error); console.error('Error generating UUID:', error);
const msg = window.i18n ? window.i18n.t('notifications.uuidGenerateFailed') : 'Failed to generate new UUID'; }
}
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'); 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() { async function setCustomUuid() {
try { try {
if (!customUuidInput || !customUuidInput.value.trim()) { if (!customUuidInput || !customUuidInput.value.trim()) {
@@ -865,13 +1020,13 @@ async function performSetCustomUuid(uuid) {
if (result.success) { if (result.success) {
if (currentUuidDisplay) currentUuidDisplay.value = uuid; if (currentUuidDisplay) currentUuidDisplay.value = uuid;
if (modalCurrentUuid) modalCurrentUuid.value = uuid;
if (customUuidInput) customUuidInput.value = ''; if (customUuidInput) customUuidInput.value = '';
const msg = window.i18n ? window.i18n.t('notifications.uuidSetSuccess') : 'Custom UUID set successfully!'; const msg = window.i18n ? window.i18n.t('notifications.uuidSetSuccess') : 'Custom UUID set successfully!';
showNotification(msg, 'success'); showNotification(msg, 'success');
await loadAllUuids(); await loadAllUuids();
if (window.loadIdentities) window.loadIdentities();
} else { } else {
throw new Error(result.error || 'Failed to set custom UUID'); throw new Error(result.error || 'Failed to set custom UUID');
} }
@@ -950,6 +1105,9 @@ async function performSwitchToUsername(username) {
// Refresh the UUID list to show new "Current" badge // Refresh the UUID list to show new "Current" badge
await loadAllUuids(); await loadAllUuids();
// Refresh header identity dropdown
if (window.loadIdentities) window.loadIdentities();
const msg = window.i18n const msg = window.i18n
? window.i18n.t('notifications.switchUsernameSuccess').replace('{username}', username) ? window.i18n.t('notifications.switchUsernameSuccess').replace('{username}', username)
: `Switched to "${username}" successfully!`; : `Switched to "${username}" successfully!`;
@@ -997,6 +1155,7 @@ async function performDeleteUuid(username) {
const msg = window.i18n ? window.i18n.t('notifications.uuidDeleteSuccess') : 'UUID deleted successfully!'; const msg = window.i18n ? window.i18n.t('notifications.uuidDeleteSuccess') : 'UUID deleted successfully!';
showNotification(msg, 'success'); showNotification(msg, 'success');
await loadAllUuids(); await loadAllUuids();
if (window.loadIdentities) window.loadIdentities();
} else { } else {
throw new Error(result.error || 'Failed to delete UUID'); throw new Error(result.error || 'Failed to delete UUID');
} }

View File

@@ -79,12 +79,18 @@ function setupWindowControls() {
const header = document.querySelector('.header'); const header = document.querySelector('.header');
const profileSelector = document.querySelector('.profile-selector'); const profileSelector = document.querySelector('.profile-selector');
const identitySelector = document.querySelector('.identity-selector');
if (profileSelector) { if (profileSelector) {
profileSelector.style.pointerEvents = 'auto'; profileSelector.style.pointerEvents = 'auto';
profileSelector.style.zIndex = '10000'; profileSelector.style.zIndex = '10000';
} }
if (identitySelector) {
identitySelector.style.pointerEvents = 'auto';
identitySelector.style.zIndex = '10000';
}
if (windowControls) { if (windowControls) {
windowControls.style.pointerEvents = 'auto'; windowControls.style.pointerEvents = 'auto';
windowControls.style.zIndex = '10000'; windowControls.style.zIndex = '10000';
@@ -98,6 +104,9 @@ function setupWindowControls() {
if (profileSelector) { if (profileSelector) {
profileSelector.style.webkitAppRegion = 'no-drag'; profileSelector.style.webkitAppRegion = 'no-drag';
} }
if (identitySelector) {
identitySelector.style.webkitAppRegion = 'no-drag';
}
} }
if (window.electronAPI) { if (window.electronAPI) {
@@ -1109,4 +1118,50 @@ window.openDiscordExternal = function() {
window.toggleMaximize = toggleMaximize; 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); document.addEventListener('DOMContentLoaded', setupUI);

View File

@@ -8,7 +8,10 @@
}, },
"header": { "header": {
"playersLabel": "اللاعبون:", "playersLabel": "اللاعبون:",
"manageProfiles": "إدارة الملفات الشخصية", "manageProfiles": "إدارة",
"manageIdentities": "إدارة",
"identityTooltip": "اسم اللاعب ومعرّف UUID المستخدمان في اللعبة",
"configTooltip": "إعدادات اللعبة: المودات، Java والذاكرة",
"defaultProfile": "الافتراضي" "defaultProfile": "الافتراضي"
}, },
"install": { "install": {
@@ -137,6 +140,8 @@
"closeLauncher": "سلوك المشغل", "closeLauncher": "سلوك المشغل",
"closeOnStart": "إغلاق المشغل عند بدء اللعبة", "closeOnStart": "إغلاق المشغل عند بدء اللعبة",
"closeOnStartDescription": "إغلاق المشغل تلقائياً بعد تشغيل Hytale", "closeOnStartDescription": "إغلاق المشغل تلقائياً بعد تشغيل Hytale",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "تسريع الأجهزة (Hardware Acceleration)", "hwAccel": "تسريع الأجهزة (Hardware Acceleration)",
"hwAccelDescription": "تفعيل تسريع الأجهزة للمشغل", "hwAccelDescription": "تفعيل تسريع الأجهزة للمشغل",
"gameBranch": "فرع اللعبة", "gameBranch": "فرع اللعبة",
@@ -151,9 +156,12 @@
}, },
"uuid": { "uuid": {
"modalTitle": "إدارة UUID", "modalTitle": "إدارة UUID",
"currentUserUUID": "UUID المستخدم الحالي",
"allPlayerUUIDs": "جميع معرفات UUID للاعبين", "allPlayerUUIDs": "جميع معرفات UUID للاعبين",
"generateNew": "إنشاء UUID جديد", "addIdentity": "إضافة هوية",
"usernamePlaceholder": "اسم المستخدم",
"add": "إضافة",
"cancel": "إلغاء",
"advanced": "متقدم",
"loadingUUIDs": "جاري تحميل الـ UUIDs...", "loadingUUIDs": "جاري تحميل الـ UUIDs...",
"setCustomUUID": "تعيين UUID مخصص", "setCustomUUID": "تعيين UUID مخصص",
"customPlaceholder": "أدخل UUID مخصص (الصيغة: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "أدخل UUID مخصص (الصيغة: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +170,10 @@
"copyTooltip": "نسخ UUID", "copyTooltip": "نسخ UUID",
"regenerateTooltip": "إنشاء UUID جديد" "regenerateTooltip": "إنشاء UUID جديد"
}, },
"profiles": { "configurations": {
"modalTitle": "إدارة الملفات الشخصية", "modalTitle": "إدارة التكوينات",
"newProfilePlaceholder": "اسم الملف الشخصي الجديد", "newProfilePlaceholder": "اسم التكوين الجديد",
"createProfile": "إنشاء ملف شخصي" "createProfile": "إنشاء تكوين"
}, },
"discord": { "discord": {
"notificationText": "انضم إلى مجتمعنا على ديسكورد!", "notificationText": "انضم إلى مجتمعنا على ديسكورد!",
@@ -219,6 +227,8 @@
"hwAccelSaved": "تم حفظ إعداد تسريع الأجهزة", "hwAccelSaved": "تم حفظ إعداد تسريع الأجهزة",
"hwAccelSaveFailed": "فشل حفظ إعداد تسريع الأجهزة", "hwAccelSaveFailed": "فشل حفظ إعداد تسريع الأجهزة",
"noUsername": "لم يتم تهيئة اسم مستخدم. يرجى حفظ اسم المستخدم أولاً.", "noUsername": "لم يتم تهيئة اسم مستخدم. يرجى حفظ اسم المستخدم أولاً.",
"identityAdded": "تمت إضافة الهوية بنجاح!",
"identityAddFailed": "فشل في إضافة الهوية",
"switchUsernameSuccess": "تم التبديل إلى المستخدم \"{username}\" بنجاح!", "switchUsernameSuccess": "تم التبديل إلى المستخدم \"{username}\" بنجاح!",
"switchUsernameFailed": "فشل تبديل اسم المستخدم", "switchUsernameFailed": "فشل تبديل اسم المستخدم",
"playerNameTooLong": "يجب أن يكون اسم اللاعب 16 حرفاً أو أقل" "playerNameTooLong": "يجب أن يكون اسم اللاعب 16 حرفاً أو أقل"
@@ -247,8 +257,8 @@
"installing": "جاري التثبيت...", "installing": "جاري التثبيت...",
"extracting": "جاري الاستخراج...", "extracting": "جاري الاستخراج...",
"verifying": "جاري التحقق...", "verifying": "جاري التحقق...",
"switchingProfile": "جاري تبديل الملف الشخصي...", "switchingProfile": "جاري تبديل التكوين...",
"profileSwitched": "تم تبديل الملف الشخصي!", "profileSwitched": "تم تبديل التكوين!",
"startingGame": "جاري بدء اللعبة...", "startingGame": "جاري بدء اللعبة...",
"launching": "جاري التشغيل...", "launching": "جاري التشغيل...",
"uninstallingGame": "جاري إلغاء تثبيت اللعبة...", "uninstallingGame": "جاري إلغاء تثبيت اللعبة...",

View File

@@ -8,7 +8,10 @@
}, },
"header": { "header": {
"playersLabel": "Spieler:", "playersLabel": "Spieler:",
"manageProfiles": "Profile verwalten", "manageProfiles": "Verwalten",
"manageIdentities": "Verwalten",
"identityTooltip": "Dein Spielername und UUID im Spiel",
"configTooltip": "Spielkonfiguration: Mods, Java- und Speichereinstellungen",
"defaultProfile": "Standard" "defaultProfile": "Standard"
}, },
"install": { "install": {
@@ -137,6 +140,8 @@
"closeLauncher": "Launcher-Verhalten", "closeLauncher": "Launcher-Verhalten",
"closeOnStart": "Launcher beim Spielstart schließen", "closeOnStart": "Launcher beim Spielstart schließen",
"closeOnStartDescription": "Schließe den Launcher automatisch, nachdem Hytale gestartet wurde", "closeOnStartDescription": "Schließe den Launcher automatisch, nachdem Hytale gestartet wurde",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Hardware-Beschleunigung", "hwAccel": "Hardware-Beschleunigung",
"hwAccelDescription": "Hardware-Beschleunigung für den Launcher aktivieren", "hwAccelDescription": "Hardware-Beschleunigung für den Launcher aktivieren",
"gameBranch": "Spiel-Branch", "gameBranch": "Spiel-Branch",
@@ -151,9 +156,12 @@
}, },
"uuid": { "uuid": {
"modalTitle": "UUID-Verwaltung", "modalTitle": "UUID-Verwaltung",
"currentUserUUID": "Aktuelle Benutzer-UUID",
"allPlayerUUIDs": "Alle Spieler-UUIDs", "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...", "loadingUUIDs": "UUIDs werden geladen...",
"setCustomUUID": "Benutzerdefinierte UUID festlegen", "setCustomUUID": "Benutzerdefinierte UUID festlegen",
"customPlaceholder": "Benutzerdefinierte UUID eingeben (Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Benutzerdefinierte UUID eingeben (Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +170,10 @@
"copyTooltip": "UUID kopieren", "copyTooltip": "UUID kopieren",
"regenerateTooltip": "Neue UUID generieren" "regenerateTooltip": "Neue UUID generieren"
}, },
"profiles": { "configurations": {
"modalTitle": "Profile verwalten", "modalTitle": "Konfigurationen verwalten",
"newProfilePlaceholder": "Neuer Profilname", "newProfilePlaceholder": "Neuer Konfigurationsname",
"createProfile": "Profil erstellen" "createProfile": "Konfiguration erstellen"
}, },
"discord": { "discord": {
"notificationText": "Tritt unserer Discord-Community bei!", "notificationText": "Tritt unserer Discord-Community bei!",
@@ -219,6 +227,8 @@
"hwAccelSaved": "Hardware-Beschleunigungseinstellung gespeichert", "hwAccelSaved": "Hardware-Beschleunigungseinstellung gespeichert",
"hwAccelSaveFailed": "Hardware-Beschleunigungseinstellung konnte nicht gespeichert werden", "hwAccelSaveFailed": "Hardware-Beschleunigungseinstellung konnte nicht gespeichert werden",
"noUsername": "Kein Benutzername konfiguriert. Bitte speichere zuerst deinen Benutzernamen.", "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!", "switchUsernameSuccess": "Erfolgreich zu \"{username}\" gewechselt!",
"switchUsernameFailed": "Benutzername konnte nicht gewechselt werden", "switchUsernameFailed": "Benutzername konnte nicht gewechselt werden",
"playerNameTooLong": "Spielername darf maximal 16 Zeichen haben" "playerNameTooLong": "Spielername darf maximal 16 Zeichen haben"
@@ -247,8 +257,8 @@
"installing": "Installiere...", "installing": "Installiere...",
"extracting": "Entpacke...", "extracting": "Entpacke...",
"verifying": "Überprüfe...", "verifying": "Überprüfe...",
"switchingProfile": "Profil wird gewechselt...", "switchingProfile": "Konfiguration wird gewechselt...",
"profileSwitched": "Profil gewechselt!", "profileSwitched": "Konfiguration gewechselt!",
"startingGame": "Spiel wird gestartet...", "startingGame": "Spiel wird gestartet...",
"launching": "STARTET...", "launching": "STARTET...",
"uninstallingGame": "Spiel wird deinstalliert...", "uninstallingGame": "Spiel wird deinstalliert...",

View File

@@ -8,7 +8,10 @@
}, },
"header": { "header": {
"playersLabel": "Players:", "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" "defaultProfile": "Default"
}, },
"install": { "install": {
@@ -137,6 +140,8 @@
"closeLauncher": "Launcher Behavior", "closeLauncher": "Launcher Behavior",
"closeOnStart": "Close Launcher on game start", "closeOnStart": "Close Launcher on game start",
"closeOnStartDescription": "Automatically close the launcher after Hytale has launched", "closeOnStartDescription": "Automatically close the launcher after Hytale has launched",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Hardware Acceleration", "hwAccel": "Hardware Acceleration",
"hwAccelDescription": "Enable hardware acceleration for the launcher", "hwAccelDescription": "Enable hardware acceleration for the launcher",
"gameBranch": "Game Branch", "gameBranch": "Game Branch",
@@ -162,21 +167,24 @@
}, },
"uuid": { "uuid": {
"modalTitle": "UUID Management", "modalTitle": "UUID Management",
"currentUserUUID": "Current User UUID",
"allPlayerUUIDs": "All Player UUIDs", "allPlayerUUIDs": "All Player UUIDs",
"generateNew": "Generate New UUID", "addIdentity": "Add Identity",
"usernamePlaceholder": "Username",
"add": "Add",
"cancel": "Cancel",
"advanced": "Advanced",
"loadingUUIDs": "Loading UUIDs...", "loadingUUIDs": "Loading UUIDs...",
"setCustomUUID": "Set Custom UUID", "setCustomUUID": "Set Custom UUID for Current User",
"customPlaceholder": "Enter custom UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Enter custom UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
"setUUID": "Set UUID", "setUUID": "Set UUID",
"warning": "Warning: Setting a custom UUID will change your current player identity", "warning": "Warning: Setting a custom UUID will change your current player identity",
"copyTooltip": "Copy UUID", "copyTooltip": "Copy UUID",
"regenerateTooltip": "Generate New UUID" "regenerateTooltip": "Generate New UUID"
}, },
"profiles": { "configurations": {
"modalTitle": "Manage Profiles", "modalTitle": "Manage Configurations",
"newProfilePlaceholder": "New Profile Name", "newProfilePlaceholder": "New Configuration Name",
"createProfile": "Create Profile" "createProfile": "Create Configuration"
}, },
"discord": { "discord": {
"notificationText": "Join our Discord community!", "notificationText": "Join our Discord community!",
@@ -230,6 +238,8 @@
"hwAccelSaved": "Hardware acceleration setting saved", "hwAccelSaved": "Hardware acceleration setting saved",
"hwAccelSaveFailed": "Failed to save hardware acceleration setting", "hwAccelSaveFailed": "Failed to save hardware acceleration setting",
"noUsername": "No username configured. Please save your username first.", "noUsername": "No username configured. Please save your username first.",
"identityAdded": "Identity added successfully!",
"identityAddFailed": "Failed to add identity",
"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",
@@ -266,8 +276,8 @@
"installing": "Installing...", "installing": "Installing...",
"extracting": "Extracting...", "extracting": "Extracting...",
"verifying": "Verifying...", "verifying": "Verifying...",
"switchingProfile": "Switching profile...", "switchingProfile": "Switching configuration...",
"profileSwitched": "Profile switched!", "profileSwitched": "Configuration switched!",
"startingGame": "Starting game...", "startingGame": "Starting game...",
"launching": "LAUNCHING...", "launching": "LAUNCHING...",
"uninstallingGame": "Uninstalling game...", "uninstallingGame": "Uninstalling game...",

View File

@@ -8,7 +8,10 @@
}, },
"header": { "header": {
"playersLabel": "Jugadores:", "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" "defaultProfile": "Predeterminado"
}, },
"install": { "install": {
@@ -137,6 +140,8 @@
"closeLauncher": "Comportamiento del Launcher", "closeLauncher": "Comportamiento del Launcher",
"closeOnStart": "Cerrar Launcher al iniciar el juego", "closeOnStart": "Cerrar Launcher al iniciar el juego",
"closeOnStartDescription": "Cierra automáticamente el launcher después de que Hytale se haya iniciado", "closeOnStartDescription": "Cierra automáticamente el launcher después de que Hytale se haya iniciado",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Aceleración por Hardware", "hwAccel": "Aceleración por Hardware",
"hwAccelDescription": "Habilitar aceleración por hardware para el launcher", "hwAccelDescription": "Habilitar aceleración por hardware para el launcher",
"gameBranch": "Rama del Juego", "gameBranch": "Rama del Juego",
@@ -151,9 +156,12 @@
}, },
"uuid": { "uuid": {
"modalTitle": "Gestión de UUID", "modalTitle": "Gestión de UUID",
"currentUserUUID": "UUID del usuario actual",
"allPlayerUUIDs": "Todos los UUIDs de jugadores", "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...", "loadingUUIDs": "Cargando UUIDs...",
"setCustomUUID": "Establecer UUID personalizado", "setCustomUUID": "Establecer UUID personalizado",
"customPlaceholder": "Ingresa un UUID personalizado (formato: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Ingresa un UUID personalizado (formato: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +170,10 @@
"copyTooltip": "Copiar UUID", "copyTooltip": "Copiar UUID",
"regenerateTooltip": "Generar nuevo UUID" "regenerateTooltip": "Generar nuevo UUID"
}, },
"profiles": { "configurations": {
"modalTitle": "Gestionar perfiles", "modalTitle": "Gestionar Configuraciones",
"newProfilePlaceholder": "Nombre del nuevo perfil", "newProfilePlaceholder": "Nombre de la nueva configuración",
"createProfile": "Crear perfil" "createProfile": "Crear Configuración"
}, },
"discord": { "discord": {
"notificationText": "¡Únete a nuestra comunidad de Discord!", "notificationText": "¡Únete a nuestra comunidad de Discord!",
@@ -219,6 +227,8 @@
"hwAccelSaved": "Configuración de aceleración por hardware guardada", "hwAccelSaved": "Configuración de aceleración por hardware guardada",
"hwAccelSaveFailed": "Error al guardar la configuración de aceleración por hardware", "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.", "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!", "switchUsernameSuccess": "¡Cambiado a \"{username}\" con éxito!",
"switchUsernameFailed": "Error al cambiar nombre de usuario", "switchUsernameFailed": "Error al cambiar nombre de usuario",
"playerNameTooLong": "El nombre del jugador debe tener 16 caracteres o menos" "playerNameTooLong": "El nombre del jugador debe tener 16 caracteres o menos"
@@ -247,8 +257,8 @@
"installing": "Instalando...", "installing": "Instalando...",
"extracting": "Extrayendo...", "extracting": "Extrayendo...",
"verifying": "Verificando...", "verifying": "Verificando...",
"switchingProfile": "Cambiando perfil...", "switchingProfile": "Cambiando configuración...",
"profileSwitched": Perfil cambiado!", "profileSwitched": Configuración cambiada!",
"startingGame": "Iniciando juego...", "startingGame": "Iniciando juego...",
"launching": "INICIANDO...", "launching": "INICIANDO...",
"uninstallingGame": "Desinstalando juego...", "uninstallingGame": "Desinstalando juego...",

View File

@@ -8,7 +8,10 @@
}, },
"header": { "header": {
"playersLabel": "Joueurs:", "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" "defaultProfile": "Par défaut"
}, },
"install": { "install": {
@@ -137,6 +140,8 @@
"closeLauncher": "Comportement du Launcher", "closeLauncher": "Comportement du Launcher",
"closeOnStart": "Fermer le Launcher au démarrage du jeu", "closeOnStart": "Fermer le Launcher au démarrage du jeu",
"closeOnStartDescription": "Fermer automatiquement le launcher après le lancement d'Hytale", "closeOnStartDescription": "Fermer automatiquement le launcher après le lancement d'Hytale",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Accélération Matérielle", "hwAccel": "Accélération Matérielle",
"hwAccelDescription": "Activer l'accélération matérielle pour le launcher", "hwAccelDescription": "Activer l'accélération matérielle pour le launcher",
"gameBranch": "Branche du Jeu", "gameBranch": "Branche du Jeu",
@@ -151,9 +156,12 @@
}, },
"uuid": { "uuid": {
"modalTitle": "Gestion UUID", "modalTitle": "Gestion UUID",
"currentUserUUID": "UUID Utilisateur Actuel",
"allPlayerUUIDs": "Tous les UUIDs Joueurs", "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...", "loadingUUIDs": "Chargement des UUIDs...",
"setCustomUUID": "Définir UUID Personnalisé", "setCustomUUID": "Définir UUID Personnalisé",
"customPlaceholder": "Entrez UUID personnalisé (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Entrez UUID personnalisé (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +170,10 @@
"copyTooltip": "Copier UUID", "copyTooltip": "Copier UUID",
"regenerateTooltip": "Générer Nouvel UUID" "regenerateTooltip": "Générer Nouvel UUID"
}, },
"profiles": { "configurations": {
"modalTitle": "Gérer les Profils", "modalTitle": "Gérer les Configurations",
"newProfilePlaceholder": "Nom du Nouveau Profil", "newProfilePlaceholder": "Nom de la Nouvelle Configuration",
"createProfile": "Créer un Profil" "createProfile": "Créer une Configuration"
}, },
"discord": { "discord": {
"notificationText": "Rejoignez notre communauté Discord!", "notificationText": "Rejoignez notre communauté Discord!",
@@ -219,6 +227,8 @@
"hwAccelSaved": "Paramètre d'accélération matérielle sauvegardé", "hwAccelSaved": "Paramètre d'accélération matérielle sauvegardé",
"hwAccelSaveFailed": "Échec de la sauvegarde du paramètre d'accélération matérielle", "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.", "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!", "switchUsernameSuccess": "Basculé vers \"{username}\" avec succès!",
"switchUsernameFailed": "Échec du changement de nom d'utilisateur", "switchUsernameFailed": "Échec du changement de nom d'utilisateur",
"playerNameTooLong": "Le nom du joueur doit comporter 16 caractères ou moins" "playerNameTooLong": "Le nom du joueur doit comporter 16 caractères ou moins"
@@ -247,8 +257,8 @@
"installing": "Installation...", "installing": "Installation...",
"extracting": "Extraction...", "extracting": "Extraction...",
"verifying": "Vérification...", "verifying": "Vérification...",
"switchingProfile": "Changement de profil...", "switchingProfile": "Changement de configuration...",
"profileSwitched": "Profil changé!", "profileSwitched": "Configuration changée !",
"startingGame": "Démarrage du jeu...", "startingGame": "Démarrage du jeu...",
"launching": "LANCEMENT...", "launching": "LANCEMENT...",
"uninstallingGame": "Désinstallation du jeu...", "uninstallingGame": "Désinstallation du jeu...",

View File

@@ -8,7 +8,10 @@
}, },
"header": { "header": {
"playersLabel": "Pemain:", "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" "defaultProfile": "Default"
}, },
"install": { "install": {
@@ -137,6 +140,8 @@
"closeLauncher": "Perilaku Launcher", "closeLauncher": "Perilaku Launcher",
"closeOnStart": "Tutup launcher saat game dimulai", "closeOnStart": "Tutup launcher saat game dimulai",
"closeOnStartDescription": "Tutup launcher secara otomatis setelah Hytale diluncurkan", "closeOnStartDescription": "Tutup launcher secara otomatis setelah Hytale diluncurkan",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Akselerasi Perangkat Keras", "hwAccel": "Akselerasi Perangkat Keras",
"hwAccelDescription": "Aktifkan akselerasi perangkat keras untuk launcher`", "hwAccelDescription": "Aktifkan akselerasi perangkat keras untuk launcher`",
"gameBranch": "Cabang Game", "gameBranch": "Cabang Game",
@@ -151,9 +156,12 @@
}, },
"uuid": { "uuid": {
"modalTitle": "Manajemen UUID", "modalTitle": "Manajemen UUID",
"currentUserUUID": "UUID Pengguna Saat Ini",
"allPlayerUUIDs": "Semua UUID Pemain", "allPlayerUUIDs": "Semua UUID Pemain",
"generateNew": "Hasilkan UUID Baru", "addIdentity": "Tambah Identitas",
"usernamePlaceholder": "Nama Pengguna",
"add": "Tambah",
"cancel": "Batal",
"advanced": "Lanjutan",
"loadingUUIDs": "Memuat UUID...", "loadingUUIDs": "Memuat UUID...",
"setCustomUUID": "Setel UUID Kustom", "setCustomUUID": "Setel UUID Kustom",
"customPlaceholder": "Masukkan UUID kustom (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Masukkan UUID kustom (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +170,10 @@
"copyTooltip": "Salin UUID", "copyTooltip": "Salin UUID",
"regenerateTooltip": "Hasilkan UUID Baru" "regenerateTooltip": "Hasilkan UUID Baru"
}, },
"profiles": { "configurations": {
"modalTitle": "Kelola Profil", "modalTitle": "Kelola Konfigurasi",
"newProfilePlaceholder": "Nama Profil Baru", "newProfilePlaceholder": "Nama Konfigurasi Baru",
"createProfile": "Buat Profil" "createProfile": "Buat Konfigurasi"
}, },
"discord": { "discord": {
"notificationText": "Gabung komunitas Discord kami!", "notificationText": "Gabung komunitas Discord kami!",
@@ -219,6 +227,8 @@
"hwAccelSaved": "Pengaturan akselerasi perangkat keras disimpan", "hwAccelSaved": "Pengaturan akselerasi perangkat keras disimpan",
"hwAccelSaveFailed": "Gagal menyimpan pengaturan akselerasi perangkat keras", "hwAccelSaveFailed": "Gagal menyimpan pengaturan akselerasi perangkat keras",
"noUsername": "Nama pengguna belum dikonfigurasi. Silakan simpan nama pengguna terlebih dahulu.", "noUsername": "Nama pengguna belum dikonfigurasi. Silakan simpan nama pengguna terlebih dahulu.",
"identityAdded": "Identitas berhasil ditambahkan!",
"identityAddFailed": "Gagal menambahkan identitas",
"switchUsernameSuccess": "Berhasil beralih ke \"{username}\"!", "switchUsernameSuccess": "Berhasil beralih ke \"{username}\"!",
"switchUsernameFailed": "Gagal beralih nama pengguna", "switchUsernameFailed": "Gagal beralih nama pengguna",
"playerNameTooLong": "Nama pemain harus 16 karakter atau kurang" "playerNameTooLong": "Nama pemain harus 16 karakter atau kurang"
@@ -247,8 +257,8 @@
"installing": "Menginstal...", "installing": "Menginstal...",
"extracting": "Mengekstrak...", "extracting": "Mengekstrak...",
"verifying": "Memverifikasi...", "verifying": "Memverifikasi...",
"switchingProfile": "Beralih profil...", "switchingProfile": "Beralih konfigurasi...",
"profileSwitched": "Profil dialihkan!", "profileSwitched": "Konfigurasi dialihkan!",
"startingGame": "Memulai game...", "startingGame": "Memulai game...",
"launching": "MELUNCURKAN...", "launching": "MELUNCURKAN...",
"uninstallingGame": "Menghapus instalasi game...", "uninstallingGame": "Menghapus instalasi game...",

View File

@@ -8,7 +8,10 @@
}, },
"header": { "header": {
"playersLabel": "Graczy:", "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" "defaultProfile": "Domyślny"
}, },
"install": { "install": {
@@ -137,6 +140,8 @@
"closeLauncher": "Zachowanie Launchera", "closeLauncher": "Zachowanie Launchera",
"closeOnStart": "Zamknij Launcher przy starcie gry", "closeOnStart": "Zamknij Launcher przy starcie gry",
"closeOnStartDescription": "Automatycznie zamknij launcher po uruchomieniu Hytale", "closeOnStartDescription": "Automatycznie zamknij launcher po uruchomieniu Hytale",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Przyspieszenie Sprzętowe", "hwAccel": "Przyspieszenie Sprzętowe",
"hwAccelDescription": "Włącz przyspieszenie sprzętowe dla launchera", "hwAccelDescription": "Włącz przyspieszenie sprzętowe dla launchera",
"gameBranch": "Gałąź Gry", "gameBranch": "Gałąź Gry",
@@ -151,9 +156,12 @@
}, },
"uuid": { "uuid": {
"modalTitle": "Zarządzanie UUID", "modalTitle": "Zarządzanie UUID",
"currentUserUUID": "Aktualny UUID użytkownika",
"allPlayerUUIDs": "Wszystkie identyfikatory UUID graczy", "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...", "loadingUUIDs": "Ładowanie UUID...",
"setCustomUUID": "Ustaw niestandardowy UUID", "setCustomUUID": "Ustaw niestandardowy UUID",
"customPlaceholder": "Wprowadź niestandardowy UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Wprowadź niestandardowy UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +170,10 @@
"copyTooltip": "Kopiuj UUID", "copyTooltip": "Kopiuj UUID",
"regenerateTooltip": "Wygeneruj nowy UUID" "regenerateTooltip": "Wygeneruj nowy UUID"
}, },
"profiles": { "configurations": {
"modalTitle": "Zarządzaj Profilami", "modalTitle": "Zarządzaj Konfiguracjami",
"newProfilePlaceholder": "Nowa Nazwa Profilu", "newProfilePlaceholder": "Nazwa Nowej Konfiguracji",
"createProfile": "Utwórz Profil" "createProfile": "Utwórz Konfigurację"
}, },
"discord": { "discord": {
"notificationText": "Dołącz do naszej społeczności Discord!", "notificationText": "Dołącz do naszej społeczności Discord!",
@@ -219,6 +227,8 @@
"hwAccelSaved": "Zapisano ustawienie przyspieszenia sprzętowego", "hwAccelSaved": "Zapisano ustawienie przyspieszenia sprzętowego",
"hwAccelSaveFailed": "Nie udało się zapisać ustawienia 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.", "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}\"!", "switchUsernameSuccess": "Pomyślnie przełączono na \"{username}\"!",
"switchUsernameFailed": "Nie udało się przełączyć nazwy użytkownika", "switchUsernameFailed": "Nie udało się przełączyć nazwy użytkownika",
"playerNameTooLong": "Nazwa gracza musi mieć 16 znaków lub mniej" "playerNameTooLong": "Nazwa gracza musi mieć 16 znaków lub mniej"
@@ -247,8 +257,8 @@
"installing": "Instalowanie...", "installing": "Instalowanie...",
"extracting": "Ekstraktowanie...", "extracting": "Ekstraktowanie...",
"verifying": "Weryfikowanie...", "verifying": "Weryfikowanie...",
"switchingProfile": "Przełączanie profilu...", "switchingProfile": "Przełączanie konfiguracji...",
"profileSwitched": "Profil zmieniony!", "profileSwitched": "Konfiguracja zmieniona!",
"startingGame": "Uruchamianie gry...", "startingGame": "Uruchamianie gry...",
"launching": "URUCHAMIANIE...", "launching": "URUCHAMIANIE...",
"uninstallingGame": "Odinstalowywanie gry...", "uninstallingGame": "Odinstalowywanie gry...",

View File

@@ -8,7 +8,10 @@
}, },
"header": { "header": {
"playersLabel": "Jogadores:", "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" "defaultProfile": "Padrão"
}, },
"install": { "install": {
@@ -137,6 +140,8 @@
"closeLauncher": "Comportamento do Lançador", "closeLauncher": "Comportamento do Lançador",
"closeOnStart": "Fechar Lançador ao iniciar o jogo", "closeOnStart": "Fechar Lançador ao iniciar o jogo",
"closeOnStartDescription": "Fechar automaticamente o lançador após o Hytale ter sido iniciado", "closeOnStartDescription": "Fechar automaticamente o lançador após o Hytale ter sido iniciado",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Aceleração de Hardware", "hwAccel": "Aceleração de Hardware",
"hwAccelDescription": "Ativar aceleração de hardware para o lançador", "hwAccelDescription": "Ativar aceleração de hardware para o lançador",
"gameBranch": "Versão do Jogo", "gameBranch": "Versão do Jogo",
@@ -151,9 +156,12 @@
}, },
"uuid": { "uuid": {
"modalTitle": "Gerenciamento de UUID", "modalTitle": "Gerenciamento de UUID",
"currentUserUUID": "UUID do usuário atual",
"allPlayerUUIDs": "Todos os UUIDs de jogadores", "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...", "loadingUUIDs": "Carregando UUIDs...",
"setCustomUUID": "Definir UUID personalizado", "setCustomUUID": "Definir UUID personalizado",
"customPlaceholder": "Digite um UUID personalizado (formato: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Digite um UUID personalizado (formato: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +170,10 @@
"copyTooltip": "Copiar UUID", "copyTooltip": "Copiar UUID",
"regenerateTooltip": "Gerar novo UUID" "regenerateTooltip": "Gerar novo UUID"
}, },
"profiles": { "configurations": {
"modalTitle": "Gerenciar perfis", "modalTitle": "Gerenciar Configurações",
"newProfilePlaceholder": "Nome do novo perfil", "newProfilePlaceholder": "Nome da Nova Configuração",
"createProfile": "Criar perfil" "createProfile": "Criar Configuração"
}, },
"discord": { "discord": {
"notificationText": "Junte-se à nossa comunidade do Discord!", "notificationText": "Junte-se à nossa comunidade do Discord!",
@@ -219,6 +227,8 @@
"hwAccelSaved": "Configuração de aceleração de hardware salva", "hwAccelSaved": "Configuração de aceleração de hardware salva",
"hwAccelSaveFailed": "Falha ao salvar configuração de aceleração de hardware", "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.", "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!", "switchUsernameSuccess": "Alterado para \"{username}\" com sucesso!",
"switchUsernameFailed": "Falha ao trocar nome de usuário", "switchUsernameFailed": "Falha ao trocar nome de usuário",
"playerNameTooLong": "O nome do jogador deve ter 16 caracteres ou menos" "playerNameTooLong": "O nome do jogador deve ter 16 caracteres ou menos"
@@ -247,8 +257,8 @@
"installing": "Instalando...", "installing": "Instalando...",
"extracting": "Extraindo...", "extracting": "Extraindo...",
"verifying": "Verificando...", "verifying": "Verificando...",
"switchingProfile": "Alternando perfil...", "switchingProfile": "Alternando configuração...",
"profileSwitched": "Perfil alternado!", "profileSwitched": "Configuração alternada!",
"startingGame": "Iniciando jogo...", "startingGame": "Iniciando jogo...",
"launching": "INICIANDO...", "launching": "INICIANDO...",
"uninstallingGame": "Desinstalando jogo...", "uninstallingGame": "Desinstalando jogo...",

View File

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

View File

@@ -8,7 +8,10 @@
}, },
"header": { "header": {
"playersLabel": "Spelare:", "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" "defaultProfile": "Standard"
}, },
"install": { "install": {
@@ -137,6 +140,8 @@
"closeLauncher": "Launcher-beteende", "closeLauncher": "Launcher-beteende",
"closeOnStart": "Stäng launcher vid spelstart", "closeOnStart": "Stäng launcher vid spelstart",
"closeOnStartDescription": "Stäng automatiskt launcher efter att Hytale har startats", "closeOnStartDescription": "Stäng automatiskt launcher efter att Hytale har startats",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Hårdvaruacceleration", "hwAccel": "Hårdvaruacceleration",
"hwAccelDescription": "Aktivera hårdvaruacceleration för launchern", "hwAccelDescription": "Aktivera hårdvaruacceleration för launchern",
"gameBranch": "Spelgren", "gameBranch": "Spelgren",
@@ -151,9 +156,12 @@
}, },
"uuid": { "uuid": {
"modalTitle": "UUID-hantering", "modalTitle": "UUID-hantering",
"currentUserUUID": "Nuvarande användar-UUID",
"allPlayerUUIDs": "Alla spelare-UUID:er", "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...", "loadingUUIDs": "Laddar UUID:er...",
"setCustomUUID": "Ange anpassad UUID", "setCustomUUID": "Ange anpassad UUID",
"customPlaceholder": "Ange anpassad UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Ange anpassad UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +170,10 @@
"copyTooltip": "Kopiera UUID", "copyTooltip": "Kopiera UUID",
"regenerateTooltip": "Generera ny UUID" "regenerateTooltip": "Generera ny UUID"
}, },
"profiles": { "configurations": {
"modalTitle": "Hantera profiler", "modalTitle": "Hantera konfigurationer",
"newProfilePlaceholder": "Nytt profilnamn", "newProfilePlaceholder": "Nytt konfigurationsnamn",
"createProfile": "Skapa profil" "createProfile": "Skapa konfiguration"
}, },
"discord": { "discord": {
"notificationText": "Gå med i vår Discord-gemenskap!", "notificationText": "Gå med i vår Discord-gemenskap!",
@@ -219,6 +227,8 @@
"hwAccelSaved": "Hårdvaruaccelerationsinställning sparad", "hwAccelSaved": "Hårdvaruaccelerationsinställning sparad",
"hwAccelSaveFailed": "Misslyckades med att spara hårdvaruaccelerationsinställning", "hwAccelSaveFailed": "Misslyckades med att spara hårdvaruaccelerationsinställning",
"noUsername": "Inget användarnamn konfigurerat. Vänligen spara ditt användarnamn först.", "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!", "switchUsernameSuccess": "Bytte till \"{username}\" framgångsrikt!",
"switchUsernameFailed": "Misslyckades med att byta användarnamn", "switchUsernameFailed": "Misslyckades med att byta användarnamn",
"playerNameTooLong": "Spelarnamnet måste vara 16 tecken eller mindre" "playerNameTooLong": "Spelarnamnet måste vara 16 tecken eller mindre"
@@ -247,8 +257,8 @@
"installing": "Installerar...", "installing": "Installerar...",
"extracting": "Extraherar...", "extracting": "Extraherar...",
"verifying": "Verifierar...", "verifying": "Verifierar...",
"switchingProfile": "Byter profil...", "switchingProfile": "Byter konfiguration...",
"profileSwitched": "Profil bytt!", "profileSwitched": "Konfiguration bytt!",
"startingGame": "Startar spel...", "startingGame": "Startar spel...",
"launching": "STARTAR...", "launching": "STARTAR...",
"uninstallingGame": "Avinstallerar spel...", "uninstallingGame": "Avinstallerar spel...",

View File

@@ -8,7 +8,10 @@
}, },
"header": { "header": {
"playersLabel": "Oyuncular:", "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" "defaultProfile": "Varsayılan"
}, },
"install": { "install": {
@@ -137,6 +140,8 @@
"closeLauncher": "Başlatıcı Davranışı", "closeLauncher": "Başlatıcı Davranışı",
"closeOnStart": "Oyun başlatıldığında Başlatıcıyı Kapat", "closeOnStart": "Oyun başlatıldığında Başlatıcıyı Kapat",
"closeOnStartDescription": "Hytale başlatıldıktan sonra başlatıcıyı otomatik olarak kapatın", "closeOnStartDescription": "Hytale başlatıldıktan sonra başlatıcıyı otomatik olarak kapatın",
"allowMultiInstance": "Allow multiple game instances",
"allowMultiInstanceDescription": "Allow running multiple game clients at the same time (useful for mod development)",
"hwAccel": "Donanım Hızlandırma", "hwAccel": "Donanım Hızlandırma",
"hwAccelDescription": "Başlatıcı için donanım hızlandırmasını etkinleştir", "hwAccelDescription": "Başlatıcı için donanım hızlandırmasını etkinleştir",
"gameBranch": "Oyun Dalı", "gameBranch": "Oyun Dalı",
@@ -151,9 +156,12 @@
}, },
"uuid": { "uuid": {
"modalTitle": "UUID Yönetimi", "modalTitle": "UUID Yönetimi",
"currentUserUUID": "Geçerli Kullanıcı UUID",
"allPlayerUUIDs": "Tüm Oyuncu UUID'leri", "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...", "loadingUUIDs": "UUID'ler yükleniyor...",
"setCustomUUID": "Özel UUID Ayarla", "setCustomUUID": "Özel UUID Ayarla",
"customPlaceholder": "Özel UUID girin (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", "customPlaceholder": "Özel UUID girin (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
@@ -162,10 +170,10 @@
"copyTooltip": "UUID'yi Kopyala", "copyTooltip": "UUID'yi Kopyala",
"regenerateTooltip": "Yeni UUID Oluştur" "regenerateTooltip": "Yeni UUID Oluştur"
}, },
"profiles": { "configurations": {
"modalTitle": "Profilleri Yönet", "modalTitle": "Yapılandırmaları Yönet",
"newProfilePlaceholder": "Yeni Profil Adı", "newProfilePlaceholder": "Yeni Yapılandırma Adı",
"createProfile": "Profil Oluştur" "createProfile": "Yapılandırma Oluştur"
}, },
"discord": { "discord": {
"notificationText": "Discord topluluğumuza katılın!", "notificationText": "Discord topluluğumuza katılın!",
@@ -219,6 +227,8 @@
"hwAccelSaved": "Donanım hızlandırma ayarı kaydedildi", "hwAccelSaved": "Donanım hızlandırma ayarı kaydedildi",
"hwAccelSaveFailed": "Donanım hızlandırma ayarı kaydedilemedi", "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.", "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!", "switchUsernameSuccess": "\"{username}\" adına başarıyla geçildi!",
"switchUsernameFailed": "Kullanıcı adı değiştirilemedi", "switchUsernameFailed": "Kullanıcı adı değiştirilemedi",
"playerNameTooLong": "Oyuncu adı 16 karakter veya daha az olmalıdır" "playerNameTooLong": "Oyuncu adı 16 karakter veya daha az olmalıdır"
@@ -247,8 +257,8 @@
"installing": "Kuruluyur...", "installing": "Kuruluyur...",
"extracting": "Ayıklanıyor...", "extracting": "Ayıklanıyor...",
"verifying": "Doğrulanıyor...", "verifying": "Doğrulanıyor...",
"switchingProfile": "Profil değiştiriliyor...", "switchingProfile": "Yapılandırma değiştiriliyor...",
"profileSwitched": "Profil değiştirildi!", "profileSwitched": "Yapılandırma değiştirildi!",
"startingGame": "Oyun başlatılıyor...", "startingGame": "Oyun başlatılıyor...",
"launching": "BAŞLATILIYOR...", "launching": "BAŞLATILIYOR...",
"uninstallingGame": "Oyun kaldırılıyor...", "uninstallingGame": "Oyun kaldırılıyor...",

View File

@@ -5575,9 +5575,8 @@ input[type="text"].uuid-input,
font-family: 'Space Grotesk', sans-serif; font-family: 'Space Grotesk', sans-serif;
} }
.uuid-current-section,
.uuid-list-section, .uuid-list-section,
.uuid-custom-section { .uuid-advanced-section {
background: rgba(255, 255, 255, 0.03); background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px; border-radius: 12px;
@@ -5824,6 +5823,96 @@ input[type="text"].uuid-input,
color: #3b82f6; 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) { @media (max-width: 600px) {
.uuid-modal-content { .uuid-modal-content {
width: 95vw; width: 95vw;
@@ -5835,8 +5924,8 @@ input[type="text"].uuid-input,
gap: 1.5rem; gap: 1.5rem;
} }
.uuid-current-display, .uuid-custom-form,
.uuid-custom-form { .uuid-add-form-row {
flex-direction: column; flex-direction: column;
} }
@@ -6213,6 +6302,155 @@ input[type="text"].uuid-input,
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5); 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 Styles */
.profile-selector { .profile-selector {
position: relative; position: relative;

View File

@@ -708,6 +708,15 @@ function loadLauncherHardwareAcceleration() {
return config.launcherHardwareAcceleration !== undefined ? config.launcherHardwareAcceleration : true; return config.launcherHardwareAcceleration !== undefined ? config.launcherHardwareAcceleration : true;
} }
function saveAllowMultiInstance(enabled) {
saveConfig({ allowMultiInstance: !!enabled });
}
function loadAllowMultiInstance() {
const config = loadConfig();
return config.allowMultiInstance !== undefined ? config.allowMultiInstance : false;
}
// ============================================================================= // =============================================================================
// MODS MANAGEMENT // MODS MANAGEMENT
// ============================================================================= // =============================================================================
@@ -1105,6 +1114,8 @@ module.exports = {
loadCloseLauncherOnStart, loadCloseLauncherOnStart,
saveLauncherHardwareAcceleration, saveLauncherHardwareAcceleration,
loadLauncherHardwareAcceleration, loadLauncherHardwareAcceleration,
saveAllowMultiInstance,
loadAllowMultiInstance,
// Mods // Mods
saveModsToConfig, saveModsToConfig,

View File

@@ -20,6 +20,8 @@ const {
saveLauncherHardwareAcceleration, saveLauncherHardwareAcceleration,
loadLauncherHardwareAcceleration, loadLauncherHardwareAcceleration,
saveAllowMultiInstance,
loadAllowMultiInstance,
loadConfig, loadConfig,
saveConfig, saveConfig,
@@ -151,6 +153,10 @@ module.exports = {
saveLauncherHardwareAcceleration, saveLauncherHardwareAcceleration,
loadLauncherHardwareAcceleration, loadLauncherHardwareAcceleration,
// Multi-instance functions
saveAllowMultiInstance,
loadAllowMultiInstance,
// Config functions // Config functions
loadConfig, loadConfig,
saveConfig, saveConfig,

View File

@@ -30,6 +30,7 @@ const { syncModsForCurrentProfile } = require('./modManager');
const { getUserDataPath } = require('../utils/userDataMigration'); const { getUserDataPath } = require('../utils/userDataMigration');
const { syncServerList } = require('../utils/serverListSync'); const { syncServerList } = require('../utils/serverListSync');
const { killGameProcesses } = require('./gameManager'); const { killGameProcesses } = require('./gameManager');
const { loadAllowMultiInstance } = require('../core/config');
// Client patcher for custom auth server (sanasol.ws) // Client patcher for custom auth server (sanasol.ws)
let clientPatcher = null; let clientPatcher = null;
@@ -464,8 +465,10 @@ async function launchGame(playerNameOverride = null, progressCallback, javaPathO
} }
// Kill any stalled game processes from a previous launch to prevent file locks // Kill any stalled game processes from a previous launch to prevent file locks
// and "game already running" issues // and "game already running" issues (skip if multi-instance mode is enabled)
await killGameProcesses(); if (!loadAllowMultiInstance()) {
await killGameProcesses();
}
// Remove AOT cache: generated by official Hytale JRE, incompatible with F2P JRE. // Remove AOT cache: generated by official Hytale JRE, incompatible with F2P JRE.
// Client adds -XX:AOTCache when this file exists, causing classloading failures. // Client adds -XX:AOTCache when this file exists, causing classloading failures.
@@ -479,15 +482,11 @@ async function launchGame(playerNameOverride = null, progressCallback, javaPathO
} }
} }
// DualAuth Agent: Set JAVA_TOOL_OPTIONS so java picks up -javaagent: flag // DualAuth Agent: DISABLED for debug - testing fastutil classloader issue
// This enables runtime auth patching without modifying the server JAR // TODO: re-enable after testing
const agentJar = path.join(gameLatest, 'Server', 'dualauth-agent.jar'); const agentJar = path.join(gameLatest, 'Server', 'dualauth-agent.jar');
if (fs.existsSync(agentJar)) { if (fs.existsSync(agentJar)) {
const agentFlag = `-javaagent:"${agentJar}"`; console.log('DualAuth Agent: SKIPPED (debug build - fastutil classloader test)');
env.JAVA_TOOL_OPTIONS = env.JAVA_TOOL_OPTIONS
? `${env.JAVA_TOOL_OPTIONS} ${agentFlag}`
: agentFlag;
console.log('DualAuth Agent: enabled via JAVA_TOOL_OPTIONS');
} }
try { try {

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; return null;
} }
@@ -409,7 +426,7 @@ function extractTarGz(tarGzPath, dest) {
function flattenJREDir(jreLatest) { function flattenJREDir(jreLatest) {
try { try {
const entries = fs.readdirSync(jreLatest, { withFileTypes: true }); const entries = fs.readdirSync(jreLatest, { withFileTypes: true });
if (entries.length !== 1 || !entries[0].isDirectory()) { if (entries.length !== 1 || !entries[0].isDirectory()) {
return; return;
} }
@@ -420,12 +437,48 @@ function flattenJREDir(jreLatest) {
for (const file of files) { for (const file of files) {
const oldPath = path.join(nested, file.name); const oldPath = path.join(nested, file.name);
const newPath = path.join(jreLatest, 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) { } 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 @@
# Singleplayer Server Crash: fastutil ClassNotFoundException # Singleplayer Server Crash: fastutil ClassNotFoundException
## Status: Open (user-specific, Feb 24 2026) ## Status: Open (multiple users, Feb 24-27 2026)
## Symptom ## Symptom
@@ -16,12 +16,14 @@ Caused by: java.lang.ClassNotFoundException: it.unimi.dsi.fastutil.objects.Objec
Server exits with code 1. Multiplayer works fine for the same user. Server exits with code 1. Multiplayer works fine for the same user.
## Affected User ## Affected Users
1. **ヅ𝚃 JAYED !** (Feb 24) — Windows x86_64, had AOT cache errors before fastutil crash
2. **Asentrix** (Feb 27) — Windows x86_64 (NT 10.0.26200.0), RTX 4060, Launcher v2.4.4, NO AOT cache errors
- Discord: ヅ𝚃 JAYED !
- Platform: Windows (standard x86_64, NOT ARM)
- Reproduces 100% on singleplayer, every attempt - Reproduces 100% on singleplayer, every attempt
- Other users (including macOS/Linux) are NOT affected - Multiplayer works fine for both users
- macOS/Linux users are NOT affected
## What Works ## What Works
@@ -43,42 +45,49 @@ Server exits with code 1. Multiplayer works fine for the same user.
- **Corrupted game files**: Repair/reinstall didn't help - **Corrupted game files**: Repair/reinstall didn't help
- **ARM64/Parallels**: User is on standard Windows, not ARM - **ARM64/Parallels**: User is on standard Windows, not ARM
### Likely Causes (user-specific) ### Likely Cause
1. **Antivirus interference** — Windows Defender or third-party AV blocking Java from reading classes out of JAR files, especially with `-javaagent` active **CDS (Class Data Sharing) broken by bootstrap classloader modification.** DualAuth agent calls `appendToBootstrapClassLoaderSearch()` which triggers JVM warning: `"Sharing is only supported for boot loader classes because bootstrap classpath has been appended"`. This disables AppCDS for application classes. On some Windows systems, this breaks the classloader's ability to find classes (including fastutil) from the fat JAR.
2. **Corrupted/incompatible JRE** — bundled JRE might be broken on their system
3. **File locking** — another process holding HytaleServer.jar open
## Debugging Steps (ask user) This warning appears for ALL users, but only breaks classloading on some Windows systems — reason unknown.
1. **Does official Hytale singleplayer work?** (without F2P launcher) ### Other Possible Causes
- Yes → something about our launch setup
- No → their system/JRE issue
2. **Check antivirus** — add game directory to Windows Defender exclusions: 1. **Antivirus interference** — AV blocking Java from reading classes out of JAR files
- Settings → Windows Security → Virus & threat protection → Exclusions 2. **File locking** — another process holding HytaleServer.jar open (Asentrix had stalled java.exe killed at launch)
- Add their HytaleF2P install folder
3. **Verify fastutil is in the JAR**: ## Potential Fix: `-Xshare:off` (testing Feb 27)
```cmd
jar tf "D:\path\to\Server\HytaleServer.jar" | findstr fastutil
```
- If output shows fastutil classes → JAR is fine, classloader issue
- If no output → JAR is incomplete/corrupt (different from other users)
4. **Try without DualAuth agent** — rename `dualauth-agent.jar` in Server/ folder, retry singleplayer Disables CDS entirely, forcing standard classloading. User can add via launcher:
- If works → agent's classloader manipulation breaks fastutil on their setup 1. **Settings****Java Wrapper Configuration****Arguments to Inject**
- If still fails → unrelated to agent 2. Add `-Xshare:off` with **Server Only** condition
3. Retry singleplayer
5. **Check JRE version** — have them run: Sent to affected users for testing — **awaiting results**.
```cmd
"D:\path\to\jre\latest\bin\java.exe" -version
```
## Update (Feb 24): `-XX:+UseCompactObjectHeaders` stripping removed from defaults If confirmed, should be added as default inject arg (server-only) in launcher config.
## Debugging Steps (for reference)
Most steps are impractical for F2P users:
- ~~Official Hytale singleplayer~~ — F2P users don't have official access
- ~~Try without DualAuth agent~~ — not possible, agent required for F2P token validation
- ~~Verify fastutil in JAR~~ — same JAR for all users, not a user-actionable step
- ~~Check JRE version~~ — bundled with launcher, same for all users
**Practical steps:**
1. **Add `-Xshare:off`** via wrapper inject args (server-only) — testing now
2. **Check antivirus** — add game directory to Windows Defender exclusions
3. **Check for stalled processes** — kill any leftover java.exe/HytaleServer before launch
## Update History
### Feb 24: `-XX:+UseCompactObjectHeaders` stripping removed from defaults
Stripping this flag did NOT fix the issue. The server already has `-XX:+IgnoreUnrecognizedVMOptions` so unrecognized flags are harmless. The flag was removed from default `stripFlags` in `backend/core/config.js`. Stripping this flag did NOT fix the issue. The server already has `-XX:+IgnoreUnrecognizedVMOptions` so unrecognized flags are harmless. The flag was removed from default `stripFlags` in `backend/core/config.js`.
### Feb 27: Second user (Asentrix) reported, `-Xshare:off` sent for testing
Asentrix hit the same crash on Launcher v2.4.4. Unlike JAYED, no AOT cache errors — just the CDS sharing warning followed by fastutil ClassNotFoundException. This confirms the issue is not AOT-specific but related to CDS/classloader interaction with the DualAuth agent's bootstrap CL modification. Sent `-Xshare:off` workaround to affected users — awaiting results.
## Using the Java Wrapper to Strip JVM Flags ## Using the Java Wrapper to Strip JVM Flags
If a user needs to strip a specific JVM flag (e.g., for debugging or compatibility), they can do it via the launcher UI: If a user needs to strip a specific JVM flag (e.g., for debugging or compatibility), they can do it via the launcher UI:
@@ -110,4 +119,5 @@ Config is stored in `config.json` under `javaWrapperConfig`:
- Java wrapper config: `backend/core/config.js` (stripFlags / injectArgs) - Java wrapper config: `backend/core/config.js` (stripFlags / injectArgs)
- DualAuth Agent: v1.1.12, package `ws.sanasol.dualauth` - DualAuth Agent: v1.1.12, package `ws.sanasol.dualauth`
- Game version at time of report: `2026.02.19-1a311a592` - Game version at time of reports: `2026.02.19-1a311a592`
- Log submission ID (Asentrix): `c88e7b71`

16
main.js
View File

@@ -3,7 +3,7 @@ require('dotenv').config({ path: path.join(__dirname, '.env') });
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron'); const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
const { autoUpdater } = require('electron-updater'); const { autoUpdater } = require('electron-updater');
const fs = require('fs'); const fs = require('fs');
const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, saveLauncherHardwareAcceleration, loadLauncherHardwareAcceleration, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched, loadConfig, saveConfig, checkLaunchReady } = require('./backend/launcher'); const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, saveLauncherHardwareAcceleration, loadLauncherHardwareAcceleration, saveAllowMultiInstance, loadAllowMultiInstance, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched, loadConfig, saveConfig, checkLaunchReady } = require('./backend/launcher');
const { retryPWRDownload } = require('./backend/managers/gameManager'); const { retryPWRDownload } = require('./backend/managers/gameManager');
const { migrateUserDataToCentralized } = require('./backend/utils/userDataMigration'); const { migrateUserDataToCentralized } = require('./backend/utils/userDataMigration');
@@ -23,8 +23,9 @@ const profileManager = require('./backend/managers/profileManager');
logger.interceptConsole(); logger.interceptConsole();
// Single instance lock // Single instance lock (skip if multi-instance mode is enabled)
const gotTheLock = app.requestSingleInstanceLock(); const multiInstanceEnabled = loadAllowMultiInstance();
const gotTheLock = multiInstanceEnabled || app.requestSingleInstanceLock();
if (!gotTheLock) { if (!gotTheLock) {
console.log('Another instance is already running. Quitting...'); console.log('Another instance is already running. Quitting...');
@@ -740,6 +741,15 @@ ipcMain.handle('load-close-launcher', () => {
return loadCloseLauncherOnStart(); return loadCloseLauncherOnStart();
}); });
ipcMain.handle('save-allow-multi-instance', (event, enabled) => {
saveAllowMultiInstance(enabled);
return { success: true };
});
ipcMain.handle('load-allow-multi-instance', () => {
return loadAllowMultiInstance();
});
ipcMain.handle('save-launcher-hw-accel', (event, enabled) => { ipcMain.handle('save-launcher-hw-accel', (event, enabled) => {
saveLauncherHardwareAcceleration(enabled); saveLauncherHardwareAcceleration(enabled);
return { success: true }; return { success: true };

View File

@@ -1,6 +1,6 @@
{ {
"name": "hytale-f2p-launcher", "name": "hytale-f2p-launcher",
"version": "2.4.3", "version": "2.4.5",
"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",

View File

@@ -20,6 +20,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
loadLanguage: () => ipcRenderer.invoke('load-language'), loadLanguage: () => ipcRenderer.invoke('load-language'),
saveCloseLauncher: (enabled) => ipcRenderer.invoke('save-close-launcher', enabled), saveCloseLauncher: (enabled) => ipcRenderer.invoke('save-close-launcher', enabled),
loadCloseLauncher: () => ipcRenderer.invoke('load-close-launcher'), loadCloseLauncher: () => ipcRenderer.invoke('load-close-launcher'),
saveAllowMultiInstance: (enabled) => ipcRenderer.invoke('save-allow-multi-instance', enabled),
loadAllowMultiInstance: () => ipcRenderer.invoke('load-allow-multi-instance'),
loadConfig: () => ipcRenderer.invoke('load-config'), loadConfig: () => ipcRenderer.invoke('load-config'),
saveConfig: (configUpdate) => ipcRenderer.invoke('save-config', configUpdate), saveConfig: (configUpdate) => ipcRenderer.invoke('save-config', configUpdate),

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)