Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions SabreTools.Serialization/Readers/ISO9660.cs
Original file line number Diff line number Diff line change
Expand Up @@ -626,21 +626,23 @@ public class ISO9660 : BaseBinaryReader<Volume>
// 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
Expand Down Expand Up @@ -701,7 +703,7 @@ public class ISO9660 : BaseBinaryReader<Volume>
// 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)
Expand Down
104 changes: 57 additions & 47 deletions SabreTools.Serialization/Wrappers/ISO9660.Extraction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<int, int>();

// 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--)
Expand All @@ -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);
}
}
}
Expand All @@ -60,7 +56,7 @@ public virtual bool Extract(string outputDirectory, bool includeDebug)
/// <summary>
/// Extract all files from within a directory/file extent
/// </summary>
private bool ExtractExtent(int extentLocation, Dictionary<int, int> 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))
Expand All @@ -81,82 +77,96 @@ private bool ExtractExtent(int extentLocation, Dictionary<int, int> 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;
}

/// <summary>
/// Extract file pointed to by a directory record
/// </summary>
private bool ExtractFile(DirectoryRecord dr, Dictionary<int, int> 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
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.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);
Expand All @@ -169,7 +179,7 @@ private bool ExtractFile(DirectoryRecord dr, Dictionary<int, int> extractedFiles
}

// Mark the file as extracted
extractedFiles.Add(fileOffset, dr.ExtentLength);
extractedFiles.Add(dr.ExtentLocation, dr.ExtentLength);
}

return true;
Expand Down
8 changes: 8 additions & 0 deletions SabreTools.Serialization/Wrappers/ISO9660.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ public partial class ISO9660 : WrapperBase<Volume>

#endregion

#region Global Variables

private Dictionary<int, int> extractedFiles = new Dictionary<int, int>();

private List<byte[]> multiExtentFiles = new List<byte[]>();

#endregion

#region Constructors

/// <inheritdoc/>
Expand Down