Fix DS/3DS sttuff, add 3DS wrapper/printing

This commit is contained in:
Matt Nadareski
2023-01-07 12:33:15 -08:00
parent 0316edb8cb
commit 761b418d21
9 changed files with 783 additions and 113 deletions

View File

@@ -56,7 +56,7 @@ namespace BurnOutSharp.Builders
// Create a new cart image to fill
var cart = new Cart();
#region Header
#region NCSD Header
// Try to parse the header
var header = ParseNCSDHeader(data);
@@ -68,6 +68,34 @@ namespace BurnOutSharp.Builders
#endregion
#region Card Info Header
// Try to parse the card info header
var cardInfoHeader = ParseCardInfoHeader(data);
if (cardInfoHeader == null)
return null;
// Set the card info header
cart.CardInfoHeader = cardInfoHeader;
#endregion
#region Development Card Info Header
// TODO: Hook up a builder flag to enable development parsing
if (false)
{
// Try to parse the development card info header
var developmentCardInfoHeader = ParseDevelopmentCardInfoHeader(data);
if (developmentCardInfoHeader == null)
return null;
// Set the development card info header
cart.DevelopmentCardInfoHeader = developmentCardInfoHeader;
}
#endregion
#region Partitions
// Create the partition table
@@ -88,16 +116,15 @@ namespace BurnOutSharp.Builders
/// Parse a Stream into an NCSD header
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="development">Indicates if the cart is development or not</param>
/// <returns>Filled NCSD header on success, null on error</returns>
private static NCSDHeader ParseNCSDHeader(Stream data, bool development = false)
private static NCSDHeader ParseNCSDHeader(Stream data)
{
// TODO: Use marshalling here instead of building
NCSDHeader header = new NCSDHeader();
header.RSA2048Signature = data.ReadBytes(0x100);
byte[] magicNumber = data.ReadBytes(4);
header.MagicNumber = Encoding.ASCII.GetString(magicNumber);
header.MagicNumber = Encoding.ASCII.GetString(magicNumber).TrimEnd('\0');;
if (header.MagicNumber != NCSDMagicNumber)
return null;
@@ -119,36 +146,16 @@ namespace BurnOutSharp.Builders
header.SectorZeroOffset = data.ReadUInt32();
header.PartitionFlags = data.ReadBytes(8);
header.PartitionIdTable = new byte[8][];
header.PartitionIdTable = new ulong[8];
for (int i = 0; i < 8; i++)
{
header.PartitionIdTable[i] = data.ReadBytes(8);
header.PartitionIdTable[i] = data.ReadUInt64();
}
header.Reserved1 = data.ReadBytes(0x20);
header.Reserved2 = data.ReadBytes(0xE);
header.Reserved2 = data.ReadBytes(0x0E);
header.FirmUpdateByte1 = data.ReadByteValue();
header.FirmUpdateByte2 = data.ReadByteValue();
header.CARD2WritableAddressMediaUnits = data.ReadBytes(4);
header.CardInfoBytemask = data.ReadBytes(4);
header.Reserved3 = data.ReadBytes(0x108);
header.TitleVersion = data.ReadUInt16();
header.CardRevision = data.ReadUInt16();
header.Reserved4 = data.ReadBytes(0xCEC); // Incorrectly documented as 0xCEE
header.CardSeedKeyY = data.ReadBytes(0x10);
header.EncryptedCardSeed = data.ReadBytes(0x10);
header.CardSeedAESMAC = data.ReadBytes(0x10);
header.CardSeedNonce = data.ReadBytes(0xC);
header.Reserved5 = data.ReadBytes(0xC4);
header.BackupHeader = ParseNCCHHeader(data, true);
if (development)
{
header.CardDeviceReserved1 = data.ReadBytes(0x200);
header.TitleKey = data.ReadBytes(0x10);
header.CardDeviceReserved2 = data.ReadBytes(0xF0);
}
}
else if (header.PartitionsFSType == FilesystemType.FIRM)
{
@@ -175,6 +182,78 @@ namespace BurnOutSharp.Builders
return partitionTableEntry;
}
/// <summary>
/// Parse a Stream into a card info header
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled card info header on success, null on error</returns>
private static CardInfoHeader ParseCardInfoHeader(Stream data)
{
// TODO: Use marshalling here instead of building
CardInfoHeader cardInfoHeader = new CardInfoHeader();
cardInfoHeader.WritableAddressMediaUnits = data.ReadUInt32();
cardInfoHeader.CardInfoBitmask = data.ReadUInt32();
cardInfoHeader.Reserved1 = data.ReadBytes(0xF8);
cardInfoHeader.FilledSize = data.ReadUInt32();
cardInfoHeader.Reserved2 = data.ReadBytes(0x0C);
cardInfoHeader.TitleVersion = data.ReadUInt16();
cardInfoHeader.CardRevision = data.ReadUInt16();
cardInfoHeader.Reserved3 = data.ReadBytes(0x0C);
cardInfoHeader.CVerTitleID = data.ReadBytes(8);
cardInfoHeader.CVerVersionNumber = data.ReadUInt16();
cardInfoHeader.Reserved4 = data.ReadBytes(0xCD6);
return cardInfoHeader;
}
/// <summary>
/// Parse a Stream into a development card info header
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled development card info header on success, null on error</returns>
private static DevelopmentCardInfoHeader ParseDevelopmentCardInfoHeader(Stream data)
{
// TODO: Use marshalling here instead of building
DevelopmentCardInfoHeader developmentCardInfoHeader = new DevelopmentCardInfoHeader();
developmentCardInfoHeader.InitialData = ParseInitialData(data);
if (developmentCardInfoHeader.InitialData == null)
return null;
developmentCardInfoHeader.CardDeviceReserved1 = data.ReadBytes(0x200);
developmentCardInfoHeader.TitleKey = data.ReadBytes(0x10);
developmentCardInfoHeader.CardDeviceReserved2 = data.ReadBytes(0x1BF0);
developmentCardInfoHeader.TestData = ParseTestData(data);
if (developmentCardInfoHeader.TestData == null)
return null;
return developmentCardInfoHeader;
}
/// <summary>
/// Parse a Stream into an initial data
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled initial data on success, null on error</returns>
private static InitialData ParseInitialData(Stream data)
{
// TODO: Use marshalling here instead of building
InitialData initialData = new InitialData();
initialData.CardSeedKeyY = data.ReadBytes(0x10);
initialData.EncryptedCardSeed = data.ReadBytes(0x10);
initialData.CardSeedAESMAC = data.ReadBytes(0x10);
initialData.CardSeedNonce = data.ReadBytes(0xC);
initialData.Reserved = data.ReadBytes(0xC4);
initialData.BackupHeader = ParseNCCHHeader(data, true);
if (initialData.BackupHeader == null)
return null;
return initialData;
}
/// <summary>
/// Parse a Stream into an NCCH header
/// </summary>
@@ -190,7 +269,7 @@ namespace BurnOutSharp.Builders
header.RSA2048Signature = data.ReadBytes(0x100);
byte[] magicId = data.ReadBytes(4);
header.MagicID = Encoding.ASCII.GetString(magicId);
header.MagicID = Encoding.ASCII.GetString(magicId).TrimEnd('\0');;
if (header.MagicID != NCCHMagicNumber)
return null;
@@ -247,6 +326,31 @@ namespace BurnOutSharp.Builders
return headerFlags;
}
/// <summary>
/// Parse a Stream into an initial data
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled initial data on success, null on error</returns>
private static TestData ParseTestData(Stream data)
{
// TODO: Use marshalling here instead of building
TestData testData = new TestData();
// TODO: Validate some of the values
testData.Signature = data.ReadBytes(8);
testData.AscendingByteSequence = data.ReadBytes(0x1F8);
testData.DescendingByteSequence = data.ReadBytes(0x200);
testData.Filled00 = data.ReadBytes(0x200);
testData.FilledFF = data.ReadBytes(0x200);
testData.Filled0F = data.ReadBytes(0x200);
testData.FilledF0 = data.ReadBytes(0x200);
testData.Filled55 = data.ReadBytes(0x200);
testData.FilledAA = data.ReadBytes(0x1FF);
testData.FinalByte = data.ReadByteValue();
return testData;
}
#endregion
}
}

View File

@@ -113,10 +113,10 @@ namespace BurnOutSharp.Builders
CommonHeader commonHeader = new CommonHeader();
byte[] gameTitle = data.ReadBytes(0x0C);
commonHeader.GameTitle = Encoding.ASCII.GetString(gameTitle);
commonHeader.GameTitle = Encoding.ASCII.GetString(gameTitle).TrimEnd('\0');
commonHeader.GameCode = data.ReadUInt32();
byte[] makerCode = data.ReadBytes(2);
commonHeader.MakerCode = Encoding.ASCII.GetString(bytes: makerCode);
commonHeader.MakerCode = Encoding.ASCII.GetString(bytes: makerCode).TrimEnd('\0');
commonHeader.UnitCode = (Unitcode)data.ReadByteValue();
commonHeader.EncryptionSeedSelect = data.ReadByteValue();
commonHeader.DeviceCapacity = data.ReadByteValue();

View File

@@ -0,0 +1,61 @@
namespace BurnOutSharp.Models.N3DS
{
/// <see href="https://www.3dbrew.org/wiki/NCSD#Card_Info_Header"/>
public sealed class CardInfoHeader
{
/// <summary>
/// CARD2: Writable Address In Media Units (For 'On-Chip' Savedata). CARD1: Always 0xFFFFFFFF.
/// </summary>
public uint WritableAddressMediaUnits;
/// <summary>
/// Card Info Bitmask
/// </summary>
public uint CardInfoBitmask;
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved1;
/// <summary>
/// Filled size of cartridge
/// </summary>
public uint FilledSize;
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved2;
/// <summary>
/// Title version
/// </summary>
public ushort TitleVersion;
/// <summary>
/// Card revision
/// </summary>
public ushort CardRevision;
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved3;
/// <summary>
/// Title ID of CVer in included update partition
/// </summary>
public byte[] CVerTitleID;
/// <summary>
/// Version number of CVer in included update partition
/// </summary>
public ushort CVerVersionNumber;
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved4;
}
}

View File

@@ -10,6 +10,16 @@ namespace BurnOutSharp.Models.N3DS
/// </summary>
public NCSDHeader Header { get; set; }
/// <summary>
/// 3DS card info header
/// </summary>
public CardInfoHeader CardInfoHeader { get; set; }
/// <summary>
/// 3DS development card info header
/// </summary>
public DevelopmentCardInfoHeader DevelopmentCardInfoHeader { get; set; }
/// <summary>
/// NCCH partitions
/// </summary>

View File

@@ -0,0 +1,31 @@
namespace BurnOutSharp.Models.N3DS
{
/// <see href="https://www.3dbrew.org/wiki/NCSD#Development_Card_Info_Header_Extension"/>
public sealed class DevelopmentCardInfoHeader
{
/// <summary>
/// InitialData
/// </summary>
public InitialData InitialData;
/// <summary>
/// CardDeviceReserved1
/// </summary>
public byte[] CardDeviceReserved1;
/// <summary>
/// TitleKey
/// </summary>
public byte[] TitleKey;
/// <summary>
/// CardDeviceReserved2
/// </summary>
public byte[] CardDeviceReserved2;
/// <summary>
/// TestData
/// </summary>
public TestData TestData;
}
}

View File

@@ -0,0 +1,35 @@
namespace BurnOutSharp.Models.N3DS
{
/// <see href="https://www.3dbrew.org/wiki/NCSD#InitialData"/>
public sealed class InitialData
{
/// <summary>
/// Card seed keyY (first u64 is Media ID (same as first NCCH partitionId))
/// </summary>
public byte[] CardSeedKeyY;
/// <summary>
/// Encrypted card seed (AES-CCM, keyslot 0x3B for retail cards, see CTRCARD_SECSEED) /// </summary>
public byte[] EncryptedCardSeed;
/// <summary>
/// Card seed AES-MAC
/// </summary>
public byte[] CardSeedAESMAC;
/// <summary>
/// Card seed nonce
/// </summary>
public byte[] CardSeedNonce;
/// <summary>
/// Reserved3
/// </summary>
public byte[] Reserved;
/// <summary>
/// Copy of first NCCH header (excluding RSA signature)
/// </summary>
public NCCHHeader BackupHeader;
}
}

View File

@@ -75,7 +75,7 @@
/// <summary>
/// Partition ID table
/// </summary>
public byte[][] PartitionIdTable;
public ulong[] PartitionIdTable;
/// <summary>
/// Reserved
@@ -115,87 +115,5 @@
public byte[] EncryptedMBR;
#endregion
#region Card Info Header
/// <summary>
/// CARD2: Writable Address In Media Units (For 'On-Chip' Savedata). CARD1: Always 0xFFFFFFFF.
/// </summary>
public byte[] CARD2WritableAddressMediaUnits;
/// <summary>
/// Card Info Bitmask
/// </summary>
public byte[] CardInfoBytemask;
/// <summary>
/// Reserved1
/// </summary>
public byte[] Reserved3;
/// <summary>
/// Title version
/// </summary>
public ushort TitleVersion;
/// <summary>
/// Card revision
/// </summary>
public ushort CardRevision;
/// <summary>
/// Reserved2
/// </summary>
public byte[] Reserved4;
/// <summary>
/// Card seed keyY (first u64 is Media ID (same as first NCCH partitionId))
/// </summary>
public byte[] CardSeedKeyY;
/// <summary>
/// Encrypted card seed (AES-CCM, keyslot 0x3B for retail cards, see CTRCARD_SECSEED) /// </summary>
public byte[] EncryptedCardSeed;
/// <summary>
/// Card seed AES-MAC
/// </summary>
public byte[] CardSeedAESMAC;
/// <summary>
/// Card seed nonce
/// </summary>
public byte[] CardSeedNonce;
/// <summary>
/// Reserved3
/// </summary>
public byte[] Reserved5;
/// <summary>
/// Copy of first NCCH header (excluding RSA signature)
/// </summary>
public NCCHHeader BackupHeader;
#endregion
#region Development Card Info Header Extension
/// <summary>
/// CardDeviceReserved1
/// </summary>
public byte[] CardDeviceReserved1;
/// <summary>
/// TitleKey
/// </summary>
public byte[] TitleKey;
/// <summary>
/// CardDeviceReserved2
/// </summary>
public byte[] CardDeviceReserved2;
#endregion
}
}

View File

@@ -0,0 +1,59 @@
namespace BurnOutSharp.Models.N3DS
{
/// <summary>
/// The test data is the same one encountered in development DS/DSi cartridges.
/// </summary>
/// <see href="https://www.3dbrew.org/wiki/NCSD#TestData"/>
public sealed class TestData
{
/// <summary>
/// The bytes FF 00 FF 00 AA 55 AA 55.
/// </summary>
public byte[] Signature;
/// <summary>
/// An ascending byte sequence equal to the offset mod 256 (08 09 0A ... FE FF 00 01 ... FF).
/// </summary>
public byte[] AscendingByteSequence;
/// <summary>
/// A descending byte sequence equal to 255 minus the offset mod 256 (FF FE FD ... 00 FF DE ... 00).
/// </summary>
public byte[] DescendingByteSequence;
/// <summary>
/// Filled with 00 (0b00000000) bytes.
/// </summary>
public byte[] Filled00;
/// <summary>
/// Filled with FF (0b11111111) bytes.
/// </summary>
public byte[] FilledFF;
/// <summary>
/// Filled with 0F (0b00001111) bytes.
/// </summary>
public byte[] Filled0F;
/// <summary>
/// Filled with F0 (0b11110000) bytes.
/// </summary>
public byte[] FilledF0;
/// <summary>
/// Filled with 55 (0b01010101) bytes.
/// </summary>
public byte[] Filled55;
/// <summary>
/// Filled with AA (0b10101010) bytes.
/// </summary>
public byte[] FilledAA;
/// <summary>
/// The final byte is 00 (0b00000000).
/// </summary>
public byte FinalByte;
}
}

View File

@@ -0,0 +1,452 @@
using System;
using System.IO;
namespace BurnOutSharp.Wrappers
{
public class N3DS : WrapperBase
{
#region Pass-Through Properties
#region Header
#region Common to all NCSD files
/// <inheritdoc cref="Models.N3DS.NCSDHeader.RSA2048Signature"/>
public byte[] RSA2048Signature => _cart.Header.RSA2048Signature;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.MagicNumber"/>
public string MagicNumber => _cart.Header.MagicNumber;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.ImageSizeInMediaUnits"/>
public uint ImageSizeInMediaUnits => _cart.Header.ImageSizeInMediaUnits;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.MediaId"/>
public byte[] MediaId => _cart.Header.MediaId;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.PartitionsFSType"/>
public Models.N3DS.FilesystemType PartitionsFSType => _cart.Header.PartitionsFSType;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.PartitionsCryptType"/>
public byte[] PartitionsCryptType => _cart.Header.PartitionsCryptType;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.PartitionsTable"/>
public Models.N3DS.PartitionTableEntry[] PartitionsTable => _cart.Header.PartitionsTable;
#endregion
#region CTR Cart Image (CCI) Specific
/// <inheritdoc cref="Models.N3DS.NCSDHeader.ExheaderHash"/>
public byte[] ExheaderHash => _cart.Header.ExheaderHash;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.AdditionalHeaderSize"/>
public uint AdditionalHeaderSize => _cart.Header.AdditionalHeaderSize;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.SectorZeroOffset"/>
public uint SectorZeroOffset => _cart.Header.SectorZeroOffset;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.PartitionFlags"/>
public byte[] PartitionFlags => _cart.Header.PartitionFlags;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.PartitionIdTable"/>
public ulong[] PartitionIdTable => _cart.Header.PartitionIdTable;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.Reserved1"/>
public byte[] Reserved1 => _cart.Header.Reserved1;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.Reserved2"/>
public byte[] Reserved2 => _cart.Header.Reserved2;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.FirmUpdateByte1"/>
public byte FirmUpdateByte1 => _cart.Header.FirmUpdateByte1;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.FirmUpdateByte2"/>
public byte FirmUpdateByte2 => _cart.Header.FirmUpdateByte2;
#endregion
#region Raw NAND Format Specific
/// <inheritdoc cref="Models.N3DS.NCSDHeader.Unknown"/>
public byte[] Unknown => _cart.Header.Unknown;
/// <inheritdoc cref="Models.N3DS.NCSDHeader.EncryptedMBR"/>
public byte[] EncryptedMBR => _cart.Header.EncryptedMBR;
#endregion
#endregion
#region Card Info Header
/// <inheritdoc cref="Models.N3DS.CardInfoHeader.WritableAddressMediaUnits"/>
public uint CIH_WritableAddressMediaUnits => _cart.CardInfoHeader.WritableAddressMediaUnits;
/// <inheritdoc cref="Models.N3DS.CardInfoHeader.CardInfoBitmask"/>
public uint CIH_CardInfoBitmask => _cart.CardInfoHeader.CardInfoBitmask;
/// <inheritdoc cref="Models.N3DS.CardInfoHeader.Reserved1"/>
public byte[] CIH_Reserved1 => _cart.CardInfoHeader.Reserved1;
/// <inheritdoc cref="Models.N3DS.CardInfoHeader.FilledSize"/>
public uint CIH_FilledSize => _cart.CardInfoHeader.FilledSize;
/// <inheritdoc cref="Models.N3DS.CardInfoHeader.Reserved2"/>
public byte[] CIH_Reserved2 => _cart.CardInfoHeader.Reserved2;
/// <inheritdoc cref="Models.N3DS.CardInfoHeader.TitleVersion"/>
public ushort CIH_TitleVersion => _cart.CardInfoHeader.TitleVersion;
/// <inheritdoc cref="Models.N3DS.CardInfoHeader.CardRevision"/>
public ushort CIH_CardRevision => _cart.CardInfoHeader.CardRevision;
/// <inheritdoc cref="Models.N3DS.CardInfoHeader.Reserved3"/>
public byte[] CIH_Reserved3 => _cart.CardInfoHeader.Reserved3;
/// <inheritdoc cref="Models.N3DS.CardInfoHeader.CVerTitleID"/>
public byte[] CIH_CVerTitleID => _cart.CardInfoHeader.CVerTitleID;
/// <inheritdoc cref="Models.N3DS.CardInfoHeader.CVerVersionNumber"/>
public ushort CIH_CVerVersionNumber => _cart.CardInfoHeader.CVerVersionNumber;
/// <inheritdoc cref="Models.N3DS.CardInfoHeader.Reserved4"/>
public byte[] CIH_Reserved4 => _cart.CardInfoHeader.Reserved4;
#endregion
#region Development Card Info Header
#region Initial Data
/// <inheritdoc cref="Models.N3DS.InitialData.CardSeedKeyY"/>
public byte[] DCIH_ID_CardSeedKeyY => _cart.DevelopmentCardInfoHeader?.InitialData?.CardSeedKeyY;
/// <inheritdoc cref="Models.N3DS.InitialData.EncryptedCardSeed"/>
public byte[] DCIH_ID_EncryptedCardSeed => _cart.DevelopmentCardInfoHeader?.InitialData?.EncryptedCardSeed;
/// <inheritdoc cref="Models.N3DS.InitialData.CardSeedAESMAC"/>
public byte[] DCIH_ID_CardSeedAESMAC => _cart.DevelopmentCardInfoHeader?.InitialData?.CardSeedAESMAC;
/// <inheritdoc cref="Models.N3DS.InitialData.CardSeedNonce"/>
public byte[] DCIH_ID_CardSeedNonce => _cart.DevelopmentCardInfoHeader?.InitialData?.CardSeedNonce;
/// <inheritdoc cref="Models.N3DS.InitialData.Reserved3"/>
public byte[] DCIH_ID_Reserved => _cart.DevelopmentCardInfoHeader?.InitialData?.Reserved;
/// <inheritdoc cref="Models.N3DS.InitialData.BackupHeader"/>
public Models.N3DS.NCCHHeader DCIH_ID_BackupHeader => _cart.DevelopmentCardInfoHeader?.InitialData?.BackupHeader;
#endregion
/// <inheritdoc cref="Models.N3DS.DevelopmentCardInfoHeader.CardDeviceReserved1"/>
public byte[] DCIH_CardDeviceReserved1 => _cart.DevelopmentCardInfoHeader?.CardDeviceReserved1;
/// <inheritdoc cref="Models.N3DS.DevelopmentCardInfoHeader.TitleKey"/>
public byte[] DCIH_TitleKey => _cart.DevelopmentCardInfoHeader?.TitleKey;
/// <inheritdoc cref="Models.N3DS.DevelopmentCardInfoHeader.CardDeviceReserved2"/>
public byte[] DCIH_CardDeviceReserved2 => _cart.DevelopmentCardInfoHeader?.CardDeviceReserved2;
#region Test Data
/// <inheritdoc cref="Models.N3DS.TestData.Signature"/>
public byte[] DCIH_TD_Signature => _cart.DevelopmentCardInfoHeader?.TestData?.Signature;
/// <inheritdoc cref="Models.N3DS.TestData.AscendingByteSequence"/>
public byte[] DCIH_TD_AscendingByteSequence => _cart.DevelopmentCardInfoHeader?.TestData?.AscendingByteSequence;
/// <inheritdoc cref="Models.N3DS.TestData.DescendingByteSequence"/>
public byte[] DCIH_TD_DescendingByteSequence => _cart.DevelopmentCardInfoHeader?.TestData?.DescendingByteSequence;
/// <inheritdoc cref="Models.N3DS.TestData.Filled00"/>
public byte[] DCIH_TD_Filled00 => _cart.DevelopmentCardInfoHeader?.TestData?.Filled00;
/// <inheritdoc cref="Models.N3DS.TestData.FilledFF"/>
public byte[] DCIH_TD_FilledFF => _cart.DevelopmentCardInfoHeader?.TestData?.FilledFF;
/// <inheritdoc cref="Models.N3DS.TestData.Filled0F"/>
public byte[] DCIH_TD_Filled0F => _cart.DevelopmentCardInfoHeader?.TestData?.Filled0F;
/// <inheritdoc cref="Models.N3DS.TestData.FilledF0"/>
public byte[] DCIH_TD_FilledF0 => _cart.DevelopmentCardInfoHeader?.TestData?.FilledF0;
/// <inheritdoc cref="Models.N3DS.TestData.Filled55"/>
public byte[] DCIH_TD_Filled55 => _cart.DevelopmentCardInfoHeader?.TestData?.Filled55;
/// <inheritdoc cref="Models.N3DS.TestData.FilledAA"/>
public byte[] DCIH_TD_FilledAA => _cart.DevelopmentCardInfoHeader?.TestData?.FilledAA;
/// <inheritdoc cref="Models.N3DS.TestData.FinalByte"/>
public byte? DCIH_TD_FinalByte => _cart.DevelopmentCardInfoHeader?.TestData?.FinalByte;
#endregion
#endregion
#region Partitions
/// <inheritdoc cref="Models.N3DS.Cart.Partitions"/>
public Models.N3DS.NCCHHeader[] Partitions => _cart.Partitions;
#endregion
#endregion
#region Instance Variables
/// <summary>
/// Internal representation of the cart
/// </summary>
private Models.N3DS.Cart _cart;
#endregion
#region Constructors
/// <summary>
/// Private constructor
/// </summary>
private N3DS() { }
/// <summary>
/// Create a 3DS cart image 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>A 3DS cart image wrapper on success, null on failure</returns>
public static N3DS Create(byte[] data, int offset)
{
// If the data is invalid
if (data == null)
return null;
// If the offset is out of bounds
if (offset < 0 || offset >= data.Length)
return null;
// Create a memory stream and use that
MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset);
return Create(dataStream);
}
/// <summary>
/// Create a 3DS cart image from a Stream
/// </summary>
/// <param name="data">Stream representing the archive</param>
/// <returns>A 3DS cart image wrapper on success, null on failure</returns>
public static N3DS Create(Stream data)
{
// If the data is invalid
if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
return null;
var archive = Builders.N3DS.ParseCart(data);
if (archive == null)
return null;
var wrapper = new N3DS
{
_cart = archive,
_dataSource = DataSource.Stream,
_streamData = data,
};
return wrapper;
}
#endregion
#region Printing
/// <inheritdoc/>
public override void Print()
{
Console.WriteLine("3DS Cart Information:");
Console.WriteLine("-------------------------");
Console.WriteLine();
PrintNCSDHeader();
PrintCardInfoHeader();
PrintDevelopmentCardInfoHeader();
PrintPartitions();
}
/// <summary>
/// Print NCSD header information
/// </summary>
private void PrintNCSDHeader()
{
Console.WriteLine(" NCSD Header Information:");
Console.WriteLine(" -------------------------");
Console.WriteLine($" RSA-2048 SHA-256 signature: {BitConverter.ToString(RSA2048Signature).Replace('-', ' ')}");
Console.WriteLine($" Magic number: {MagicNumber}");
Console.WriteLine($" Image size in media units: {ImageSizeInMediaUnits}");
Console.WriteLine($" Media ID: {BitConverter.ToString(MediaId).Replace('-', ' ')}");
Console.WriteLine($" Partitions filesystem type: {PartitionsFSType}");
Console.WriteLine($" Partitions crypt type: {BitConverter.ToString(PartitionsCryptType).Replace('-', ' ')}");
Console.WriteLine();
Console.WriteLine($" Partition table:");
Console.WriteLine(" -------------------------");
for (int i = 0; i < PartitionsTable.Length; i++)
{
var partitionTableEntry = PartitionsTable[i];
Console.WriteLine($" Partition table entry {i}");
Console.WriteLine($" Offset: {partitionTableEntry.Offset}");
Console.WriteLine($" Length: {partitionTableEntry.Length}");
}
Console.WriteLine();
// If we have a cart image
if (PartitionsFSType == Models.N3DS.FilesystemType.Normal || PartitionsFSType == Models.N3DS.FilesystemType.None)
{
Console.WriteLine($" Exheader SHA-256 hash: {BitConverter.ToString(ExheaderHash).Replace('-', ' ')}");
Console.WriteLine($" Additional header size: {AdditionalHeaderSize}");
Console.WriteLine($" Sector zero offset: {SectorZeroOffset}");
Console.WriteLine($" Partition flags: {BitConverter.ToString(PartitionFlags).Replace('-', ' ')}");
Console.WriteLine();
Console.WriteLine($" Partition ID table:");
Console.WriteLine(" -------------------------");
for (int i = 0; i < PartitionIdTable.Length; i++)
{
Console.WriteLine($" Partition {i} ID: {PartitionIdTable[i]}");
}
Console.WriteLine();
Console.WriteLine($" Reserved 1: {BitConverter.ToString(Reserved1).Replace('-', ' ')}");
Console.WriteLine($" Reserved 2: {BitConverter.ToString(Reserved2).Replace('-', ' ')}");
Console.WriteLine($" Firmware update byte 1: {FirmUpdateByte1}");
Console.WriteLine($" Firmware update byte 2: {FirmUpdateByte2}");
}
// If we have a firmware image
else if (PartitionsFSType == Models.N3DS.FilesystemType.FIRM)
{
Console.WriteLine($" Unknown: {BitConverter.ToString(Unknown).Replace('-', ' ')}");
Console.WriteLine($" Encrypted MBR: {BitConverter.ToString(EncryptedMBR).Replace('-', ' ')}");
}
Console.WriteLine();
}
/// <summary>
/// Print card info header information
/// </summary>
private void PrintCardInfoHeader()
{
Console.WriteLine(" Card Info Header Information:");
Console.WriteLine(" -------------------------");
Console.WriteLine($" Writable address in media units: {CIH_WritableAddressMediaUnits}");
Console.WriteLine($" Card info bitmask: {CIH_CardInfoBitmask}");
Console.WriteLine($" Reserved 1: {BitConverter.ToString(CIH_Reserved1).Replace('-', ' ')}");
Console.WriteLine($" Filled size of cartridge: {CIH_FilledSize}");
Console.WriteLine($" Reserved 2: {BitConverter.ToString(CIH_Reserved2).Replace('-', ' ')}");
Console.WriteLine($" Title version: {CIH_TitleVersion}");
Console.WriteLine($" Card revision: {CIH_CardRevision}");
Console.WriteLine($" Reserved 3: {BitConverter.ToString(CIH_Reserved3).Replace('-', ' ')}");
Console.WriteLine($" Title ID of CVer in included update partition: {BitConverter.ToString(CIH_CVerTitleID).Replace('-', ' ')}");
Console.WriteLine($" Version number of CVer in included update partition: {CIH_CVerVersionNumber}");
Console.WriteLine($" Reserved 4: {BitConverter.ToString(CIH_Reserved4).Replace('-', ' ')}");
Console.WriteLine();
}
/// <summary>
/// Print development card info header information
/// </summary>
private void PrintDevelopmentCardInfoHeader()
{
Console.WriteLine(" Development Card Info Header Information:");
Console.WriteLine(" -------------------------");
if (_cart.DevelopmentCardInfoHeader == null)
{
Console.WriteLine(" No development card info header");
}
else
{
Console.WriteLine();
Console.WriteLine(" Initial Data:");
Console.WriteLine(" -------------------------");
Console.WriteLine($" Card seed keyY: {BitConverter.ToString(DCIH_ID_CardSeedKeyY).Replace('-', ' ')}");
Console.WriteLine($" Encrypted card seed: {BitConverter.ToString(DCIH_ID_EncryptedCardSeed).Replace('-', ' ')}");
Console.WriteLine($" Card seed AES-MAC: {BitConverter.ToString(DCIH_ID_CardSeedAESMAC).Replace('-', ' ')}");
Console.WriteLine($" Card seed nonce: {BitConverter.ToString(DCIH_ID_CardSeedNonce).Replace('-', ' ')}");
Console.WriteLine($" Reserved: {BitConverter.ToString(DCIH_ID_Reserved).Replace('-', ' ')}");
// TODO: Print Backup Header?
Console.WriteLine();
Console.WriteLine($" Card device reserved 1: {BitConverter.ToString(DCIH_CardDeviceReserved1).Replace('-', ' ')}");
Console.WriteLine($" Title key: {BitConverter.ToString(DCIH_TitleKey).Replace('-', ' ')}");
Console.WriteLine($" Card device reserved 2: {BitConverter.ToString(DCIH_CardDeviceReserved2).Replace('-', ' ')}");
Console.WriteLine();
Console.WriteLine(" Test Data:");
Console.WriteLine(" -------------------------");
Console.WriteLine($" Signature: {BitConverter.ToString(DCIH_TD_Signature).Replace('-', ' ')}");
Console.WriteLine($" Ascending byte sequence: {BitConverter.ToString(DCIH_TD_AscendingByteSequence).Replace('-', ' ')}");
Console.WriteLine($" Descending byte sequence: {BitConverter.ToString(DCIH_TD_DescendingByteSequence).Replace('-', ' ')}");
Console.WriteLine($" Filled with 00: {BitConverter.ToString(DCIH_TD_Filled00).Replace('-', ' ')}");
Console.WriteLine($" Filled with FF: {BitConverter.ToString(DCIH_TD_FilledFF).Replace('-', ' ')}");
Console.WriteLine($" Filled with 0F: {BitConverter.ToString(DCIH_TD_Filled0F).Replace('-', ' ')}");
Console.WriteLine($" Filled with F0: {BitConverter.ToString(DCIH_TD_FilledF0).Replace('-', ' ')}");
Console.WriteLine($" Filled with 55: {BitConverter.ToString(DCIH_TD_Filled55).Replace('-', ' ')}");
Console.WriteLine($" Filled with AA: {BitConverter.ToString(DCIH_TD_FilledAA).Replace('-', ' ')}");
Console.WriteLine($" Final byte: {DCIH_TD_FinalByte}");
}
Console.WriteLine();
}
/// <summary>
/// Print NCCH partition header information
/// </summary>
private void PrintPartitions()
{
Console.WriteLine(" NCCH Partition Header Information:");
Console.WriteLine(" -------------------------");
if (Partitions == null || Partitions.Length == 0)
{
Console.WriteLine(" No NCCH partition headers");
}
else
{
for (int i = 0; i < Partitions.Length; i++)
{
var partitionHeader = Partitions[i];
Console.WriteLine($" NCCH Partition Header {i}");
Console.WriteLine($" RSA-2048 SHA-256 signature: {BitConverter.ToString(partitionHeader.RSA2048Signature).Replace('-', ' ')}");
Console.WriteLine($" Magic ID: {partitionHeader.MagicID}");
Console.WriteLine($" Content size in media units: {partitionHeader.ContentSizeInMediaUnits}");
Console.WriteLine($" Partition ID: {partitionHeader.PartitionId}");
Console.WriteLine($" Maker code: {partitionHeader.MakerCode}");
Console.WriteLine($" Version: {partitionHeader.Version}");
Console.WriteLine($" Verification hash: {partitionHeader.VerificationHash}");
Console.WriteLine($" Program ID: {BitConverter.ToString(partitionHeader.ProgramId).Replace('-', ' ')}");
Console.WriteLine($" Reserved 1: {BitConverter.ToString(partitionHeader.Reserved1).Replace('-', ' ')}");
Console.WriteLine($" Logo region SHA-256 hash: {BitConverter.ToString(partitionHeader.LogoRegionHash).Replace('-', ' ')}");
Console.WriteLine($" Product code: {BitConverter.ToString(partitionHeader.ProductCode).Replace('-', ' ')}");
Console.WriteLine($" Extended header SHA-256 hash: {BitConverter.ToString(partitionHeader.ExtendedHeaderHash).Replace('-', ' ')}");
Console.WriteLine($" Extended header size in bytes: {partitionHeader.ExtendedHeaderSizeInBytes}");
Console.WriteLine($" Reserved 2: {BitConverter.ToString(partitionHeader.Reserved2).Replace('-', ' ')}");
Console.WriteLine($" Flags: {partitionHeader.Flags}");
Console.WriteLine($" Plain region offset, in media units: {partitionHeader.PlainRegionOffsetInMediaUnits}");
Console.WriteLine($" Plain region size, in media units: {partitionHeader.PlainRegionSizeInMediaUnits}");
Console.WriteLine($" Logo region offset, in media units: {partitionHeader.LogoRegionOffsetInMediaUnits}");
Console.WriteLine($" Logo region size, in media units: {partitionHeader.LogoRegionSizeInMediaUnits}");
Console.WriteLine($" ExeFS offset, in media units: {partitionHeader.ExeFSOffsetInMediaUnits}");
Console.WriteLine($" ExeFS size, in media units: {partitionHeader.ExeFSSizeInMediaUnits}");
Console.WriteLine($" ExeFS hash region size, in media units: {partitionHeader.ExeFSHashRegionSizeInMediaUnits}");
Console.WriteLine($" Reserved 3: {BitConverter.ToString(partitionHeader.Reserved3).Replace('-', ' ')}");
Console.WriteLine($" RomFS offset, in media units: {partitionHeader.RomFSOffsetInMediaUnits}");
Console.WriteLine($" RomFS size, in media units: {partitionHeader.RomFSSizeInMediaUnits}");
Console.WriteLine($" RomFS hash region size, in media units: {partitionHeader.RomFSHashRegionSizeInMediaUnits}");
Console.WriteLine($" Reserved 4: {BitConverter.ToString(partitionHeader.Reserved4).Replace('-', ' ')}");
Console.WriteLine($" ExeFS superblock SHA-256 hash: {BitConverter.ToString(partitionHeader.ExeFSSuperblockHash).Replace('-', ' ')}");
Console.WriteLine($" RomFS superblock SHA-256 hash: {BitConverter.ToString(partitionHeader.RomFSSuperblockHash).Replace('-', ' ')}");
}
}
Console.WriteLine();
}
#endregion
}
}