Upload via GUI (25 Dateien)
This commit is contained in:
+196
@@ -0,0 +1,196 @@
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* cfimport.js – Instanzen aus dem CurseForge-Launcher übernehmen
|
||||
* ---------------------------------------------------------------
|
||||
* Unterstützt sowohl den Launcher-Wurzelordner als auch direkt den
|
||||
* Instances-Ordner. Gelesen werden nach Möglichkeit minecraftinstance.json
|
||||
* und die vorhandenen Spieldaten im Profilordner.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
const META_FILES = ['minecraftinstance.json', 'instance.json'];
|
||||
|
||||
function readJson(file, fallback) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function existingMetaFile(dir) {
|
||||
return META_FILES.map((name) => path.join(dir, name)).find((file) => fs.existsSync(file)) || null;
|
||||
}
|
||||
|
||||
function metaVersion(meta) {
|
||||
return String(
|
||||
(meta && (
|
||||
meta.gameVersion || meta.minecraftVersion || meta.mcVersion ||
|
||||
(meta.installedModpack && meta.installedModpack.gameVersion)
|
||||
)) || ''
|
||||
).trim();
|
||||
}
|
||||
|
||||
function resolveInstancesDir(inputPath) {
|
||||
if (!inputPath || !fs.existsSync(inputPath)) return null;
|
||||
const directNames = new Set(['instances', 'minecraftinstances']);
|
||||
if (directNames.has(path.basename(inputPath).toLowerCase())) return inputPath;
|
||||
|
||||
const candidates = [
|
||||
path.join(inputPath, 'Instances'),
|
||||
path.join(inputPath, 'instances'),
|
||||
path.join(inputPath, 'minecraftInstances'),
|
||||
path.join(inputPath, 'Minecraft', 'Instances'),
|
||||
path.join(inputPath, 'minecraft', 'Instances'),
|
||||
];
|
||||
|
||||
const looksLikeWindowsInstall =
|
||||
/curseforge windows/i.test(inputPath) ||
|
||||
(fs.existsSync(path.join(inputPath, 'CurseForge.exe')) && fs.existsSync(path.join(inputPath, 'resources')));
|
||||
if (looksLikeWindowsInstall) {
|
||||
candidates.push(
|
||||
path.join(os.homedir(), 'curseforge', 'minecraft', 'Instances'),
|
||||
path.join(os.homedir(), 'CurseForge', 'minecraft', 'Instances'),
|
||||
);
|
||||
}
|
||||
return candidates.find((candidate) => fs.existsSync(candidate)) || null;
|
||||
}
|
||||
|
||||
function normalizeLoaderVersion(loader, rawValue, mcVersion) {
|
||||
let value = String(rawValue || '').trim();
|
||||
if (!value) return '';
|
||||
|
||||
if (loader === 'forge') {
|
||||
value = value.replace(/^forge-/i, '');
|
||||
if (mcVersion && value.startsWith(mcVersion + '-')) value = value.slice(mcVersion.length + 1);
|
||||
return value;
|
||||
}
|
||||
if (loader === 'neoforge') {
|
||||
return value.replace(/^neoforge-/i, '');
|
||||
}
|
||||
if (loader === 'fabric') {
|
||||
value = value.replace(/^fabric(?:-loader)?-/i, '');
|
||||
if (mcVersion && value.endsWith('-' + mcVersion)) value = value.slice(0, -1 * (mcVersion.length + 1));
|
||||
return value;
|
||||
}
|
||||
if (loader === 'quilt') {
|
||||
value = value.replace(/^quilt(?:-loader)?-/i, '');
|
||||
if (mcVersion && value.endsWith('-' + mcVersion)) value = value.slice(0, -1 * (mcVersion.length + 1));
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function detectLoader(meta, mcVersion) {
|
||||
const raw = meta && (
|
||||
meta.baseModLoader || meta.modLoader || meta.modloader || meta.loader || meta.modLoaderId
|
||||
);
|
||||
const value = typeof raw === 'object'
|
||||
? (raw.name || raw.id || raw.value || raw.version || '')
|
||||
: (raw || '');
|
||||
const lower = String(value).toLowerCase();
|
||||
const objectVersion = raw && typeof raw === 'object'
|
||||
? (raw.version || raw.name || raw.id || '')
|
||||
: '';
|
||||
|
||||
if (!lower) return { loader: 'vanilla', loaderVersion: '' };
|
||||
if (lower.includes('neoforge')) return { loader: 'neoforge', loaderVersion: normalizeLoaderVersion('neoforge', objectVersion || value, mcVersion) };
|
||||
if (lower.includes('fabric')) return { loader: 'fabric', loaderVersion: normalizeLoaderVersion('fabric', objectVersion || value, mcVersion) };
|
||||
if (lower.includes('quilt')) return { loader: 'quilt', loaderVersion: normalizeLoaderVersion('quilt', objectVersion || value, mcVersion) };
|
||||
if (lower.includes('forge')) return { loader: 'forge', loaderVersion: normalizeLoaderVersion('forge', objectVersion || value, mcVersion) };
|
||||
return { loader: 'vanilla', loaderVersion: '' };
|
||||
}
|
||||
|
||||
function detectVersion(meta, loaderVersion) {
|
||||
return metaVersion(meta) || (loaderVersion && loaderVersion.includes('-') ? loaderVersion.split('-')[0] : '');
|
||||
}
|
||||
|
||||
function findGameDir(dir) {
|
||||
const nested = ['minecraft', '.minecraft']
|
||||
.map((name) => path.join(dir, name))
|
||||
.find((candidate) => fs.existsSync(candidate));
|
||||
if (nested) return nested;
|
||||
|
||||
const markers = ['mods', 'config', 'resourcepacks', 'shaderpacks', 'saves', 'options.txt'];
|
||||
return markers.some((name) => fs.existsSync(path.join(dir, name))) ? dir : null;
|
||||
}
|
||||
|
||||
function scan(inputPath) {
|
||||
const instancesDir = resolveInstancesDir(inputPath);
|
||||
if (!instancesDir) return { ok: false, reason: 'not-found' };
|
||||
|
||||
const list = [];
|
||||
for (const entry of fs.readdirSync(instancesDir, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const dir = path.join(instancesDir, entry.name);
|
||||
const metaFile = existingMetaFile(dir);
|
||||
const meta = metaFile ? readJson(metaFile, {}) : {};
|
||||
const gameDir = findGameDir(dir);
|
||||
if (!metaFile && !gameDir) continue;
|
||||
|
||||
const versionHint = metaVersion(meta);
|
||||
const loaderInfo = detectLoader(meta, versionHint);
|
||||
list.push({
|
||||
folder: entry.name,
|
||||
dir,
|
||||
name: meta.name || meta.displayName || entry.name,
|
||||
group: '',
|
||||
notes: meta.notes || meta.summary || '',
|
||||
version: versionHint || detectVersion(meta, loaderInfo.loaderVersion),
|
||||
loader: loaderInfo.loader,
|
||||
loaderVersion: loaderInfo.loaderVersion,
|
||||
javaPath: String(meta.javaPath || meta.javaExecutable || '').trim(),
|
||||
minMemMb: Number(meta.minimumMemory || meta.minMemory || meta.minMemAlloc) || null,
|
||||
maxMemMb: Number(meta.maximumMemory || meta.maxMemory || meta.maxMemAlloc || meta.allocatedMemory) || null,
|
||||
gameDir: gameDir || dir,
|
||||
hasGameData: !!gameDir,
|
||||
});
|
||||
}
|
||||
list.sort((a, b) => a.name.localeCompare(b.name));
|
||||
return { ok: true, instancesDir, count: list.length, instances: list };
|
||||
}
|
||||
|
||||
function copyInto(sourceDir, targetDir) {
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
for (const name of fs.readdirSync(sourceDir)) {
|
||||
if (META_FILES.includes(name)) continue;
|
||||
fs.cpSync(path.join(sourceDir, name), path.join(targetDir, name), { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
function importOne(store, entry, copyData = true) {
|
||||
const created = store.createOrReplaceImportedInstance({
|
||||
name: entry.name,
|
||||
group: entry.group,
|
||||
notes: entry.notes,
|
||||
minecraft: {
|
||||
version: entry.version,
|
||||
loader: entry.loader,
|
||||
loaderVersion: entry.loaderVersion,
|
||||
},
|
||||
});
|
||||
|
||||
const patch = {};
|
||||
if (entry.javaPath || entry.minMemMb || entry.maxMemMb) {
|
||||
patch.java = {
|
||||
path: entry.javaPath || '',
|
||||
minMemMb: entry.minMemMb || null,
|
||||
maxMemMb: entry.maxMemMb || null,
|
||||
extraArgs: '',
|
||||
};
|
||||
}
|
||||
if (Object.keys(patch).length) store.updateInstance(created.id, patch);
|
||||
|
||||
let copied = false;
|
||||
if (copyData && entry.gameDir && fs.existsSync(entry.gameDir)) {
|
||||
copyInto(entry.gameDir, store.gameDir(created.id));
|
||||
copied = true;
|
||||
}
|
||||
return { id: created.id, name: created.name, copied };
|
||||
}
|
||||
|
||||
module.exports = { scan, importOne, resolveInstancesDir };
|
||||
Reference in New Issue
Block a user