'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 };