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.Classes;
|
||||||
using StarterNG.Domain.Settings;
|
using StarterNG.Domain.Settings;
|
||||||
using StarterNG.Infrastructure.Settings;
|
using StarterNG.Infrastructure.Settings;
|
||||||
|
using StarterNG.Infrastructure.Adapters;
|
||||||
|
|
||||||
namespace StarterNG.Application;
|
namespace StarterNG.Application;
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ public sealed class SettingsStore
|
|||||||
_paths = paths;
|
_paths = paths;
|
||||||
_serializer = serializer;
|
_serializer = serializer;
|
||||||
_executables = executables;
|
_executables = executables;
|
||||||
_encoding = LegacyEncoding();
|
_encoding = LegacyText.CodePage1250;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>The settings the application is running with.</summary>
|
/// <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);
|
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.Classes;
|
||||||
using StarterNG.Domain;
|
using StarterNG.Domain;
|
||||||
using StarterNG.Domain.Settings;
|
using StarterNG.Domain.Settings;
|
||||||
|
using StarterNG.Infrastructure.Adapters;
|
||||||
|
|
||||||
namespace StarterNG.Application;
|
namespace StarterNG.Application;
|
||||||
|
|
||||||
@@ -125,7 +126,7 @@ public sealed class StartSimulation
|
|||||||
{
|
{
|
||||||
_files.WriteAllText(exportPath,
|
_files.WriteAllText(exportPath,
|
||||||
scenery.BuildExportContent(_settings.Settings.IgnoreIrrelevantTrains, _random),
|
scenery.BuildExportContent(_settings.Settings.IgnoreIrrelevantTrains, _random),
|
||||||
LegacyEncoding());
|
LegacyText.CodePage1250);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using StarterNG.Infrastructure.Adapters;
|
||||||
|
|
||||||
namespace StarterNG.Classes;
|
namespace StarterNG.Classes;
|
||||||
|
|
||||||
@@ -43,8 +44,7 @@ public sealed class KeyboardConfig
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
return LegacyText.CodePage1250;
|
||||||
return Encoding.GetEncoding(1250);
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System.Text.Json;
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Text.Json.Serialization.Metadata;
|
using System.Text.Json.Serialization.Metadata;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using StarterNG.Infrastructure.Adapters;
|
||||||
|
|
||||||
namespace StarterNG.Classes;
|
namespace StarterNG.Classes;
|
||||||
|
|
||||||
@@ -100,7 +101,7 @@ public static class PresetStore
|
|||||||
int added = 0;
|
int added = 0;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string text = File.ReadAllText(path, Encoding.GetEncoding(1250));
|
string text = File.ReadAllText(path, LegacyText.CodePage1250);
|
||||||
var section = Regex.Matches(text,
|
var section = Regex.Matches(text,
|
||||||
@"\[TRAINSET\d+\s*=\s*([^\]]*)\](.*?)(?=\[TRAINSET|\z)",
|
@"\[TRAINSET\d+\s*=\s*([^\]]*)\](.*?)(?=\[TRAINSET|\z)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||||
|
|||||||
@@ -169,11 +169,17 @@ public static class TrainsetDisplay
|
|||||||
if (p != null)
|
if (p != null)
|
||||||
massKg += p.Mass;
|
massKg += p.Mass;
|
||||||
|
|
||||||
if (loadWeightKg != null &&
|
if (loadWeightKg != null && car.LoadCount > 0 && !string.IsNullOrEmpty(car.LoadType))
|
||||||
car.LoadCount > 0 &&
|
{
|
||||||
!string.IsNullOrEmpty(car.LoadType) &&
|
if (LoadAccepted(p, car.LoadType))
|
||||||
LoadAccepted(p, car.LoadType))
|
loadKg += (double)car.LoadCount * loadWeightKg(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);
|
return (lengthM, massKg, loadKg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Text;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using StarterNG.Classes;
|
using StarterNG.Classes;
|
||||||
using StarterNG.Infrastructure;
|
using StarterNG.Infrastructure;
|
||||||
|
using StarterNG.Infrastructure.Adapters;
|
||||||
|
|
||||||
namespace StarterNG.Domain;
|
namespace StarterNG.Domain;
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ public static class UDepot
|
|||||||
string text;
|
string text;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
text = File.ReadAllText(file, Encoding.GetEncoding(1250));
|
text = File.ReadAllText(file, LegacyText.CodePage1250);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -141,7 +142,7 @@ public static class UDepot
|
|||||||
|
|
||||||
string? dir = Path.GetDirectoryName(path);
|
string? dir = Path.GetDirectoryName(path);
|
||||||
if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);
|
if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);
|
||||||
File.WriteAllText(path, sb.ToString(), Encoding.GetEncoding(1250));
|
File.WriteAllText(path, sb.ToString(), LegacyText.CodePage1250);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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 System.Threading.Tasks;
|
||||||
using StarterNG.Application.Abstractions;
|
using StarterNG.Application.Abstractions;
|
||||||
using StarterNG.Domain.Sceneries;
|
using StarterNG.Domain.Sceneries;
|
||||||
|
using StarterNG.Infrastructure.Adapters;
|
||||||
|
|
||||||
namespace StarterNG.Infrastructure.Sceneries;
|
namespace StarterNG.Infrastructure.Sceneries;
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ public sealed class SceneryRepository : ISceneryRepository
|
|||||||
_paths = paths;
|
_paths = paths;
|
||||||
_log = log;
|
_log = log;
|
||||||
_parser = parser;
|
_parser = parser;
|
||||||
_encoding = CodePage1250();
|
_encoding = LegacyText.CodePage1250;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<Scenery> LoadAll(IProgress<SceneryLoadProgress>? progress = null)
|
public IReadOnlyList<Scenery> LoadAll(IProgress<SceneryLoadProgress>? progress = null)
|
||||||
@@ -83,19 +84,4 @@ public sealed class SceneryRepository : ISceneryRepository
|
|||||||
_files.GetFiles(_paths.Scenery, "*.scn")
|
_files.GetFiles(_paths.Scenery, "*.scn")
|
||||||
.Where(path => Path.GetFileName(path).FirstOrDefault() != ExcludedPrefix);
|
.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 System.Text;
|
||||||
using StarterNG.Application.Abstractions;
|
using StarterNG.Application.Abstractions;
|
||||||
using StarterNG.Domain.Vehicles;
|
using StarterNG.Domain.Vehicles;
|
||||||
|
using StarterNG.Infrastructure.Adapters;
|
||||||
|
|
||||||
namespace StarterNG.Infrastructure.Vehicles;
|
namespace StarterNG.Infrastructure.Vehicles;
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ public sealed class FizPhysicsRepository : IPhysicsRepository
|
|||||||
_files = files;
|
_files = files;
|
||||||
_paths = paths;
|
_paths = paths;
|
||||||
_log = log;
|
_log = log;
|
||||||
_encoding = LegacyEncoding();
|
_encoding = LegacyText.CodePage1250;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Preload() => Index();
|
public void Preload() => Index();
|
||||||
@@ -270,15 +271,4 @@ public sealed class FizPhysicsRepository : IPhysicsRepository
|
|||||||
.ToList();
|
.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 System.Text;
|
||||||
using StarterNG.Application.Abstractions;
|
using StarterNG.Application.Abstractions;
|
||||||
using StarterNG.Domain.Vehicles;
|
using StarterNG.Domain.Vehicles;
|
||||||
|
using StarterNG.Infrastructure.Adapters;
|
||||||
|
|
||||||
namespace StarterNG.Infrastructure.Vehicles;
|
namespace StarterNG.Infrastructure.Vehicles;
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ public sealed class LoadWeightsRepository
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new LoadWeightsTable(ParsePairs(_files.ReadAllText(path, LegacyEncoding())));
|
return new LoadWeightsTable(ParsePairs(_files.ReadAllText(path, LegacyText.CodePage1250)));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -100,15 +101,4 @@ public sealed class LoadWeightsRepository
|
|||||||
return weights;
|
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 System.Text;
|
||||||
using StarterNG.Application.Abstractions;
|
using StarterNG.Application.Abstractions;
|
||||||
using StarterNG.Domain.Vehicles;
|
using StarterNG.Domain.Vehicles;
|
||||||
|
using StarterNG.Infrastructure.Adapters;
|
||||||
|
|
||||||
namespace StarterNG.Infrastructure.Vehicles;
|
namespace StarterNG.Infrastructure.Vehicles;
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ public sealed class TexturesTxtVehicleRepository : IVehicleRepository
|
|||||||
_paths = paths;
|
_paths = paths;
|
||||||
_log = log;
|
_log = log;
|
||||||
_parser = parser;
|
_parser = parser;
|
||||||
_encoding = LegacyEncoding();
|
_encoding = LegacyText.CodePage1250;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Load(VehicleCatalog catalog)
|
public int Load(VehicleCatalog catalog)
|
||||||
@@ -98,15 +99,4 @@ public sealed class TexturesTxtVehicleRepository : IVehicleRepository
|
|||||||
return relative.Replace('\\', '/').TrimEnd('/') + "/";
|
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.Application;
|
||||||
using StarterNG.Domain.Sceneries;
|
using StarterNG.Domain.Sceneries;
|
||||||
using StarterNG.Presentation.Scenarios;
|
using StarterNG.Presentation.Scenarios;
|
||||||
|
using StarterNG.Infrastructure.Adapters;
|
||||||
|
|
||||||
namespace StarterNG.Views;
|
namespace StarterNG.Views;
|
||||||
|
|
||||||
@@ -708,7 +709,7 @@ public partial class Scenarios : UserControl
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
timetableContent.Text = File.ReadAllText(path, Encoding.GetEncoding(1250));
|
timetableContent.Text = File.ReadAllText(path, LegacyText.CodePage1250);
|
||||||
UpdateTimetableTab(true);
|
UpdateTimetableTab(true);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
Reference in New Issue
Block a user