diff --git a/SabreTools.Data.Models/XDVDFS/Constants.cs b/SabreTools.Data.Models/XDVDFS/Constants.cs
index 5394aaf6..5ac4601e 100644
--- a/SabreTools.Data.Models/XDVDFS/Constants.cs
+++ b/SabreTools.Data.Models/XDVDFS/Constants.cs
@@ -1,31 +1,31 @@
-namespace SabreTools.Data.Models.XDVDFS
-{
- ///
- public static class Constants
- {
- ///
- /// Number of bytes in a sector
- ///
- public const int SectorSize = 2048;
-
- ///
- /// Number of sectors reserved at beginning of volume
- ///
- public const int ReservedSectors = 32;
-
- ///
- /// Minimum length of a directory record
- ///
- public const int MinimumRecordLength = 14;
-
- ///
- /// Volume Descriptor signature at start of sector 32
- ///
- public const string VolumeDescriptorSignature = "MICROSOFT*XBOX*MEDIA";
-
- ///
- /// Xbox DVD Layout Descriptor signature at start of sector 33
- ///
- public const string LayoutDescriptorSignature = "XBOX_DVD_LAYOUT_TOOL_SIG";
- }
-}
+namespace SabreTools.Data.Models.XDVDFS
+{
+ ///
+ public static class Constants
+ {
+ ///
+ /// Number of bytes in a sector
+ ///
+ public const int SectorSize = 2048;
+
+ ///
+ /// Number of sectors reserved at beginning of volume
+ ///
+ public const int ReservedSectors = 32;
+
+ ///
+ /// Minimum length of a directory record
+ ///
+ public const int MinimumRecordLength = 14;
+
+ ///
+ /// Volume Descriptor signature at start of sector 32
+ ///
+ public const string VolumeDescriptorSignature = "MICROSOFT*XBOX*MEDIA";
+
+ ///
+ /// Xbox DVD Layout Descriptor signature at start of sector 33
+ ///
+ public const string LayoutDescriptorSignature = "XBOX_DVD_LAYOUT_TOOL_SIG";
+ }
+}
diff --git a/SabreTools.Data.Models/XDVDFS/DirectoryDescriptor.cs b/SabreTools.Data.Models/XDVDFS/DirectoryDescriptor.cs
index 1db2a99e..cc27f15a 100644
--- a/SabreTools.Data.Models/XDVDFS/DirectoryDescriptor.cs
+++ b/SabreTools.Data.Models/XDVDFS/DirectoryDescriptor.cs
@@ -1,23 +1,23 @@
-namespace SabreTools.Data.Models.XDVDFS
-{
- ///
- /// 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
- ///
- ///
- ///
- public class DirectoryDescriptor
- {
- ///
- /// List of directory records
- ///
- public DirectoryRecord[] DirectoryRecords { get; set; } = [];
-
- ///
- /// Padding to fill up remainder of sector
- /// Not present if prior data is a multiple of 2048 bytes
- /// All 0xFF
- public byte[]? Padding { get; set; }
- }
-}
+namespace SabreTools.Data.Models.XDVDFS
+{
+ ///
+ /// 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
+ ///
+ ///
+ ///
+ public class DirectoryDescriptor
+ {
+ ///
+ /// List of directory records
+ ///
+ public DirectoryRecord[] DirectoryRecords { get; set; } = [];
+
+ ///
+ /// Padding to fill up remainder of sector
+ /// Not present if prior data is a multiple of 2048 bytes
+ /// All 0xFF
+ public byte[]? Padding { get; set; }
+ }
+}
diff --git a/SabreTools.Data.Models/XDVDFS/DirectoryRecord.cs b/SabreTools.Data.Models/XDVDFS/DirectoryRecord.cs
index 7956b2b9..8e14305e 100644
--- a/SabreTools.Data.Models/XDVDFS/DirectoryRecord.cs
+++ b/SabreTools.Data.Models/XDVDFS/DirectoryRecord.cs
@@ -1,64 +1,64 @@
-namespace SabreTools.Data.Models.XDVDFS
-{
- ///
- /// Xbox DVD Filesystem Directory Record
- /// Padded with 0xFF to be a multiple of 4 bytes
- ///
- ///
- ///
- public class DirectoryRecord
- {
- ///
- /// Offset of left child directory record
- /// Unit is number of uints from start of directory descriptor
- /// If zero, no left child directory record exists
- ///
- /// Little-endian
- public ushort LeftChildOffset { get; set; }
-
- ///
- /// Offset of right child directory record
- /// Unit is number of uints from start of directory descriptor
- /// If zero, no right child directory record exists
- ///
- /// Little-endian
- public ushort RightChildOffset { get; set; }
-
- ///
- /// 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
- ///
- /// Little-endian
- public uint ExtentOffset { get; set; }
-
- ///
- /// 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
- ///
- /// Little-endian
- public uint ExtentSize { get; set; }
-
- ///
- /// File attributes of current record
- ///
- public FileFlags FileFlags { get; set; }
-
- ///
- /// Length in bytes of the following filename
- ///
- public byte FilenameLength { get; set; }
-
- ///
- /// Name of the record, encoded in single-byte per character
- ///
- public byte[] Filename { get; set; } = [];
-
- ///
- /// Padding to fill up remainder of uint32
- /// Not present if filename ends on a byte offset multiple of 4
- /// 0-3 bytes, all 0xFF
- public byte[]? Padding { get; set; }
- }
-}
+namespace SabreTools.Data.Models.XDVDFS
+{
+ ///
+ /// Xbox DVD Filesystem Directory Record
+ /// Padded with 0xFF to be a multiple of 4 bytes
+ ///
+ ///
+ ///
+ public class DirectoryRecord
+ {
+ ///
+ /// Offset of left child directory record
+ /// Unit is number of uints from start of directory descriptor
+ /// If zero, no left child directory record exists
+ ///
+ /// Little-endian
+ public ushort LeftChildOffset { get; set; }
+
+ ///
+ /// Offset of right child directory record
+ /// Unit is number of uints from start of directory descriptor
+ /// If zero, no right child directory record exists
+ ///
+ /// Little-endian
+ public ushort RightChildOffset { get; set; }
+
+ ///
+ /// 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
+ ///
+ /// Little-endian
+ public uint ExtentOffset { get; set; }
+
+ ///
+ /// 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
+ ///
+ /// Little-endian
+ public uint ExtentSize { get; set; }
+
+ ///
+ /// File attributes of current record
+ ///
+ public FileFlags FileFlags { get; set; }
+
+ ///
+ /// Length in bytes of the following filename
+ ///
+ public byte FilenameLength { get; set; }
+
+ ///
+ /// Name of the record, encoded in single-byte per character
+ ///
+ public byte[] Filename { get; set; } = [];
+
+ ///
+ /// Padding to fill up remainder of uint32
+ /// Not present if filename ends on a byte offset multiple of 4
+ /// 0-3 bytes, all 0xFF
+ public byte[]? Padding { get; set; }
+ }
+}
diff --git a/SabreTools.Data.Models/XDVDFS/Enums.cs b/SabreTools.Data.Models/XDVDFS/Enums.cs
index 6b32693f..aef799c3 100644
--- a/SabreTools.Data.Models/XDVDFS/Enums.cs
+++ b/SabreTools.Data.Models/XDVDFS/Enums.cs
@@ -1,54 +1,54 @@
-using System;
-
-namespace SabreTools.Data.Models.XDVDFS
-{
- ///
- /// Enum for DirectoryRecord.FileFlags
- /// Values are assumed to match lowest byte value of DOS/FAT/NTFS file attributes
- ///
- ///
- ///
- [Flags]
- public enum FileFlags : byte
- {
- ///
- /// Record is read-only
- ///
- READ_ONLY = 0x01,
-
- ///
- /// Record is hidden
- ///
- HIDDEN = 0x02,
-
- ///
- /// Record is part of or for the operating system
- ///
- SYSTEM = 0x04,
-
- ///
- /// Record is a volume ID
- ///
- VOLUME_ID = 0x08,
-
- ///
- /// Record is a directory
- ///
- DIRECTORY = 0x10,
-
- ///
- /// Record should be archived
- ///
- ARCHIVE = 0x20,
-
- ///
- /// Record is a device
- ///
- DEVICE = 0x40,
-
- ///
- /// Record has no other attributes
- ///
- NORMAL = 0x80,
- }
-}
+using System;
+
+namespace SabreTools.Data.Models.XDVDFS
+{
+ ///
+ /// Enum for DirectoryRecord.FileFlags
+ /// Values are assumed to match lowest byte value of DOS/FAT/NTFS file attributes
+ ///
+ ///
+ ///
+ [Flags]
+ public enum FileFlags : byte
+ {
+ ///
+ /// Record is read-only
+ ///
+ READ_ONLY = 0x01,
+
+ ///
+ /// Record is hidden
+ ///
+ HIDDEN = 0x02,
+
+ ///
+ /// Record is part of or for the operating system
+ ///
+ SYSTEM = 0x04,
+
+ ///
+ /// Record is a volume ID
+ ///
+ VOLUME_ID = 0x08,
+
+ ///
+ /// Record is a directory
+ ///
+ DIRECTORY = 0x10,
+
+ ///
+ /// Record should be archived
+ ///
+ ARCHIVE = 0x20,
+
+ ///
+ /// Record is a device
+ ///
+ DEVICE = 0x40,
+
+ ///
+ /// Record has no other attributes
+ ///
+ NORMAL = 0x80,
+ }
+}
diff --git a/SabreTools.Data.Models/XDVDFS/FourPartVersionType.cs b/SabreTools.Data.Models/XDVDFS/FourPartVersionType.cs
index 27ecabb0..63308a6b 100644
--- a/SabreTools.Data.Models/XDVDFS/FourPartVersionType.cs
+++ b/SabreTools.Data.Models/XDVDFS/FourPartVersionType.cs
@@ -1,32 +1,32 @@
-namespace SabreTools.Data.Models.XDVDFS
-{
- ///
- /// Four-part version number commonly used by Microsoft
- ///
- public class FourPartVersionType
- {
- ///
- /// Major Version Number
- ///
- /// Little-endian
- public ushort Major { get; set; }
-
- ///
- /// Minor Version Number
- ///
- /// Little-endian
- public ushort Minor { get; set; }
-
- ///
- /// Build Version Number
- ///
- /// Little-endian
- public ushort Build { get; set; }
-
- ///
- /// Revision Version Number
- ///
- /// Little-endian
- public ushort Revision { get; set; }
- }
-}
+namespace SabreTools.Data.Models.XDVDFS
+{
+ ///
+ /// Four-part version number commonly used by Microsoft
+ ///
+ public class FourPartVersionType
+ {
+ ///
+ /// Major Version Number
+ ///
+ /// Little-endian
+ public ushort Major { get; set; }
+
+ ///
+ /// Minor Version Number
+ ///
+ /// Little-endian
+ public ushort Minor { get; set; }
+
+ ///
+ /// Build Version Number
+ ///
+ /// Little-endian
+ public ushort Build { get; set; }
+
+ ///
+ /// Revision Version Number
+ ///
+ /// Little-endian
+ public ushort Revision { get; set; }
+ }
+}
diff --git a/SabreTools.Data.Models/XDVDFS/LayoutDescriptor.cs b/SabreTools.Data.Models/XDVDFS/LayoutDescriptor.cs
index c2582d73..b01b01f0 100644
--- a/SabreTools.Data.Models/XDVDFS/LayoutDescriptor.cs
+++ b/SabreTools.Data.Models/XDVDFS/LayoutDescriptor.cs
@@ -1,75 +1,75 @@
-namespace SabreTools.Data.Models.XDVDFS
-{
- ///
- /// Xbox DVD Layout Descriptor, present at sector 33 (offset 0x10800) of an Xbox DVD Filesystem
- /// Only present on XGD1 and XGD2 discs
- ///
- ///
- ///
- public class LayoutDescriptor
- {
- ///
- /// Xbox DVD Layout descriptor signature for 2nd sector start
- /// For XGD2, this should be the only non-zero field
- ///
- /// 24 bytes
- public byte[] Signature { get; set; } = new byte[24]; // LayoutDescriptorSignature
-
- ///
- /// Seemingly unused 8 bytes after the signature, should be zeroed
- ///
- /// 8 bytes
- public byte[] Unusued8Bytes { get; set; } = new byte[8];
-
- ///
- /// 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
- ///
- /// 8 bytes
- public FourPartVersionType XBLayoutVersion { get; set; } = new();
-
- ///
- /// Version number of xbpremaster(?) tool used to master the filesystem
- /// If zeroed, xbpremaster was not used
- ///
- /// 8 bytes
- public FourPartVersionType XBPremasterVersion { get; set; } = new();
-
- ///
- /// 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
- ///
- /// 8 bytes
- public FourPartVersionType XBGameDiscVersion { get; set; } = new();
-
- ///
- /// Version number of other microsoft tool used to master the filesystem
- /// May be zeroed, not always present
- ///
- /// 8 bytes
- public FourPartVersionType XBOther1Version { get; set; } = new();
-
- ///
- /// Version number of other microsoft tool used to master the filesystem
- /// May be zeroed, not always present
- ///
- /// 8 bytes
- public FourPartVersionType XBOther2Version { get; set; } = new();
-
- ///
- /// Version number of other microsoft tool used to master the filesystem
- /// May be zeroed, not always present
- ///
- /// 8 bytes
- public FourPartVersionType XBOther3Version { get; set; } = new();
-
- ///
- /// Padding the remainder of sector, should be zeroed
- ///
- /// 1968 bytes
- public byte[] Reserved { get; set; } = new byte[1968];
- }
-}
+namespace SabreTools.Data.Models.XDVDFS
+{
+ ///
+ /// Xbox DVD Layout Descriptor, present at sector 33 (offset 0x10800) of an Xbox DVD Filesystem
+ /// Only present on XGD1 and XGD2 discs
+ ///
+ ///
+ ///
+ public class LayoutDescriptor
+ {
+ ///
+ /// Xbox DVD Layout descriptor signature for 2nd sector start
+ /// For XGD2, this should be the only non-zero field
+ ///
+ /// 24 bytes
+ public byte[] Signature { get; set; } = new byte[24]; // LayoutDescriptorSignature
+
+ ///
+ /// Seemingly unused 8 bytes after the signature, should be zeroed
+ ///
+ /// 8 bytes
+ public byte[] Unusued8Bytes { get; set; } = new byte[8];
+
+ ///
+ /// 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
+ ///
+ /// 8 bytes
+ public FourPartVersionType XBLayoutVersion { get; set; } = new();
+
+ ///
+ /// Version number of xbpremaster(?) tool used to master the filesystem
+ /// If zeroed, xbpremaster was not used
+ ///
+ /// 8 bytes
+ public FourPartVersionType XBPremasterVersion { get; set; } = new();
+
+ ///
+ /// 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
+ ///
+ /// 8 bytes
+ public FourPartVersionType XBGameDiscVersion { get; set; } = new();
+
+ ///
+ /// Version number of other microsoft tool used to master the filesystem
+ /// May be zeroed, not always present
+ ///
+ /// 8 bytes
+ public FourPartVersionType XBOther1Version { get; set; } = new();
+
+ ///
+ /// Version number of other microsoft tool used to master the filesystem
+ /// May be zeroed, not always present
+ ///
+ /// 8 bytes
+ public FourPartVersionType XBOther2Version { get; set; } = new();
+
+ ///
+ /// Version number of other microsoft tool used to master the filesystem
+ /// May be zeroed, not always present
+ ///
+ /// 8 bytes
+ public FourPartVersionType XBOther3Version { get; set; } = new();
+
+ ///
+ /// Padding the remainder of sector, should be zeroed
+ ///
+ /// 1968 bytes
+ public byte[] Reserved { get; set; } = new byte[1968];
+ }
+}
diff --git a/SabreTools.Data.Models/XDVDFS/Volume.cs b/SabreTools.Data.Models/XDVDFS/Volume.cs
index 7adadd44..618c672d 100644
--- a/SabreTools.Data.Models/XDVDFS/Volume.cs
+++ b/SabreTools.Data.Models/XDVDFS/Volume.cs
@@ -1,45 +1,45 @@
-using System.Collections.Generic;
-
-namespace SabreTools.Data.Models.XDVDFS
-{
- ///
- /// 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)
- ///
- ///
- ///
- public class Volume
- {
- ///
- /// Reserved Area, made up of 32 sectors
- /// Contains pseudo-random filler data on-disc
- /// Some XDVDFS images zero the reserved area
- ///
- /// 65,536 bytes
- public byte[] ReservedArea { get; set; } = new byte[0x10000];
-
- ///
- /// XDVDFS Volume Descriptor
- ///
- /// 2048 bytes
- public VolumeDescriptor VolumeDescriptor { get; set; } = new();
-
- ///
- /// 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
- ///
- /// 2048 bytes
- public LayoutDescriptor? LayoutDescriptor { get; set; }
-
- ///
- /// Map of sector numbers and the directory descriptor at that sector number
- /// The root directory descriptor is not guaranteed to be the earliest
- ///
- public Dictionary DirectoryDescriptors { get; set; } = [];
- }
-}
+using System.Collections.Generic;
+
+namespace SabreTools.Data.Models.XDVDFS
+{
+ ///
+ /// 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)
+ ///
+ ///
+ ///
+ public class Volume
+ {
+ ///
+ /// Reserved Area, made up of 32 sectors
+ /// Contains pseudo-random filler data on-disc
+ /// Some XDVDFS images zero the reserved area
+ ///
+ /// 65,536 bytes
+ public byte[] ReservedArea { get; set; } = new byte[0x10000];
+
+ ///
+ /// XDVDFS Volume Descriptor
+ ///
+ /// 2048 bytes
+ public VolumeDescriptor VolumeDescriptor { get; set; } = new();
+
+ ///
+ /// 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
+ ///
+ /// 2048 bytes
+ public LayoutDescriptor? LayoutDescriptor { get; set; }
+
+ ///
+ /// Map of sector numbers and the directory descriptor at that sector number
+ /// The root directory descriptor is not guaranteed to be the earliest
+ ///
+ public Dictionary DirectoryDescriptors { get; set; } = [];
+ }
+}
diff --git a/SabreTools.Data.Models/XDVDFS/VolumeDescriptor.cs b/SabreTools.Data.Models/XDVDFS/VolumeDescriptor.cs
index a39bb838..0bdbf024 100644
--- a/SabreTools.Data.Models/XDVDFS/VolumeDescriptor.cs
+++ b/SabreTools.Data.Models/XDVDFS/VolumeDescriptor.cs
@@ -1,52 +1,52 @@
-namespace SabreTools.Data.Models.XDVDFS
-{
- ///
- /// XDVDFS Volume Descriptor, present at sector 32 (offset 0x10000) of an Xbox DVD Filesystem
- /// Present on XGD1, XGD2, and XGD3 discs
- ///
- ///
- ///
- public class VolumeDescriptor
- {
- ///
- /// Volume descriptor magic, start
- ///
- /// 20 bytes
- public byte[] StartSignature { get; set; } = new byte[20]; // VolumeDescriptorSignature
-
- ///
- /// UInt32 sector location of the root directory descriptor
- ///
- /// Little-endian
- public uint RootOffset { get; set; }
-
- ///
- /// UInt32 size of the root directory descriptor in bytes
- ///
- public uint RootSize { get; set; }
-
- ///
- /// Win32 FILETIME filesystem mastering timestamp
- ///
- /// Little-endian
- public long MasteringTimestamp { get; set; }
-
- ///
- /// Unknown byte, seemingly 0x00 for XGD1, and 0x01 for XGD2 and XGD3
- ///
- /// 1991 bytes
- public byte UnknownByte { get; set; }
-
- ///
- /// Seemingly unused bytes in first sector that are expected to be zeroed
- ///
- /// 1991 bytes
- public byte[] Reserved { get; set; } = new byte[1991];
-
- ///
- /// Volume descriptor magic, start
- ///
- /// 20 bytes
- public byte[] EndSignature { get; set; } = new byte[20]; // VolumeDescriptorSignature
- }
-}
+namespace SabreTools.Data.Models.XDVDFS
+{
+ ///
+ /// XDVDFS Volume Descriptor, present at sector 32 (offset 0x10000) of an Xbox DVD Filesystem
+ /// Present on XGD1, XGD2, and XGD3 discs
+ ///
+ ///
+ ///
+ public class VolumeDescriptor
+ {
+ ///
+ /// Volume descriptor magic, start
+ ///
+ /// 20 bytes
+ public byte[] StartSignature { get; set; } = new byte[20]; // VolumeDescriptorSignature
+
+ ///
+ /// UInt32 sector location of the root directory descriptor
+ ///
+ /// Little-endian
+ public uint RootOffset { get; set; }
+
+ ///
+ /// UInt32 size of the root directory descriptor in bytes
+ ///
+ public uint RootSize { get; set; }
+
+ ///
+ /// Win32 FILETIME filesystem mastering timestamp
+ ///
+ /// Little-endian
+ public long MasteringTimestamp { get; set; }
+
+ ///
+ /// Unknown byte, seemingly 0x00 for XGD1, and 0x01 for XGD2 and XGD3
+ ///
+ /// 1991 bytes
+ public byte UnknownByte { get; set; }
+
+ ///
+ /// Seemingly unused bytes in first sector that are expected to be zeroed
+ ///
+ /// 1991 bytes
+ public byte[] Reserved { get; set; } = new byte[1991];
+
+ ///
+ /// Volume descriptor magic, start
+ ///
+ /// 20 bytes
+ public byte[] EndSignature { get; set; } = new byte[20]; // VolumeDescriptorSignature
+ }
+}
diff --git a/SabreTools.Serialization.Readers.Test/XDVDFSTests.cs b/SabreTools.Serialization.Readers.Test/XDVDFSTests.cs
index 1cd49715..af940d5d 100644
--- a/SabreTools.Serialization.Readers.Test/XDVDFSTests.cs
+++ b/SabreTools.Serialization.Readers.Test/XDVDFSTests.cs
@@ -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(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(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(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(0xFF, 1024)]);
+ var deserializer = new XDVDFS();
+
+ var actual = deserializer.Deserialize(data);
+ Assert.Null(actual);
+ }
+ }
+}
diff --git a/SabreTools.Serialization.Readers/XDVDFS.cs b/SabreTools.Serialization.Readers/XDVDFS.cs
index d0fd44c4..2e7990c2 100644
--- a/SabreTools.Serialization.Readers/XDVDFS.cs
+++ b/SabreTools.Serialization.Readers/XDVDFS.cs
@@ -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
- {
- ///
- 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;
- }
- }
-
- ///
- /// Parse a Stream into a VolumeDescriptor
- ///
- /// Stream to parse
- /// Filled VolumeDescriptor on success, null on error
- 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;
- }
-
- ///
- /// Parse a Stream into a LayoutDescriptor
- ///
- /// Stream to parse
- /// Filled LayoutDescriptor on success, null on error
- 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;
- }
-
- ///
- /// Parse a Stream into a FourPartVersionType
- ///
- /// Stream to parse
- /// Filled FourPartVersionType on success, null on error
- 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;
- }
-
- ///
- /// Parse a Stream into a Dictionary of int to DirectoryDescriptors
- ///
- /// Stream to parse
- /// Sector number descriptor is located at
- /// Number of bytes descriptor contains
- /// Filled Dictionary of int to DirectoryDescriptors on success, null on error
- public static Dictionary? 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();
-
- 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;
- }
-
- ///
- /// Parse a Stream into a DirectoryDescriptor
- ///
- /// Stream to parse
- /// Sector number descriptor is located at
- /// Number of bytes descriptor contains
- /// Filled DirectoryDescriptor on success, null on error
- 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();
-
- 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;
- }
-
- ///
- /// Parse a Stream into a DirectoryRecord
- ///
- /// Stream to parse
- /// Filled DirectoryRecord on success, null on error
- 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
+ {
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Parse a Stream into a VolumeDescriptor
+ ///
+ /// Stream to parse
+ /// Filled VolumeDescriptor on success, null on error
+ 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;
+ }
+
+ ///
+ /// Parse a Stream into a LayoutDescriptor
+ ///
+ /// Stream to parse
+ /// Filled LayoutDescriptor on success, null on error
+ 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;
+ }
+
+ ///
+ /// Parse a Stream into a FourPartVersionType
+ ///
+ /// Stream to parse
+ /// Filled FourPartVersionType on success, null on error
+ 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;
+ }
+
+ ///
+ /// Parse a Stream into a Dictionary of int to DirectoryDescriptors
+ ///
+ /// Stream to parse
+ /// Sector number descriptor is located at
+ /// Number of bytes descriptor contains
+ /// Filled Dictionary of int to DirectoryDescriptors on success, null on error
+ public static Dictionary? 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();
+
+ 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;
+ }
+
+ ///
+ /// Parse a Stream into a DirectoryDescriptor
+ ///
+ /// Stream to parse
+ /// Sector number descriptor is located at
+ /// Number of bytes descriptor contains
+ /// Filled DirectoryDescriptor on success, null on error
+ 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();
+
+ 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;
+ }
+
+ ///
+ /// Parse a Stream into a DirectoryRecord
+ ///
+ /// Stream to parse
+ /// Filled DirectoryRecord on success, null on error
+ 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;
+ }
+ }
+}
diff --git a/SabreTools.Wrappers/XDVDFS.Extraction.cs b/SabreTools.Wrappers/XDVDFS.Extraction.cs
index ee3b212a..29ba3fb1 100644
--- a/SabreTools.Wrappers/XDVDFS.Extraction.cs
+++ b/SabreTools.Wrappers/XDVDFS.Extraction.cs
@@ -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
-
- ///
- /// List of extracted files by their sector offset
- ///
- private readonly Dictionary extractedFiles = [];
-
- #endregion
-
- ///
- 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);
- }
-
- ///
- /// Extracts all directory records recursively from directory descriptor
- ///
- 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
+
+ ///
+ /// List of extracted files by their sector offset
+ ///
+ private readonly Dictionary extractedFiles = [];
+
+ #endregion
+
+ ///
+ 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);
+ }
+
+ ///
+ /// Extracts all directory records recursively from directory descriptor
+ ///
+ 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;
+ }
+ }
+}
diff --git a/SabreTools.Wrappers/XDVDFS.Printing.cs b/SabreTools.Wrappers/XDVDFS.Printing.cs
index d5f496d7..60b9a114 100644
--- a/SabreTools.Wrappers/XDVDFS.Printing.cs
+++ b/SabreTools.Wrappers/XDVDFS.Printing.cs
@@ -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
- ///
- public string ExportJSON() => System.Text.Json.JsonSerializer.Serialize(Model, _jsonSerializerOptions);
-#endif
-
- ///
- 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
+ ///
+ public string ExportJSON() => System.Text.Json.JsonSerializer.Serialize(Model, _jsonSerializerOptions);
+#endif
+
+ ///
+ 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();
+ }
+ }
+}
diff --git a/SabreTools.Wrappers/XDVDFS.cs b/SabreTools.Wrappers/XDVDFS.cs
index 2542b4b8..1795e70b 100644
--- a/SabreTools.Wrappers/XDVDFS.cs
+++ b/SabreTools.Wrappers/XDVDFS.cs
@@ -1,107 +1,107 @@
-using System.Collections.Generic;
-using System.IO;
-using SabreTools.Data.Models.XDVDFS;
-
-namespace SabreTools.Wrappers
-{
- public partial class XDVDFS : WrapperBase
- {
- #region Descriptive Properties
-
- ///
- public override string DescriptionString => "Xbox DVD Filesystem";
-
- #endregion
-
- #region Extension Properties
-
- ///
- public byte[] ReservedArea => Model.ReservedArea;
-
- ///
- public VolumeDescriptor VolumeDescriptor => Model.VolumeDescriptor;
-
- ///
- public LayoutDescriptor? LayoutDescriptor => Model.LayoutDescriptor;
-
- ///
- public Dictionary DirectoryDescriptors => Model.DirectoryDescriptors;
-
- #endregion
-
- #region Constructors
-
- ///
- public XDVDFS(Volume model, byte[] data) : base(model, data) { }
-
- ///
- public XDVDFS(Volume model, byte[] data, int offset) : base(model, data, offset) { }
-
- ///
- public XDVDFS(Volume model, byte[] data, int offset, int length) : base(model, data, offset, length) { }
-
- ///
- public XDVDFS(Volume model, Stream data) : base(model, data) { }
-
- ///
- public XDVDFS(Volume model, Stream data, long offset) : base(model, data, offset) { }
-
- ///
- public XDVDFS(Volume model, Stream data, long offset, long length) : base(model, data, offset, length) { }
-
- #endregion
-
- #region Static Constructors
-
- ///
- /// Create an XDVDFS Volume from a byte array and offset
- ///
- /// Byte array representing the XDVDFS Volume
- /// Offset within the array to parse
- /// An XDVDFS Volume wrapper on success, null on failure
- 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);
- }
-
- ///
- /// Create an XDVDFS Volume from a Stream
- ///
- /// Stream representing the XDVDFS Volume
- /// An XDVDFS Volume wrapper on success, null on failure
- 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
+ {
+ #region Descriptive Properties
+
+ ///
+ public override string DescriptionString => "Xbox DVD Filesystem";
+
+ #endregion
+
+ #region Extension Properties
+
+ ///
+ public byte[] ReservedArea => Model.ReservedArea;
+
+ ///
+ public VolumeDescriptor VolumeDescriptor => Model.VolumeDescriptor;
+
+ ///
+ public LayoutDescriptor? LayoutDescriptor => Model.LayoutDescriptor;
+
+ ///
+ public Dictionary DirectoryDescriptors => Model.DirectoryDescriptors;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ public XDVDFS(Volume model, byte[] data) : base(model, data) { }
+
+ ///
+ public XDVDFS(Volume model, byte[] data, int offset) : base(model, data, offset) { }
+
+ ///
+ public XDVDFS(Volume model, byte[] data, int offset, int length) : base(model, data, offset, length) { }
+
+ ///
+ public XDVDFS(Volume model, Stream data) : base(model, data) { }
+
+ ///
+ public XDVDFS(Volume model, Stream data, long offset) : base(model, data, offset) { }
+
+ ///
+ public XDVDFS(Volume model, Stream data, long offset, long length) : base(model, data, offset, length) { }
+
+ #endregion
+
+ #region Static Constructors
+
+ ///
+ /// Create an XDVDFS Volume from a byte array and offset
+ ///
+ /// Byte array representing the XDVDFS Volume
+ /// Offset within the array to parse
+ /// An XDVDFS Volume wrapper on success, null on failure
+ 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);
+ }
+
+ ///
+ /// Create an XDVDFS Volume from a Stream
+ ///
+ /// Stream representing the XDVDFS Volume
+ /// An XDVDFS Volume wrapper on success, null on failure
+ 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
+ }
+}