From c42695c12f4279ccc0a9ad0627b46ed6e240797d Mon Sep 17 00:00:00 2001 From: flysimdev Date: Mon, 9 Nov 2026 09:03:00 +0100 Subject: [PATCH] Adjusted management for scenery_packs.ini --- .../Scenery/FileSystem/SceneryPacks.cs | 27 ++++++++----- .../OrthoStreamXP.Tests/SceneryPacksTests.cs | 40 ++++++++++++++++++- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/OrthoStreamXP/Scenery/FileSystem/SceneryPacks.cs b/src/OrthoStreamXP/Scenery/FileSystem/SceneryPacks.cs index e7bed9d..484c2de 100644 --- a/src/OrthoStreamXP/Scenery/FileSystem/SceneryPacks.cs +++ b/src/OrthoStreamXP/Scenery/FileSystem/SceneryPacks.cs @@ -47,7 +47,7 @@ public static bool MoveToEnd(string customScenery, IReadOnlyList package string file = Path.Join(customScenery, FileName); string? text = File.Exists(file) ? File.ReadAllText(file) : null; string[] lines = text is null ? NewFileHeader : text.ReplaceLineEndings("\n").TrimEnd('\n').Split('\n'); - List updated = Reorder(lines, packageNames, customScenery); + List updated = Reorder(lines, packageNames); if (lines.SequenceEqual(updated)) return false; string newline = text is not null && !text.Contains("\r\n", StringComparison.Ordinal) ? "\n" : "\r\n"; if (text is not null) File.Copy(file, file + ".bak", overwrite: true); @@ -55,34 +55,41 @@ public static bool MoveToEnd(string customScenery, IReadOnlyList package return true; } - /// The lines without the packages' entries, then one entry per package at the end. - internal static List Reorder(IReadOnlyList lines, IReadOnlyList packageNames, string customScenery) + /// + /// The lines without the packages' entries, then one entry per package at the end. Overlay packages + /// (such as XPME_Overlays) come first among them, so their overlays draw on top of the other base packages. + /// + internal static List Reorder(IReadOnlyList lines, IReadOnlyList packageNames) { var disabled = new HashSet(StringComparer.OrdinalIgnoreCase); var kept = new List(); foreach (string line in lines) { - string? package = PackageOf(line, packageNames, customScenery, out bool isDisabled); + string? package = PackageOf(line, packageNames, out bool isDisabled); if (package is null) kept.Add(line); else if (isDisabled) disabled.Add(package); } while (kept.Count > 0 && kept[^1].Trim().Length == 0) kept.RemoveAt(kept.Count - 1); - foreach (string name in packageNames) + foreach (string name in packageNames.OrderBy(name => IsOverlays(name) ? 0 : 1)) kept.Add($"{(disabled.Contains(name) ? Disabled : Enabled)}Custom Scenery/{name}/"); return kept; } - /// Which of the packages a SCENERY_PACK line names (relative to the X-Plane folder, or as a full path). - private static string? PackageOf(string line, IReadOnlyList packageNames, string customScenery, out bool isDisabled) + /// + /// Which of the packages a SCENERY_PACK line names, by its folder name wherever the path points: an entry + /// for a package of the same name elsewhere (such as the base packages folder itself) would load it twice. + /// + private static string? PackageOf(string line, IReadOnlyList packageNames, out bool isDisabled) { string trimmed = line.Trim(); isDisabled = trimmed.StartsWith(Disabled, StringComparison.Ordinal); if (!isDisabled && !trimmed.StartsWith(Enabled, StringComparison.Ordinal)) return null; string path = Normalize(trimmed[(isDisabled ? Disabled : Enabled).Length..]); - return packageNames.FirstOrDefault(name => - path.Equals("Custom Scenery/" + name, StringComparison.OrdinalIgnoreCase) - || path.Equals(Normalize(Path.Join(customScenery, name)), StringComparison.OrdinalIgnoreCase)); + string folderName = path[(path.LastIndexOf('/') + 1)..]; + return packageNames.FirstOrDefault(name => folderName.Equals(name, StringComparison.OrdinalIgnoreCase)); } + private static bool IsOverlays(string packageName) => packageName.Contains("Overlays", StringComparison.OrdinalIgnoreCase); + private static string Normalize(string path) => path.Trim().Replace('\\', '/').TrimEnd('/'); } diff --git a/tests/OrthoStreamXP.Tests/SceneryPacksTests.cs b/tests/OrthoStreamXP.Tests/SceneryPacksTests.cs index 1f4e00e..ef3c766 100644 --- a/tests/OrthoStreamXP.Tests/SceneryPacksTests.cs +++ b/tests/OrthoStreamXP.Tests/SceneryPacksTests.cs @@ -52,10 +52,48 @@ SCENERY_PACK Custom Scenery/XPME_Asia/ public void An_entry_with_the_full_path_is_recognised() { string fullPath = Path.Join(CustomScenery, "XPME_Europe").Replace('\\', '/') + "/"; - List lines = SceneryPacks.Reorder(["SCENERY", "", $"SCENERY_PACK {fullPath}", "SCENERY_PACK Custom Scenery/KSEA/"], ["XPME_Europe"], CustomScenery); + List lines = SceneryPacks.Reorder(["SCENERY", "", $"SCENERY_PACK {fullPath}", "SCENERY_PACK Custom Scenery/KSEA/"], ["XPME_Europe"]); Assert.Equal(["SCENERY", "", "SCENERY_PACK Custom Scenery/KSEA/", "SCENERY_PACK Custom Scenery/XPME_Europe/"], lines); } + [Fact] + public void An_entry_for_a_package_of_the_same_name_elsewhere_is_replaced() + { + List lines = SceneryPacks.Reorder( + [ + "SCENERY", "", + @"SCENERY_PACK C:\OrthoData\XPME\Base Packages\XPME_Africa/", + "SCENERY_PACK Custom Scenery/KSEA/", + @"SCENERY_PACK D:\Other\XPME_Asia", + @"SCENERY_PACK C:\OrthoData\XPME\Base Packages\XPME_Africa_Extra/", + ], + ["XPME_Africa", "XPME_Asia"]); + Assert.Equal( + [ + "SCENERY", "", + "SCENERY_PACK Custom Scenery/KSEA/", + @"SCENERY_PACK C:\OrthoData\XPME\Base Packages\XPME_Africa_Extra/", + "SCENERY_PACK Custom Scenery/XPME_Africa/", + "SCENERY_PACK Custom Scenery/XPME_Asia/", + ], lines); + } + + [Fact] + public void An_overlays_package_goes_above_the_other_packages() + { + List lines = SceneryPacks.Reorder( + ["SCENERY", "", "SCENERY_PACK Custom Scenery/KSEA/", "SCENERY_PACK_DISABLED Custom Scenery/XPME_Overlays/"], + ["XPME_Africa", "XPME_Asia", "XPME_Overlays", "XPME_South_America"]); + Assert.Equal( + [ + "SCENERY", "", "SCENERY_PACK Custom Scenery/KSEA/", + "SCENERY_PACK_DISABLED Custom Scenery/XPME_Overlays/", + "SCENERY_PACK Custom Scenery/XPME_Africa/", + "SCENERY_PACK Custom Scenery/XPME_Asia/", + "SCENERY_PACK Custom Scenery/XPME_South_America/", + ], lines); + } + [Fact] public void A_missing_file_is_created() {