Upload via GUI (43 Dateien)

This commit is contained in:
Git Manager GUI
2026-08-05 09:04:48 +02:00
parent ec4992c5c7
commit bafad25059
12 changed files with 1433 additions and 79 deletions
+172
View File
@@ -0,0 +1,172 @@
'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 };