Files
AeroMc-Launcher/src/technicimport.js
T

173 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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';
/*
* technicimport.js Instanzen aus dem Technic Launcher übernehmen
* -------------------------------------------------------------------
* Der Technic Launcher legt jedes installierte Modpack als eigenen Ordner
* unter "<Technic-Wurzel>/modpacks/<name>" an (Standard unter Windows:
* %APPDATA%\.technic\modpacks). Darin liegen "bin" (Launcher-interner
* Jar-/Versions-Cache), "config" und "mods" als Geschwisterordner Welten,
* Ressourcenpakete usw. landen ebenfalls direkt im Pack-Ordner, genau wie
* bei einem klassischen .minecraft.
*
* Es gibt keine für uns nutzbare, stabil dokumentierte JSON-Datei mit einem
* schönen Anzeigenamen "installedPacks" im Wurzelordner listet Packs nur
* über ihren Ordnernamen (Slug). Wir lesen sie deshalb nur als optionale,
* fehlertolerante Ergänzung; die eigentliche Erkennung läuft wie bei den
* anderen Importern über die tatsächlich vorhandenen Ordner/Dateien.
*/
const fs = require('fs');
const path = require('path');
const forge = require('./forge');
// Ordner, die zum Technic-internen Verwaltungs-/Jar-Cache gehören und beim
// Übernehmen NICHT mit ins Spielverzeichnis der neuen Instanz sollen.
const SKIP_DIRS = new Set(['bin']);
function technicRoot() {
return path.join(process.env.APPDATA || '', '.technic');
}
function readJson(file) {
try { return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^/, '')); }
catch { return null; }
}
// findet den modpacks-Ordner, egal ob Technic-Wurzel, modpacks-Ordner direkt
// oder ein einzelner Pack-Ordner angegeben wurde
function resolveModpacksDir(inputPath) {
const root = inputPath && inputPath.trim() ? inputPath.trim() : technicRoot();
if (!fs.existsSync(root)) return null;
const direct = path.join(root, 'modpacks');
if (fs.existsSync(direct)) return direct;
if (path.basename(root).toLowerCase() === 'modpacks') return root;
return root; // z. B. ein selbst gewählter Ordner mit Pack-Unterordnern
}
// ein Pack-Ordner hat i. d. R. bin/, config/ und/oder mods/ als Geschwister
function looksLikePack(dir) {
const markers = ['bin', 'mods', 'config', 'saves', 'resourcepacks', 'options.txt'];
return markers.some((name) => fs.existsSync(path.join(dir, name)));
}
// optionale Anreicherung aus der "installedPacks"-Datei der Technic-Wurzel;
// bewusst rein additiv und fehlertolerant, da das Format nicht offiziell
// als stabile API dokumentiert ist
function readInstalledPacksMeta(root) {
const data = readJson(path.join(root, 'installedPacks'));
const packs = data && data.installedPacks;
return packs && typeof packs === 'object' ? packs : {};
}
// versucht, aus bin/modpack.jar (version.json) oder der alten bin/version
// -Textdatei Minecraft-Version und Mod-Loader zu bestimmen. Rein
// bestmöglich schlägt die Erkennung fehl, bleibt die Instanz einfach
// ohne Version/Loader stehen und lässt sich manuell nachtragen.
function detectVersion(packDir) {
const bin = path.join(packDir, 'bin');
const jarFile = path.join(bin, 'modpack.jar');
if (fs.existsSync(jarFile)) {
try {
const buf = fs.readFileSync(jarFile);
const idx = forge.readZipIndex(buf);
const entry = idx['version.json'];
if (entry) {
// manche Jars speichern JSON mit BOM ohne Entfernen bricht JSON.parse
const text = forge.readZipEntry(buf, entry).toString('utf8').replace(/^/, '');
const profile = JSON.parse(text);
const libs = Array.isArray(profile.libraries) ? profile.libraries : [];
let loader = 'vanilla';
let loaderVersion = '';
for (const lib of libs) {
const name = String((lib && lib.name) || '');
if (/net\.minecraftforge:forge:/i.test(name)) {
loader = 'forge';
loaderVersion = (name.split(':')[2] || '').split('-').pop() || '';
} else if (/net\.neoforged:neoforge:/i.test(name) && loader === 'vanilla') {
loader = 'neoforge';
loaderVersion = name.split(':')[2] || '';
}
}
const id = String(profile.id || profile.inheritsFrom || '');
const version = String(profile.inheritsFrom || id.split('-')[0] || '').trim();
if (version) return { version, loader, loaderVersion };
}
} catch { /* modpack.jar nicht lesbar/kein gültiges ZIP weiter unten Fallback */ }
}
// ältere Packs hinterlegen nur eine reine Textdatei mit der MC-Version
const legacy = path.join(bin, 'version');
try {
if (fs.existsSync(legacy)) {
const version = fs.readFileSync(legacy, 'utf8').trim();
if (version) return { version, loader: 'vanilla', loaderVersion: '' };
}
} catch { /* egal, bleibt leer */ }
return { version: '', loader: 'vanilla', loaderVersion: '' };
}
function scan(inputPath) {
const modpacksDir = resolveModpacksDir(inputPath);
if (!modpacksDir || !fs.existsSync(modpacksDir)) return { ok: false, reason: 'not-found' };
const root = path.basename(modpacksDir).toLowerCase() === 'modpacks'
? path.dirname(modpacksDir) : modpacksDir;
const meta = readInstalledPacksMeta(root);
const list = [];
for (const entry of fs.readdirSync(modpacksDir, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
const dir = path.join(modpacksDir, entry.name);
if (!looksLikePack(dir)) continue;
const { version, loader, loaderVersion } = detectVersion(dir);
const info = meta[entry.name] || {};
list.push({
folder: entry.name,
dir,
name: info.displayName || entry.name,
group: '',
notes: '',
version,
loader,
loaderVersion,
gameDir: dir,
hasGameData: true,
});
}
list.sort((a, b) => a.name.localeCompare(b.name));
return { ok: true, instancesDir: modpacksDir, count: list.length, instances: list };
}
// kopiert den Pack-Ordner, lässt aber den Technic-internen bin-Cache weg
function copyInto(sourceDir, targetDir) {
fs.mkdirSync(targetDir, { recursive: true });
for (const name of fs.readdirSync(sourceDir)) {
if (SKIP_DIRS.has(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,
},
});
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, resolveModpacksDir, technicRoot, detectVersion };