diff --git a/StarterNG.Tests/LegacyTextTests.cs b/StarterNG.Tests/LegacyTextTests.cs
new file mode 100644
index 0000000..9e48eee
--- /dev/null
+++ b/StarterNG.Tests/LegacyTextTests.cs
@@ -0,0 +1,38 @@
+using StarterNG.Infrastructure.Adapters;
+
+namespace StarterNG.Tests;
+
+public class LegacyTextTests
+{
+ [Fact]
+ public void The_game_code_page_resolves_without_anyone_registering_it_first()
+ {
+ // Regression guard: when this falls back, every Polish character in a
+ // scenery description comes out as Latin-1 mojibake ("Skład" as "Sk³ad").
+ Assert.False(LegacyText.IsFallback);
+ Assert.Equal(1250, LegacyText.CodePage1250.CodePage);
+ }
+
+ [Fact]
+ public void Polish_characters_survive_a_round_trip_through_it()
+ {
+ const string text = "Skład wygaszony pod wjazdowym. Strzebiń - Woźniki Śl.";
+
+ byte[] bytes = LegacyText.CodePage1250.GetBytes(text);
+
+ Assert.Equal(text, LegacyText.CodePage1250.GetString(bytes));
+ }
+
+ [Fact]
+ public void A_scenery_written_in_the_game_code_page_reads_back_intact()
+ {
+ var files = new StarterNG.Tests.Fakes.InMemoryFileSystem()
+ .WithLegacyFile(StarterNG.Tests.Fakes.TestInstallation.At("scenery", "td.scn"),
+ "//$n Strzebiń - Woźniki Śl. '92\n//$d Skład wygaszony pod wjazdowym.");
+
+ var scenery = Assert.Single(new StarterNG.Tests.Fakes.TestInstallation(files).Sceneries.LoadAll());
+
+ Assert.Equal("Strzebiń - Woźniki Śl. '92", scenery.Name);
+ Assert.Equal("Skład wygaszony pod wjazdowym.", scenery.Description);
+ }
+}
diff --git a/StarterNG/Application/SettingsStore.cs b/StarterNG/Application/SettingsStore.cs
index 9edb3d0..bdd40c9 100644
--- a/StarterNG/Application/SettingsStore.cs
+++ b/StarterNG/Application/SettingsStore.cs
@@ -5,6 +5,7 @@ using StarterNG.Application.Abstractions;
using StarterNG.Classes;
using StarterNG.Domain.Settings;
using StarterNG.Infrastructure.Settings;
+using StarterNG.Infrastructure.Adapters;
namespace StarterNG.Application;
@@ -50,7 +51,7 @@ public sealed class SettingsStore
_paths = paths;
_serializer = serializer;
_executables = executables;
- _encoding = LegacyEncoding();
+ _encoding = LegacyText.CodePage1250;
}
/// The settings the application is running with.
@@ -172,20 +173,4 @@ public sealed class SettingsStore
public string ResolveExecutable(out ExeProblem problem) => _executables.Resolve(Settings, out problem);
- ///
- /// The .ini keeps its own code page. UTF-8 is a safe fallback if the code
- /// page provider is unavailable.
- ///
- private static Encoding LegacyEncoding()
- {
- try
- {
- Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
- return Encoding.GetEncoding(1250);
- }
- catch (Exception)
- {
- return Encoding.UTF8;
- }
- }
}
diff --git a/StarterNG/Application/StartSimulation.cs b/StarterNG/Application/StartSimulation.cs
index fd28b53..a6b75dd 100644
--- a/StarterNG/Application/StartSimulation.cs
+++ b/StarterNG/Application/StartSimulation.cs
@@ -6,6 +6,7 @@ using StarterNG.Application.Abstractions;
using StarterNG.Classes;
using StarterNG.Domain;
using StarterNG.Domain.Settings;
+using StarterNG.Infrastructure.Adapters;
namespace StarterNG.Application;
@@ -125,7 +126,7 @@ public sealed class StartSimulation
{
_files.WriteAllText(exportPath,
scenery.BuildExportContent(_settings.Settings.IgnoreIrrelevantTrains, _random),
- LegacyEncoding());
+ LegacyText.CodePage1250);
}
catch (Exception ex)
{
@@ -174,15 +175,4 @@ public sealed class StartSimulation
};
}
- private static Encoding LegacyEncoding()
- {
- try
- {
- return Encoding.GetEncoding(1250);
- }
- catch (Exception)
- {
- return Encoding.Latin1;
- }
- }
}
diff --git a/StarterNG/Classes/KeyboardConfig.cs b/StarterNG/Classes/KeyboardConfig.cs
index b3fd6cd..b717853 100644
--- a/StarterNG/Classes/KeyboardConfig.cs
+++ b/StarterNG/Classes/KeyboardConfig.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
+using StarterNG.Infrastructure.Adapters;
namespace StarterNG.Classes;
@@ -43,8 +44,7 @@ public sealed class KeyboardConfig
try
{
- Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
- return Encoding.GetEncoding(1250);
+ return LegacyText.CodePage1250;
}
catch
{
diff --git a/StarterNG/Classes/PresetStore.cs b/StarterNG/Classes/PresetStore.cs
index c024e57..b03a748 100644
--- a/StarterNG/Classes/PresetStore.cs
+++ b/StarterNG/Classes/PresetStore.cs
@@ -8,6 +8,7 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
+using StarterNG.Infrastructure.Adapters;
namespace StarterNG.Classes;
@@ -100,7 +101,7 @@ public static class PresetStore
int added = 0;
try
{
- string text = File.ReadAllText(path, Encoding.GetEncoding(1250));
+ string text = File.ReadAllText(path, LegacyText.CodePage1250);
var section = Regex.Matches(text,
@"\[TRAINSET\d+\s*=\s*([^\]]*)\](.*?)(?=\[TRAINSET|\z)",
RegexOptions.IgnoreCase | RegexOptions.Singleline);
diff --git a/StarterNG/Domain/TrainsetDisplay.cs b/StarterNG/Domain/TrainsetDisplay.cs
index 040add4..10037af 100644
--- a/StarterNG/Domain/TrainsetDisplay.cs
+++ b/StarterNG/Domain/TrainsetDisplay.cs
@@ -169,11 +169,17 @@ public static class TrainsetDisplay
if (p != null)
massKg += p.Mass;
- if (loadWeightKg != null &&
- car.LoadCount > 0 &&
- !string.IsNullOrEmpty(car.LoadType) &&
- LoadAccepted(p, car.LoadType))
- loadKg += (double)car.LoadCount * loadWeightKg(car.LoadType);
+ if (loadWeightKg != null && car.LoadCount > 0 && !string.IsNullOrEmpty(car.LoadType))
+ {
+ if (LoadAccepted(p, car.LoadType))
+ loadKg += (double)car.LoadCount * loadWeightKg(car.LoadType);
+ else
+ // The scenery loads something this vehicle's .fiz does not
+ // accept. It is left out of the gross mass, and saying so beats
+ // leaving the reading quietly short.
+ AppServices.Current.Log.Log(
+ $"{car.Name}: nieobslugiwany ladunek \"{car.LoadType}\"");
+ }
}
return (lengthM, massKg, loadKg);
}
diff --git a/StarterNG/Domain/UDepot.cs b/StarterNG/Domain/UDepot.cs
index 25beae8..36ee242 100644
--- a/StarterNG/Domain/UDepot.cs
+++ b/StarterNG/Domain/UDepot.cs
@@ -6,6 +6,7 @@ using System.Text;
using System.Text.RegularExpressions;
using StarterNG.Classes;
using StarterNG.Infrastructure;
+using StarterNG.Infrastructure.Adapters;
namespace StarterNG.Domain;
@@ -40,7 +41,7 @@ public static class UDepot
string text;
try
{
- text = File.ReadAllText(file, Encoding.GetEncoding(1250));
+ text = File.ReadAllText(file, LegacyText.CodePage1250);
}
catch (Exception ex)
{
@@ -141,7 +142,7 @@ public static class UDepot
string? dir = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);
- File.WriteAllText(path, sb.ToString(), Encoding.GetEncoding(1250));
+ File.WriteAllText(path, sb.ToString(), LegacyText.CodePage1250);
return true;
}
catch (Exception ex)
diff --git a/StarterNG/Infrastructure/Adapters/LegacyText.cs b/StarterNG/Infrastructure/Adapters/LegacyText.cs
new file mode 100644
index 0000000..aa48b34
--- /dev/null
+++ b/StarterNG/Infrastructure/Adapters/LegacyText.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Text;
+
+namespace StarterNG.Infrastructure.Adapters;
+
+///
+/// The code page every file the simulator owns is written in: sceneries,
+/// textures.txt, .fiz, timetables, eu07.ini.
+///
+///
+/// Code page 1250 is not built into .NET; it arrives with
+/// , which has to be registered before the
+/// first GetEncoding call. Doing that registration at each call site is how
+/// Polish characters end up as Latin-1 mojibake ("Skład" as "Sk³ad"): whichever
+/// site runs first wins, and the composition root builds its repositories before
+/// any of them. One property, resolved once, removes the ordering question.
+///
+public static class LegacyText
+{
+ /// The .scn / .fiz / .ini code page, or Latin-1 if it is unavailable.
+ public static Encoding CodePage1250 { get; } = Resolve();
+
+ ///
+ /// True when the code page could not be loaded and text is being read as
+ /// Latin-1 instead. Polish characters will be wrong; the startup check
+ /// reports it rather than leaving it to be discovered on screen.
+ ///
+ public static bool IsFallback { get; private set; }
+
+ private static Encoding Resolve()
+ {
+ try
+ {
+ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+ return Encoding.GetEncoding(1250);
+ }
+ catch (Exception)
+ {
+ IsFallback = true;
+ return Encoding.Latin1;
+ }
+ }
+}
diff --git a/StarterNG/Infrastructure/Sceneries/SceneryRepository.cs b/StarterNG/Infrastructure/Sceneries/SceneryRepository.cs
index afcb536..e8308a2 100644
--- a/StarterNG/Infrastructure/Sceneries/SceneryRepository.cs
+++ b/StarterNG/Infrastructure/Sceneries/SceneryRepository.cs
@@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks;
using StarterNG.Application.Abstractions;
using StarterNG.Domain.Sceneries;
+using StarterNG.Infrastructure.Adapters;
namespace StarterNG.Infrastructure.Sceneries;
@@ -36,7 +37,7 @@ public sealed class SceneryRepository : ISceneryRepository
_paths = paths;
_log = log;
_parser = parser;
- _encoding = CodePage1250();
+ _encoding = LegacyText.CodePage1250;
}
public IReadOnlyList LoadAll(IProgress? progress = null)
@@ -83,19 +84,4 @@ public sealed class SceneryRepository : ISceneryRepository
_files.GetFiles(_paths.Scenery, "*.scn")
.Where(path => Path.GetFileName(path).FirstOrDefault() != ExcludedPrefix);
- ///
- /// The .scn dialect predates Unicode. The provider is registered by the entry
- /// point; fall back to Latin-1 if that ever fails so loading still works.
- ///
- private static Encoding CodePage1250()
- {
- try
- {
- return Encoding.GetEncoding(1250);
- }
- catch (Exception)
- {
- return Encoding.Latin1;
- }
- }
}
diff --git a/StarterNG/Infrastructure/Vehicles/FizPhysicsRepository.cs b/StarterNG/Infrastructure/Vehicles/FizPhysicsRepository.cs
index 1856982..b530a59 100644
--- a/StarterNG/Infrastructure/Vehicles/FizPhysicsRepository.cs
+++ b/StarterNG/Infrastructure/Vehicles/FizPhysicsRepository.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Text;
using StarterNG.Application.Abstractions;
using StarterNG.Domain.Vehicles;
+using StarterNG.Infrastructure.Adapters;
namespace StarterNG.Infrastructure.Vehicles;
@@ -51,7 +52,7 @@ public sealed class FizPhysicsRepository : IPhysicsRepository
_files = files;
_paths = paths;
_log = log;
- _encoding = LegacyEncoding();
+ _encoding = LegacyText.CodePage1250;
}
public void Preload() => Index();
@@ -270,15 +271,4 @@ public sealed class FizPhysicsRepository : IPhysicsRepository
.ToList();
}
- private static Encoding LegacyEncoding()
- {
- try
- {
- return Encoding.GetEncoding(1250);
- }
- catch (Exception)
- {
- return Encoding.Latin1;
- }
- }
}
diff --git a/StarterNG/Infrastructure/Vehicles/LoadWeightsRepository.cs b/StarterNG/Infrastructure/Vehicles/LoadWeightsRepository.cs
index 486c991..8639771 100644
--- a/StarterNG/Infrastructure/Vehicles/LoadWeightsRepository.cs
+++ b/StarterNG/Infrastructure/Vehicles/LoadWeightsRepository.cs
@@ -5,6 +5,7 @@ using System.IO;
using System.Text;
using StarterNG.Application.Abstractions;
using StarterNG.Domain.Vehicles;
+using StarterNG.Infrastructure.Adapters;
namespace StarterNG.Infrastructure.Vehicles;
@@ -51,7 +52,7 @@ public sealed class LoadWeightsRepository
try
{
- return new LoadWeightsTable(ParsePairs(_files.ReadAllText(path, LegacyEncoding())));
+ return new LoadWeightsTable(ParsePairs(_files.ReadAllText(path, LegacyText.CodePage1250)));
}
catch (Exception ex)
{
@@ -100,15 +101,4 @@ public sealed class LoadWeightsRepository
return weights;
}
- private static Encoding LegacyEncoding()
- {
- try
- {
- return Encoding.GetEncoding(1250);
- }
- catch (Exception)
- {
- return Encoding.Latin1;
- }
- }
}
diff --git a/StarterNG/Infrastructure/Vehicles/TexturesTxtVehicleRepository.cs b/StarterNG/Infrastructure/Vehicles/TexturesTxtVehicleRepository.cs
index 6c959ab..d2f709c 100644
--- a/StarterNG/Infrastructure/Vehicles/TexturesTxtVehicleRepository.cs
+++ b/StarterNG/Infrastructure/Vehicles/TexturesTxtVehicleRepository.cs
@@ -4,6 +4,7 @@ using System.IO;
using System.Text;
using StarterNG.Application.Abstractions;
using StarterNG.Domain.Vehicles;
+using StarterNG.Infrastructure.Adapters;
namespace StarterNG.Infrastructure.Vehicles;
@@ -28,7 +29,7 @@ public sealed class TexturesTxtVehicleRepository : IVehicleRepository
_paths = paths;
_log = log;
_parser = parser;
- _encoding = LegacyEncoding();
+ _encoding = LegacyText.CodePage1250;
}
public int Load(VehicleCatalog catalog)
@@ -98,15 +99,4 @@ public sealed class TexturesTxtVehicleRepository : IVehicleRepository
return relative.Replace('\\', '/').TrimEnd('/') + "/";
}
- private static Encoding LegacyEncoding()
- {
- try
- {
- return Encoding.GetEncoding(1250);
- }
- catch (Exception)
- {
- return Encoding.Latin1;
- }
- }
}
diff --git a/StarterNG/Views/Scenarios.axaml.cs b/StarterNG/Views/Scenarios.axaml.cs
index 3339d08..a55b984 100644
--- a/StarterNG/Views/Scenarios.axaml.cs
+++ b/StarterNG/Views/Scenarios.axaml.cs
@@ -20,6 +20,7 @@ using StarterNG.Domain;
using StarterNG.Application;
using StarterNG.Domain.Sceneries;
using StarterNG.Presentation.Scenarios;
+using StarterNG.Infrastructure.Adapters;
namespace StarterNG.Views;
@@ -708,7 +709,7 @@ public partial class Scenarios : UserControl
try
{
- timetableContent.Text = File.ReadAllText(path, Encoding.GetEncoding(1250));
+ timetableContent.Text = File.ReadAllText(path, LegacyText.CodePage1250);
UpdateTimetableTab(true);
}
catch