From 6f000b1e89cae6643ab847f5abb4e89f9181a658 Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Sun, 19 Apr 2026 18:35:33 +0900 Subject: [PATCH] XRD File Support --- SabreTools.Data.Models/XDVDFS/Constants.cs | 2 +- .../XDVDFS/DirectoryDescriptor.cs | 2 +- .../XDVDFS/DirectoryRecord.cs | 2 +- .../XDVDFS/LayoutDescriptor.cs | 2 +- SabreTools.Data.Models/XDVDFS/Volume.cs | 2 +- .../XDVDFS/VolumeDescriptor.cs | 2 +- SabreTools.Data.Models/XRD/Constants.cs | 10 + SabreTools.Data.Models/XRD/DirectoryEntry.cs | 20 ++ SabreTools.Data.Models/XRD/File.cs | 270 +++++++++++++++ SabreTools.Data.Models/XRD/FileEntry.cs | 20 ++ .../XenonExecutable/TableEntry.cs | 2 +- .../XRDTests.cs | 72 ++++ SabreTools.Serialization.Readers/XDVDFS.cs | 7 +- SabreTools.Serialization.Readers/XRD.cs | 154 +++++++++ SabreTools.Serialization.Writers/XDVDFS.cs | 47 +-- SabreTools.Serialization.Writers/XRD.cs | 322 ++++++++++++++++++ SabreTools.Wrappers.Test/XRDTests.cs | 60 ++++ SabreTools.Wrappers/WrapperFactory.cs | 11 + SabreTools.Wrappers/WrapperType.cs | 5 + SabreTools.Wrappers/XDVDFS.Printing.cs | 8 +- SabreTools.Wrappers/XRD.Printing.cs | 114 +++++++ SabreTools.Wrappers/XRD.cs | 89 +++++ .../XboxExecutable.Printing.cs | 2 +- .../XenonExecutable.Printing.cs | 2 +- 24 files changed, 1188 insertions(+), 39 deletions(-) create mode 100644 SabreTools.Data.Models/XRD/Constants.cs create mode 100644 SabreTools.Data.Models/XRD/DirectoryEntry.cs create mode 100644 SabreTools.Data.Models/XRD/File.cs create mode 100644 SabreTools.Data.Models/XRD/FileEntry.cs create mode 100644 SabreTools.Serialization.Readers.Test/XRDTests.cs create mode 100644 SabreTools.Serialization.Readers/XRD.cs create mode 100644 SabreTools.Serialization.Writers/XRD.cs create mode 100644 SabreTools.Wrappers.Test/XRDTests.cs create mode 100644 SabreTools.Wrappers/XRD.Printing.cs create mode 100644 SabreTools.Wrappers/XRD.cs diff --git a/SabreTools.Data.Models/XDVDFS/Constants.cs b/SabreTools.Data.Models/XDVDFS/Constants.cs index 3e37b883c..9b804d6af 100644 --- a/SabreTools.Data.Models/XDVDFS/Constants.cs +++ b/SabreTools.Data.Models/XDVDFS/Constants.cs @@ -1,6 +1,6 @@ namespace SabreTools.Data.Models.XDVDFS { - /// + /// public static class Constants { /// diff --git a/SabreTools.Data.Models/XDVDFS/DirectoryDescriptor.cs b/SabreTools.Data.Models/XDVDFS/DirectoryDescriptor.cs index cc27f15aa..784b367ec 100644 --- a/SabreTools.Data.Models/XDVDFS/DirectoryDescriptor.cs +++ b/SabreTools.Data.Models/XDVDFS/DirectoryDescriptor.cs @@ -6,7 +6,7 @@ namespace SabreTools.Data.Models.XDVDFS /// Padded with 0xFF to be a multiple of 2048 bytes /// /// - /// + /// public class DirectoryDescriptor { /// diff --git a/SabreTools.Data.Models/XDVDFS/DirectoryRecord.cs b/SabreTools.Data.Models/XDVDFS/DirectoryRecord.cs index 8e14305e6..478e5e9f3 100644 --- a/SabreTools.Data.Models/XDVDFS/DirectoryRecord.cs +++ b/SabreTools.Data.Models/XDVDFS/DirectoryRecord.cs @@ -5,7 +5,7 @@ namespace SabreTools.Data.Models.XDVDFS /// Padded with 0xFF to be a multiple of 4 bytes /// /// - /// + /// public class DirectoryRecord { /// diff --git a/SabreTools.Data.Models/XDVDFS/LayoutDescriptor.cs b/SabreTools.Data.Models/XDVDFS/LayoutDescriptor.cs index 43f5dd8b4..edca9fd92 100644 --- a/SabreTools.Data.Models/XDVDFS/LayoutDescriptor.cs +++ b/SabreTools.Data.Models/XDVDFS/LayoutDescriptor.cs @@ -5,7 +5,7 @@ namespace SabreTools.Data.Models.XDVDFS /// Only present on XGD1 and XGD2 discs /// /// - /// + /// public class LayoutDescriptor { /// diff --git a/SabreTools.Data.Models/XDVDFS/Volume.cs b/SabreTools.Data.Models/XDVDFS/Volume.cs index 618c672d1..7a2e5b886 100644 --- a/SabreTools.Data.Models/XDVDFS/Volume.cs +++ b/SabreTools.Data.Models/XDVDFS/Volume.cs @@ -10,7 +10,7 @@ namespace SabreTools.Data.Models.XDVDFS /// Gaps also include the security sector ranges, 4096 sectors each that cannot be read from discs (usually zeroed) /// /// - /// + /// public class Volume { /// diff --git a/SabreTools.Data.Models/XDVDFS/VolumeDescriptor.cs b/SabreTools.Data.Models/XDVDFS/VolumeDescriptor.cs index f4a4e0971..901a6baa4 100644 --- a/SabreTools.Data.Models/XDVDFS/VolumeDescriptor.cs +++ b/SabreTools.Data.Models/XDVDFS/VolumeDescriptor.cs @@ -5,7 +5,7 @@ namespace SabreTools.Data.Models.XDVDFS /// Present on XGD1, XGD2, and XGD3 discs /// /// - /// + /// public class VolumeDescriptor { /// diff --git a/SabreTools.Data.Models/XRD/Constants.cs b/SabreTools.Data.Models/XRD/Constants.cs new file mode 100644 index 000000000..80dd2b5dc --- /dev/null +++ b/SabreTools.Data.Models/XRD/Constants.cs @@ -0,0 +1,10 @@ +namespace SabreTools.Data.Models.XRD +{ + public static class Constants + { + /// + /// Magic string at start of XRD file + /// + public static readonly byte[] MagicBytes = [0x58, 0x52, 0x44, 0xFF, 0x00]; + } +} diff --git a/SabreTools.Data.Models/XRD/DirectoryEntry.cs b/SabreTools.Data.Models/XRD/DirectoryEntry.cs new file mode 100644 index 000000000..60629bbdc --- /dev/null +++ b/SabreTools.Data.Models/XRD/DirectoryEntry.cs @@ -0,0 +1,20 @@ +namespace SabreTools.Data.Models.XRD +{ + public class DirectoryEntry + { + /// + /// Sector offset of descriptor in XDVDFS filesystem + /// + public uint Offset { get; set; } + + /// + /// Size of descriptor in sectors + /// + public uint Size { get; set; } + + /// + /// SHA-1 Hash of file content + /// + public XDVDFS.DirectoryDescriptor DirectoryDescriptor { get; set; } = new(); + } +} diff --git a/SabreTools.Data.Models/XRD/File.cs b/SabreTools.Data.Models/XRD/File.cs new file mode 100644 index 000000000..c233d4a5d --- /dev/null +++ b/SabreTools.Data.Models/XRD/File.cs @@ -0,0 +1,270 @@ +namespace SabreTools.Data.Models.XRD +{ + /// + /// Xbox Rebuild/Recovery/Redump Data + /// Custom file format containing descriptive metadata about Xbox / Xbox 360 disc images + /// + public class File + { + /// + /// "XRD\xFF\x00" + /// + public byte[] Magic { get; set; } = new byte[5]; + + /// + /// XRD File Format Version + /// 0x01 = Standard + /// 0x02 = Also contains Wiped Video ISO size/hashes, and Video ISO file size/hashes + /// Intended use is Version 0x02 only for XGD3 discs, but not mandated + /// + public byte Version { get; set; } + + /// + /// XGD Type: XGD1, XGD2, XGD3, 0xFF = Unknown + /// + public byte XGDType { get; set; } + + /// + /// XGD SubType + /// XGD1: + /// subtype 0 = XGD1 Beta (XB00104M) + /// subtype 1 = Standard + /// XGD2: + /// subtype 0 = XGD2 Beta "Wave 0" (3CFB91D5) + /// subtype 1-20 = Wave 1-20, Standard + /// subtype 0x81 = Hybrid XGD2 / DVD-Video (65472451) + /// XGD3: + /// subtype 0 = XGD3 Beta (152C2978, FD91511A, FFFFFDEB, FFFFFDE3) + /// subtype 1 = Standard + /// 0xFF = Unknown + /// Others undefined + /// + public byte XGDSubtype { get; set; } + + /// + /// 8-character serial in disc ringcode + /// e.g. XGD1: e.g. "MS00101A" + /// e.g. XGD2/3: e.g. "1A2B3C4D" + /// Can be parsed from XBE/XEX Certificate + /// + public byte[] Ringcode { get; set; } = new byte[8]; + + /// + /// Size of the redump ISO in bytes + /// + /// Little-endian + public ulong RedumpSize { get; set; } + + /// + /// CRC-32 hash of the redump ISO + /// + public byte[] RedumpCRC { get; set; } = new byte[4]; + + /// + /// MD5 hash of the redump ISO + /// + public byte[] RedumpMD5 { get; set; } = new byte[16]; + + /// + /// SHA-1 hash of the redump ISO + /// + public byte[] RedumpSHA1 { get; set; } = new byte[20]; + + /// + /// Size of the Raw XISO in bytes + /// + /// Little-endian + public ulong RawXISOSize { get; set; } + + /// + /// CRC-32 hash of the Raw XISO + /// + public byte[] RawXISOCRC { get; set; } = new byte[4]; + + /// + /// MD5 hash of the Raw XISO + /// + public byte[] RawXISOMD5 { get; set; } = new byte[16]; + + /// + /// SHA-1 hash of the Raw XISO + /// + public byte[] RawXISOSHA1 { get; set; } = new byte[20]; + + /// + /// Size of the Wiped/Trimmed XISO in bytes + /// + /// Little-endian + public ulong CookedXISOSize { get; set; } + + /// + /// CRC-32 hash of the Wiped/Trimmed XISO + /// + public byte[] CookedXISOCRC { get; set; } = new byte[4]; + + /// + /// MD5 hash of the Wiped/Trimmed XISO + /// + public byte[] CookedXISOMD5 { get; set; } = new byte[16]; + + /// + /// SHA-1 hash of the Wiped/Trimmed XISO + /// + public byte[] CookedXISOSHA1 { get; set; } = new byte[20]; + + /// + /// Size of the Video ISO in bytes + /// + /// Little-endian + public ulong VideoISOSize { get; set; } + + /// + /// CRC-32 hash of the Video ISO + /// + public byte[] VideoISOCRC { get; set; } = new byte[4]; + + /// + /// MD5 hash of the Video ISO + /// + public byte[] VideoISOMD5 { get; set; } = new byte[16]; + + /// + /// SHA-1 hash of the Video ISO + /// + public byte[] VideoISOSHA1 { get; set; } = new byte[20]; + + /// + /// Size of the wiped Video ISO in bytes + /// Field does not exist for Version = 1 + /// + /// Little-endian + public ulong? WipedVideoISOSize { get; set; } + + /// + /// CRC-32 hash of the wiped Video ISO + /// Field does not exist for Version = 1 + /// + public byte[]? WipedVideoISOCRC { get; set; } = new byte[4]; + + /// + /// MD5 hash of the wiped Video ISO + /// Field does not exist for Version = 1 + /// + public byte[]? WipedVideoISOMD5 { get; set; } = new byte[16]; + + /// + /// SHA-1 hash of the wiped Video ISO + /// Field does not exist for Version = 1 + /// + public byte[]? WipedVideoISOSHA1 { get; set; } = new byte[20]; + + /// + /// Size of the filler data that has been hashed, in bytes + /// Should always be 2048 + /// + /// Little-endian + public ulong FillerSize { get; set; } + + /// + /// CRC-32 Hash of the first sector of the XDVDFS filesystem (filler data) + /// The hash is used to identify filler data or brute force the seed + /// + public byte[] FillerCRC { get; set; } = new byte[4]; + + /// + /// MD5 Hash of the first sector of the XDVDFS filesystem (filler data) + /// The hash is used to identify filler data or brute force the seed + /// + public byte[] FillerMD5 { get; set; } = new byte[16]; + + /// + /// SHA-1 Hash of the first sector of the XDVDFS filesystem (filler data) + /// The hash is used to identify filler data or brute force the seed + /// + public byte[] FillerSHA1 { get; set; } = new byte[20]; + + /// + /// The starting sector offset for each security sector range + /// Security sector ranges are 4096-sectors long + /// XGD1: 16 sector offsets + /// XGD2/3: 2 sector offsets + public uint[]? SecuritySectors { get; set; } = []; + + /// + /// XBE Certificate, starts with length of structure + /// + /// XGD1 only, Little-endian + public XboxExecutable.Certificate? XboxCertificate { get; set; } + + /// + /// XEX Certificate, starts with length of structure + /// + /// XGD2/3 only, Big-endian + public XenonExecutable.Certificate? Xbox360Certificate { get; set; } + + /// + /// Number of files in the XISO + /// + /// Little-endian + public int FileCount { get; set; } + + /// + /// File offsets and hashes in the XISO + /// Length of array equal to FileCount + /// + public FileEntry[] FileInfo { get; set; } = []; + + /// + /// XISO Volume Descriptor + /// + /// 2048 bytes + public XDVDFS.VolumeDescriptor VolumeDescriptor { get; set; } = new(); + + /// + /// Xbox DVD Layout Descriptor, immediately follows Volume Descriptor + /// XGD1: Contains version numbers and signature bytes (always present) + /// XGD2: Zeroed apart from initial signature bytes (always present?) + /// XGD3: Sector not present (always not present?) + /// Always check if this is present by reading LayoutDescriptor magic bytes + /// + /// 2048 bytes + public XDVDFS.LayoutDescriptor? LayoutDescriptor { get; set; } + + /// + /// Number of directory records in the XISO + /// + /// Little-endian + public int DirectoryCount { get; set; } + + /// + /// List of XISO descriptors and their sector offsets and sizes + /// The root directory descriptor is not guaranteed to be the first + /// + public DirectoryEntry[] DirectoryInfo { get; set; } = []; + + /// + /// Number of files in Video ISO + /// Field does not exist for Version = 1 + /// + /// Little-endian + public int? VideoISOFileCount { get; set; } + + /// + /// File offsets and hashes in the Video ISO + /// Length of array equal to VideoISOFileCount + /// Field does not exist for Version = 1 + /// + public FileEntry[]? VideoISOFileInfo { get; set; } + + /// + /// Size of all data in XRD file prior to this field + /// + /// Little-endian + public uint XRDSize { get; set; } + + /// + /// SHA-1 Hash of the XRD file prior to XRDSize field + /// + public byte[] XRDSHA1 { get; set; } = new byte[20]; + } +} diff --git a/SabreTools.Data.Models/XRD/FileEntry.cs b/SabreTools.Data.Models/XRD/FileEntry.cs new file mode 100644 index 000000000..c693f30fb --- /dev/null +++ b/SabreTools.Data.Models/XRD/FileEntry.cs @@ -0,0 +1,20 @@ +namespace SabreTools.Data.Models.XRD +{ + public class FileEntry + { + /// + /// Sector offset of file in XDVDFS filesystem + /// + public uint Offset { get; set; } + + /// + /// Size of file in bytes + /// + public ulong Size { get; set; } + + /// + /// SHA-1 Hash of file content + /// + public byte[] SHA1 { get; set; } = new byte[20]; + } +} diff --git a/SabreTools.Data.Models/XenonExecutable/TableEntry.cs b/SabreTools.Data.Models/XenonExecutable/TableEntry.cs index b7f98bb1f..8a7c7b8e7 100644 --- a/SabreTools.Data.Models/XenonExecutable/TableEntry.cs +++ b/SabreTools.Data.Models/XenonExecutable/TableEntry.cs @@ -29,6 +29,6 @@ public class TableEntry /// /// Table entry data, 20 bytes /// - public byte[]? Data { get; set; } = new byte[20]; + public byte[] Data { get; set; } = new byte[20]; } } diff --git a/SabreTools.Serialization.Readers.Test/XRDTests.cs b/SabreTools.Serialization.Readers.Test/XRDTests.cs new file mode 100644 index 000000000..aaf7e1070 --- /dev/null +++ b/SabreTools.Serialization.Readers.Test/XRDTests.cs @@ -0,0 +1,72 @@ +using System.IO; +using System.Linq; +using Xunit; + +namespace SabreTools.Serialization.Readers.Test +{ + public class XRDTests + { + [Fact] + public void NullArray_Null() + { + byte[]? data = null; + int offset = 0; + var deserializer = new XRD(); + + var actual = deserializer.Deserialize(data, offset); + Assert.Null(actual); + } + + [Fact] + public void EmptyArray_Null() + { + byte[]? data = []; + int offset = 0; + var deserializer = new XRD(); + + 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 XRD(); + + var actual = deserializer.Deserialize(data, offset); + Assert.Null(actual); + } + + [Fact] + public void NullStream_Null() + { + Stream? data = null; + var deserializer = new XRD(); + + var actual = deserializer.Deserialize(data); + Assert.Null(actual); + } + + [Fact] + public void EmptyStream_Null() + { + Stream? data = new MemoryStream([]); + var deserializer = new XRD(); + + var actual = deserializer.Deserialize(data); + Assert.Null(actual); + } + + [Fact] + public void InvalidStream_Null() + { + Stream? data = new MemoryStream([.. Enumerable.Repeat(0xFF, 1024)]); + var deserializer = new XRD(); + + var actual = deserializer.Deserialize(data); + Assert.Null(actual); + } + } +} diff --git a/SabreTools.Serialization.Readers/XDVDFS.cs b/SabreTools.Serialization.Readers/XDVDFS.cs index f169a0cc3..1127f363d 100644 --- a/SabreTools.Serialization.Readers/XDVDFS.cs +++ b/SabreTools.Serialization.Readers/XDVDFS.cs @@ -162,16 +162,17 @@ public static FourPartVersionType ParseFourPartVersionType(Stream data) continue; // Ensure offset is valid - if ((((long)offset) * Constants.SectorSize) + size > data.Length) + if ((((long)dr.ExtentOffset) * Constants.SectorSize) + size > data.Length) return null; + // Seek to child descriptor + data.SeekIfPossible(initialOffset + (((long)dr.ExtentOffset) * Constants.SectorSize), SeekOrigin.Begin); + // Get all descriptors from child var descriptors = ParseDirectoryDescriptors(data, initialOffset, dr.ExtentOffset, dr.ExtentSize); if (descriptors is null) continue; - data.SeekIfPossible(initialOffset + (((long)offset) * Constants.SectorSize), SeekOrigin.Begin); - // Merge dictionaries foreach (var kvp in descriptors) { diff --git a/SabreTools.Serialization.Readers/XRD.cs b/SabreTools.Serialization.Readers/XRD.cs new file mode 100644 index 000000000..c8c090d0c --- /dev/null +++ b/SabreTools.Serialization.Readers/XRD.cs @@ -0,0 +1,154 @@ +using System.Collections.Generic; +using System.IO; +using SabreTools.Data.Models.XRD; +using SabreTools.IO.Extensions; +using SabreTools.Matching; +using SabreTools.Numerics.Extensions; + +namespace SabreTools.Serialization.Readers +{ + public class XRD : BaseBinaryReader + { + /// + public override Data.Models.XRD.File? Deserialize(Stream? data) + { + // If the data is invalid + if (data is null || !data.CanRead) + return null; + + // Simple check for a valid stream length + if (2320 > data.Length - data.Position) + return null; + + try + { + // Cache the current offset + long initialOffset = data.Position; + + // Create a new Volume to fill + var xrd = new Data.Models.XRD.File(); + + xrd.Magic = data.ReadBytes(5); + if (!xrd.Magic.EqualsExactly(Constants.MagicBytes)) + return null; + + xrd.Version = data.ReadByteValue(); + if (xrd.Version != 0x01 || xrd.Version != 0x02) + return null; + + xrd.XGDType = data.ReadByteValue(); + xrd.XGDSubtype = data.ReadByteValue(); + xrd.Ringcode = data.ReadBytes(8); + xrd.RedumpSize = data.ReadUInt64LittleEndian(); + xrd.RedumpCRC = data.ReadBytes(4); + xrd.RedumpMD5 = data.ReadBytes(16); + xrd.RedumpSHA1 = data.ReadBytes(20); + xrd.RawXISOSize = data.ReadUInt64LittleEndian(); + xrd.RawXISOCRC = data.ReadBytes(4); + xrd.RawXISOMD5 = data.ReadBytes(16); + xrd.RawXISOSHA1 = data.ReadBytes(20); + xrd.CookedXISOSize = data.ReadUInt64LittleEndian(); + xrd.CookedXISOCRC = data.ReadBytes(4); + xrd.CookedXISOMD5 = data.ReadBytes(16); + xrd.CookedXISOSHA1 = data.ReadBytes(20); + xrd.VideoISOSize = data.ReadUInt64LittleEndian(); + xrd.VideoISOCRC = data.ReadBytes(4); + xrd.VideoISOMD5 = data.ReadBytes(16); + xrd.VideoISOSHA1 = data.ReadBytes(20); + + if (xrd.Version == 0x02) + { + xrd.WipedVideoISOSize = data.ReadUInt64LittleEndian(); + xrd.WipedVideoISOCRC = data.ReadBytes(4); + xrd.WipedVideoISOMD5 = data.ReadBytes(16); + xrd.WipedVideoISOSHA1 = data.ReadBytes(20); + } + + xrd.FillerSize = data.ReadUInt64LittleEndian(); + xrd.FillerCRC = data.ReadBytes(4); + xrd.FillerMD5 = data.ReadBytes(16); + xrd.FillerSHA1 = data.ReadBytes(20); + + if (xrd.XGDType == 1) + { + xrd.SecuritySectors = new uint[16]; + for (int i = 0; i < 16; i++) + { + xrd.SecuritySectors[i] = data.ReadUInt32LittleEndian(); + } + + xrd.XboxCertificate = XboxExecutable.ParseCertificate(data); + } + else if (xrd.XGDType == 2 || xrd.XGDType == 3) + { + xrd.SecuritySectors = new uint[2]; + for (int i = 0; i < 2; i++) + { + xrd.SecuritySectors[i] = data.ReadUInt32LittleEndian(); + } + + xrd.Xbox360Certificate = XenonExecutable.ParseCertificate(data); + } + + xrd.FileCount = data.ReadInt32LittleEndian(); + + xrd.FileInfo = new FileEntry[xrd.FileCount]; + for (int i = 0; i < xrd.FileCount; i++) + { + FileEntry file = new FileEntry(); + file.Offset = data.ReadUInt32LittleEndian(); + file.Size = data.ReadUInt64LittleEndian(); + file.SHA1 = data.ReadBytes(20); + xrd.FileInfo[i] = file; + } + + var vd = XDVDFS.ParseVolumeDescriptor(data); + if (vd is null) + return null; + + xrd.VolumeDescriptor = vd; + xrd.LayoutDescriptor = XDVDFS.ParseLayoutDescriptor(data); + + xrd.DirectoryCount = data.ReadInt32LittleEndian(); + + xrd.DirectoryInfo = new DirectoryEntry[xrd.DirectoryCount]; + for (int i = 0; i < xrd.DirectoryCount; i++) + { + DirectoryEntry directory = new DirectoryEntry(); + directory.Offset = data.ReadUInt32LittleEndian(); + directory.Size = data.ReadUInt32LittleEndian(); + var dd = XDVDFS.ParseDirectoryDescriptor(data, initialOffset, directory.Offset, directory.Size); + if (dd is null) + return null; + directory.DirectoryDescriptor = dd; + xrd.DirectoryInfo[i] = directory; + } + + if (xrd.Version == 0x02) + { + xrd.VideoISOFileCount = data.ReadInt32LittleEndian(); + + xrd.VideoISOFileInfo = new FileEntry[xrd.VideoISOFileCount ?? 0]; + for (int i = 0; i < xrd.VideoISOFileCount; i++) + { + FileEntry file = new FileEntry(); + file.Offset = data.ReadUInt32LittleEndian(); + file.Size = data.ReadUInt64LittleEndian(); + file.SHA1 = data.ReadBytes(20); + xrd.VideoISOFileInfo[i] = file; + } + } + + xrd.XRDSize = data.ReadUInt32LittleEndian(); + xrd.XRDSHA1 = data.ReadBytes(20); + + return xrd; + } + catch + { + // Ignore the actual error + return null; + } + } + } +} diff --git a/SabreTools.Serialization.Writers/XDVDFS.cs b/SabreTools.Serialization.Writers/XDVDFS.cs index 55354c529..14129e097 100644 --- a/SabreTools.Serialization.Writers/XDVDFS.cs +++ b/SabreTools.Serialization.Writers/XDVDFS.cs @@ -23,7 +23,10 @@ public bool SerializeFile(Volume? obj, string? path) // Create the file stream using var fs = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None); - SerializeHeader(fs, obj); + fs.Write(obj.ReservedArea, 0, obj.ReservedArea.Length); + SerializeVolumeDescriptor(fs, obj.VolumeDescriptor); + if (obj.LayoutDescriptor is not null) + SerializeLayoutDescriptor(fs, obj.LayoutDescriptor); // Loop over all directory descriptors in order of offset uint[] keys = new uint[obj.DirectoryDescriptors.Count]; @@ -74,30 +77,28 @@ public static bool ValidateVolume(Volume? obj) return true; } - public static void SerializeHeader(Stream stream, Volume obj) + public static void SerializeVolumeDescriptor(Stream stream, VolumeDescriptor obj) { - stream.Write(obj.ReservedArea, 0, obj.ReservedArea.Length); - - stream.Write(obj.VolumeDescriptor.StartSignature, 0, obj.VolumeDescriptor.StartSignature.Length); - stream.WriteLittleEndian(obj.VolumeDescriptor.RootOffset); - stream.WriteLittleEndian(obj.VolumeDescriptor.RootSize); - stream.WriteLittleEndian(obj.VolumeDescriptor.MasteringTimestamp); - stream.WriteByte(obj.VolumeDescriptor.UnknownByte); - stream.Write(obj.VolumeDescriptor.Reserved, 0, obj.VolumeDescriptor.Reserved.Length); - stream.Write(obj.VolumeDescriptor.EndSignature, 0, obj.VolumeDescriptor.EndSignature.Length); + stream.Write(obj.StartSignature, 0, obj.StartSignature.Length); + stream.WriteLittleEndian(obj.RootOffset); + stream.WriteLittleEndian(obj.RootSize); + stream.WriteLittleEndian(obj.MasteringTimestamp); + stream.WriteByte(obj.UnknownByte); + stream.Write(obj.Reserved, 0, obj.Reserved.Length); + stream.Write(obj.EndSignature, 0, obj.EndSignature.Length); + } - if (obj.LayoutDescriptor is not null) - { - stream.Write(obj.LayoutDescriptor.Signature, 0, obj.LayoutDescriptor.Signature.Length); - stream.Write(obj.LayoutDescriptor.Unused8Bytes, 0, obj.LayoutDescriptor.Unused8Bytes.Length); - SerializeFourPartVersionType(stream, obj.LayoutDescriptor.XBLayoutVersion); - SerializeFourPartVersionType(stream, obj.LayoutDescriptor.XBPremasterVersion); - SerializeFourPartVersionType(stream, obj.LayoutDescriptor.XBGameDiscVersion); - SerializeFourPartVersionType(stream, obj.LayoutDescriptor.XBOther1Version); - SerializeFourPartVersionType(stream, obj.LayoutDescriptor.XBOther2Version); - SerializeFourPartVersionType(stream, obj.LayoutDescriptor.XBOther3Version); - stream.Write(obj.LayoutDescriptor.Reserved, 0, obj.LayoutDescriptor.Reserved.Length); - } + public static void SerializeLayoutDescriptor(Stream stream, LayoutDescriptor obj) + { + stream.Write(obj.Signature, 0, obj.Signature.Length); + stream.Write(obj.Unused8Bytes, 0, obj.Unused8Bytes.Length); + SerializeFourPartVersionType(stream, obj.XBLayoutVersion); + SerializeFourPartVersionType(stream, obj.XBPremasterVersion); + SerializeFourPartVersionType(stream, obj.XBGameDiscVersion); + SerializeFourPartVersionType(stream, obj.XBOther1Version); + SerializeFourPartVersionType(stream, obj.XBOther2Version); + SerializeFourPartVersionType(stream, obj.XBOther3Version); + stream.Write(obj.Reserved, 0, obj.Reserved.Length); } public static void SerializeFourPartVersionType(Stream stream, FourPartVersionType obj) diff --git a/SabreTools.Serialization.Writers/XRD.cs b/SabreTools.Serialization.Writers/XRD.cs new file mode 100644 index 000000000..dfa410c33 --- /dev/null +++ b/SabreTools.Serialization.Writers/XRD.cs @@ -0,0 +1,322 @@ +using System; +using System.IO; +using System.Text; + +namespace SabreTools.Serialization.Writers +{ + public class XRD : BaseBinaryWriter + { + /// + public override Stream? SerializeStream(Data.Models.XRD.File? obj) + { + // If the data is invalid + if (obj?.Magic is null) + return null; + + // If the magic doesn't match + if (obj.Magic != Data.Models.XRD.Constants.MagicBytes) + return null; + + // If the version is not supported + if (obj.Version == 0 || obj.Version > 2) + return null; + + // If the version specific fields are not set/unset + if (obj.Version == 1 && (obj.WipedVideoISOSize is not null || obj.WipedVideoISOCRC is not null || obj.WipedVideoISOMD5 is not null || obj.WipedVideoISOSHA1 is not null)) + return null; + if (obj.Version == 2 && (obj.WipedVideoISOSize is null || obj.WipedVideoISOCRC is null || obj.WipedVideoISOMD5 is null || obj.WipedVideoISOSHA1 is null)) + return null; + if (obj.XGDType == 1 && obj.XboxCertificate is null) + return null; + if (obj.XGDType != 1 && obj.XboxCertificate is not null) + return null; + if (obj.Version == 1 && (obj.VideoISOFileCount is not null || obj.VideoISOFileInfo is not null)) + return null; + if (obj.Version == 2 && (obj.VideoISOFileCount is null || obj.VideoISOFileInfo is null)) + return null; + + // If the XGD Type specific fields are not set/unset + if ((obj.XGDType == 1 || obj.XGDType == 2 || obj.XGDType == 3) && obj.SecuritySectors is null) + return null; + if ((obj.XGDType != 1 && obj.XGDType != 2 && obj.XGDType != 3) && obj.SecuritySectors is not null) + return null; + if ((obj.XGDType == 2 || obj.XGDType == 3) && obj.Xbox360Certificate is null) + return null; + if ((obj.XGDType != 2 && obj.XGDType != 3) && obj.Xbox360Certificate is not null) + return null; + + // If any static-length fields aren't the correct length + if (obj.Ringcode.Length != 8) + return null; + if (obj.RedumpCRC.Length != 4) + return null; + if (obj.RedumpMD5.Length != 16) + return null; + if (obj.RedumpSHA1.Length != 20) + return null; + if (obj.RawXISOCRC.Length != 4) + return null; + if (obj.RawXISOMD5.Length != 16) + return null; + if (obj.RawXISOSHA1.Length != 20) + return null; + if (obj.CookedXISOCRC.Length != 4) + return null; + if (obj.CookedXISOMD5.Length != 16) + return null; + if (obj.CookedXISOSHA1.Length != 20) + return null; + if (obj.VideoISOCRC.Length != 4) + return null; + if (obj.VideoISOMD5.Length != 16) + return null; + if (obj.VideoISOSHA1.Length != 20) + return null; + if (obj.WipedVideoISOCRC is not null && obj.WipedVideoISOCRC.Length != 4) + return null; + if (obj.WipedVideoISOMD5 is not null && obj.WipedVideoISOMD5.Length != 16) + return null; + if (obj.WipedVideoISOSHA1 is not null && obj.WipedVideoISOSHA1.Length != 20) + return null; + if (obj.FillerCRC.Length != 4) + return null; + if (obj.FillerMD5.Length != 16) + return null; + if (obj.FillerSHA1.Length != 20) + return null; + if (obj.XGDType == 1 && obj.SecuritySectors?.Length != 16) + return null; + if (obj.XGDType == 2 && obj.SecuritySectors?.Length != 2) + return null; + if (obj.XGDType == 3 && obj.SecuritySectors?.Length != 2) + return null; + if (obj.XboxCertificate is not null) + { + if (obj.XboxCertificate.SizeOfCertificate + 16 != 492) + return null; + if (obj.XboxCertificate.TitleName.Length != 0x50) + return null; + if (obj.XboxCertificate.AlternativeTitleIDs.Length != 16) + return null; + if (obj.XboxCertificate.LANKey.Length != 16) + return null; + if (obj.XboxCertificate.SignatureKey.Length != 16) + return null; + if (obj.XboxCertificate.AlternateSignatureKeys.Length != 16) + return null; + for (int i = 0; i < obj.XboxCertificate.AlternateSignatureKeys.Length; i++) + { + if (obj.XboxCertificate.AlternateSignatureKeys[i].Length != 16) + return null; + } + if (obj.XboxCertificate.CodeEncKey.Length != 16) + return null; + } + if (obj.Xbox360Certificate is not null) + { + var certificateLength = 388 + 24 * obj.Xbox360Certificate.Table.Length; + if (obj.Xbox360Certificate.Length != certificateLength) + return null; + if (obj.Xbox360Certificate.Signature.Length != 256) + return null; + if (obj.Xbox360Certificate.UnknownHash1.Length != 20) + return null; + if (obj.Xbox360Certificate.UnknownHash2.Length != 20) + return null; + if (obj.Xbox360Certificate.MediaID.Length != 16) + return null; + if (obj.Xbox360Certificate.XEXFileKey.Length != 16) + return null; + if (obj.Xbox360Certificate.UnknownHash3.Length != 20) + return null; + for (int i = 0; i < obj.Xbox360Certificate.Table.Length; i++) + { + if (obj.Xbox360Certificate.Table[i].Data.Length != 20) + return null; + } + } + if (obj.FileCount != obj.FileInfo.Length) + return null; + for (int i = 0; i < obj.FileInfo.Length; i++) + { + if (obj.FileInfo[i].SHA1.Length != 20) + return null; + } + if (obj.DirectoryCount != obj.DirectoryInfo.Length) + return null; + if (obj.VideoISOFileCount is not null && obj.VideoISOFileInfo is not null && obj.VideoISOFileCount != obj.VideoISOFileInfo.Length) + return null; + if (obj.XRDSHA1.Length != 20) + return null; + + // Create the output stream + var stream = new MemoryStream(); + + stream.Write(obj.Magic, 0, obj.Magic.Length); + stream.WriteByte(obj.Version); + stream.WriteByte(obj.XGDType); + stream.WriteByte(obj.XGDSubtype); + stream.Write(obj.Ringcode, 0, obj.Ringcode.Length); + + byte[] redumpSize = BitConverter.GetBytes(obj.RedumpSize); + stream.Write(redumpSize, 0, redumpSize.Length); + stream.Write(obj.RedumpCRC, 0, obj.RedumpCRC.Length); + stream.Write(obj.RedumpMD5, 0, obj.RedumpMD5.Length); + stream.Write(obj.RedumpSHA1, 0, obj.RedumpSHA1.Length); + + byte[] rawXISOSize = BitConverter.GetBytes(obj.RawXISOSize); + stream.Write(rawXISOSize, 0, rawXISOSize.Length); + stream.Write(obj.RawXISOCRC, 0, obj.RawXISOCRC.Length); + stream.Write(obj.RawXISOMD5, 0, obj.RawXISOMD5.Length); + stream.Write(obj.RawXISOSHA1, 0, obj.RawXISOSHA1.Length); + + byte[] cookedXISOSize = BitConverter.GetBytes(obj.CookedXISOSize); + stream.Write(cookedXISOSize, 0, cookedXISOSize.Length); + stream.Write(obj.CookedXISOCRC, 0, obj.CookedXISOCRC.Length); + stream.Write(obj.CookedXISOMD5, 0, obj.CookedXISOMD5.Length); + stream.Write(obj.CookedXISOSHA1, 0, obj.CookedXISOSHA1.Length); + + byte[] videoISOSize = BitConverter.GetBytes(obj.VideoISOSize); + stream.Write(videoISOSize, 0, videoISOSize.Length); + stream.Write(obj.VideoISOCRC, 0, obj.VideoISOCRC.Length); + stream.Write(obj.VideoISOMD5, 0, obj.VideoISOMD5.Length); + stream.Write(obj.VideoISOSHA1, 0, obj.VideoISOSHA1.Length); + + if (obj.WipedVideoISOSize is not null) + { + byte[] wipedVideoISOSize = BitConverter.GetBytes(obj.WipedVideoISOSize ?? 0); + stream.Write(videoISOSize, 0, videoISOSize.Length); + } + if (obj.WipedVideoISOCRC is not null) + stream.Write(obj.WipedVideoISOCRC, 0, obj.WipedVideoISOCRC.Length); + if (obj.WipedVideoISOMD5 is not null) + stream.Write(obj.WipedVideoISOMD5, 0, obj.WipedVideoISOMD5.Length); + if (obj.WipedVideoISOSHA1 is not null) + stream.Write(obj.WipedVideoISOSHA1, 0, obj.WipedVideoISOSHA1.Length); + + byte[] fillerSize = BitConverter.GetBytes(obj.FillerSize); + stream.Write(fillerSize, 0, fillerSize.Length); + stream.Write(obj.FillerCRC, 0, obj.FillerCRC.Length); + stream.Write(obj.FillerMD5, 0, obj.FillerMD5.Length); + stream.Write(obj.FillerSHA1, 0, obj.FillerSHA1.Length); + + if (obj.SecuritySectors is not null) + { + for (int i = 0; i < obj.SecuritySectors.Length; i++) + { + byte[] securitySector = BitConverter.GetBytes(obj.SecuritySectors[i]); + stream.Write(securitySector, 0, securitySector.Length); + } + } + + if (obj.XboxCertificate is not null) + SerializeXboxCertificate(stream, obj.XboxCertificate); + if (obj.Xbox360Certificate is not null) + SerializeXbox360Certificate(stream, obj.Xbox360Certificate); + + byte[] fileCount = BitConverter.GetBytes(obj.FileCount); + stream.Write(fileCount, 0, fileCount.Length); + for (int i = 0; i < obj.FileInfo.Length; i++) + { + byte[] offset = BitConverter.GetBytes(obj.FileInfo[i].Offset); + stream.Write(offset, 0, offset.Length); + byte[] size = BitConverter.GetBytes(obj.FileInfo[i].Size); + stream.Write(size, 0, size.Length); + stream.Write(obj.FileInfo[i].SHA1, 0, obj.FileInfo[i].SHA1.Length); + } + + XDVDFS.SerializeVolumeDescriptor(stream, obj.VolumeDescriptor); + if (obj.LayoutDescriptor is not null) + XDVDFS.SerializeLayoutDescriptor(stream, obj.LayoutDescriptor); + + byte[] directoryCount = BitConverter.GetBytes(obj.DirectoryCount); + stream.Write(directoryCount, 0, directoryCount.Length); + for (int i = 0; i < obj.DirectoryInfo.Length; i++) + { + byte[] offset = BitConverter.GetBytes(obj.DirectoryInfo[i].Offset); + stream.Write(offset, 0, offset.Length); + byte[] size = BitConverter.GetBytes(obj.DirectoryInfo[i].Size); + stream.Write(size, 0, size.Length); + XDVDFS.SerializeDirectoryDescriptor(stream, obj.DirectoryInfo[i].DirectoryDescriptor); + } + + return stream; + } + + public static void SerializeXboxCertificate(Stream stream, Data.Models.XboxExecutable.Certificate obj) + { + byte[] sizeOfCertificate = BitConverter.GetBytes(obj.SizeOfCertificate); + stream.Write(sizeOfCertificate, 0, sizeOfCertificate.Length); + byte[] timeDate = BitConverter.GetBytes(obj.TimeDate); + stream.Write(timeDate, 0, timeDate.Length); + byte[] titleID = BitConverter.GetBytes(obj.TitleID); + stream.Write(titleID, 0, titleID.Length); + stream.Write(obj.TitleName, 0, obj.TitleName.Length); + for (int i = 0; i < obj.AlternativeTitleIDs.Length; i++) + { + byte[] alternativeTitleID = BitConverter.GetBytes(obj.AlternativeTitleIDs[i]); + stream.Write(alternativeTitleID, 0, alternativeTitleID.Length); + } + byte[] allowedMediaTypes = BitConverter.GetBytes((uint)obj.AllowedMediaTypes); + stream.Write(allowedMediaTypes, 0, allowedMediaTypes.Length); + byte[] gameRegion = BitConverter.GetBytes((uint)obj.GameRegion); + stream.Write(gameRegion, 0, gameRegion.Length); + byte[] gameRatings = BitConverter.GetBytes(obj.GameRatings); + stream.Write(gameRatings, 0, gameRatings.Length); + byte[] diskNumber = BitConverter.GetBytes(obj.DiskNumber); + stream.Write(diskNumber, 0, diskNumber.Length); + byte[] version = BitConverter.GetBytes(obj.Version); + stream.Write(version, 0, version.Length); + stream.Write(obj.LANKey, 0, obj.LANKey.Length); + stream.Write(obj.SignatureKey, 0, obj.SignatureKey.Length); + for (int i = 0; i < obj.AlternateSignatureKeys.Length; i++) + { + stream.Write(obj.AlternateSignatureKeys[i], 0, obj.AlternateSignatureKeys[i].Length); + } + byte[] originalCertificateSize = BitConverter.GetBytes(obj.OriginalCertificateSize); + stream.Write(originalCertificateSize, 0, originalCertificateSize.Length); + byte[] onlineService = BitConverter.GetBytes(obj.OnlineService); + stream.Write(onlineService, 0, onlineService.Length); + byte[] securityFlags = BitConverter.GetBytes(obj.SecurityFlags); + stream.Write(securityFlags, 0, securityFlags.Length); + stream.Write(obj.CodeEncKey, 0, obj.CodeEncKey.Length); + } + + public static void SerializeXbox360Certificate(Stream stream, Data.Models.XenonExecutable.Certificate obj) + { + byte[] length = BitConverter.GetBytes(obj.Length); + stream.Write(length, 0, length.Length); + byte[] imageSize = BitConverter.GetBytes(obj.ImageSize); + stream.Write(imageSize, 0, imageSize.Length); + stream.Write(obj.Signature, 0, obj.Signature.Length); + byte[] baseFileLoadAddress = BitConverter.GetBytes(obj.BaseFileLoadAddress); + stream.Write(baseFileLoadAddress, 0, baseFileLoadAddress.Length); + byte[] imageFlags = BitConverter.GetBytes(obj.ImageFlags); + stream.Write(imageFlags, 0, imageFlags.Length); + byte[] imageBaseAddress = BitConverter.GetBytes(obj.ImageBaseAddress); + stream.Write(imageBaseAddress, 0, imageBaseAddress.Length); + stream.Write(obj.UnknownHash1, 0, obj.UnknownHash1.Length); + byte[] unknown0128 = BitConverter.GetBytes(obj.Unknown0128); + stream.Write(unknown0128, 0, unknown0128.Length); + stream.Write(obj.UnknownHash2, 0, obj.UnknownHash2.Length); + stream.Write(obj.MediaID, 0, obj.MediaID.Length); + stream.Write(obj.XEXFileKey, 0, obj.XEXFileKey.Length); + byte[] unknown0160 = BitConverter.GetBytes(obj.Unknown0160); + stream.Write(unknown0160, 0, unknown0160.Length); + stream.Write(obj.UnknownHash3, 0, obj.UnknownHash3.Length); + byte[] regionFlags = BitConverter.GetBytes(obj.RegionFlags); + stream.Write(regionFlags, 0, regionFlags.Length); + byte[] allowedMediaTypeFlags = BitConverter.GetBytes(obj.AllowedMediaTypeFlags); + stream.Write(allowedMediaTypeFlags, 0, allowedMediaTypeFlags.Length); + byte[] tableCount = BitConverter.GetBytes(obj.TableCount); + stream.Write(tableCount, 0, tableCount.Length); + + for (int i = 0; i < obj.Table.Length; i++) + { + byte[] id = BitConverter.GetBytes(obj.Table[i].ID); + stream.Write(id, 0, id.Length); + stream.Write(obj.Table[i].Data, 0, obj.Table[i].Data.Length); + } + } + } +} diff --git a/SabreTools.Wrappers.Test/XRDTests.cs b/SabreTools.Wrappers.Test/XRDTests.cs new file mode 100644 index 000000000..bc19b3dbb --- /dev/null +++ b/SabreTools.Wrappers.Test/XRDTests.cs @@ -0,0 +1,60 @@ +using System.IO; +using System.Linq; +using Xunit; + +namespace SabreTools.Wrappers.Test +{ + public class XRDTests + { + [Fact] + public void NullArray_Null() + { + byte[]? data = null; + int offset = 0; + var actual = XRD.Create(data, offset); + Assert.Null(actual); + } + + [Fact] + public void EmptyArray_Null() + { + byte[]? data = []; + int offset = 0; + var actual = XRD.Create(data, offset); + Assert.Null(actual); + } + + [Fact] + public void InvalidArray_Null() + { + byte[]? data = [.. Enumerable.Repeat(0xFF, 1024)]; + int offset = 0; + var actual = XRD.Create(data, offset); + Assert.Null(actual); + } + + [Fact] + public void NullStream_Null() + { + Stream? data = null; + var actual = XRD.Create(data); + Assert.Null(actual); + } + + [Fact] + public void EmptyStream_Null() + { + Stream? data = new MemoryStream([]); + var actual = XRD.Create(data); + Assert.Null(actual); + } + + [Fact] + public void InvalidStream_Null() + { + Stream? data = new MemoryStream([.. Enumerable.Repeat(0xFF, 1024)]); + var actual = XRD.Create(data); + Assert.Null(actual); + } + } +} diff --git a/SabreTools.Wrappers/WrapperFactory.cs b/SabreTools.Wrappers/WrapperFactory.cs index 2ffa55300..febda9e86 100644 --- a/SabreTools.Wrappers/WrapperFactory.cs +++ b/SabreTools.Wrappers/WrapperFactory.cs @@ -71,6 +71,7 @@ public static class WrapperFactory WrapperType.XboxExecutable => XboxExecutable.Create(data), WrapperType.XDVDFS => XDVDFS.Create(data), WrapperType.XenonExecutable => XenonExecutable.Create(data), + WrapperType.XRD => XRD.Create(data), WrapperType.XZ => XZ.Create(data), WrapperType.XZP => XZP.Create(data), WrapperType.ZArchive => ZArchive.Create(data), @@ -997,6 +998,16 @@ public static WrapperType GetFileType(byte[]? magic, string? extension) #endregion + #region XRD + + if (magic.StartsWith(Data.Models.XRD.Constants.MagicBytes)) + return WrapperType.XRD; + + if (extension.Equals("xrd", StringComparison.OrdinalIgnoreCase)) + return WrapperType.XRD; + + #endregion + #region XZ if (magic.StartsWith(Data.Models.XZ.Constants.HeaderSignatureBytes)) diff --git a/SabreTools.Wrappers/WrapperType.cs b/SabreTools.Wrappers/WrapperType.cs index cf44f759b..71c105401 100644 --- a/SabreTools.Wrappers/WrapperType.cs +++ b/SabreTools.Wrappers/WrapperType.cs @@ -303,6 +303,11 @@ public enum WrapperType /// XenonExecutable, + /// + /// Xbox Rebuild/Recovery/Redump Data + /// + XRD, + /// /// xz archive /// diff --git a/SabreTools.Wrappers/XDVDFS.Printing.cs b/SabreTools.Wrappers/XDVDFS.Printing.cs index fa62a20e5..334d060a2 100644 --- a/SabreTools.Wrappers/XDVDFS.Printing.cs +++ b/SabreTools.Wrappers/XDVDFS.Printing.cs @@ -31,7 +31,7 @@ public void PrintInformation(StringBuilder builder) } } - protected static void Print(StringBuilder builder, byte[] reservedArea) + private static void Print(StringBuilder builder, byte[] reservedArea) { if (reservedArea.Length == 0) builder.AppendLine(reservedArea, " Reserved Area"); @@ -42,7 +42,7 @@ protected static void Print(StringBuilder builder, byte[] reservedArea) builder.AppendLine(); } - private static void Print(StringBuilder builder, VolumeDescriptor vd) + internal static void Print(StringBuilder builder, VolumeDescriptor vd) { builder.AppendLine(" Volume Descriptor:"); builder.AppendLine(" -------------------------"); @@ -62,7 +62,7 @@ private static void Print(StringBuilder builder, VolumeDescriptor vd) builder.AppendLine(); } - private static void Print(StringBuilder builder, LayoutDescriptor ld) + internal static void Print(StringBuilder builder, LayoutDescriptor ld) { builder.AppendLine(" Xbox DVD Layout Descriptor:"); builder.AppendLine(" -------------------------"); @@ -88,7 +88,7 @@ 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) + internal static void Print(StringBuilder builder, DirectoryDescriptor dd, uint sectorNumber) { builder.AppendLine($" Directory Descriptor (Sector {sectorNumber}):"); builder.AppendLine(" -------------------------"); diff --git a/SabreTools.Wrappers/XRD.Printing.cs b/SabreTools.Wrappers/XRD.Printing.cs new file mode 100644 index 000000000..b0fb3f297 --- /dev/null +++ b/SabreTools.Wrappers/XRD.Printing.cs @@ -0,0 +1,114 @@ +using System.Text; +using SabreTools.Text.Extensions; + +namespace SabreTools.Wrappers +{ + public partial class XRD : IPrintable + { +#if NETCOREAPP + /// + public string ExportJSON() => System.Text.Json.JsonSerializer.Serialize(Model, _jsonSerializerOptions); +#endif + + /// + public void PrintInformation(StringBuilder builder) + { + builder.AppendLine("XRD Information:"); + builder.AppendLine("-------------------------"); + builder.AppendLine(Model.Magic, "Magic"); + builder.AppendLine(Model.Version, "Version"); + builder.AppendLine(Model.XGDType, "XGD Type"); + builder.AppendLine(Model.XGDSubtype, "XGD Subtype"); + builder.AppendLine(Model.Ringcode, "Ringcode", Encoding.ASCII); + builder.AppendLine(Model.RedumpSize, "Redump Size"); + builder.AppendLine(Model.RedumpCRC, "Redump CRC-32"); + builder.AppendLine(Model.RedumpMD5, "Redump MD5"); + builder.AppendLine(Model.RedumpSHA1, "Redump SHA-1"); + builder.AppendLine(Model.RawXISOSize, "Raw XISO Size"); + builder.AppendLine(Model.RawXISOCRC, "Raw XISO CRC-32"); + builder.AppendLine(Model.RawXISOMD5, "Raw XISO MD5"); + builder.AppendLine(Model.RawXISOSHA1, "Raw XISO SHA-1"); + builder.AppendLine(Model.CookedXISOSize, "Cooked XISO Size"); + builder.AppendLine(Model.CookedXISOCRC, "Cooked XISO CRC-32"); + builder.AppendLine(Model.CookedXISOMD5, "Cooked XISO MD5"); + builder.AppendLine(Model.CookedXISOSHA1, "Cooked XISO SHA-1"); + builder.AppendLine(Model.VideoISOSize, "Video ISO Size"); + builder.AppendLine(Model.VideoISOCRC, "Video ISO CRC-32"); + builder.AppendLine(Model.VideoISOMD5, "Video ISO MD5"); + builder.AppendLine(Model.VideoISOSHA1, "Video ISO SHA-1"); + + if (Model.WipedVideoISOSize is not null) + builder.AppendLine(Model.WipedVideoISOSize, "Wiped Video ISO Size"); + + if (Model.WipedVideoISOCRC is not null) + builder.AppendLine(Model.WipedVideoISOCRC, "Wiped Video ISO CRC-32"); + + if (Model.WipedVideoISOMD5 is not null) + builder.AppendLine(Model.WipedVideoISOMD5, "Wiped Video ISO MD5"); + + if (Model.WipedVideoISOSHA1 is not null) + builder.AppendLine(Model.WipedVideoISOSHA1, "Wiped Video ISO SHA-1"); + + builder.AppendLine(Model.FillerSize, "First Sector Filler Size"); + builder.AppendLine(Model.FillerCRC, "First Sector Filler CRC-32"); + builder.AppendLine(Model.FillerMD5, "First Sector Filler MD5"); + builder.AppendLine(Model.FillerSHA1, "First Sector Filler SHA-1"); + + if (Model.SecuritySectors is not null) + { + for (int i = 0; i < Model.SecuritySectors.Length; i++) + { + builder.AppendLine(Model.SecuritySectors[i], $"Security Sector {i}"); + } + } + + if (Model.XboxCertificate is not null) + XboxExecutable.Print(builder, Model.XboxCertificate); + + if (Model.Xbox360Certificate is not null) + XenonExecutable.Print(builder, Model.Xbox360Certificate); + + builder.AppendLine(Model.FileCount, "XISO File Count"); + + for (int i = 0; i < Model.FileInfo.Length; i++) + { + builder.AppendLine(Model.FileInfo[i].Offset, $"XISO File {i} Offset"); + builder.AppendLine(Model.FileInfo[i].Size, $"XISO File {i} Size"); + builder.AppendLine(Model.FileInfo[i].SHA1, $"XISO File {i} SHA-1"); + } + + XDVDFS.Print(builder, Model.VolumeDescriptor); + + if (Model.LayoutDescriptor is not null) + XDVDFS.Print(builder, Model.LayoutDescriptor); + + builder.AppendLine(Model.DirectoryCount, "XISO Directory Count"); + + for (int i = 0; i < Model.DirectoryInfo.Length; i++) + { + builder.AppendLine(Model.DirectoryInfo[i].Offset, $"XISO Directory {i} Offset"); + builder.AppendLine(Model.DirectoryInfo[i].Size, $"XISO Directory {i} Size"); + XDVDFS.Print(builder, Model.DirectoryInfo[i].DirectoryDescriptor, Model.DirectoryInfo[i].Offset); + } + + if (Model.VideoISOFileCount is not null) + { + builder.AppendLine(Model.VideoISOFileCount, "Video ISO File Count"); + } + + if (Model.VideoISOFileInfo is not null) + { + for (int i = 0; i < Model.VideoISOFileInfo.Length; i++) + { + builder.AppendLine(Model.VideoISOFileInfo[i].Offset, $"Video ISO File {i} Offset"); + builder.AppendLine(Model.VideoISOFileInfo[i].Size, $"Video ISO File {i} Size"); + builder.AppendLine(Model.VideoISOFileInfo[i].SHA1, $"Video ISO File {i} SHA-1"); + } + } + + builder.AppendLine(Model.XRDSize, "XRD Size"); + builder.AppendLine(Model.XRDSHA1, "XRD SHA-1"); + builder.AppendLine(); + } + } +} diff --git a/SabreTools.Wrappers/XRD.cs b/SabreTools.Wrappers/XRD.cs new file mode 100644 index 000000000..3a4447481 --- /dev/null +++ b/SabreTools.Wrappers/XRD.cs @@ -0,0 +1,89 @@ +using System.IO; + +namespace SabreTools.Wrappers +{ + public partial class XRD : WrapperBase + { + #region Descriptive Properties + + /// + public override string DescriptionString => "Xbox XRD file"; + + #endregion + + #region Constructors + + /// + public XRD(Data.Models.XRD.File model, byte[] data) : base(model, data) { } + + /// + public XRD(Data.Models.XRD.File model, byte[] data, int offset) : base(model, data, offset) { } + + /// + public XRD(Data.Models.XRD.File model, byte[] data, int offset, int length) : base(model, data, offset, length) { } + + /// + public XRD(Data.Models.XRD.File model, Stream data) : base(model, data) { } + + /// + public XRD(Data.Models.XRD.File model, Stream data, long offset) : base(model, data, offset) { } + + /// + public XRD(Data.Models.XRD.File model, Stream data, long offset, long length) : base(model, data, offset, length) { } + + #endregion + + #region Static Constructors + + /// + /// Create an XRD from a byte array and offset + /// + /// Byte array representing the archive + /// Offset within the array to parse + /// An XRD wrapper on success, null on failure + public static XRD? 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 XRD from a Stream + /// + /// Stream representing the archive + /// An XRD wrapper on success, null on failure + public static XRD? 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.XRD().Deserialize(data); + if (model is null) + return null; + + return new XRD(model, data, currentOffset); + } + catch + { + return null; + } + } + + #endregion + } +} diff --git a/SabreTools.Wrappers/XboxExecutable.Printing.cs b/SabreTools.Wrappers/XboxExecutable.Printing.cs index 38757212e..1b5b207a5 100644 --- a/SabreTools.Wrappers/XboxExecutable.Printing.cs +++ b/SabreTools.Wrappers/XboxExecutable.Printing.cs @@ -29,7 +29,7 @@ public void PrintInformation(StringBuilder builder) Print(builder, Model.XAPILibraryVersion, "XAPI ", string.Empty, 2); } - private static void Print(StringBuilder builder, Certificate? certificate) + internal static void Print(StringBuilder builder, Certificate? certificate) { builder.AppendLine(" Certificate Information:"); builder.AppendLine(" -------------------------"); diff --git a/SabreTools.Wrappers/XenonExecutable.Printing.cs b/SabreTools.Wrappers/XenonExecutable.Printing.cs index 583c3ce33..9546d7d0f 100644 --- a/SabreTools.Wrappers/XenonExecutable.Printing.cs +++ b/SabreTools.Wrappers/XenonExecutable.Printing.cs @@ -79,7 +79,7 @@ private static void Print(StringBuilder builder, OptionalHeader[]? optionalHeade builder.AppendLine(); } - private static void Print(StringBuilder builder, Certificate? certificate) + internal static void Print(StringBuilder builder, Certificate? certificate) { builder.AppendLine(" Certificate Information:"); builder.AppendLine(" -------------------------");