164 lines
5.0 KiB
JavaScript
164 lines
5.0 KiB
JavaScript
import path from 'path';
|
|
import { promises as fs } from 'fs';
|
|
import fse from 'fs-extra';
|
|
import pMap from 'p-map';
|
|
import { getDirectories, normalizeModData, isMod } from '.';
|
|
import { getFileMurmurHash2 } from '../../../common/utils';
|
|
import { getAddonsByFingerprint, getAddon } from '../../../common/api';
|
|
|
|
const modsFingerprintsScan = async instancesPath => {
|
|
const mapFolderToInstance = async instance => {
|
|
try {
|
|
const configPath = path.join(
|
|
path.join(instancesPath, instance, 'config.json')
|
|
);
|
|
const config = await fse.readJSON(configPath);
|
|
|
|
if (!config.loader) {
|
|
throw new Error(`Config for ${instance} could not be parsed`);
|
|
}
|
|
|
|
const modsFolder = path.join(instancesPath, instance, 'mods');
|
|
const modsFolderExists = await fse.pathExists(modsFolder);
|
|
|
|
if (!modsFolderExists) return { ...config, name: instance };
|
|
|
|
// Check if config.mods has a different number of mods than the actual number of mods
|
|
|
|
// Count the actual mods inside the folder
|
|
const files = await fs.readdir(modsFolder);
|
|
|
|
const fileNamesToRemove = [];
|
|
const missingMods = {};
|
|
/* eslint-disable */
|
|
// Check for new mods in local storage that are not present in config
|
|
for (const file of files) {
|
|
try {
|
|
const completeFilePath = path.join(modsFolder, file);
|
|
const stat = await fs.lstat(completeFilePath);
|
|
if (stat.isFile() && isMod(completeFilePath, instancesPath)) {
|
|
// Check if file is in config
|
|
if (!(config?.mods || []).find(mod => mod.fileName === file)) {
|
|
const murmurHash = await getFileMurmurHash2(completeFilePath);
|
|
console.log(
|
|
'[MODS SCANNER] Local mod not found in config',
|
|
file,
|
|
murmurHash
|
|
);
|
|
missingMods[file] = murmurHash;
|
|
}
|
|
}
|
|
} catch {}
|
|
}
|
|
|
|
// Check for old mods in config that are not present on local storage
|
|
for (const configMod of config?.mods || []) {
|
|
if (!files.includes(configMod.fileName)) {
|
|
fileNamesToRemove.push(configMod.fileName);
|
|
console.log(
|
|
`[MODS SCANNER] Removing ${configMod.fileName} from config`
|
|
);
|
|
}
|
|
}
|
|
/* eslint-enable */
|
|
|
|
let newMods = config?.mods || [];
|
|
|
|
if (Object.values(missingMods).length !== 0) {
|
|
let success = false;
|
|
let tries = 10;
|
|
|
|
while (!success || tries > 10) {
|
|
try {
|
|
const data = await getAddonsByFingerprint(
|
|
Object.values(missingMods)
|
|
);
|
|
|
|
const matches = await Promise.all(
|
|
Object.entries(missingMods).map(async ([fileName, hash]) => {
|
|
const exactMatch = (data.exactMatches || []).find(
|
|
v => v.file.fileFingerprint === hash
|
|
);
|
|
if (exactMatch?.file) {
|
|
let addonData = null;
|
|
try {
|
|
addonData = await getAddon(exactMatch.file.modId);
|
|
return {
|
|
...normalizeModData(
|
|
exactMatch.file,
|
|
exactMatch.file.modId,
|
|
addonData.name
|
|
),
|
|
fileName
|
|
};
|
|
} catch {
|
|
return {
|
|
fileName,
|
|
displayName: fileName,
|
|
packageFingerprint: hash
|
|
};
|
|
}
|
|
}
|
|
return {
|
|
fileName,
|
|
displayName: fileName,
|
|
packageFingerprint: hash
|
|
};
|
|
})
|
|
);
|
|
|
|
newMods = [...newMods, ...matches];
|
|
success = true;
|
|
} catch (err) {
|
|
console.error(err);
|
|
tries += 1;
|
|
await new Promise(resolve => {
|
|
setTimeout(() => {
|
|
resolve();
|
|
}, 5000);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
const filterMods = newMods
|
|
.filter(_ => _)
|
|
.filter(v => !fileNamesToRemove.includes(v.fileName));
|
|
|
|
const newConfig = {
|
|
...config,
|
|
mods: filterMods
|
|
};
|
|
|
|
if (JSON.stringify(config) !== JSON.stringify(newConfig)) {
|
|
await fse.outputJson(configPath, newConfig);
|
|
return { ...newConfig, name: instance };
|
|
}
|
|
return { ...config, name: instance };
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const folders = await getDirectories(instancesPath);
|
|
const instances = await pMap(
|
|
folders.filter(folder => folder !== '.DS_Store'),
|
|
mapFolderToInstance,
|
|
{
|
|
concurrency: 5
|
|
}
|
|
);
|
|
const hashMap = {};
|
|
// eslint-disable-next-line
|
|
for (const instance of instances) {
|
|
// eslint-disable-next-line
|
|
if (!instance) continue;
|
|
hashMap[instance.name] = instance;
|
|
}
|
|
|
|
return hashMap;
|
|
};
|
|
|
|
export default modsFingerprintsScan;
|