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