Upload via GUI (39 Dateien)
This commit is contained in:
180
src/crashreports.js
Normal file
180
src/crashreports.js
Normal file
@@ -0,0 +1,180 @@
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* crashreports.js – Absturzberichte von Minecraft auswerten
|
||||
* ----------------------------------------------------------
|
||||
* Minecraft legt bei einem Absturz eine Datei unter
|
||||
* <spielordner>/crash-reports/crash-<datum>-client.txt an. Die enthält
|
||||
* hunderte Zeilen Systemangaben, aber nur wenige, die weiterhelfen.
|
||||
*
|
||||
* Dieses Modul sucht den neuesten Bericht und zieht daraus heraus:
|
||||
* Beschreibung, Fehlerart, verdächtige Mods und die ersten Stellen im
|
||||
* Aufrufverlauf.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const zlib = require('zlib');
|
||||
|
||||
// größere Berichte gibt es kaum; die Grenze schützt vor Ausreißern
|
||||
const MAX_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
function berichtsOrdner(gameDir) {
|
||||
return path.join(gameDir, 'crash-reports');
|
||||
}
|
||||
|
||||
/*
|
||||
* Neuester Bericht im Ordner. seit: nur Berichte ab diesem Zeitpunkt
|
||||
* (Millisekunden) – sonst zeigt man nach einem sauberen Start den Absturz
|
||||
* von vorgestern an.
|
||||
*/
|
||||
function neuesterBericht(gameDir, seit) {
|
||||
const dir = berichtsOrdner(gameDir);
|
||||
let eintraege = [];
|
||||
try { eintraege = fs.readdirSync(dir, { withFileTypes: true }); } catch { return null; }
|
||||
|
||||
let bester = null;
|
||||
for (const e of eintraege) {
|
||||
if (!e.isFile() || !/^crash-.*\.txt$/i.test(e.name)) continue;
|
||||
const voll = path.join(dir, e.name);
|
||||
let st;
|
||||
try { st = fs.statSync(voll); } catch { continue; }
|
||||
if (seit && st.mtimeMs < seit) continue;
|
||||
if (!bester || st.mtimeMs > bester.mtimeMs) bester = { pfad: voll, name: e.name, mtimeMs: st.mtimeMs, groesse: st.size };
|
||||
}
|
||||
return bester;
|
||||
}
|
||||
|
||||
/*
|
||||
* Zieht die aussagekräftigen Zeilen aus einem Bericht.
|
||||
* -> { beschreibung, fehler, verdaechtigeMods, verlauf: [] }
|
||||
*/
|
||||
function auswerten(text) {
|
||||
const zeilen = String(text || '').split(/\r?\n/);
|
||||
const befund = { beschreibung: '', fehler: '', verdaechtigeMods: '', verlauf: [] };
|
||||
|
||||
for (let i = 0; i < zeilen.length; i++) {
|
||||
const z = zeilen[i];
|
||||
const roh = z.trim();
|
||||
|
||||
if (!befund.beschreibung && /^Description:/i.test(roh)) {
|
||||
befund.beschreibung = roh.replace(/^Description:\s*/i, '').trim();
|
||||
// Die Fehlerzeile steht direkt darunter, nach einer Leerzeile.
|
||||
for (let j = i + 1; j < Math.min(i + 6, zeilen.length); j++) {
|
||||
const k = zeilen[j].trim();
|
||||
if (!k) continue;
|
||||
befund.fehler = k;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Forge/NeoForge nennen Verdächtige ausdrücklich
|
||||
if (!befund.verdaechtigeMods && /^Suspected Mods?:/i.test(roh)) {
|
||||
befund.verdaechtigeMods = roh.replace(/^Suspected Mods?:\s*/i, '').trim();
|
||||
continue;
|
||||
}
|
||||
|
||||
// die obersten Stellen im Aufrufverlauf – dort steht meist der Verursacher
|
||||
if (befund.verlauf.length < 8 && /^at\s+\S/.test(roh)) {
|
||||
befund.verlauf.push(roh);
|
||||
}
|
||||
}
|
||||
|
||||
// Manche Berichte nennen keine Description, dafür eine Ausnahme weiter oben
|
||||
if (!befund.fehler) {
|
||||
const treffer = zeilen.find((z) => /^[\w.$]+(Exception|Error)(:|\s|$)/.test(z.trim()));
|
||||
if (treffer) befund.fehler = treffer.trim();
|
||||
}
|
||||
return befund;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sucht und liest den neuesten Bericht.
|
||||
* -> { ok: false, grund } oder { ok: true, datei, zeitpunkt, ...auswertung, text }
|
||||
*/
|
||||
function lies(gameDir, seit) {
|
||||
const bericht = neuesterBericht(gameDir, seit);
|
||||
if (!bericht) return { ok: false, grund: 'kein-bericht' };
|
||||
if (bericht.groesse > MAX_BYTES) return { ok: false, grund: 'zu-gross', datei: bericht.name };
|
||||
|
||||
let text = '';
|
||||
try { text = fs.readFileSync(bericht.pfad, 'utf8'); }
|
||||
catch (err) { return { ok: false, grund: 'nicht-lesbar', message: err.message }; }
|
||||
|
||||
return Object.assign({
|
||||
ok: true,
|
||||
datei: bericht.name,
|
||||
pfad: bericht.pfad,
|
||||
zeitpunkt: new Date(bericht.mtimeMs).toISOString(),
|
||||
text,
|
||||
}, auswerten(text));
|
||||
}
|
||||
|
||||
/*
|
||||
* Logdateien der Instanz auflisten. Minecraft schreibt latest.log und legt
|
||||
* ältere Läufe gepackt als <datum>-<nr>.log.gz daneben.
|
||||
* -> [{ name, groesse, geaendert, gepackt }] – neueste zuerst
|
||||
*/
|
||||
function listeLogs(gameDir) {
|
||||
const dir = path.join(gameDir, 'logs');
|
||||
let eintraege = [];
|
||||
try { eintraege = fs.readdirSync(dir, { withFileTypes: true }); } catch { return []; }
|
||||
|
||||
const logs = [];
|
||||
for (const e of eintraege) {
|
||||
if (!e.isFile()) continue;
|
||||
const gepackt = /\.log\.gz$/i.test(e.name);
|
||||
if (!gepackt && !/\.log$/i.test(e.name)) continue;
|
||||
try {
|
||||
const st = fs.statSync(path.join(dir, e.name));
|
||||
logs.push({ name: e.name, groesse: st.size, geaendert: st.mtimeMs, gepackt });
|
||||
} catch { /* überspringen */ }
|
||||
}
|
||||
// latest.log immer zuoberst, danach nach Datum
|
||||
logs.sort((a, b) => {
|
||||
if (a.name.toLowerCase() === 'latest.log') return -1;
|
||||
if (b.name.toLowerCase() === 'latest.log') return 1;
|
||||
return b.geaendert - a.geaendert;
|
||||
});
|
||||
return logs;
|
||||
}
|
||||
|
||||
/*
|
||||
* Inhalt einer Logdatei. Gepackte werden entpackt.
|
||||
* maxZeilen begrenzt die Rückgabe – ein Log kann zehntausende Zeilen haben,
|
||||
* und die Oberfläche soll dabei nicht einfrieren.
|
||||
*/
|
||||
function liesLog(gameDir, name, maxZeilen = 5000) {
|
||||
const dir = path.join(gameDir, 'logs');
|
||||
const voll = path.join(dir, path.basename(String(name || '')));
|
||||
if (path.dirname(voll) !== dir) return { ok: false, grund: 'bad-name' };
|
||||
if (!fs.existsSync(voll)) return { ok: false, grund: 'nicht-gefunden' };
|
||||
|
||||
let text = '';
|
||||
try {
|
||||
if (/\.gz$/i.test(voll)) text = zlib.gunzipSync(fs.readFileSync(voll)).toString('utf8');
|
||||
else text = fs.readFileSync(voll, 'utf8');
|
||||
} catch (err) {
|
||||
return { ok: false, grund: 'nicht-lesbar', message: err.message };
|
||||
}
|
||||
|
||||
const zeilen = text.split(/\r?\n/);
|
||||
const gekuerzt = zeilen.length > maxZeilen;
|
||||
// bei langen Logs das Ende zeigen – dort steht, was zuletzt passierte
|
||||
const sichtbar = gekuerzt ? zeilen.slice(zeilen.length - maxZeilen) : zeilen;
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
name: path.basename(voll),
|
||||
zeilenGesamt: zeilen.length,
|
||||
gekuerzt,
|
||||
text: sichtbar.join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
function logOrdner(gameDir) {
|
||||
return path.join(gameDir, 'logs');
|
||||
}
|
||||
|
||||
module.exports = { lies, auswerten, neuesterBericht, berichtsOrdner, listeLogs, liesLog, logOrdner };
|
||||
Reference in New Issue
Block a user