Skip to content
Merged
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
8 changes: 5 additions & 3 deletions InfoPrint/Features/MainFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ internal sealed class MainFeature : Feature
private const string _jsonName = "json";
internal readonly FlagInput JsonInput = new(_jsonName, ["-j", "--json"], "Print info as JSON");

private const string _recursiveName = "recursive";
internal readonly FlagInput RecursiveInput = new(_recursiveName, ["-r", "--recursive"], "Recursively print info from embedded files");

#endregion

/// <summary>
Expand Down Expand Up @@ -74,6 +77,7 @@ public MainFeature()
Add(HashInput);
Add(FileOnlyInput);
Add(JsonInput);
Add(RecursiveInput);
}

/// <inheritdoc/>
Expand All @@ -84,9 +88,7 @@ public override bool Execute()
Hash = GetBoolean(_hashName);
FileOnly = GetBoolean(_fileOnlyName);
Json = GetBoolean(_jsonName);

// TODO: Add flag for this once there are examples of it
Recursive = false;
Recursive = GetBoolean(_recursiveName);

// Loop through the input paths
for (int i = 0; i < Inputs.Count; i++)
Expand Down
13 changes: 7 additions & 6 deletions SabreTools.Wrappers/ISO9660.Extraction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,9 @@ private bool ExtractExtent(int extentLocation, Encoding encoding, int blockLengt
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);
// TODO: How to treat files with same name but different location?
// if (!dr.ExtentLocation.IsValid)
// succeeded &= ExtractFile(dr, encoding, blockLength, true, outputDirectory, includeDebug);
}
}

Expand All @@ -151,7 +152,7 @@ private bool ExtractFile(DirectoryRecord dr, Encoding encoding, int blockLength,
int extentLocation = bigEndian ? dr.ExtentLocation.BigEndian : dr.ExtentLocation.LittleEndian;

// Check that the file hasn't been extracted already
if (extractedFiles.ContainsKey(dr.ExtentLocation))
if (extractedFiles.ContainsKey(extentLocation))
return true;

// TODO: Decode properly (Use VD's separator characters and encoding)
Expand Down Expand Up @@ -183,7 +184,7 @@ private bool ExtractFile(DirectoryRecord dr, Encoding encoding, int blockLength,
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);
extractedFiles.Add(extentLocation, dr.ExtentLength);
return false;
}

Expand All @@ -201,7 +202,7 @@ private bool ExtractFile(DirectoryRecord dr, Encoding encoding, int blockLength,
const uint chunkSize = 2048 * 1024;
lock (_dataSourceLock)
{
long fileOffset = ((long)dr.ExtentLocation + dr.ExtendedAttributeRecordLength) * blockLength;
long fileOffset = ((long)extentLocation + dr.ExtendedAttributeRecordLength) * blockLength;
_dataSource.SeekIfPossible(fileOffset, SeekOrigin.Begin);

// Get the length, and make sure it won't EOF
Expand All @@ -224,7 +225,7 @@ private bool ExtractFile(DirectoryRecord dr, Encoding encoding, int blockLength,
}

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

return true;
Expand Down
166 changes: 163 additions & 3 deletions SabreTools.Wrappers/ISO9660.Printing.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SabreTools.Data.Extensions;
using SabreTools.Data.Models.ISO9660;
using SabreTools.IO.Extensions;
using SabreTools.Matching;
using SabreTools.Numerics.Extensions;
using SabreTools.Text.Extensions;

namespace SabreTools.Wrappers
{
public partial class ISO9660 : IPrintable
{
#region Printing State

/// <summary>
/// List of printed embedded files by their sector offset
/// </summary>
private readonly HashSet<int> printedFiles = [];

#endregion

#if NETCOREAPP
/// <inheritdoc/>
public string ExportJSON(bool recursive) => System.Text.Json.JsonSerializer.Serialize(Model, _jsonSerializerOptions);
Expand All @@ -20,6 +33,8 @@ public partial class ISO9660 : IPrintable
/// <inheritdoc/>
public void PrintInformation(StringBuilder builder, bool recursive)
{
printedFiles.Clear();

builder.AppendLine("ISO 9660 Information:");
builder.AppendLine("-------------------------");
builder.AppendLine();
Expand All @@ -32,6 +47,148 @@ public void PrintInformation(StringBuilder builder, bool recursive)
Encoding encoding = Encoding.UTF8;
Print(builder, Model.PathTableGroups);
Print(builder, Model.DirectoryDescriptors, encoding);

if (recursive)
{
long initialOffset = _dataSource.Position;

// Determine and validate sector length, default to 2048
short sectorLength = (short)(Model.SystemArea.Length / 16);
if (sectorLength < 2048 || (sectorLength & (sectorLength - 1)) != 0)
sectorLength = 2048;

// Loop through all Volume Descriptors to print files from each directory hierarchy
// Note: This will prioritize the last volume descriptor directory hierarchies first (prioritises those filenames)
// This is useful as ASCII filenames are usually in the first VD
for (int i = VolumeDescriptorSet.Length - 1; i >= 0; i--)
{
var vd = VolumeDescriptorSet[i];

DirectoryRecord rootDirectoryRecord;
if (vd is PrimaryVolumeDescriptor pvd)
rootDirectoryRecord = pvd.RootDirectoryRecord;
else if (vd is SupplementaryVolumeDescriptor svd)
rootDirectoryRecord = svd.RootDirectoryRecord;
else
continue;

var blockLength = vd.GetLogicalBlockSize(sectorLength);

// TODO: Better encoding detection (EscapeSequences)
encoding = Encoding.UTF8;
if (vd is SupplementaryVolumeDescriptor)
encoding = Encoding.BigEndianUnicode;

RecursivePrint(builder, rootDirectoryRecord.ExtentLocation.LittleEndian, "\\", encoding, blockLength, initialOffset);
if (!rootDirectoryRecord.ExtentLocation.IsValid)
RecursivePrint(builder, rootDirectoryRecord.ExtentLocation.BigEndian, "\\", encoding, blockLength, initialOffset);
}
}
}

private void RecursivePrint(StringBuilder builder, int sectorNumber, string filePath, Encoding encoding, short blockLength, long initialOffset)
{
// Check that directory exists in model
if (!Model.DirectoryDescriptors.TryGetValue(sectorNumber, out FileExtent? value))
return;

// Expect a directory
if (value is not DirectoryExtent dir)
return;

foreach (var dr in dir.DirectoryRecords)
{
string filename = encoding.GetString(dr.FileIdentifier);
string path = Path.Combine(filePath, filename);

// Recurse if record is directory
#if NET20 || NET35
if ((dr.FileFlags & FileFlags.DIRECTORY) != 0)
#else
if (dr.FileFlags.HasFlag(FileFlags.DIRECTORY))
#endif
{
// Don't recurse up or self
if (dr.FileIdentifier.EqualsExactly(Constants.CurrentDirectory) || dr.FileIdentifier.EqualsExactly(Constants.ParentDirectory))
continue;

// Add extent before recursion
if (!printedFiles.Contains(dr.ExtentLocation.LittleEndian))
{
printedFiles.Add(dr.ExtentLocation.LittleEndian);
RecursivePrint(builder, dr.ExtentLocation.LittleEndian, path, encoding, blockLength, initialOffset);
}

if (!dr.ExtentLocation.IsValid && !printedFiles.Contains(dr.ExtentLocation.BigEndian))
{
printedFiles.Add(dr.ExtentLocation.BigEndian);
RecursivePrint(builder, dr.ExtentLocation.BigEndian, path, encoding, blockLength, initialOffset);
}
}
else
{
// Skip multi-extent and interleaved files
if ((dr.FileFlags & FileFlags.MULTI_EXTENT) != 0 || dr.FileUnitSize != 0 || dr.InterleaveGapSize != 0)
continue;

// Print embedded file from LittleEndian location
if (!printedFiles.Contains(dr.ExtentLocation.LittleEndian))
{
try
{
long offset = initialOffset + ((long)dr.ExtentLocation.LittleEndian + (long)dr.ExtendedAttributeRecordLength) * (long)blockLength;
var wrapper = GetFileWrapper(offset, filename);
if (wrapper is not null && wrapper is IPrintable printable)
{
// Print info for embedded file
builder.AppendLine($"Information for {path}");
builder.AppendLine("-------------------------");
printable.PrintInformation(builder, true);

printedFiles.Add(dr.ExtentLocation.LittleEndian);
}
}
catch
{
// Ignore the actual error
continue;
}
}

// Print embedded file from BigEndian location
if (!dr.ExtentLocation.IsValid && !printedFiles.Contains(dr.ExtentLocation.BigEndian))
{
try
{
long offset = initialOffset + (dr.ExtentLocation.BigEndian + dr.ExtendedAttributeRecordLength) * blockLength;
var wrapper = GetFileWrapper(offset, filename);
if (wrapper is not null && wrapper is IPrintable printable)
{
// Print info for embedded file
builder.AppendLine($"Information for {path}");
builder.AppendLine("-------------------------");
printable.PrintInformation(builder, true);

printedFiles.Add(dr.ExtentLocation.BigEndian);
}
}
catch
{
// Ignore the actual error
continue;
}
}
}
}
}

private IWrapper? GetFileWrapper(long offset, string filename)
{
_dataSource.Seek(offset, SeekOrigin.Begin);
byte[] magic = _dataSource.PeekBytes(16);
string extension = Path.GetExtension(filename).TrimStart('.');
WrapperType ft = WrapperFactory.GetFileType(magic, extension);
return WrapperFactory.CreateWrapper(ft, _dataSource);
}

protected static void Print(StringBuilder builder, byte[] systemArea)
Expand Down Expand Up @@ -168,7 +325,7 @@ private static void Print(StringBuilder builder, BaseVolumeDescriptor vd)
builder.AppendLine(vd.OptionalPathTableLocationM, " Optional Type-M Path Table Location");

builder.AppendLine(" Root Directory Record:");
Print(builder, vd.RootDirectoryRecord);
Print(builder, vd.RootDirectoryRecord, encoding);

builder.AppendLine(encoding.GetString(vd.VolumeSetIdentifier), " Volume Set Identifier");
builder.AppendLine(encoding.GetString(vd.PublisherIdentifier), " Publisher Identifier");
Expand Down Expand Up @@ -385,7 +542,7 @@ private static void Print(StringBuilder builder, FileExtent extent, Encoding enc
{
builder.AppendLine($" Directory Record {recordNum}:");
builder.AppendLine(" -------------------------");
Print(builder, dir.DirectoryRecords[recordNum]);
Print(builder, dir.DirectoryRecords[recordNum], encoding);
builder.AppendLine();
}
}
Expand All @@ -398,7 +555,7 @@ private static void Print(StringBuilder builder, FileExtent extent, Encoding enc
builder.AppendLine();
}

private static void Print(StringBuilder builder, DirectoryRecord dr)
private static void Print(StringBuilder builder, DirectoryRecord dr, Encoding encoding)
{
builder.AppendLine(dr.DirectoryRecordLength, " Directory Record Length");
builder.AppendLine(dr.ExtendedAttributeRecordLength, " Extended Attribute Record Length");
Expand All @@ -425,6 +582,9 @@ private static void Print(StringBuilder builder, DirectoryRecord dr)

builder.AppendLine(dr.FileIdentifierLength, " File Identifier Length");
builder.AppendLine(dr.FileIdentifier, " File Identifier");
if (dr.FileIdentifier is not null && dr.FileIdentifier.Length > 0)
builder.AppendLine(encoding.GetString(dr.FileIdentifier), " File Identifier (Decoded)");

builder.AppendLine(dr.PaddingField, " Padding Field");

if (dr.SystemUse.Length == 0)
Expand Down
71 changes: 71 additions & 0 deletions SabreTools.Wrappers/XDVDFS.Printing.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SabreTools.Data.Models.XDVDFS;
using SabreTools.IO.Extensions;
using SabreTools.Numerics.Extensions;
using SabreTools.Text.Extensions;

namespace SabreTools.Wrappers
{
public partial class XDVDFS : IPrintable
{
#region Printing State

/// <summary>
/// List of printed embedded files by their sector offset
/// </summary>
private readonly HashSet<uint> printedFiles = [];

#endregion

#if NETCOREAPP
/// <inheritdoc/>
public string ExportJSON(bool recursive) => System.Text.Json.JsonSerializer.Serialize(Model, _jsonSerializerOptions);
Expand All @@ -18,6 +31,8 @@ public partial class XDVDFS : IPrintable
/// <inheritdoc/>
public void PrintInformation(StringBuilder builder, bool recursive)
{
printedFiles.Clear();

builder.AppendLine("Xbox DVD Filesystem Information:");
builder.AppendLine("-------------------------");
builder.AppendLine();
Expand All @@ -32,6 +47,62 @@ public void PrintInformation(StringBuilder builder, bool recursive)
{
Print(builder, kvp.Value, kvp.Key);
}

if (recursive)
{
long initialOffset = _dataSource.Position;
RecursivePrint(builder, Model.VolumeDescriptor.RootOffset, "\\", initialOffset);
}
}

private void RecursivePrint(StringBuilder builder, uint sectorNumber, string filePath, long initialOffset)
{
if (!Model.DirectoryDescriptors.ContainsKey(sectorNumber))
return;

foreach (DirectoryRecord dr in Model.DirectoryDescriptors[sectorNumber].DirectoryRecords)
{
string filename = Encoding.UTF8.GetString(dr.Filename);
string path = Path.Combine(filePath, filename);

// Skip already printed files
if (printedFiles.Contains(dr.ExtentOffset))
continue;

// Recurse into directory
if ((dr.FileFlags & FileFlags.DIRECTORY) == FileFlags.DIRECTORY)
{
// Add directory extent before recursing
printedFiles.Add(dr.ExtentOffset);

RecursivePrint(builder, dr.ExtentOffset, path, initialOffset);
continue;
}

// Parse embedded file
try
{
_dataSource.Seek(initialOffset + Constants.SectorSize * dr.ExtentOffset, SeekOrigin.Begin);
byte[] magic = _dataSource.PeekBytes(16);
string extension = Path.GetExtension(filename).TrimStart('.');
WrapperType ft = WrapperFactory.GetFileType(magic, extension);
var wrapper = WrapperFactory.CreateWrapper(ft, _dataSource);
if (wrapper is null || wrapper is not IPrintable printable)
continue;

// Print info for embedded file
builder.AppendLine($"Information for {path}");
builder.AppendLine("-------------------------");
printable.PrintInformation(builder, true);

printedFiles.Add(dr.ExtentOffset);
}
catch
{
// Ignore the actual error
continue;
}
}
}

private static void Print(StringBuilder builder, byte[] reservedArea)
Expand Down