diff --git a/StarterNG.Tests/SimulatorAftermathTests.cs b/StarterNG.Tests/SimulatorAftermathTests.cs
new file mode 100644
index 0000000..ecbdf93
--- /dev/null
+++ b/StarterNG.Tests/SimulatorAftermathTests.cs
@@ -0,0 +1,39 @@
+using StarterNG.Application;
+using StarterNG.Tests.Fakes;
+
+namespace StarterNG.Tests;
+
+public class SimulatorAftermathTests
+{
+ private static readonly string Build =
+ Path.Combine(Path.GetTempPath(), "maszyna", "cmake-build-diag", "bin", "eu07_2026-10-04_fc195880");
+
+ [Fact]
+ public void Minidumps_of_this_run_are_found_in_the_installation_and_under_crashpad()
+ {
+ var installation = new TestInstallation(new InMemoryFileSystem()
+ .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 aftermath = new SimulatorAftermath(installation.Files, installation.Paths, installation.Log);
+
+ var thisRun = aftermath.CrashDumpsSince(new DateTime(2026, 10, 4, 11, 3, 0, DateTimeKind.Utc), Build);
+ var later = aftermath.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()
+ {
+ var installation = new TestInstallation(new InMemoryFileSystem()
+ .WithFile(TestInstallation.At("log.txt"), "first\r\nsecond\r\n\r\nthird\r\n"));
+
+ var tail = new SimulatorAftermath(installation.Files, installation.Paths, installation.Log).SimulatorLogTail(2);
+
+ Assert.Equal(new[] { "second", "third" }, tail);
+ }
+}
diff --git a/StarterNG.Tests/StartSimulationTests.cs b/StarterNG.Tests/StartSimulationTests.cs
index 9c9d563..2940fa6 100644
--- a/StarterNG.Tests/StartSimulationTests.cs
+++ b/StarterNG.Tests/StartSimulationTests.cs
@@ -68,33 +68,6 @@ 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()
- {
- var (start, _, _) = Rig(files => files
- .WithExecutable(Build)
- .WithFile(TestInstallation.At("log.txt"), "first\r\nsecond\r\n\r\nthird\r\n"));
-
- Assert.Equal(new[] { "second", "third" }, start.SimulatorLogTail(2));
- }
-
[Fact]
public void The_editor_opens_the_scenery_itself_not_an_export()
{
diff --git a/StarterNG/Application/AppServices.cs b/StarterNG/Application/AppServices.cs
index 77fd3f4..a2cb1cc 100644
--- a/StarterNG/Application/AppServices.cs
+++ b/StarterNG/Application/AppServices.cs
@@ -62,6 +62,7 @@ public sealed class AppServices
files, clock, log, settingsPaths.UpdateCheckPath());
StartSimulation = new StartSimulation(State, SettingsStore, Library.Vehicles, Executables, files, paths,
Processes, Random, log);
+ Aftermath = new SimulatorAftermath(files, paths, log);
}
public static AppServices Current =>
@@ -139,5 +140,7 @@ public sealed class AppServices
public StartSimulation StartSimulation { get; }
+ public SimulatorAftermath Aftermath { get; }
+
public LocalizationService Localization { get; }
}
diff --git a/StarterNG/Application/SimulatorAftermath.cs b/StarterNG/Application/SimulatorAftermath.cs
new file mode 100644
index 0000000..90f63c7
--- /dev/null
+++ b/StarterNG/Application/SimulatorAftermath.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using StarterNG.Application.Abstractions;
+using StarterNG.Infrastructure.Adapters;
+
+namespace StarterNG.Application;
+
+/// What a simulator that went down left behind: its log and its crash dumps.
+public sealed class SimulatorAftermath
+{
+ private readonly IFileSystem _files;
+ private readonly IGamePaths _paths;
+ private readonly IDiagnosticsLog _log;
+
+ public SimulatorAftermath(IFileSystem files, IGamePaths paths, IDiagnosticsLog log)
+ {
+ _files = files;
+ _paths = paths;
+ _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)
+ {
+ string path = _paths.FromRoot("log.txt");
+ try
+ {
+ if (!_files.FileExists(path))
+ return Array.Empty();
+
+ var lines = LegacyText.Decode(_files.ReadAllBytes(path))
+ .Split('\n')
+ .Select(line => line.TrimEnd('\r'))
+ .Where(line => line.Trim().Length > 0)
+ .ToList();
+ return lines.Skip(Math.Max(0, lines.Count - count)).ToList();
+ }
+ catch (Exception ex)
+ {
+ _log.Log($"reading {path}", ex);
+ return Array.Empty();
+ }
+ }
+}
diff --git a/StarterNG/Application/StartSimulation.cs b/StarterNG/Application/StartSimulation.cs
index d24d2ac..d8da998 100644
--- a/StarterNG/Application/StartSimulation.cs
+++ b/StarterNG/Application/StartSimulation.cs
@@ -63,64 +63,6 @@ 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)
- {
- string path = _paths.FromRoot("log.txt");
- try
- {
- if (!_files.FileExists(path))
- return Array.Empty();
-
- var lines = LegacyText.Decode(_files.ReadAllBytes(path))
- .Split('\n')
- .Select(line => line.TrimEnd('\r'))
- .Where(line => line.Trim().Length > 0)
- .ToList();
- return lines.Skip(Math.Max(0, lines.Count - count)).ToList();
- }
- catch (Exception ex)
- {
- _log.Log($"reading {path}", ex);
- return Array.Empty();
- }
- }
-
public static string? StartableVehicle(Trainset? trainset, string? preferred)
{
if (trainset is null || trainset.Vehicles.Count == 0)
diff --git a/StarterNG/MainWindow.axaml.cs b/StarterNG/MainWindow.axaml.cs
index 6bb4895..2cc5225 100644
--- a/StarterNG/MainWindow.axaml.cs
+++ b/StarterNG/MainWindow.axaml.cs
@@ -597,7 +597,7 @@ public partial class MainWindow : Window
var words = exit.CleanErrors.Count > 0
? exit.CleanErrors
- : AppServices.Current.StartSimulation.SimulatorLogTail(12);
+ : AppServices.Current.Aftermath.SimulatorLogTail(12);
var message = new StringBuilder();
message.AppendLine(string.Format(App.Loc["SimExitCode"], exit.DescribeCode()));
@@ -611,7 +611,7 @@ public partial class MainWindow : Window
}
message.AppendLine().Append(App.Loc["SimExitSeeLog"]);
- string? dump = AppServices.Current.StartSimulation.CrashDumpsSince(startedUtc, executable).FirstOrDefault();
+ string? dump = AppServices.Current.Aftermath.CrashDumpsSince(startedUtc, executable).FirstOrDefault();
if (dump is not null)
message.AppendLine().AppendLine().AppendLine(string.Format(App.Loc["SimExitDump"], dump))
.Append(App.Loc["SimExitOpenDump"]);