diff --git a/BurnOutSharp.Builders/N3DS.cs b/BurnOutSharp.Builders/N3DS.cs
index 0ea02d59..32ac33e6 100644
--- a/BurnOutSharp.Builders/N3DS.cs
+++ b/BurnOutSharp.Builders/N3DS.cs
@@ -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
///
/// Stream to parse
- /// Indicates if the cart is development or not
/// Filled NCSD header on success, null on error
- 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;
}
+ ///
+ /// Parse a Stream into a card info header
+ ///
+ /// Stream to parse
+ /// Filled card info header on success, null on error
+ 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;
+ }
+
+ ///
+ /// Parse a Stream into a development card info header
+ ///
+ /// Stream to parse
+ /// Filled development card info header on success, null on error
+ 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;
+ }
+
+ ///
+ /// Parse a Stream into an initial data
+ ///
+ /// Stream to parse
+ /// Filled initial data on success, null on error
+ 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;
+ }
+
///
/// Parse a Stream into an NCCH header
///
@@ -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;
}
+ ///
+ /// Parse a Stream into an initial data
+ ///
+ /// Stream to parse
+ /// Filled initial data on success, null on error
+ 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
}
}
diff --git a/BurnOutSharp.Builders/Nitro.cs b/BurnOutSharp.Builders/Nitro.cs
index 030f7934..46ee3878 100644
--- a/BurnOutSharp.Builders/Nitro.cs
+++ b/BurnOutSharp.Builders/Nitro.cs
@@ -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();
diff --git a/BurnOutSharp.Models/N3DS/CardInfoHeader.cs b/BurnOutSharp.Models/N3DS/CardInfoHeader.cs
new file mode 100644
index 00000000..4e3ca216
--- /dev/null
+++ b/BurnOutSharp.Models/N3DS/CardInfoHeader.cs
@@ -0,0 +1,61 @@
+namespace BurnOutSharp.Models.N3DS
+{
+ ///
+ public sealed class CardInfoHeader
+ {
+ ///
+ /// CARD2: Writable Address In Media Units (For 'On-Chip' Savedata). CARD1: Always 0xFFFFFFFF.
+ ///
+ public uint WritableAddressMediaUnits;
+
+ ///
+ /// Card Info Bitmask
+ ///
+ public uint CardInfoBitmask;
+
+ ///
+ /// Reserved
+ ///
+ public byte[] Reserved1;
+
+ ///
+ /// Filled size of cartridge
+ ///
+ public uint FilledSize;
+
+ ///
+ /// Reserved
+ ///
+ public byte[] Reserved2;
+
+ ///
+ /// Title version
+ ///
+ public ushort TitleVersion;
+
+ ///
+ /// Card revision
+ ///
+ public ushort CardRevision;
+
+ ///
+ /// Reserved
+ ///
+ public byte[] Reserved3;
+
+ ///
+ /// Title ID of CVer in included update partition
+ ///
+ public byte[] CVerTitleID;
+
+ ///
+ /// Version number of CVer in included update partition
+ ///
+ public ushort CVerVersionNumber;
+
+ ///
+ /// Reserved
+ ///
+ public byte[] Reserved4;
+ }
+}
diff --git a/BurnOutSharp.Models/N3DS/Cart.cs b/BurnOutSharp.Models/N3DS/Cart.cs
index 85fe08fe..98496919 100644
--- a/BurnOutSharp.Models/N3DS/Cart.cs
+++ b/BurnOutSharp.Models/N3DS/Cart.cs
@@ -10,6 +10,16 @@ namespace BurnOutSharp.Models.N3DS
///
public NCSDHeader Header { get; set; }
+ ///
+ /// 3DS card info header
+ ///
+ public CardInfoHeader CardInfoHeader { get; set; }
+
+ ///
+ /// 3DS development card info header
+ ///
+ public DevelopmentCardInfoHeader DevelopmentCardInfoHeader { get; set; }
+
///
/// NCCH partitions
///
diff --git a/BurnOutSharp.Models/N3DS/DevelopmentCardInfoHeader.cs b/BurnOutSharp.Models/N3DS/DevelopmentCardInfoHeader.cs
new file mode 100644
index 00000000..9c7429d6
--- /dev/null
+++ b/BurnOutSharp.Models/N3DS/DevelopmentCardInfoHeader.cs
@@ -0,0 +1,31 @@
+namespace BurnOutSharp.Models.N3DS
+{
+ ///
+ public sealed class DevelopmentCardInfoHeader
+ {
+ ///
+ /// InitialData
+ ///
+ public InitialData InitialData;
+
+ ///
+ /// CardDeviceReserved1
+ ///
+ public byte[] CardDeviceReserved1;
+
+ ///
+ /// TitleKey
+ ///
+ public byte[] TitleKey;
+
+ ///
+ /// CardDeviceReserved2
+ ///
+ public byte[] CardDeviceReserved2;
+
+ ///
+ /// TestData
+ ///
+ public TestData TestData;
+ }
+}
diff --git a/BurnOutSharp.Models/N3DS/InitialData.cs b/BurnOutSharp.Models/N3DS/InitialData.cs
new file mode 100644
index 00000000..9415671c
--- /dev/null
+++ b/BurnOutSharp.Models/N3DS/InitialData.cs
@@ -0,0 +1,35 @@
+namespace BurnOutSharp.Models.N3DS
+{
+ ///
+ public sealed class InitialData
+ {
+ ///
+ /// Card seed keyY (first u64 is Media ID (same as first NCCH partitionId))
+ ///
+ public byte[] CardSeedKeyY;
+
+ ///
+ /// Encrypted card seed (AES-CCM, keyslot 0x3B for retail cards, see CTRCARD_SECSEED) ///
+ public byte[] EncryptedCardSeed;
+
+ ///
+ /// Card seed AES-MAC
+ ///
+ public byte[] CardSeedAESMAC;
+
+ ///
+ /// Card seed nonce
+ ///
+ public byte[] CardSeedNonce;
+
+ ///
+ /// Reserved3
+ ///
+ public byte[] Reserved;
+
+ ///
+ /// Copy of first NCCH header (excluding RSA signature)
+ ///
+ public NCCHHeader BackupHeader;
+ }
+}
diff --git a/BurnOutSharp.Models/N3DS/NCSDHeader.cs b/BurnOutSharp.Models/N3DS/NCSDHeader.cs
index 87018d07..9d5142ce 100644
--- a/BurnOutSharp.Models/N3DS/NCSDHeader.cs
+++ b/BurnOutSharp.Models/N3DS/NCSDHeader.cs
@@ -75,7 +75,7 @@
///
/// Partition ID table
///
- public byte[][] PartitionIdTable;
+ public ulong[] PartitionIdTable;
///
/// Reserved
@@ -115,87 +115,5 @@
public byte[] EncryptedMBR;
#endregion
-
- #region Card Info Header
-
- ///
- /// CARD2: Writable Address In Media Units (For 'On-Chip' Savedata). CARD1: Always 0xFFFFFFFF.
- ///
- public byte[] CARD2WritableAddressMediaUnits;
-
- ///
- /// Card Info Bitmask
- ///
- public byte[] CardInfoBytemask;
-
- ///
- /// Reserved1
- ///
- public byte[] Reserved3;
-
- ///
- /// Title version
- ///
- public ushort TitleVersion;
-
- ///
- /// Card revision
- ///
- public ushort CardRevision;
-
- ///
- /// Reserved2
- ///
- public byte[] Reserved4;
-
- ///
- /// Card seed keyY (first u64 is Media ID (same as first NCCH partitionId))
- ///
- public byte[] CardSeedKeyY;
-
- ///
- /// Encrypted card seed (AES-CCM, keyslot 0x3B for retail cards, see CTRCARD_SECSEED) ///
- public byte[] EncryptedCardSeed;
-
- ///
- /// Card seed AES-MAC
- ///
- public byte[] CardSeedAESMAC;
-
- ///
- /// Card seed nonce
- ///
- public byte[] CardSeedNonce;
-
- ///
- /// Reserved3
- ///
- public byte[] Reserved5;
-
- ///
- /// Copy of first NCCH header (excluding RSA signature)
- ///
- public NCCHHeader BackupHeader;
-
- #endregion
-
- #region Development Card Info Header Extension
-
- ///
- /// CardDeviceReserved1
- ///
- public byte[] CardDeviceReserved1;
-
- ///
- /// TitleKey
- ///
- public byte[] TitleKey;
-
- ///
- /// CardDeviceReserved2
- ///
- public byte[] CardDeviceReserved2;
-
- #endregion
}
}
diff --git a/BurnOutSharp.Models/N3DS/TestData.cs b/BurnOutSharp.Models/N3DS/TestData.cs
new file mode 100644
index 00000000..212816c9
--- /dev/null
+++ b/BurnOutSharp.Models/N3DS/TestData.cs
@@ -0,0 +1,59 @@
+namespace BurnOutSharp.Models.N3DS
+{
+ ///
+ /// The test data is the same one encountered in development DS/DSi cartridges.
+ ///
+ ///
+ public sealed class TestData
+ {
+ ///
+ /// The bytes FF 00 FF 00 AA 55 AA 55.
+ ///
+ public byte[] Signature;
+
+ ///
+ /// An ascending byte sequence equal to the offset mod 256 (08 09 0A ... FE FF 00 01 ... FF).
+ ///
+ public byte[] AscendingByteSequence;
+
+ ///
+ /// A descending byte sequence equal to 255 minus the offset mod 256 (FF FE FD ... 00 FF DE ... 00).
+ ///
+ public byte[] DescendingByteSequence;
+
+ ///
+ /// Filled with 00 (0b00000000) bytes.
+ ///
+ public byte[] Filled00;
+
+ ///
+ /// Filled with FF (0b11111111) bytes.
+ ///
+ public byte[] FilledFF;
+
+ ///
+ /// Filled with 0F (0b00001111) bytes.
+ ///
+ public byte[] Filled0F;
+
+ ///
+ /// Filled with F0 (0b11110000) bytes.
+ ///
+ public byte[] FilledF0;
+
+ ///
+ /// Filled with 55 (0b01010101) bytes.
+ ///
+ public byte[] Filled55;
+
+ ///
+ /// Filled with AA (0b10101010) bytes.
+ ///
+ public byte[] FilledAA;
+
+ ///
+ /// The final byte is 00 (0b00000000).
+ ///
+ public byte FinalByte;
+ }
+}
diff --git a/BurnOutSharp.Wrappers/N3DS.cs b/BurnOutSharp.Wrappers/N3DS.cs
new file mode 100644
index 00000000..d8371b1a
--- /dev/null
+++ b/BurnOutSharp.Wrappers/N3DS.cs
@@ -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
+
+ ///
+ public byte[] RSA2048Signature => _cart.Header.RSA2048Signature;
+
+ ///
+ public string MagicNumber => _cart.Header.MagicNumber;
+
+ ///
+ public uint ImageSizeInMediaUnits => _cart.Header.ImageSizeInMediaUnits;
+
+ ///
+ public byte[] MediaId => _cart.Header.MediaId;
+
+ ///
+ public Models.N3DS.FilesystemType PartitionsFSType => _cart.Header.PartitionsFSType;
+
+ ///
+ public byte[] PartitionsCryptType => _cart.Header.PartitionsCryptType;
+
+ ///
+ public Models.N3DS.PartitionTableEntry[] PartitionsTable => _cart.Header.PartitionsTable;
+
+ #endregion
+
+ #region CTR Cart Image (CCI) Specific
+
+ ///
+ public byte[] ExheaderHash => _cart.Header.ExheaderHash;
+
+ ///
+ public uint AdditionalHeaderSize => _cart.Header.AdditionalHeaderSize;
+
+ ///
+ public uint SectorZeroOffset => _cart.Header.SectorZeroOffset;
+
+ ///
+ public byte[] PartitionFlags => _cart.Header.PartitionFlags;
+
+ ///
+ public ulong[] PartitionIdTable => _cart.Header.PartitionIdTable;
+
+ ///
+ public byte[] Reserved1 => _cart.Header.Reserved1;
+
+ ///
+ public byte[] Reserved2 => _cart.Header.Reserved2;
+
+ ///
+ public byte FirmUpdateByte1 => _cart.Header.FirmUpdateByte1;
+
+ ///
+ public byte FirmUpdateByte2 => _cart.Header.FirmUpdateByte2;
+
+ #endregion
+
+ #region Raw NAND Format Specific
+
+ ///
+ public byte[] Unknown => _cart.Header.Unknown;
+
+ ///
+ public byte[] EncryptedMBR => _cart.Header.EncryptedMBR;
+
+ #endregion
+
+ #endregion
+
+ #region Card Info Header
+
+ ///
+ public uint CIH_WritableAddressMediaUnits => _cart.CardInfoHeader.WritableAddressMediaUnits;
+
+ ///
+ public uint CIH_CardInfoBitmask => _cart.CardInfoHeader.CardInfoBitmask;
+
+ ///
+ public byte[] CIH_Reserved1 => _cart.CardInfoHeader.Reserved1;
+
+ ///
+ public uint CIH_FilledSize => _cart.CardInfoHeader.FilledSize;
+
+ ///
+ public byte[] CIH_Reserved2 => _cart.CardInfoHeader.Reserved2;
+
+ ///
+ public ushort CIH_TitleVersion => _cart.CardInfoHeader.TitleVersion;
+
+ ///
+ public ushort CIH_CardRevision => _cart.CardInfoHeader.CardRevision;
+
+ ///
+ public byte[] CIH_Reserved3 => _cart.CardInfoHeader.Reserved3;
+
+ ///
+ public byte[] CIH_CVerTitleID => _cart.CardInfoHeader.CVerTitleID;
+
+ ///
+ public ushort CIH_CVerVersionNumber => _cart.CardInfoHeader.CVerVersionNumber;
+
+ ///
+ public byte[] CIH_Reserved4 => _cart.CardInfoHeader.Reserved4;
+
+ #endregion
+
+ #region Development Card Info Header
+
+ #region Initial Data
+
+ ///
+ public byte[] DCIH_ID_CardSeedKeyY => _cart.DevelopmentCardInfoHeader?.InitialData?.CardSeedKeyY;
+
+ ///
+ public byte[] DCIH_ID_EncryptedCardSeed => _cart.DevelopmentCardInfoHeader?.InitialData?.EncryptedCardSeed;
+
+ ///
+ public byte[] DCIH_ID_CardSeedAESMAC => _cart.DevelopmentCardInfoHeader?.InitialData?.CardSeedAESMAC;
+
+ ///
+ public byte[] DCIH_ID_CardSeedNonce => _cart.DevelopmentCardInfoHeader?.InitialData?.CardSeedNonce;
+
+ ///
+ public byte[] DCIH_ID_Reserved => _cart.DevelopmentCardInfoHeader?.InitialData?.Reserved;
+
+ ///
+ public Models.N3DS.NCCHHeader DCIH_ID_BackupHeader => _cart.DevelopmentCardInfoHeader?.InitialData?.BackupHeader;
+
+ #endregion
+
+ ///
+ public byte[] DCIH_CardDeviceReserved1 => _cart.DevelopmentCardInfoHeader?.CardDeviceReserved1;
+
+ ///
+ public byte[] DCIH_TitleKey => _cart.DevelopmentCardInfoHeader?.TitleKey;
+
+ ///
+ public byte[] DCIH_CardDeviceReserved2 => _cart.DevelopmentCardInfoHeader?.CardDeviceReserved2;
+
+ #region Test Data
+
+ ///
+ public byte[] DCIH_TD_Signature => _cart.DevelopmentCardInfoHeader?.TestData?.Signature;
+
+ ///
+ public byte[] DCIH_TD_AscendingByteSequence => _cart.DevelopmentCardInfoHeader?.TestData?.AscendingByteSequence;
+
+ ///
+ public byte[] DCIH_TD_DescendingByteSequence => _cart.DevelopmentCardInfoHeader?.TestData?.DescendingByteSequence;
+
+ ///
+ public byte[] DCIH_TD_Filled00 => _cart.DevelopmentCardInfoHeader?.TestData?.Filled00;
+
+ ///
+ public byte[] DCIH_TD_FilledFF => _cart.DevelopmentCardInfoHeader?.TestData?.FilledFF;
+
+ ///
+ public byte[] DCIH_TD_Filled0F => _cart.DevelopmentCardInfoHeader?.TestData?.Filled0F;
+
+ ///
+ public byte[] DCIH_TD_FilledF0 => _cart.DevelopmentCardInfoHeader?.TestData?.FilledF0;
+
+ ///
+ public byte[] DCIH_TD_Filled55 => _cart.DevelopmentCardInfoHeader?.TestData?.Filled55;
+
+ ///
+ public byte[] DCIH_TD_FilledAA => _cart.DevelopmentCardInfoHeader?.TestData?.FilledAA;
+
+ ///
+ public byte? DCIH_TD_FinalByte => _cart.DevelopmentCardInfoHeader?.TestData?.FinalByte;
+
+ #endregion
+
+ #endregion
+
+ #region Partitions
+
+ ///
+ public Models.N3DS.NCCHHeader[] Partitions => _cart.Partitions;
+
+ #endregion
+
+ #endregion
+
+ #region Instance Variables
+
+ ///
+ /// Internal representation of the cart
+ ///
+ private Models.N3DS.Cart _cart;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Private constructor
+ ///
+ private N3DS() { }
+
+ ///
+ /// Create a 3DS cart image from a byte array and offset
+ ///
+ /// Byte array representing the archive
+ /// Offset within the array to parse
+ /// A 3DS cart image wrapper on success, null on failure
+ 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);
+ }
+
+ ///
+ /// Create a 3DS cart image from a Stream
+ ///
+ /// Stream representing the archive
+ /// A 3DS cart image wrapper on success, null on failure
+ 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
+
+ ///
+ public override void Print()
+ {
+ Console.WriteLine("3DS Cart Information:");
+ Console.WriteLine("-------------------------");
+ Console.WriteLine();
+
+ PrintNCSDHeader();
+ PrintCardInfoHeader();
+ PrintDevelopmentCardInfoHeader();
+ PrintPartitions();
+ }
+
+ ///
+ /// Print NCSD header information
+ ///
+ 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();
+ }
+
+ ///
+ /// Print card info header information
+ ///
+ 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();
+ }
+
+ ///
+ /// Print development card info header information
+ ///
+ 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();
+ }
+
+ ///
+ /// Print NCCH partition header information
+ ///
+ 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
+ }
+}
\ No newline at end of file