Files
AeroMc-Launcher/src/cfimport.js
T
2026-10-04 18:46:00 +00:00

209 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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 kopierNamen(sourceDir) {
return fs.readdirSync(sourceDir).filter((name) => !META_FILES.includes(name));
}
function copyInto(sourceDir, targetDir) {
fs.mkdirSync(targetDir, { recursive: true });
for (const name of kopierNamen(sourceDir)) {
fs.cpSync(path.join(sourceDir, name), path.join(targetDir, name), { recursive: true });
}
}
/*
* Was beim Übernehmen kopiert wird: [{ von, name }] – `name` ist der Zielname
* im Spielordner der neuen Instanz. Das Kopieren selbst erledigt kopieren.js
* asynchron mit Fortschritt (main.js → fuehreImportAus).
*/
function kopierQuellen(entry) {
if (!entry.gameDir || !fs.existsSync(entry.gameDir)) return [];
return kopierNamen(entry.gameDir).map((name) => ({ von: path.join(entry.gameDir, name), name }));
}
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, kopierQuellen, resolveInstancesDir };