mirror of
https://git.sanhost.net/sanasol/hytale-f2p
synced 2026-02-26 09:21:48 -03:00
Enforce 16-char player name limit and update mod sync
Added a maxlength attribute to the player name input and enforced a 16-character limit in both install and settings scripts, providing user feedback if exceeded. Refactored modManager.js to replace symlink-based mod management with a copy-based system, copying enabled mods to HytaleSaves\Mods and removing legacy symlink logic to improve compatibility and avoid permission issues.
This commit is contained in:
@@ -120,7 +120,7 @@
|
||||
<label class="form-label" data-i18n="install.playerName">Player Name</label>
|
||||
<input type="text" id="installPlayerName"
|
||||
data-i18n-placeholder="install.playerNamePlaceholder" class="form-input"
|
||||
value="Player" />
|
||||
value="Player" maxlength="16" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
||||
@@ -45,9 +45,17 @@ export function setupInstallation() {
|
||||
export async function installGame() {
|
||||
if (isDownloading || (installBtn && installBtn.disabled)) return;
|
||||
|
||||
const playerName = (installPlayerName ? installPlayerName.value.trim() : '') || 'Player';
|
||||
let playerName = (installPlayerName ? installPlayerName.value.trim() : '') || 'Player';
|
||||
const installPath = installPathInput ? installPathInput.value.trim() : '';
|
||||
|
||||
// Limit player name to 16 characters
|
||||
if (playerName.length > 16) {
|
||||
playerName = playerName.substring(0, 16);
|
||||
if (installPlayerName) {
|
||||
installPlayerName.value = playerName;
|
||||
}
|
||||
}
|
||||
|
||||
const selectedBranchRadio = document.querySelector('input[name="installBranch"]:checked');
|
||||
const selectedBranch = selectedBranchRadio ? selectedBranchRadio.value : 'release';
|
||||
|
||||
@@ -194,7 +202,16 @@ export async function browseInstallPath() {
|
||||
async function savePlayerName() {
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.saveSettings) {
|
||||
const playerName = (installPlayerName ? installPlayerName.value.trim() : '') || 'Player';
|
||||
let playerName = (installPlayerName ? installPlayerName.value.trim() : '') || 'Player';
|
||||
|
||||
// Limit player name to 16 characters
|
||||
if (playerName.length > 16) {
|
||||
playerName = playerName.substring(0, 16);
|
||||
if (installPlayerName) {
|
||||
installPlayerName.value = playerName;
|
||||
}
|
||||
}
|
||||
|
||||
await window.electronAPI.saveSettings({ playerName });
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -439,6 +439,13 @@ async function savePlayerName() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerName.length > 16) {
|
||||
const msg = window.i18n ? window.i18n.t('notifications.playerNameTooLong') : 'Player name must be 16 characters or less';
|
||||
showNotification(msg, 'error');
|
||||
settingsPlayerName.value = playerName.substring(0, 16);
|
||||
return;
|
||||
}
|
||||
|
||||
await window.electronAPI.saveUsername(playerName);
|
||||
const successMsg = window.i18n ? window.i18n.t('notifications.playerNameSaved') : 'Player name saved successfully';
|
||||
showNotification(successMsg, 'success');
|
||||
|
||||
@@ -3,7 +3,7 @@ const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const axios = require('axios');
|
||||
const { getOS } = require('../utils/platformUtils');
|
||||
const { getModsPath, getProfilesDir } = require('../core/paths');
|
||||
const { getModsPath, getProfilesDir, getHytaleSavesDir } = require('../core/paths');
|
||||
const { saveModsToConfig, loadModsFromConfig } = require('../core/config');
|
||||
const profileManager = require('./profileManager');
|
||||
|
||||
@@ -296,8 +296,9 @@ async function syncModsForCurrentProfile() {
|
||||
console.log(`[ModManager] Syncing mods for profile: ${activeProfile.name} (${activeProfile.id})`);
|
||||
|
||||
// 1. Resolve Paths
|
||||
// globalModsPath is the one the game uses (symlink target)
|
||||
const globalModsPath = await getModsPath();
|
||||
// centralModsPath is HytaleSaves\Mods (centralized location for active mods)
|
||||
const hytaleSavesDir = getHytaleSavesDir();
|
||||
const centralModsPath = path.join(hytaleSavesDir, 'Mods');
|
||||
// profileModsPath is the real storage for this profile
|
||||
const profileModsPath = getProfileModsPath(activeProfile.id);
|
||||
const profileDisabledModsPath = path.join(path.dirname(profileModsPath), 'DisabledMods');
|
||||
@@ -306,94 +307,49 @@ async function syncModsForCurrentProfile() {
|
||||
fs.mkdirSync(profileDisabledModsPath, { recursive: true });
|
||||
}
|
||||
|
||||
// 2. Symlink / Migration Logic
|
||||
let needsLink = false;
|
||||
let globalStats = null;
|
||||
// 2. Copy-based Mod Sync (No symlinks - avoids permission issues)
|
||||
// Ensure HytaleSaves\Mods directory exists
|
||||
if (!fs.existsSync(centralModsPath)) {
|
||||
fs.mkdirSync(centralModsPath, { recursive: true });
|
||||
console.log(`[ModManager] Created centralized mods directory: ${centralModsPath}`);
|
||||
}
|
||||
|
||||
// Check for old symlink and convert to real directory if needed (one-time migration)
|
||||
try {
|
||||
globalStats = fs.lstatSync(globalModsPath);
|
||||
const centralStats = fs.lstatSync(centralModsPath);
|
||||
if (centralStats.isSymbolicLink()) {
|
||||
console.log('[ModManager] Removing old symlink, converting to copy-based system...');
|
||||
fs.unlinkSync(centralModsPath);
|
||||
fs.mkdirSync(centralModsPath, { recursive: true });
|
||||
}
|
||||
} catch (e) {
|
||||
// Path doesn't exist
|
||||
// Path doesn't exist, will be created above
|
||||
}
|
||||
|
||||
if (globalStats) {
|
||||
if (globalStats.isSymbolicLink()) {
|
||||
const linkTarget = fs.readlinkSync(globalModsPath);
|
||||
// Normalize paths for comparison
|
||||
if (path.resolve(linkTarget) !== path.resolve(profileModsPath)) {
|
||||
console.log(`[ModManager] Updating symlink from ${linkTarget} to ${profileModsPath}`);
|
||||
fs.unlinkSync(globalModsPath);
|
||||
needsLink = true;
|
||||
}
|
||||
} else if (globalStats.isDirectory()) {
|
||||
// MIGRATION: It's a real directory. Move contents to profile.
|
||||
console.log('[ModManager] Migrating global mods folder to profile folder...');
|
||||
const files = fs.readdirSync(globalModsPath);
|
||||
for (const file of files) {
|
||||
const src = path.join(globalModsPath, file);
|
||||
const dest = path.join(profileModsPath, file);
|
||||
// Only move if dest doesn't exist to avoid overwriting
|
||||
if (!fs.existsSync(dest)) {
|
||||
fs.renameSync(src, dest);
|
||||
}
|
||||
}
|
||||
// Copy enabled mods from profile to HytaleSaves\Mods (for game to use)
|
||||
console.log(`[ModManager] Copying enabled mods from ${profileModsPath} to ${centralModsPath}`);
|
||||
|
||||
// Also migrate DisabledMods if it exists globally
|
||||
const globalDisabledPath = path.join(path.dirname(globalModsPath), 'DisabledMods');
|
||||
if (fs.existsSync(globalDisabledPath) && fs.lstatSync(globalDisabledPath).isDirectory()) {
|
||||
const dFiles = fs.readdirSync(globalDisabledPath);
|
||||
for (const file of dFiles) {
|
||||
const src = path.join(globalDisabledPath, file);
|
||||
const dest = path.join(profileDisabledModsPath, file);
|
||||
if (!fs.existsSync(dest)) {
|
||||
fs.renameSync(src, dest);
|
||||
}
|
||||
}
|
||||
// We can remove global DisabledMods now, as it's not used by game
|
||||
try { fs.rmSync(globalDisabledPath, { recursive: true, force: true }); } catch(e) {}
|
||||
}
|
||||
|
||||
// Remove the directory so we can link it
|
||||
// First, clear central mods folder
|
||||
const existingCentralMods = fs.existsSync(centralModsPath) ? fs.readdirSync(centralModsPath) : [];
|
||||
for (const file of existingCentralMods) {
|
||||
const filePath = path.join(centralModsPath, file);
|
||||
try {
|
||||
let retries = 3;
|
||||
while (retries > 0) {
|
||||
try {
|
||||
fs.rmSync(globalModsPath, { recursive: true, force: true });
|
||||
break;
|
||||
} catch (err) {
|
||||
if ((err.code === 'EPERM' || err.code === 'EBUSY') && retries > 0) {
|
||||
retries--;
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
needsLink = true;
|
||||
fs.unlinkSync(filePath);
|
||||
} catch (e) {
|
||||
console.error('Failed to remove global mods dir:', e);
|
||||
// Throw error to stop.
|
||||
throw new Error('Failed to migrate mods directory. Please clear ' + globalModsPath);
|
||||
console.warn(`Failed to remove ${file} from central mods:`, e.message);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
needsLink = true;
|
||||
}
|
||||
|
||||
if (needsLink) {
|
||||
console.log(`[ModManager] Creating symlink: ${globalModsPath} -> ${profileModsPath}`);
|
||||
// Copy enabled mods to HytaleSaves\Mods
|
||||
const enabledModFiles = fs.existsSync(profileModsPath) ? fs.readdirSync(profileModsPath).filter(f => f.endsWith('.jar') || f.endsWith('.zip')) : [];
|
||||
for (const file of enabledModFiles) {
|
||||
const src = path.join(profileModsPath, file);
|
||||
const dest = path.join(centralModsPath, file);
|
||||
try {
|
||||
const symlinkType = getOS() === 'windows' ? 'junction' : 'dir';
|
||||
fs.symlinkSync(profileModsPath, globalModsPath, symlinkType);
|
||||
} catch (err) {
|
||||
// If we can't create the symlink, try creating the directory first
|
||||
console.error('[ModManager] Failed to create symlink. Falling back to direct folder mode.');
|
||||
console.error(err.message);
|
||||
|
||||
// Fallback: create a real directory so the game still works
|
||||
if (!fs.existsSync(globalModsPath)) {
|
||||
fs.mkdirSync(globalModsPath, { recursive: true });
|
||||
}
|
||||
fs.copyFileSync(src, dest);
|
||||
console.log(`[ModManager] Copied ${file} to HytaleSaves\\Mods`);
|
||||
} catch (e) {
|
||||
console.error(`Failed to copy ${file}:`, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,7 +416,7 @@ async function syncModsForCurrentProfile() {
|
||||
}
|
||||
|
||||
// 5. Enforce Enabled/Disabled State (Move files between Profile/Mods and Profile/DisabledMods)
|
||||
// Note: Since Global/Mods IS Profile/Mods (via symlink), moving out of Profile/Mods disables it for the game.
|
||||
// Note: Enabled mods are copied to HytaleSaves\Mods, disabled mods stay in Profile/DisabledMods
|
||||
|
||||
const disabledFiles = fs.existsSync(profileDisabledModsPath) ? fs.readdirSync(profileDisabledModsPath).filter(f => f.endsWith('.jar') || f.endsWith('.zip')) : [];
|
||||
const allFiles = new Set([...enabledFiles, ...disabledFiles]);
|
||||
|
||||
Reference in New Issue
Block a user