Files
StarterNG/StarterNG/Domain/Sceneries/Scenery.cs
maj00r a8f8cb0b74 Keep each file's encoding in the copy written for a start
The copy carries the scenario includes put in place of their include lines,
read as UTF-8 where they are, and it was written all in code page 1250. A name
with a Polish letter in a UTF-8 include then no longer matched the same name in
the files the simulator reads apart, and what 1250 lacks became '?'. Each part
now goes out in the encoding of the file it came from, the scenery's own file
too, as a save of a trainset already did.
2026-11-03 21:51:52 +01:00

160 lines
5.0 KiB
C#

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using StarterNG.Application.Abstractions;
using StarterNG.Classes;
using StarterNG.Infrastructure.Adapters;
namespace StarterNG.Domain.Sceneries;
public sealed class SceneryAttachment
{
public string FilePath = "";
public string Label = "";
}
public sealed class SceneryInclude
{
public string FilePath = "";
public string Desc = "";
public int Kind;
public bool Selected;
}
public sealed class Scenery
{
public Scenery(string path, string template)
{
Path = path;
Template = template;
}
public string Path { get; }
public string Template { get; }
public bool HasCompanionTimetable { get; set; }
public string? Group { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public string? ImageName { get; set; }
public bool Archival { get; set; }
public List<Trainset> Trainsets { get; } = new();
public List<SceneryAttachment> Attachments { get; } = new();
public List<SceneryInclude> Includes { get; } = new();
public List<Dynamic> LooseVehicles { get; } = new();
public List<string> LooseVehicleNames { get; } = new();
public List<SceneryFault> Faults { get; } = new();
public SceneryWeather Weather { get; } = new();
/// <summary>
/// The files it was read from, as each stood then: its own and those put in place
/// of their includes. Replaced whole, as it is looked at off the UI thread too.
/// </summary>
public IReadOnlyDictionary<string, FileStamp?> Sources { get; set; } = new Dictionary<string, FileStamp?>();
/// <summary>What its own file is written in, for the copy written for a start to be in the same.</summary>
public Encoding TextEncoding { get; set; } = LegacyText.CodePage1250;
/// <summary>What each file put in place of its include is written in, by the name its marker carries.</summary>
public IReadOnlyDictionary<string, Encoding> IncludeEncodings { get; set; } = new Dictionary<string, Encoding>();
public string DisplayName => System.IO.Path.GetFileNameWithoutExtension(Path);
public string LocalizedDescription(ISceneryTranslations translations) =>
string.IsNullOrEmpty(Description)
? ""
: string.Join("\n", Description.Split('\n').Select(translations.Translate));
public string BuildExportContent(bool skipDecorTrainsets, IRandomSource random)
{
string result = Template;
if (Weather.Dirty)
result = SceneryWeatherWriter.Rewrite(result, Weather, random);
result = InjectIncludes(result);
for (int i = 0; i < Trainsets.Count; i++)
{
// The simulator logs an empty trainset as a bad one and does nothing else with it.
bool empty = Trainsets[i].Parsed && Trainsets[i].Vehicles.Count == 0;
string entry = empty || (skipDecorTrainsets && Trainsets[i].Decor)
? string.Empty
: Trainsets[i].ToSceneryEntry();
result = result.Replace("{{" + i + "}}", entry);
}
if (LooseVehicles.Count > 0)
result = AppendLooseVehicles(result);
return result;
}
/// <summary>
/// <see cref="BuildExportContent"/> as the file to write: each part in the encoding of
/// the file it came from, so the simulator gets every word as that file has it.
/// </summary>
public byte[] BuildExportBytes(bool skipDecorTrainsets, IRandomSource random) =>
SceneryIncludes.Encode(BuildExportContent(skipDecorTrainsets, random), TextEncoding, IncludeEncodings);
private string AppendLooseVehicles(string text)
{
var sb = new StringBuilder();
foreach (var vehicle in LooseVehicles)
sb.Append(vehicle.ToLooseNode());
if (sb.Length == 0)
return text;
return InsertBeforeFirstInit(text, sb.ToString(), fallbackAtEnd: true);
}
private string InjectIncludes(string text)
{
if (Includes.Count == 0)
return text;
var sb = new StringBuilder();
foreach (var include in Includes)
{
if (include.Kind == 2)
{
if (!HasCompanionTimetable)
sb.Append("include ").Append(include.FilePath).Append(" end\r\n");
continue;
}
if (include.Selected)
sb.Append("include ").Append(include.FilePath).Append(" end\r\n");
}
if (sb.Length == 0)
return text;
return InsertBeforeFirstInit(text, sb.ToString(), fallbackAtEnd: false);
}
private static string InsertBeforeFirstInit(string text, string addition, bool fallbackAtEnd)
{
var firstInit = Regex.Match(text, @"\bFirstInit\b", RegexOptions.IgnoreCase);
if (firstInit.Success)
return text[..firstInit.Index] + addition + text[firstInit.Index..];
return fallbackAtEnd ? text + addition : addition + text;
}
}