Files
AeroMc-Launcher/src/crashhelp.js
T
2026-10-02 19:39:47 +00:00

219 lines
12 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';
/*
* crashhelp.js – Absturz-Assistent
* ---------------------------------
* Liest Absturzbericht, latest.log und die letzte Konsolenausgabe eines Starts
* und erkennt die häufigsten Ursachen. Jeder Fund bekommt eine verständliche
* Erklärung und – wo möglich – eine Aktion, die der Launcher mit einem Klick
* ausführen kann:
*
* { typ: 'mod-installieren', projekt, name } fehlende Abhängigkeit von Modrinth holen
* { typ: 'mod-deaktivieren', dateiname, name } Mod abschalten (umbenennen, nichts gelöscht)
* { typ: 'ram', mb } Arbeitsspeicher der Instanz ändern
* { typ: 'java-auto' } passendes Java automatisch wählen lassen
* { typ: 'mods-ordner' } Mods-Ordner öffnen
*
* Bewusst nur Muster, die eindeutig sind – lieber keinen Fund als einen falschen.
*/
// Mod-IDs, die keine installierbaren Mods sind
const KEINE_MODS = new Set(['minecraft', 'java', 'fabricloader', 'fabric-loader', 'forge', 'neoforge', 'quilt_loader', 'quilt-loader', 'fml', 'mixinextras']);
// Mod-ID -> Modrinth-Projekt, wo sich beides unterscheidet
const PROJEKT_FUER_ID = {
fabric: 'fabric-api',
'fabric-api-base': 'fabric-api',
quilted_fabric_api: 'qsl',
cloth_config: 'cloth-config',
'cloth-config2': 'cloth-config',
architectury: 'architectury-api',
geckolib3: 'geckolib',
};
// Java-Hauptversion aus der "class file version" (52 = Java 8, 61 = 17, 65 = 21 …)
const javaAusKlassenversion = (v) => Number(v) - 44;
function projektFuer(modId) {
const id = String(modId || '').toLowerCase();
return PROJEKT_FUER_ID[id] || id.replace(/_/g, '-');
}
// installierte Mod zu einer ID/einem Namen finden (für "abschalten")
function findeMod(mods, modId, name) {
const id = String(modId || '').toLowerCase();
const kurz = id.replace(/[-_]/g, '');
const n = String(name || '').toLowerCase().replace(/[^a-z0-9]/g, '');
return (mods || []).find((m) => {
if (m.enabled === false) return false;
const datei = String(m.filename || '').toLowerCase();
const titel = String(m.title || '').toLowerCase().replace(/[^a-z0-9]/g, '');
return (id && (datei.startsWith(id) || datei.replace(/[-_]/g, '').startsWith(kurz)))
|| (n && titel && (titel === n || datei.replace(/[^a-z0-9]/g, '').startsWith(n)));
}) || null;
}
/*
* text: zusammengeführter Text aus Bericht, Log und Konsole
* ctx: { loader, version, maxMemMb, gesamtMb, mods: [{ filename, title, projectId, enabled }] }
* -> [{ schluessel, titel, text, aktion? }]
*/
function analysiere(text, ctx = {}) {
const t = String(text || '');
const funde = [];
const schonDa = new Set();
const fund = (schluessel, titel, erklaerung, aktion) => {
if (schonDa.has(schluessel)) return;
schonDa.add(schluessel);
funde.push({ schluessel, titel, text: erklaerung, aktion: aktion || null });
};
const mods = ctx.mods || [];
// --- Fehlende Abhängigkeiten (Fabric/Quilt) ---
// "Mod 'Mod Menu' (modmenu) 7.2.2 requires any version of fabric-api, which is missing!"
// neuere Fassung: "... requires any version of mod 'Fabric API' (fabric-api), which is missing!"
for (const m of t.matchAll(/Mod '([^']+)' \(([\w.-]+)\)[^\n]*? requires (?:version ([^\n,]+?)|any version) of (?:mod '([^']+)' \(([\w.-]+)\)|([\w.-]+)), which is missing/gi)) {
const [, name, , version, fehltName, fehltIdNeu, fehltIdAlt] = m;
const fehlt = fehltIdNeu || fehltIdAlt;
if (KEINE_MODS.has(fehlt.toLowerCase())) continue;
const anzeige = fehltName || fehlt;
fund(`fehlt:${fehlt}`, `„${name}“ braucht ${anzeige}`,
`Der Mod „${name}“ funktioniert nur zusammen mit „${anzeige}“${version ? ` (Version ${version.trim()})` : ''} – und der fehlt in dieser Instanz.`,
{ typ: 'mod-installieren', projekt: projektFuer(fehlt), name: anzeige });
}
// Lösungsvorschlag von Fabric: "- Install fabric-api, any version."
for (const m of t.matchAll(/^\s*-\s*Install ([\w.-]+), (?:any version|version [^\n.]+)/gim)) {
const fehlt = m[1];
if (KEINE_MODS.has(fehlt.toLowerCase())) continue;
fund(`fehlt:${fehlt}`, `Es fehlt „${fehlt}“`,
`Ein installierter Mod braucht „${fehlt}“, der in dieser Instanz nicht installiert ist.`,
{ typ: 'mod-installieren', projekt: projektFuer(fehlt), name: fehlt });
}
// --- Fehlende Abhängigkeiten (Forge/NeoForge) ---
// "Mod ID: 'architectury', Requested by: 'rei', Expected range: '[9.1.12,)', Actual version: '[MISSING]'"
for (const m of t.matchAll(/Mod ID: '([\w.-]+)', Requested by: '([\w.-]+)', Expected range: '([^']*)', Actual version: '([^']*)'/gi)) {
const [, fehlt, von, bereich, ist] = m;
if (fehlt.toLowerCase() === 'minecraft') {
const mod = findeMod(mods, von);
fund(`mcversion:${von}`, `„${von}“ passt nicht zu dieser Minecraft-Version`,
`Der Mod „${von}“ ist für Minecraft ${bereich} gemacht, deine Instanz hat ${ctx.version || ist}.`,
mod ? { typ: 'mod-deaktivieren', dateiname: mod.filename, name: mod.title || von } : null);
continue;
}
if (KEINE_MODS.has(fehlt.toLowerCase())) continue;
if (/MISSING/i.test(ist)) {
fund(`fehlt:${fehlt}`, `„${von}“ braucht ${fehlt}`,
`Der Mod „${von}“ funktioniert nur zusammen mit „${fehlt}“ – und der fehlt in dieser Instanz.`,
{ typ: 'mod-installieren', projekt: projektFuer(fehlt), name: fehlt });
} else {
fund(`alt:${fehlt}`, `„${fehlt}“ ist zu alt oder zu neu`,
`„${von}“ braucht „${fehlt}“ in der Version ${bereich}, installiert ist ${ist}. Aktualisiere „${fehlt}“ im Inhalts-Dialog („Nach Updates suchen“).`);
}
}
// --- Mod für eine andere Minecraft-Version (Fabric) ---
// "Mod 'X' (x) 1.0 requires version 1.20.4 of minecraft, but only the wrong version is present: 1.21!"
for (const m of t.matchAll(/Mod '([^']+)' \(([\w.-]+)\)[^\n]*? requires (?:version |any version of )?([^\n]*?) of (?:mod )?'?minecraft'?/gi)) {
const [, name, id, fuer] = m;
const mod = findeMod(mods, id, name);
fund(`mcversion:${id}`, `„${name}“ passt nicht zu dieser Minecraft-Version`,
`„${name}“ ist für Minecraft ${fuer.replace(/^version\s+/i, '').trim() || 'eine andere Version'} gemacht, deine Instanz hat ${ctx.version || 'eine andere'}. Abschalten oder eine passende Version des Mods installieren.`,
mod ? { typ: 'mod-deaktivieren', dateiname: mod.filename, name } : null);
}
// Lösungsvorschlag: "- Remove mod 'X' (x)."
for (const m of t.matchAll(/^\s*-\s*Remove mod '([^']+)' \(([\w.-]+)\)/gim)) {
const [, name, id] = m;
const mod = findeMod(mods, id, name);
fund(`entfernen:${id}`, `„${name}“ verursacht den Fehler`,
`Der Mod-Loader schlägt vor, „${name}“ zu entfernen – er verträgt sich nicht mit den anderen Mods oder dieser Version.`,
mod ? { typ: 'mod-deaktivieren', dateiname: mod.filename, name } : null);
}
// "Mod 'A' (a) is incompatible with any version of mod 'B' (b)"
for (const m of t.matchAll(/Mod '([^']+)' \(([\w.-]+)\)[^\n]*? is incompatible with [^\n]*?mod '([^']+)' \(([\w.-]+)\)/gi)) {
const [, a, idA, b] = m;
const mod = findeMod(mods, idA, a);
fund(`inkompatibel:${idA}`, `„${a}“ und „${b}“ vertragen sich nicht`,
`Diese beiden Mods können nicht zusammen installiert sein. Schalte einen der beiden ab.`,
mod ? { typ: 'mod-deaktivieren', dateiname: mod.filename, name: a } : null);
}
// --- Doppelte Mods ---
if (/Duplicate mods found|Found duplicate mods|is provided by multiple mods|Mod ID ['"]?[\w.-]+['"]? is (?:provided|present) multiple times/i.test(t)) {
fund('doppelt', 'Ein Mod ist doppelt installiert',
'Mindestens ein Mod liegt zweimal im Mods-Ordner (z. B. zwei Versionen desselben Mods). Lösche die ältere Datei.',
{ typ: 'mods-ordner' });
}
// --- Falsche Java-Version ---
const klasse = t.match(/UnsupportedClassVersionError[\s\S]{0,300}?class file version (\d+)\.\d+/i);
const javaPflicht = t.match(/requires (?:at least )?Java (\d+)|Java (\d+) (?:or (?:newer|later|above)) is required|Unsupported Java detected \((\d+)/i);
if (klasse || javaPflicht) {
const noetig = klasse ? javaAusKlassenversion(klasse[1]) : Number(javaPflicht[1] || javaPflicht[2] || 0);
fund('java', noetig ? `Diese Instanz braucht Java ${noetig}` : 'Falsche Java-Version',
`Minecraft oder ein Mod wurde für ${noetig ? `Java ${noetig}` : 'eine andere Java-Version'} gebaut, gestartet wurde mit einer älteren. AeroMC kann das passende Java automatisch wählen und bei Bedarf herunterladen.`,
{ typ: 'java-auto' });
}
// --- Zu wenig Arbeitsspeicher ---
if (/java\.lang\.OutOfMemoryError|There is insufficient memory for the Java Runtime/i.test(t)) {
const jetzt = Number(ctx.maxMemMb) || 2048;
const grenze = Math.max(2048, Math.floor((Number(ctx.gesamtMb) || 8192) * 0.6));
const neu = Math.min(grenze, jetzt + 2048);
fund('ram', 'Minecraft hatte zu wenig Arbeitsspeicher',
`Der Instanz waren ${Math.round(jetzt / 102.4) / 10} GB zugewiesen – zu wenig für diese Mods oder Welt.`
+ (neu > jetzt ? ` Empfohlen: ${Math.round(neu / 102.4) / 10} GB.` : ' Mehr lässt dein PC kaum zu – weniger Mods oder eine kleinere Sichtweite helfen.'),
neu > jetzt ? { typ: 'ram', mb: neu } : null);
}
if (/Could not reserve enough space for (?:\d+KB )?object heap|Invalid maximum heap size|Initial heap size set to a larger value than the maximum/i.test(t)) {
const jetzt = Number(ctx.maxMemMb) || 4096;
const neu = Math.max(2048, Math.min(jetzt - 1024, Math.floor((Number(ctx.gesamtMb) || 8192) * 0.5)));
fund('ram-zuviel', 'Zu viel Arbeitsspeicher eingestellt',
`Java konnte die eingestellten ${Math.round(jetzt / 102.4) / 10} GB nicht reservieren – entweder ist gerade nicht genug frei oder es läuft ein 32-Bit-Java.`,
neu < jetzt ? { typ: 'ram', mb: neu } : { typ: 'java-auto' });
}
// --- Mixin-Fehler: ein bestimmter Mod passt nicht ---
for (const m of t.matchAll(/Mixin (?:apply|transformation)[^\n]*? (?:for|from) mod ([\w.-]+) failed|from mod ([\w.-]+) failed injection check|mixins?\.json:[^\n]*? from mod ([\w.-]+)/gi)) {
const id = m[1] || m[2] || m[3];
if (!id || KEINE_MODS.has(id.toLowerCase())) continue;
const mod = findeMod(mods, id);
fund(`mixin:${id}`, `„${mod ? (mod.title || id) : id}“ passt nicht zu dieser Version`,
`Der Mod „${id}“ greift tief in Minecraft ein und ist mit dieser Minecraft-Version oder einem anderen Mod nicht kompatibel. Schalte ihn ab oder installiere eine passende Version.`,
mod ? { typ: 'mod-deaktivieren', dateiname: mod.filename, name: mod.title || id } : null);
}
// --- Grafiktreiber ---
if (/Pixel format not accelerated|WGL: The driver does not appear to support OpenGL|GLFW error 65542|OpenGL 3\.2 (?:is )?not supported|Couldn't set pixel format|No OpenGL context found/i.test(t)) {
fund('treiber', 'Problem mit dem Grafiktreiber',
'Minecraft konnte OpenGL nicht starten. Meist hilft es, den Grafiktreiber direkt vom Hersteller (NVIDIA, AMD oder Intel) zu aktualisieren. Bei Laptops: in den Windows-Grafikeinstellungen Java auf „Hohe Leistung“ stellen.');
}
// --- Bekannte Unverträglichkeiten anhand der installierten Mods ---
const aktive = mods.filter((m) => m.enabled !== false);
const hat = (re) => aktive.find((m) => re.test(String(m.filename || '') + ' ' + String(m.title || '')));
const optifine = hat(/optifine|optifabric/i);
const sodium = hat(/sodium|embeddium|rubidium/i);
if (optifine && sodium) {
fund('optifine-sodium', 'OptiFine und Sodium zusammen installiert',
'OptiFine und Sodium (bzw. Embeddium/Rubidium) machen dasselbe und vertragen sich nicht. Schalte OptiFine ab – für Shader gibt es Iris als Ergänzung zu Sodium.',
{ typ: 'mod-deaktivieren', dateiname: optifine.filename, name: optifine.title || 'OptiFine' });
}
// --- Forge nennt einen Verdächtigen, aber sonst nichts Eindeutiges ---
if (!funde.length) {
const verdacht = t.match(/^Suspected Mods?:\s*([^\n]+)$/im);
if (verdacht && !/^\s*NONE\s*$/i.test(verdacht[1])) {
const name = verdacht[1].split(/[,(]/)[0].trim();
const mod = findeMod(mods, '', name);
fund('verdacht', `Vermutlich verursacht von „${name}“`,
`Laut Absturzbericht steckt wahrscheinlich „${verdacht[1].trim()}“ dahinter. Schalte den Mod testweise ab und starte erneut.`,
mod ? { typ: 'mod-deaktivieren', dateiname: mod.filename, name: mod.title || name } : null);
}
}
return funde;
}
module.exports = { analysiere, projektFuer };