XDVDFS editorconfig cleanup

This commit is contained in:
Matt Nadareski
2026-03-28 23:06:46 -04:00
parent 07fe4415fe
commit d39c2e81d0
13 changed files with 1090 additions and 1095 deletions

View File

@@ -1,31 +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";
}
}
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";
}
}

View File

@@ -1,23 +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; }
}
}
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; }
}
}

View File

@@ -1,64 +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; }
}
}
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; }
}
}

View File

@@ -1,54 +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,
}
}
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,
}
}

View File

@@ -1,32 +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; }
}
}
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; }
}
}

View File

@@ -1,75 +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];
}
}
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];
}
}

View File

@@ -1,45 +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; } = [];
}
}
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; } = [];
}
}

View File

@@ -1,52 +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
}
}
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
}
}

View File

@@ -1,72 +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);
}
}
}
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);
}
}
}

View File

@@ -1,253 +1,252 @@
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;
}
}
}
using System.Collections.Generic;
using System.IO;
using SabreTools.Data.Models.XDVDFS;
using SabreTools.IO.Extensions;
using SabreTools.Numerics.Extensions;
#pragma warning disable IDE0017 // Simplify object initialization
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;
while (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;
}
}
}

View File

@@ -1,143 +1,141 @@
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;
}
}
}
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;
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;
}
}
}

View File

@@ -1,144 +1,142 @@
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();
}
}
}
using System;
using System.Text;
using SabreTools.Data.Models.XDVDFS;
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();
}
}
}

View File

@@ -1,107 +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
}
}
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
}
}