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 + } +}