mirror of
https://git.sanhost.net/sanasol/hytale-f2p.git
synced 2026-02-26 06:41:47 -03:00
Profile System & Mod Loading Fixes
Added a full profile system and fixed a few critical mod loading issues. What changed Profiles — Implemented proper profile management (create, switch, delete). Each profile now has its own isolated mod list. Mod Isolation — Fixed ModManager so mods are strictly scoped to the active profile. Browsing and installing only affects the selected profile. Critical Fix — Fixed a path bug where mods were being saved to ~/AppData/Local on macOS (Windows path) instead of ~/Library/Application Support. Mods now save to the correct location and load correctly in-game. Stability — Added an auto-sync step before every launch to make sure the physical mods folder always matches the active profile. UI — Added a profile selector dropdown and a profile management modal.
This commit is contained in:
@@ -14,6 +14,10 @@ function getAuthDomain() {
|
||||
}
|
||||
// Then check config file
|
||||
const config = loadConfig();
|
||||
if (config.activeProfileId && config.profiles && config.profiles[config.activeProfileId]) {
|
||||
// Allow profile to override auth domain if ever needed
|
||||
// but for now stick to global or env
|
||||
}
|
||||
if (config.authDomain) {
|
||||
return config.authDomain;
|
||||
}
|
||||
@@ -111,6 +115,16 @@ function saveJavaPath(javaPath) {
|
||||
|
||||
function loadJavaPath() {
|
||||
const config = loadConfig();
|
||||
|
||||
// Prefer Active Profile's Java Path
|
||||
if (config.activeProfileId && config.profiles && config.profiles[config.activeProfileId]) {
|
||||
const profile = config.profiles[config.activeProfileId];
|
||||
if (profile.javaPath && profile.javaPath.trim().length > 0) {
|
||||
return profile.javaPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to global setting
|
||||
return config.javaPath || '';
|
||||
}
|
||||
|
||||
@@ -135,14 +149,29 @@ function loadDiscordRPC() {
|
||||
|
||||
function saveModsToConfig(mods) {
|
||||
try {
|
||||
let config = loadConfig();
|
||||
config.installedMods = mods;
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
// Config migration to profiles handles the structure,
|
||||
// but here we ensure that if we are saving mods, we save them to the ACTIVE profile
|
||||
// OR we save to the global pool if that's the design.
|
||||
// Based on the plan: Profile has "Enabled Mods".
|
||||
// This function seems to be used to save the list of *Installed* mods.
|
||||
|
||||
// We'll update the global config for "installedMods" mostly for reference/migration,
|
||||
// but primarily we should be updating the active profile's mod list.
|
||||
|
||||
if (config.activeProfileId && config.profiles && config.profiles[config.activeProfileId]) {
|
||||
config.profiles[config.activeProfileId].mods = mods;
|
||||
} else {
|
||||
// Fallback for legacy or no-profile state
|
||||
config.installedMods = mods;
|
||||
}
|
||||
|
||||
const configDir = path.dirname(CONFIG_FILE);
|
||||
if (!fs.existsSync(configDir)) {
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
}
|
||||
|
||||
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
||||
console.log('Mods saved to config.json');
|
||||
} catch (error) {
|
||||
@@ -153,6 +182,12 @@ function saveModsToConfig(mods) {
|
||||
function loadModsFromConfig() {
|
||||
try {
|
||||
const config = loadConfig();
|
||||
|
||||
// Prefer Active Profile
|
||||
if (config.activeProfileId && config.profiles && config.profiles[config.activeProfileId]) {
|
||||
return config.profiles[config.activeProfileId].mods || [];
|
||||
}
|
||||
|
||||
return config.installedMods || [];
|
||||
} catch (error) {
|
||||
console.error('Error loading mods from config:', error);
|
||||
@@ -162,19 +197,19 @@ function loadModsFromConfig() {
|
||||
|
||||
function isFirstLaunch() {
|
||||
const config = loadConfig();
|
||||
|
||||
|
||||
if ('hasLaunchedBefore' in config) {
|
||||
return !config.hasLaunchedBefore;
|
||||
}
|
||||
|
||||
const hasUserData = config.installPath || config.username || config.javaPath ||
|
||||
config.chatUsername || config.userUuids ||
|
||||
Object.keys(config).length > 0;
|
||||
|
||||
|
||||
const hasUserData = config.installPath || config.username || config.javaPath ||
|
||||
config.chatUsername || config.userUuids ||
|
||||
Object.keys(config).length > 0;
|
||||
|
||||
if (!hasUserData) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -195,17 +230,17 @@ function getAllUuidMappings() {
|
||||
|
||||
function setUuidForUser(username, uuid) {
|
||||
const { v4: uuidv4, validate: validateUuid } = require('uuid');
|
||||
|
||||
|
||||
// Validate UUID format
|
||||
if (!validateUuid(uuid)) {
|
||||
throw new Error('Invalid UUID format');
|
||||
}
|
||||
|
||||
|
||||
const config = loadConfig();
|
||||
const userUuids = config.userUuids || {};
|
||||
userUuids[username] = uuid;
|
||||
saveConfig({ userUuids });
|
||||
|
||||
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@@ -217,13 +252,13 @@ function generateNewUuid() {
|
||||
function deleteUuidForUser(username) {
|
||||
const config = loadConfig();
|
||||
const userUuids = config.userUuids || {};
|
||||
|
||||
|
||||
if (userUuids[username]) {
|
||||
delete userUuids[username];
|
||||
saveConfig({ userUuids });
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -231,7 +266,7 @@ function resetCurrentUserUuid() {
|
||||
const username = loadUsername();
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const newUuid = uuidv4();
|
||||
|
||||
|
||||
return setUuidForUser(username, newUuid);
|
||||
}
|
||||
|
||||
|
||||
@@ -77,32 +77,32 @@ function findClientPath(gameLatest) {
|
||||
|
||||
function findUserDataPath(gameLatest) {
|
||||
const candidates = [];
|
||||
|
||||
|
||||
candidates.push(path.join(gameLatest, 'Client', 'UserData'));
|
||||
|
||||
|
||||
candidates.push(path.join(gameLatest, 'Client', 'Hytale.app', 'Contents', 'UserData'));
|
||||
candidates.push(path.join(gameLatest, 'Hytale.app', 'Contents', 'UserData'));
|
||||
candidates.push(path.join(gameLatest, 'UserData'));
|
||||
|
||||
|
||||
candidates.push(path.join(gameLatest, 'Client', 'UserData'));
|
||||
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let defaultPath;
|
||||
if (process.platform === 'darwin') {
|
||||
defaultPath = path.join(gameLatest, 'Client', 'UserData');
|
||||
} else {
|
||||
defaultPath = path.join(gameLatest, 'Client', 'UserData');
|
||||
}
|
||||
|
||||
|
||||
if (!fs.existsSync(defaultPath)) {
|
||||
fs.mkdirSync(defaultPath, { recursive: true });
|
||||
}
|
||||
|
||||
|
||||
return defaultPath;
|
||||
}
|
||||
|
||||
@@ -110,15 +110,15 @@ function findUserDataRecursive(gameLatest) {
|
||||
function searchDirectory(dir) {
|
||||
try {
|
||||
const items = fs.readdirSync(dir, { withFileTypes: true });
|
||||
|
||||
|
||||
for (const item of items) {
|
||||
if (item.isDirectory()) {
|
||||
const fullPath = path.join(dir, item.name);
|
||||
|
||||
|
||||
if (item.name === 'UserData') {
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
|
||||
const found = searchDirectory(fullPath);
|
||||
if (found) {
|
||||
return found;
|
||||
@@ -127,14 +127,14 @@ function findUserDataRecursive(gameLatest) {
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if (!fs.existsSync(gameLatest)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
const found = searchDirectory(gameLatest);
|
||||
return found;
|
||||
}
|
||||
@@ -152,16 +152,14 @@ async function getModsPath(customInstallPath = null) {
|
||||
}
|
||||
|
||||
if (!installPath) {
|
||||
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
||||
installPath = path.join(localAppData, 'HytaleF2P');
|
||||
} else {
|
||||
installPath = path.join(installPath, 'HytaleF2P');
|
||||
// Use the standard app directory logic which handles platforms correctly
|
||||
installPath = getAppDir();
|
||||
}
|
||||
|
||||
const gameLatest = path.join(installPath, 'release', 'package', 'game', 'latest');
|
||||
|
||||
|
||||
const userDataPath = findUserDataPath(gameLatest);
|
||||
|
||||
|
||||
const modsPath = path.join(userDataPath, 'Mods');
|
||||
const disabledModsPath = path.join(userDataPath, 'DisabledMods');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user