mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-04-05 22:01:33 +00:00
Xbox DVD Filesystem support (XISO) (#73)
* Xbox DVD Filesystem support (XISO) * Apply file attributes to extracted files * Fix * Fix build and PR review * Support Archive file attribute
This commit is contained in:
@@ -304,6 +304,11 @@ namespace ExtractionTool.Features
|
||||
wad.Extract(OutputPath, Debug);
|
||||
break;
|
||||
|
||||
// XDVDFS volume
|
||||
case XDVDFS xdvdfs:
|
||||
xdvdfs.Extract(OutputPath, Debug);
|
||||
break;
|
||||
|
||||
// xz
|
||||
case XZ xz:
|
||||
xz.Extract(OutputPath, Debug);
|
||||
|
||||
@@ -57,6 +57,7 @@ Options:
|
||||
| Half-Life 2 Level (VBSP) | |
|
||||
| InstallShield Archive V3 (Z) | |
|
||||
| InstallShield CAB | |
|
||||
| ISO9660 | ISO and BIN files |
|
||||
| Microsoft cabinet file | Does not support LZX or Quantum compression |
|
||||
| Microsoft LZ-compressed files | KWAJ, QBasic, and SZDD variants |
|
||||
| MoPaQ game data archive (MPQ) | Windows only |
|
||||
@@ -71,6 +72,7 @@ Options:
|
||||
| Tape archive (TAR) | |
|
||||
| Valve Package File (VPK) | |
|
||||
| XBox Package File (XZP) | |
|
||||
| Xbox DVD Filesystem (XISO) | |
|
||||
| xz archive (XZ) | .NET Framework 4.6.2 and greater |
|
||||
|
||||
## Namespaces
|
||||
|
||||
@@ -86,6 +86,7 @@ Below is a list of all existing namespaces with the `SabreTools.Data.Models` pre
|
||||
| `WiseInstaller` | Wise script- and section-based installers |
|
||||
| `Xbox` | Xbox and Xbox 360 file formats |
|
||||
| `XboxExecutable` | Xbox Executable (XBE) |
|
||||
| `XDVDFS` | Xbox DVD Filesystem (XISO) |
|
||||
| `XZ` | xz archive |
|
||||
| `XZP` | XBox Package File |
|
||||
| `ZSTD` | ZSTD archive |
|
||||
|
||||
31
SabreTools.Data.Models/XDVDFS/Constants.cs
Normal file
31
SabreTools.Data.Models/XDVDFS/Constants.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace SabreTools.Data.Models.XDVDFS
|
||||
{
|
||||
/// <see href="https://github.dev/Deterous/XboxKit/"/>
|
||||
public static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of bytes in a sector
|
||||
/// </summary>
|
||||
public const int SectorSize = 2048;
|
||||
|
||||
/// <summary>
|
||||
/// Number of sectors reserved at beginning of volume
|
||||
/// </summary>
|
||||
public const int ReservedSectors = 32;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum length of a directory record
|
||||
/// </summary>
|
||||
public const int MinimumRecordLength = 14;
|
||||
|
||||
/// <summary>
|
||||
/// Volume Descriptor signature at start of sector 32
|
||||
/// </summary>
|
||||
public const string VolumeDescriptorSignature = "MICROSOFT*XBOX*MEDIA";
|
||||
|
||||
/// <summary>
|
||||
/// Xbox DVD Layout Descriptor signature at start of sector 33
|
||||
/// </summary>
|
||||
public const string LayoutDescriptorSignature = "XBOX_DVD_LAYOUT_TOOL_SIG";
|
||||
}
|
||||
}
|
||||
23
SabreTools.Data.Models/XDVDFS/DirectoryDescriptor.cs
Normal file
23
SabreTools.Data.Models/XDVDFS/DirectoryDescriptor.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace SabreTools.Data.Models.XDVDFS
|
||||
{
|
||||
/// <summary>
|
||||
/// Xbox DVD Filesystem Directory Descriptor
|
||||
/// The descriptor is stored as a binary tree, left being alphabetically smaller, right larger
|
||||
/// Padded with 0xFF to be a multiple of 2048 bytes
|
||||
/// </summary>
|
||||
/// <see href="https://multimedia.cx/xdvdfs.html"/>
|
||||
/// <see href="https://github.dev/Deterous/XboxKit/"/>
|
||||
public class DirectoryDescriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// List of directory records
|
||||
/// </summary>
|
||||
public DirectoryRecord[] DirectoryRecords { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Padding to fill up remainder of sector
|
||||
/// Not present if prior data is a multiple of 2048 bytes
|
||||
/// <remarks>All 0xFF</remarks>
|
||||
public byte[]? Padding { get; set; }
|
||||
}
|
||||
}
|
||||
64
SabreTools.Data.Models/XDVDFS/DirectoryRecord.cs
Normal file
64
SabreTools.Data.Models/XDVDFS/DirectoryRecord.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
namespace SabreTools.Data.Models.XDVDFS
|
||||
{
|
||||
/// <summary>
|
||||
/// Xbox DVD Filesystem Directory Record
|
||||
/// Padded with 0xFF to be a multiple of 4 bytes
|
||||
/// </summary>
|
||||
/// <see href="https://multimedia.cx/xdvdfs.html"/>
|
||||
/// <see href="https://github.dev/Deterous/XboxKit/"/>
|
||||
public class DirectoryRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset of left child directory record
|
||||
/// Unit is number of uints from start of directory descriptor
|
||||
/// If zero, no left child directory record exists
|
||||
/// </summary>
|
||||
/// <remarks>Little-endian</remarks>
|
||||
public ushort LeftChildOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset of right child directory record
|
||||
/// Unit is number of uints from start of directory descriptor
|
||||
/// If zero, no right child directory record exists
|
||||
/// </summary>
|
||||
/// <remarks>Little-endian</remarks>
|
||||
public ushort RightChildOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sector offset into filesystem for the current record
|
||||
/// If record is a file, points to the first sector of file
|
||||
/// If record is a directory, points to first sector of directory descriptor
|
||||
/// </summary>
|
||||
/// <remarks>Little-endian</remarks>
|
||||
public uint ExtentOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Extent size into filesystem for the current record
|
||||
/// If record is a file, size of file in bytes
|
||||
/// If record is a directory, number of bytes in directory descriptor
|
||||
/// </summary>
|
||||
/// <remarks>Little-endian</remarks>
|
||||
public uint ExtentSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File attributes of current record
|
||||
/// </summary>
|
||||
public FileFlags FileFlags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Length in bytes of the following filename
|
||||
/// </summary>
|
||||
public byte FilenameLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the record, encoded in single-byte per character
|
||||
/// </summary>
|
||||
public byte[] Filename { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Padding to fill up remainder of uint32
|
||||
/// Not present if filename ends on a byte offset multiple of 4
|
||||
/// <remarks>0-3 bytes, all 0xFF</remarks>
|
||||
public byte[]? Padding { get; set; }
|
||||
}
|
||||
}
|
||||
54
SabreTools.Data.Models/XDVDFS/Enums.cs
Normal file
54
SabreTools.Data.Models/XDVDFS/Enums.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
|
||||
namespace SabreTools.Data.Models.XDVDFS
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum for DirectoryRecord.FileFlags
|
||||
/// Values are assumed to match lowest byte value of DOS/FAT/NTFS file attributes
|
||||
/// </summary>
|
||||
/// <see href="https://multimedia.cx/xdvdfs.html"/>
|
||||
/// <see href="https://learn.microsoft.com/dotnet/api/system.io.fileattributes"/>
|
||||
[Flags]
|
||||
public enum FileFlags : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Record is read-only
|
||||
/// </summary>
|
||||
READ_ONLY = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// Record is hidden
|
||||
/// </summary>
|
||||
HIDDEN = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// Record is part of or for the operating system
|
||||
/// </summary>
|
||||
SYSTEM = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// Record is a volume ID
|
||||
/// </summary>
|
||||
VOLUME_ID = 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// Record is a directory
|
||||
/// </summary>
|
||||
DIRECTORY = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// Record should be archived
|
||||
/// </summary>
|
||||
ARCHIVE = 0x20,
|
||||
|
||||
/// <summary>
|
||||
/// Record is a device
|
||||
/// </summary>
|
||||
DEVICE = 0x40,
|
||||
|
||||
/// <summary>
|
||||
/// Record has no other attributes
|
||||
/// </summary>
|
||||
NORMAL = 0x80,
|
||||
}
|
||||
}
|
||||
32
SabreTools.Data.Models/XDVDFS/FourPartVersionType.cs
Normal file
32
SabreTools.Data.Models/XDVDFS/FourPartVersionType.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace SabreTools.Data.Models.XDVDFS
|
||||
{
|
||||
/// <summary>
|
||||
/// Four-part version number commonly used by Microsoft
|
||||
/// </summary>
|
||||
public class FourPartVersionType
|
||||
{
|
||||
/// <summary>
|
||||
/// Major Version Number
|
||||
/// </summary>
|
||||
/// <remarks>Little-endian</remarks>
|
||||
public ushort Major { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Minor Version Number
|
||||
/// </summary>
|
||||
/// <remarks>Little-endian</remarks>
|
||||
public ushort Minor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Build Version Number
|
||||
/// </summary>
|
||||
/// <remarks>Little-endian</remarks>
|
||||
public ushort Build { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Revision Version Number
|
||||
/// </summary>
|
||||
/// <remarks>Little-endian</remarks>
|
||||
public ushort Revision { get; set; }
|
||||
}
|
||||
}
|
||||
75
SabreTools.Data.Models/XDVDFS/LayoutDescriptor.cs
Normal file
75
SabreTools.Data.Models/XDVDFS/LayoutDescriptor.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
namespace SabreTools.Data.Models.XDVDFS
|
||||
{
|
||||
/// <summary>
|
||||
/// Xbox DVD Layout Descriptor, present at sector 33 (offset 0x10800) of an Xbox DVD Filesystem
|
||||
/// Only present on XGD1 and XGD2 discs
|
||||
/// </summary>
|
||||
/// <see href="https://xboxdevwiki.net/XDVDFS"/>
|
||||
/// <see href="https://github.dev/Deterous/XboxKit/"/>
|
||||
public class LayoutDescriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// Xbox DVD Layout descriptor signature for 2nd sector start
|
||||
/// For XGD2, this should be the only non-zero field
|
||||
/// </summary>
|
||||
/// <remarks>24 bytes</remarks>
|
||||
public byte[] Signature { get; set; } = new byte[24]; // LayoutDescriptorSignature
|
||||
|
||||
/// <summary>
|
||||
/// Seemingly unused 8 bytes after the signature, should be zeroed
|
||||
/// </summary>
|
||||
/// <remarks>8 bytes</remarks>
|
||||
public byte[] Unusued8Bytes { get; set; } = new byte[8];
|
||||
|
||||
/// <summary>
|
||||
/// Version number of xblayout(?) tool used to master the filesystem
|
||||
/// Known versions are 1.0.x.1, x = 3926 to 5120
|
||||
/// If zeroed, xblayout was not used
|
||||
/// </summary>
|
||||
/// <remarks>8 bytes</remarks>
|
||||
public FourPartVersionType XBLayoutVersion { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Version number of xbpremaster(?) tool used to master the filesystem
|
||||
/// If zeroed, xbpremaster was not used
|
||||
/// </summary>
|
||||
/// <remarks>8 bytes</remarks>
|
||||
public FourPartVersionType XBPremasterVersion { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Version number of xbgamedisc(?) tool used to master the filesystem
|
||||
/// The major version is set to [0x01, 0x02] which may not be a ushort ?
|
||||
/// Known versions are 513.0.x.1 (aka 2.1.0.x.1), x = 5233 to 5849
|
||||
/// If zeroed, xbgamedisc was not used
|
||||
/// </summary>
|
||||
/// <remarks>8 bytes</remarks>
|
||||
public FourPartVersionType XBGameDiscVersion { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Version number of other microsoft tool used to master the filesystem
|
||||
/// May be zeroed, not always present
|
||||
/// </summary>
|
||||
/// <remarks>8 bytes</remarks>
|
||||
public FourPartVersionType XBOther1Version { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Version number of other microsoft tool used to master the filesystem
|
||||
/// May be zeroed, not always present
|
||||
/// </summary>
|
||||
/// <remarks>8 bytes</remarks>
|
||||
public FourPartVersionType XBOther2Version { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Version number of other microsoft tool used to master the filesystem
|
||||
/// May be zeroed, not always present
|
||||
/// </summary>
|
||||
/// <remarks>8 bytes</remarks>
|
||||
public FourPartVersionType XBOther3Version { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Padding the remainder of sector, should be zeroed
|
||||
/// </summary>
|
||||
/// <remarks>1968 bytes</remarks>
|
||||
public byte[] Reserved { get; set; } = new byte[1968];
|
||||
}
|
||||
}
|
||||
45
SabreTools.Data.Models/XDVDFS/Volume.cs
Normal file
45
SabreTools.Data.Models/XDVDFS/Volume.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SabreTools.Data.Models.XDVDFS
|
||||
{
|
||||
/// <summary>
|
||||
/// Xbox DVD Filesystem (aka "XISO"), 2048-byte sector size
|
||||
/// Present in XGD1, XGD2, and XGD3 discs
|
||||
/// Gaps between directory and file extents are filled with pseudo-random filler data (unless intentionally wiped)
|
||||
/// Some early XGD1 filesystems use a simpler PRNG algorithm that can have its seed brute forced
|
||||
/// Gaps also include the security sector ranges, 4096 sectors each that cannot be read from discs (usually zeroed)
|
||||
/// </summary>
|
||||
/// <see href="https://xboxdevwiki.net/XDVDFS"/>
|
||||
/// <see href="https://github.dev/Deterous/XboxKit/"/>
|
||||
public class Volume
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserved Area, made up of 32 sectors
|
||||
/// Contains pseudo-random filler data on-disc
|
||||
/// Some XDVDFS images zero the reserved area
|
||||
/// </summary>
|
||||
/// <remarks>65,536 bytes</remarks>
|
||||
public byte[] ReservedArea { get; set; } = new byte[0x10000];
|
||||
|
||||
/// <summary>
|
||||
/// XDVDFS Volume Descriptor
|
||||
/// </summary>
|
||||
/// <remarks>2048 bytes</remarks>
|
||||
public VolumeDescriptor VolumeDescriptor { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Xbox DVD Layout Descriptor, immediately follows Volume Descriptor
|
||||
/// XGD1: Contains version numbers and signature bytes
|
||||
/// XGD2: Zeroed apart from initial signature bytes
|
||||
/// XGD3: Sector not present
|
||||
/// </summary>
|
||||
/// <remarks>2048 bytes</remarks>
|
||||
public LayoutDescriptor? LayoutDescriptor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Map of sector numbers and the directory descriptor at that sector number
|
||||
/// The root directory descriptor is not guaranteed to be the earliest
|
||||
/// </summary>
|
||||
public Dictionary<uint, DirectoryDescriptor> DirectoryDescriptors { get; set; } = [];
|
||||
}
|
||||
}
|
||||
52
SabreTools.Data.Models/XDVDFS/VolumeDescriptor.cs
Normal file
52
SabreTools.Data.Models/XDVDFS/VolumeDescriptor.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
namespace SabreTools.Data.Models.XDVDFS
|
||||
{
|
||||
/// <summary>
|
||||
/// XDVDFS Volume Descriptor, present at sector 32 (offset 0x10000) of an Xbox DVD Filesystem
|
||||
/// Present on XGD1, XGD2, and XGD3 discs
|
||||
/// </summary>
|
||||
/// <see href="https://multimedia.cx/xdvdfs.html"/>
|
||||
/// <see href="https://github.dev/Deterous/XboxKit/"/>
|
||||
public class VolumeDescriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// Volume descriptor magic, start
|
||||
/// </summary>
|
||||
/// <remarks>20 bytes</remarks>
|
||||
public byte[] StartSignature { get; set; } = new byte[20]; // VolumeDescriptorSignature
|
||||
|
||||
/// <summary>
|
||||
/// UInt32 sector location of the root directory descriptor
|
||||
/// </summary>
|
||||
/// <remarks>Little-endian</remarks>
|
||||
public uint RootOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// UInt32 size of the root directory descriptor in bytes
|
||||
/// </summary>
|
||||
public uint RootSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Win32 FILETIME filesystem mastering timestamp
|
||||
/// </summary>
|
||||
/// <remarks>Little-endian</remarks>
|
||||
public long MasteringTimestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unknown byte, seemingly 0x00 for XGD1, and 0x01 for XGD2 and XGD3
|
||||
/// </summary>
|
||||
/// <remarks>1991 bytes</remarks>
|
||||
public byte UnknownByte { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Seemingly unused bytes in first sector that are expected to be zeroed
|
||||
/// </summary>
|
||||
/// <remarks>1991 bytes</remarks>
|
||||
public byte[] Reserved { get; set; } = new byte[1991];
|
||||
|
||||
/// <summary>
|
||||
/// Volume descriptor magic, start
|
||||
/// </summary>
|
||||
/// <remarks>20 bytes</remarks>
|
||||
public byte[] EndSignature { get; set; } = new byte[20]; // VolumeDescriptorSignature
|
||||
}
|
||||
}
|
||||
72
SabreTools.Serialization.Readers.Test/XDVDFSTests.cs
Normal file
72
SabreTools.Serialization.Readers.Test/XDVDFSTests.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Serialization.Readers.Test
|
||||
{
|
||||
public class XDVDFSTests
|
||||
{
|
||||
[Fact]
|
||||
public void NullArray_Null()
|
||||
{
|
||||
byte[]? data = null;
|
||||
int offset = 0;
|
||||
var deserializer = new XDVDFS();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_Null()
|
||||
{
|
||||
byte[]? data = [];
|
||||
int offset = 0;
|
||||
var deserializer = new XDVDFS();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidArray_Null()
|
||||
{
|
||||
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
|
||||
int offset = 0;
|
||||
var deserializer = new XDVDFS();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullStream_Null()
|
||||
{
|
||||
Stream? data = null;
|
||||
var deserializer = new XDVDFS();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([]);
|
||||
var deserializer = new XDVDFS();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
|
||||
var deserializer = new XDVDFS();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
253
SabreTools.Serialization.Readers/XDVDFS.cs
Normal file
253
SabreTools.Serialization.Readers/XDVDFS.cs
Normal file
@@ -0,0 +1,253 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.Data.Extensions;
|
||||
using SabreTools.Data.Models.XDVDFS;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Matching;
|
||||
using SabreTools.Numerics.Extensions;
|
||||
|
||||
namespace SabreTools.Serialization.Readers
|
||||
{
|
||||
public class XDVDFS : BaseBinaryReader<Volume>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Volume? Deserialize(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data is null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// Simple check for a valid stream length
|
||||
if ((Constants.ReservedSectors + 2) * Constants.SectorSize > data.Length - data.Position)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Create a new Volume to fill
|
||||
var volume = new Volume();
|
||||
|
||||
// Read the Reserved Area
|
||||
volume.ReservedArea = data.ReadBytes(Constants.ReservedSectors * Constants.SectorSize);
|
||||
|
||||
// Read and validate the volume descriptor
|
||||
var vd = ParseVolumeDescriptor(data);
|
||||
if (vd is null)
|
||||
return null;
|
||||
|
||||
volume.VolumeDescriptor = vd;
|
||||
|
||||
// Parse the optional layout descriptor
|
||||
volume.LayoutDescriptor = ParseLayoutDescriptor(data);
|
||||
|
||||
// Parse the descriptors from the root directory descriptor
|
||||
var dd = ParseDirectoryDescriptors(data, vd.RootOffset, vd.RootSize);
|
||||
if (dd is null)
|
||||
return null;
|
||||
|
||||
volume.DirectoryDescriptors = dd;
|
||||
|
||||
return volume;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a VolumeDescriptor
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled VolumeDescriptor on success, null on error</returns>
|
||||
public static VolumeDescriptor? ParseVolumeDescriptor(Stream data)
|
||||
{
|
||||
var obj = new VolumeDescriptor();
|
||||
|
||||
obj.StartSignature = data.ReadBytes(20);
|
||||
var signature = System.Text.Encoding.ASCII.GetString(obj.StartSignature);
|
||||
if (!signature.Equals(Constants.VolumeDescriptorSignature))
|
||||
return null;
|
||||
|
||||
obj.RootOffset = data.ReadUInt32LittleEndian();
|
||||
obj.RootSize = data.ReadUInt32LittleEndian();
|
||||
obj.MasteringTimestamp = data.ReadInt64LittleEndian();
|
||||
obj.UnknownByte = data.ReadByteValue();
|
||||
obj.Reserved = data.ReadBytes(1991);
|
||||
obj.EndSignature = data.ReadBytes(20);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a LayoutDescriptor
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled LayoutDescriptor on success, null on error</returns>
|
||||
public static LayoutDescriptor? ParseLayoutDescriptor(Stream data)
|
||||
{
|
||||
var obj = new LayoutDescriptor();
|
||||
|
||||
obj.Signature = data.ReadBytes(24);
|
||||
var signature = System.Text.Encoding.ASCII.GetString(obj.Signature);
|
||||
if (!signature.Equals(Constants.LayoutDescriptorSignature))
|
||||
return null;
|
||||
obj.Unusued8Bytes = data.ReadBytes(8);
|
||||
|
||||
obj.XBLayoutVersion = ParseFourPartVersionType(data);
|
||||
obj.XBPremasterVersion = ParseFourPartVersionType(data);
|
||||
obj.XBGameDiscVersion = ParseFourPartVersionType(data);
|
||||
obj.XBOther1Version = ParseFourPartVersionType(data);
|
||||
obj.XBOther2Version = ParseFourPartVersionType(data);
|
||||
obj.XBOther3Version = ParseFourPartVersionType(data);
|
||||
|
||||
obj.Reserved = data.ReadBytes(1968);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a FourPartVersionType
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled FourPartVersionType on success, null on error</returns>
|
||||
public static FourPartVersionType ParseFourPartVersionType(Stream data)
|
||||
{
|
||||
var obj = new FourPartVersionType();
|
||||
|
||||
obj.Major = data.ReadUInt16LittleEndian();
|
||||
obj.Minor = data.ReadUInt16LittleEndian();
|
||||
obj.Build = data.ReadUInt16LittleEndian();
|
||||
obj.Revision = data.ReadUInt16LittleEndian();
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a Dictionary of int to DirectoryDescriptors
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="offset">Sector number descriptor is located at</param>
|
||||
/// <param name="size">Number of bytes descriptor contains</param>
|
||||
/// <returns>Filled Dictionary of int to DirectoryDescriptors on success, null on error</returns>
|
||||
public static Dictionary<uint, DirectoryDescriptor>? ParseDirectoryDescriptors(Stream data, uint offset, uint size)
|
||||
{
|
||||
// Ensure descriptor size is valid
|
||||
if (size < 14)
|
||||
return null;
|
||||
|
||||
// Ensure offset is valid
|
||||
if (offset * Constants.SectorSize + size > data.Length)
|
||||
return null;
|
||||
|
||||
var obj = new Dictionary<uint, DirectoryDescriptor>();
|
||||
|
||||
var dd = ParseDirectoryDescriptor(data, offset, size);
|
||||
if (dd is null)
|
||||
return null;
|
||||
|
||||
obj.Add(offset, dd);
|
||||
|
||||
// Parse all child descriptors
|
||||
foreach (var dr in dd.DirectoryRecords)
|
||||
{
|
||||
if ((dr.FileFlags & FileFlags.DIRECTORY) == FileFlags.DIRECTORY)
|
||||
{
|
||||
// Ensure same descriptor is never parsed twice
|
||||
if (obj.ContainsKey(dr.ExtentOffset))
|
||||
continue;
|
||||
|
||||
// Get all descriptors from child
|
||||
var descriptors = ParseDirectoryDescriptors(data, dr.ExtentOffset, dr.ExtentSize);
|
||||
if (descriptors is null)
|
||||
continue;
|
||||
|
||||
// Merge dictionaries
|
||||
foreach (var kvp in descriptors)
|
||||
{
|
||||
if (!obj.ContainsKey(kvp.Key))
|
||||
obj.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a DirectoryDescriptor
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="offset">Sector number descriptor is located at</param>
|
||||
/// <param name="size">Number of bytes descriptor contains</param>
|
||||
/// <returns>Filled DirectoryDescriptor on success, null on error</returns>
|
||||
public static DirectoryDescriptor? ParseDirectoryDescriptor(Stream data, uint offset, uint size)
|
||||
{
|
||||
// Ensure descriptor size is valid
|
||||
if (size < Constants.MinimumRecordLength)
|
||||
return null;
|
||||
|
||||
// Ensure offset is valid
|
||||
if (((long)offset) * Constants.SectorSize + size > data.Length)
|
||||
return null;
|
||||
|
||||
var obj = new DirectoryDescriptor();
|
||||
var records = new List<DirectoryRecord>();
|
||||
|
||||
data.SeekIfPossible(((long)offset) * Constants.SectorSize, SeekOrigin.Begin);
|
||||
long curPosition = data.Position;
|
||||
while ((long)size > data.Position - ((long)offset) * Constants.SectorSize)
|
||||
{
|
||||
curPosition = data.Position;
|
||||
var dr = ParseDirectoryRecord(data);
|
||||
if (dr is not null)
|
||||
records.Add(dr);
|
||||
|
||||
// If invalid record read or next descriptor cannot fit in the current sector, skip ahead
|
||||
if (dr is null || data.Position % Constants.SectorSize > (Constants.SectorSize - Constants.MinimumRecordLength))
|
||||
{
|
||||
data.Position += Constants.SectorSize - (int)(data.Position % Constants.SectorSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Exit loop if stream has not advanced
|
||||
if (curPosition == data.Position)
|
||||
break;
|
||||
}
|
||||
|
||||
obj.DirectoryRecords = [.. records];
|
||||
|
||||
int remainder = Constants.SectorSize - (int)(size % Constants.SectorSize);
|
||||
if (remainder > 0 && remainder < Constants.SectorSize)
|
||||
obj.Padding = data.ReadBytes(remainder);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a DirectoryRecord
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled DirectoryRecord on success, null on error</returns>
|
||||
public static DirectoryRecord? ParseDirectoryRecord(Stream data)
|
||||
{
|
||||
var obj = new DirectoryRecord();
|
||||
|
||||
obj.LeftChildOffset = data.ReadUInt16LittleEndian();
|
||||
obj.RightChildOffset = data.ReadUInt16LittleEndian();
|
||||
if (obj.LeftChildOffset == 0xFFFF && obj.RightChildOffset == 0xFFFF)
|
||||
return null;
|
||||
|
||||
obj.ExtentOffset = data.ReadUInt32LittleEndian();
|
||||
obj.ExtentSize = data.ReadUInt32LittleEndian();
|
||||
obj.FileFlags = (FileFlags)data.ReadByteValue();
|
||||
obj.FilenameLength = data.ReadByteValue();
|
||||
obj.Filename = data.ReadBytes(obj.FilenameLength);
|
||||
int remainder = 4 - (int)(data.Position % 4);
|
||||
if (remainder > 0 && remainder < 4)
|
||||
obj.Padding = data.ReadBytes(remainder);
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
SabreTools.Wrappers.Test/XDVDFSTests.cs
Normal file
60
SabreTools.Wrappers.Test/XDVDFSTests.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Wrappers.Test
|
||||
{
|
||||
public class XDVDFSTests
|
||||
{
|
||||
[Fact]
|
||||
public void NullArray_Null()
|
||||
{
|
||||
byte[]? data = null;
|
||||
int offset = 0;
|
||||
var actual = XDVDFS.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_Null()
|
||||
{
|
||||
byte[]? data = [];
|
||||
int offset = 0;
|
||||
var actual = XDVDFS.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidArray_Null()
|
||||
{
|
||||
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
|
||||
int offset = 0;
|
||||
var actual = XDVDFS.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullStream_Null()
|
||||
{
|
||||
Stream? data = null;
|
||||
var actual = XDVDFS.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([]);
|
||||
var actual = XDVDFS.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
|
||||
var actual = XDVDFS.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@ namespace SabreTools.Wrappers
|
||||
WrapperType.VPK => VPK.Create(data),
|
||||
WrapperType.WAD => WAD3.Create(data),
|
||||
WrapperType.XboxExecutable => XboxExecutable.Create(data),
|
||||
WrapperType.XDVDFS => XDVDFS.Create(data),
|
||||
WrapperType.XZ => XZ.Create(data),
|
||||
WrapperType.XZP => XZP.Create(data),
|
||||
WrapperType.ZSTD => ZSTD.Create(data),
|
||||
@@ -925,6 +926,18 @@ namespace SabreTools.Wrappers
|
||||
|
||||
#endregion
|
||||
|
||||
#region XDVDFS
|
||||
|
||||
// TODO: Overload .iso wrapper to call both ISO and XDVDFS wrappers if present
|
||||
|
||||
if (extension.Equals("xiso", StringComparison.OrdinalIgnoreCase))
|
||||
return WrapperType.XDVDFS;
|
||||
|
||||
if (extension.Equals("xdvdfs", StringComparison.OrdinalIgnoreCase))
|
||||
return WrapperType.XDVDFS;
|
||||
|
||||
#endregion
|
||||
|
||||
#region XZ
|
||||
|
||||
if (magic.StartsWith(Data.Models.XZ.Constants.HeaderSignatureBytes))
|
||||
|
||||
@@ -288,6 +288,11 @@ namespace SabreTools.Wrappers
|
||||
/// </summary>
|
||||
XboxExecutable,
|
||||
|
||||
/// <summary>
|
||||
/// Xbox DVD Filesystem
|
||||
/// </summary>
|
||||
XDVDFS,
|
||||
|
||||
/// <summary>
|
||||
/// xz archive
|
||||
/// </summary>
|
||||
|
||||
143
SabreTools.Wrappers/XDVDFS.Extraction.cs
Normal file
143
SabreTools.Wrappers/XDVDFS.Extraction.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.Data.Extensions;
|
||||
using SabreTools.Data.Models.XDVDFS;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Matching;
|
||||
using SabreTools.Numerics.Extensions;
|
||||
|
||||
namespace SabreTools.Wrappers
|
||||
{
|
||||
public partial class XDVDFS : IExtractable
|
||||
{
|
||||
#region Extraction State
|
||||
|
||||
/// <summary>
|
||||
/// List of extracted files by their sector offset
|
||||
/// </summary>
|
||||
private readonly Dictionary<uint, uint> extractedFiles = [];
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool Extract(string outputDirectory, bool includeDebug)
|
||||
{
|
||||
// Clear the extraction state
|
||||
extractedFiles.Clear();
|
||||
|
||||
// Extract files from all directories from root directory
|
||||
return ExtractDescriptor(outputDirectory, includeDebug, VolumeDescriptor.RootOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts all directory records recursively from directory descriptor
|
||||
/// </summary>
|
||||
public bool ExtractDescriptor(string outputDirectory, bool includeDebug, uint sectorNumber)
|
||||
{
|
||||
// If no descriptor exists at that sector, we cannot extract from it
|
||||
if (!DirectoryDescriptors.ContainsKey(sectorNumber))
|
||||
return false;
|
||||
|
||||
bool allExtracted = true;
|
||||
|
||||
// Extract directory records within directory descriptor
|
||||
foreach (var dr in DirectoryDescriptors[sectorNumber].DirectoryRecords)
|
||||
{
|
||||
// Skip invalid records
|
||||
if (dr.FilenameLength == 0 || dr.Filename is null)
|
||||
{
|
||||
if (includeDebug) Console.WriteLine($"Empty filename in directory record at sector {sectorNumber}");
|
||||
continue;
|
||||
}
|
||||
|
||||
string outputPath = Path.Combine(outputDirectory, Encoding.UTF8.GetString(dr.Filename));
|
||||
|
||||
// If record is a directory, create it and extract child records
|
||||
if ((dr.FileFlags & FileFlags.DIRECTORY) == FileFlags.DIRECTORY)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(outputPath) && !Directory.Exists(outputPath))
|
||||
Directory.CreateDirectory(outputPath);
|
||||
|
||||
allExtracted |= ExtractDescriptor(outputPath, includeDebug, dr.ExtentOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skip invalid file size
|
||||
if (dr.ExtentSize == 0)
|
||||
{
|
||||
if (includeDebug) Console.WriteLine($"Zero file size for file {dr.Filename} at sector {dr.ExtentOffset}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip invalid file location
|
||||
if (((long)dr.ExtentOffset) * Constants.SectorSize + dr.ExtentSize > _dataSource.Length)
|
||||
{
|
||||
if (includeDebug) Console.WriteLine($"Invalid file location for file {dr.Filename} at sector {dr.ExtentOffset}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that the file hasn't been extracted already
|
||||
if (extractedFiles.ContainsKey(dr.ExtentOffset))
|
||||
{
|
||||
if (includeDebug) Console.WriteLine($"File {dr.Filename} at sector {dr.ExtentOffset} already extracted");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read and extract the file extent
|
||||
const uint chunkSize = 2048 * 1024;
|
||||
lock (_dataSourceLock)
|
||||
{
|
||||
long fileOffset = ((long)dr.ExtentOffset) * Constants.SectorSize;
|
||||
_dataSource.SeekIfPossible(fileOffset, SeekOrigin.Begin);
|
||||
|
||||
// Get the length, and make sure it won't EOF
|
||||
uint length = dr.ExtentSize;
|
||||
if (length > _dataSource.Length - _dataSource.Position)
|
||||
return false;
|
||||
|
||||
// Write the output file
|
||||
if (includeDebug) Console.WriteLine($"Extracting: {outputPath}");
|
||||
using var fs = File.Open(outputPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
|
||||
while (length > 0)
|
||||
{
|
||||
int bytesToRead = (int)Math.Min(length, chunkSize);
|
||||
|
||||
byte[] buffer = _dataSource.ReadBytes(bytesToRead);
|
||||
fs.Write(buffer, 0, bytesToRead);
|
||||
fs.Flush();
|
||||
|
||||
length -= (uint)bytesToRead;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the file as extracted
|
||||
extractedFiles.Add(dr.ExtentOffset, dr.ExtentSize);
|
||||
|
||||
// Don't set any file attributes if file is normal
|
||||
if ((dr.FileFlags & FileFlags.NORMAL) == FileFlags.NORMAL)
|
||||
continue;
|
||||
|
||||
// Copy over hidden flag
|
||||
if ((dr.FileFlags & FileFlags.HIDDEN) == FileFlags.HIDDEN)
|
||||
File.SetAttributes(outputPath, FileAttributes.Hidden);
|
||||
|
||||
// Copy over read-only flag
|
||||
if ((dr.FileFlags & FileFlags.READ_ONLY) == FileFlags.READ_ONLY)
|
||||
File.SetAttributes(outputPath, FileAttributes.ReadOnly);
|
||||
|
||||
// Copy over system flag
|
||||
if ((dr.FileFlags & FileFlags.SYSTEM) == FileFlags.SYSTEM)
|
||||
File.SetAttributes(outputPath, FileAttributes.System);
|
||||
|
||||
// Copy over archive flag
|
||||
if ((dr.FileFlags & FileFlags.ARCHIVE) == FileFlags.ARCHIVE)
|
||||
File.SetAttributes(outputPath, FileAttributes.Archive);
|
||||
}
|
||||
}
|
||||
|
||||
return allExtracted;
|
||||
}
|
||||
}
|
||||
}
|
||||
144
SabreTools.Wrappers/XDVDFS.Printing.cs
Normal file
144
SabreTools.Wrappers/XDVDFS.Printing.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using SabreTools.Data.Models.XDVDFS;
|
||||
using SabreTools.Numerics.Extensions;
|
||||
using SabreTools.Text.Extensions;
|
||||
|
||||
namespace SabreTools.Wrappers
|
||||
{
|
||||
public partial class XDVDFS : IPrintable
|
||||
{
|
||||
#if NETCOREAPP
|
||||
/// <inheritdoc/>
|
||||
public string ExportJSON() => System.Text.Json.JsonSerializer.Serialize(Model, _jsonSerializerOptions);
|
||||
#endif
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PrintInformation(StringBuilder builder)
|
||||
{
|
||||
builder.AppendLine("Xbox DVD Filesystem Information:");
|
||||
builder.AppendLine("-------------------------");
|
||||
builder.AppendLine();
|
||||
|
||||
Print(builder, Model.ReservedArea);
|
||||
Print(builder, Model.VolumeDescriptor);
|
||||
|
||||
if (Model.LayoutDescriptor is not null)
|
||||
Print(builder, Model.LayoutDescriptor);
|
||||
|
||||
foreach (var kvp in Model.DirectoryDescriptors)
|
||||
{
|
||||
Print(builder, kvp.Value, kvp.Key);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void Print(StringBuilder builder, byte[] reservedArea)
|
||||
{
|
||||
if (reservedArea.Length == 0)
|
||||
builder.AppendLine(reservedArea, " Reserved Area");
|
||||
else if (Array.TrueForAll(reservedArea, b => b == 0))
|
||||
builder.AppendLine("Zeroed", " Reserved Area");
|
||||
else
|
||||
builder.AppendLine("Not Zeroed", " Reserved Area");
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
private static void Print(StringBuilder builder, VolumeDescriptor vd)
|
||||
{
|
||||
builder.AppendLine(" Volume Descriptor:");
|
||||
builder.AppendLine(" -------------------------");
|
||||
|
||||
builder.AppendLine(Encoding.ASCII.GetString(vd.StartSignature), " Start Signature");
|
||||
builder.AppendLine(vd.RootOffset, " Root Offset");
|
||||
builder.AppendLine(vd.RootSize, " Root Size");
|
||||
DateTime datetime = DateTime.FromFileTime(vd.MasteringTimestamp);
|
||||
builder.AppendLine(datetime.ToString("yyyy-MM-dd HH:mm:ss"), " Mastering Timestamp");
|
||||
builder.AppendLine(vd.UnknownByte, " Unknown Byte");
|
||||
if (Array.TrueForAll(vd.Reserved, b => b == 0))
|
||||
builder.AppendLine("Zeroed", " Reserved Bytes");
|
||||
else
|
||||
builder.AppendLine("Not Zeroed", " Reserved Bytes");
|
||||
builder.AppendLine(Encoding.ASCII.GetString(vd.EndSignature), " End Signature");
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
private static void Print(StringBuilder builder, LayoutDescriptor ld)
|
||||
{
|
||||
builder.AppendLine(" Xbox DVD Layout Descriptor:");
|
||||
builder.AppendLine(" -------------------------");
|
||||
|
||||
builder.AppendLine(Encoding.ASCII.GetString(ld.Signature), " Signature");
|
||||
builder.AppendLine(ld.Unusued8Bytes, " Unusued 8 Bytes");
|
||||
builder.AppendLine(GetVersionString(ld.XBLayoutVersion), " xblayout Version");
|
||||
builder.AppendLine(GetVersionString(ld.XBPremasterVersion), " xbpremaster Version");
|
||||
builder.AppendLine(GetVersionString(ld.XBGameDiscVersion), " xbgamedisc Version");
|
||||
builder.AppendLine(GetVersionString(ld.XBOther1Version), " Unknown Tool 1 Version");
|
||||
builder.AppendLine(GetVersionString(ld.XBOther2Version), " Unknown Tool 2 Version");
|
||||
builder.AppendLine(GetVersionString(ld.XBOther3Version), " Unknown Tool 2 Version");
|
||||
if (Array.TrueForAll(ld.Reserved, b => b == 0))
|
||||
builder.AppendLine("Zeroed", " Reserved Bytes");
|
||||
else
|
||||
builder.AppendLine("Not Zeroed", " Reserved Bytes");
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
private static string GetVersionString(FourPartVersionType ver)
|
||||
{
|
||||
return $"{ver.Major}.{ver.Minor}.{ver.Build}.{ver.Revision}";
|
||||
}
|
||||
|
||||
private static void Print(StringBuilder builder, DirectoryDescriptor dd, uint sectorNumber)
|
||||
{
|
||||
builder.AppendLine($" Directory Descriptor (Sector {sectorNumber}):");
|
||||
builder.AppendLine(" -------------------------");
|
||||
|
||||
foreach (DirectoryRecord dr in dd.DirectoryRecords)
|
||||
Print(builder, dr);
|
||||
|
||||
if (dd.Padding is null)
|
||||
builder.AppendLine("None", " Padding");
|
||||
else if (Array.TrueForAll(dd.Padding, b => b == 0xFF))
|
||||
builder.AppendLine("All 0xFF", " Padding");
|
||||
else
|
||||
builder.AppendLine("Not all 0xFF", " Padding");
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
private static void Print(StringBuilder builder, DirectoryRecord dr)
|
||||
{
|
||||
builder.AppendLine($" Directory Record:");
|
||||
builder.AppendLine(" -------------------------");
|
||||
|
||||
builder.AppendLine(dr.LeftChildOffset, " Left Child Offset");
|
||||
builder.AppendLine(dr.RightChildOffset, " Right Child Offset");
|
||||
builder.AppendLine(dr.ExtentOffset, " Extent Offset");
|
||||
builder.AppendLine(dr.ExtentSize, " Extent Size");
|
||||
|
||||
builder.AppendLine(" File Flags:");
|
||||
builder.AppendLine((dr.FileFlags & FileFlags.READ_ONLY) == FileFlags.READ_ONLY, " Read-only");
|
||||
builder.AppendLine((dr.FileFlags & FileFlags.HIDDEN) == FileFlags.HIDDEN, " Hidden");
|
||||
builder.AppendLine((dr.FileFlags & FileFlags.SYSTEM) == FileFlags.SYSTEM, " System");
|
||||
builder.AppendLine((dr.FileFlags & FileFlags.VOLUME_ID) == FileFlags.VOLUME_ID, " Volume ID");
|
||||
builder.AppendLine((dr.FileFlags & FileFlags.DIRECTORY) == FileFlags.DIRECTORY, " Directory");
|
||||
builder.AppendLine((dr.FileFlags & FileFlags.ARCHIVE) == FileFlags.ARCHIVE, " Archive");
|
||||
builder.AppendLine((dr.FileFlags & FileFlags.DEVICE) == FileFlags.DEVICE, " Device");
|
||||
builder.AppendLine((dr.FileFlags & FileFlags.NORMAL) == FileFlags.NORMAL, " Normal");
|
||||
|
||||
builder.AppendLine(dr.FilenameLength, " Filename Length");
|
||||
builder.AppendLine(Encoding.UTF8.GetString(dr.Filename), " Filename");
|
||||
|
||||
if (dr.Padding is null)
|
||||
builder.AppendLine("None", " Padding");
|
||||
else if (Array.TrueForAll(dr.Padding, b => b == 0xFF))
|
||||
builder.AppendLine("All 0xFF", " Padding");
|
||||
else
|
||||
builder.AppendLine("Not all 0xFF", " Padding");
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
107
SabreTools.Wrappers/XDVDFS.cs
Normal file
107
SabreTools.Wrappers/XDVDFS.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.Data.Models.XDVDFS;
|
||||
|
||||
namespace SabreTools.Wrappers
|
||||
{
|
||||
public partial class XDVDFS : WrapperBase<Volume>
|
||||
{
|
||||
#region Descriptive Properties
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string DescriptionString => "Xbox DVD Filesystem";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extension Properties
|
||||
|
||||
/// <inheritdoc cref="Volume.ReservedArea"/>
|
||||
public byte[] ReservedArea => Model.ReservedArea;
|
||||
|
||||
/// <inheritdoc cref="Volume.VolumeDescriptor"/>
|
||||
public VolumeDescriptor VolumeDescriptor => Model.VolumeDescriptor;
|
||||
|
||||
/// <inheritdoc cref="Volume.LayoutDescriptor"/>
|
||||
public LayoutDescriptor? LayoutDescriptor => Model.LayoutDescriptor;
|
||||
|
||||
/// <inheritdoc cref="Volume.DirectoryDescriptors"/>
|
||||
public Dictionary<uint, DirectoryDescriptor> DirectoryDescriptors => Model.DirectoryDescriptors;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <inheritdoc/>
|
||||
public XDVDFS(Volume model, byte[] data) : base(model, data) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public XDVDFS(Volume model, byte[] data, int offset) : base(model, data, offset) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public XDVDFS(Volume model, byte[] data, int offset, int length) : base(model, data, offset, length) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public XDVDFS(Volume model, Stream data) : base(model, data) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public XDVDFS(Volume model, Stream data, long offset) : base(model, data, offset) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public XDVDFS(Volume model, Stream data, long offset, long length) : base(model, data, offset, length) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create an XDVDFS Volume from a byte array and offset
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array representing the XDVDFS Volume</param>
|
||||
/// <param name="offset">Offset within the array to parse</param>
|
||||
/// <returns>An XDVDFS Volume wrapper on success, null on failure</returns>
|
||||
public static XDVDFS? Create(byte[]? data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data is null || data.Length == 0)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a memory stream and use that
|
||||
var dataStream = new MemoryStream(data, offset, data.Length - offset);
|
||||
return Create(dataStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an XDVDFS Volume from a Stream
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the XDVDFS Volume</param>
|
||||
/// <returns>An XDVDFS Volume wrapper on success, null on failure</returns>
|
||||
public static XDVDFS? Create(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data is null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Cache the current offset
|
||||
long currentOffset = data.Position;
|
||||
|
||||
var model = new Serialization.Readers.XDVDFS().Deserialize(data);
|
||||
if (model is null)
|
||||
return null;
|
||||
|
||||
return new XDVDFS(model, data, currentOffset);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user