XRD File Support (#87)

This commit is contained in:
Deterous
2026-04-19 22:04:25 +09:00
committed by GitHub
parent bba9bfcbd6
commit e383771fb6
24 changed files with 1188 additions and 39 deletions

View File

@@ -1,6 +1,6 @@
namespace SabreTools.Data.Models.XDVDFS
{
/// <see href="https://github.dev/Deterous/XboxKit/"/>
/// <see href="https://github.com/Deterous/XboxKit/"/>
public static class Constants
{
/// <summary>

View File

@@ -6,7 +6,7 @@ namespace SabreTools.Data.Models.XDVDFS
/// Padded with 0xFF to be a multiple of 2048 bytes
/// </summary>
/// <see href="https://multimedia.cx/xdvdfs.html"/>
/// <see href="https://github.dev/Deterous/XboxKit/"/>
/// <see href="https://github.com/Deterous/XboxKit/"/>
public class DirectoryDescriptor
{
/// <summary>

View File

@@ -5,7 +5,7 @@ namespace SabreTools.Data.Models.XDVDFS
/// Padded with 0xFF to be a multiple of 4 bytes
/// </summary>
/// <see href="https://multimedia.cx/xdvdfs.html"/>
/// <see href="https://github.dev/Deterous/XboxKit/"/>
/// <see href="https://github.com/Deterous/XboxKit/"/>
public class DirectoryRecord
{
/// <summary>

View File

@@ -5,7 +5,7 @@ namespace SabreTools.Data.Models.XDVDFS
/// Only present on XGD1 and XGD2 discs
/// </summary>
/// <see href="https://xboxdevwiki.net/XDVDFS"/>
/// <see href="https://github.dev/Deterous/XboxKit/"/>
/// <see href="https://github.com/Deterous/XboxKit/"/>
public class LayoutDescriptor
{
/// <summary>

View File

@@ -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)
/// </summary>
/// <see href="https://xboxdevwiki.net/XDVDFS"/>
/// <see href="https://github.dev/Deterous/XboxKit/"/>
/// <see href="https://github.com/Deterous/XboxKit/"/>
public class Volume
{
/// <summary>

View File

@@ -5,7 +5,7 @@ namespace SabreTools.Data.Models.XDVDFS
/// Present on XGD1, XGD2, and XGD3 discs
/// </summary>
/// <see href="https://multimedia.cx/xdvdfs.html"/>
/// <see href="https://github.dev/Deterous/XboxKit/"/>
/// <see href="https://github.com/Deterous/XboxKit/"/>
public class VolumeDescriptor
{
/// <summary>

View File

@@ -0,0 +1,10 @@
namespace SabreTools.Data.Models.XRD
{
public static class Constants
{
/// <summary>
/// Magic string at start of XRD file
/// </summary>
public static readonly byte[] MagicBytes = [0x58, 0x52, 0x44, 0xFF, 0x00];
}
}

View File

@@ -0,0 +1,20 @@
namespace SabreTools.Data.Models.XRD
{
public class DirectoryEntry
{
/// <summary>
/// Sector offset of descriptor in XDVDFS filesystem
/// </summary>
public uint Offset { get; set; }
/// <summary>
/// Size of descriptor in sectors
/// </summary>
public uint Size { get; set; }
/// <summary>
/// SHA-1 Hash of file content
/// </summary>
public XDVDFS.DirectoryDescriptor DirectoryDescriptor { get; set; } = new();
}
}

View File

@@ -0,0 +1,270 @@
namespace SabreTools.Data.Models.XRD
{
/// <summary>
/// Xbox Rebuild/Recovery/Redump Data
/// Custom file format containing descriptive metadata about Xbox / Xbox 360 disc images
/// </summary>
public class File
{
/// <summary>
/// "XRD\xFF\x00"
/// </summary>
public byte[] Magic { get; set; } = new byte[5];
/// <summary>
/// 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
/// </summary>
public byte Version { get; set; }
/// <summary>
/// XGD Type: XGD1, XGD2, XGD3, 0xFF = Unknown
/// </summary>
public byte XGDType { get; set; }
/// <summary>
/// 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
/// </summary>
public byte XGDSubtype { get; set; }
/// <summary>
/// 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
/// </summary>
public byte[] Ringcode { get; set; } = new byte[8];
/// <summary>
/// Size of the redump ISO in bytes
/// </summary>
/// <remarks>Little-endian</remarks>
public ulong RedumpSize { get; set; }
/// <summary>
/// CRC-32 hash of the redump ISO
/// </summary>
public byte[] RedumpCRC { get; set; } = new byte[4];
/// <summary>
/// MD5 hash of the redump ISO
/// </summary>
public byte[] RedumpMD5 { get; set; } = new byte[16];
/// <summary>
/// SHA-1 hash of the redump ISO
/// </summary>
public byte[] RedumpSHA1 { get; set; } = new byte[20];
/// <summary>
/// Size of the Raw XISO in bytes
/// </summary>
/// <remarks>Little-endian</remarks>
public ulong RawXISOSize { get; set; }
/// <summary>
/// CRC-32 hash of the Raw XISO
/// </summary>
public byte[] RawXISOCRC { get; set; } = new byte[4];
/// <summary>
/// MD5 hash of the Raw XISO
/// </summary>
public byte[] RawXISOMD5 { get; set; } = new byte[16];
/// <summary>
/// SHA-1 hash of the Raw XISO
/// </summary>
public byte[] RawXISOSHA1 { get; set; } = new byte[20];
/// <summary>
/// Size of the Wiped/Trimmed XISO in bytes
/// </summary>
/// <remarks>Little-endian</remarks>
public ulong CookedXISOSize { get; set; }
/// <summary>
/// CRC-32 hash of the Wiped/Trimmed XISO
/// </summary>
public byte[] CookedXISOCRC { get; set; } = new byte[4];
/// <summary>
/// MD5 hash of the Wiped/Trimmed XISO
/// </summary>
public byte[] CookedXISOMD5 { get; set; } = new byte[16];
/// <summary>
/// SHA-1 hash of the Wiped/Trimmed XISO
/// </summary>
public byte[] CookedXISOSHA1 { get; set; } = new byte[20];
/// <summary>
/// Size of the Video ISO in bytes
/// </summary>
/// <remarks>Little-endian</remarks>
public ulong VideoISOSize { get; set; }
/// <summary>
/// CRC-32 hash of the Video ISO
/// </summary>
public byte[] VideoISOCRC { get; set; } = new byte[4];
/// <summary>
/// MD5 hash of the Video ISO
/// </summary>
public byte[] VideoISOMD5 { get; set; } = new byte[16];
/// <summary>
/// SHA-1 hash of the Video ISO
/// </summary>
public byte[] VideoISOSHA1 { get; set; } = new byte[20];
/// <summary>
/// Size of the wiped Video ISO in bytes
/// Field does not exist for Version = 1
/// </summary>
/// <remarks>Little-endian</remarks>
public ulong? WipedVideoISOSize { get; set; }
/// <summary>
/// CRC-32 hash of the wiped Video ISO
/// Field does not exist for Version = 1
/// </summary>
public byte[]? WipedVideoISOCRC { get; set; } = new byte[4];
/// <summary>
/// MD5 hash of the wiped Video ISO
/// Field does not exist for Version = 1
/// </summary>
public byte[]? WipedVideoISOMD5 { get; set; } = new byte[16];
/// <summary>
/// SHA-1 hash of the wiped Video ISO
/// Field does not exist for Version = 1
/// </summary>
public byte[]? WipedVideoISOSHA1 { get; set; } = new byte[20];
/// <summary>
/// Size of the filler data that has been hashed, in bytes
/// Should always be 2048
/// </summary>
/// <remarks>Little-endian</remarks>
public ulong FillerSize { get; set; }
/// <summary>
/// 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
/// </summary>
public byte[] FillerCRC { get; set; } = new byte[4];
/// <summary>
/// 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
/// </summary>
public byte[] FillerMD5 { get; set; } = new byte[16];
/// <summary>
/// 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
/// </summary>
public byte[] FillerSHA1 { get; set; } = new byte[20];
/// <summary>
/// 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; } = [];
/// <summary>
/// XBE Certificate, starts with length of structure
/// </summary>
/// <remarks>XGD1 only, Little-endian</remarks>
public XboxExecutable.Certificate? XboxCertificate { get; set; }
/// <summary>
/// XEX Certificate, starts with length of structure
/// </summary>
/// <remarks>XGD2/3 only, Big-endian</remarks>
public XenonExecutable.Certificate? Xbox360Certificate { get; set; }
/// <summary>
/// Number of files in the XISO
/// </summary>
/// <remarks>Little-endian</remarks>
public int FileCount { get; set; }
/// <summary>
/// File offsets and hashes in the XISO
/// Length of array equal to FileCount
/// </summary>
public FileEntry[] FileInfo { get; set; } = [];
/// <summary>
/// XISO Volume Descriptor
/// </summary>
/// <remarks>2048 bytes</remarks>
public XDVDFS.VolumeDescriptor VolumeDescriptor { get; set; } = new();
/// <summary>
/// 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
/// </summary>
/// <remarks>2048 bytes</remarks>
public XDVDFS.LayoutDescriptor? LayoutDescriptor { get; set; }
/// <summary>
/// Number of directory records in the XISO
/// </summary>
/// <remarks>Little-endian</remarks>
public int DirectoryCount { get; set; }
/// <summary>
/// List of XISO descriptors and their sector offsets and sizes
/// The root directory descriptor is not guaranteed to be the first
/// </summary>
public DirectoryEntry[] DirectoryInfo { get; set; } = [];
/// <summary>
/// Number of files in Video ISO
/// Field does not exist for Version = 1
/// </summary>
/// <remarks>Little-endian</remarks>
public int? VideoISOFileCount { get; set; }
/// <summary>
/// File offsets and hashes in the Video ISO
/// Length of array equal to VideoISOFileCount
/// Field does not exist for Version = 1
/// </summary>
public FileEntry[]? VideoISOFileInfo { get; set; }
/// <summary>
/// Size of all data in XRD file prior to this field
/// </summary>
/// <remarks>Little-endian</remarks>
public uint XRDSize { get; set; }
/// <summary>
/// SHA-1 Hash of the XRD file prior to XRDSize field
/// </summary>
public byte[] XRDSHA1 { get; set; } = new byte[20];
}
}

View File

@@ -0,0 +1,20 @@
namespace SabreTools.Data.Models.XRD
{
public class FileEntry
{
/// <summary>
/// Sector offset of file in XDVDFS filesystem
/// </summary>
public uint Offset { get; set; }
/// <summary>
/// Size of file in bytes
/// </summary>
public ulong Size { get; set; }
/// <summary>
/// SHA-1 Hash of file content
/// </summary>
public byte[] SHA1 { get; set; } = new byte[20];
}
}

View File

@@ -29,6 +29,6 @@ namespace SabreTools.Data.Models.XenonExecutable
/// <summary>
/// Table entry data, 20 bytes
/// </summary>
public byte[]? Data { get; set; } = new byte[20];
public byte[] Data { get; set; } = new byte[20];
}
}

View File

@@ -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<byte>(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<byte>(0xFF, 1024)]);
var deserializer = new XRD();
var actual = deserializer.Deserialize(data);
Assert.Null(actual);
}
}
}

View File

@@ -162,16 +162,17 @@ namespace SabreTools.Serialization.Readers
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)
{

View File

@@ -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<Data.Models.XRD.File>
{
/// <inheritdoc/>
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;
}
}
}
}

View File

@@ -23,7 +23,10 @@ namespace SabreTools.Serialization.Writers
// 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 @@ namespace SabreTools.Serialization.Writers
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.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);
}
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);
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)

View File

@@ -0,0 +1,322 @@
using System;
using System.IO;
using System.Text;
namespace SabreTools.Serialization.Writers
{
public class XRD : BaseBinaryWriter<Data.Models.XRD.File>
{
/// <inheritdoc/>
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);
}
}
}
}

View File

@@ -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<byte>(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<byte>(0xFF, 1024)]);
var actual = XRD.Create(data);
Assert.Null(actual);
}
}
}

View File

@@ -71,6 +71,7 @@ namespace SabreTools.Wrappers
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 @@ namespace SabreTools.Wrappers
#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))

View File

@@ -303,6 +303,11 @@ namespace SabreTools.Wrappers
/// </summary>
XenonExecutable,
/// <summary>
/// Xbox Rebuild/Recovery/Redump Data
/// </summary>
XRD,
/// <summary>
/// xz archive
/// </summary>

View File

@@ -31,7 +31,7 @@ namespace SabreTools.Wrappers
}
}
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 @@ namespace SabreTools.Wrappers
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 @@ namespace SabreTools.Wrappers
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 @@ namespace SabreTools.Wrappers
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(" -------------------------");

View File

@@ -0,0 +1,114 @@
using System.Text;
using SabreTools.Text.Extensions;
namespace SabreTools.Wrappers
{
public partial class XRD : IPrintable
{
#if NETCOREAPP
/// <inheritdoc/>
public string ExportJSON() => System.Text.Json.JsonSerializer.Serialize(Model, _jsonSerializerOptions);
#endif
/// <inheritdoc/>
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();
}
}
}

View File

@@ -0,0 +1,89 @@
using System.IO;
namespace SabreTools.Wrappers
{
public partial class XRD : WrapperBase<Data.Models.XRD.File>
{
#region Descriptive Properties
/// <inheritdoc/>
public override string DescriptionString => "Xbox XRD file";
#endregion
#region Constructors
/// <inheritdoc/>
public XRD(Data.Models.XRD.File model, byte[] data) : base(model, data) { }
/// <inheritdoc/>
public XRD(Data.Models.XRD.File model, byte[] data, int offset) : base(model, data, offset) { }
/// <inheritdoc/>
public XRD(Data.Models.XRD.File model, byte[] data, int offset, int length) : base(model, data, offset, length) { }
/// <inheritdoc/>
public XRD(Data.Models.XRD.File model, Stream data) : base(model, data) { }
/// <inheritdoc/>
public XRD(Data.Models.XRD.File model, Stream data, long offset) : base(model, data, offset) { }
/// <inheritdoc/>
public XRD(Data.Models.XRD.File model, Stream data, long offset, long length) : base(model, data, offset, length) { }
#endregion
#region Static Constructors
/// <summary>
/// Create an XRD from a byte array and offset
/// </summary>
/// <param name="data">Byte array representing the archive</param>
/// <param name="offset">Offset within the array to parse</param>
/// <returns>An XRD wrapper on success, null on failure</returns>
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);
}
/// <summary>
/// Create an XRD from a Stream
/// </summary>
/// <param name="data">Stream representing the archive</param>
/// <returns>An XRD wrapper on success, null on failure</returns>
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
}
}

View File

@@ -29,7 +29,7 @@ namespace SabreTools.Wrappers
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(" -------------------------");

View File

@@ -79,7 +79,7 @@ namespace SabreTools.Wrappers
builder.AppendLine();
}
private static void Print(StringBuilder builder, Certificate? certificate)
internal static void Print(StringBuilder builder, Certificate? certificate)
{
builder.AppendLine(" Certificate Information:");
builder.AppendLine(" -------------------------");