219 lines
12 KiB
JavaScript
219 lines
12 KiB
JavaScript
'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 };
|