diff --git a/SabreTools.Serialization/Readers/ISO9660.cs b/SabreTools.Serialization/Readers/ISO9660.cs index 384f519f..f9061bc4 100644 --- a/SabreTools.Serialization/Readers/ISO9660.cs +++ b/SabreTools.Serialization/Readers/ISO9660.cs @@ -171,31 +171,6 @@ namespace SabreTools.Serialization.Readers var obj = new PrimaryVolumeDescriptor(); obj.Type = VolumeDescriptorType.PRIMARY_VOLUME_DESCRIPTOR; - return (PrimaryVolumeDescriptor?)ParseBaseVolumeDescriptor(data, sectorLength, obj); - } - - /// - /// Parse a Stream into a SupplementaryVolumeDescriptor - /// - /// Stream to parse - /// Number of bytes in a logical sector (usually 2048) - /// Filled SupplementaryVolumeDescriptor on success, null on error - public static SupplementaryVolumeDescriptor? ParseSupplementaryVolumeDescriptor(Stream data, short sectorLength) - { - var obj = new SupplementaryVolumeDescriptor(); - - obj.Type = VolumeDescriptorType.SUPPLEMENTARY_VOLUME_DESCRIPTOR; - return (SupplementaryVolumeDescriptor?)ParseBaseVolumeDescriptor(data, sectorLength, obj); - } - - /// - /// Parse a Stream into a BaseVolumeDescriptor - /// - /// Stream to parse - /// Number of bytes in a logical sector (usually 2048) - /// Filled BaseVolumeDescriptor on success, null on error - public static BaseVolumeDescriptor? ParseBaseVolumeDescriptor(Stream data, short sectorLength, BaseVolumeDescriptor obj) - { obj.Identifier = data.ReadBytes(5); // Validate Identifier, return null and rewind if invalid @@ -206,36 +181,12 @@ namespace SabreTools.Serialization.Readers } obj.Version = data.ReadByteValue(); - - // Read the child-specific field - if (obj is PrimaryVolumeDescriptor objPVD) - objPVD.UnusedByte = data.ReadByteValue(); - else if (obj is SupplementaryVolumeDescriptor objSVD) - objSVD.VolumeFlags = (VolumeFlags)data.ReadByteValue(); - else - { - // Rewind and return for unknown descriptor - data.SeekIfPossible(-8, SeekOrigin.Current); - return null; - } - + obj.UnusedByte = data.ReadByteValue(); obj.SystemIdentifier = data.ReadBytes(32); obj.VolumeIdentifier = data.ReadBytes(32); obj.Unused8Bytes = data.ReadBytes(8); obj.VolumeSpaceSize = data.ReadInt32BothEndian(); - - // Read the child-specific field - if (obj is PrimaryVolumeDescriptor objPVD2) - objPVD2.Unused32Bytes = data.ReadBytes(32); - else if (obj is SupplementaryVolumeDescriptor objSVD2) - objSVD2.EscapeSequences = data.ReadBytes(32); - else - { - // Rewind and return for unknown descriptor - data.SeekIfPossible(-120, SeekOrigin.Current); - return null; - } - + obj.Unused32Bytes = data.ReadBytes(32); obj.VolumeSetSize = data.ReadInt16BothEndian(); obj.VolumeSequenceNumber = data.ReadInt16BothEndian(); obj.LogicalBlockSize = data.ReadInt16BothEndian(); @@ -248,6 +199,7 @@ namespace SabreTools.Serialization.Readers var dr = ParseDirectoryRecord(data, true); if (dr == null) return null; + obj.RootDirectoryRecord = dr; obj.VolumeSetIdentifier = data.ReadBytes(128); @@ -270,7 +222,74 @@ namespace SabreTools.Serialization.Readers // Skip remainder of the logical sector if (sectorLength > Constants.MinimumSectorSize) - data.SeekIfPossible(sectorLength - Constants.MinimumSectorSize, SeekOrigin.Current); + _ = data.ReadBytes(sectorLength - Constants.MinimumSectorSize); + + return obj; + } + + /// + /// Parse a Stream into a SupplementaryVolumeDescriptor + /// + /// Stream to parse + /// Number of bytes in a logical sector (usually 2048) + /// Filled SupplementaryVolumeDescriptor on success, null on error + public static SupplementaryVolumeDescriptor? ParseSupplementaryVolumeDescriptor(Stream data, short sectorLength) + { + var obj = new SupplementaryVolumeDescriptor(); + + obj.Type = VolumeDescriptorType.SUPPLEMENTARY_VOLUME_DESCRIPTOR; + obj.Identifier = data.ReadBytes(5); + + // Validate Identifier, return null and rewind if invalid + if (!obj.Identifier.EqualsExactly(Constants.StandardIdentifier)) + { + data.SeekIfPossible(-6, SeekOrigin.Current); + return null; + } + + obj.Version = data.ReadByteValue(); + obj.VolumeFlags = (VolumeFlags)data.ReadByteValue(); + obj.SystemIdentifier = data.ReadBytes(32); + obj.VolumeIdentifier = data.ReadBytes(32); + obj.Unused8Bytes = data.ReadBytes(8); + obj.VolumeSpaceSize = data.ReadInt32BothEndian(); + obj.EscapeSequences = data.ReadBytes(32); + obj.VolumeSetSize = data.ReadInt16BothEndian(); + obj.VolumeSequenceNumber = data.ReadInt16BothEndian(); + obj.LogicalBlockSize = data.ReadInt16BothEndian(); + obj.PathTableSize = data.ReadInt32BothEndian(); + obj.PathTableLocationL = data.ReadInt32LittleEndian(); + obj.OptionalPathTableLocationL = data.ReadInt32LittleEndian(); + obj.PathTableLocationM = data.ReadInt32BigEndian(); + obj.OptionalPathTableLocationM = data.ReadInt32BigEndian(); + + var dr = ParseDirectoryRecord(data, true); + if (dr == null) + return null; + + obj.RootDirectoryRecord = dr; + + obj.VolumeSetIdentifier = data.ReadBytes(128); + obj.PublisherIdentifier = data.ReadBytes(128); + obj.DataPreparerIdentifier = data.ReadBytes(128); + obj.ApplicationIdentifier = data.ReadBytes(128); + obj.CopyrightFileIdentifier = data.ReadBytes(37); + obj.AbstractFileIdentifier = data.ReadBytes(37); + obj.BibliographicFileIdentifier = data.ReadBytes(37); + + obj.VolumeCreationDateTime = ParseDecDateTime(data); + obj.VolumeModificationDateTime = ParseDecDateTime(data); + obj.VolumeExpirationDateTime = ParseDecDateTime(data); + obj.VolumeEffectiveDateTime = ParseDecDateTime(data); + + obj.FileStructureVersion = data.ReadByteValue(); + obj.ReservedByte = data.ReadByteValue(); + obj.ApplicationUse = data.ReadBytes(512); + obj.Reserved653Bytes = data.ReadBytes(653); + + // Skip remainder of the logical sector + if (sectorLength > Constants.MinimumSectorSize) + _ = data.ReadBytes(sectorLength - Constants.MinimumSectorSize); return obj; } @@ -305,7 +324,7 @@ namespace SabreTools.Serialization.Readers // Skip remainder of the logical sector if (sectorLength > Constants.MinimumSectorSize) - data.SeekIfPossible(sectorLength - Constants.MinimumSectorSize, SeekOrigin.Current); + _ = data.ReadBytes(sectorLength - Constants.MinimumSectorSize); return obj; } @@ -335,7 +354,7 @@ namespace SabreTools.Serialization.Readers // Skip remainder of the logical sector if (sectorLength > Constants.MinimumSectorSize) - data.SeekIfPossible(sectorLength - Constants.MinimumSectorSize, SeekOrigin.Current); + _ = data.ReadBytes(sectorLength - Constants.MinimumSectorSize); return obj; } @@ -366,7 +385,7 @@ namespace SabreTools.Serialization.Readers // Skip remainder of the logical sector if (sectorLength > Constants.MinimumSectorSize) - data.SeekIfPossible(sectorLength - Constants.MinimumSectorSize, SeekOrigin.Current); + _ = data.ReadBytes(sectorLength - Constants.MinimumSectorSize); return obj; } @@ -387,13 +406,13 @@ namespace SabreTools.Serialization.Readers var groups = new List(); foreach (VolumeDescriptor vd in vdSet) { - if (vd is BaseVolumeDescriptor bvd) - { - // Parse the path table group in the base volume descriptor - var pathTableGroups = ParsePathTableGroup(data, sectorLength, bvd); - if (pathTableGroups != null && pathTableGroups.Count > 0) - groups.AddRange(pathTableGroups); - } + if (vd is not BaseVolumeDescriptor bvd) + continue; + + // Parse the path table group in the base volume descriptor + var pathTableGroups = ParsePathTableGroup(data, sectorLength, bvd); + if (pathTableGroups != null && pathTableGroups.Count > 0) + groups.AddRange(pathTableGroups); } // Return error (null) if no valid path table groups were found @@ -427,22 +446,22 @@ namespace SabreTools.Serialization.Readers if (locationL != 0 && ((locationL * blockLength) + sizeL) < data.Length) { data.SeekIfPossible(locationL * blockLength, SeekOrigin.Begin); - groupL.PathTableL = ParsePathTable(data, sectorLength, sizeL, true); + groupL.PathTableL = ParsePathTable(data, sizeL, true); } if (locationL2 != 0 && ((locationL2 * blockLength) + sizeL) < data.Length) { data.SeekIfPossible(locationL2 * blockLength, SeekOrigin.Begin); - groupL.OptionalPathTableL = ParsePathTable(data, sectorLength, sizeL, true); + groupL.OptionalPathTableL = ParsePathTable(data, sizeL, true); } if (locationM != 0 && ((locationM * blockLength) + sizeL) < data.Length) { data.SeekIfPossible(locationM * blockLength, SeekOrigin.Begin); - groupL.PathTableM = ParsePathTable(data, sectorLength, sizeL, false); + groupL.PathTableM = ParsePathTable(data, sizeL, false); } if (locationM2 != 0 && ((locationM2 * blockLength) + sizeL) < data.Length) { data.SeekIfPossible(locationM2 * blockLength, SeekOrigin.Begin); - groupL.OptionalPathTableM = ParsePathTable(data, sectorLength, sizeL, false); + groupL.OptionalPathTableM = ParsePathTable(data, sizeL, false); } // If no valid path tables were found, don't add the table group @@ -458,22 +477,22 @@ namespace SabreTools.Serialization.Readers if (locationL != 0 && ((locationL * blockLength) + sizeB) < data.Length) { data.SeekIfPossible(locationL * blockLength, SeekOrigin.Begin); - groupB.PathTableL = ParsePathTable(data, sectorLength, sizeB, true); + groupB.PathTableL = ParsePathTable(data, sizeB, true); } if (locationL2 != 0 && ((locationL2 * blockLength) + sizeB) < data.Length) { data.SeekIfPossible(locationL2 * blockLength, SeekOrigin.Begin); - groupB.OptionalPathTableL = ParsePathTable(data, sectorLength, sizeB, true); + groupB.OptionalPathTableL = ParsePathTable(data, sizeB, true); } if (locationM != 0 && ((locationM * blockLength) + sizeB) < data.Length) { data.SeekIfPossible(locationM * blockLength, SeekOrigin.Begin); - groupB.PathTableM = ParsePathTable(data, sectorLength, sizeB, false); + groupB.PathTableM = ParsePathTable(data, sizeB, false); } if (locationM2 != 0 && ((locationM2 * blockLength) + sizeB) < data.Length) { data.SeekIfPossible(locationM2 * blockLength, SeekOrigin.Begin); - groupB.OptionalPathTableM = ParsePathTable(data, sectorLength, sizeB, false); + groupB.OptionalPathTableM = ParsePathTable(data, sizeB, false); } // If no valid path tables were found, don't add the table group @@ -487,11 +506,10 @@ namespace SabreTools.Serialization.Readers /// Parse a Stream into an array of path table records /// /// Stream to parse - /// Number of bytes in a logical sector (usually 2048) /// Size of the path table /// True if path table is little endian, false if big endian /// Filled array of path table records on success, null on error - public static PathTableRecord[]? ParsePathTable(Stream data, short sectorLength, int tableSize, bool littleEndian) + public static PathTableRecord[]? ParsePathTable(Stream data, int tableSize, bool littleEndian) { var pathTable = new List(); @@ -515,7 +533,6 @@ namespace SabreTools.Serialization.Readers } record.DirectoryIdentifierLength = directoryIdentifierLength; - record.ExtendedAttributeRecordLength = data.ReadByteValue(); // Read numerics with correct endianness @@ -563,21 +580,22 @@ namespace SabreTools.Serialization.Readers var directories = new Dictionary(); foreach (VolumeDescriptor vd in vdSet) { - if (vd is BaseVolumeDescriptor bvd) - { - // Determine logical block size - short blockLength = bvd.GetLogicalBlockSize(sectorLength); + if (vd is not BaseVolumeDescriptor bvd) + continue; - // Parse the root directory pointed to from the base volume descriptor - var descriptors = ParseDirectory(data, sectorLength, blockLength, bvd.RootDirectoryRecord, false); - if (descriptors == null || descriptors.Count == 0) - continue; - // Merge dictionaries - foreach (var kvp in descriptors) - { - if (!directories.ContainsKey(kvp.Key)) - directories.Add(kvp.Key, kvp.Value); - } + // Determine logical block size + short blockLength = bvd.GetLogicalBlockSize(sectorLength); + + // Parse the root directory pointed to from the base volume descriptor + var descriptors = ParseDirectory(data, sectorLength, blockLength, bvd.RootDirectoryRecord, false); + if (descriptors == null || descriptors.Count == 0) + continue; + + // Merge dictionaries + foreach (var kvp in descriptors) + { + if (!directories.ContainsKey(kvp.Key)) + directories.Add(kvp.Key, kvp.Value); } } @@ -662,11 +680,13 @@ namespace SabreTools.Serialization.Readers // 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) { @@ -732,8 +752,10 @@ namespace SabreTools.Serialization.Readers // Calculate actual size of record int totalBytes = 33 + obj.FileIdentifierLength; + // Calculate the size of the system use section (remaining allocated bytes) int systemUseLength = obj.DirectoryRecordLength - 33 - obj.FileIdentifierLength; + // Account for padding field after file identifier if (obj.FileIdentifierLength % 2 == 0) { diff --git a/SabreTools.Serialization/Wrappers/ISO9660.Printing.cs b/SabreTools.Serialization/Wrappers/ISO9660.Printing.cs index ebc931f7..59864bd2 100644 --- a/SabreTools.Serialization/Wrappers/ISO9660.Printing.cs +++ b/SabreTools.Serialization/Wrappers/ISO9660.Printing.cs @@ -160,14 +160,10 @@ namespace SabreTools.Serialization.Wrappers builder.AppendLine(vd.AbstractFileIdentifier, " Abstract Identifier"); builder.AppendLine(vd.BibliographicFileIdentifier, " Bibliographic Identifier"); - builder.AppendLine(" Volume Creation Date Time:"); - Print(builder, vd.VolumeCreationDateTime); - builder.AppendLine(" Volume Modification Date Time:"); - Print(builder, vd.VolumeModificationDateTime); - builder.AppendLine(" Volume Expiration Date Time:"); - Print(builder, vd.VolumeExpirationDateTime); - builder.AppendLine(" Volume Effective Date Time:"); - Print(builder, vd.VolumeEffectiveDateTime); + builder.AppendLine(Format(vd.VolumeCreationDateTime), " Volume Creation Date Time:"); + builder.AppendLine(Format(vd.VolumeModificationDateTime), " Volume Modification Date Time:"); + builder.AppendLine(Format(vd.VolumeExpirationDateTime), " Volume Expiration Date Time:"); + builder.AppendLine(Format(vd.VolumeEffectiveDateTime), " Volume Effective Date Time:"); builder.AppendLine(vd.FileStructureVersion, " File Structure Version"); @@ -445,51 +441,37 @@ namespace SabreTools.Serialization.Wrappers #endregion - private static void Print(StringBuilder builder, DecDateTime? dt) + private static string? Format(DecDateTime? dt) { if (dt == null) - { - builder.AppendLine(" [NULL]"); - return; - } + return null; - if (dt.Year.IsNumericArray()) - builder.AppendLine(Encoding.ASCII.GetString(dt.Year), " Year"); - else - builder.AppendLine(dt.Year, " Year"); + string year = dt.Year.IsNumericArray() + ? Encoding.ASCII.GetString(dt.Year) + : BitConverter.ToString(dt.Year).Replace('-', ' '); + string month = dt.Month.IsNumericArray() + ? Encoding.ASCII.GetString(dt.Month) + : BitConverter.ToString(dt.Month).Replace('-', ' '); + string day = dt.Day.IsNumericArray() + ? Encoding.ASCII.GetString(dt.Day) + : BitConverter.ToString(dt.Day).Replace('-', ' '); - if (dt.Month.IsNumericArray()) - builder.AppendLine(Encoding.ASCII.GetString(dt.Month), " Month"); - else - builder.AppendLine(dt.Month, " Month"); + string hour = dt.Hour.IsNumericArray() + ? Encoding.ASCII.GetString(dt.Hour) + : BitConverter.ToString(dt.Hour).Replace('-', ' '); + string minute = dt.Minute.IsNumericArray() + ? Encoding.ASCII.GetString(dt.Minute) + : BitConverter.ToString(dt.Minute).Replace('-', ' '); + string second = dt.Second.IsNumericArray() + ? Encoding.ASCII.GetString(dt.Second) + : BitConverter.ToString(dt.Second).Replace('-', ' '); + string csecond = dt.Centisecond.IsNumericArray() + ? Encoding.ASCII.GetString(dt.Centisecond) + : BitConverter.ToString(dt.Centisecond).Replace('-', ' '); - if (dt.Day.IsNumericArray()) - builder.AppendLine(Encoding.ASCII.GetString(dt.Day), " Day"); - else - builder.AppendLine(dt.Day, " Day"); + string tz = $"{((dt.TimezoneOffset - 48) * 15 / 60):+0;-0}:{((dt.TimezoneOffset - 48) * 15 % 60 + 60) % 60:00} (0x{dt.TimezoneOffset:X2})"; - if (dt.Hour.IsNumericArray()) - builder.AppendLine(Encoding.ASCII.GetString(dt.Hour), " Hour"); - else - builder.AppendLine(dt.Hour, " Hour"); - - if (dt.Minute.IsNumericArray()) - builder.AppendLine(Encoding.ASCII.GetString(dt.Minute), " Minute"); - else - builder.AppendLine(dt.Minute, " Minute"); - if (dt.Second.IsNumericArray()) - - builder.AppendLine(Encoding.ASCII.GetString(dt.Second), " Second"); - else - builder.AppendLine(dt.Second, " Second"); - - if (dt.Centisecond.IsNumericArray()) - builder.AppendLine(Encoding.ASCII.GetString(dt.Centisecond), " Centisecond"); - else - builder.AppendLine(dt.Centisecond, " Centisecond"); - - string tz = $"{((dt.TimezoneOffset - 48) * 15 / 60):+0;-0}:{((dt.TimezoneOffset - 48) * 15 % 60 + 60) % 60:00} (0x{dt.TimezoneOffset.ToString("X2")})"; - builder.AppendLine(tz, " Timezone Offset"); + return $"{year}-{month}-{day} {hour}:{minute}:{second}.{csecond} [{tz}]"; } } }