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

232 lines
8.3 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';
const fs = require('fs');
const os = require('os');
const path = require('path');
const PROFILE_FILES = [
'launcher_profiles.json',
'launcher_profiles_microsoft_store.json',
'launcher_profiles_microsoft_store_2.json',
];
const ROOT_COPY_DIRS = new Set([
'config', 'defaultconfigs', 'kubejs', 'mods', 'resourcepacks', 'screenshots', 'shaderpacks', 'saves',
]);
const ROOT_COPY_FILE_PATTERNS = [
/^options.*\.(txt|of)$/i,
/^servers\.dat(?:_old)?$/i,
/^usercache\.json$/i,
/^tl_skin_cape\.json$/i,
/^journeymap.*\.(json|txt|cfg)$/i,
];
function readJson(file, fallback) {
try {
return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
} catch {
return fallback;
}
}
function resolveLauncherRoot(inputPath) {
const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
const defaultRoot = path.join(appData, '.minecraft');
const candidates = [];
if (inputPath && fs.existsSync(inputPath)) candidates.push(inputPath);
candidates.push(defaultRoot);
for (const candidate of candidates) {
for (const profileFile of PROFILE_FILES) {
if (fs.existsSync(path.join(candidate, profileFile))) return candidate;
}
}
return null;
}
function resolveProfileFile(rootDir) {
return PROFILE_FILES.map((name) => path.join(rootDir, name)).find((file) => fs.existsSync(file)) || 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 parseLastVersionId(lastVersionId) {
const raw = String(lastVersionId || '').trim();
if (!raw) return { version: '', loader: 'vanilla', loaderVersion: '' };
let match = raw.match(/^fabric-loader-([^-]+(?:\.[^-]+)*)-(.+)$/i);
if (match) return { version: match[2], loader: 'fabric', loaderVersion: normalizeLoaderVersion('fabric', match[1], match[2]) };
match = raw.match(/^quilt-loader-([^-]+(?:\.[^-]+)*)-(.+)$/i);
if (match) return { version: match[2], loader: 'quilt', loaderVersion: normalizeLoaderVersion('quilt', match[1], match[2]) };
match = raw.match(/^(.+)-forge-([\w.-]+)$/i);
if (match) return { version: match[1], loader: 'forge', loaderVersion: normalizeLoaderVersion('forge', match[2], match[1]) };
match = raw.match(/^forge-(.+)-([\w.-]+)$/i);
if (match) return { version: match[1], loader: 'forge', loaderVersion: normalizeLoaderVersion('forge', match[2], match[1]) };
match = raw.match(/^(.+)-neoforge-([\w.-]+)$/i);
if (match) return { version: match[1], loader: 'neoforge', loaderVersion: normalizeLoaderVersion('neoforge', match[2], match[1]) };
if (/^\d+(?:\.\d+)+(?:-[\w.]+)?$/i.test(raw)) return { version: raw, loader: 'vanilla', loaderVersion: '' };
return { version: raw, loader: 'vanilla', loaderVersion: '' };
}
function parseJavaSettings(javaDir, javaArgs) {
const args = String(javaArgs || '').trim();
const minMatch = args.match(/(?:^|\s)-Xms(\d+)([mMgG])/);
const maxMatch = args.match(/(?:^|\s)-Xmx(\d+)([mMgG])/);
const toMb = (match) => {
if (!match) return null;
const num = Number(match[1]);
if (!Number.isFinite(num)) return null;
return match[2].toLowerCase() === 'g' ? num * 1024 : num;
};
return {
path: String(javaDir || '').trim(),
minMemMb: toMb(minMatch),
maxMemMb: toMb(maxMatch),
extraArgs: args
.replace(/(?:^|\s)-Xms\d+[mMgG]/g, ' ')
.replace(/(?:^|\s)-Xmx\d+[mMgG]/g, ' ')
.replace(/\s+/g, ' ')
.trim(),
};
}
function isImportableProfile(profile) {
const type = String((profile && profile.type) || '').trim().toLowerCase();
if (type === 'latest-release' || type === 'latest-snapshot') return false;
const name = String((profile && profile.name) || '').trim();
const lastVersionId = String((profile && profile.lastVersionId) || '').trim();
return !!(name || lastVersionId);
}
function shouldCopyRootEntry(name) {
if (ROOT_COPY_DIRS.has(name)) return true;
return ROOT_COPY_FILE_PATTERNS.some((pattern) => pattern.test(name));
}
function scan(inputPath) {
const rootDir = resolveLauncherRoot(inputPath);
if (!rootDir) return { ok: false, reason: 'not-found' };
const profileFile = resolveProfileFile(rootDir);
const json = readJson(profileFile, {});
const profiles = json.profiles || {};
const list = [];
const defaultRoot = path.normalize(rootDir).toLowerCase();
for (const [id, profile] of Object.entries(profiles)) {
if (!isImportableProfile(profile)) continue;
const name = String(profile.name || '').trim() || String(profile.lastVersionId || '').trim() || id;
const parsed = parseLastVersionId(profile.lastVersionId);
const gameDir = path.normalize(String(profile.gameDir || rootDir));
const java = parseJavaSettings(profile.javaDir, profile.javaArgs);
list.push({
id,
name,
group: '',
notes: 'Importiert aus dem Minecraft Launcher',
version: parsed.version,
loader: parsed.loader,
loaderVersion: parsed.loaderVersion,
javaPath: java.path,
minMemMb: java.minMemMb,
maxMemMb: java.maxMemMb,
extraJavaArgs: java.extraArgs,
gameDir,
hasGameData: fs.existsSync(gameDir),
usesDefaultGameDir: gameDir.toLowerCase() === defaultRoot,
rootDir,
});
}
list.sort((a, b) => a.name.localeCompare(b.name));
return { ok: true, rootDir, count: list.length, instances: list };
}
function kopierNamen(sourceDir, usesDefaultGameDir) {
return fs.readdirSync(sourceDir).filter((name) => {
if (/^launcher_profiles.*\.json$/i.test(name)) return false;
if (name === 'versions' || name === 'libraries' || name === 'assets' || name === 'runtime' || name === 'webcache2') return false;
return !(usesDefaultGameDir && !shouldCopyRootEntry(name));
});
}
function copyInto(sourceDir, targetDir, usesDefaultGameDir) {
fs.mkdirSync(targetDir, { recursive: true });
for (const name of kopierNamen(sourceDir, usesDefaultGameDir)) {
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, !!entry.usesDefaultGameDir).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 || entry.extraJavaArgs) {
patch.java = {
path: entry.javaPath || '',
minMemMb: entry.minMemMb || null,
maxMemMb: entry.maxMemMb || null,
extraArgs: entry.extraJavaArgs || '',
};
}
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), !!entry.usesDefaultGameDir);
copied = true;
}
return { id: created.id, name: created.name, copied };
}
module.exports = { scan, importOne, kopierQuellen, parseLastVersionId, resolveLauncherRoot };