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 изменён вне стартера. Загрузить заново?