From 5e130fcd9866d978163b78b04cfacba09b8da84d Mon Sep 17 00:00:00 2001 From: maj00r Date: Sun, 4 Oct 2026 13:44:47 +0200 Subject: [PATCH] Point the crash report at the minidump the simulator left The simulator writes crash_*.dmp relative to its working directory, and crashpad under crashdumps; the newest one from this run is named, with an offer to open its folder. --- StarterNG.Tests/StartSimulationTests.cs | 17 ++++++++++++ StarterNG/Application/StartSimulation.cs | 35 ++++++++++++++++++++++++ StarterNG/MainWindow.axaml.cs | 22 +++++++++++---- StarterNG/startercfg/lang/cz.xml | 2 ++ StarterNG/startercfg/lang/en.xml | 2 ++ StarterNG/startercfg/lang/hu.xml | 2 ++ StarterNG/startercfg/lang/pl.xml | 2 ++ StarterNG/startercfg/lang/ru.xml | 2 ++ 8 files changed, 78 insertions(+), 6 deletions(-) diff --git a/StarterNG.Tests/StartSimulationTests.cs b/StarterNG.Tests/StartSimulationTests.cs index e21f256..744c5f7 100644 --- a/StarterNG.Tests/StartSimulationTests.cs +++ b/StarterNG.Tests/StartSimulationTests.cs @@ -68,6 +68,23 @@ public class StartSimulationTests Assert.False(launcher.CapturedErrors); } + [Fact] + public void Minidumps_of_this_run_are_found_in_the_installation_and_under_crashpad() + { + var (start, _, installation) = Rig(files => files + .WithExecutable(Build) + .WithFile(TestInstallation.At("crash_2026-10-04_13-03-12.dmp"), "MDMP") + .WithFile(TestInstallation.At("crashdumps", "reports", "1a2b.dmp"), "MDMP")); + installation.Files.LastWriteTimeUtc = new DateTime(2026, 10, 4, 11, 3, 12, DateTimeKind.Utc); + + var thisRun = start.CrashDumpsSince(new DateTime(2026, 10, 4, 11, 3, 0, DateTimeKind.Utc), Build); + var later = start.CrashDumpsSince(new DateTime(2026, 10, 4, 12, 0, 0, DateTimeKind.Utc), Build); + + Assert.Equal(2, thisRun.Count); + Assert.Contains(thisRun, dump => dump.EndsWith("crash_2026-10-04_13-03-12.dmp")); + Assert.Empty(later); + } + [Fact] public void The_end_of_the_simulator_log_is_read_for_a_silent_crash() { diff --git a/StarterNG/Application/StartSimulation.cs b/StarterNG/Application/StartSimulation.cs index 9678958..b1a25c0 100644 --- a/StarterNG/Application/StartSimulation.cs +++ b/StarterNG/Application/StartSimulation.cs @@ -63,6 +63,41 @@ public sealed class StartSimulation _log = log; } + /// + /// Minidumps written since the simulator was started, newest first. The + /// simulator names them crash_*.dmp relative to its working directory - the + /// installation - and crashpad, where it is set up, keeps its own under + /// crashdumps; the folder of the binary is looked at too, for a simulator that + /// was once run from there. + /// + public IReadOnlyList CrashDumpsSince(DateTime startedUtc, string executable) + { + var dumps = new List(); + try + { + foreach (string directory in new[] { _paths.Root, Path.GetDirectoryName(executable) ?? _paths.Root } + .Distinct(StringComparer.OrdinalIgnoreCase) + .Where(_files.DirectoryExists)) + dumps.AddRange(_files.GetFiles(directory, "*.dmp")); + + string crashpad = _paths.FromRoot("crashdumps"); + if (_files.DirectoryExists(crashpad)) + dumps.AddRange(_files.GetFilesRecursive(crashpad, "*.dmp")); + + // A little slack: the start is taken just after the launch, not before it. + var since = startedUtc - TimeSpan.FromSeconds(5); + return dumps.Distinct(StringComparer.OrdinalIgnoreCase) + .Where(dump => _files.GetLastWriteTimeUtc(dump) >= since) + .OrderByDescending(_files.GetLastWriteTimeUtc) + .ToList(); + } + catch (Exception ex) + { + _log.Log("looking for crash dumps", ex); + return Array.Empty(); + } + } + /// The end of the simulator's own log, for when it died without a word on its error stream. public IReadOnlyList SimulatorLogTail(int count) { diff --git a/StarterNG/MainWindow.axaml.cs b/StarterNG/MainWindow.axaml.cs index f9f2972..b1ddeec 100644 --- a/StarterNG/MainWindow.axaml.cs +++ b/StarterNG/MainWindow.axaml.cs @@ -522,14 +522,14 @@ public partial class MainWindow : Window GC.Collect(); GC.WaitForPendingFinalizers(); - WatchSimulator(result.Process); + WatchSimulator(result.Process, result.ExecutablePath); } /// /// Brings the starter back when the simulator exits. Adopts an already /// running simulator when we did not start it ourselves. /// - private void WatchSimulator(IProcessHandle? simulator) + private void WatchSimulator(IProcessHandle? simulator, string executable) { var started = DateTime.UtcNow; simulator ??= AppServices.Current.Processes.FindRunning( @@ -544,7 +544,7 @@ public partial class MainWindow : Window _ = simulator.WaitForExitAsync().ContinueWith(_ => Dispatcher.UIThread.Post(() => { RestoreFromSimulator(); - ReportIfCrashed(simulator, DateTime.UtcNow - started); + ReportIfCrashed(simulator, executable, started); }), TaskScheduler.Default); } @@ -552,13 +552,15 @@ public partial class MainWindow : Window /// Says why the simulator is gone when it did not end the way a session does: /// an error code, or a close so soon after the start that nobody played - a /// window that only flashed. Its own words come from the error stream, or from - /// its log when that stayed silent. + /// its log when that stayed silent, and a minidump it left is pointed at. /// - private async void ReportIfCrashed(IProcessHandle simulator, TimeSpan runTime) + private async void ReportIfCrashed(IProcessHandle simulator, string executable, DateTime startedUtc) { if (simulator.ExitCode is not { } code) return; + var runTime = DateTime.UtcNow - startedUtc; + var exit = new SimulatorExit(code, runTime, simulator.ErrorTail); if (!exit.Failed) return; @@ -579,8 +581,16 @@ public partial class MainWindow : Window } message.AppendLine().Append(App.Loc["SimExitSeeLog"]); + string? dump = AppServices.Current.StartSimulation.CrashDumpsSince(startedUtc, executable).FirstOrDefault(); + if (dump is not null) + message.AppendLine().AppendLine().AppendLine(string.Format(App.Loc["SimExitDump"], dump)) + .Append(App.Loc["SimExitOpenDump"]); + Diagnostics.Log(message.ToString().Replace(Environment.NewLine, " | ")); - await MessageBox.Show(this, message.ToString(), App.Loc["SimExitTitle"], MessageBoxButtons.Ok); + bool open = await MessageBox.Show(this, message.ToString(), App.Loc["SimExitTitle"], + dump is null ? MessageBoxButtons.Ok : MessageBoxButtons.YesNo); + if (open && dump is not null && Path.GetDirectoryName(Path.GetFullPath(dump)) is { } folder) + AppServices.Current.Processes.OpenInShell(folder); } private async void CheckExternalSettings() diff --git a/StarterNG/startercfg/lang/cz.xml b/StarterNG/startercfg/lang/cz.xml index e6a40bf..f40ec0c 100644 --- a/StarterNG/startercfg/lang/cz.xml +++ b/StarterNG/startercfg/lang/cz.xml @@ -258,6 +258,8 @@ Ukončil se {0:0.0} s po spuštění. Poslední zprávy: Celý průběh je v souboru log.txt ve složce simulátoru. + Simulátor uložil výpis paměti: {0} + Otevřít složku s výpisem? Přiložte jej k hlášení chyby. Informace Nastavení Soubor eu07.ini byl změněn mimo starter. Načíst jej znovu? diff --git a/StarterNG/startercfg/lang/en.xml b/StarterNG/startercfg/lang/en.xml index 3451d1f..305b53b 100644 --- a/StarterNG/startercfg/lang/en.xml +++ b/StarterNG/startercfg/lang/en.xml @@ -258,6 +258,8 @@ It closed {0:0.0} s after starting. Last messages: The full run is in log.txt in the simulator folder. + The simulator saved a memory dump: {0} + Open the folder with the dump? Attach it to the bug report. Info Settings eu07.ini was changed outside the starter. Reload it? diff --git a/StarterNG/startercfg/lang/hu.xml b/StarterNG/startercfg/lang/hu.xml index f9ba872..eccaad0 100644 --- a/StarterNG/startercfg/lang/hu.xml +++ b/StarterNG/startercfg/lang/hu.xml @@ -258,6 +258,8 @@ {0:0.0} másodperccel az indítás után bezárult. Utolsó üzenetek: A teljes futás a szimulátor mappájában lévő log.txt fájlban található. + A szimulátor memóriaképet mentett: {0} + Megnyitod a memóriaképet tartalmazó mappát? Csatold a hibajelentéshez. Információ Beállítások Az eu07.ini fájl a starteren kívül módosult. Újratölti? diff --git a/StarterNG/startercfg/lang/pl.xml b/StarterNG/startercfg/lang/pl.xml index 5afc1d1..7168818 100644 --- a/StarterNG/startercfg/lang/pl.xml +++ b/StarterNG/startercfg/lang/pl.xml @@ -258,6 +258,8 @@ Zamknął się {0:0.0} s po uruchomieniu. Ostatnie komunikaty: Pełny przebieg jest w pliku log.txt w katalogu symulatora. + Symulator zapisał zrzut pamięci: {0} + Otworzyć folder ze zrzutem? Dołącz go do zgłoszenia błędu. Informacje Ustawienia Plik eu07.ini został zmieniony poza starterem. Wczytać ponownie? diff --git a/StarterNG/startercfg/lang/ru.xml b/StarterNG/startercfg/lang/ru.xml index f9de835..333c9e7 100644 --- a/StarterNG/startercfg/lang/ru.xml +++ b/StarterNG/startercfg/lang/ru.xml @@ -258,6 +258,8 @@ Он закрылся через {0:0.0} с после запуска. Последние сообщения: Полный журнал находится в файле log.txt в папке симулятора. + Симулятор сохранил дамп памяти: {0} + Открыть папку с дампом? Приложите его к сообщению об ошибке. Информация Настройки Файл eu07.ini изменён вне стартера. Загрузить заново?