mirror of
https://git.sanhost.net/sanasol/hytale-f2p.git
synced 2026-02-26 06:41:47 -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>
|
<label class="form-label" data-i18n="install.playerName">Player Name</label>
|
||||||
<input type="text" id="installPlayerName"
|
<input type="text" id="installPlayerName"
|
||||||
data-i18n-placeholder="install.playerNamePlaceholder" class="form-input"
|
data-i18n-placeholder="install.playerNamePlaceholder" class="form-input"
|
||||||
value="Player" />
|
value="Player" maxlength="16" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -45,9 +45,17 @@ export function setupInstallation() {
|
|||||||
export async function installGame() {
|
export async function installGame() {
|
||||||
if (isDownloading || (installBtn && installBtn.disabled)) return;
|
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() : '';
|
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 selectedBranchRadio = document.querySelector('input[name="installBranch"]:checked');
|
||||||
const selectedBranch = selectedBranchRadio ? selectedBranchRadio.value : 'release';
|
const selectedBranch = selectedBranchRadio ? selectedBranchRadio.value : 'release';
|
||||||
|
|
||||||
@@ -194,7 +202,16 @@ export async function browseInstallPath() {
|
|||||||
async function savePlayerName() {
|
async function savePlayerName() {
|
||||||
try {
|
try {
|
||||||
if (window.electronAPI && window.electronAPI.saveSettings) {
|
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 });
|
await window.electronAPI.saveSettings({ playerName });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -439,6 +439,13 @@ async function savePlayerName() {
|
|||||||
return;
|
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);
|
await window.electronAPI.saveUsername(playerName);
|
||||||
const successMsg = window.i18n ? window.i18n.t('notifications.playerNameSaved') : 'Player name saved successfully';
|
const successMsg = window.i18n ? window.i18n.t('notifications.playerNameSaved') : 'Player name saved successfully';
|
||||||
showNotification(successMsg, 'success');
|
showNotification(successMsg, 'success');
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const path = require('path');
|
|||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const { getOS } = require('../utils/platformUtils');
|
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 { saveModsToConfig, loadModsFromConfig } = require('../core/config');
|
||||||
const profileManager = require('./profileManager');
|
const profileManager = require('./profileManager');
|
||||||
|
|
||||||
@@ -296,8 +296,9 @@ async function syncModsForCurrentProfile() {
|
|||||||
console.log(`[ModManager] Syncing mods for profile: ${activeProfile.name} (${activeProfile.id})`);
|
console.log(`[ModManager] Syncing mods for profile: ${activeProfile.name} (${activeProfile.id})`);
|
||||||
|
|
||||||
// 1. Resolve Paths
|
// 1. Resolve Paths
|
||||||
// globalModsPath is the one the game uses (symlink target)
|
// centralModsPath is HytaleSaves\Mods (centralized location for active mods)
|
||||||
const globalModsPath = await getModsPath();
|
const hytaleSavesDir = getHytaleSavesDir();
|
||||||
|
const centralModsPath = path.join(hytaleSavesDir, 'Mods');
|
||||||
// profileModsPath is the real storage for this profile
|
// profileModsPath is the real storage for this profile
|
||||||
const profileModsPath = getProfileModsPath(activeProfile.id);
|
const profileModsPath = getProfileModsPath(activeProfile.id);
|
||||||
const profileDisabledModsPath = path.join(path.dirname(profileModsPath), 'DisabledMods');
|
const profileDisabledModsPath = path.join(path.dirname(profileModsPath), 'DisabledMods');
|
||||||
@@ -306,96 +307,51 @@ async function syncModsForCurrentProfile() {
|
|||||||
fs.mkdirSync(profileDisabledModsPath, { recursive: true });
|
fs.mkdirSync(profileDisabledModsPath, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Symlink / Migration Logic
|
// 2. Copy-based Mod Sync (No symlinks - avoids permission issues)
|
||||||
let needsLink = false;
|
// Ensure HytaleSaves\Mods directory exists
|
||||||
let globalStats = null;
|
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 {
|
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) {
|
} catch (e) {
|
||||||
// Path doesn't exist
|
// Path doesn't exist, will be created above
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalStats) {
|
// Copy enabled mods from profile to HytaleSaves\Mods (for game to use)
|
||||||
if (globalStats.isSymbolicLink()) {
|
console.log(`[ModManager] Copying enabled mods from ${profileModsPath} to ${centralModsPath}`);
|
||||||
const linkTarget = fs.readlinkSync(globalModsPath);
|
|
||||||
// Normalize paths for comparison
|
// First, clear central mods folder
|
||||||
if (path.resolve(linkTarget) !== path.resolve(profileModsPath)) {
|
const existingCentralMods = fs.existsSync(centralModsPath) ? fs.readdirSync(centralModsPath) : [];
|
||||||
console.log(`[ModManager] Updating symlink from ${linkTarget} to ${profileModsPath}`);
|
for (const file of existingCentralMods) {
|
||||||
fs.unlinkSync(globalModsPath);
|
const filePath = path.join(centralModsPath, file);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
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;
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
needsLink = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsLink) {
|
|
||||||
console.log(`[ModManager] Creating symlink: ${globalModsPath} -> ${profileModsPath}`);
|
|
||||||
try {
|
try {
|
||||||
const symlinkType = getOS() === 'windows' ? 'junction' : 'dir';
|
fs.unlinkSync(filePath);
|
||||||
fs.symlinkSync(profileModsPath, globalModsPath, symlinkType);
|
} catch (e) {
|
||||||
} catch (err) {
|
console.warn(`Failed to remove ${file} from central mods:`, e.message);
|
||||||
// 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);
|
|
||||||
|
// Copy enabled mods to HytaleSaves\Mods
|
||||||
// Fallback: create a real directory so the game still works
|
const enabledModFiles = fs.existsSync(profileModsPath) ? fs.readdirSync(profileModsPath).filter(f => f.endsWith('.jar') || f.endsWith('.zip')) : [];
|
||||||
if (!fs.existsSync(globalModsPath)) {
|
for (const file of enabledModFiles) {
|
||||||
fs.mkdirSync(globalModsPath, { recursive: true });
|
const src = path.join(profileModsPath, file);
|
||||||
|
const dest = path.join(centralModsPath, file);
|
||||||
|
try {
|
||||||
|
fs.copyFileSync(src, dest);
|
||||||
|
console.log(`[ModManager] Copied ${file} to HytaleSaves\\Mods`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to copy ${file}:`, e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Auto-Repair (Download missing mods)
|
// 3. Auto-Repair (Download missing mods)
|
||||||
const profileModsSnapshot = activeProfile.mods || [];
|
const profileModsSnapshot = activeProfile.mods || [];
|
||||||
@@ -460,7 +416,7 @@ async function syncModsForCurrentProfile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 5. Enforce Enabled/Disabled State (Move files between Profile/Mods and Profile/DisabledMods)
|
// 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 disabledFiles = fs.existsSync(profileDisabledModsPath) ? fs.readdirSync(profileDisabledModsPath).filter(f => f.endsWith('.jar') || f.endsWith('.zip')) : [];
|
||||||
const allFiles = new Set([...enabledFiles, ...disabledFiles]);
|
const allFiles = new Set([...enabledFiles, ...disabledFiles]);
|
||||||
|
|||||||
Reference in New Issue
Block a user