diff --git a/3DSDecrypt/Headers/NCCHHeader.cs b/3DSDecrypt/Headers/NCCHHeader.cs index bb0b35e..55e7829 100644 --- a/3DSDecrypt/Headers/NCCHHeader.cs +++ b/3DSDecrypt/Headers/NCCHHeader.cs @@ -160,14 +160,16 @@ namespace ThreeDS.Headers /// Read from a stream and get an NCCH header, if possible /// /// BinaryReader representing the input stream + /// True if the RSA signature is read, false otherwise /// NCCH header object, null on error - public static NCCHHeader Read(BinaryReader reader) + public static NCCHHeader Read(BinaryReader reader, bool readSignature) { NCCHHeader header = new NCCHHeader(); try { - header.RSA2048Signature = reader.ReadBytes(0x100); + if (readSignature) + header.RSA2048Signature = reader.ReadBytes(0x100); if (new string(reader.ReadChars(4)) != NCCHMagicNumber) return null; diff --git a/3DSDecrypt/Headers/NCSDHeader.cs b/3DSDecrypt/Headers/NCSDHeader.cs index 71dcc74..78a7292 100644 --- a/3DSDecrypt/Headers/NCSDHeader.cs +++ b/3DSDecrypt/Headers/NCSDHeader.cs @@ -40,9 +40,34 @@ namespace ThreeDS.Headers /// public PartitionTableEntry[] PartitionsTable { get; private set; } + /// + /// Partition table entry for Executable Content (CXI) + /// + public PartitionTableEntry ExecutableContent { get { return PartitionsTable[0]; } } + + /// + /// Partition table entry for E-Manual (CFA) + /// + public PartitionTableEntry EManual { get { return PartitionsTable[1]; } } + + /// + /// Partition table entry for Download Play Child container (CFA) + /// + public PartitionTableEntry DownloadPlayChildContainer { get { return PartitionsTable[2]; } } + + /// + /// Partition table entry for New3DS Update Data (CFA) + /// + public PartitionTableEntry New3DSUpdateData { get { return PartitionsTable[6]; } } + + /// + /// Partition table entry for Update Data (CFA) + /// + public PartitionTableEntry UpdateData { get { return PartitionsTable[7]; } } + #endregion - #region For carts + #region CTR Cart Image (CCI) Specific /// /// Exheader SHA-256 hash @@ -62,13 +87,38 @@ namespace ThreeDS.Headers /// /// Partition Flags /// - private byte[] partitionFlags; - public byte BackupWriteWaitTime { get { return partitionFlags[(int)NCSDFlags.BackupWriteWaitTime]; } } - public MediaCardDeviceType MediaCardDevice3X { get { return (MediaCardDeviceType)partitionFlags[(int)NCSDFlags.MediaCardDevice3X]; } } - public MediaPlatformIndex MediaPlatformIndex { get { return (MediaPlatformIndex)partitionFlags[(int)NCSDFlags.MediaPlatformIndex]; } } - public MediaTypeIndex MediaTypeIndex { get { return (MediaTypeIndex)partitionFlags[(int)NCSDFlags.MediaTypeIndex]; } } - public uint SectorSize { get { return (uint)(0x200 * Math.Pow(2, partitionFlags[(int)NCSDFlags.MediaUnitSize])); } } - public MediaCardDeviceType MediaCardDevice2X { get { return (MediaCardDeviceType)partitionFlags[(int)NCSDFlags.MediaCardDevice2X]; } } + public byte[] PartitionFlags { get; private set; } + + /// + /// Backup Write Wait Time (The time to wait to write save to backup after the card is recognized (0-255 + /// seconds)).NATIVE_FIRM loads this flag from the gamecard NCSD header starting with 6.0.0-11. + /// + public byte BackupWriteWaitTime { get { return PartitionFlags[(int)NCSDFlags.BackupWriteWaitTime]; } } + + /// + /// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (SDK 3.X+) + /// + public MediaCardDeviceType MediaCardDevice3X { get { return (MediaCardDeviceType)PartitionFlags[(int)NCSDFlags.MediaCardDevice3X]; } } + + /// + /// Media Platform Index (1 = CTR) + /// + public MediaPlatformIndex MediaPlatformIndex { get { return (MediaPlatformIndex)PartitionFlags[(int)NCSDFlags.MediaPlatformIndex]; } } + + /// + /// Media Type Index (0 = Inner Device, 1 = Card1, 2 = Card2, 3 = Extended Device) + /// + public MediaTypeIndex MediaTypeIndex { get { return (MediaTypeIndex)PartitionFlags[(int)NCSDFlags.MediaTypeIndex]; } } + + /// + /// Media Unit Size i.e. u32 MediaUnitSize = 0x200*2^flags[6]; + /// + public uint MediaUnitSize { get { return (uint)(0x200 * Math.Pow(2, PartitionFlags[(int)NCSDFlags.MediaUnitSize])); } } + + /// + /// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (Only SDK 2.X) + /// + public MediaCardDeviceType MediaCardDevice2X { get { return (MediaCardDeviceType)PartitionFlags[(int)NCSDFlags.MediaCardDevice2X]; } } /// /// Partition ID table @@ -96,11 +146,11 @@ namespace ThreeDS.Headers /// /// Support for this was implemented with 9.6.0-X FIRM, see below regarding save crypto. /// - public byte FIrmUpdateByte2 { get; private set; } + public byte FirmUpdateByte2 { get; private set; } #endregion - #region For NAND + #region Raw NAND Format Specific /// /// Unknown @@ -114,12 +164,95 @@ namespace ThreeDS.Headers #endregion + #region Card Info Header + + /// + /// CARD2: Writable Address In Media Units (For 'On-Chip' Savedata). CARD1: Always 0xFFFFFFFF. + /// + public byte[] CARD2WritableAddressMediaUnits { get; private set; } + + /// + /// Card Info Bitmask + /// + public byte[] CardInfoBytemask { get; private set; } + + /// + /// Reserved1 + /// + public byte[] Reserved3 { get; private set; } + + /// + /// Title version + /// + public ushort TitleVersion { get; private set; } + + /// + /// Card revision + /// + public ushort CardRevision { get; private set; } + + /// + /// Reserved2 + /// + public byte[] Reserved4 { get; private set; } + + /// + /// Card seed keyY (first u64 is Media ID (same as first NCCH partitionId)) + /// + public byte[] CardSeedKeyY { get; private set; } + + /// + /// Encrypted card seed (AES-CCM, keyslot 0x3B for retail cards, see CTRCARD_SECSEED) /// + public byte[] EncryptedCardSeed { get; private set; } + + /// + /// Card seed AES-MAC + /// + public byte[] CardSeedAESMAC { get; private set; } + + /// + /// Card seed nonce + /// + public byte[] CardSeedNonce { get; private set; } + + /// + /// Reserved3 + /// + public byte[] Reserved5 { get; private set; } + + /// + /// Copy of first NCCH header (excluding RSA signature) + /// + public NCCHHeader BackupHeader { get; private set; } + + #endregion + + #region Development Card Info Header Extension + + /// + /// CardDeviceReserved1 + /// + public byte[] CardDeviceReserved1 { get; private set; } + + /// + /// TitleKey + /// + public byte[] TitleKey { get; private set; } + + /// + /// CardDeviceReserved2 + /// + public byte[] CardDeviceReserved2 { get; private set; } + + #endregion + /// /// Read from a stream and get an NCSD header, if possible /// /// BinaryReader representing the input stream + /// True if development cart, false otherwise /// NCSD header object, null on error - public static NCSDHeader Read(BinaryReader reader) + public static NCSDHeader Read(BinaryReader reader, bool development) { NCSDHeader header = new NCSDHeader(); @@ -145,7 +278,7 @@ namespace ThreeDS.Headers header.ExheaderHash = reader.ReadBytes(0x20); header.AdditionalHeaderSize = reader.ReadUInt32(); header.SectorZeroOffset = reader.ReadUInt32(); - header.partitionFlags = reader.ReadBytes(8); + header.PartitionFlags = reader.ReadBytes(8); header.PartitionIdTable = new byte[8][]; for (int i = 0; i < 8; i++) @@ -154,7 +287,27 @@ namespace ThreeDS.Headers header.Reserved1 = reader.ReadBytes(0x20); header.Reserved2 = reader.ReadBytes(0xE); header.FirmUpdateByte1 = reader.ReadByte(); - header.FIrmUpdateByte2 = reader.ReadByte(); + header.FirmUpdateByte2 = reader.ReadByte(); + + header.CARD2WritableAddressMediaUnits = reader.ReadBytes(4); + header.CardInfoBytemask = reader.ReadBytes(4); + header.Reserved3 = reader.ReadBytes(0x108); + header.TitleVersion = reader.ReadUInt16(); + header.CardRevision = reader.ReadUInt16(); + header.Reserved4 = reader.ReadBytes(0xCEE); + header.CardSeedKeyY = reader.ReadBytes(0x10); + header.EncryptedCardSeed = reader.ReadBytes(0x10); + header.CardSeedAESMAC = reader.ReadBytes(0x10); + header.CardSeedNonce = reader.ReadBytes(0x10); + header.Reserved5 = reader.ReadBytes(0xC4); + header.BackupHeader = NCCHHeader.Read(reader, false); + + if (development) + { + header.CardDeviceReserved1 = reader.ReadBytes(0x200); + header.TitleKey = reader.ReadBytes(0x10); + header.CardDeviceReserved2 = reader.ReadBytes(0xF0); + } } else if (header.PartitionsFSType == FilesystemType.FIRM) { diff --git a/3DSDecrypt/ThreeDSTool.cs b/3DSDecrypt/ThreeDSTool.cs index 7351e04..fc7fac8 100644 --- a/3DSDecrypt/ThreeDSTool.cs +++ b/3DSDecrypt/ThreeDSTool.cs @@ -66,7 +66,7 @@ namespace ThreeDS using (BinaryReader f = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) using (BinaryWriter g = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))) { - NCSDHeader header = NCSDHeader.Read(f); + NCSDHeader header = NCSDHeader.Read(f, development); if (header == null) { Console.WriteLine("Error: Not a 3DS Rom!"); @@ -83,9 +83,9 @@ namespace ThreeDS } // Seek to the beginning of the NCCH partition - f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize), SeekOrigin.Begin); + f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.MediaUnitSize), SeekOrigin.Begin); - NCCHHeader partitionHeader = NCCHHeader.Read(f); + NCCHHeader partitionHeader = NCCHHeader.Read(f, true); if (partitionHeader == null) { Console.WriteLine($"Partition {p} Unable to read NCCH header"); @@ -108,12 +108,12 @@ namespace ThreeDS ProcessRomFS(f, g, header, p, partitionHeader, false); // Write the new CryptoMethod - g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18B, SeekOrigin.Begin); + g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.MediaUnitSize) + 0x18B, SeekOrigin.Begin); g.Write((byte)CryptoMethod.Original); g.Flush(); // Write the new BitMasks flag - g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18F, SeekOrigin.Begin); + g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.MediaUnitSize) + 0x18F, SeekOrigin.Begin); BitMasks flag = partitionHeader.Flags.BitMasks; flag = flag & (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF); flag = (flag | BitMasks.NoCrypto); @@ -139,17 +139,13 @@ namespace ThreeDS using (BinaryReader f = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) using (BinaryWriter g = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))) { - NCSDHeader header = NCSDHeader.Read(f); + NCSDHeader header = NCSDHeader.Read(f, development); if (header == null) { Console.WriteLine("Error: Not a 3DS Rom!"); return; } - // Get the backup flags - f.BaseStream.Seek(0x1188, SeekOrigin.Begin); - NCCHHeaderFlags backupFlags = NCCHHeaderFlags.Read(f); - // Iterate over all 8 NCCH partitions for (int p = 0; p < 8; p++) { @@ -160,9 +156,9 @@ namespace ThreeDS } // Seek to the beginning of the NCCH partition - f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize), SeekOrigin.Begin); + f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.MediaUnitSize), SeekOrigin.Begin); - NCCHHeader partitionHeader = NCCHHeader.Read(f); + NCCHHeader partitionHeader = NCCHHeader.Read(f, true); if (partitionHeader == null) { Console.WriteLine($"Partition {p} Unable to read NCCH header"); @@ -177,15 +173,15 @@ namespace ThreeDS } // Determine the Keys to be used - SetEncryptionKeys(partitionHeader.RSA2048Signature, backupFlags.BitMasks, backupFlags.CryptoMethod, p); + SetEncryptionKeys(partitionHeader.RSA2048Signature, header.BackupHeader.Flags.BitMasks, header.BackupHeader.Flags.CryptoMethod, p); // Encrypt each of the pieces if they exist ProcessExtendedHeader(f, g, header, p, partitionHeader, true); ProcessExeFS(f, g, header, p, partitionHeader, true); - ProcessRomFS(f, g, header, p, partitionHeader, true, backupFlags); + ProcessRomFS(f, g, header, p, partitionHeader, true, header.BackupHeader.Flags); // Write the new CryptoMethod - g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18B, SeekOrigin.Begin); + g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.MediaUnitSize) + 0x18B, SeekOrigin.Begin); if (p > 0) { g.Write((byte)CryptoMethod.Original); // For partitions 1 and up, set crypto-method to 0x00 @@ -193,15 +189,15 @@ namespace ThreeDS } else { - g.Write((byte)backupFlags.CryptoMethod); // If partition 0, restore crypto-method from backup flags + g.Write((byte)header.BackupHeader.Flags.CryptoMethod); // If partition 0, restore crypto-method from backup flags g.Flush(); } // Write the new BitMasks flag - g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18F, SeekOrigin.Begin); + g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.MediaUnitSize) + 0x18F, SeekOrigin.Begin); BitMasks flag = partitionHeader.Flags.BitMasks; flag = (flag & ((BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF)); - flag = (flag | (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & backupFlags.BitMasks); + flag = (flag | (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & header.BackupHeader.Flags.BitMasks); g.Write((byte)flag); g.Flush(); } @@ -352,8 +348,8 @@ namespace ThreeDS { if (partitionHeader.ExtendedHeaderSizeInBytes > 0) { - reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin); - writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin); + reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset * header.MediaUnitSize) + 0x200, SeekOrigin.Begin); + writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset * header.MediaUnitSize) + 0x200, SeekOrigin.Begin); Console.WriteLine($"Partition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + ": ExHeader"); @@ -387,7 +383,7 @@ namespace ThreeDS // For all but the original crypto method, process each of the files in the table if (partitionHeader.Flags.CryptoMethod != CryptoMethod.Original) { - reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); + reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin); ExeFSHeader exefsHeader = ExeFSHeader.Read(reader); if (exefsHeader != null) { @@ -399,15 +395,15 @@ namespace ThreeDS uint datalenM = ((fileHeader.FileSize) / (1024 * 1024)); uint datalenB = ((fileHeader.FileSize) % (1024 * 1024)); - uint ctroffset = ((fileHeader.FileOffset + header.SectorSize) / 0x10); + uint ctroffset = ((fileHeader.FileOffset + header.MediaUnitSize) / 0x10); byte[] exefsIVWithOffsetForHeader = AddToByteArray(partitionHeader.ExeFSIV, (int)ctroffset); var firstCipher = CreateAESCipher(NormalKey, exefsIVWithOffsetForHeader, encrypt); var secondCipher = CreateAESCipher(NormalKey2C, exefsIVWithOffsetForHeader, !encrypt); - reader.BaseStream.Seek((((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin); - writer.BaseStream.Seek((((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin); + reader.BaseStream.Seek((((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.MediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin); + writer.BaseStream.Seek((((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.MediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin); if (datalenM > 0) { @@ -435,16 +431,16 @@ namespace ThreeDS ProcessExeFSFilenameTable(reader, writer, header, partitionNumber, partitionHeader, encrypt); // Process the ExeFS - int exefsSizeM = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) / (1024 * 1024); - int exefsSizeB = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) % (1024 * 1024); - int ctroffsetE = (int)(header.SectorSize / 0x10); + int exefsSizeM = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.MediaUnitSize) / (1024 * 1024); + int exefsSizeB = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.MediaUnitSize) % (1024 * 1024); + int ctroffsetE = (int)(header.MediaUnitSize / 0x10); byte[] exefsIVWithOffset = AddToByteArray(partitionHeader.ExeFSIV, ctroffsetE); var exeFS = CreateAESCipher(NormalKey2C, exefsIVWithOffset, encrypt); - reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin); - writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin); + reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.MediaUnitSize, SeekOrigin.Begin); + writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.MediaUnitSize, SeekOrigin.Begin); if (exefsSizeM > 0) { for (int i = 0; i < exefsSizeM; i++) @@ -479,13 +475,13 @@ namespace ThreeDS /// True if we want to encrypt the extended header, false otherwise private void ProcessExeFSFilenameTable(BinaryReader reader, BinaryWriter writer, NCSDHeader header, int partitionNumber, NCCHHeader partitionHeader, bool encrypt) { - reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); - writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); + reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin); + writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin); Console.WriteLine($"Partition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table"); var exeFSFilenameTable = CreateAESCipher(NormalKey2C, partitionHeader.ExeFSIV, encrypt); - writer.Write(exeFSFilenameTable.ProcessBytes(reader.ReadBytes((int)header.SectorSize))); + writer.Write(exeFSFilenameTable.ProcessBytes(reader.ReadBytes((int)header.MediaUnitSize))); writer.Flush(); } @@ -503,8 +499,8 @@ namespace ThreeDS { if (partitionHeader.RomFSOffsetInMediaUnits != 0) { - int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (1024 * 1024); - int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) % (1024 * 1024); + int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.MediaUnitSize) / (1024 * 1024); + int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.MediaUnitSize) % (1024 * 1024); // Encrypting RomFS for partitions 1 and up always use Key0x2C if (encrypt && partitionNumber > 0) @@ -529,8 +525,8 @@ namespace ThreeDS var cipher = CreateAESCipher(NormalKey, partitionHeader.RomFSIV, encrypt); - reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); - writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); + reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin); + writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin); if (romfsSizeM > 0) { for (int i = 0; i < romfsSizeM; i++)