2 Commits
1.0.4 ... main

Author SHA1 Message Date
3e28b50f1b README.md aktualisiert 2026-08-03 19:10:09 +00:00
Git Manager GUI
54d248133c Upload via GUI (39 Dateien) 2026-08-03 21:00:24 +02:00
8 changed files with 261 additions and 44 deletions

View File

@@ -6,7 +6,7 @@
Ein Minecraft-Launcher für Windows mit getrennten Instanzen, Mod-Unterstützung und Microsoft-Anmeldung.
Aktueller Stand: Version 1.0.4
Aktueller Stand: Version 1.0.5
</div>
@@ -21,22 +21,25 @@ Der Launcher konzentriert sich auf den Alltag: Instanz anlegen, Konto auswählen
- Beliebig viele getrennte Minecraft-Instanzen
- Unterstützung für Vanilla, Fabric, Quilt, Forge und NeoForge
- Versionsliste mit Filter: Releases, Snapshots oder alle
- Modrinth-Integration für **Mods, Ressourcenpakete und Shader** (inkl. Pflicht-Abhängigkeiten und Update-Prüfung)
- Modrinth- **und CurseForge**-Integration für **Mods, Ressourcenpakete und Shader** (Suche wahlweise über beide Quellen, Installation inkl. rekursiver Pflicht-Abhängigkeiten, Update-Prüfung samt Sammel-Update)
- Modpacks direkt von Modrinth durchsuchen und mit einem Klick als neue Instanz installieren ohne vorherigen manuellen Download
- Microsoft-Login mit mehreren Konten
- Gemeinsamer Import für AeroMC-ZIP, Modrinth-Modpacks (`.mrpack`), Minecraft Launcher, CurseForge sowie MultiMC/Prism
- Reimport überschreibt vorhandene Instanzen mit gleichem Namen
- Export, Kopieren, Gruppieren, Löschen und Desktop-Verknüpfung pro Instanz
- Mehrfachauswahl mit Strg-Klick für Sammellöschen
- Globales Backup inkl. automatischer Zeitplan-Sicherungen
- Welten, Ressourcenpakete, Screenshots, Serverliste und Logdateien verwalten
- Welten, Ressourcenpakete, Datenpakete, Screenshots, Serverliste und Logdateien verwalten
- Serverlisten-Einträge auf einen Schlag in andere Instanzen übernehmen
- Online- und Offline-Start
- Eingebaute Konsole/Log-Ansicht
- Eingebaute Konsole/Log-Ansicht inkl. Absturzberichte pro Instanz
- Eigenen Minecraft-Skin direkt im Launcher hochladen, mit Live-Vorschau anpassen (klassisch/schlank) und zurücksetzen
- Skin-Hintergrund des aktiven Kontos (mehrere Posen, einstellbare Deckkraft)
- Eigene Icons (inkl. eigenes Bild) und Notizen pro Instanz
- Eigene Icons pro Instanz vorgefertigte Symbole, direkt aus Minecraft extrahierte Block-/Item-Icons oder ein eigenes Bild plus Notizen
- Java pro Instanz oder global, inkl. automatischem Download fehlender Versionen (Eclipse Temurin)
- RAM-, JVM-, Pre-Launch- und Post-Exit-Einstellungen (global und pro Instanz)
- Optionaler Wrapper-Befehl und HTTP-Proxy für Downloads
- Bedrock-Bereich für vorhandene Bedrock-Installation unter Windows
- Bedrock-Bereich für vorhandene Bedrock-Installation unter Windows, inkl. Installation zusätzlicher Add-ons
- Update-Funktion für neue Launcher-Versionen
- Design Hell/Dunkel und Sortierung der Instanzen
@@ -71,8 +74,8 @@ Jede Instanz hat:
- Namen und optionale Gruppe
- Minecraft-Version und optionalen Mod-Loader
- optionales Icon (vorgegeben oder eigenes Bild) und Notizen
- eigene Mods, Ressourcenpakete, Shader, Welten, Screenshots und Serverliste
- optionales Icon (vorgegeben, aus Minecraft extrahiert oder eigenes Bild) und Notizen
- eigene Mods, Ressourcenpakete, Shader, Welten, Datenpakete, Screenshots und Serverliste
- eigene Java-, RAM-, JVM-, Pre-Launch- und Post-Exit-Einstellungen (leer = globale Vorgabe)
Über die Seitenleiste einer ausgewählten Instanz erreichst du u. a.:
@@ -80,9 +83,9 @@ Jede Instanz hat:
- Starten / Offline starten
- Instanz und Notizen bearbeiten
- Mods, Ressourcenpakete und Shader
- Welten und Bildschirmfotos
- Server verwalten
- Logdateien ansehen
- Welten, Datenpakete und Bildschirmfotos
- Server verwalten (inkl. Übernahme eines Eintrags in andere Instanzen)
- Logdateien und Absturzberichte ansehen
- Minecraft-, Konfigurations- und Instanz-Ordner öffnen
- Desktop-Verknüpfung erstellen
- Exportieren, Kopieren, Gruppe ändern, Löschen
@@ -97,7 +100,7 @@ In der Instanzliste: Suche, Sortierung und **Strg + Klick** für Mehrfachauswahl
| Quelle | Typische Nutzung |
|---|---|
| ZIP-Archiv | AeroMC-Export (`instance.json`) sowie einzelne MultiMC/Prism-Instanzen (auch gezippt) |
| Modpack | Modrinth `.mrpack` |
| Modpack | Modrinth-Katalog direkt durchsuchen und installieren, oder Datei importieren: `.mrpack` sowie CurseForge-ZIP (`manifest.json`) |
| Minecraft Launcher | Profile aus dem offiziellen Launcher |
| CurseForge | Instanzen aus dem CurseForge-Ordner |
| MultiMC / Prism | Vorhandene MultiMC- oder Prism-Instanzen |
@@ -106,17 +109,30 @@ Existiert bereits eine Instanz mit demselben Namen, wird sie ersetzt.
Beim offiziellen Minecraft Launcher werden nur benutzerdefinierte Profile angeboten (keine „Latest Release“/„Latest Snapshot“). Bei gemeinsamen Standard-Spielordnern übernimmt AeroMC spielrelevante Daten (Mods, Welten, Konfiguration), nicht den kompletten Cache.
Im Reiter **Modpack** desselben Dialogs lässt sich zusätzlich direkt im Modrinth-Katalog suchen und ein Modpack mit einem Klick installieren ganz ohne vorherigen manuellen Download der `.mrpack`-Datei.
## Mods, Ressourcenpakete und Shader
Im Inhalts-Dialog einer Instanz:
- Katalog durchsuchen (Sortierung, Kategorien)
- Installation inkl. Abhängigkeiten
- Katalog durchsuchen (Quelle Modrinth oder CurseForge, Sortierung, Kategorien)
- Installation inkl. **automatischer** Installation aller Pflicht-Abhängigkeiten (rekursiv, auch quellübergreifend)
- Installierte Einträge aktivieren, deaktivieren, entfernen
- Nach Updates suchen und alle aktualisieren
- Ordner der jeweiligen Inhaltsart öffnen
Ressourcenpakete und Shader funktionieren auch in Vanilla-Instanzen. Mods benötigen einen Mod-Loader.
Ressourcenpakete und Shader funktionieren auch in Vanilla-Instanzen. Mods benötigen einen Mod-Loader. Für CurseForge-Inhalte wird ein eigener API-Key oder eine Weiterleitungs-Adresse benötigt (siehe **Einstellungen → Netzwerk**).
## Skin ändern
Über **Konten → Skin ändern…** lässt sich der Skin des aktiven Microsoft-Kontos direkt im Launcher ändern:
- PNG-Datei auswählen, sofortige Prüfung auf gültige Maße
- Live-Vorschau am 3D-Modell vor dem Anwenden
- Modell wählen: klassisch oder schlank (slim)
- Auf einen früheren Standard-Skin zurücksetzen
Die Änderung wird direkt bei Mojang für das angemeldete Konto hinterlegt und gilt somit auch außerhalb von AeroMC.
## Backup und Wiederherstellung
@@ -171,7 +187,7 @@ Ohne eigenen Key und ohne Weiterleitung funktionieren Modrinth-Inhalte weiterhin
## Bedrock
Der Bedrock-Bereich startet die vorhandene Windows-Store-Version und erleichtert den Zugriff auf Welten, Pakete, Add-ons und Ordner. AeroMC installiert Bedrock nicht selbst. Fehlt die Installation, wird der Bereich automatisch ausgeblendet.
Der Bedrock-Bereich startet die vorhandene Windows-Store-Version und erleichtert den Zugriff auf Welten, Pakete, Add-ons und Ordner. Zusätzliche Add-on-Dateien lassen sich direkt über AeroMC installieren. AeroMC installiert Bedrock selbst (die App aus dem Microsoft Store) nicht. Fehlt die Installation, wird der Bereich automatisch ausgeblendet.
## Konsole, Logs und Crash-Reports
@@ -190,9 +206,17 @@ Nutzerdaten liegen lokal unter `%APPDATA%\aeromc`.
## Rechtliches
AeroMC ist kein offizielles Produkt von Mojang oder Microsoft.
AeroMC ist kein offizielles Produkt von Mojang oder Microsoft.
Minecraft ist eine Marke von Mojang AB.
## Lizenz
### Urheberrecht & Nutzungsbedingungen
MIT
AeroMC ist urheberrechtlich geschützt. © M_Viper Alle Rechte vorbehalten.
- ❌ Die Weitergabe dieses Launchers an Dritte ist strengstens untersagt
- ❌ Das Verändern, Dekompilieren oder Bearbeiten des Codes ist nicht erlaubt
- ❌ Eine Veröffentlichung auch in veränderter Form ist verboten
- ❌ Der Verkauf oder die kommerzielle Nutzung ist nicht gestattet
- ✅ Die Nutzung ist ausschließlich für den privaten, nicht-kommerziellen Gebrauch erlaubt
Wer gegen diese Bedingungen verstößt, macht sich strafbar und kann rechtlich belangt werden.

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "aeromc",
"version": "1.0.4-beta",
"version": "1.0.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "aeromc",
"version": "1.0.4-beta",
"version": "1.0.5",
"license": "MIT",
"dependencies": {
"archiver": "^7.0.1",

View File

@@ -1,7 +1,7 @@
{
"name": "aeromc",
"productName": "AeroMC",
"version": "1.0.4",
"version": "1.0.5",
"description": "AeroMC Instanz-basierter Minecraft-Launcher (à la MultiMC/Prism)",
"homepage": "https://m-viper.de",
"main": "src/main.js",

View File

@@ -47,11 +47,18 @@ function neuesterBericht(gameDir, seit) {
/*
* Zieht die aussagekräftigen Zeilen aus einem Bericht.
* -> { beschreibung, fehler, verdaechtigeMods, verlauf: [] }
* -> { beschreibung, fehler, verdaechtigeMods, diagnose, empfehlung, verlauf: [] }
*/
function auswerten(text) {
const zeilen = String(text || '').split(/\r?\n/);
const befund = { beschreibung: '', fehler: '', verdaechtigeMods: '', verlauf: [] };
const befund = {
beschreibung: '',
fehler: '',
verdaechtigeMods: '',
diagnose: '',
empfehlung: '',
verlauf: [],
};
for (let i = 0; i < zeilen.length; i++) {
const z = zeilen[i];
@@ -86,6 +93,22 @@ function auswerten(text) {
const treffer = zeilen.find((z) => /^[\w.$]+(Exception|Error)(:|\s|$)/.test(z.trim()));
if (treffer) befund.fehler = treffer.trim();
}
const volltext = zeilen.join('\n');
const shutdownWatchdog = /Description:\s*Client shutdown from post-main/i.test(volltext)
&& /ClientShutdownWatchdog/i.test(volltext);
const webMapJetty = /journeymap_webmap:\s*JourneyMap WebMap\b/i.test(volltext)
&& /JettyServerThreadPool/i.test(volltext)
&& /ServerConnector.*\{0\.0\.0\.0:\d+\}/i.test(volltext);
if (shutdownWatchdog && webMapJetty) {
const version = volltext.match(/journeymap_webmap:\s*JourneyMap WebMap\s+([^\s\r\n]+)/i);
const name = `JourneyMap WebMap${version ? ` ${version[1]}` : ''}`;
befund.verdaechtigeMods = name;
befund.diagnose = `${name} hat seinen eingebetteten Jetty-Webserver beim Beenden nicht gestoppt. `
+ 'Dessen nicht beendete Server-Threads haben den Minecraft-Shutdown-Watchdog ausgelöst.';
befund.empfehlung = 'AeroMC beendet einen nach dem regulären Client-Shutdown verbliebenen Java-Prozess nun '
+ 'nach einer kurzen Schonfrist automatisch. JourneyMap WebMap kann installiert und aktiviert bleiben.';
}
return befund;
}

View File

@@ -21,6 +21,7 @@ const { spawn, spawnSync } = require('child_process');
const services = require('./services');
const loaders = require('./loaders');
const forge = require('./forge');
const processwatchdog = require('./processwatchdog');
const UA = 'AeroMC-Launcher/0.2';
const RESOURCES = 'https://resources.download.minecraft.net';
@@ -253,20 +254,28 @@ async function prepareAssets(vj, sharedDir, onProgress) {
// ---------------------------------------------------------------------------
function pickJava(vj, preferredPath) {
const required = (vj.javaVersion && vj.javaVersion.majorVersion) || 8;
let preferred = null;
if (preferredPath && preferredPath.trim()) {
const info = services.probeJava(preferredPath);
if (info) return { path: preferredPath, major: info.major, forced: true };
if (info) {
preferred = { path: preferredPath, major: info.major, forced: true };
if (info.major === required) return preferred;
}
}
const required = (vj.javaVersion && vj.javaVersion.majorVersion) || 8;
const jvms = services.scanJava();
// exakt passende oder nächsthöhere 64-bit-Version
// Mod-Loader und Mods unterstützen neuere Java-Versionen nicht automatisch.
// Deshalb ausschließlich die von Mojang angegebene Major-Version verwenden.
const x64 = jvms.filter((j) => j.arch === 'x64');
const exact = x64.find((j) => j.major === required);
if (exact) return { path: exact.path, major: exact.major };
const higher = x64.filter((j) => j.major >= required).sort((a, b) => a.major - b.major)[0];
if (higher) return { path: higher.path, major: higher.major };
const any = jvms[0];
return any ? { path: any.path, major: any.major, mismatch: required } : null;
const fallback = preferred || x64[0] || jvms[0];
return fallback ? {
path: fallback.path,
major: fallback.major,
forced: !!fallback.forced,
mismatch: required,
} : null;
}
// ---------------------------------------------------------------------------
@@ -387,6 +396,14 @@ async function prepareAndLaunch(opts, onProgress) {
}
}
if (!java) return { ok: false, message: 'Keine Java-Installation gefunden. Bitte in den Einstellungen Java festlegen.' };
if (java.mismatch) {
return {
ok: false,
requiredJava: java.mismatch,
message: `Minecraft ${vj.id} benötigt Java ${java.mismatch}, gefunden wurde nur Java ${java.major || 'unbekannt'}. `
+ 'Bitte Auto-Java aktivieren oder eine passende Java-Installation auswählen.',
};
}
// Mod-Loader
let effectiveVj = vj;
@@ -503,14 +520,33 @@ async function prepareAndLaunch(opts, onProgress) {
spawnArgs = [...w.slice(1), java.path, ...args];
}
const child = spawn(cmd, spawnArgs, { cwd: opts.gameDir, detached: false });
child.stdout.on('data', (d) => emit('log', { line: d.toString() }));
child.stderr.on('data', (d) => emit('log', { line: d.toString() }));
const shutdownWatchdog = opts.wrapperCommand && opts.wrapperCommand.trim()
? null
: processwatchdog.createProcessWatchdog({
isRunning: () => child.exitCode === null && child.signalCode === null,
kill: () => child.kill(),
onForce: (graceMs) => emit('log', {
line: `[Launcher] Regulärer Client-Shutdown war nach ${graceMs / 1000} Sekunden noch nicht beendet; `
+ 'verbliebenen Java-Prozess beendet.\n',
}),
onError: () => emit('log', {
line: '[Launcher] Der nach dem regulären Client-Shutdown verbliebene Java-Prozess konnte nicht beendet werden.\n',
}),
});
child.stdout.on('data', (d) => {
emit('log', { line: d.toString() });
if (shutdownWatchdog) shutdownWatchdog.feed(d, 'stdout');
});
child.stderr.on('data', (d) => {
emit('log', { line: d.toString() });
if (shutdownWatchdog) shutdownWatchdog.feed(d, 'stderr');
});
// Ende genau einmal melden 'exit' und 'error' können beide kommen
let beendetGemeldet = false;
const meldeEnde = (code) => {
const meldeEnde = (code, cleanShutdown) => {
if (beendetGemeldet) return;
beendetGemeldet = true;
emit('exit', { code });
emit('exit', { code, cleanShutdown: !!cleanShutdown });
// Post-Exit-Befehl
if (opts.postExitCommand && opts.postExitCommand.trim()) {
emit('log', { line: '[Post-Exit] ' + opts.postExitCommand + '\n' });
@@ -521,12 +557,19 @@ async function prepareAndLaunch(opts, onProgress) {
} catch { /* egal */ }
}
};
child.on('exit', (code) => meldeEnde(code));
child.on('exit', (code) => {
// vor end() lesen: sawCleanShutdown wird zwar nicht von end()/clear()
// zurückgesetzt, aber so bleibt die Reihenfolge unabhängig von internen Details
const cleanShutdown = !!(shutdownWatchdog && shutdownWatchdog.sawCleanShutdown);
if (shutdownWatchdog) shutdownWatchdog.end();
meldeEnde(shutdownWatchdog && shutdownWatchdog.forced ? 0 : code, cleanShutdown);
});
// ohne diesen Listener würde ein Fehlstart (z. B. falscher Java-Pfad) als
// unbehandeltes 'error'-Ereignis den Hauptprozess beenden
child.on('error', (err) => {
if (shutdownWatchdog) shutdownWatchdog.end();
emit('log', { line: '[Fehler] Minecraft konnte nicht gestartet werden: ' + err.message + '\n' });
meldeEnde(-1);
meldeEnde(-1, false);
});
return { ok: true, pid: child.pid, java };

View File

@@ -762,7 +762,11 @@ async function importMrpack(buf, zip) {
const herkunft = await modpackHerkunft(buf, index);
store.updateInstance(created.id, { modpack: Object.assign(herkunft, { dateien: eingespielt.dateien }) });
return { ok: true, instance: store.getInstance(created.id), fileCount: eingespielt.dateien.length };
return {
ok: true,
instance: store.getInstance(created.id),
fileCount: eingespielt.dateien.length,
};
}
/*
@@ -921,7 +925,9 @@ ipcMain.handle('modpack:update', async (_e, id, versionId) => {
if (SPIELERDATEN.some((r) => r.test(alt))) continue;
const p = path.join(gameDir, String(alt).replace(/\//g, path.sep));
if (!p.startsWith(gameDir + path.sep)) continue;
try { if (fs.existsSync(p)) { fs.rmSync(p, { force: true }); entfernt.push(alt); } } catch { /* egal */ }
try {
if (fs.existsSync(p)) { fs.rmSync(p, { force: true }); entfernt.push(alt); }
} catch { /* egal */ }
}
// Minecraft-Version und Loader können sich mit der neuen Fassung ändern
@@ -1514,7 +1520,10 @@ ipcMain.handle('mmc:import', (_e, entries, copyData) => {
const done = [];
const failed = [];
for (const entry of entries || []) {
try { done.push(mmcimport.importOne(store, entry, copyData !== false)); }
try {
const imported = mmcimport.importOne(store, entry, copyData !== false);
done.push(imported);
}
catch (err) { failed.push({ name: entry.name, message: err.message }); }
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('mmc:progress', { current: done.length + failed.length, total: entries.length });
@@ -1540,7 +1549,10 @@ ipcMain.handle('cf:import', (_e, entries, copyData) => {
const done = [];
const failed = [];
for (const entry of entries || []) {
try { done.push(cfimport.importOne(store, entry, copyData !== false)); }
try {
const imported = cfimport.importOne(store, entry, copyData !== false);
done.push(imported);
}
catch (err) { failed.push({ name: entry.name, message: err.message }); }
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('cf:progress', { current: done.length + failed.length, total: entries.length });
@@ -1566,7 +1578,10 @@ ipcMain.handle('mc:import', (_e, entries, copyData) => {
const done = [];
const failed = [];
for (const entry of entries || []) {
try { done.push(mcimport.importOne(store, entry, copyData !== false)); }
try {
const imported = mcimport.importOne(store, entry, copyData !== false);
done.push(imported);
}
catch (err) { failed.push({ name: entry.name, message: err.message }); }
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('mc:progress', { current: done.length + failed.length, total: entries.length });

101
src/processwatchdog.js Normal file
View File

@@ -0,0 +1,101 @@
'use strict';
const DEFAULT_GRACE_MS = 15000;
const CLEAN_SHUTDOWN = /\[(?:Render thread|Client thread)\/INFO\](?:\s+\([^)]*\))?:\s*Stopping!\s*$/;
const FATAL_OUTPUT = [
/---- Minecraft Crash Report ----/i,
/\b(?:Encountered an unexpected exception|Unreported exception thrown|Reported exception thrown)\b/i,
/\b(?:This crash report has been saved to|Saving crash report to)\b/i,
/\bA fatal error has been detected by the Java Runtime Environment\b/i,
/Exception in thread "(?:Render thread|Client thread|main)"/i,
/\[(?:Render thread|Client thread|main)\/FATAL\]/i,
];
function createProcessWatchdog(options) {
const graceMs = options.graceMs || DEFAULT_GRACE_MS;
const schedule = options.setTimer || setTimeout;
const cancel = options.clearTimer || clearTimeout;
const buffers = { stdout: '', stderr: '' };
let timer = null;
let armed = false;
let crashed = false;
let ended = false;
let forced = false;
// bleibt true, sobald einmal ein regulärer Shutdown ("Stopping!") gesehen
// wurde im Gegensatz zu `armed` wird das NICHT durch markCrash()
// zurückgesetzt, damit ein später vom internen Watchdog erzwungener Halt
// weiterhin als "war ein regulärer Shutdown" erkennbar bleibt
let sawCleanShutdown = false;
function clear() {
if (timer !== null) cancel(timer);
timer = null;
armed = false;
}
function markCrash() {
crashed = true;
clear();
}
function scheduleKill() {
if (timer !== null) cancel(timer);
timer = schedule(() => {
timer = null;
if (ended || crashed || !armed || !options.isRunning()) return;
try {
forced = options.kill() === true;
} catch {
forced = false;
}
if (forced && options.onForce) options.onForce(graceMs);
else if (!forced && options.isRunning() && options.onError) options.onError();
}, graceMs);
if (timer && typeof timer.unref === 'function') timer.unref();
}
function inspectLine(line) {
const isCleanShutdown = CLEAN_SHUTDOWN.test(line);
if (isCleanShutdown) sawCleanShutdown = true;
if (FATAL_OUTPUT.some((pattern) => pattern.test(line))) {
markCrash();
return;
}
if (ended || crashed || armed || !isCleanShutdown) return;
armed = true;
scheduleKill();
}
function feed(chunk, stream) {
if (ended) return;
const key = stream === 'stderr' ? 'stderr' : 'stdout';
const text = buffers[key] + String(chunk || '');
const lines = text.split(/\r?\n/);
buffers[key] = lines.pop() || '';
for (const line of lines) inspectLine(line);
if (armed && !crashed && !ended && lines.length) scheduleKill();
}
function end() {
ended = true;
clear();
}
return {
feed,
end,
get armed() { return armed; },
get crashed() { return crashed; },
get forced() { return forced; },
get sawCleanShutdown() { return sawCleanShutdown; },
};
}
module.exports = {
DEFAULT_GRACE_MS,
CLEAN_SHUTDOWN,
FATAL_OUTPUT,
createProcessWatchdog,
};

View File

@@ -2227,7 +2227,8 @@ async function zeigeAbsturzbericht(instanzId) {
const zeile = (titel, wert) => wert
? `<div class="crash-zeile"><div class="crash-titel">${titel}</div><div class="crash-wert">${escapeHtml(wert)}</div></div>`
: '';
let html = zeile('Beschreibung', res.beschreibung) + zeile('Fehler', res.fehler)
let html = zeile('Diagnose', res.diagnose) + zeile('Empfohlene Lösung', res.empfehlung)
+ zeile('Beschreibung', res.beschreibung) + zeile('Fehler', res.fehler)
+ zeile('Verdächtige Mods', res.verdaechtigeMods);
if (res.verlauf && res.verlauf.length) {
html += `<div class="crash-zeile"><div class="crash-titel">Aufrufverlauf</div>`
@@ -2246,7 +2247,17 @@ function onLaunchEvt(evt) {
status('Minecraft beendet (Code ' + evt.code + ')');
el('status-right').textContent = '';
appendConsole('\n[Prozess beendet, Code ' + evt.code + ']\n');
const crashed = evt.code !== 0 && evt.code !== null;
// War vorher "Stopping!" im Log (regulärer Client-Shutdown), zählt ein
// nachträglicher harter Exit-Code nicht als Absturz während des Spiels
// z. B. wenn Minecrafts interner Shutdown-Watchdog wegen hängender
// Mod-Threads (z. B. JourneyMap WebMap) die JVM nach Ablauf einer
// Wartezeit zwangsweise beendet. Der Bericht bleibt trotzdem gespeichert.
const crashed = evt.code !== 0 && evt.code !== null && !evt.cleanShutdown;
if (evt.cleanShutdown && evt.code !== 0 && evt.code !== null) {
appendConsole('[Hinweis] Der Client wurde regulär beendet (Stopping!), aber danach vom '
+ 'internen Shutdown-Watchdog hart terminiert (z. B. durch einen Mod, dessen Threads '
+ 'nicht rechtzeitig beenden etwa JourneyMap WebMap). Kein Absturz während des Spiels.\n');
}
// Absturzbericht suchen, bevor über die Konsole entschieden wird
// ein Bericht sagt mehr als der Java-Auswurf
if (crashed) zeigeAbsturzbericht(evt.id);