Upload via GUI (25 Dateien)
This commit is contained in:
+217
@@ -0,0 +1,217 @@
|
||||
'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 copyInto(sourceDir, targetDir, usesDefaultGameDir) {
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
for (const name of fs.readdirSync(sourceDir)) {
|
||||
if (/^launcher_profiles.*\.json$/i.test(name)) continue;
|
||||
if (name === 'versions' || name === 'libraries' || name === 'assets' || name === 'runtime' || name === 'webcache2') continue;
|
||||
if (usesDefaultGameDir && !shouldCopyRootEntry(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 || 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, parseLastVersionId, resolveLauncherRoot };
|
||||
Reference in New Issue
Block a user