From c7853f658889599af51ae93800d261b30643f5cc Mon Sep 17 00:00:00 2001 From: maj00r Date: Sat, 29 Aug 2026 16:42:36 +0200 Subject: [PATCH] Leave shaders/tonemapping.glsl alone and validate the sim executable Shaders: drop ApplyHdrShader and FindBundled. The Pascal starter only copied starter/Reinhard.glsl over shaders/tonemapping.glsl when the user picked an operator in the HDR combo (uSettings.pas ChangeHDR, its only caller being cbHDRChange), never on an ordinary save. We ran it from SaveTo, so every save replaced the file the game ships. That file has been tracked in the game repo since 2020 and now defaults to AgX, so overwriting it with a 2021-vintage Reinhard was a two-generation downgrade that also lost the NaN/Inf sanitize. Also comments out gfx.postfx.tonemapping on save; the engine has no such key. Executable: ResolveExecutable now validates its pick (file exists, has an execute bit outside Windows, and the MZ/ELF header matches the running OS) and returns the first candidate that passes instead of the first that matches by name. .exe is no longer a Linux candidate - a Steam install with the native binary still zipped would otherwise resolve to the Windows PE. On failure the problem reaches the caller, and MainWindow shows it instead of swallowing the exception, so a launch that does nothing is no longer silent. A manual pick whose file is temporarily missing is kept in the combo rather than falling back to auto and being overwritten on the next save. Claude-Session: https://claude.ai/code/session_01HamVNWE9mwmz1vs2iEpNQq --- StarterNG/Classes/Settings.cs | 123 +++++++++++++++++++----------- StarterNG/MainWindow.axaml.cs | 25 +++++- StarterNG/Views/MessageBox.cs | 12 ++- StarterNG/Views/Settings.axaml.cs | 17 ++++- StarterNG/startercfg/lang/cz.xml | 5 ++ StarterNG/startercfg/lang/en.xml | 5 ++ StarterNG/startercfg/lang/hu.xml | 5 ++ StarterNG/startercfg/lang/pl.xml | 5 ++ StarterNG/startercfg/lang/ru.xml | 5 ++ 9 files changed, 148 insertions(+), 54 deletions(-) diff --git a/StarterNG/Classes/Settings.cs b/StarterNG/Classes/Settings.cs index 477399b..f5a6528 100644 --- a/StarterNG/Classes/Settings.cs +++ b/StarterNG/Classes/Settings.cs @@ -9,6 +9,8 @@ namespace StarterNG.Classes; public enum BatteryDefault { Default = 0, AlwaysOff = 1, AlwaysOn = 2 } +public enum ExeProblem { None, NotFound, NotExecutable, WrongPlatform } + public sealed class Settings { public static Settings Instance { get; } = new(); @@ -114,7 +116,6 @@ public sealed class Settings public bool DrivableOnly = true; public bool ShowArchivalSceneries = true; public bool HideArchivalVehicles = true; - public string LastScenery = ""; public static readonly string[] RenderEngines = @@ -159,33 +160,87 @@ public sealed class Settings public static string UserConfigDirectory() => Path.GetDirectoryName(UserConfigPath()) ?? AppContext.BaseDirectory; - public string ResolveExecutable() + public string ResolveExecutable() => ResolveExecutable(out _); + + public string ResolveExecutable(out ExeProblem problem) { if (!SelectExeAutomatically && !string.IsNullOrWhiteSpace(ExecutablePath)) + { + problem = ValidateExecutable(FullPath(ExecutablePath)); return ExecutablePath; + } string canonical = OperatingSystem.IsWindows() ? "eu07.exe" : "eu07"; + string? fallback = null; try { - string? best = null; - foreach (string path in Directory.GetFiles(Directory.GetCurrentDirectory(), "eu07*")) + var candidates = Directory.GetFiles(Directory.GetCurrentDirectory(), "eu07*") + .Where(IsExecutableCandidate) + .OrderBy(p => string.Equals(Path.GetFileName(p), canonical, StringComparison.OrdinalIgnoreCase) ? 0 : 1) + .ThenBy(p => Path.GetFileName(p)!.Length); + + foreach (string path in candidates) { - if (!IsExecutableCandidate(path)) - continue; - string file = Path.GetFileName(path); - if (string.Equals(file, canonical, StringComparison.OrdinalIgnoreCase)) + if (ValidateExecutable(path) == ExeProblem.None) + { + problem = ExeProblem.None; return path; - if (best == null || file.Length < Path.GetFileName(best).Length) - best = path; + } + fallback ??= path; } - if (best != null) - return best; } catch { } + if (fallback != null) + { + problem = ValidateExecutable(fallback); + return fallback; + } + + problem = ExeProblem.NotFound; return canonical; } + private static string FullPath(string path) => + Path.IsPathRooted(path) ? path : Path.Combine(Directory.GetCurrentDirectory(), path); + + public static ExeProblem ValidateExecutable(string path) + { + try + { + if (!File.Exists(path)) + return ExeProblem.NotFound; + + if (!OperatingSystem.IsWindows()) + { + const UnixFileMode anyExecute = + UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; + if ((File.GetUnixFileMode(path) & anyExecute) == 0) + return ExeProblem.NotExecutable; + } + + byte[] head = new byte[4]; + using (var fs = File.OpenRead(path)) + { + if (fs.Read(head, 0, 4) == 4) + { + bool pe = head[0] == 0x4D && head[1] == 0x5A; + bool elf = head[0] == 0x7F && head[1] == 0x45 && head[2] == 0x4C && head[3] == 0x46; + if (OperatingSystem.IsWindows() && elf) + return ExeProblem.WrongPlatform; + if (OperatingSystem.IsLinux() && pe) + return ExeProblem.WrongPlatform; + } + } + + return ExeProblem.None; + } + catch + { + return ExeProblem.NotFound; + } + } + public static List ListCandidateExecutables(string? directory = null) { directory ??= Directory.GetCurrentDirectory(); @@ -208,6 +263,8 @@ public sealed class Settings return false; if (OperatingSystem.IsWindows()) return ext == ".exe"; + if (OperatingSystem.IsLinux()) + return ext.Length == 0 || ext is ".x86_64" or ".run" or ".appimage"; return ext.Length == 0 || ext is ".exe" or ".x86_64" or ".run" or ".appimage"; } @@ -270,7 +327,6 @@ public sealed class Settings { WriteToConfig(); ApplyFramebufferFidelity(); - ApplyHdrShader(); try { @@ -331,39 +387,6 @@ public sealed class Settings _config.Remove("gfx.framebuffer.fidelity"); } - public void ApplyHdrShader() - { - - if (RenderEngine is not (0 or 1 or 5)) - return; - - string? src = FindBundled("Reinhard.glsl"); - if (src is null) return; - - try - { - string destDir = Path.Combine(Directory.GetCurrentDirectory(), "shaders"); - Directory.CreateDirectory(destDir); - File.Copy(src, Path.Combine(destDir, "tonemapping.glsl"), overwrite: true); - } - catch { } - } - - private static string? FindBundled(string fileName) - { - foreach (string dir in new[] - { - Path.Combine(Directory.GetCurrentDirectory(), "starter"), - Path.Combine(AppContext.BaseDirectory, "startercfg"), - Path.Combine(Directory.GetCurrentDirectory(), "startercfg") - }) - { - string path = Path.Combine(dir, fileName); - if (File.Exists(path)) return path; - } - return null; - } - public void DumpMissingVehicleLog() { if (!LogMissingVehicleFiles) return; @@ -506,10 +529,18 @@ public sealed class Settings LastScenery = c.GetString("starter.last.scenery", ""); } + private static readonly string[] ObsoleteKeys = + { + "gfx.postfx.tonemapping", + }; + private void WriteToConfig() { var c = _config; + foreach (string key in ObsoleteKeys) + c.Remove(key); + c.Set("lang", Language == "Polski" ? "pl" : "en"); c.SetBool("fullscreen", Fullscreen); c.SetBool("inactivepause", PauseWhenInactive); diff --git a/StarterNG/MainWindow.axaml.cs b/StarterNG/MainWindow.axaml.cs index 0ea06b4..8907906 100644 --- a/StarterNG/MainWindow.axaml.cs +++ b/StarterNG/MainWindow.axaml.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading; +using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Input; @@ -223,6 +224,18 @@ public partial class MainWindow : Window return trainset.Vehicles.Any(CanStart); } + private Task ShowExeProblem(ExeProblem problem, string exe) + { + string key = problem switch + { + ExeProblem.NotExecutable => "ExeNotExecutable", + ExeProblem.WrongPlatform => "ExeWrongPlatform", + _ => "ExeNotFound" + }; + return MessageBox.Show(this, $"{exe}\n\n{App.Loc[key]}", + App.Loc["ExeLaunchFailed"], MessageBoxButtons.Ok); + } + private async void StartButton_OnClick(object? sender, RoutedEventArgs e) { var scenery = AppState.Instance.CurrentScenery; @@ -271,10 +284,16 @@ public partial class MainWindow : Window SettingsModel.Instance.CaptureAndSave(); + string exe = Path.GetFullPath(SettingsModel.Instance.ResolveExecutable(out var exeProblem)); + if (exeProblem != ExeProblem.None) + { + await ShowExeProblem(exeProblem, exe); + return; + } + Process? sim; try { - string exe = Path.GetFullPath(SettingsModel.Instance.ResolveExecutable()); sim = Process.Start(new ProcessStartInfo { FileName = exe, @@ -283,8 +302,10 @@ public partial class MainWindow : Window UseShellExecute = false }); } - catch + catch (Exception ex) { + await MessageBox.Show(this, $"{exe}\n\n{ex.Message}", + App.Loc["ExeLaunchFailed"], MessageBoxButtons.Ok); return; } diff --git a/StarterNG/Views/MessageBox.cs b/StarterNG/Views/MessageBox.cs index 035a674..ef12289 100644 --- a/StarterNG/Views/MessageBox.cs +++ b/StarterNG/Views/MessageBox.cs @@ -7,13 +7,13 @@ namespace StarterNG.Views; public enum MessageBoxButtons { - YesNo + YesNo, + Ok } public static class MessageBox { - - public static async Task Show(Window owner, string message, string title, MessageBoxButtons _) + public static async Task Show(Window owner, string message, string title, MessageBoxButtons buttons) { var win = new Window { @@ -33,6 +33,12 @@ public static class MessageBox noBtn.Classes.Add("Flat"); noBtn.Click += (_, _) => win.Close(); + if (buttons == MessageBoxButtons.Ok) + { + yesBtn.Content = App.Loc["Ok"]; + noBtn.IsVisible = false; + } + win.Content = new StackPanel { Margin = new Avalonia.Thickness(16), diff --git a/StarterNG/Views/Settings.axaml.cs b/StarterNG/Views/Settings.axaml.cs index 4365f8d..675a524 100644 --- a/StarterNG/Views/Settings.axaml.cs +++ b/StarterNG/Views/Settings.axaml.cs @@ -158,9 +158,20 @@ public partial class Settings : UserControl UartRadioChannelCb.IsChecked = s.UartRadioChannel; UpdateFeedbackDetailVisibility(); - SelectExeCb.SelectedIndex = s.SelectExeAutomatically - ? 0 - : Math.Max(0, FindComboIndexByContent(SelectExeCb, s.ExecutablePath)); + if (s.SelectExeAutomatically) + { + SelectExeCb.SelectedIndex = 0; + } + else + { + int exeIdx = FindComboIndexByContent(SelectExeCb, s.ExecutablePath); + if (exeIdx < 0) + { + SelectExeCb.Items.Add(new ComboBoxItem { Content = s.ExecutablePath }); + exeIdx = SelectExeCb.Items.Count - 1; + } + SelectExeCb.SelectedIndex = exeIdx; + } DebugModeCb.IsChecked = s.DebugMode; VirtualShuntingCb.IsChecked = s.VirtualShunting; LogMissingVehicleFilesCb.IsChecked = s.LogMissingVehicleFiles; diff --git a/StarterNG/startercfg/lang/cz.xml b/StarterNG/startercfg/lang/cz.xml index 8cc2e7a..df5a136 100644 --- a/StarterNG/startercfg/lang/cz.xml +++ b/StarterNG/startercfg/lang/cz.xml @@ -222,6 +222,11 @@ Zrušit Ano Ne + OK + Nepodařilo se spustit simulátor + Spustitelný soubor simulátoru nebyl nalezen. + Soubor není označen jako spustitelný (chmod +x). + Tento binární soubor je pro jiný operační systém. 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 fc8ec32..59beaff 100644 --- a/StarterNG/startercfg/lang/en.xml +++ b/StarterNG/startercfg/lang/en.xml @@ -222,6 +222,11 @@ Cancel Yes No + OK + Could not start the simulator + The simulator executable was not found. + The file is not marked as executable (chmod +x). + This binary is built for a different operating system. 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 0ff8c56..d3dfd6a 100644 --- a/StarterNG/startercfg/lang/hu.xml +++ b/StarterNG/startercfg/lang/hu.xml @@ -222,6 +222,11 @@ Mégse Igen Nem + OK + A szimulátor indítása sikertelen + A szimulátor futtatható fájlja nem található. + A fájl nincs futtathatóként megjelölve (chmod +x). + Ez a bináris másik operációs rendszerhez készült. 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 3bc69cc..3699fea 100644 --- a/StarterNG/startercfg/lang/pl.xml +++ b/StarterNG/startercfg/lang/pl.xml @@ -222,6 +222,11 @@ Anuluj Tak Nie + OK + Nie udało się uruchomić symulatora + Nie znaleziono pliku wykonywalnego symulatora. + Plik nie ma prawa wykonywania (chmod +x). + To jest plik wykonywalny dla innego systemu niż uruchomiony. 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 8fff66c..1cc4f2b 100644 --- a/StarterNG/startercfg/lang/ru.xml +++ b/StarterNG/startercfg/lang/ru.xml @@ -222,6 +222,11 @@ Отмена Да Нет + OK + Не удалось запустить симулятор + Исполняемый файл симулятора не найден. + Файл не помечен как исполняемый (chmod +x). + Этот исполняемый файл собран для другой операционной системы. Информация Настройки Файл eu07.ini изменён вне стартера. Загрузить заново?