diff --git a/src/OrthoStreamXP/Scenery/FileSystem/SceneryOverlay.cs b/src/OrthoStreamXP/Scenery/FileSystem/SceneryOverlay.cs index 4a13313..e50ea5a 100644 --- a/src/OrthoStreamXP/Scenery/FileSystem/SceneryOverlay.cs +++ b/src/OrthoStreamXP/Scenery/FileSystem/SceneryOverlay.cs @@ -50,8 +50,9 @@ public SceneryOverlay(SceneryRoots roots, TextureScheduler? textures, bool lazyT /// /// What X-Plane gets for a path. A tile's terrain definition is made in memory. With streaming, a photo texture - /// is streamed level by level unless it is already complete in the cache or the package has its own copy (both - /// are served as they are); everything else, and every texture without streaming, is a backing file from + /// is streamed level by level unless it is already complete in the cache or the lazy store has a copy (both + /// are served as they are; the package's own photo copies are ignored); everything else, and every texture + /// without streaming, is a backing file from /// . /// public OverlayEntry Open(VirtualPath path) @@ -188,7 +189,8 @@ private void ReportTextureRequest(VirtualPath path, RequestOutcome outcome, Time /// /// Merged listing of a directory: source entries, replaced by same-named cache entries. The textures - /// directory merges the source's textures\ and _textures\; _textures itself is not shown. + /// directory merges the source's textures\ and _textures\, without the package's own photo textures; + /// _textures itself is not shown. /// public IReadOnlyList List(VirtualPath directory) { @@ -196,17 +198,22 @@ public IReadOnlyList List(VirtualPath directory) return []; // DSF references terrain definitions by path; X-Plane opens them directly. var entries = new Dictionary(StringComparer.OrdinalIgnoreCase); // Lowest priority first, so later folders replace same-named entries. - foreach (string path in _roots.SourceFolders(directory).Reverse().Append(_roots.CachePath(directory))) + foreach (string path in _roots.SourceFolders(directory).Reverse()) + AddEntries(entries, directory, path, fromSource: true); + AddEntries(entries, directory, _roots.CachePath(directory), fromSource: false); + return [.. entries.Values.OrderBy(e => e.Name, StringComparer.OrdinalIgnoreCase)]; + } + + private static void AddEntries(Dictionary entries, VirtualPath directory, string path, bool fromSource) + { + var folder = new DirectoryInfo(path); + if (!folder.Exists) return; + foreach (FileSystemInfo child in folder.EnumerateFileSystemInfos()) { - var folder = new DirectoryInfo(path); - if (!folder.Exists) continue; - foreach (FileSystemInfo child in folder.EnumerateFileSystemInfos()) - { - if (directory.IsRoot && HiddenRootEntries.Contains(child.Name, StringComparer.OrdinalIgnoreCase)) continue; - entries[child.Name] = child; - } + if (directory.IsRoot && HiddenRootEntries.Contains(child.Name, StringComparer.OrdinalIgnoreCase)) continue; + if (fromSource && SceneryRoots.IsPackagePhoto(directory.Child(child.Name))) continue; + entries[child.Name] = child; } - return [.. entries.Values.OrderBy(e => e.Name, StringComparer.OrdinalIgnoreCase)]; } public (int Terrain, int Textures) RequestCounts() diff --git a/src/OrthoStreamXP/Scenery/FileSystem/SceneryPaths.cs b/src/OrthoStreamXP/Scenery/FileSystem/SceneryPaths.cs index f5142d9..6331362 100644 --- a/src/OrthoStreamXP/Scenery/FileSystem/SceneryPaths.cs +++ b/src/OrthoStreamXP/Scenery/FileSystem/SceneryPaths.cs @@ -100,9 +100,20 @@ public IEnumerable SourceFolders(VirtualPath path) => ? SourceTextureFolders.Select(folder => Path.Join(Source, folder, string.Join('\\', path.Parts[1..]))) : [Path.Join(Source, string.Join('\\', path.Parts))]; - /// The existing source file or directory behind a virtual path, looking in textures\ then _textures\. + /// + /// The existing source file or directory behind a virtual path, looking in textures\ then _textures\. + /// The package's own photo textures are never found (see ). + /// public string? FindInSource(VirtualPath path) => - SourceFolders(path).FirstOrDefault(candidate => File.Exists(candidate) || Directory.Exists(candidate)); + IsPackagePhoto(path) ? null + : SourceFolders(path).FirstOrDefault(candidate => File.Exists(candidate) || Directory.Exists(candidate)); + + /// + /// Whether a path names a photo texture (textures\21904_35824_BI16.dds). The package's own copies of these + /// (made by Ortho4XP or similar) are hidden and never served: photos are always streamed or downloaded, + /// while the package's masks (PNGs) and other files pass through as they are. + /// + public static bool IsPackagePhoto(VirtualPath path) => PhotoTexture(path) is not null; /// /// Where a virtual path is kept in the cache: a photo texture under its cache name (textures\87520_143440_BI18.dds @@ -176,9 +187,10 @@ public bool DdsMatchesSize(VirtualPath path, string file) } /// - /// A file under textures/ that the package itself provides: from the source's textures\ or _textures\, - /// or from the lazy texture store (which holds a textures folder's contents). For a DDS this is the - /// local copy that can be served while, or instead of, a download. + /// A file under textures/ that the package itself provides: from the source's textures\ or _textures\ + /// (never a photo texture, see ), or from the lazy texture store (which holds a + /// textures folder's contents). For a store DDS this is the local copy that can be served while, or instead + /// of, a download. /// public string? FindTexture(VirtualPath path) { diff --git a/tests/OrthoStreamXP.Tests/SceneryOverlayTests.cs b/tests/OrthoStreamXP.Tests/SceneryOverlayTests.cs index 0774800..493ba32 100644 --- a/tests/OrthoStreamXP.Tests/SceneryOverlayTests.cs +++ b/tests/OrthoStreamXP.Tests/SceneryOverlayTests.cs @@ -65,6 +65,34 @@ public void Textures_merges_the_cache_textures_and_underscore_textures_and_hides Assert.Equal(["Earth nav data", "terrain", "textures"], overlay.List(VirtualPath.Parse(@"\")).Select(e => e.Name)); } + [Theory] + [InlineData("textures")] + [InlineData("_textures")] + public void The_packages_own_photo_textures_are_hidden_and_never_served_but_its_pngs_are(string folder) + { + _tree.WriteText($"source/{folder}/21904_35824_BI16.dds", "Ortho4XP photo"); + string mask = _tree.WritePngHeader($"source/{folder}/21904_35824_ZL16.png", 1024); + string transition = _tree.WritePngHeader($"source/{folder}/water_transition.png", 64); + string other = _tree.WriteText($"source/{folder}/custom.dds", "not a photo texture"); + SceneryOverlay overlay = Overlay(); + + Assert.Equal(["21904_35824_ZL16.png", "custom.dds", "water_transition.png"], + overlay.List(VirtualPath.Of("textures")).Select(e => e.Name)); + var notFound = Assert.Throws(() => overlay.Resolve(@"\textures\21904_35824_BI16.dds")); + Assert.Equal(NtStatus.ObjectNameNotFound, notFound.Status); + Assert.Equal(mask, overlay.Resolve(@"\textures\21904_35824_ZL16.png")); + Assert.Equal(transition, overlay.Resolve(@"\textures\water_transition.png")); + Assert.Equal(other, overlay.Resolve(@"\textures\custom.dds")); + } + + [Fact] + public void A_downloaded_photo_texture_is_served_over_the_packages_own_copy() + { + _tree.WriteText("source/textures/87520_143440_BI18.dds", "Ortho4XP photo"); + string cached = _tree.WriteText("cache/textures/10940_17930_PX15.dds", "DDS "); // Overlay() is at level -3 + Assert.Equal(cached, Overlay().Resolve(@"\textures\87520_143440_BI18.dds")); + } + [Fact] public void A_lazy_store_dds_is_served_in_place_without_copying() {