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
This commit is contained in:
maj00r
2026-08-29 16:42:36 +02:00
parent 3b7a8135ea
commit c7853f6588
9 changed files with 148 additions and 54 deletions

View File

@@ -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<string> 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);

View File

@@ -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<bool> 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;
}

View File

@@ -7,13 +7,13 @@ namespace StarterNG.Views;
public enum MessageBoxButtons
{
YesNo
YesNo,
Ok
}
public static class MessageBox
{
public static async Task<bool> Show(Window owner, string message, string title, MessageBoxButtons _)
public static async Task<bool> 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),

View File

@@ -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;

View File

@@ -222,6 +222,11 @@
<String key="Cancel">Zrušit</String>
<String key="Yes">Ano</String>
<String key="No">Ne</String>
<String key="Ok">OK</String>
<String key="ExeLaunchFailed">Nepodařilo se spustit simulátor</String>
<String key="ExeNotFound">Spustitelný soubor simulátoru nebyl nalezen.</String>
<String key="ExeNotExecutable">Soubor není označen jako spustitelný (chmod +x).</String>
<String key="ExeWrongPlatform">Tento binární soubor je pro jiný operační systém.</String>
<String key="TabInfo">Informace</String>
<String key="SettingsFileChangedTitle">Nastavení</String>
<String key="SettingsFileChanged">Soubor eu07.ini byl změněn mimo starter. Načíst jej znovu?</String>

View File

@@ -222,6 +222,11 @@
<String key="Cancel">Cancel</String>
<String key="Yes">Yes</String>
<String key="No">No</String>
<String key="Ok">OK</String>
<String key="ExeLaunchFailed">Could not start the simulator</String>
<String key="ExeNotFound">The simulator executable was not found.</String>
<String key="ExeNotExecutable">The file is not marked as executable (chmod +x).</String>
<String key="ExeWrongPlatform">This binary is built for a different operating system.</String>
<String key="TabInfo">Info</String>
<String key="SettingsFileChangedTitle">Settings</String>
<String key="SettingsFileChanged">eu07.ini was changed outside the starter. Reload it?</String>

View File

@@ -222,6 +222,11 @@
<String key="Cancel">Mégse</String>
<String key="Yes">Igen</String>
<String key="No">Nem</String>
<String key="Ok">OK</String>
<String key="ExeLaunchFailed">A szimulátor indítása sikertelen</String>
<String key="ExeNotFound">A szimulátor futtatható fájlja nem található.</String>
<String key="ExeNotExecutable">A fájl nincs futtathatóként megjelölve (chmod +x).</String>
<String key="ExeWrongPlatform">Ez a bináris másik operációs rendszerhez készült.</String>
<String key="TabInfo">Információ</String>
<String key="SettingsFileChangedTitle">Beállítások</String>
<String key="SettingsFileChanged">Az eu07.ini fájl a starteren kívül módosult. Újratölti?</String>

View File

@@ -222,6 +222,11 @@
<String key="Cancel">Anuluj</String>
<String key="Yes">Tak</String>
<String key="No">Nie</String>
<String key="Ok">OK</String>
<String key="ExeLaunchFailed">Nie udało się uruchomić symulatora</String>
<String key="ExeNotFound">Nie znaleziono pliku wykonywalnego symulatora.</String>
<String key="ExeNotExecutable">Plik nie ma prawa wykonywania (chmod +x).</String>
<String key="ExeWrongPlatform">To jest plik wykonywalny dla innego systemu niż uruchomiony.</String>
<String key="TabInfo">Informacje</String>
<String key="SettingsFileChangedTitle">Ustawienia</String>
<String key="SettingsFileChanged">Plik eu07.ini został zmieniony poza starterem. Wczytać ponownie?</String>

View File

@@ -222,6 +222,11 @@
<String key="Cancel">Отмена</String>
<String key="Yes">Да</String>
<String key="No">Нет</String>
<String key="Ok">OK</String>
<String key="ExeLaunchFailed">Не удалось запустить симулятор</String>
<String key="ExeNotFound">Исполняемый файл симулятора не найден.</String>
<String key="ExeNotExecutable">Файл не помечен как исполняемый (chmod +x).</String>
<String key="ExeWrongPlatform">Этот исполняемый файл собран для другой операционной системы.</String>
<String key="TabInfo">Информация</String>
<String key="SettingsFileChangedTitle">Настройки</String>
<String key="SettingsFileChanged">Файл eu07.ini изменён вне стартера. Загрузить заново?</String>