Skip to content

Commit 9647b94

Browse files
committed
Path case normalization rules now properly apply for nested directories.
1 parent 28302d8 commit 9647b94

File tree

1 file changed

+33
-22
lines changed

1 file changed

+33
-22
lines changed

src/electron.js

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3362,9 +3362,15 @@ class ElectronLoader {
33623362
const gameDetails = gameDb[profile.gameId];
33633363
const gamePluginFormats = gameDetails?.pluginFormats ?? [];
33643364

3365-
const existingDataSubdirs = (await fs.readdir(gameModDir)).filter((existingModFile) => {
3366-
return fs.lstatSync(path.join(gameModDir, existingModFile)).isDirectory();
3367-
});
3365+
// Build Map of all existing data subdirs for path normalization
3366+
/** @type {Map<string, string>} */ const existingDataSubdirs = new Map();
3367+
if (normalizePathCasing) {
3368+
(await fs.readdir(gameModDir, { recursive: true })).forEach((existingModFile) => {
3369+
if (fs.lstatSync(path.join(gameModDir, /** @type {string} */ (existingModFile))).isDirectory()) {
3370+
existingDataSubdirs.set(/** @type {string} */ (existingModFile).toLowerCase(), existingModFile);
3371+
}
3372+
});
3373+
}
33683374

33693375
// Copy all mods to the gameModDir for this profile
33703376
// (Copy mods in reverse with `overwrite: false` to follow load order and allow existing manual mods in the folder to be preserved)
@@ -3390,21 +3396,16 @@ class ElectronLoader {
33903396
modFile = modFile.toLowerCase();
33913397
}
33923398

3393-
if (root) {
3394-
// Preserve capitalization of Data directory for root mods
3395-
modFile = modFile.replace(/^data[\\/]/, `Data${path.sep}`);
3396-
} else {
3397-
// Apply capitalization rules of any existing Data subdirectories to ensure only one folder is created
3398-
// TODO - Also do this for root mods
3399-
// TODO - Do this recursively
3400-
existingDataSubdirs.forEach((existingDataSubdir) => {
3401-
existingDataSubdir = `${existingDataSubdir}${path.sep}`;
3402-
const lowerSubdir = existingDataSubdir.toLowerCase();
3403-
3404-
if (modFile.startsWith(lowerSubdir)) {
3405-
modFile = modFile.replace(lowerSubdir, existingDataSubdir);
3406-
}
3407-
});
3399+
// Apply existing capitalization rules of mod data subdirectories to ensure only one folder is created
3400+
let modFileBase = path.dirname(modFile);
3401+
while (modFileBase !== "." && modFileBase !== path.sep) {
3402+
const existingBase = existingDataSubdirs.get(modFileBase.toLowerCase());
3403+
if (existingBase) {
3404+
modFile = modFile.replace(modFileBase, existingBase);
3405+
break;
3406+
} else {
3407+
modFileBase = path.dirname(modFileBase);
3408+
}
34083409
}
34093410
}
34103411

@@ -3612,12 +3613,22 @@ class ElectronLoader {
36123613

36133614
// Some games require processing of plugin file timestamps to enforce load order
36143615
if (!!gameDetails?.pluginListType && profile.plugins && timestampedPluginTypes.includes(gameDetails.pluginListType)) {
3615-
const gameModDir = path.resolve(this.#expandPath(profile.gameInstallation.modDir));
3616+
let gamePluginDir = this.#expandPath(profile.gameInstallation.modDir);
3617+
3618+
if (gameDetails.pluginDataRoot) {
3619+
gamePluginDir = path.join(gamePluginDir, gameDetails.pluginDataRoot);
3620+
}
3621+
36163622
let pluginTimestamp = Date.now() / 1000 | 0;
36173623
profile.plugins.forEach((pluginRef) => {
3618-
// Set plugin order using the plugin file's "last modified" timestamp
3619-
fs.utimesSync(path.join(gameModDir, pluginRef.plugin), pluginTimestamp, pluginTimestamp);
3620-
++pluginTimestamp;
3624+
const pluginPath = path.join(gamePluginDir, pluginRef.plugin);
3625+
if (fs.existsSync(pluginPath)) {
3626+
// Set plugin order using the plugin file's "last modified" timestamp
3627+
fs.utimesSync(pluginPath, pluginTimestamp, pluginTimestamp);
3628+
++pluginTimestamp;
3629+
} else {
3630+
log.warn("Missing plugin file", pluginPath);
3631+
}
36213632
});
36223633
}
36233634

0 commit comments

Comments
 (0)