diff --git a/SabreTools.Serialization/Readers/ISO9660.cs b/SabreTools.Serialization/Readers/ISO9660.cs index 3b7487d6..0a42df4a 100644 --- a/SabreTools.Serialization/Readers/ISO9660.cs +++ b/SabreTools.Serialization/Readers/ISO9660.cs @@ -626,21 +626,23 @@ public class ISO9660 : BaseBinaryReader // Use provided extent endinanness int extentLocation = bigEndian ? dr.ExtentLocation.BigEndian : dr.ExtentLocation.LittleEndian; int extentLength = bigEndian ? dr.ExtentLength.BigEndian : dr.ExtentLength.LittleEndian; + long extentOffset = (long)extentLocation * (long)blockLength; + long extentFinal = extentOffset + (long)extentLength; // 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) + if (extentLength <= 0 || extentFinal > (long)data.Length) extentLength = bigEndian ? dr.ExtentLength.LittleEndian : dr.ExtentLength.BigEndian; } // Validate extent length - if (extentLength <= 0 || (extentLocation * blockLength) + extentLength > data.Length) + if (extentLength <= 0 || extentFinal > (long)data.Length) return null; // Move stream to directory location - data.SeekIfPossible(extentLocation * blockLength, SeekOrigin.Begin); + data.SeekIfPossible(extentOffset, SeekOrigin.Begin); // Check if the current extent is a directory #if NET20 || NET35 @@ -701,7 +703,7 @@ public class ISO9660 : BaseBinaryReader // Add current directory to dictionary var currentDirectory = new DirectoryExtent(); currentDirectory.DirectoryRecords = [.. records]; - directories.Add(extentLocation * blocksPerSector, currentDirectory); + directories.Add(extentLocation, currentDirectory); // Add all child directories to dictionary recursively foreach (var record in records) diff --git a/SabreTools.Serialization/Wrappers/ISO9660.Extraction.cs b/SabreTools.Serialization/Wrappers/ISO9660.Extraction.cs index 0a1a3476..3b6e2c2e 100644 --- a/SabreTools.Serialization/Wrappers/ISO9660.Extraction.cs +++ b/SabreTools.Serialization/Wrappers/ISO9660.Extraction.cs @@ -24,10 +24,6 @@ public virtual bool Extract(string outputDirectory, bool includeDebug) 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--) @@ -44,12 +40,12 @@ public virtual bool Extract(string outputDirectory, bool includeDebug) encoding = Encoding.BigEndianUnicode; // Extract all files within root directory hierarchy - allExtracted &= ExtractExtent(rootDir.ExtentLocation.LittleEndian, extractedFiles, encoding, blockLength, outputDirectory, includeDebug); + allExtracted &= ExtractExtent(rootDir.ExtentLocation.LittleEndian, 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); + allExtracted &= ExtractExtent(rootDir.ExtentLocation.BigEndian, encoding, blockLength, outputDirectory, includeDebug); } } } @@ -60,7 +56,7 @@ public virtual bool Extract(string outputDirectory, bool includeDebug) /// /// Extract all files from within a directory/file extent /// - private bool ExtractExtent(int extentLocation, Dictionary extractedFiles, Encoding encoding, int blockLength, string outputDirectory, bool includeDebug) + private bool ExtractExtent(int extentLocation, Encoding encoding, int blockLength, string outputDirectory, bool includeDebug) { // Check that directory exists in model if (!DirectoryDescriptors.ContainsKey(extentLocation)) @@ -81,53 +77,86 @@ private bool ExtractExtent(int extentLocation, Dictionary extractedFil // 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); + + // Recursively extract from LittleEndian extent location + ExtractExtent(dr.ExtentLocation.LittleEndian, encoding, blockLength, outDirTemp, includeDebug); // Also extract from BigEndian values if ambiguous if (!dr.ExtentLocation.IsValid!) - { - ExtractExtent(dr.ExtentLocation.BigEndian, extractedFiles, encoding, blockLength, outDirTemp, includeDebug); - } + ExtractExtent(dr.ExtentLocation.BigEndian, encoding, blockLength, outDirTemp, includeDebug); } - else if ((dr.FileFlags & FileFlags.MULTI_EXTENT) == 0) + else { - // Record is a file extent, extract file - succeeded &= ExtractFile(dr, extractedFiles, encoding, blockLength, false, outputDirectory, includeDebug); + // Record is a simple file extent, extract file + succeeded &= ExtractFile(dr, 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); - } - } - else - { - if (includeDebug) Console.WriteLine("Extraction of multi-extent files is currently not supported"); + succeeded &= ExtractFile(dr, encoding, blockLength, true, outputDirectory, includeDebug); } } } - return true; + return succeeded; } /// /// 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) + private bool ExtractFile(DirectoryRecord dr, 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 + dr.ExtendedAttributeRecordLength) * blockLength; // Check that the file hasn't been extracted already - if (extractedFiles.ContainsKey(fileOffset)) + if (extractedFiles.ContainsKey(dr.ExtentLocation)) return true; + // TODO: Decode properly (Use VD's separator characters and encoding) + string filename = encoding.GetString(dr.FileIdentifier); + int index = filename.LastIndexOf(';'); + if (index > 0) + filename = filename.Substring(0, index); + + // Ensure the full output directory exists + var filepath = Path.Combine(outputDirectory, filename); + var directoryName = Path.GetDirectoryName(filepath); + if (directoryName != null && !Directory.Exists(directoryName)) + Directory.CreateDirectory(directoryName); + + bool multiExtent = false; + + // Currently cannot extract multi-extent or interleaved files + if ((dr.FileFlags & FileFlags.MULTI_EXTENT) != 0) + { + if (includeDebug) Console.WriteLine($"Extraction of multi-extent files is currently WIP: {filename}"); + multiExtentFiles.Add(dr.FileIdentifier); + multiExtent = true; + } + else if (dr.FileUnitSize != 0 || dr.InterleaveGapSize != 0) + { + Console.WriteLine($"Extraction of interleaved files is currently not supported: {filename}"); + extractedFiles.Add(dr.ExtentLocation, dr.ExtentLength); + return false; + } + + // Check that the output file doesn't already exist + if (!multiExtent && (File.Exists(filepath) || Directory.Exists(filepath))) + { + // If it's the last extent of a multi-extent file, continue to append + if (multiExtentFiles.Exists(item => item.EqualsExactly(dr.FileIdentifier))) + { + if (includeDebug) Console.WriteLine($"File/Folder already exists, cannot extract: {filename}"); + return false; + } + } + const int chunkSize = 2048 * 1024; lock (_dataSourceLock) { + long fileOffset = ((long)dr.ExtentLocation + (long)dr.ExtendedAttributeRecordLength) * (long)blockLength; _dataSource.SeekIfPossible(fileOffset, SeekOrigin.Begin); // Get the length, and make sure it won't EOF @@ -135,28 +164,9 @@ private bool ExtractFile(DirectoryRecord dr, Dictionary extractedFiles 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.LastIndexOf(';'); - 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); - - // 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}"); - using var fs = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); + if (includeDebug) Console.WriteLine($"Extracting: {filepath}"); + using var fs = File.Open(filepath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); while (length > 0) { int bytesToRead = (int)Math.Min(length, chunkSize); @@ -169,7 +179,7 @@ private bool ExtractFile(DirectoryRecord dr, Dictionary extractedFiles } // Mark the file as extracted - extractedFiles.Add(fileOffset, dr.ExtentLength); + extractedFiles.Add(dr.ExtentLocation, dr.ExtentLength); } return true; diff --git a/SabreTools.Serialization/Wrappers/ISO9660.cs b/SabreTools.Serialization/Wrappers/ISO9660.cs index b2626ea9..4120c71c 100644 --- a/SabreTools.Serialization/Wrappers/ISO9660.cs +++ b/SabreTools.Serialization/Wrappers/ISO9660.cs @@ -29,6 +29,14 @@ public partial class ISO9660 : WrapperBase #endregion + #region Global Variables + + private Dictionary extractedFiles = new Dictionary(); + + private List multiExtentFiles = new List(); + + #endregion + #region Constructors ///