mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-10-16 23:49:19 +02:00
Fix cp1250 decoding, log unsupported loads
The repositories resolved GetEncoding(1250) in their constructors, before anything registered CodePagesEncodingProvider - reads fell back to Latin-1 and Polish characters came out as mojibake.
This commit is contained in:
38
StarterNG.Tests/LegacyTextTests.cs
Normal file
38
StarterNG.Tests/LegacyTextTests.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>The settings the application is running with.</summary>
|
||||
@@ -172,20 +173,4 @@ public sealed class SettingsStore
|
||||
|
||||
public string ResolveExecutable(out ExeProblem problem) => _executables.Resolve(Settings, out problem);
|
||||
|
||||
/// <summary>
|
||||
/// The .ini keeps its own code page. UTF-8 is a safe fallback if the code
|
||||
/// page provider is unavailable.
|
||||
/// </summary>
|
||||
private static Encoding LegacyEncoding()
|
||||
{
|
||||
try
|
||||
{
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
return Encoding.GetEncoding(1250);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return Encoding.UTF8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
43
StarterNG/Infrastructure/Adapters/LegacyText.cs
Normal file
43
StarterNG/Infrastructure/Adapters/LegacyText.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace StarterNG.Infrastructure.Adapters;
|
||||
|
||||
/// <summary>
|
||||
/// The code page every file the simulator owns is written in: sceneries,
|
||||
/// textures.txt, .fiz, timetables, eu07.ini.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Code page 1250 is not built into .NET; it arrives with
|
||||
/// <see cref="CodePagesEncodingProvider"/>, which has to be registered before the
|
||||
/// first <c>GetEncoding</c> 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.
|
||||
/// </remarks>
|
||||
public static class LegacyText
|
||||
{
|
||||
/// <summary>The .scn / .fiz / .ini code page, or Latin-1 if it is unavailable.</summary>
|
||||
public static Encoding CodePage1250 { get; } = Resolve();
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Scenery> LoadAll(IProgress<SceneryLoadProgress>? progress = null)
|
||||
@@ -83,19 +84,4 @@ public sealed class SceneryRepository : ISceneryRepository
|
||||
_files.GetFiles(_paths.Scenery, "*.scn")
|
||||
.Where(path => Path.GetFileName(path).FirstOrDefault() != ExcludedPrefix);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private static Encoding CodePage1250()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Encoding.GetEncoding(1250);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return Encoding.Latin1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user