Support ISO9660 Extended Attribute Record (#34)

* Support ISO9660 Extended Attribute Record

* fix

* Fix

* Don't extract multi-extent files

* ReadUInt16LittleEndian

* debug

* Fix

* Skip EAR when extracting files

* Add comment about EAR

* Fix same-name extraction

* Safer directory parsing

* even safer

* return null

* more directory record checks

* not nullable nullable

* init system use

* valid records should pass

* review

* remove empty case
This commit is contained in:
Deterous
2025-11-01 10:04:45 +09:00
committed by GitHub
parent e382635b85
commit c2321669b6
4 changed files with 132 additions and 26 deletions

View File

@@ -14,18 +14,19 @@ namespace SabreTools.Data.Models.ISO9660
public byte DirectoryRecordLength { get; set; }
/// <summary>
/// Length of the extended attribute record
/// Length of the extended attribute record (in number of logical blocks)
/// If no extended attribute record is used, set to 0x00
/// </summary>
public byte ExtendedAttributeRecordLength { get; set; }
/// <summary>
/// Logical block number of the first logical block allocated to this extent
/// Extent begins with the extended attribute record (if present)
/// </summary>
public BothInt32 ExtentLocation { get; set; } = 0;
/// <summary>
/// Number of bytes allocated to this extent
/// Number of bytes allocated to this extent (not including extended attribute record length)
/// </summary>
public BothInt32 ExtentLength { get; set; } = 0;

View File

@@ -649,6 +649,11 @@ namespace SabreTools.Serialization.Readers
if (dr.FileFlags.HasFlag(FileFlags.DIRECTORY))
#endif
{
// Start of directory should not be 0
int firstRecordLength = data.PeekByteValue();
if (firstRecordLength == 0)
return null;
// Read all directory records in this directory
var records = new List<DirectoryRecord>();
int pos = 0;
@@ -663,6 +668,16 @@ namespace SabreTools.Serialization.Readers
int paddingLength = sectorLength - (pos % sectorLength);
pos += paddingLength;
_ = data.ReadBytes(paddingLength);
// Finish parsing records if end reached
if (pos >= extentLength)
break;
// Start of sector should not be 0, ignore entire directory
int nextRecordLength = data.PeekByteValue();
if (nextRecordLength <= paddingLength)
return null;
continue;
}
@@ -674,6 +689,12 @@ namespace SabreTools.Serialization.Readers
// Get the next directory record
var directoryRecord = ParseDirectoryRecord(data, false);
// Compare recordLength with number of bytes in directoryRecord and return null if mismatch
var readLength = 33 + directoryRecord.FileIdentifier.Length + (directoryRecord.PaddingField == null ? 0 : 1) + directoryRecord.SystemUse.Length;
if (readLength != recordLength)
return null;
records.Add(directoryRecord);
}
@@ -705,20 +726,25 @@ namespace SabreTools.Serialization.Readers
}
else
{
// 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);
// }
var fileExtent = new FileExtent();
if (dr.ExtendedAttributeRecordLength > 0)
{
var ear = ParseExtendedAttributeRecord(data);
if (ear != null)
{
fileExtent.ExtendedAttributeRecord = ear;
}
// Do not parse file data into file extent, too large
// Put the file extent is the dictionary
if (!directories.ContainsKey(extentLocation))
directories.Add(extentLocation, fileExtent);
}
}
// If the extent location field is ambiguous, also parse the big-endian directory extent
if (!dr.ExtentLocation.IsValid)
if (!bigEndian && dr.ExtentLocation.IsValid)
{
var bigEndianDir = ParseDirectory(data, sectorLength, blockLength, dr, true);
if (bigEndianDir != null)
@@ -806,6 +832,42 @@ namespace SabreTools.Serialization.Readers
return obj;
}
/// <summary>
/// Parse a Stream into a ExtendedAttributeRecord
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="root">true if root directory record, false otherwise</param>
/// <returns>Filled ExtendedAttributeRecord on success, null on error</returns>
public static ExtendedAttributeRecord ParseExtendedAttributeRecord(Stream data)
{
var obj = new ExtendedAttributeRecord();
obj.OwnerIdentification = data.ReadInt16BothEndian();
obj.GroupIdentification = data.ReadInt16BothEndian();
obj.Permissions = (Permissions)data.ReadUInt16LittleEndian();
obj.FileCreationDateTime = ParseDecDateTime(data);
obj.FileModificationDateTime = ParseDecDateTime(data);
obj.FileExpirationDateTime = ParseDecDateTime(data);
obj.FileEffectiveDateTime = ParseDecDateTime(data);
obj.RecordFormat = (RecordFormat)data.ReadByteValue();
obj.RecordAttributes = (RecordAttributes)data.ReadByteValue();
obj.RecordLength = data.ReadInt16BothEndian();
obj.SystemIdentifier = data.ReadBytes(32);
obj.SystemUse = data.ReadBytes(64);
obj.ExtendedAttributeRecordVersion = data.ReadByteValue();
obj.EscapeSequencesLength = data.ReadByteValue();
obj.Reserved64Bytes = data.ReadBytes(64);
obj.ApplicationLength = data.ReadInt16BothEndian();
if (obj.ApplicationLength > 0)
obj.ApplicationUse = data.ReadBytes(obj.ApplicationLength);
if (obj.EscapeSequencesLength > 0)
obj.EscapeSequences = data.ReadBytes(obj.EscapeSequencesLength);
return obj;
}
/// <summary>
/// Parse a Stream into a DirectoryRecordDateTime
/// </summary>

View File

@@ -89,7 +89,7 @@ namespace SabreTools.Serialization.Wrappers
ExtractExtent(dr.ExtentLocation.BigEndian, extractedFiles, encoding, blockLength, outDirTemp, includeDebug);
}
}
else
else if ((dr.FileFlags & FileFlags.MULTI_EXTENT) == 0)
{
// Record is a file extent, extract file
succeeded &= ExtractFile(dr, extractedFiles, encoding, blockLength, false, outputDirectory, includeDebug);
@@ -99,7 +99,10 @@ namespace SabreTools.Serialization.Wrappers
succeeded &= ExtractFile(dr, extractedFiles, encoding, blockLength, true, outputDirectory, includeDebug);
}
}
else
{
if (includeDebug) Console.WriteLine("Extraction of multi-extent files is currently not supported");
}
}
}
@@ -116,7 +119,7 @@ namespace SabreTools.Serialization.Wrappers
return false;
int extentLocation = bigEndian ? dr.ExtentLocation.BigEndian : dr.ExtentLocation.LittleEndian;
int fileOffset = dr.ExtentLocation * blockLength;
int fileOffset = (dr.ExtentLocation + dr.ExtendedAttributeRecordLength) * blockLength;
// Check that the file hasn't been extracted already
if (extractedFiles.ContainsKey(fileOffset))
@@ -134,7 +137,7 @@ namespace SabreTools.Serialization.Wrappers
// TODO: Decode properly (Use VD's separator characters and encoding)
string filename = encoding.GetString(dr.FileIdentifier);
int index = filename.IndexOf(';');
int index = filename.LastIndexOf(';');
if (index > 0)
filename = filename.Substring(0, index);
@@ -143,6 +146,13 @@ namespace SabreTools.Serialization.Wrappers
var directoryName = Path.GetDirectoryName(filename);
if (directoryName != null && !Directory.Exists(directoryName))
Directory.CreateDirectory(directoryName);
// Check that the output file doesn't already exist
if (File.Exists(filename) || Directory.Exists(filename))
{
if (includeDebug) Console.WriteLine($"File/Folder already exists, cannot extract: {filename}");
return false;
}
// Write the output file
if (includeDebug) Console.WriteLine($"Extracting: {filename}");

View File

@@ -353,8 +353,6 @@ namespace SabreTools.Serialization.Wrappers
builder.AppendLine(" -------------------------");
Print(builder, kvp.Value, encoding);
}
builder.AppendLine();
}
private static void Print(StringBuilder builder, FileExtent? extent, Encoding encoding)
@@ -387,19 +385,12 @@ namespace SabreTools.Serialization.Wrappers
else
{
// File extent is a file, print the file's Extended Attribute Record
Print(builder, extent.ExtendedAttributeRecord);
Print(builder, extent.ExtendedAttributeRecord, encoding);
}
builder.AppendLine();
}
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)
@@ -463,6 +454,48 @@ namespace SabreTools.Serialization.Wrappers
builder.AppendLine(tz, " Timezone Offset");
}
private static void Print(StringBuilder builder, ExtendedAttributeRecord? ear, Encoding encoding)
{
builder.AppendLine(" File Extent");
if (ear == null)
return;
builder.AppendLineBothEndian(ear.OwnerIdentification, " Owner Identification");
builder.AppendLineBothEndian(ear.OwnerIdentification, " Group Identification");
builder.AppendLine(" Permissions:");
builder.AppendLine((ear.Permissions & Permissions.SYSTEM_USER_CANNOT_READ) != 0, " System User Cannot Read");
builder.AppendLine((ear.Permissions & Permissions.SYSTEM_USER_CANNOT_EXECUTE) != 0, " System User Cannot Execute");
builder.AppendLine((ear.Permissions & Permissions.OWNER_CANNOT_READ) != 0, " System User Cannot Execute");
builder.AppendLine((ear.Permissions & Permissions.OWNER_CANNOT_EXECUTE) != 0, " System User Cannot Execute");
builder.AppendLine((ear.Permissions & Permissions.GROUP_MEMBER_CANNOT_READ) != 0, " System User Cannot Execute");
builder.AppendLine((ear.Permissions & Permissions.GROUP_MEMBER_CANNOT_EXECUTE) != 0, " System User Cannot Execute");
builder.AppendLine((ear.Permissions & Permissions.NON_GROUP_MEMBER_CANNOT_READ) != 0, " System User Cannot Execute");
builder.AppendLine((ear.Permissions & Permissions.NON_GROUP_MEMBER_CANNOT_EXECUTE) != 0, " System User Cannot Execute");
if ((ear.Permissions & Permissions.PERMISSIONS_MASK) == Permissions.PERMISSIONS_MASK)
builder.AppendLine(" Fixed Bits: All Set");
else
builder.AppendLine(" Fixed Bits: Not All Set");
builder.AppendLine(Format(ear.FileCreationDateTime), " File Creation Date Time");
builder.AppendLine(Format(ear.FileModificationDateTime), " File Modification Date Time");
builder.AppendLine(Format(ear.FileExpirationDateTime), " File Expiration Date Time");
builder.AppendLine(Format(ear.FileEffectiveDateTime), " File Effective Date Time");
builder.AppendLine((byte)ear.RecordFormat, " Record Format:");
builder.AppendLine((byte)ear.RecordAttributes, " Record Attributes");
builder.AppendLineBothEndian(ear.RecordLength, " Record Length");
builder.AppendLine(encoding.GetString(ear.SystemIdentifier), " System Identifier");
builder.AppendLine(ear.SystemUse, " System Use");
builder.AppendLine(ear.ExtendedAttributeRecordVersion, " Extended Attribute Record Version");
builder.AppendLine(ear.EscapeSequencesLength, " Escape Sequences Length");
builder.AppendLine(ear.Reserved64Bytes, " Reserved 64 Bytes");
builder.AppendLineBothEndian(ear.ApplicationLength, " Application Length");
builder.AppendLine(ear.ApplicationUse, " Application Use");
builder.AppendLine(ear.EscapeSequences, " Escape Sequences");
}
#endregion
private static string? Format(DecDateTime? dt)