mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-09-21 14:25:03 +00:00
XRD File Support (#87)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
10
SabreTools.Data.Models/XRD/Constants.cs
Normal file
10
SabreTools.Data.Models/XRD/Constants.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
20
SabreTools.Data.Models/XRD/DirectoryEntry.cs
Normal file
20
SabreTools.Data.Models/XRD/DirectoryEntry.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
270
SabreTools.Data.Models/XRD/File.cs
Normal file
270
SabreTools.Data.Models/XRD/File.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
20
SabreTools.Data.Models/XRD/FileEntry.cs
Normal file
20
SabreTools.Data.Models/XRD/FileEntry.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
72
SabreTools.Serialization.Readers.Test/XRDTests.cs
Normal file
72
SabreTools.Serialization.Readers.Test/XRDTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
154
SabreTools.Serialization.Readers/XRD.cs
Normal file
154
SabreTools.Serialization.Readers/XRD.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
322
SabreTools.Serialization.Writers/XRD.cs
Normal file
322
SabreTools.Serialization.Writers/XRD.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
SabreTools.Wrappers.Test/XRDTests.cs
Normal file
60
SabreTools.Wrappers.Test/XRDTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -303,6 +303,11 @@ namespace SabreTools.Wrappers
|
||||
/// </summary>
|
||||
XenonExecutable,
|
||||
|
||||
/// <summary>
|
||||
/// Xbox Rebuild/Recovery/Redump Data
|
||||
/// </summary>
|
||||
XRD,
|
||||
|
||||
/// <summary>
|
||||
/// xz archive
|
||||
/// </summary>
|
||||
|
||||
@@ -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(" -------------------------");
|
||||
|
||||
114
SabreTools.Wrappers/XRD.Printing.cs
Normal file
114
SabreTools.Wrappers/XRD.Printing.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
89
SabreTools.Wrappers/XRD.cs
Normal file
89
SabreTools.Wrappers/XRD.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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(" -------------------------");
|
||||
|
||||
@@ -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(" -------------------------");
|
||||
|
||||
Reference in New Issue
Block a user