From 85248b0135f4d624893428a3936f93dd81da422b Mon Sep 17 00:00:00 2001
From: Deterous <138427222+Deterous@users.noreply.github.com>
Date: Thu, 30 Oct 2025 23:00:10 +0900
Subject: [PATCH] ISO 9660 Extraction (#31)
* ISO9660 Extraction
* Fix build
* Decode strings in Printer
* Double comment
---
.../Models/ISO9660/Volume.cs | 3 +-
SabreTools.Serialization/Readers/ISO9660.cs | 151 +++++++++--------
.../Wrappers/ISO9660.Extraction.cs | 154 +++++++++++++++++-
.../Wrappers/ISO9660.Printing.cs | 94 +++++++----
SabreTools.Serialization/Wrappers/ISO9660.cs | 5 +-
.../Wrappers/WrapperBase.cs | 2 +-
6 files changed, 301 insertions(+), 108 deletions(-)
diff --git a/SabreTools.Serialization/Models/ISO9660/Volume.cs b/SabreTools.Serialization/Models/ISO9660/Volume.cs
index a3094a18..666456c7 100644
--- a/SabreTools.Serialization/Models/ISO9660/Volume.cs
+++ b/SabreTools.Serialization/Models/ISO9660/Volume.cs
@@ -42,8 +42,9 @@ namespace SabreTools.Data.Models.ISO9660
///
/// Map of sector numbers and the directory at that sector number
/// Each Directory contains child directory and file descriptors
+ /// Note: FileExtent is the base class for DirectoryExtent
///
- public Dictionary DirectoryDescriptors { get; set; }
+ public Dictionary DirectoryDescriptors { get; set; }
#endregion
}
diff --git a/SabreTools.Serialization/Readers/ISO9660.cs b/SabreTools.Serialization/Readers/ISO9660.cs
index 36281bee..2af9e6fc 100644
--- a/SabreTools.Serialization/Readers/ISO9660.cs
+++ b/SabreTools.Serialization/Readers/ISO9660.cs
@@ -577,10 +577,10 @@ namespace SabreTools.Serialization.Readers
/// Stream to parse
/// Number of bytes in a logical sector (usually 2048)
/// Set of volume descriptors for a volume
- /// Filled Dictionary of int to Directory on success, null on error
- public static Dictionary? ParseDirectoryDescriptors(Stream data, short sectorLength, VolumeDescriptor[] vdSet)
+ /// Filled Dictionary of int to FileExtent on success, null on error
+ public static Dictionary? ParseDirectoryDescriptors(Stream data, short sectorLength, VolumeDescriptor[] vdSet)
{
- var directories = new Dictionary();
+ var directories = new Dictionary();
foreach (VolumeDescriptor vd in vdSet)
{
if (vd is not BaseVolumeDescriptor bvd)
@@ -617,85 +617,104 @@ namespace SabreTools.Serialization.Readers
/// Number of bytes in a logical block (usually 2048)
/// Directory record pointing to the directory extent
/// True if the Big Endian extent location/length should be parsed
- /// Filled Dictionary of int to Directory on success, null on error
- public static Dictionary? ParseDirectory(Stream data, short sectorLength, short blockLength, DirectoryRecord dr, bool bigEndian)
+ /// Filled Dictionary of int to FileExtent on success, null on error
+ public static Dictionary? ParseDirectory(Stream data, short sectorLength, short blockLength, DirectoryRecord dr, bool bigEndian)
{
- // Do not parse file extents
-#if NET20 || NET35
- if ((dr.FileFlags & FileFlags.DIRECTORY) == 0)
- return null;
-#else
- if (!dr.FileFlags.HasFlag(FileFlags.DIRECTORY))
- return null;
-#endif
-
+ var directories = new Dictionary();
int blocksPerSector = sectorLength / blockLength;
- // Validate both-endian extent location
- // TODO: Validate both-endian extent length (use the longest / non-zero one)
- int extentLocation = bigEndian ? dr.ExtentLocation.LittleEndian : dr.ExtentLocation.BigEndian;
- int extentLength = bigEndian ? dr.ExtentLength.LittleEndian : dr.ExtentLength.BigEndian;
+ // Use provided extent endinanness
+ int extentLocation = bigEndian ? dr.ExtentLocation.BigEndian : dr.ExtentLocation.LittleEndian;
+ int extentLength = bigEndian ? dr.ExtentLength.BigEndian : dr.ExtentLength.LittleEndian;
- // Validate extent within data stream
- if ((extentLocation * blockLength) + extentLength > data.Length)
+ // Deal with extent length ambiguity
+ if (!dr.ExtentLength.IsValid)
+ {
+ // If provided extent length is invalid, use the other value
+ if (extentLength <= 0 || (extentLocation * blockLength) + extentLength > data.Length)
+ extentLength = bigEndian ? dr.ExtentLength.LittleEndian : dr.ExtentLength.BigEndian;
+ }
+
+ // Validate extent length
+ if (extentLength <= 0 || (extentLocation * blockLength) + extentLength > data.Length)
return null;
// Move stream to directory location
data.SeekIfPossible(extentLocation * blockLength, SeekOrigin.Begin);
- // Read all directory records in this directory
- var records = new List();
- int pos = 0;
- while (pos < extentLength)
+ // Check if the current extent is a directory
+#if NET20 || NET35
+ if ((dr.FileFlags & FileFlags.DIRECTORY) == FileFlags.DIRECTORY)
+#else
+ if (dr.FileFlags.HasFlag(FileFlags.DIRECTORY))
+#endif
{
- // Peek next byte to check whether the next record length is not greater than the end of the dir extent
- var recordLength = data.PeekByteValue();
-
- // If record length of 0x00, next record begins in next sector
- if (recordLength == 0)
+ // Read all directory records in this directory
+ var records = new List();
+ int pos = 0;
+ while (pos < extentLength)
{
- int paddingLength = sectorLength - (pos % sectorLength);
- pos += paddingLength;
- _ = data.ReadBytes(paddingLength);
- continue;
+ // Peek next byte to check whether the next record length is not greater than the end of the dir extent
+ var recordLength = data.PeekByteValue();
+
+ // If record length of 0x00, next record begins in next sector
+ if (recordLength == 0)
+ {
+ int paddingLength = sectorLength - (pos % sectorLength);
+ pos += paddingLength;
+ _ = data.ReadBytes(paddingLength);
+ continue;
+ }
+
+ // Ensure record will end in this extent
+ // TODO: Smartly detect record length for invalid record lengths
+ pos += recordLength;
+ if (pos > extentLength)
+ break;
+
+ // Get the next directory record
+ var directoryRecord = ParseDirectoryRecord(data, false);
+ records.Add(directoryRecord);
}
- // Ensure record will end in this extent
- // TODO: Smartly detect record length for invalid record lengths
- pos += recordLength;
- if (pos > extentLength)
- break;
+ // Add current directory to dictionary
+ var currentDirectory = new DirectoryExtent();
+ currentDirectory.DirectoryRecords = [.. records];
+ directories.Add(extentLocation * blocksPerSector, currentDirectory);
- // Get the next directory record
- var directoryRecord = ParseDirectoryRecord(data, false);
- records.Add(directoryRecord);
+ // Add all child directories to dictionary recursively
+ foreach (var record in records)
+ {
+ // Don't traverse to parent or self
+ if (record.FileIdentifier.EqualsExactly(Constants.CurrentDirectory) || record.FileIdentifier.EqualsExactly(Constants.ParentDirectory))
+ continue;
+
+ // Recursively parse child directory
+ int sectorNum = record.ExtentLocation * blocksPerSector;
+ var dir = ParseDirectory(data, sectorLength, blockLength, record, false);
+ if (dir == null)
+ continue;
+
+ // Add new directories to dictionary
+ foreach (var kvp in dir)
+ {
+ if (!directories.ContainsKey(kvp.Key))
+ directories.Add(kvp.Key, kvp.Value);
+ }
+ }
}
-
- // Add current directory to dictionary
- var directories = new Dictionary();
- var currentDirectory = new DirectoryExtent();
- currentDirectory.DirectoryRecords = [.. records];
- directories.Add(extentLocation * blocksPerSector, currentDirectory);
-
- // Add all child directories to dictionary recursively
- foreach (var record in records)
+ else
{
- // Don't traverse to parent or self
- if (record.FileIdentifier.EqualsExactly(Constants.CurrentDirectory) || record.FileIdentifier.EqualsExactly(Constants.ParentDirectory))
- continue;
-
- // Recursively parse child directory
- int sectorNum = record.ExtentLocation * blocksPerSector;
- var dir = ParseDirectory(data, sectorLength, blockLength, record, false);
- if (dir == null)
- continue;
-
- // Add new directories to dictionary
- foreach (var kvp in dir)
- {
- if (!directories.ContainsKey(kvp.Key))
- directories.Add(kvp.Key, kvp.Value);
- }
+ // TODO: Create ParseExtendedAttributeRecord()
+ // Extent is a file, parse the Extended Attribute Record
+ // var ear = ParseExtendedAttributeRecord();
+ // if (ear != null)
+ // {
+ // var fileExtent = new FileExtent();
+ // fileExtent.ExtendedAttributeRecord = ear;
+ // if (!directories.ContainsKey(extentLocation))
+ // directories.Add(extentLocation, fileExtent);
+ // }
}
// If the extent location field is ambiguous, also parse the big-endian directory extent
diff --git a/SabreTools.Serialization/Wrappers/ISO9660.Extraction.cs b/SabreTools.Serialization/Wrappers/ISO9660.Extraction.cs
index 1df18731..7c2f1697 100644
--- a/SabreTools.Serialization/Wrappers/ISO9660.Extraction.cs
+++ b/SabreTools.Serialization/Wrappers/ISO9660.Extraction.cs
@@ -1,6 +1,11 @@
using System;
+using System.Collections.Generic;
using System.IO;
+using System.Text;
using SabreTools.Data.Models.ISO9660;
+using SabreTools.Data.Extensions;
+using SabreTools.IO;
+using SabreTools.IO.Extensions;
namespace SabreTools.Serialization.Wrappers
{
@@ -9,15 +14,156 @@ namespace SabreTools.Serialization.Wrappers
///
public bool Extract(string outputDirectory, bool includeDebug)
{
- // If we have no path tables or directory descriptors, there is nothing to extract
- if (PathTableGroups.Length == 0 && DirectoryDescriptors.Count == 0)
+ // If we have no volume or directory descriptors, there is nothing to extract
+ if (VolumeDescriptorSet.Length == 0 || DirectoryDescriptors.Count == 0)
return true;
- bool allExtracted = false;
+ bool allExtracted = true;
- // TODO: Extract all directories and file extents
+ // Determine and validate sector length, default to 2048
+ short sectorLength = (short)(SystemArea.Length / 16);
+ if (sectorLength < 2048 || (sectorLength & (sectorLength - 1)) != 0)
+ sectorLength = 2048;
+
+ // Keep track of extracted files according to their byte location
+ // Note: Using Dictionary instead of HashSet because .NET Framework doesn't support HashSet
+ var extractedFiles = new Dictionary();
+
+ // Loop through all Base Volume Descriptors to extract files from each directory hierarchy
+ // Note: This will prioritize the last volume descriptor directory hierarchies first (prioritises those filenames)
+ for (int i = VolumeDescriptorSet.Length - 1; i >= 0; i--)
+ {
+ if (VolumeDescriptorSet[i] is BaseVolumeDescriptor bvd)
+ {
+ var rootDir = bvd.RootDirectoryRecord;
+
+ var blockLength = bvd.GetLogicalBlockSize(sectorLength);
+
+ // TODO: Better encoding detection (EscapeSequences)
+ var encoding = Encoding.UTF8;
+ if (bvd is SupplementaryVolumeDescriptor svd)
+ encoding = Encoding.BigEndianUnicode;
+
+ // Extract all files within root directory hierarchy
+ allExtracted &= ExtractExtent(rootDir.ExtentLocation.LittleEndian, extractedFiles, encoding, blockLength, outputDirectory, includeDebug);
+ // If Big Endian extent location differs from Little Endian extent location, also extract that directory hierarchy
+ if (!rootDir.ExtentLocation.IsValid)
+ {
+ if (includeDebug) Console.WriteLine($"Extracting from volume descriptor (big endian root dir location)");
+ allExtracted &= ExtractExtent(rootDir.ExtentLocation.BigEndian, extractedFiles, encoding, blockLength, outputDirectory, includeDebug);
+ }
+ }
+ }
return allExtracted;
}
+
+ ///
+ /// Extract all files from within a directory/file extent
+ ///
+ private bool ExtractExtent(int extentLocation, Dictionary extractedFiles, Encoding encoding, int blockLength, string outputDirectory, bool includeDebug)
+ {
+ // Check that directory exists in model
+ if (!DirectoryDescriptors.ContainsKey(extentLocation))
+ return false;
+
+ bool succeeded = true;
+ if (DirectoryDescriptors[extentLocation] is DirectoryExtent dir)
+ {
+ foreach (var dr in dir.DirectoryRecords)
+ {
+ // Recurse if record is directory
+ if ((dr.FileFlags & FileFlags.DIRECTORY) == FileFlags.DIRECTORY)
+ {
+ // Don't recurse up or self
+ if (dr.FileIdentifier.EqualsExactly(Constants.CurrentDirectory) || dr.FileIdentifier.EqualsExactly(Constants.ParentDirectory))
+ continue;
+
+ // Append directory name
+ string outDirTemp = Path.Combine(outputDirectory, encoding.GetString(dr.FileIdentifier));
+ if (includeDebug) Console.WriteLine($"Extracting to directory: {outDirTemp}");
+ ExtractExtent(dr.ExtentLocation.LittleEndian, extractedFiles, encoding, blockLength, outDirTemp, includeDebug);
+
+ // Also extract from BigEndian values if ambiguous
+ if (!dr.ExtentLocation.IsValid!)
+ {
+ ExtractExtent(dr.ExtentLocation.BigEndian, extractedFiles, encoding, blockLength, outDirTemp, includeDebug);
+ }
+ }
+ else
+ {
+ // Record is a file extent, extract file
+ succeeded &= ExtractFile(dr, extractedFiles, encoding, blockLength, false, outputDirectory, includeDebug);
+ // Also extract from BigEndian values if ambiguous
+ if (!dr.ExtentLocation.IsValid!)
+ {
+ succeeded &= ExtractFile(dr, extractedFiles, encoding, blockLength, true, outputDirectory, includeDebug);
+ }
+ }
+
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Extract file pointed to by a directory record
+ ///
+ private bool ExtractFile(DirectoryRecord dr, Dictionary extractedFiles, Encoding encoding, int blockLength, bool bigEndian, string outputDirectory, bool includeDebug)
+ {
+ // Cannot extract file if it is a directory
+ if ((dr.FileFlags & FileFlags.DIRECTORY) == FileFlags.DIRECTORY)
+ return false;
+
+ int extentLocation = bigEndian ? dr.ExtentLocation.BigEndian : dr.ExtentLocation.LittleEndian;
+ int fileOffset = dr.ExtentLocation * blockLength;
+
+ // Check that the file hasn't been extracted already
+ if (extractedFiles.ContainsKey(fileOffset))
+ return true;
+
+ const int chunkSize = 2048 * 1024;
+ lock (_dataSourceLock)
+ {
+ _dataSource.SeekIfPossible(fileOffset, SeekOrigin.Begin);
+
+ // Get the length, and make sure it won't EOF
+ int length = dr.ExtentLength;
+ if (length > _dataSource.Length - _dataSource.Position)
+ return false;
+
+ // TODO: Decode properly (Use VD's separator characters and encoding)
+ string filename = encoding.GetString(dr.FileIdentifier);
+ int index = filename.IndexOf(';');
+ if (index > 0)
+ filename = filename.Substring(0, index);
+
+ // Ensure the full output directory exists
+ filename = Path.Combine(outputDirectory, filename);
+ var directoryName = Path.GetDirectoryName(filename);
+ if (directoryName != null && !Directory.Exists(directoryName))
+ Directory.CreateDirectory(directoryName);
+
+ // Write the output file
+ if (includeDebug) Console.WriteLine($"Extracting: {filename}");
+ using var fs = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
+ while (length > 0)
+ {
+ int bytesToRead = (int)Math.Min(length, chunkSize);
+
+ byte[] buffer = _dataSource.ReadBytes(bytesToRead);
+ fs.Write(buffer, 0, bytesToRead);
+ fs.Flush();
+
+ length -= bytesToRead;
+ }
+
+ // Mark the file as extracted
+ extractedFiles.Add(fileOffset, dr.ExtentLength);
+ }
+
+ return true;
+ }
}
}
diff --git a/SabreTools.Serialization/Wrappers/ISO9660.Printing.cs b/SabreTools.Serialization/Wrappers/ISO9660.Printing.cs
index 59864bd2..34487470 100644
--- a/SabreTools.Serialization/Wrappers/ISO9660.Printing.cs
+++ b/SabreTools.Serialization/Wrappers/ISO9660.Printing.cs
@@ -29,8 +29,11 @@ namespace SabreTools.Serialization.Wrappers
builder.AppendLine();
Print(builder, Model.VolumeDescriptorSet);
- Print(builder, Model.PathTableGroups);
- Print(builder, Model.DirectoryDescriptors);
+
+ // TODO: Parse the volume descriptors to print the Path Table Groups and Directory Descriptors with proper encoding
+ Encoding encoding = Encoding.UTF8;
+ Print(builder, Model.PathTableGroups, encoding);
+ Print(builder, Model.DirectoryDescriptors, encoding);
}
#region Volume Descriptors
@@ -96,12 +99,16 @@ namespace SabreTools.Serialization.Wrappers
builder.AppendLine(" Unidentified Base Volume Descriptor:");
builder.AppendLine(" -------------------------");
+ // Default to UTF-8 deocding (note: Spec says PVD uses subset of ASCII, but UTF-8 is used here as it is a strict superset)
+ Encoding encoding = Encoding.UTF8;
if (vd is PrimaryVolumeDescriptor pvd)
{
builder.AppendLine(pvd.UnusedByte, " Unused Byte");
}
else if (vd is SupplementaryVolumeDescriptor svd)
{
+ // Decode strings using UTF-16 BigEndian (note: Spec says SVD uses UCS-2, but UTF-16 is used here as it is a strict superset)
+ encoding = Encoding.BigEndianUnicode;
builder.AppendLine(" Volume Flags:");
#if NET20 || NET35
builder.AppendLine((svd.VolumeFlags & VolumeFlags.UNREGISTERED_ESCAPE_SEQUENCES) != 0, " Unregistered Escape Sequences");
@@ -114,10 +121,10 @@ namespace SabreTools.Serialization.Wrappers
builder.AppendLine("Zeroed", " Reserved Flags");
}
- // TODO: Decode all byte arrays into strings (based on encoding above)
+ // TODO: Better string decoding (based on spec, EscapeSequences, and detection)
- builder.AppendLine(vd.SystemIdentifier, " System Identifier");
- builder.AppendLine(vd.VolumeIdentifier, " Volume Identifier");
+ builder.AppendLine(encoding.GetString(vd.SystemIdentifier), " System Identifier");
+ builder.AppendLine(encoding.GetString(vd.VolumeIdentifier), " Volume Identifier");
if (vd.Unused8Bytes != null && Array.TrueForAll(vd.Unused8Bytes, b => b == 0))
@@ -150,15 +157,15 @@ namespace SabreTools.Serialization.Wrappers
builder.AppendLine(vd.OptionalPathTableLocationM, " Optional Type-M Path Table Location");
builder.AppendLine(" Root Directory Record:");
- Print(builder, vd.RootDirectoryRecord);
+ Print(builder, vd.RootDirectoryRecord, encoding);
- builder.AppendLine(vd.VolumeSetIdentifier, " Volume Set Identifier");
- builder.AppendLine(vd.PublisherIdentifier, " Publisher Identifier");
- builder.AppendLine(vd.DataPreparerIdentifier, " Data Preparer Identifier");
- builder.AppendLine(vd.ApplicationIdentifier, " Application Identifier");
- builder.AppendLine(vd.CopyrightFileIdentifier, " Copyright Identifier");
- builder.AppendLine(vd.AbstractFileIdentifier, " Abstract Identifier");
- builder.AppendLine(vd.BibliographicFileIdentifier, " Bibliographic Identifier");
+ builder.AppendLine(encoding.GetString(vd.VolumeSetIdentifier), " Volume Set Identifier");
+ builder.AppendLine(encoding.GetString(vd.PublisherIdentifier), " Publisher Identifier");
+ builder.AppendLine(encoding.GetString(vd.DataPreparerIdentifier), " Data Preparer Identifier");
+ builder.AppendLine(encoding.GetString(vd.ApplicationIdentifier), " Application Identifier");
+ builder.AppendLine(encoding.GetString(vd.CopyrightFileIdentifier), " Copyright Identifier");
+ builder.AppendLine(encoding.GetString(vd.AbstractFileIdentifier), " Abstract Identifier");
+ builder.AppendLine(encoding.GetString(vd.BibliographicFileIdentifier), " Bibliographic Identifier");
builder.AppendLine(Format(vd.VolumeCreationDateTime), " Volume Creation Date Time:");
builder.AppendLine(Format(vd.VolumeModificationDateTime), " Volume Modification Date Time:");
@@ -240,7 +247,7 @@ namespace SabreTools.Serialization.Wrappers
#region Path Tables
- private static void Print(StringBuilder builder, PathTableGroup[]? ptgs)
+ private static void Print(StringBuilder builder, PathTableGroup[]? ptgs, Encoding encoding)
{
builder.AppendLine(" Path Table Group(s):");
builder.AppendLine(" -------------------------");
@@ -257,7 +264,7 @@ namespace SabreTools.Serialization.Wrappers
{
builder.AppendLine($" Type-L Path Table {tableNum}:");
builder.AppendLine(" -------------------------");
- Print(builder, ptgs[tableNum].PathTableL);
+ Print(builder, ptgs[tableNum].PathTableL, encoding);
}
else
{
@@ -268,7 +275,7 @@ namespace SabreTools.Serialization.Wrappers
{
builder.AppendLine($" Optional Type-L Path Table {tableNum}:");
builder.AppendLine(" -------------------------");
- Print(builder, ptgs[tableNum].OptionalPathTableL);
+ Print(builder, ptgs[tableNum].OptionalPathTableL, encoding);
}
else
{
@@ -279,7 +286,7 @@ namespace SabreTools.Serialization.Wrappers
{
builder.AppendLine($" Type-M Path Table {tableNum}:");
builder.AppendLine(" -------------------------");
- Print(builder, ptgs[tableNum].PathTableM);
+ Print(builder, ptgs[tableNum].PathTableM, encoding);
}
else
{
@@ -290,7 +297,7 @@ namespace SabreTools.Serialization.Wrappers
{
builder.AppendLine($" Optional Type-M Path Table {tableNum}:");
builder.AppendLine(" -------------------------");
- Print(builder, ptgs[tableNum].OptionalPathTableM);
+ Print(builder, ptgs[tableNum].OptionalPathTableM, encoding);
}
else
{
@@ -302,7 +309,7 @@ namespace SabreTools.Serialization.Wrappers
builder.AppendLine();
}
- private static void Print(StringBuilder builder, PathTableRecord[] records)
+ private static void Print(StringBuilder builder, PathTableRecord[] records, Encoding encoding)
{
if (records.Length == 0)
{
@@ -329,7 +336,7 @@ namespace SabreTools.Serialization.Wrappers
#region Directories
- private static void Print(StringBuilder builder, Dictionary? dirs)
+ private static void Print(StringBuilder builder, Dictionary? dirs, Encoding encoding)
{
builder.AppendLine(" Directory Descriptors Information:");
builder.AppendLine(" -------------------------");
@@ -344,39 +351,56 @@ namespace SabreTools.Serialization.Wrappers
{
builder.AppendLine($" Directory at Sector {kvp.Key}");
builder.AppendLine(" -------------------------");
- Print(builder, kvp.Value);
+ Print(builder, kvp.Value, encoding);
}
builder.AppendLine();
}
- private static void Print(StringBuilder builder, DirectoryExtent? dir)
+ private static void Print(StringBuilder builder, FileExtent? extent, Encoding encoding)
{
- if (dir == null)
+ if (extent == null)
{
builder.AppendLine(" No directory descriptor");
builder.AppendLine();
return;
}
- if (dir.DirectoryRecords == null)
- {
- builder.AppendLine(" No directory records");
- builder.AppendLine();
- return;
- }
- for (int recordNum = 0; recordNum < dir.DirectoryRecords.Length; recordNum++)
+ if (extent is DirectoryExtent dir)
{
- builder.AppendLine($" Directory Record {recordNum}:");
- builder.AppendLine(" -------------------------");
- Print(builder, dir.DirectoryRecords[recordNum]);
- builder.AppendLine();
+ if (dir.DirectoryRecords == null)
+ {
+ builder.AppendLine(" No directory records");
+ builder.AppendLine();
+ return;
+ }
+
+ // File extent is a directory, print all directory records
+ for (int recordNum = 0; recordNum < dir.DirectoryRecords.Length; recordNum++)
+ {
+ builder.AppendLine($" Directory Record {recordNum}:");
+ builder.AppendLine(" -------------------------");
+ Print(builder, dir.DirectoryRecords[recordNum], encoding);
+ builder.AppendLine();
+ }
+ }
+ else
+ {
+ // File extent is a file, print the file's Extended Attribute Record
+ Print(builder, extent.ExtendedAttributeRecord);
}
builder.AppendLine();
}
- private static void Print(StringBuilder builder, DirectoryRecord? dr)
+ private static void Print(StringBuilder builder, ExtendedAttributeRecord? ear)
+ {
+ // TODO: Implement ExtendedAttributeRecord printing
+
+ return;
+ }
+
+ private static void Print(StringBuilder builder, DirectoryRecord? dr, Encoding encoding)
{
if (dr == null)
{
diff --git a/SabreTools.Serialization/Wrappers/ISO9660.cs b/SabreTools.Serialization/Wrappers/ISO9660.cs
index 169324e3..19e2c593 100644
--- a/SabreTools.Serialization/Wrappers/ISO9660.cs
+++ b/SabreTools.Serialization/Wrappers/ISO9660.cs
@@ -15,6 +15,9 @@ namespace SabreTools.Serialization.Wrappers
#region Extension Properties
+ ///
+ public byte[] SystemArea => Model.SystemArea ?? [];
+
///
public VolumeDescriptor[] VolumeDescriptorSet => Model.VolumeDescriptorSet ?? [];
@@ -22,7 +25,7 @@ namespace SabreTools.Serialization.Wrappers
public PathTableGroup[] PathTableGroups => Model.PathTableGroups ?? [];
///
- public Dictionary DirectoryDescriptors => Model.DirectoryDescriptors ?? [];
+ public Dictionary DirectoryDescriptors => Model.DirectoryDescriptors ?? [];
#endregion
diff --git a/SabreTools.Serialization/Wrappers/WrapperBase.cs b/SabreTools.Serialization/Wrappers/WrapperBase.cs
index 1f94753d..deffd9cd 100644
--- a/SabreTools.Serialization/Wrappers/WrapperBase.cs
+++ b/SabreTools.Serialization/Wrappers/WrapperBase.cs
@@ -148,7 +148,7 @@ namespace SabreTools.Serialization.Wrappers
#region Data
///
- /// Read a number of bytes from an offset fomr the data source, if possible
+ /// Read a number of bytes from an offset from the data source, if possible
///
/// Offset within the data source to start reading
/// Number of bytes to read from the offset