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(" -------------------------");