From 38ef961478fc9bfb391c4fab1b620777b981f205 Mon Sep 17 00:00:00 2001 From: gmipf Date: Thu, 15 Oct 2026 02:34:04 +0200 Subject: [PATCH] Parse the big-endian directory extent only when ambiguous ParseDirectory parsed the big-endian extent when the extent location was valid (both values equal) instead of when it was ambiguous, so every directory was parsed again, once per directory level. Co-Authored-By: Claude Opus 5.5 --- .../ISO9660Tests.cs | 111 ++++++++++++++++++ SabreTools.Serialization.Readers/ISO9660.cs | 2 +- 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/SabreTools.Serialization.Readers.Test/ISO9660Tests.cs b/SabreTools.Serialization.Readers.Test/ISO9660Tests.cs index 91504c40b..5367a1045 100644 --- a/SabreTools.Serialization.Readers.Test/ISO9660Tests.cs +++ b/SabreTools.Serialization.Readers.Test/ISO9660Tests.cs @@ -1,5 +1,8 @@ +using System.Collections.Generic; using System.IO; using System.Linq; +using SabreTools.Data.Models.ISO9660; +using SabreTools.Numerics; using Xunit; namespace SabreTools.Serialization.Readers.Test @@ -68,5 +71,113 @@ public void InvalidStream_Null() var actual = deserializer.Deserialize(data); Assert.Null(actual); } + + [Fact] + public void ParseDirectory_AmbiguousExtentLocation_ParsesBothExtents() + { + // Little-endian location points to sector 1, big-endian location to sector 2 + byte[] image = new byte[4 * 2048]; + WriteDirectory(image, 1); + WriteDirectory(image, 2); + var dr = new DirectoryRecord + { + ExtentLocation = new BothInt32(1, 2), + ExtentLength = 2048, + FileFlags = FileFlags.DIRECTORY, + }; + + var actual = ISO9660.ParseDirectory(new MemoryStream(image), 2048, 2048, dr, false); + + Assert.NotNull(actual); + Assert.True(actual.ContainsKey(1)); + Assert.True(actual.ContainsKey(2)); + } + + [Fact] + public void ParseDirectory_ConsistentExtentLocation_ReadsEachDirectoryOnce() + { + // Sector 1 holds directory A at sector 2, which holds directory B at sector 3 + byte[] image = new byte[5 * 2048]; + WriteDirectory(image, 1, "A", 2); + WriteDirectory(image, 2, "B", 3); + WriteDirectory(image, 3); + var dr = new DirectoryRecord + { + ExtentLocation = 1, + ExtentLength = 2048, + FileFlags = FileFlags.DIRECTORY, + }; + var stream = new SeekCountingStream(image); + + var actual = ISO9660.ParseDirectory(stream, 2048, 2048, dr, false); + + Assert.NotNull(actual); + Assert.Equal(3, actual.Count); + Assert.Equal(1, stream.SeeksTo(1 * 2048)); + Assert.Equal(1, stream.SeeksTo(2 * 2048)); + Assert.Equal(1, stream.SeeksTo(3 * 2048)); + } + + /// + /// Write a one-sector directory extent with self and parent records and an optional child directory + /// + private static void WriteDirectory(byte[] image, int sector, string? childName = null, int childSector = 0) + { + int offset = sector * 2048; + offset = WriteRecord(image, offset, Constants.CurrentDirectory, sector); + offset = WriteRecord(image, offset, Constants.ParentDirectory, sector); + if (childName is not null) + WriteRecord(image, offset, System.Text.Encoding.ASCII.GetBytes(childName), childSector); + } + + /// + /// Write a directory record for a one-sector directory extent, returns the offset after the record + /// + private static int WriteRecord(byte[] image, int offset, byte[] identifier, int location) + { + int length = 33 + identifier.Length + (identifier.Length % 2 == 0 ? 1 : 0); + image[offset] = (byte)length; + WriteBothEndian(image, offset + 2, location); + WriteBothEndian(image, offset + 10, 2048); + image[offset + 25] = (byte)FileFlags.DIRECTORY; + image[offset + 28] = 1; + image[offset + 31] = 1; + image[offset + 32] = (byte)identifier.Length; + identifier.CopyTo(image, offset + 33); + return offset + length; + } + + /// + /// Write a 32-bit value in both-endian format + /// + private static void WriteBothEndian(byte[] image, int offset, int value) + { + for (int i = 0; i < 4; i++) + { + image[offset + i] = (byte)(value >> (8 * i)); + image[offset + 7 - i] = (byte)(value >> (8 * i)); + } + } + + /// + /// Memory stream that counts absolute seeks per offset + /// + private sealed class SeekCountingStream : MemoryStream + { + private readonly Dictionary _seeks = []; + + public SeekCountingStream(byte[] buffer) : base(buffer) { } + + public int SeeksTo(long offset) + => _seeks.TryGetValue(offset, out int count) ? count : 0; + + public override long Seek(long offset, SeekOrigin loc) + { + if (loc == SeekOrigin.Begin) + _seeks[offset] = SeeksTo(offset) + 1; + + return base.Seek(offset, loc); + } + } } } diff --git a/SabreTools.Serialization.Readers/ISO9660.cs b/SabreTools.Serialization.Readers/ISO9660.cs index ca233422d..7ddd2358e 100644 --- a/SabreTools.Serialization.Readers/ISO9660.cs +++ b/SabreTools.Serialization.Readers/ISO9660.cs @@ -770,7 +770,7 @@ public static List ParsePathTableGroup(Stream data, short sector } // If the extent location field is ambiguous, also parse the big-endian directory extent - if (!bigEndian && dr.ExtentLocation.IsValid) + if (!bigEndian && !dr.ExtentLocation.IsValid) { var bigEndianDir = ParseDirectory(data, sectorLength, blockLength, dr, true); if (bigEndianDir is not null)