diff --git a/NDecrypt.CMD/Program.cs b/NDecrypt.CMD/Program.cs
index f873acb..63ddfa1 100644
--- a/NDecrypt.CMD/Program.cs
+++ b/NDecrypt.CMD/Program.cs
@@ -192,7 +192,7 @@ More than one path can be specified at a time.");
Console.WriteLine("File recognized as Nintendo 3DS");
return new ThreeDSTool(filename, decryptArgs);
// case FileType.N3DSCIA:
- // Console.WriteLine("File recognized as Nintendo 3DS");
+ // Console.WriteLine("File recognized as Nintendo 3DS CIA");
// return new CIATool(filename, decryptArgs);
case FileType.NULL:
default:
diff --git a/NDecrypt/N3DS/CIATool.cs b/NDecrypt/N3DS/CIATool.cs
index 34752df..308293e 100644
--- a/NDecrypt/N3DS/CIATool.cs
+++ b/NDecrypt/N3DS/CIATool.cs
@@ -5,7 +5,7 @@ using NDecrypt.N3DS.Headers;
namespace NDecrypt.N3DS
{
// https://www.3dbrew.org/wiki/CIA
- internal class CIATool : ITool
+ public class CIATool : ITool
{
///
/// Name of the input CIA file
diff --git a/NDecrypt/N3DS/Enums.cs b/NDecrypt/N3DS/Enums.cs
index 70b869d..93e1db3 100644
--- a/NDecrypt/N3DS/Enums.cs
+++ b/NDecrypt/N3DS/Enums.cs
@@ -186,8 +186,8 @@ namespace NDecrypt.N3DS
internal enum PublicKeyType : uint
{
RSA_4096 = 0x00000000,
- RSA_2048 = 0x00000001,
- ECDSA = 0x00000002,
+ RSA_2048 = 0x01000000,
+ ECDSA = 0x02000000,
}
internal enum ResourceLimitCategory
@@ -198,14 +198,15 @@ namespace NDecrypt.N3DS
OTHER = 3,
}
+ // Note: These are reversed because of how C# reads values
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,
+ RSA_4096_SHA1 = 0x00000100,
+ RSA_2048_SHA1 = 0x01000100,
+ ECDSA_SHA1 = 0x02000100,
+ RSA_4096_SHA256 = 0x03000100,
+ RSA_2048_SHA256 = 0x04000100,
+ ECDSA_SHA256 = 0x05000100,
}
[Flags]
diff --git a/NDecrypt/N3DS/Headers/CIAHeader.cs b/NDecrypt/N3DS/Headers/CIAHeader.cs
index ab1351d..ff6e33b 100644
--- a/NDecrypt/N3DS/Headers/CIAHeader.cs
+++ b/NDecrypt/N3DS/Headers/CIAHeader.cs
@@ -1,3 +1,4 @@
+using System;
using System.IO;
namespace NDecrypt.N3DS.Headers
@@ -8,7 +9,7 @@ namespace NDecrypt.N3DS.Headers
///
/// Archive header size, usually 0x2020 bytes
///
- public uint HeaderSize { get; private set; }
+ public int HeaderSize { get; private set; }
///
/// Type
@@ -45,6 +46,11 @@ namespace NDecrypt.N3DS.Headers
///
public long ContentSize { get; private set; }
+ ///
+ /// Content Index
+ ///
+ public int ContentIndex { get; private set; }
+
#region Content Index
///
@@ -68,10 +74,7 @@ namespace NDecrypt.N3DS.Headers
///
/// Content file data
///
- ///
- /// Probably not going to be read in fully later
- ///
- public byte[] ContentFileData { get; set; }
+ public NCCHHeader ContentFileData { get; set; }
///
/// Meta file data (Not a necessary component)
@@ -91,7 +94,7 @@ namespace NDecrypt.N3DS.Headers
try
{
- header.HeaderSize = reader.ReadUInt32();
+ header.HeaderSize = reader.ReadInt32();
header.Type = reader.ReadUInt16();
header.Version = reader.ReadUInt16();
header.CertificateChainSize = reader.ReadInt32();
@@ -99,16 +102,33 @@ namespace NDecrypt.N3DS.Headers
header.TMDFileSize = reader.ReadInt32();
header.MetaSize = reader.ReadInt32();
header.ContentSize = reader.ReadInt64();
+ header.ContentIndex = reader.ReadInt32();
+ reader.ReadBytes(0x2000); // TODO: Not sure what's in the Content Index area
+ if (reader.BaseStream.Position % 64 != 0)
+ reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current);
header.CertificateChain = new Certificate[3];
header.CertificateChain[0] = Certificate.Read(reader); // CA
header.CertificateChain[1] = Certificate.Read(reader); // Ticket
header.CertificateChain[2] = Certificate.Read(reader); // TMD
+ if (reader.BaseStream.Position % 64 != 0)
+ reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current);
header.Ticket = Ticket.Read(reader, header.TicketSize);
+ if (reader.BaseStream.Position % 64 != 0)
+ reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current);
+
header.TMDFileData = TitleMetadata.Read(reader, header.TMDFileSize);
- header.ContentFileData = reader.ReadBytes((int)header.ContentSize);
-
+ if (reader.BaseStream.Position % 64 != 0)
+ reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current);
+
+ header.ContentFileData = NCCHHeader.Read(reader, readSignature: true);
+ if (header.ContentFileData == null)
+ {
+ Console.WriteLine($"CIA content file data error: Unable to read NCCH header");
+ return null;
+ }
+
if (header.MetaSize > 0)
header.MetaFileData = MetaFile.Read(reader);
diff --git a/NDecrypt/N3DS/Headers/Certificate.cs b/NDecrypt/N3DS/Headers/Certificate.cs
index b9accbb..6e67c84 100644
--- a/NDecrypt/N3DS/Headers/Certificate.cs
+++ b/NDecrypt/N3DS/Headers/Certificate.cs
@@ -1,4 +1,5 @@
using System.IO;
+using System.Text;
namespace NDecrypt.N3DS.Headers
{
@@ -30,6 +31,13 @@ namespace NDecrypt.N3DS.Headers
///
public byte[] Issuer { get; private set; }
+ ///
+ /// Issuer as a trimmed string
+ ///
+ public string IssuerString => Issuer != null && Issuer.Length > 0
+ ? Encoding.ASCII.GetString(Issuer)?.TrimEnd('\0')
+ : null;
+
///
/// Key Type
///
@@ -40,6 +48,13 @@ namespace NDecrypt.N3DS.Headers
///
public byte[] Name { get; private set; }
+ ///
+ /// Name as a trimmed string
+ ///
+ public string NameString => Name != null && Name.Length > 0
+ ? Encoding.ASCII.GetString(Name)?.TrimEnd('\0')
+ : null;
+
///
/// Expiration time as UNIX Timestamp, used at least for CTCert
///
@@ -99,6 +114,8 @@ namespace NDecrypt.N3DS.Headers
ct.SignatureSize = 0x03C;
ct.PaddingSize = 0x40;
break;
+ default:
+ return null;
}
ct.Signature = reader.ReadBytes(ct.SignatureSize);
@@ -116,14 +133,18 @@ namespace NDecrypt.N3DS.Headers
reader.ReadBytes(0x34); // Padding
break;
case PublicKeyType.RSA_2048:
+ case PublicKeyType.RSA_2048_REV:
ct.Modulus = reader.ReadBytes(0x100);
ct.PublicExponent = reader.ReadUInt32();
reader.ReadBytes(0x34); // Padding
break;
case PublicKeyType.ECDSA:
+ case PublicKeyType.ECDSA_REV:
ct.PublicKey = reader.ReadBytes(0x3C);
reader.ReadBytes(0x3C); // Padding
break;
+ default:
+ return null;
}
return ct;
diff --git a/NDecrypt/N3DS/Headers/NCCHHeader.cs b/NDecrypt/N3DS/Headers/NCCHHeader.cs
index b33f629..6644f68 100644
--- a/NDecrypt/N3DS/Headers/NCCHHeader.cs
+++ b/NDecrypt/N3DS/Headers/NCCHHeader.cs
@@ -64,12 +64,12 @@ namespace NDecrypt.N3DS.Headers
///
/// Maker code
///
- public byte[] MakerCode { get; private set; }
+ public ushort MakerCode { get; private set; }
///
/// Version
///
- public byte[] Version { get; private set; }
+ public ushort Version { get; private set; }
///
/// When ncchflag[7] = 0x20 starting with FIRM 9.6.0-X, this is compared with the first output u32 from a
@@ -77,7 +77,7 @@ namespace NDecrypt.N3DS.Headers
/// [programID from NCCH + 0x118]. This hash is only used for verification of the content lock seed, and
/// is not the actual keyY.
///
- public byte[] VerificationHash { get; private set; }
+ public uint VerificationHash { get; private set; }
///
/// Program ID
@@ -211,9 +211,9 @@ namespace NDecrypt.N3DS.Headers
header.ContentSizeInMediaUnits = reader.ReadUInt32();
header.PartitionId = reader.ReadBytes(8).Reverse().ToArray();
- header.MakerCode = reader.ReadBytes(2);
- header.Version = reader.ReadBytes(2);
- header.VerificationHash = reader.ReadBytes(4);
+ header.MakerCode = reader.ReadUInt16();
+ header.Version = reader.ReadUInt16();
+ header.VerificationHash = reader.ReadUInt32();
header.ProgramId = reader.ReadBytes(8);
header.Reserved1 = reader.ReadBytes(0x10);
header.LogoRegionHash = reader.ReadBytes(0x20);
diff --git a/NDecrypt/N3DS/Headers/NCSDHeader.cs b/NDecrypt/N3DS/Headers/NCSDHeader.cs
index 6f262f2..7097eeb 100644
--- a/NDecrypt/N3DS/Headers/NCSDHeader.cs
+++ b/NDecrypt/N3DS/Headers/NCSDHeader.cs
@@ -299,7 +299,7 @@ namespace NDecrypt.N3DS.Headers
header.CardSeedAESMAC = reader.ReadBytes(0x10);
header.CardSeedNonce = reader.ReadBytes(0xC);
header.Reserved5 = reader.ReadBytes(0xC4);
- header.BackupHeader = NCCHHeader.Read(reader, false);
+ header.BackupHeader = NCCHHeader.Read(reader, readSignature: false);
if (development)
{
diff --git a/NDecrypt/N3DS/Headers/Ticket.cs b/NDecrypt/N3DS/Headers/Ticket.cs
index 2af9e6e..08fb501 100644
--- a/NDecrypt/N3DS/Headers/Ticket.cs
+++ b/NDecrypt/N3DS/Headers/Ticket.cs
@@ -1,4 +1,7 @@
+using System;
using System.IO;
+using System.Linq;
+using System.Text;
namespace NDecrypt.N3DS.Headers
{
@@ -30,6 +33,13 @@ namespace NDecrypt.N3DS.Headers
///
public byte[] Issuer { get; private set; }
+ ///
+ /// Issuer as a trimmed string
+ ///
+ public string IssuerString => Issuer != null && Issuer.Length > 0
+ ? Encoding.ASCII.GetString(Issuer)?.TrimEnd('\0')
+ : null;
+
///
/// ECC PublicKey
///
@@ -166,16 +176,6 @@ namespace NDecrypt.N3DS.Headers
///
public int ContentIndexSize { 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; }
-
///
/// Certificate chain
///
@@ -196,6 +196,8 @@ namespace NDecrypt.N3DS.Headers
try
{
+ long startingPosition = reader.BaseStream.Position;
+
tk.SignatureType = (SignatureType)reader.ReadUInt32();
switch (tk.SignatureType)
{
@@ -214,6 +216,8 @@ namespace NDecrypt.N3DS.Headers
tk.SignatureSize = 0x03C;
tk.PaddingSize = 0x40;
break;
+ default:
+ return null;
}
tk.Signature = reader.ReadBytes(tk.SignatureSize);
@@ -245,12 +249,14 @@ namespace NDecrypt.N3DS.Headers
tk.Limits[i] = reader.ReadInt32();
}
- reader.ReadBytes(4);
- tk.ContentIndexSize = reader.ReadInt32();
+ reader.ReadBytes(4); // Seek to size in Content Index
+ tk.ContentIndexSize = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);
reader.BaseStream.Seek(-8, SeekOrigin.Current);
- tk.ContentIndex = reader.ReadBytes(tk.ContentIndexSize);
+ reader.ReadBytes(tk.ContentIndexSize); // TODO: Not sure what's in the Content Index area
+ if (reader.BaseStream.Position % 64 != 0)
+ reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current);
- if (ticketSize - (0x164 + tk.ContentIndexSize) > 0)
+ if (ticketSize > (reader.BaseStream.Position - startingPosition) + (2 * 0x200))
{
tk.CertificateChain = new Certificate[2];
tk.CertificateChain[0] = Certificate.Read(reader); // Ticket
diff --git a/NDecrypt/N3DS/Headers/TitleMetadata.cs b/NDecrypt/N3DS/Headers/TitleMetadata.cs
index f52983b..257e546 100644
--- a/NDecrypt/N3DS/Headers/TitleMetadata.cs
+++ b/NDecrypt/N3DS/Headers/TitleMetadata.cs
@@ -1,4 +1,7 @@
+using System;
using System.IO;
+using System.Linq;
+using System.Text;
namespace NDecrypt.N3DS.Headers
{
@@ -30,6 +33,13 @@ namespace NDecrypt.N3DS.Headers
///
public byte[] SignatureIssuer { get; private set; }
+ ///
+ /// Signature Issuer as a trimmed string
+ ///
+ public string SignatureIssuerString => SignatureIssuer != null && SignatureIssuer.Length > 0
+ ? Encoding.ASCII.GetString(SignatureIssuer)?.TrimEnd('\0')
+ : null;
+
///
/// Version
///
@@ -156,6 +166,8 @@ namespace NDecrypt.N3DS.Headers
try
{
+ long startingPosition = reader.BaseStream.Position;
+
tm.SignatureType = (SignatureType)reader.ReadUInt32();
switch (tm.SignatureType)
{
@@ -194,7 +206,7 @@ namespace NDecrypt.N3DS.Headers
tm.Reserved3 = reader.ReadBytes(0x31);
tm.AccessRights = reader.ReadUInt32();
tm.TitleVersion = reader.ReadUInt16();
- tm.ContentCount = reader.ReadUInt16();
+ tm.ContentCount = BitConverter.ToUInt16(reader.ReadBytes(2).Reverse().ToArray(), 0);
tm.BootContent = reader.ReadUInt16();
tm.Padding = reader.ReadUInt16();
tm.SHA256HashContentInfoRecords = reader.ReadBytes(0x20);
@@ -211,7 +223,7 @@ namespace NDecrypt.N3DS.Headers
tm.ContentChunkRecords[i] = ContentChunkRecord.Read(reader);
}
- if (metadataSize - (0xC4 + (64 * 0x24) + (tm.ContentCount * 0x30)) > 0)
+ if (metadataSize > (reader.BaseStream.Position - startingPosition) + (2 * 0x200))
{
tm.CertificateChain = new Certificate[2];
tm.CertificateChain[0] = Certificate.Read(reader); // TMD
diff --git a/NDecrypt/N3DS/ThreeDSTool.cs b/NDecrypt/N3DS/ThreeDSTool.cs
index 4cb27a2..d031d78 100644
--- a/NDecrypt/N3DS/ThreeDSTool.cs
+++ b/NDecrypt/N3DS/ThreeDSTool.cs
@@ -103,7 +103,7 @@ namespace NDecrypt.N3DS
// Seek to the beginning of the NCCH partition
reader.BaseStream.Seek((ncsdHeader.PartitionsTable[partitionNumber].Offset * ncsdHeader.MediaUnitSize), SeekOrigin.Begin);
- NCCHHeader partitionHeader = NCCHHeader.Read(reader, true);
+ NCCHHeader partitionHeader = NCCHHeader.Read(reader, readSignature: true);
if (partitionHeader == null)
{
Console.WriteLine($"Partition {partitionNumber} Unable to read NCCH header");