diff --git a/patches/0013-Explain-what-went-wrong-when-the-game-crashed.patch b/patches/0013-Explain-what-went-wrong-when-the-game-crashed.patch new file mode 100644 index 0000000..8779bd4 --- /dev/null +++ b/patches/0013-Explain-what-went-wrong-when-the-game-crashed.patch @@ -0,0 +1,2087 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Modrinth Enhanced +Date: Tue, 15 Sep 2026 09:42:35 +0200 +Subject: [PATCH] Explain what went wrong when the game crashed + +The Logs tab now says what a crash was, without a connection. After a +run ends, the launcher reads the game's crash report, the JVM's own +hs_err file and the end of the log, and matches them against rules for +the usual causes: out of memory, the wrong Java version, missing or +duplicate mods, mods for another game version, failed mixins, a +software renderer and more. Each finding gets a plain explanation, the +line it was found on, and where it helps a button to the Java settings +or the instance folder. Picking an older log from the list runs the same +rules against it. Files from an earlier run than the log are ignored, +so an old crash report is not blamed for a new crash. + +The rules report an id and what they captured; the words are in the +frontend, so they can be translated. mclo.gs analysis in the console is +unchanged. + +Adapted from Noctrinth (https://github.com/Everelsu/Noctrinth). +--- + .../src/components/ui/CrashDiagnosis.vue | 641 ++++++++++ + .../src/helpers/crash-analysis.ts | 84 ++ + .../src/pages/instance/logs/index.vue | 44 +- + apps/app/build.rs | 11 + + apps/app/capabilities/plugins.json | 3 +- + apps/app/src/api/crash_analysis.rs | 37 + + apps/app/src/api/mod.rs | 1 + + apps/app/src/main.rs | 1 + + packages/app-lib/src/api/crash_analysis.rs | 1087 +++++++++++++++++ + packages/app-lib/src/api/mod.rs | 1 + + 10 files changed, 1908 insertions(+), 2 deletions(-) + create mode 100644 apps/app-frontend/src/components/ui/CrashDiagnosis.vue + create mode 100644 apps/app-frontend/src/helpers/crash-analysis.ts + create mode 100644 apps/app/src/api/crash_analysis.rs + create mode 100644 packages/app-lib/src/api/crash_analysis.rs + +diff --git a/apps/app-frontend/src/components/ui/CrashDiagnosis.vue b/apps/app-frontend/src/components/ui/CrashDiagnosis.vue +new file mode 100644 +index 0000000..26b04eb +--- /dev/null ++++ b/apps/app-frontend/src/components/ui/CrashDiagnosis.vue +@@ -0,0 +1,641 @@ ++ ++ ++ +diff --git a/apps/app-frontend/src/helpers/crash-analysis.ts b/apps/app-frontend/src/helpers/crash-analysis.ts +new file mode 100644 +index 0000000..1c8a37f +--- /dev/null ++++ b/apps/app-frontend/src/helpers/crash-analysis.ts +@@ -0,0 +1,84 @@ ++/** ++ * The launcher's own reading of a crash. ++ * ++ * Upstream sends the live console to mclo.gs and shows what it makes of it, ++ * which is the better answer for anything unusual — and no answer at all ++ * without a connection, or for the crash report and the JVM's own error file, ++ * which is where the cause is usually written in plain sight. ++ * ++ * This is the local half: rules that run against those files as well, on this ++ * machine, and fixes that are pages of this launcher rather than advice. See ++ * `packages/app-lib/src/api/crash_analysis.rs` for why the words a player reads ++ * are not in the Rust. ++ */ ++import { invoke } from '@tauri-apps/api/core' ++ ++export type CrashSeverity = 'note' | 'warning' | 'critical' ++export type CrashSourceKind = 'crash_report' | 'log' | 'jvm_error' ++ ++export interface CrashFinding { ++ rule: string ++ severity: CrashSeverity ++ source: CrashSourceKind ++ source_name: string ++ evidence: string ++ values: Record ++} ++ ++export interface CrashSourceFile { ++ kind: CrashSourceKind ++ name: string ++ modified: number ++} ++ ++export interface CrashDiagnosis { ++ findings: CrashFinding[] ++ sources: CrashSourceFile[] ++} ++ ++const EMPTY: CrashDiagnosis = { findings: [], sources: [] } ++ ++/** Reads the files the last run left behind and reports what it recognised. */ ++export async function analyzeInstanceCrash(instanceId: string): Promise { ++ try { ++ return await invoke('plugin:crash-analysis|crash_analyze_instance', { ++ instanceId, ++ }) ++ } catch (error) { ++ // A diagnosis that cannot be made is not worth a notification: the log ++ // the player came here to read is on screen either way. ++ console.warn('Could not read the crash files for a diagnosis', error) ++ return EMPTY ++ } ++} ++ ++/** The same rules, against text already in hand. */ ++export async function analyzeCrashText( ++ text: string, ++ kind: CrashSourceKind, ++ sourceName: string, ++): Promise { ++ if (!text.trim()) return EMPTY ++ ++ try { ++ return await invoke('plugin:crash-analysis|crash_analyze_text', { ++ text, ++ kind, ++ sourceName, ++ }) ++ } catch (error) { ++ console.warn('Could not analyse the log', error) ++ return EMPTY ++ } ++} ++ ++/** Which of two diagnoses to lead with, worst first. */ ++const SEVERITY_ORDER: Record = { ++ critical: 0, ++ warning: 1, ++ note: 2, ++} ++ ++export function sortFindings(findings: CrashFinding[]): CrashFinding[] { ++ return [...findings].sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]) ++} +diff --git a/apps/app-frontend/src/pages/instance/logs/index.vue b/apps/app-frontend/src/pages/instance/logs/index.vue +index e2a59fd..4fcddea 100644 +--- a/apps/app-frontend/src/pages/instance/logs/index.vue ++++ b/apps/app-frontend/src/pages/instance/logs/index.vue +@@ -1,5 +1,16 @@ +