Parse the big-endian directory extent only when ambiguous (#98)

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 <noreply@anthropic.com>
This commit is contained in:
gmipf
2026-10-15 03:16:14 +02:00
committed by GitHub
parent 04e8751f9b
commit 2f6698edac
2 changed files with 112 additions and 1 deletions

View File

@@ -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 @@ namespace SabreTools.Serialization.Readers.Test
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));
}
/// <summary>
/// Write a one-sector directory extent with self and parent records and an optional child directory
/// </summary>
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);
}
/// <summary>
/// Write a directory record for a one-sector directory extent, returns the offset after the record
/// </summary>
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;
}
/// <summary>
/// Write a 32-bit value in both-endian format
/// </summary>
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));
}
}
/// <summary>
/// Memory stream that counts absolute seeks per offset
/// </summary>
private sealed class SeekCountingStream : MemoryStream
{
private readonly Dictionary<long, int> _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);
}
}
}
}

View File

@@ -770,7 +770,7 @@ namespace SabreTools.Serialization.Readers
}
// 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)