diff --git a/NDecrypt/N3DS/Enums.cs b/NDecrypt/N3DS/Enums.cs
index 2911ea5..70b869d 100644
--- a/NDecrypt/N3DS/Enums.cs
+++ b/NDecrypt/N3DS/Enums.cs
@@ -66,6 +66,24 @@ namespace NDecrypt.N3DS
NewKeyYGenerator = 0x20,
}
+ internal enum ContentIndex : ushort
+ {
+ ///
+ /// Main Content (.CXI for 3DS executable content/.CFA for 3DS Data Archives/.SRL for TWL content)
+ ///
+ MainContent = 0x0000,
+
+ ///
+ /// Home Menu Manual (.CFA)
+ ///
+ HomeMenuManual = 0x0001,
+
+ ///
+ /// DLP Child Container (.CFA)
+ ///
+ DLPChildContainer = 0x0002,
+ }
+
internal enum ContentPlatform : byte
{
CTR = 0x01,
@@ -165,6 +183,13 @@ namespace NDecrypt.N3DS
MediaCardDevice2X = 0x07,
}
+ internal enum PublicKeyType : uint
+ {
+ RSA_4096 = 0x00000000,
+ RSA_2048 = 0x00000001,
+ ECDSA = 0x00000002,
+ }
+
internal enum ResourceLimitCategory
{
APPLICATION = 0,
@@ -173,10 +198,30 @@ namespace NDecrypt.N3DS
OTHER = 3,
}
+ internal enum SignatureType : uint
+ {
+ RSA_4096_SHA1 = 0x010000,
+ RSA_2048_SHA1 = 0x010001,
+ ECDSA_SHA1 = 0x010002,
+ RSA_4096_SHA256 = 0x010003,
+ RSA_2048_SHA256 = 0x010004,
+ ECDSA_SHA256 = 0x010005,
+ }
+
[Flags]
internal enum StorageInfoOtherAttributes : byte
{
NotUseROMFS = 0x01,
UseExtendedSavedataAccess = 0x02,
}
+
+ [Flags]
+ internal enum TMDContentType : ushort
+ {
+ Encrypted = 0x0001,
+ Disc = 0x0002,
+ CFM = 0x0004,
+ Optional = 0x4000,
+ Shared = 0x8000,
+ }
}
diff --git a/NDecrypt/N3DS/Headers/CIAHeader.cs b/NDecrypt/N3DS/Headers/CIAHeader.cs
index 0a95c65..3a304c4 100644
--- a/NDecrypt/N3DS/Headers/CIAHeader.cs
+++ b/NDecrypt/N3DS/Headers/CIAHeader.cs
@@ -2,6 +2,7 @@ using System.IO;
namespace NDecrypt.N3DS.Headers
{
+ // https://www.3dbrew.org/wiki/CIA
internal class CIAHeader
{
///
@@ -44,35 +45,25 @@ namespace NDecrypt.N3DS.Headers
///
public long ContentSize { get; private set; }
- ///
- /// Content Index
- ///
- public byte[] ContentIndex { get; private set; }
+ #region Content Index
///
/// Certificate chain
///
///
- /// https://www.3dbrew.org/wiki/Certificates
- /// https://www.3dbrew.org/wiki/Ticket#Certificate_Chain
+ /// https://www.3dbrew.org/wiki/CIA#Certificate_Chain
///
- public byte[] CertificateChain { get; set; }
+ public Certificate[] CertificateChain { get; set; }
///
/// Ticket
///
- ///
- /// https://www.3dbrew.org/wiki/Ticket#Structure
- ///
- public byte[] Ticket { get; set; }
+ public Ticket Ticket { get; set; }
///
/// TMD file data
///
- ///
- /// https://www.3dbrew.org/wiki/Title_metadata
- ///
- public byte[] TMDFileData { get; set; }
+ public TitleMetadata TMDFileData { get; set; }
///
/// Content file data
@@ -87,6 +78,8 @@ namespace NDecrypt.N3DS.Headers
///
public MetaFile MetaFileData { get; set; }
+ #endregion
+
///
/// Read from a stream and get a CIA header, if possible
///
@@ -106,11 +99,15 @@ namespace NDecrypt.N3DS.Headers
header.TMDFileSize = reader.ReadInt32();
header.MetaSize = reader.ReadInt32();
header.ContentSize = reader.ReadInt64();
- header.ContentIndex = reader.ReadBytes(0x2000);
- header.CertificateChain = reader.ReadBytes(header.CertificateChainSize);
- header.Ticket = reader.ReadBytes(header.TicketSize);
- header.TMDFileData = reader.ReadBytes(header.TMDFileSize);
+ header.CertificateChain = new Certificate[3];
+ for (int i = 0; i < 3; i++)
+ {
+ header.CertificateChain[i] = Certificate.Read(reader);
+ }
+
+ header.Ticket = Ticket.Read(reader, header.TicketSize);
+ header.TMDFileData = TitleMetadata.Read(reader, header.TMDFileSize);
header.ContentFileData = reader.ReadBytes((int)header.ContentSize);
if (header.MetaSize > 0)
diff --git a/NDecrypt/N3DS/Headers/Certificate.cs b/NDecrypt/N3DS/Headers/Certificate.cs
new file mode 100644
index 0000000..b9accbb
--- /dev/null
+++ b/NDecrypt/N3DS/Headers/Certificate.cs
@@ -0,0 +1,137 @@
+using System.IO;
+
+namespace NDecrypt.N3DS.Headers
+{
+ // https://www.3dbrew.org/wiki/Certificates
+ internal class Certificate
+ {
+ ///
+ /// Signature Type
+ ///
+ public SignatureType SignatureType { get; private set; }
+
+ ///
+ /// Signature size
+ ///
+ public ushort SignatureSize { get; private set; }
+
+ ///
+ /// Padding size
+ ///
+ public byte PaddingSize { get; private set; }
+
+ ///
+ /// Signature
+ ///
+ public byte[] Signature { get; private set; }
+
+ ///
+ /// Issuer
+ ///
+ public byte[] Issuer { get; private set; }
+
+ ///
+ /// Key Type
+ ///
+ public PublicKeyType KeyType { get; private set; }
+
+ ///
+ /// Name
+ ///
+ public byte[] Name { get; private set; }
+
+ ///
+ /// Expiration time as UNIX Timestamp, used at least for CTCert
+ ///
+ public uint ExpirationTime { get; private set; }
+
+ // This contains the Public Key(i.e. Modulus & Public Exponent)
+ #region RSA
+
+ ///
+ /// Modulus
+ ///
+ public byte[] Modulus { get; private set; }
+
+ ///
+ /// Public Exponent
+ ///
+ public uint PublicExponent { get; private set; }
+
+ #endregion
+
+ // This contains the ECC public key, and is as follows:
+ #region ECC
+
+ ///
+ /// Public Key
+ ///
+ public byte[] PublicKey { get; private set; }
+
+ #endregion
+
+ ///
+ /// Read from a stream and get certificate, if possible
+ ///
+ /// BinaryReader representing the input stream
+ /// Certificate object, null on error
+ public static Certificate Read(BinaryReader reader)
+ {
+ Certificate ct = new Certificate();
+
+ try
+ {
+ ct.SignatureType = (SignatureType)reader.ReadUInt32();
+ switch (ct.SignatureType)
+ {
+ case SignatureType.RSA_4096_SHA1:
+ case SignatureType.RSA_4096_SHA256:
+ ct.SignatureSize = 0x200;
+ ct.PaddingSize = 0x3C;
+ break;
+ case SignatureType.RSA_2048_SHA1:
+ case SignatureType.RSA_2048_SHA256:
+ ct.SignatureSize = 0x100;
+ ct.PaddingSize = 0x3C;
+ break;
+ case SignatureType.ECDSA_SHA1:
+ case SignatureType.ECDSA_SHA256:
+ ct.SignatureSize = 0x03C;
+ ct.PaddingSize = 0x40;
+ break;
+ }
+
+ ct.Signature = reader.ReadBytes(ct.SignatureSize);
+ reader.ReadBytes(ct.PaddingSize); // Padding
+ ct.Issuer = reader.ReadBytes(0x40);
+ ct.KeyType = (PublicKeyType)reader.ReadUInt32();
+ ct.Name = reader.ReadBytes(0x40);
+ ct.ExpirationTime = reader.ReadUInt32();
+
+ switch (ct.KeyType)
+ {
+ case PublicKeyType.RSA_4096:
+ ct.Modulus = reader.ReadBytes(0x200);
+ ct.PublicExponent = reader.ReadUInt32();
+ reader.ReadBytes(0x34); // Padding
+ break;
+ case PublicKeyType.RSA_2048:
+ ct.Modulus = reader.ReadBytes(0x100);
+ ct.PublicExponent = reader.ReadUInt32();
+ reader.ReadBytes(0x34); // Padding
+ break;
+ case PublicKeyType.ECDSA:
+ ct.PublicKey = reader.ReadBytes(0x3C);
+ reader.ReadBytes(0x3C); // Padding
+ break;
+ }
+
+ return ct;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ }
+}
diff --git a/NDecrypt/N3DS/Headers/ContentChunkRecord.cs b/NDecrypt/N3DS/Headers/ContentChunkRecord.cs
new file mode 100644
index 0000000..f8356cb
--- /dev/null
+++ b/NDecrypt/N3DS/Headers/ContentChunkRecord.cs
@@ -0,0 +1,59 @@
+using System.IO;
+
+namespace NDecrypt.N3DS.Headers
+{
+ internal class ContentChunkRecord
+ {
+ ///
+ /// Content id
+ ///
+ public uint ContentId { get; private set; }
+
+ ///
+ /// Content index
+ ///
+ ///
+ /// This does not apply to DLC.
+ ///
+ public ContentIndex ContentIndex { get; private set; }
+
+ ///
+ /// Content type
+ ///
+ public TMDContentType ContentType { get; private set; }
+
+ ///
+ /// Content size
+ ///
+ public ulong ContentSize { get; private set; }
+
+ ///
+ /// SHA-256 hash
+ ///
+ public byte[] SHA256Hash { get; private set; }
+
+ ///
+ /// Read from a stream and get content chunk record, if possible
+ ///
+ /// BinaryReader representing the input stream
+ /// Content chunk record object, null on error
+ public static ContentChunkRecord Read(BinaryReader reader)
+ {
+ ContentChunkRecord ccr = new ContentChunkRecord();
+
+ try
+ {
+ ccr.ContentId = reader.ReadUInt32();
+ ccr.ContentIndex = (ContentIndex)reader.ReadUInt16();
+ ccr.ContentType = (TMDContentType)reader.ReadUInt16();
+ ccr.ContentSize = reader.ReadUInt64();
+ ccr.SHA256Hash = reader.ReadBytes(0x20);
+ return ccr;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/NDecrypt/N3DS/Headers/ContentInfoRecord.cs b/NDecrypt/N3DS/Headers/ContentInfoRecord.cs
new file mode 100644
index 0000000..617703c
--- /dev/null
+++ b/NDecrypt/N3DS/Headers/ContentInfoRecord.cs
@@ -0,0 +1,44 @@
+using System.IO;
+
+namespace NDecrypt.N3DS.Headers
+{
+ internal class ContentInfoRecord
+ {
+ ///
+ /// Content index offset
+ ///
+ public ushort ContentIndexOffset { get; private set; }
+
+ ///
+ /// Content command count [k]
+ ///
+ public ushort ContentCommandCount { get; private set; }
+
+ ///
+ /// SHA-256 hash of the next k content records that have not been hashed yet
+ ///
+ public byte[] UnhashedContentRecordsSHA256Hash { get; private set; }
+
+ ///
+ /// Read from a stream and get content info record, if possible
+ ///
+ /// BinaryReader representing the input stream
+ /// Content info record object, null on error
+ public static ContentInfoRecord Read(BinaryReader reader)
+ {
+ ContentInfoRecord cir = new ContentInfoRecord();
+
+ try
+ {
+ cir.ContentIndexOffset = reader.ReadUInt16();
+ cir.ContentCommandCount = reader.ReadUInt16();
+ cir.UnhashedContentRecordsSHA256Hash = reader.ReadBytes(0x20);
+ return cir;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/NDecrypt/N3DS/Headers/Ticket.cs b/NDecrypt/N3DS/Headers/Ticket.cs
new file mode 100644
index 0000000..f9d9bd1
--- /dev/null
+++ b/NDecrypt/N3DS/Headers/Ticket.cs
@@ -0,0 +1,229 @@
+using System.IO;
+
+namespace NDecrypt.N3DS.Headers
+{
+ // https://www.3dbrew.org/wiki/Ticket
+ internal class Ticket
+ {
+ ///
+ /// Signature Type
+ ///
+ public SignatureType SignatureType { get; private set; }
+
+ ///
+ /// Signature size
+ ///
+ public ushort SignatureSize { get; private set; }
+
+ ///
+ /// Padding size
+ ///
+ public byte PaddingSize { get; private set; }
+
+ ///
+ /// Signature
+ ///
+ public byte[] Signature { get; private set; }
+
+ ///
+ /// Issuer
+ ///
+ public byte[] Issuer { get; private set; }
+
+ ///
+ /// ECC PublicKey
+ ///
+ public byte[] ECCPublicKey { get; private set; }
+
+ ///
+ /// Version (For 3DS this is always 1)
+ ///
+ public byte Version { get; private set; }
+
+ ///
+ /// CaCrlVersion
+ ///
+ public byte CaCrlVersion { get; private set; }
+
+ ///
+ /// SignerCrlVersion
+ ///
+ public byte SignerCrlVersion { get; private set; }
+
+ ///
+ /// TitleKey (normal-key encrypted using one of the common keyYs; see below)
+ ///
+ ///
+ /// The titlekey is decrypted by using the AES engine with the ticket common-key keyslot.
+ /// The keyY is selected through an index (ticket offset 0xB1) into a plaintext array
+ /// of 6 keys ("common keyYs") stored in the data section of Process9. AES-CBC mode is used
+ /// where the IV is the big-endian titleID. Note that on a retail unit index0 is a retail keyY,
+ /// while on a dev-unit index0 is the dev common-key which is a normal-key.
+ /// (On retail for these keyYs, the hardware key-scrambler is used)
+ ///
+ /// The titlekey is used to decrypt content downloaded from the CDN using 128-bit AES-CBC with
+ /// the content index (as big endian u16, padded with trailing zeroes) as the IV.
+ ///
+ public byte[] TitleKey { get; private set; }
+
+ ///
+ /// Reserved
+ ///
+ public byte Reserved1 { get; private set; }
+
+ ///
+ /// TicketID
+ ///
+ public ulong TicketID { get; private set; }
+
+ ///
+ /// ConsoleID
+ ///
+ public uint ConsoleID { get; private set; }
+
+ ///
+ /// TitleID
+ ///
+ public ulong TitleID { get; private set; }
+
+ ///
+ /// Reserved
+ ///
+ public ushort Reserved2 { get; private set; }
+
+ ///
+ /// Ticket title version
+ ///
+ ///
+ /// The Ticket Title Version is generally the same as the title version stored in the
+ /// Title Metadata. Although it doesn't have to match the TMD version to be valid.
+ ///
+ public ushort TicketTitleVersion { get; private set; }
+
+ ///
+ /// Reserved
+ ///
+ public ulong Reserved3 { get; private set; }
+
+ ///
+ /// License Type
+ ///
+ public byte LicenseType { get; private set; }
+
+ ///
+ /// Index to the common keyY used for this ticket, usually 0x1 for retail system titles;
+ /// see below.
+ ///
+ public byte CommonKeyYIndex { get; private set; }
+
+ ///
+ /// Reserved
+ ///
+ public byte[] Reserved4 { get; private set; }
+
+ ///
+ /// eShop Account ID?
+ ///
+ public uint eShopAccountID { get; private set; }
+
+ ///
+ /// Reserved
+ ///
+ public byte Reserved5 { get; private set; }
+
+ ///
+ /// Audit
+ ///
+ public byte Audit { get; private set; }
+
+ ///
+ /// Reserved
+ ///
+ public byte[] Reserved6 { get; private set; }
+
+ ///
+ /// Limits
+ ///
+ ///
+ /// In demos, the first u32 in the "Limits" section is 0x4, then the second u32 is the max-playcount.
+ ///
+ public byte[] Limits { get; private set; }
+
+ ///
+ /// Content Index
+ ///
+ ///
+ /// The Content Index of a ticket has its own size defined within itself,
+ /// with seemingly a minimal of 20 bytes, the second u32 in big endian defines
+ /// the full value of X.
+ ///
+ public byte[] ContentIndex { get; private set; }
+
+ ///
+ /// Read from a stream and get ticket, if possible
+ ///
+ /// BinaryReader representing the input stream
+ /// Ticket size from the header
+ /// Ticket object, null on error
+ public static Ticket Read(BinaryReader reader, int ticketSize)
+ {
+ Ticket tk = new Ticket();
+
+ try
+ {
+ tk.SignatureType = (SignatureType)reader.ReadUInt32();
+ switch (tk.SignatureType)
+ {
+ case SignatureType.RSA_4096_SHA1:
+ case SignatureType.RSA_4096_SHA256:
+ tk.SignatureSize = 0x200;
+ tk.PaddingSize = 0x3C;
+ break;
+ case SignatureType.RSA_2048_SHA1:
+ case SignatureType.RSA_2048_SHA256:
+ tk.SignatureSize = 0x100;
+ tk.PaddingSize = 0x3C;
+ break;
+ case SignatureType.ECDSA_SHA1:
+ case SignatureType.ECDSA_SHA256:
+ tk.SignatureSize = 0x03C;
+ tk.PaddingSize = 0x40;
+ break;
+ }
+
+ tk.Signature = reader.ReadBytes(tk.SignatureSize);
+ reader.ReadBytes(tk.PaddingSize); // Padding
+ tk.Issuer = reader.ReadBytes(0x40);
+ tk.ECCPublicKey = reader.ReadBytes(0x3C);
+ tk.Version = reader.ReadByte();
+ tk.CaCrlVersion = reader.ReadByte();
+ tk.SignerCrlVersion = reader.ReadByte();
+ tk.TitleKey = reader.ReadBytes(0x10);
+ tk.Reserved1 = reader.ReadByte();
+ tk.TicketID = reader.ReadUInt64();
+ tk.ConsoleID = reader.ReadUInt32();
+ tk.TitleID = reader.ReadUInt64();
+ tk.Reserved2 = reader.ReadUInt16();
+ tk.TicketTitleVersion = reader.ReadUInt16();
+ tk.Reserved3 = reader.ReadUInt64();
+ tk.LicenseType = reader.ReadByte();
+ tk.CommonKeyYIndex = reader.ReadByte();
+ tk.Reserved4 = reader.ReadBytes(0x2A);
+ tk.eShopAccountID = reader.ReadUInt32();
+ tk.Reserved5 = reader.ReadByte();
+ tk.Audit = reader.ReadByte();
+ tk.Reserved6 = reader.ReadBytes(0x42);
+ tk.Limits = reader.ReadBytes(0x40);
+
+ if (ticketSize - 0x164 > 0)
+ tk.ContentIndex = reader.ReadBytes(ticketSize - 0x164);
+
+ return tk;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ }
+}
diff --git a/NDecrypt/N3DS/Headers/TitleMetadata.cs b/NDecrypt/N3DS/Headers/TitleMetadata.cs
new file mode 100644
index 0000000..5162b01
--- /dev/null
+++ b/NDecrypt/N3DS/Headers/TitleMetadata.cs
@@ -0,0 +1,214 @@
+using System.IO;
+
+namespace NDecrypt.N3DS.Headers
+{
+ // https://www.3dbrew.org/wiki/Title_metadata
+ internal class TitleMetadata
+ {
+ ///
+ /// Signature Type
+ ///
+ public SignatureType SignatureType { get; private set; }
+
+ ///
+ /// Signature size
+ ///
+ public ushort SignatureSize { get; private set; }
+
+ ///
+ /// Padding size
+ ///
+ public byte PaddingSize { get; private set; }
+
+ ///
+ /// Signature
+ ///
+ public byte[] Signature { get; private set; }
+
+ ///
+ /// Signature Issuer
+ ///
+ public byte[] SignatureIssuer { get; private set; }
+
+ ///
+ /// Version
+ ///
+ public byte Version { get; private set; }
+
+ ///
+ /// CaCrlVersion
+ ///
+ public byte CaCrlVersion { get; private set; }
+
+ ///
+ /// SignerCrlVersion
+ ///
+ public byte SignerCrlVersion { get; private set; }
+
+ ///
+ /// Reserved
+ ///
+ public byte Reserved1 { get; private set; }
+
+ ///
+ /// System Version
+ ///
+ public ulong SystemVersion { get; private set; }
+
+ ///
+ /// TitleID
+ ///
+ public ulong TitleID { get; private set; }
+
+ ///
+ /// Title Type
+ ///
+ public uint TitleType { get; private set; }
+
+ ///
+ /// Group ID
+ ///
+ public ushort GroupID { get; private set; }
+
+ ///
+ /// Save Data Size in Little Endian (Bytes) (Also SRL Public Save Data Size)
+ ///
+ public uint SaveDataSize { get; private set; }
+
+ ///
+ /// SRL Private Save Data Size in Little Endian (Bytes)
+ ///
+ public uint SRLPrivateSaveDataSize { get; private set; }
+
+ ///
+ /// Reserved
+ ///
+ public uint Reserved2 { get; private set; }
+
+ ///
+ /// SRL Flag
+ ///
+ public byte SRLFlag { get; private set; }
+
+ ///
+ /// Reserved
+ ///
+ public byte[] Reserved3 { get; private set; }
+
+ ///
+ /// Access Rights
+ ///
+ public uint AccessRights { get; private set; }
+
+ ///
+ /// Title Version
+ ///
+ public ushort TitleVersion { get; private set; }
+
+ ///
+ /// Content Count
+ ///
+ public ushort ContentCount { get; private set; }
+
+ ///
+ /// Boot Content
+ ///
+ public ushort BootContent { get; private set; }
+
+ ///
+ /// Padding
+ ///
+ public ushort Padding { get; private set; }
+
+ ///
+ /// SHA-256 Hash of the Content Info Records
+ ///
+ public byte[] SHA256HashContentInfoRecords { get; private set; }
+
+ ///
+ /// There are 64 of these records, usually only the first is used.
+ ///
+ public ContentInfoRecord[] ContentInfoRecords { get; private set; }
+
+ ///
+ /// There is one of these for each content contained in this title.
+ /// (Determined by "Content Count" in the TMD Header).
+ ///
+ public ContentChunkRecord[] ContentChunkRecords { get; private set; }
+
+ ///
+ /// Read from a stream and get ticket metadata, if possible
+ ///
+ /// BinaryReader representing the input stream
+ /// Metadata size from the header
+ /// Title metadata object, null on error
+ public static TitleMetadata Read(BinaryReader reader, int metadataSize)
+ {
+ TitleMetadata tm = new TitleMetadata();
+
+ try
+ {
+ tm.SignatureType = (SignatureType)reader.ReadUInt32();
+ switch (tm.SignatureType)
+ {
+ case SignatureType.RSA_4096_SHA1:
+ case SignatureType.RSA_4096_SHA256:
+ tm.SignatureSize = 0x200;
+ tm.PaddingSize = 0x3C;
+ break;
+ case SignatureType.RSA_2048_SHA1:
+ case SignatureType.RSA_2048_SHA256:
+ tm.SignatureSize = 0x100;
+ tm.PaddingSize = 0x3C;
+ break;
+ case SignatureType.ECDSA_SHA1:
+ case SignatureType.ECDSA_SHA256:
+ tm.SignatureSize = 0x03C;
+ tm.PaddingSize = 0x40;
+ break;
+ }
+
+ tm.Signature = reader.ReadBytes(tm.SignatureSize);
+ reader.ReadBytes(tm.PaddingSize); // Padding
+ tm.SignatureIssuer = reader.ReadBytes(0x40);
+ tm.Version = reader.ReadByte();
+ tm.CaCrlVersion = reader.ReadByte();
+ tm.SignerCrlVersion = reader.ReadByte();
+ tm.Reserved1 = reader.ReadByte();
+ tm.SystemVersion = reader.ReadUInt64();
+ tm.TitleID = reader.ReadUInt64();
+ tm.TitleType = reader.ReadUInt32();
+ tm.GroupID = reader.ReadUInt16();
+ tm.SaveDataSize = reader.ReadUInt32();
+ tm.SRLPrivateSaveDataSize = reader.ReadUInt32();
+ tm.Reserved2 = reader.ReadUInt32();
+ tm.SRLFlag = reader.ReadByte();
+ tm.Reserved3 = reader.ReadBytes(0x31);
+ tm.AccessRights = reader.ReadUInt32();
+ tm.TitleVersion = reader.ReadUInt16();
+ tm.ContentCount = reader.ReadUInt16();
+ tm.BootContent = reader.ReadUInt16();
+ tm.Padding = reader.ReadUInt16();
+ tm.SHA256HashContentInfoRecords = reader.ReadBytes(0x20);
+
+ tm.ContentInfoRecords = new ContentInfoRecord[64];
+ for (int i = 0; i < 64; i++)
+ {
+ tm.ContentInfoRecords[i] = ContentInfoRecord.Read(reader);
+ }
+
+ tm.ContentChunkRecords = new ContentChunkRecord[tm.ContentCount];
+ for (int i = 0; i < tm.ContentCount; i++)
+ {
+ tm.ContentChunkRecords[i] = ContentChunkRecord.Read(reader);
+ }
+
+ return tm;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ }
+}
\ No newline at end of file