mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-09-22 23:05:12 +00:00
ISO9660: Skip interleaved files and other fixes (#35)
* Skip interleaved files * Fix warning logic * Refactor * Fix extraction * Multi extent * review
This commit is contained in:
@@ -626,21 +626,23 @@ namespace SabreTools.Serialization.Readers
|
||||
// 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 @@ namespace SabreTools.Serialization.Readers
|
||||
// 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)
|
||||
|
||||
@@ -10,6 +10,21 @@ namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
public partial class ISO9660 : IExtractable
|
||||
{
|
||||
#region Extraction State
|
||||
|
||||
/// <summary>
|
||||
/// List of extracted files by their sector offset
|
||||
/// </summary>
|
||||
private Dictionary<int, int> extractedFiles = [];
|
||||
|
||||
/// <summary>
|
||||
/// List of multi-extent files written, by their FileIdentifier
|
||||
/// </summary>
|
||||
/// <remarks>Used as last-extent has mutli-extent flag=0</remarks>
|
||||
private List<byte[]> multiExtentFiles = [];
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool Extract(string outputDirectory, bool includeDebug)
|
||||
{
|
||||
@@ -24,10 +39,6 @@ namespace SabreTools.Serialization.Wrappers
|
||||
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--)
|
||||
@@ -44,12 +55,12 @@ namespace SabreTools.Serialization.Wrappers
|
||||
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 +71,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// <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))
|
||||
@@ -81,53 +92,86 @@ namespace SabreTools.Serialization.Wrappers
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
else if ((dr.FileFlags & FileFlags.MULTI_EXTENT) == 0)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
ExtractExtent(dr.ExtentLocation.BigEndian, encoding, blockLength, outDirTemp, includeDebug);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (includeDebug) Console.WriteLine("Extraction of multi-extent files is currently not supported");
|
||||
// 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, 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
|
||||
@@ -135,28 +179,9 @@ namespace SabreTools.Serialization.Wrappers
|
||||
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 +194,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
}
|
||||
|
||||
// Mark the file as extracted
|
||||
extractedFiles.Add(fileOffset, dr.ExtentLength);
|
||||
extractedFiles.Add(dr.ExtentLocation, dr.ExtentLength);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user