Let the simulator be picked from outside the installation

A build tree kept beside the data can be chosen in the settings, and when no
simulator is found the starter asks for one instead of calling the
installation broken. The simulator now runs in the installation, not in the
folder of its binary, and a binary for the other system is no longer refused.
This commit is contained in:
maj00r
2026-10-04 13:27:28 +02:00
parent 2b3c46938c
commit 3083523ceb
17 changed files with 320 additions and 49 deletions

View File

@@ -40,4 +40,38 @@ public class InstallationCheckTests
Assert.DoesNotContain(faults, fault => fault.StartsWith("missing "));
Assert.DoesNotContain("FaultNoWeights", faults);
}
[Fact]
public void A_missing_simulator_is_asked_for_rather_than_called_a_broken_installation()
{
// The simulator may be a build kept elsewhere, or a Windows build for Wine.
var installation = new TestInstallation();
var check = Check(installation);
Assert.DoesNotContain("FaultNoExe", check.Run());
Assert.True(check.ExecutableMissing());
}
[Fact]
public void A_simulator_in_the_installation_is_not_missing()
{
var installation = new TestInstallation(new InMemoryFileSystem().WithExecutable(TestInstallation.At("eu07")));
Assert.False(Check(installation).ExecutableMissing());
}
[Fact]
public void A_simulator_picked_from_elsewhere_is_not_missing()
{
string build = Path.Combine(Path.GetTempPath(), "maszyna", "bin", "eu07_2026-10-04_fc195880");
var installation = new TestInstallation(new InMemoryFileSystem().WithExecutable(build));
installation.SettingsStore.Settings.SelectExeAutomatically = false;
installation.SettingsStore.Settings.ExecutablePath = build;
Assert.False(Check(installation).ExecutableMissing());
}
private static InstallationCheck Check(TestInstallation installation) =>
new(installation.Paths, installation.Files, installation.Strings, installation.Physics, installation.Library,
installation.SettingsStore);
}

View File

@@ -0,0 +1,105 @@
using StarterNG.Application;
using StarterNG.Application.Abstractions;
using StarterNG.Infrastructure.Sceneries;
using StarterNG.Tests.Fakes;
namespace StarterNG.Tests;
public class StartSimulationTests
{
// A simulator built from source: its own tree, away from the game data.
private static readonly string Build =
Path.Combine(Path.GetTempPath(), "maszyna", "cmake-build-diag", "bin", "eu07_2026-10-04_fc195880");
[Fact]
public void A_simulator_from_a_build_tree_runs_in_the_installation()
{
var (start, launcher, installation) = Rig(files => files.WithExecutable(Build));
var result = start.Execute(freeFly: true, saveSettings: false);
Assert.Equal(SimulationStartOutcome.Started, result.Outcome);
Assert.Equal(Path.GetFullPath(Build), launcher.Executable);
Assert.Equal(installation.Paths.Root, launcher.WorkingDirectory);
}
[Fact]
public void A_build_for_the_other_system_is_still_launched()
{
// A Windows build on Linux: Wine or Proton may well run it.
var (start, launcher, _) = Rig(files => files.WithExecutable(Build, "MZ\u0090\u0000"));
Assert.Equal(SimulationStartOutcome.Started, start.Execute(freeFly: true, saveSettings: false).Outcome);
Assert.NotNull(launcher.Executable);
}
[Fact]
public void A_file_that_cannot_be_run_is_not()
{
var (start, launcher, _) = Rig(files => files.WithFile(Build, "\u007fELF"));
var result = start.Execute(freeFly: true, saveSettings: false);
Assert.Equal(SimulationStartOutcome.ExecutableProblem, result.Outcome);
Assert.Equal(Domain.Settings.ExeProblem.NotExecutable, result.Problem);
Assert.Null(launcher.Executable);
}
private static (StartSimulation Start, RecordingLauncher Launcher, TestInstallation Installation) Rig(
Action<InMemoryFileSystem> addSimulator)
{
var installation = new TestInstallation();
addSimulator(installation.Files);
var settings = installation.SettingsStore.Settings;
settings.SelectExeAutomatically = false;
settings.ExecutablePath = Build;
var scenery = new SceneryParser(installation.Clock, installation.Random).Parse(
TestInstallation.At("scenery", "td.scn"),
"""
//$n Test
trainset pociag tor_1 10 0
node -1 -1 ep07-001 dynamic PKP/EP07 ep07 ep07.mmd 0 headdriver 0
endtrainset
FirstInit
""");
var state = new AppState { CurrentScenery = scenery, CurrentTrainset = scenery.Trainsets[0] };
var launcher = new RecordingLauncher();
var start = new StartSimulation(state, installation.SettingsStore, installation.Library.Vehicles,
installation.Files, installation.Paths, launcher, installation.Random,
installation.Log);
return (start, launcher, installation);
}
private sealed class RecordingLauncher : IProcessLauncher
{
public string? Executable { get; private set; }
public string? WorkingDirectory { get; private set; }
public IReadOnlyList<string>? Arguments { get; private set; }
public IProcessHandle? Start(string executablePath, IReadOnlyList<string> arguments, string? workingDirectory,
out string? error)
{
Executable = executablePath;
Arguments = arguments;
WorkingDirectory = workingDirectory;
error = null;
return new RunningProcess();
}
public bool OpenInShell(string pathOrUrl) => false;
public IProcessHandle? FindRunning(string processName) => null;
}
private sealed class RunningProcess : IProcessHandle
{
public bool HasExited => false;
public Task WaitForExitAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
}
}

View File

@@ -176,13 +176,17 @@ public partial class App : Avalonia.Application
$"{Loc["FaultLoadData"]}{Environment.NewLine}{Environment.NewLine}{Loc["FaultDetail"]} {inner.Message}");
}
var faults = Diagnostics.CheckInstallation();
var check = Diagnostics.InstallationCheck();
var faults = check.Run();
if (faults.Count > 0)
{
await Diagnostics.ReportAsync(
string.Join(Environment.NewLine, faults) + Environment.NewLine + Environment.NewLine +
Loc["FaultBadInstall"]);
}
if (check.ExecutableMissing())
await main.OfferExecutablePickAsync();
});
});
}

View File

@@ -59,7 +59,8 @@ public sealed class AppServices
assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion);
Updates = new UpdateCheck(SettingsStore.Settings, new GitHubReleaseFeed(log, version), version, commit,
files, clock, log, settingsPaths.UpdateCheckPath());
StartSimulation = new StartSimulation(State, SettingsStore, files, Processes, Random, log);
StartSimulation = new StartSimulation(State, SettingsStore, Library.Vehicles, files, paths, Processes,
Random, log);
}
public static AppServices Current =>

View File

@@ -6,6 +6,7 @@ using StarterNG.Application.Abstractions;
using StarterNG.Classes;
using StarterNG.Domain;
using StarterNG.Domain.Settings;
using StarterNG.Domain.Vehicles;
using StarterNG.Infrastructure.Adapters;
namespace StarterNG.Application;
@@ -37,17 +38,21 @@ public sealed class StartSimulation
private readonly AppState _state;
private readonly SettingsStore _settings;
private readonly VehicleCatalog _vehicles;
private readonly IFileSystem _files;
private readonly IGamePaths _paths;
private readonly IProcessLauncher _processes;
private readonly IRandomSource _random;
private readonly IDiagnosticsLog _log;
public StartSimulation(AppState state, SettingsStore settings, IFileSystem files, IProcessLauncher processes,
IRandomSource random, IDiagnosticsLog log)
public StartSimulation(AppState state, SettingsStore settings, VehicleCatalog vehicles, IFileSystem files,
IGamePaths paths, IProcessLauncher processes, IRandomSource random, IDiagnosticsLog log)
{
_state = state;
_settings = settings;
_vehicles = vehicles;
_files = files;
_paths = paths;
_processes = processes;
_random = random;
_log = log;
@@ -91,7 +96,7 @@ public sealed class StartSimulation
break;
}
string? vehicle = TrainsetDisplay.UniquifyForLaunch(trainset, scenery, _state.StartingVehicleName)
string? vehicle = TrainsetDisplay.UniquifyForLaunch(trainset, scenery, _state.StartingVehicleName, _vehicles)
?? StartableVehicle(trainset, _state.StartingVehicleName);
_state.StartingVehicleName = vehicle;
@@ -116,7 +121,10 @@ public sealed class StartSimulation
_settings.CaptureAndSave();
string executable = Path.GetFullPath(_settings.ResolveExecutable(out var problem));
if (problem != ExeProblem.None)
// A binary for the other system is let through: on Linux a Windows build
// runs under Wine or Proton, and only the launch can tell.
if (problem is not (ExeProblem.None or ExeProblem.WrongPlatform))
return new SimulationStartResult(SimulationStartOutcome.ExecutableProblem, ExecutablePath: executable,
Problem: problem);
@@ -124,7 +132,9 @@ public sealed class StartSimulation
? new[] { "-s", exportName }
: new[] { "-s", exportName, "-v", vehicle };
var process = _processes.Start(executable, arguments, Path.GetDirectoryName(executable), out string? error);
// The installation, not the folder of the binary: a simulator picked from a
// build tree lives beside the data, and the data is what it opens.
var process = _processes.Start(executable, arguments, _paths.Root, out string? error);
if (process is null)
return new SimulationStartResult(SimulationStartOutcome.LaunchFailed, ExecutablePath: executable,
Detail: error);

View File

@@ -199,10 +199,10 @@ public static class TrainsetDisplay
return p.LoadAccepted.Contains(loadType, StringComparison.OrdinalIgnoreCase);
}
public static string? UniquifyForLaunch(Trainset trainset, Scenery scenery, string? startName)
public static string? UniquifyForLaunch(Trainset trainset, Scenery scenery, string? startName,
VehicleCatalog db)
{
var used = CollectNames(scenery);
var db = AppServices.Current.Library.Vehicles;
bool mu = trainset.Vehicles.Any(v =>
{
@@ -221,18 +221,18 @@ public static class TrainsetDisplay
if (mu)
{
foreach (var v in trainset.Vehicles)
Rename(v, used);
Rename(v, used, db);
return target?.Name;
}
if (target != null)
Rename(target, used);
Rename(target, used, db);
return target?.Name;
}
public static string PreferredBaseName(Dynamic v)
public static string PreferredBaseName(Dynamic v, VehicleCatalog db)
{
var tex = AppServices.Current.Library.Vehicles.TextureForSkin(v.SkinFile);
var tex = db.TextureForSkin(v.SkinFile);
if (tex != null)
{
@@ -294,11 +294,11 @@ public static class TrainsetDisplay
return (n, "");
}
private static void Rename(Dynamic v, HashSet<string> used)
private static void Rename(Dynamic v, HashSet<string> used, VehicleCatalog db)
{
var (_, number) = SplitWagonNumber(v.Name);
if (!string.IsNullOrEmpty(v.Name))
used.Remove(v.Name!);
v.Name = EnsureUnique(PreferredBaseName(v), used) + number;
v.Name = EnsureUnique(PreferredBaseName(v, db), used) + number;
}
}

View File

@@ -53,13 +53,11 @@ public static class Diagnostics
Dispatcher.UIThread.Post(() => _ = ReportAsync(message, title));
}
public static List<string> CheckInstallation() =>
new InstallationCheck(
AppServices.Current.Paths,
AppServices.Current.Files,
AppServices.Current.Localization,
AppServices.Current.Physics,
AppServices.Current.Library,
AppServices.Current.SettingsStore)
.Run();
public static InstallationCheck InstallationCheck() =>
new(AppServices.Current.Paths,
AppServices.Current.Files,
AppServices.Current.Localization,
AppServices.Current.Physics,
AppServices.Current.Library,
AppServices.Current.SettingsStore);
}

View File

@@ -49,10 +49,17 @@ public sealed class InstallationCheck
if (_physics.IndexedCount == 0)
faults.Add(_strings["FaultNoPhysics"]);
_settings.ResolveExecutable(out var problem);
if (problem == ExeProblem.NotFound)
faults.Add(_strings["FaultNoExe"]);
return faults;
}
/// <summary>
/// Kept out of <see cref="Run"/>: a simulator missing from the installation does
/// not make it a broken one - it may be a build kept elsewhere, or a Windows
/// build for Wine, waiting to be picked.
/// </summary>
public bool ExecutableMissing()
{
_settings.ResolveExecutable(out var problem);
return problem == ExeProblem.NotFound;
}
}

View File

@@ -427,16 +427,28 @@ 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]}",
private Task<bool> ShowExeProblem(ExeProblem problem, string exe) =>
MessageBox.Show(this, $"{exe}\n\n{App.Loc[ExeProblemText.Key(problem)]}",
App.Loc["ExeLaunchFailed"], MessageBoxButtons.Ok);
/// <summary>
/// Asks for the simulator when the installation holds none, and keeps the pick
/// straight away, so the question is not back on the next start.
/// </summary>
public async Task OfferExecutablePickAsync()
{
string paragraph = Environment.NewLine + Environment.NewLine;
string message = string.Format(App.Loc["FaultNoExe"], AppServices.Current.Paths.Root) + paragraph;
if (!AppServices.Current.Environment.IsWindows)
message += App.Loc["FaultNoExeWine"] + paragraph;
message += App.Loc["FaultNoExePick"];
if (!await MessageBox.Show(this, message, App.Loc["FaultTitle"], MessageBoxButtons.YesNo))
return;
NavSettings.IsChecked = true;
if (await SettingsView.BrowseForExecutableAsync())
SettingsView.Save();
}
private async void StartButton_OnClick(object? sender, RoutedEventArgs e) =>

View File

@@ -0,0 +1,14 @@
using StarterNG.Domain.Settings;
namespace StarterNG.Views;
public static class ExeProblemText
{
/// <summary>The language key explaining why a simulator binary cannot be used as it is.</summary>
public static string Key(ExeProblem problem) => problem switch
{
ExeProblem.NotExecutable => "ExeNotExecutable",
ExeProblem.WrongPlatform => "ExeWrongPlatform",
_ => "ExeNotFound"
};
}

View File

@@ -153,7 +153,12 @@
<ctl:SettingsSection Header="{Binding [Others], Source={x:Static starterNg:App.Loc}}">
<StackPanel>
<ctl:SettingRow ConfigKey="starter.exe.path" Label="{Binding [SelectEXE], Source={x:Static starterNg:App.Loc}}">
<ComboBox x:Name="SelectExeCb" SelectedIndex="0" />
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="6">
<ComboBox x:Name="SelectExeCb" SelectedIndex="0" HorizontalAlignment="Stretch" />
<Button x:Name="BrowseExeButton" Grid.Column="1" Classes="Flat" Cursor="Hand"
Click="BrowseExeButton_OnClick"
Content="{Binding [SelectEXEBrowse], Source={x:Static starterNg:App.Loc}}" />
</Grid>
</ctl:SettingRow>
<ctl:SettingRow Description="{Binding [DebugModeDesc], Source={x:Static starterNg:App.Loc}}" ConfigKey="debugmode">
<CheckBox x:Name="DebugModeCb" Content="{Binding [DebugMode], Source={x:Static starterNg:App.Loc}}" />

View File

@@ -4,6 +4,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
@@ -13,6 +14,7 @@ using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Media;
using Avalonia.Platform.Storage;
using StarterNG.Classes;
using StarterNG.Controls;
using StarterNG.Infrastructure;
@@ -164,6 +166,58 @@ public partial class Settings : UserControl, ISettingsCapture
}
}
private async void BrowseExeButton_OnClick(object? sender, RoutedEventArgs e) =>
await BrowseForExecutableAsync();
/// <summary>
/// Picks the simulator from anywhere, such as a build tree kept beside the data,
/// where the automatic search - the working directory only - cannot see it.
/// True when a file was picked; like any other choice here, it waits for Save.
/// </summary>
public async Task<bool> BrowseForExecutableAsync()
{
if (TopLevel.GetTopLevel(this) is not Window owner)
return false;
var picked = await owner.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = App.Loc["SelectEXEBrowseTitle"],
AllowMultiple = false
});
if (picked.Count == 0 || picked[0].TryGetLocalPath() is not { } path)
return false;
var problem = AppServices.Current.Executables.Validate(path);
string message = $"{path}\n\n{App.Loc[ExeProblemText.Key(problem)]}";
if (problem == ExeProblem.WrongPlatform)
{
// Not a dead end: Wine or Proton runs a Windows build on Linux.
if (!await MessageBox.Show(owner, $"{message}\n\n{App.Loc["SelectEXEUseAnyway"]}",
App.Loc["SelectEXE"], MessageBoxButtons.YesNo))
return false;
}
else if (problem != ExeProblem.None)
{
await MessageBox.Show(owner, message, App.Loc["SelectEXE"], MessageBoxButtons.Ok);
return false;
}
// Inside the installation the bare name is enough, and matches the list.
string root = Path.GetFullPath(AppServices.Current.Paths.Root);
string entry = string.Equals(Path.GetDirectoryName(path), root, StringComparison.Ordinal)
? Path.GetFileName(path)
: path;
int index = FindComboIndexByContent(SelectExeCb, entry);
if (index < 0)
{
SelectExeCb.Items.Add(new ComboBoxItem { Content = entry });
index = SelectExeCb.Items.Count - 1;
}
SelectExeCb.SelectedIndex = index;
return true;
}
public void ReloadFromSettings() => ApplyToUi();
private void ApplyToUi()
@@ -379,7 +433,9 @@ public partial class Settings : UserControl, ISettingsCapture
new UartWindow().ShowDialog(owner);
}
private void SaveButton_OnClick(object? sender, RoutedEventArgs e)
private void SaveButton_OnClick(object? sender, RoutedEventArgs e) => Save();
public void Save()
{
CaptureInto(AppServices.Current.Settings);
AppServices.Current.SettingsStore.Save();

View File

@@ -252,7 +252,7 @@
<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="ExeWrongPlatform">Tento binární soubor je pro jiný operační systém. Na Linuxu může verze pro Windows běžet přes Wine nebo Proton - pokud je používáte, nejde o chybu.</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>
@@ -332,6 +332,9 @@
<String key="Others">Ostatní</String>
<String key="SelectEXE">Vybrat exe</String>
<String key="SelectEXEAuto">Vybrat automaticky</String>
<String key="SelectEXEBrowse">Vybrat soubor…</String>
<String key="SelectEXEBrowseTitle">Vyberte spustitelný soubor simulátoru</String>
<String key="SelectEXEUseAnyway">Přesto jej použít?</String>
<String key="DebugMode">Režim ladění (testovací režim)</String>
<String key="VirtualShunting">Virtuální posun</String>
<String key="LogMissingVehicleFiles">Zaznamenat chybějící soubory vozidel při exportu</String>
@@ -499,7 +502,9 @@
<String key="FaultNoScenery">Nebyly nalezeny žádné scénáře.</String>
<String key="FaultNoVehicles">Nebyla nalezena žádná vozidla.</String>
<String key="FaultNoPhysics">Nebyla nalezena data vozového parku.</String>
<String key="FaultNoExe">Spustitelný soubor simulátoru nebyl nalezen.</String>
<String key="FaultNoExe">Spustitelný soubor simulátoru nebyl v adresáři {0} nalezen.</String>
<String key="FaultNoExeWine">Na Linuxu můžete vybrat i verzi pro Windows (eu07.exe) - může běžet přes Wine nebo Proton.</String>
<String key="FaultNoExePick">Pokud je simulátor jinde, např. jako vlastní build, vyberte jej a volba se uloží. Můžete to udělat i později na kartě Nastavení (sekce Ostatní, tlačítko „Vybrat soubor…“). Vybrat soubor nyní?</String>
<!-- About -->
<String key="AboutTitle">O programu</String>

View File

@@ -252,7 +252,7 @@
<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="ExeWrongPlatform">This binary is built for a different operating system. On Linux a Windows build can still run through Wine or Proton - if you use them, this is not an error.</String>
<String key="TabInfo">Info</String>
<String key="SettingsFileChangedTitle">Settings</String>
<String key="SettingsFileChanged">eu07.ini was changed outside the starter. Reload it?</String>
@@ -335,6 +335,9 @@
<String key="Others">Other</String>
<String key="SelectEXE">Select exe</String>
<String key="SelectEXEAuto">Select automatically</String>
<String key="SelectEXEBrowse">Choose file…</String>
<String key="SelectEXEBrowseTitle">Pick the simulator executable</String>
<String key="SelectEXEUseAnyway">Use it anyway?</String>
<String key="DebugMode">Debug mode</String>
<String key="DebugModeDesc">Unlocks debug menus, extra panels and test shortcuts.</String>
<String key="VirtualShunting">Virtual shunting</String>
@@ -534,7 +537,9 @@
<String key="FaultNoScenery">No scenarios found.</String>
<String key="FaultNoVehicles">No vehicles found.</String>
<String key="FaultNoPhysics">No rolling stock data found.</String>
<String key="FaultNoExe">Simulator executable not found.</String>
<String key="FaultNoExe">Simulator executable not found in {0}.</String>
<String key="FaultNoExeWine">On Linux you can also pick the Windows build (eu07.exe) - it may run through Wine or Proton.</String>
<String key="FaultNoExePick">If the simulator lives elsewhere, such as your own build, pick it and the choice will be saved. You can also do it later on the Settings tab (Other section, the “Choose file…” button). Pick the file now?</String>
<!-- About -->
<String key="AboutTitle">About</String>

View File

@@ -252,7 +252,7 @@
<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="ExeWrongPlatform">Ez a bináris másik operációs rendszerhez készült. Linuxon a Windows-verzió Wine vagy Proton segítségével futtatható - ha ezeket használod, ez nem hiba.</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>
@@ -332,6 +332,9 @@
<String key="Others">Egyéb</String>
<String key="SelectEXE">Exe kiválasztása</String>
<String key="SelectEXEAuto">Automatikus kiválasztás</String>
<String key="SelectEXEBrowse">Fájl kiválasztása…</String>
<String key="SelectEXEBrowseTitle">Válaszd ki a szimulátor futtatható fájlját</String>
<String key="SelectEXEUseAnyway">Mégis használod?</String>
<String key="DebugMode">Hibakeresési mód (teszt mód)</String>
<String key="VirtualShunting">Virtuális tolatás</String>
<String key="LogMissingVehicleFiles">Hiányzó járműfájlok naplózása exportáláskor</String>
@@ -499,7 +502,9 @@
<String key="FaultNoScenery">Nem található forgatókönyv.</String>
<String key="FaultNoVehicles">Nem található jármű.</String>
<String key="FaultNoPhysics">Nem található járműadat.</String>
<String key="FaultNoExe">A szimulátor futtatható fájlja nem található.</String>
<String key="FaultNoExe">A szimulátor futtatható fájlja nem található itt: {0}.</String>
<String key="FaultNoExeWine">Linuxon a Windows-verziót (eu07.exe) is kiválaszthatod - Wine vagy Proton segítségével futhat.</String>
<String key="FaultNoExePick">Ha a szimulátor máshol van, például saját build, válaszd ki, és a választás mentésre kerül. Később a Beállítások lapon is megteheted (Egyéb szakasz, „Fájl kiválasztása…” gomb). Kiválasztod most a fájlt?</String>
<!-- About -->
<String key="AboutTitle">Névjegy</String>

View File

@@ -252,7 +252,7 @@
<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="ExeWrongPlatform">To jest plik wykonywalny dla innego systemu niż uruchomiony. Na Linuksie wersja dla Windows może działać przez Wine lub Proton - jeśli ich używasz, to nie jest błąd.</String>
<String key="TabInfo">Informacje</String>
<String key="SettingsFileChangedTitle">Ustawienia</String>
<String key="SettingsFileChanged">Plik eu07.ini został zmieniony poza starterem. Wczytać ponownie?</String>
@@ -335,6 +335,9 @@
<String key="Others">Inne</String>
<String key="SelectEXE">Wybór exe</String>
<String key="SelectEXEAuto">Wybierz automatycznie</String>
<String key="SelectEXEBrowse">Wybierz plik…</String>
<String key="SelectEXEBrowseTitle">Wskaż plik wykonywalny symulatora</String>
<String key="SelectEXEUseAnyway">Użyć go mimo to?</String>
<String key="DebugMode">Tryb testowy</String>
<String key="DebugModeDesc">Odblokowuje menu debug, dodatkowe panele i skróty testowe.</String>
<String key="VirtualShunting">Wirtualny manewrowy</String>
@@ -534,7 +537,9 @@
<String key="FaultNoScenery">Nie znaleziono scenariuszy.</String>
<String key="FaultNoVehicles">Nie znaleziono pojazdów.</String>
<String key="FaultNoPhysics">Nie znaleziono danych taboru.</String>
<String key="FaultNoExe">Nie znaleziono pliku wykonywalnego symulatora.</String>
<String key="FaultNoExe">Nie znaleziono pliku wykonywalnego symulatora w katalogu {0}.</String>
<String key="FaultNoExeWine">Na Linuksie możesz wskazać także wersję dla Windows (eu07.exe) - może działać przez Wine lub Proton.</String>
<String key="FaultNoExePick">Jeśli symulator leży gdzie indziej, np. jako własny build, wskaż go - wybór zostanie zapisany. Możesz to zrobić też później w zakładce Ustawienia (sekcja Inne, przycisk „Wybierz plik…”). Wskazać plik teraz?</String>
<!-- About -->
<String key="AboutTitle">O programie</String>

View File

@@ -252,7 +252,7 @@
<String key="ExeLaunchFailed">Не удалось запустить симулятор</String>
<String key="ExeNotFound">Исполняемый файл симулятора не найден.</String>
<String key="ExeNotExecutable">Файл не помечен как исполняемый (chmod +x).</String>
<String key="ExeWrongPlatform">Этот исполняемый файл собран для другой операционной системы.</String>
<String key="ExeWrongPlatform">Этот исполняемый файл собран для другой операционной системы. В Linux версия для Windows может работать через Wine или Proton - если вы их используете, это не ошибка.</String>
<String key="TabInfo">Информация</String>
<String key="SettingsFileChangedTitle">Настройки</String>
<String key="SettingsFileChanged">Файл eu07.ini изменён вне стартера. Загрузить заново?</String>
@@ -332,6 +332,9 @@
<String key="Others">Прочее</String>
<String key="SelectEXE">Выбрать exe</String>
<String key="SelectEXEAuto">Выбрать автоматически</String>
<String key="SelectEXEBrowse">Выбрать файл…</String>
<String key="SelectEXEBrowseTitle">Укажите исполняемый файл симулятора</String>
<String key="SelectEXEUseAnyway">Использовать его всё равно?</String>
<String key="DebugMode">Режим отладки (тестовый режим)</String>
<String key="VirtualShunting">Виртуальный манёвр</String>
<String key="LogMissingVehicleFiles">Записывать в журнал отсутствующие файлы состава при экспорте</String>
@@ -499,7 +502,9 @@
<String key="FaultNoScenery">Сценарии не найдены.</String>
<String key="FaultNoVehicles">Транспортные средства не найдены.</String>
<String key="FaultNoPhysics">Данные подвижного состава не найдены.</String>
<String key="FaultNoExe">Исполняемый файл симулятора не найден.</String>
<String key="FaultNoExe">Исполняемый файл симулятора не найден в папке {0}.</String>
<String key="FaultNoExeWine">В Linux можно выбрать и версию для Windows (eu07.exe) - она может работать через Wine или Proton.</String>
<String key="FaultNoExePick">Если симулятор находится в другом месте, например ваша собственная сборка, укажите его - выбор будет сохранён. Это можно сделать и позже на вкладке Настройки (раздел Прочее, кнопка «Выбрать файл…»). Указать файл сейчас?</String>
<!-- About -->
<String key="AboutTitle">О программе</String>