From 179b53f462c5a748bc10ccd083e3a882de297307 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 8 Aug 2024 11:53:47 -0400 Subject: [PATCH] Migrate 3DS to using standard models --- NDecrypt.N3DS/CIATool.cs | 295 +++-- NDecrypt.N3DS/Enums.cs | 209 ---- NDecrypt.N3DS/Extensions.cs | 262 +++++ .../Headers/ARM11KernelCapabilities.cs | 65 - .../Headers/ARM11LocalSystemCapabilities.cs | 109 -- NDecrypt.N3DS/Headers/ARM9AccessControl.cs | 40 - NDecrypt.N3DS/Headers/AccessControlInfo.cs | 44 - NDecrypt.N3DS/Headers/CIAHeader.cs | 149 --- NDecrypt.N3DS/Headers/CXIExtendedHeader.cs | 56 - NDecrypt.N3DS/Headers/Certificate.cs | 156 --- NDecrypt.N3DS/Headers/CodeSetInfo.cs | 44 - NDecrypt.N3DS/Headers/ContentChunkRecord.cs | 59 - NDecrypt.N3DS/Headers/ContentInfoRecord.cs | 44 - NDecrypt.N3DS/Headers/ExeFSFileHeader.cs | 56 - NDecrypt.N3DS/Headers/ExeFSHeader.cs | 45 - NDecrypt.N3DS/Headers/MetaFile.cs | 57 - NDecrypt.N3DS/Headers/NCCHHeader.cs | 248 ---- NDecrypt.N3DS/Headers/NCCHHeaderFlags.cs | 103 -- NDecrypt.N3DS/Headers/NCSDHeader.cs | 326 ----- NDecrypt.N3DS/Headers/PartitionTableEntry.cs | 47 - NDecrypt.N3DS/Headers/RomFSHeader.cs | 127 -- NDecrypt.N3DS/Headers/StorageInfo.cs | 56 - NDecrypt.N3DS/Headers/SystemControlInfo.cs | 102 -- NDecrypt.N3DS/Headers/SystemInfo.cs | 44 - NDecrypt.N3DS/Headers/Ticket.cs | 279 ----- NDecrypt.N3DS/Headers/TitleMetadata.cs | 241 ---- NDecrypt.N3DS/NDecrypt.N3DS.csproj | 4 + NDecrypt.N3DS/Serializer.cs | 1048 +++++++++++++++++ NDecrypt.N3DS/ThreeDSTool.cs | 375 ++++-- 29 files changed, 1753 insertions(+), 2937 deletions(-) create mode 100644 NDecrypt.N3DS/Extensions.cs delete mode 100644 NDecrypt.N3DS/Headers/ARM11KernelCapabilities.cs delete mode 100644 NDecrypt.N3DS/Headers/ARM11LocalSystemCapabilities.cs delete mode 100644 NDecrypt.N3DS/Headers/ARM9AccessControl.cs delete mode 100644 NDecrypt.N3DS/Headers/AccessControlInfo.cs delete mode 100644 NDecrypt.N3DS/Headers/CIAHeader.cs delete mode 100644 NDecrypt.N3DS/Headers/CXIExtendedHeader.cs delete mode 100644 NDecrypt.N3DS/Headers/Certificate.cs delete mode 100644 NDecrypt.N3DS/Headers/CodeSetInfo.cs delete mode 100644 NDecrypt.N3DS/Headers/ContentChunkRecord.cs delete mode 100644 NDecrypt.N3DS/Headers/ContentInfoRecord.cs delete mode 100644 NDecrypt.N3DS/Headers/ExeFSFileHeader.cs delete mode 100644 NDecrypt.N3DS/Headers/ExeFSHeader.cs delete mode 100644 NDecrypt.N3DS/Headers/MetaFile.cs delete mode 100644 NDecrypt.N3DS/Headers/NCCHHeader.cs delete mode 100644 NDecrypt.N3DS/Headers/NCCHHeaderFlags.cs delete mode 100644 NDecrypt.N3DS/Headers/NCSDHeader.cs delete mode 100644 NDecrypt.N3DS/Headers/PartitionTableEntry.cs delete mode 100644 NDecrypt.N3DS/Headers/RomFSHeader.cs delete mode 100644 NDecrypt.N3DS/Headers/StorageInfo.cs delete mode 100644 NDecrypt.N3DS/Headers/SystemControlInfo.cs delete mode 100644 NDecrypt.N3DS/Headers/SystemInfo.cs delete mode 100644 NDecrypt.N3DS/Headers/Ticket.cs delete mode 100644 NDecrypt.N3DS/Headers/TitleMetadata.cs create mode 100644 NDecrypt.N3DS/Serializer.cs diff --git a/NDecrypt.N3DS/CIATool.cs b/NDecrypt.N3DS/CIATool.cs index 93390c9..0350fa9 100644 --- a/NDecrypt.N3DS/CIATool.cs +++ b/NDecrypt.N3DS/CIATool.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Numerics; using NDecrypt.Core; -using NDecrypt.N3DS.Headers; +using SabreTools.Models.N3DS; using static NDecrypt.Core.Helper; namespace NDecrypt.N3DS @@ -21,6 +21,31 @@ public class CIATool : ITool /// private readonly DecryptArgs decryptArgs; + /// + /// Set of all KeyX values + /// + private readonly BigInteger[] KeyX = new BigInteger[8]; + + /// + /// Set of all KeyX2C values + /// + private readonly BigInteger[] KeyX2C = new BigInteger[8]; + + /// + /// Set of all KeyY values + /// + private readonly BigInteger[] KeyY = new BigInteger[8]; + + /// + /// Set of all KeyY values + /// + private readonly BigInteger[] NormalKey = new BigInteger[8]; + + /// + /// Set of all KeyY values + /// + private readonly BigInteger[] NormalKey2C = new BigInteger[8]; + public CIATool(string filename, DecryptArgs decryptArgs) { this.filename = filename; @@ -47,15 +72,15 @@ public bool ProcessFile() using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))) { - CIAHeader? header = CIAHeader.Read(reader); - if (header == null) + var cia = Serializer.ReadCIAHeader(reader); + if (cia == null) { Console.WriteLine("Error: Not a 3DS CIA!"); return false; } // Process all NCCH partitions - ProcessAllPartitions(header, reader, writer); + ProcessAllPartitions(cia, reader, writer); } return false; @@ -71,81 +96,90 @@ public bool ProcessFile() /// /// Process all partitions in the content file data of a CIA header /// - /// CIA header representing the 3DS CIA file + /// CIA representing the 3DS CIA file /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void ProcessAllPartitions(CIAHeader ciaHeader, BinaryReader reader, BinaryWriter writer) + private void ProcessAllPartitions(CIA cia, BinaryReader reader, BinaryWriter writer) { // Iterate over all NCCH partitions - for (int p = 0; p < ciaHeader.Partitions!.Length; p++) + for (int p = 0; p < cia.Partitions!.Length; p++) { - NCCHHeader ncchHeader = ciaHeader.Partitions[0]; - ProcessPartition(ciaHeader, ncchHeader, reader, writer); + var ncchHeader = cia.Partitions[0]; + if (ncchHeader == null) + continue; + + ProcessPartition(p, ncchHeader, reader, writer); } } /// /// Process a single partition /// - /// CIA header representing the 3DS CIA file + /// Index of the partition /// NCCH header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void ProcessPartition(CIAHeader ciaHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void ProcessPartition(int partitionIndex, + NCCHHeader ncchHeader, + BinaryReader reader, + BinaryWriter writer) { // If we're forcing the operation, tell the user if (decryptArgs.Force) { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} is not verified due to force flag being set."); + Console.WriteLine($"Partition {partitionIndex} is not verified due to force flag being set."); } // If we're not forcing the operation, check if the 'NoCrypto' bit is set - else if (ncchHeader.Flags!.PossblyDecrypted ^ decryptArgs.Encrypt) + else if (ncchHeader.Flags!.PossblyDecrypted() ^ decryptArgs.Encrypt) { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber}: Already " + (decryptArgs.Encrypt ? "Encrypted" : "Decrypted") + "?..."); + Console.WriteLine($"Partition {partitionIndex}: Already " + (decryptArgs.Encrypt ? "Encrypted" : "Decrypted") + "?..."); return; } + // Get the table entry -- TODO: Fix this to get the real entry + var tableEntry = new PartitionTableEntry(); + // Determine the Keys to be used - SetEncryptionKeys(ciaHeader, ncchHeader); + SetEncryptionKeys(partitionIndex, ncchHeader); // Process the extended header - ProcessExtendedHeader(ncchHeader, reader, writer); + ProcessExtendedHeader(partitionIndex, ncchHeader, tableEntry, reader, writer); // If we're encrypting, encrypt the filesystems and update the flags if (decryptArgs.Encrypt) { - EncryptExeFS(ncchHeader, reader, writer); - EncryptRomFS(ncchHeader, reader, writer); - UpdateEncryptCryptoAndMasks(ciaHeader, ncchHeader, writer); + EncryptExeFS(partitionIndex, ncchHeader, tableEntry, reader, writer); + EncryptRomFS(partitionIndex, ncchHeader, tableEntry, reader, writer); + UpdateEncryptCryptoAndMasks(partitionIndex, ncchHeader, tableEntry, writer); } // If we're decrypting, decrypt the filesystems and update the flags else { - DecryptExeFS(ncchHeader, reader, writer); - DecryptRomFS(ncchHeader, reader, writer); - UpdateDecryptCryptoAndMasks(ncchHeader, writer); + DecryptExeFS(partitionIndex, ncchHeader, tableEntry, reader, writer); + DecryptRomFS(partitionIndex, ncchHeader, tableEntry, reader, writer); + UpdateDecryptCryptoAndMasks(ncchHeader, tableEntry, writer); } } /// /// Determine the set of keys to be used for encryption or decryption /// - /// NCSD header representing the 3DS CIA file + /// Index of the partition /// NCCH header representing the partition - private void SetEncryptionKeys(CIAHeader ciaHeader, NCCHHeader ncchHeader) + private void SetEncryptionKeys(int partitionIndex, NCCHHeader ncchHeader) { - ncchHeader.KeyX = 0; - ncchHeader.KeyX2C = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C; + KeyX[partitionIndex] = 0; + KeyX2C[partitionIndex] = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C; // Backup headers can't have a KeyY value set if (ncchHeader.RSA2048Signature != null) - ncchHeader.KeyY = new BigInteger(ncchHeader.RSA2048Signature.Take(16).Reverse().ToArray()); + KeyY[partitionIndex] = new BigInteger(ncchHeader.RSA2048Signature.Take(16).Reverse().ToArray()); else - ncchHeader.KeyY = new BigInteger(0); + KeyY[partitionIndex] = new BigInteger(0); - ncchHeader.NormalKey = 0; - ncchHeader.NormalKey2C = RotateLeft((RotateLeft(ncchHeader.KeyX2C, 2, 128) ^ ncchHeader.KeyY) + decryptArgs.AESHardwareConstant, 87, 128); + NormalKey[partitionIndex] = 0x00; + NormalKey2C[partitionIndex] = RotateLeft((RotateLeft(KeyX2C[partitionIndex], 2, 128) ^ KeyY[partitionIndex]) + decryptArgs.AESHardwareConstant, 87, 128); // TODO: Figure out what sane defaults for these values are // Set the header to use based on mode @@ -165,63 +199,69 @@ private void SetEncryptionKeys(CIAHeader ciaHeader, NCCHHeader ncchHeader) if (masks.HasFlag(BitMasks.FixedCryptoKey)) { - ncchHeader.NormalKey = 0x00; - ncchHeader.NormalKey2C = 0x00; + NormalKey[partitionIndex] = 0x00; + NormalKey2C[partitionIndex] = 0x00; Console.WriteLine("Encryption Method: Zero Key"); } else { if (method == CryptoMethod.Original) { - ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C; + KeyX[partitionIndex] = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C; Console.WriteLine("Encryption Method: Key 0x2C"); } else if (method == CryptoMethod.Seven) { - ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x25 : decryptArgs.KeyX0x25; + KeyX[partitionIndex] = decryptArgs.Development ? decryptArgs.DevKeyX0x25 : decryptArgs.KeyX0x25; Console.WriteLine("Encryption Method: Key 0x25"); } else if (method == CryptoMethod.NineThree) { - ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x18 : decryptArgs.KeyX0x18; + KeyX[partitionIndex] = decryptArgs.Development ? decryptArgs.DevKeyX0x18 : decryptArgs.KeyX0x18; Console.WriteLine("Encryption Method: Key 0x18"); } else if (method == CryptoMethod.NineSix) { - ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x1B : decryptArgs.KeyX0x1B; + KeyX[partitionIndex] = decryptArgs.Development ? decryptArgs.DevKeyX0x1B : decryptArgs.KeyX0x1B; Console.WriteLine("Encryption Method: Key 0x1B"); } - ncchHeader.NormalKey = RotateLeft((RotateLeft(ncchHeader.KeyX, 2, 128) ^ ncchHeader.KeyY) + decryptArgs.AESHardwareConstant, 87, 128); + NormalKey[partitionIndex] = RotateLeft((RotateLeft(KeyX[partitionIndex], 2, 128) ^ KeyY[partitionIndex]) + decryptArgs.AESHardwareConstant, 87, 128); } } /// /// Process the extended header, if it exists /// + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private bool ProcessExtendedHeader(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private bool ProcessExtendedHeader(int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryReader reader, + BinaryWriter writer) { // TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value? uint mediaUnitSize = 0x200; // mediaUnitSize; if (ncchHeader.ExtendedHeaderSizeInBytes > 0) { - reader.BaseStream.Seek((ncchHeader.Entry!.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin); - writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin); + reader.BaseStream.Seek((tableEntry.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin); - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + ": ExHeader"); + Console.WriteLine($"Partition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + ": ExHeader"); - var cipher = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.PlainIV!, decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey2C[partitionIndex], ncchHeader.PlainIV(), decryptArgs.Encrypt); writer.Write(cipher.ProcessBytes(reader.ReadBytes(Constants.CXTExtendedDataHeaderLength))); writer.Flush(); return true; } else { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Extended Header... Skipping..."); + Console.WriteLine($"Partition {partitionIndex} ExeFS: No Extended Header... Skipping..."); return false; } } @@ -229,41 +269,47 @@ private bool ProcessExtendedHeader(NCCHHeader ncchHeader, BinaryReader reader, B /// /// Process the extended header, if it exists /// + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void ProcessExeFSFileEntries(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void ProcessExeFSFileEntries(int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryReader reader, + BinaryWriter writer) { // TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value? uint mediaUnitSize = 0x200; // mediaUnitSize; - reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); - ExeFSHeader? exefsHeader = ExeFSHeader.Read(reader); + reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); + var exefsHeader = Serializer.ReadExeFSHeader(reader); // If the header failed to read, log and return - if (exefsHeader == null) + if (exefsHeader?.FileHeaders == null) { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS header could not be read. Skipping..."); + Console.WriteLine($"Partition {partitionIndex} ExeFS header could not be read. Skipping..."); return; } - foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders!) + foreach (var fileHeader in exefsHeader.FileHeaders) { // Only decrypt a file if it's a code binary - if (!fileHeader.IsCodeBinary) + if (fileHeader == null || !fileHeader.IsCodeBinary()) continue; uint datalenM = ((fileHeader.FileSize) / (1024 * 1024)); uint datalenB = ((fileHeader.FileSize) % (1024 * 1024)); uint ctroffset = ((fileHeader.FileOffset + mediaUnitSize) / 0x10); - byte[] exefsIVWithOffsetForHeader = AddToByteArray(ncchHeader.ExeFSIV!, (int)ctroffset); + byte[] exefsIVWithOffsetForHeader = AddToByteArray(ncchHeader.ExeFSIV(), (int)ctroffset); - var firstCipher = CreateAESCipher(ncchHeader.NormalKey, exefsIVWithOffsetForHeader, decryptArgs.Encrypt); - var secondCipher = CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffsetForHeader, !decryptArgs.Encrypt); + var firstCipher = CreateAESCipher(NormalKey[partitionIndex], exefsIVWithOffsetForHeader, decryptArgs.Encrypt); + var secondCipher = CreateAESCipher(NormalKey2C[partitionIndex], exefsIVWithOffsetForHeader, !decryptArgs.Encrypt); - reader.BaseStream.Seek((((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * mediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin); - writer.BaseStream.Seek((((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * mediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin); + reader.BaseStream.Seek((((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * mediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin); + writer.BaseStream.Seek((((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * mediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin); if (datalenM > 0) { @@ -271,7 +317,7 @@ private void ProcessExeFSFileEntries(NCCHHeader ncchHeader, BinaryReader reader, { writer.Write(secondCipher.ProcessBytes(firstCipher.ProcessBytes(reader.ReadBytes(1024 * 1024)))); writer.Flush(); - Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {i} / {datalenM + 1} mb..."); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.FileName}... {i} / {datalenM + 1} mb..."); } } @@ -281,27 +327,33 @@ private void ProcessExeFSFileEntries(NCCHHeader ncchHeader, BinaryReader reader, writer.Flush(); } - Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n"); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.FileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n"); } } /// /// Process the ExeFS Filename Table /// + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void ProcessExeFSFilenameTable(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void ProcessExeFSFilenameTable(int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryReader reader, + BinaryWriter writer) { // TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value? uint mediaUnitSize = 0x200; // mediaUnitSize; - reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); - writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); + reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table"); + Console.WriteLine($"Partition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table"); - var exeFSFilenameTable = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.ExeFSIV!, decryptArgs.Encrypt); + var exeFSFilenameTable = CreateAESCipher(NormalKey2C[partitionIndex], ncchHeader.ExeFSIV(), decryptArgs.Encrypt); writer.Write(exeFSFilenameTable.ProcessBytes(reader.ReadBytes((int)mediaUnitSize))); writer.Flush(); } @@ -309,10 +361,16 @@ private void ProcessExeFSFilenameTable(NCCHHeader ncchHeader, BinaryReader reade /// /// Process the ExeFS, if it exists /// + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void ProcessExeFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void ProcessExeFS(int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryReader reader, + BinaryWriter writer) { // TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value? uint mediaUnitSize = 0x200; // mediaUnitSize; @@ -321,19 +379,19 @@ private void ProcessExeFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWrit int exefsSizeB = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * mediaUnitSize) % (1024 * 1024)); int ctroffsetE = (int)(mediaUnitSize / 0x10); - byte[] exefsIVWithOffset = AddToByteArray(ncchHeader.ExeFSIV!, ctroffsetE); + byte[] exefsIVWithOffset = AddToByteArray(ncchHeader.ExeFSIV(), ctroffsetE); - var exeFS = CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffset, decryptArgs.Encrypt); + var exeFS = CreateAESCipher(NormalKey2C[partitionIndex], exefsIVWithOffset, decryptArgs.Encrypt); - reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * mediaUnitSize, SeekOrigin.Begin); - writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * mediaUnitSize, SeekOrigin.Begin); + reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * mediaUnitSize, SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * mediaUnitSize, SeekOrigin.Begin); if (exefsSizeM > 0) { for (int i = 0; i < exefsSizeM; i++) { writer.Write(exeFS.ProcessBytes(reader.ReadBytes(1024 * 1024))); writer.Flush(); - Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb"); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb"); } } if (exefsSizeB > 0) @@ -342,7 +400,7 @@ private void ProcessExeFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWrit writer.Flush(); } - Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n"); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n"); } #endregion @@ -352,37 +410,49 @@ private void ProcessExeFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWrit /// /// Decrypt the ExeFS, if it exists /// + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void DecryptExeFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void DecryptExeFS(int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryReader reader, + BinaryWriter writer) { // If the ExeFS size is 0, we log and return if (ncchHeader.ExeFSSizeInMediaUnits == 0) { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Data... Skipping..."); + Console.WriteLine($"Partition {partitionIndex} ExeFS: No Data... Skipping..."); return; } // Decrypt the filename table - ProcessExeFSFilenameTable(ncchHeader, reader, writer); + ProcessExeFSFilenameTable(partitionIndex, ncchHeader, tableEntry, reader, writer); // For all but the original crypto method, process each of the files in the table if (ncchHeader.Flags!.CryptoMethod != CryptoMethod.Original) - ProcessExeFSFileEntries(ncchHeader, reader, writer); + ProcessExeFSFileEntries(partitionIndex, ncchHeader, tableEntry, reader, writer); // Decrypt the rest of the ExeFS - ProcessExeFS(ncchHeader, reader, writer); + ProcessExeFS(partitionIndex, ncchHeader, tableEntry, reader, writer); } /// /// Decrypt the RomFS, if it exists /// + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream /// TODO: See how much can be extracted into a common method with Encrypt - private void DecryptRomFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void DecryptRomFS(int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryReader reader, + BinaryWriter writer) { // TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value? uint mediaUnitSize = 0x200; // ncsdHeader.MediaUnitSize; @@ -390,24 +460,24 @@ private void DecryptRomFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWrit // If the RomFS offset is 0, we log and return if (ncchHeader.RomFSOffsetInMediaUnits == 0) { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} RomFS: No Data... Skipping..."); + Console.WriteLine($"Partition {partitionIndex} RomFS: No Data... Skipping..."); return; } long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * mediaUnitSize) / (1024 * 1024)); int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * mediaUnitSize) % (1024 * 1024)); - var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV!, decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey[partitionIndex], ncchHeader.RomFSIV(), decryptArgs.Encrypt); - reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); - writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); + reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); if (romfsSizeM > 0) { for (int i = 0; i < romfsSizeM; i++) { writer.Write(cipher.ProcessBytes(reader.ReadBytes(1024 * 1024))); writer.Flush(); - Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Decrypting: {i} / {romfsSizeM + 1} mb"); + Console.Write($"\rPartition {partitionIndex} RomFS: Decrypting: {i} / {romfsSizeM + 1} mb"); } } if (romfsSizeB > 0) @@ -416,26 +486,29 @@ private void DecryptRomFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWrit writer.Flush(); } - Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Decrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n"); + Console.Write($"\rPartition {partitionIndex} RomFS: Decrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n"); } /// /// Update the CryptoMethod and BitMasks for the decrypted partition /// /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryWriter representing the output stream - private void UpdateDecryptCryptoAndMasks(NCCHHeader ncchHeader, BinaryWriter writer) + private void UpdateDecryptCryptoAndMasks(NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryWriter writer) { // TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value? uint mediaUnitSize = 0x200; // ncsdHeader.MediaUnitSize; // Write the new CryptoMethod - writer.BaseStream.Seek((ncchHeader.Entry!.Offset * mediaUnitSize) + 0x18B, SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset * mediaUnitSize) + 0x18B, SeekOrigin.Begin); writer.Write((byte)CryptoMethod.Original); writer.Flush(); // Write the new BitMasks flag - writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x18F, SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset * mediaUnitSize) + 0x18F, SeekOrigin.Begin); BitMasks flag = ncchHeader.Flags!.BitMasks; flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF); flag |= BitMasks.NoCrypto; @@ -450,15 +523,21 @@ private void UpdateDecryptCryptoAndMasks(NCCHHeader ncchHeader, BinaryWriter wri /// /// Encrypt the ExeFS, if it exists /// + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void EncryptExeFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void EncryptExeFS(int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryReader reader, + BinaryWriter writer) { // If the ExeFS size is 0, we log and return if (ncchHeader.ExeFSSizeInMediaUnits == 0) { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Data... Skipping..."); + Console.WriteLine($"Partition {partitionIndex} ExeFS: No Data... Skipping..."); return; } @@ -468,20 +547,26 @@ private void EncryptExeFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWrit // ProcessExeFSFileEntries(ncchHeader, reader, writer); // Encrypt the filename table - ProcessExeFSFilenameTable(ncchHeader, reader, writer); + ProcessExeFSFilenameTable(partitionIndex, ncchHeader, tableEntry, reader, writer); // Encrypt the rest of the ExeFS - ProcessExeFS(ncchHeader, reader, writer); + ProcessExeFS(partitionIndex, ncchHeader, tableEntry, reader, writer); } /// /// Encrypt the RomFS, if it exists /// + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream /// TODO: See how much can be extracted into a common method with Decrypt - private void EncryptRomFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void EncryptRomFS(int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryReader reader, + BinaryWriter writer) { // TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value? uint mediaUnitSize = 0x200; // ncsdHeader.MediaUnitSize; @@ -489,7 +574,7 @@ private void EncryptRomFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWrit // If the RomFS offset is 0, we log and return if (ncchHeader.RomFSOffsetInMediaUnits == 0) { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} RomFS: No Data... Skipping..."); + Console.WriteLine($"Partition {partitionIndex} RomFS: No Data... Skipping..."); return; } @@ -497,7 +582,7 @@ private void EncryptRomFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWrit int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * mediaUnitSize) % (1024 * 1024)); // Encrypting RomFS for partitions 1 and up always use Key0x2C - if (ncchHeader.PartitionNumber > 0) + if (partitionIndex > 0) { // TODO: Determine how to figure out the original crypto method, if possible //if (ciaHeader.BackupHeader.Flags?.BitMasks.HasFlag(BitMasks.FixedCryptoKey) == true) // except if using zero-key @@ -506,22 +591,22 @@ private void EncryptRomFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWrit //} //else //{ - ncchHeader.KeyX = (decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C); - ncchHeader.NormalKey = RotateLeft((RotateLeft(ncchHeader.KeyX, 2, 128) ^ ncchHeader.KeyY) + decryptArgs.AESHardwareConstant, 87, 128); + KeyX[partitionIndex] = (decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C); + NormalKey[partitionIndex] = RotateLeft((RotateLeft(KeyX[partitionIndex], 2, 128) ^ KeyY[partitionIndex]) + decryptArgs.AESHardwareConstant, 87, 128); //} } - var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV!, decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey[partitionIndex], ncchHeader.RomFSIV(), decryptArgs.Encrypt); - reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); - writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); + reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); if (romfsSizeM > 0) { for (int i = 0; i < romfsSizeM; i++) { writer.Write(cipher.ProcessBytes(reader.ReadBytes(1024 * 1024))); writer.Flush(); - Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Encrypting: {i} / {romfsSizeM + 1} mb"); + Console.Write($"\rPartition {partitionIndex} RomFS: Encrypting: {i} / {romfsSizeM + 1} mb"); } } if (romfsSizeB > 0) @@ -530,25 +615,29 @@ private void EncryptRomFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWrit writer.Flush(); } - Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Encrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n"); + Console.Write($"\rPartition {partitionIndex} RomFS: Encrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n"); } /// /// Update the CryptoMethod and BitMasks for the encrypted partition /// - /// CIA header representing the 3DS CIA file + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryWriter representing the output stream - private void UpdateEncryptCryptoAndMasks(CIAHeader ciaHeader, NCCHHeader ncchHeader, BinaryWriter writer) + private void UpdateEncryptCryptoAndMasks(int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryWriter writer) { // TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value? uint mediaUnitSize = 0x200; // ncsdHeader.MediaUnitSize; // Write the new CryptoMethod - writer.BaseStream.Seek((ncchHeader.Entry!.Offset * mediaUnitSize) + 0x18B, SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset * mediaUnitSize) + 0x18B, SeekOrigin.Begin); // For partitions 1 and up, set crypto-method to 0x00 - if (ncchHeader.PartitionNumber > 0) + if (partitionIndex > 0) writer.Write((byte)CryptoMethod.Original); // TODO: Determine how to figure out the original crypto method, if possible @@ -559,7 +648,7 @@ private void UpdateEncryptCryptoAndMasks(CIAHeader ciaHeader, NCCHHeader ncchHea writer.Flush(); // Write the new BitMasks flag - writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x18F, SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset * mediaUnitSize) + 0x18F, SeekOrigin.Begin); BitMasks flag = ncchHeader.Flags!.BitMasks; flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF; diff --git a/NDecrypt.N3DS/Enums.cs b/NDecrypt.N3DS/Enums.cs index 93e1db3..62b073d 100644 --- a/NDecrypt.N3DS/Enums.cs +++ b/NDecrypt.N3DS/Enums.cs @@ -16,213 +16,4 @@ internal enum ARM9AccessControlDescriptors : byte SDApplication = 0x80, MoundSdmcWriteAccess = 0xF0, } - - [Flags] - internal enum ARM11LSCFlag0 : byte - { - IdealProcessor = 0x01 | 0x02, - AffinityMask = 0x04 | 0x08, - - /// - /// Value Description - /// 0 Prod (64MB of usable application memory) - /// 1 Undefined (unusable) - /// 2 Dev1 (96MB of usable application memory) - /// 3 Dev2 (80MB of usable application memory) - /// 4 Dev3 (72MB of usable application memory) - /// 5 Dev4 (32MB of usable application memory) - /// 6-7 Undefined Same as Prod? - /// - Old3DSSystemMode = 0x0F | 0x10 | 0x20 | 0x40, - } - - [Flags] - internal enum ARM11LSCFlag1 : byte - { - EnableL2Cache = 0x01, - Cpuspeed_804MHz = 0x02, - } - - [Flags] - internal enum ARM11LSCFlag2 : byte - { - /// - /// Value Description - /// 0 Legacy (use Old3DS system mode) - /// 1 Prod (124MB of usable application memory) - /// 2 Dev1 (178MB of usable application memory) - /// 3 Dev2 (124MB of usable application memory) - /// 4-7 Undefined Same as Prod? - /// - New3DSSystemMode = 0x01 | 0x02 | 0x04 | 0x08, - } - - [Flags] - internal enum BitMasks : byte - { - FixedCryptoKey = 0x01, - NoMountRomFs = 0x02, - NoCrypto = 0x04, - 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, - Snake = 0x02, // New3DS - } - - [Flags] - internal enum ContentType : byte - { - Data = 0x01, - Executable = 0x02, - SystemUpdate = 0x04, - Manual = 0x08, - Child = 0x04 | 0x08, - Trial = 0x10, - } - - internal enum CryptoMethod : byte - { - Original = 0x00, - Seven = 0x01, - NineThree = 0x0A, - NineSix = 0x0B, - } - - [Flags] - internal enum FilesystemAccessInfo : ulong - { - CategorySystemApplication = 0x1, - CategoryHardwareCheck = 0x2, - CategoryFilesystemTool = 0x4, - Debug = 0x8, - TWLCardBackup = 0x10, - TWLNANDData = 0x20, - BOSS = 0x40, - sdmcRoot = 0x80, - Core = 0x100, - nandRootroReadOnly = 0x200, - nandRootrw = 0x400, - nandrootroWriteAccess = 0x800, - CategorySystemSettings = 0x1000, - Cardboard = 0x2000, - ExportImportIVS = 0x4000, - sdmcRootWriteOnly = 0x8000, - SwitchCleanup = 0x10000, // Introduced in 3.0.0? - SavedataMove = 0x20000, // Introduced in 5.0.0 - Shop = 0x40000, // Introduced in 5.0.0 - Shell = 0x80000, // Introduced in 5.0.0 - CategoryHomeMenu = 0x100000, // Introduced in 6.0.0 - SeedDB = 0x200000, // Introduced in 9.6.0-X FIRM. Home Menu has this bit set starting with 9.6.0-X. - } - - internal enum FilesystemType : ulong - { - None = 0, - Normal = 1, - FIRM = 3, - AGB_FIRMSave = 4, - } - - internal enum MediaCardDeviceType : byte - { - NORFlash = 0x01, - None = 0x02, - BT = 0x03, - } - - internal enum MediaPlatformIndex : byte - { - CTR = 0x01, - } - - internal enum MediaTypeIndex : byte - { - InnerDevice = 0x00, - Card1 = 0x01, - Card2 = 0x02, - ExtendedDevice = 0x03, - } - - internal enum NCCHFlags - { - CryptoMethod = 0x03, - ContentPlatform = 0x04, - ContentTypeBitMask = 0x05, - ContentUnitSize = 0x06, - BitMasks = 0x07, - } - - internal enum NCSDFlags - { - BackupWriteWaitTime = 0x00, - MediaCardDevice3X = 0x03, - MediaPlatformIndex = 0x04, - MediaTypeIndex = 0x05, - MediaUnitSize = 0x06, - MediaCardDevice2X = 0x07, - } - - internal enum PublicKeyType : uint - { - RSA_4096 = 0x00000000, - RSA_2048 = 0x01000000, - ECDSA = 0x02000000, - } - - internal enum ResourceLimitCategory - { - APPLICATION = 0, - SYS_APPLET = 1, - LIB_APPLET = 2, - OTHER = 3, - } - - // Note: These are reversed because of how C# reads values - internal enum SignatureType : uint - { - RSA_4096_SHA1 = 0x00000100, - RSA_2048_SHA1 = 0x01000100, - ECDSA_SHA1 = 0x02000100, - RSA_4096_SHA256 = 0x03000100, - RSA_2048_SHA256 = 0x04000100, - ECDSA_SHA256 = 0x05000100, - } - - [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/Extensions.cs b/NDecrypt.N3DS/Extensions.cs new file mode 100644 index 0000000..b268fd1 --- /dev/null +++ b/NDecrypt.N3DS/Extensions.cs @@ -0,0 +1,262 @@ +using System; +using System.Numerics; +using SabreTools.Models.N3DS; + +namespace NDecrypt.N3DS +{ + internal static class Extensions + { + #region ExeFSFileHeader + + /// + /// Determines if a file header represents a CODE block + /// + public static bool IsCodeBinary(this ExeFSFileHeader? header) + { + if (header == null) + return false; + + return header.FileName == ".code\0\0\0"; + } + + #endregion + + #region NCCHHeader + + /// + /// Get the initial value for the plain counter + /// + public static byte[] PlainIV(this NCCHHeader? header) + { + if (header == null) + return []; + + byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId); + return [.. partitionIdBytes, .. Constants.PlainCounter]; + } + + /// + /// Get the initial value for the ExeFS counter + /// + public static byte[] ExeFSIV(this NCCHHeader header) + { + if (header == null) + return []; + + byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId); + return [.. partitionIdBytes, .. Constants.ExefsCounter]; + } + + /// + /// Get the initial value for the RomFS counter + /// + public static byte[] RomFSIV(this NCCHHeader header) + { + if (header == null) + return []; + + byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId); + return [.. partitionIdBytes, .. Constants.RomfsCounter]; + } + + /// + /// NCCH boot rom key + /// + public static BigInteger KeyX2C { get; set; } + + /// + /// Normal AES key + /// + public static BigInteger NormalKey { get; set; } + + /// + /// NCCH AES key + /// + public static BigInteger NormalKey2C { get; set; } + + #endregion + + #region NCCHHeaderFlags + + /// + /// Get if the NoCrypto bit is set + /// + public static bool PossblyDecrypted(this NCCHHeaderFlags flags) + { + if (flags == null) + return false; + + return flags.BitMasks.HasFlag(BitMasks.NoCrypto); + } + + #endregion + + #region NCSDHeader + + /// + /// Partition table entry for Executable Content (CXI) + /// + public static PartitionTableEntry? ExecutableContent(this NCSDHeader? header) + { + if (header?.PartitionsTable == null) + return null; + + return header.PartitionsTable[0]; + } + + /// + /// Partition table entry for E-Manual (CFA) + /// + public static PartitionTableEntry? EManual(this NCSDHeader? header) + { + if (header?.PartitionsTable == null) + return null; + + return header.PartitionsTable[1]; + } + + /// + /// Partition table entry for Download Play Child container (CFA) + /// + public static PartitionTableEntry? DownloadPlayChildContainer(this NCSDHeader? header) + { + if (header?.PartitionsTable == null) + return null; + + return header.PartitionsTable[2]; + } + + /// + /// Partition table entry for New3DS Update Data (CFA) + /// + public static PartitionTableEntry? New3DSUpdateData(this NCSDHeader? header) + { + if (header?.PartitionsTable == null) + return null; + + return header.PartitionsTable[6]; + } + + /// + /// Partition table entry for Update Data (CFA) + /// + public static PartitionTableEntry? UpdateData(this NCSDHeader? header) + { + if (header?.PartitionsTable == null) + return null; + + return header.PartitionsTable[7]; + } + + /// + /// 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 static byte BackupWriteWaitTime(this NCSDHeader? header) + { + if (header?.PartitionFlags == null) + return default; + + return header.PartitionFlags[(int)NCSDFlags.BackupWriteWaitTime]; + } + + /// + /// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (SDK 3.X+) + /// + public static MediaCardDeviceType MediaCardDevice3X(this NCSDHeader? header) + { + if (header?.PartitionFlags == null) + return default; + + return (MediaCardDeviceType)header.PartitionFlags[(int)NCSDFlags.MediaCardDevice3X]; + } + + /// + /// Media Platform Index (1 = CTR) + /// + public static MediaPlatformIndex MediaPlatformIndex(this NCSDHeader? header) + { + if (header?.PartitionFlags == null) + return default; + + return (MediaPlatformIndex)header.PartitionFlags[(int)NCSDFlags.MediaPlatformIndex]; + } + + /// + /// Media Type Index (0 = Inner Device, 1 = Card1, 2 = Card2, 3 = Extended Device) + /// + public static MediaTypeIndex MediaTypeIndex(this NCSDHeader? header) + { + if (header?.PartitionFlags == null) + return default; + + return (MediaTypeIndex)header.PartitionFlags[(int)NCSDFlags.MediaTypeIndex]; + } + + /// + /// Media Unit Size i.e. u32 MediaUnitSize = 0x200*2^flags[6]; + /// + public static uint MediaUnitSize(this NCSDHeader? header) + { + if (header?.PartitionFlags == null) + return default; + + return (uint)(0x200 * Math.Pow(2, header.PartitionFlags[(int)NCSDFlags.MediaUnitSize])); + } + + /// + /// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (Only SDK 2.X) + /// + public static MediaCardDeviceType MediaCardDevice2X(this NCSDHeader? header) + { + if (header?.PartitionFlags == null) + return default; + + return (MediaCardDeviceType)header.PartitionFlags[(int)NCSDFlags.MediaCardDevice2X]; + } + + #endregion + + #region PartitionTableEntry + + /// + /// Check for a valid partition + /// + /// True if the offset and length are not 0, false otherwise + public static bool IsValid(this PartitionTableEntry? entry) + { + if (entry == null) + return false; + + return entry.Offset != 0 && entry.Length != 0; + } + + #endregion + + #region Ticket + + /// + /// Denotes if the ticket denotes a demo or not + /// + public static bool IsDemo(this Ticket? ticket) + { + if (ticket?.Limits == null || ticket.Limits.Length == 0) + return false; + + return ticket.Limits[0] == 0x0004; + } + + /// + /// Denotes if the max playcount for a demo + /// + public static uint PlayCount(this Ticket ticket) + { + if (ticket?.Limits == null || ticket.Limits.Length == 0) + return 0; + + return ticket.Limits[1]; + } + + #endregion + } +} \ No newline at end of file diff --git a/NDecrypt.N3DS/Headers/ARM11KernelCapabilities.cs b/NDecrypt.N3DS/Headers/ARM11KernelCapabilities.cs deleted file mode 100644 index f0ee2f3..0000000 --- a/NDecrypt.N3DS/Headers/ARM11KernelCapabilities.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - internal class ARM11KernelCapabilities - { - /// - /// Descriptors - /// ------------------- - /// Pattern of bits 20-31 Type Fields - /// 0b1110xxxxxxxx Interrupt info - /// 0b11110xxxxxxx System call mask Bits 24-26: System call mask table index; Bits 0-23: mask - /// 0b1111110xxxxx Kernel release version Bits 8-15: Major version; Bits 0-7: Minor version - /// 0b11111110xxxx Handle table size Bits 0-18: size - /// 0b111111110xxx Kernel flags - /// 0b11111111100x Map address range Describes a memory mapping like the 0b111111111110 descriptor, but an entire range rather than a single page is mapped.Another 0b11111111100x descriptor must follow this one to denote the(exclusive) end of the address range to map. - /// 0b111111111110 Map memory page Bits 0-19: page index to map(virtual address >> 12; the physical address is determined per-page according to Memory layout); Bit 20: Map read-only(otherwise read-write) - /// - /// ARM11 Kernel Flags - /// ------------------- - /// Bit Description - /// 0 Allow debug - /// 1 Force debug - /// 2 Allow non-alphanum - /// 3 Shared page writing - /// 4 Privilege priority - /// 5 Allow main() args - /// 6 Shared device memory - /// 7 Runnable on sleep - /// 8-11 Memory type(1: application, 2: system, 3: base) - /// 12 Special memory - /// 13 Process has access to CPU core 2 (New3DS only) - /// - public byte[][]? Descriptors { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved { get; private set; } - - /// - /// Read from a stream and get ARM11 kernel capabilities, if possible - /// - /// BinaryReader representing the input stream - /// ARM11 kernel capabilities object, null on error - public static ARM11KernelCapabilities? Read(BinaryReader reader) - { - var kc = new ARM11KernelCapabilities(); - - try - { - kc.Descriptors = new byte[28][]; - for (int i = 0; i < 28; i++) - kc.Descriptors[i] = reader.ReadBytes(4); - - kc.Reserved = reader.ReadBytes(0x10); - return kc; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/ARM11LocalSystemCapabilities.cs b/NDecrypt.N3DS/Headers/ARM11LocalSystemCapabilities.cs deleted file mode 100644 index dd43321..0000000 --- a/NDecrypt.N3DS/Headers/ARM11LocalSystemCapabilities.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - internal class ARM11LocalSystemCapabilities - { - /// - /// Program ID - /// - public byte[]? ProgramID { get; private set; } - - /// - /// Core version (The Title ID low of the required FIRM) - /// - public uint CoreVersion { get; private set; } - - /// - /// Flag1 (implemented starting from 8.0.0-18). - /// - public ARM11LSCFlag1 Flag1 { get; private set; } - - /// - /// Flag2 (implemented starting from 8.0.0-18). - /// - public ARM11LSCFlag2 Flag2 { get; private set; } - - /// - /// Flag0 - /// - public ARM11LSCFlag0 Flag0 { get; private set; } - - /// - /// Priority - /// - public byte Priority { get; private set; } - - /// - /// Resource limit descriptors. The first byte here controls the maximum allowed CpuTime. - /// - public byte[][]? ResourceLimitDescriptors { get; private set; } - - /// - /// Storage info - /// - public StorageInfo? StorageInfo { get; private set; } - - /// - /// Service access control - /// - public byte[][]? ServiceAccessControl { get; private set; } - - /// - /// Extended service access control, support for this was implemented with 9.3.0-X. - /// - public byte[][]? ExtendedServiceAccessControl { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved { get; private set; } - - /// - /// Resource limit category. (0 = APPLICATION, 1 = SYS_APPLET, 2 = LIB_APPLET, 3 = OTHER (sysmodules running under the BASE memregion)) - /// - public ResourceLimitCategory ResourceLimitCategory { get; private set; } - - /// - /// Read from a stream and get ARM11 local system capabilities, if possible - /// - /// BinaryReader representing the input stream - /// ARM11 local system capabilities object, null on error - public static ARM11LocalSystemCapabilities? Read(BinaryReader reader) - { - var lsc = new ARM11LocalSystemCapabilities(); - - try - { - lsc.ProgramID = reader.ReadBytes(8); - lsc.CoreVersion = reader.ReadUInt32(); - lsc.Flag1 = (ARM11LSCFlag1)reader.ReadByte(); - lsc.Flag2 = (ARM11LSCFlag2)reader.ReadByte(); - lsc.Flag0 = (ARM11LSCFlag0)reader.ReadByte(); - lsc.Priority = reader.ReadByte(); - - lsc.ResourceLimitDescriptors = new byte[16][]; - for (int i = 0; i < 16; i++) - lsc.ResourceLimitDescriptors[i] = reader.ReadBytes(2); - - lsc.StorageInfo = StorageInfo.Read(reader); - - lsc.ServiceAccessControl = new byte[32][]; - for (int i = 0; i < 32; i++) - lsc.ServiceAccessControl[i] = reader.ReadBytes(8); - - lsc.ExtendedServiceAccessControl = new byte[2][]; - for (int i = 0; i < 2; i++) - lsc.ExtendedServiceAccessControl[i] = reader.ReadBytes(8); - - lsc.Reserved = reader.ReadBytes(0xF); - lsc.ResourceLimitCategory = (ResourceLimitCategory)reader.ReadByte(); - return lsc; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/ARM9AccessControl.cs b/NDecrypt.N3DS/Headers/ARM9AccessControl.cs deleted file mode 100644 index a9ff74f..0000000 --- a/NDecrypt.N3DS/Headers/ARM9AccessControl.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - internal class ARM9AccessControl - { - /// - /// Descriptors - /// - public ARM9AccessControlDescriptors[]? Descriptors { get; private set; } - - /// - /// ARM9 Descriptor Version. Originally this value had to be ≥ 2. Starting with 9.3.0-X this value has to be either value 2 or value 3. - /// - public byte DescriptorVersion { get; private set; } - - /// - /// Read from a stream and get ARM9 access control, if possible - /// - /// BinaryReader representing the input stream - /// ARM9 access control object, null on error - public static ARM9AccessControl? Read(BinaryReader reader) - { - var ac = new ARM9AccessControl(); - - try - { - ac.Descriptors = new ARM9AccessControlDescriptors[15]; - for (int i = 0; i < 15; i++) - ac.Descriptors[i] = (ARM9AccessControlDescriptors)reader.ReadByte(); - ac.DescriptorVersion = reader.ReadByte(); - return ac; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/AccessControlInfo.cs b/NDecrypt.N3DS/Headers/AccessControlInfo.cs deleted file mode 100644 index bf60eee..0000000 --- a/NDecrypt.N3DS/Headers/AccessControlInfo.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - internal class AccessControlInfo - { - /// - /// ARM11 local system capabilities - /// - public ARM11LocalSystemCapabilities? ARM11LocalSystemCapabilities { get; private set; } - - /// - /// ARM11 kernel capabilities - /// - public ARM11KernelCapabilities? ARM11KernelCapabilities { get; private set; } - - /// - /// ARM9 access control - /// - public ARM9AccessControl? ARM9AccessControl { get; private set; } - - /// - /// Read from a stream and get access control info, if possible - /// - /// BinaryReader representing the input stream - /// Access control info object, null on error - public static AccessControlInfo? Read(BinaryReader reader) - { - var aci = new AccessControlInfo(); - - try - { - aci.ARM11LocalSystemCapabilities = ARM11LocalSystemCapabilities.Read(reader); - aci.ARM11KernelCapabilities = ARM11KernelCapabilities.Read(reader); - aci.ARM9AccessControl = ARM9AccessControl.Read(reader); - return aci; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/CIAHeader.cs b/NDecrypt.N3DS/Headers/CIAHeader.cs deleted file mode 100644 index 45721e0..0000000 --- a/NDecrypt.N3DS/Headers/CIAHeader.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System.Collections.Generic; -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - // https://www.3dbrew.org/wiki/CIA - internal class CIAHeader - { - /// - /// Archive header size, usually 0x2020 bytes - /// - public int HeaderSize { get; private set; } - - /// - /// Type - /// - public ushort Type { get; private set; } - - /// - /// Version - /// - public ushort Version { get; private set; } - - /// - /// Certificate chain size - /// - public int CertificateChainSize { get; private set; } - - /// - /// Ticket size - /// - public int TicketSize { get; private set; } - - /// - /// TMD file size - /// - public int TMDFileSize { get; private set; } - - /// - /// Meta size (0 if no Meta data is present) - /// - public int MetaSize { get; private set; } - - /// - /// Content size - /// - public long ContentSize { get; private set; } - - /// - /// Content Index - /// - public byte[]? ContentIndex { get; private set; } - - #region Content Index - - /// - /// Certificate chain - /// - /// - /// https://www.3dbrew.org/wiki/CIA#Certificate_Chain - /// - public Certificate[]? CertificateChain { get; set; } - - /// - /// Ticket - /// - public Ticket? Ticket { get; set; } - - /// - /// TMD file data - /// - public TitleMetadata? TMDFileData { get; set; } - - /// - /// Content file data - /// - public NCCHHeader[]? Partitions { get; set; } - - /// - /// Meta file data (Not a necessary component) - /// - public MetaFile? MetaFileData { get; set; } - - #endregion - - /// - /// Read from a stream and get a CIA header, if possible - /// - /// BinaryReader representing the input stream - /// CIA header object, null on error - public static CIAHeader? Read(BinaryReader reader) - { - var header = new CIAHeader(); - - try - { - header.HeaderSize = reader.ReadInt32(); - header.Type = reader.ReadUInt16(); - header.Version = reader.ReadUInt16(); - header.CertificateChainSize = reader.ReadInt32(); - header.TicketSize = reader.ReadInt32(); - header.TMDFileSize = reader.ReadInt32(); - header.MetaSize = reader.ReadInt32(); - header.ContentSize = reader.ReadInt64(); - header.ContentIndex = reader.ReadBytes(0x2000); - 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); - if (reader.BaseStream.Position % 64 != 0) - reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current); - - long startingPosition = reader.BaseStream.Position; - List headers = new List(); - while (reader.BaseStream.Position < startingPosition + header.ContentSize) - { - long initPosition = reader.BaseStream.Position; - NCCHHeader? ncchHeader = NCCHHeader.Read(reader, readSignature: true); - if (ncchHeader == null) - break; - - headers.Add(ncchHeader); - reader.BaseStream.Seek(initPosition + ncchHeader.ContentSizeInMediaUnits * 0x200, SeekOrigin.Begin); - } - - header.Partitions = headers.ToArray(); - if (header.MetaSize > 0) - header.MetaFileData = MetaFile.Read(reader); - - return header; - } - catch - { - return null; - } - } - } -} \ No newline at end of file diff --git a/NDecrypt.N3DS/Headers/CXIExtendedHeader.cs b/NDecrypt.N3DS/Headers/CXIExtendedHeader.cs deleted file mode 100644 index f69915b..0000000 --- a/NDecrypt.N3DS/Headers/CXIExtendedHeader.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - internal class CXIExtendedHeader - { - /// - /// SCI - /// - public SystemControlInfo? SCI { get; private set; } - - /// - /// ACI - /// - public AccessControlInfo? ACI { get; private set; } - - /// - /// AccessDesc signature (RSA-2048-SHA256) - /// - public byte[]? AccessDescSignature { get; private set; } - - /// - /// NCCH HDR RSA-2048 public key - /// - public byte[]? NCCHHDRPublicKey { get; private set; } - - /// - /// ACI (for limitation of first ACI) - /// - public AccessControlInfo? ACIForLimitations { get; private set; } - - /// - /// Read from a stream and get a CXI extended header, if possible - /// - /// BinaryReader representing the input stream - /// CXI extended header object, null on error - public static CXIExtendedHeader? Read(BinaryReader reader) - { - var header = new CXIExtendedHeader(); - - try - { - header.SCI = SystemControlInfo.Read(reader); - header.ACI = AccessControlInfo.Read(reader); - header.AccessDescSignature = reader.ReadBytes(0x100); - header.NCCHHDRPublicKey = reader.ReadBytes(0x100); - header.ACIForLimitations = AccessControlInfo.Read(reader); - return header; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/Certificate.cs b/NDecrypt.N3DS/Headers/Certificate.cs deleted file mode 100644 index 2a57d2a..0000000 --- a/NDecrypt.N3DS/Headers/Certificate.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System.IO; -using System.Text; - -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; } - - /// - /// Issuer as a trimmed string - /// - public string? IssuerString => Issuer != null && Issuer.Length > 0 - ? Encoding.ASCII.GetString(Issuer)?.TrimEnd('\0') - : null; - - /// - /// Key Type - /// - public PublicKeyType KeyType { get; private set; } - - /// - /// Name - /// - 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 - /// - 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) - { - var 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; - default: - return null; - } - - 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; - default: - return null; - } - - return ct; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/CodeSetInfo.cs b/NDecrypt.N3DS/Headers/CodeSetInfo.cs deleted file mode 100644 index 0f01071..0000000 --- a/NDecrypt.N3DS/Headers/CodeSetInfo.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - internal class CodeSetInfo - { - /// - /// Address - /// - public byte[]? Address { get; private set; } - - /// - /// Physical region size (in page-multiples) - /// - public uint PhysicalRegionSizeInPages { get; private set; } - - /// - /// Size (in bytes) - /// - public uint SizeInBytes { get; private set; } - - /// - /// Read from a stream and get code set info, if possible - /// - /// BinaryReader representing the input stream - /// Code set info object, null on error - public static CodeSetInfo? Read(BinaryReader reader) - { - var csi = new CodeSetInfo(); - - try - { - csi.Address = reader.ReadBytes(4); - csi.PhysicalRegionSizeInPages = reader.ReadUInt32(); - csi.SizeInBytes = reader.ReadUInt32(); - return csi; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/ContentChunkRecord.cs b/NDecrypt.N3DS/Headers/ContentChunkRecord.cs deleted file mode 100644 index 078c44a..0000000 --- a/NDecrypt.N3DS/Headers/ContentChunkRecord.cs +++ /dev/null @@ -1,59 +0,0 @@ -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) - { - var 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 deleted file mode 100644 index 709ed5b..0000000 --- a/NDecrypt.N3DS/Headers/ContentInfoRecord.cs +++ /dev/null @@ -1,44 +0,0 @@ -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) - { - var 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/ExeFSFileHeader.cs b/NDecrypt.N3DS/Headers/ExeFSFileHeader.cs deleted file mode 100644 index 45e7076..0000000 --- a/NDecrypt.N3DS/Headers/ExeFSFileHeader.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.IO; -using System.Linq; -using System.Text; - -namespace NDecrypt.N3DS.Headers -{ - internal class ExeFSFileHeader - { - // .code\0\0\0 - private readonly byte[] codeSegmentBytes = [0x2e, 0x63, 0x6f, 0x64, 0x65, 0x00, 0x00, 0x00]; - - /// - /// File name - /// - public byte[]? FileName { get; private set; } - public string ReadableFileName { get { return Encoding.ASCII.GetString(FileName!); } } - public bool IsCodeBinary { get { return Enumerable.SequenceEqual(FileName!, codeSegmentBytes); } } - - /// - /// File offset - /// - public uint FileOffset { get; private set; } - - /// - /// File size - /// - public uint FileSize { get; private set; } - - /// - /// SHA256 hash calculated over the entire file contents - /// - public byte[]? FileHash { get; set; } - - /// - /// Read from a stream and get an ExeFS file header, if possible - /// - /// BinaryReader representing the input stream - /// ExeFS file header object, null on error - public static ExeFSFileHeader? Read(BinaryReader reader) - { - var header = new ExeFSFileHeader(); - - try - { - header.FileName = reader.ReadBytes(8); - header.FileOffset = reader.ReadUInt32(); - header.FileSize = reader.ReadUInt32(); - return header; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/ExeFSHeader.cs b/NDecrypt.N3DS/Headers/ExeFSHeader.cs deleted file mode 100644 index 709c03b..0000000 --- a/NDecrypt.N3DS/Headers/ExeFSHeader.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - internal class ExeFSHeader - { - /// - /// File headers (10 headers maximum, 16 bytes each) - /// - public ExeFSFileHeader[]? FileHeaders { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved { get; private set; } - - /// - /// Read from a stream and get an ExeFS header, if possible - /// - /// BinaryReader representing the input stream - /// ExeFS header object, null on error - public static ExeFSHeader? Read(BinaryReader reader) - { - var header = new ExeFSHeader(); - - try - { - header.FileHeaders = new ExeFSFileHeader[10]; - for (int i = 0; i < 10; i++) - header.FileHeaders[i] = ExeFSFileHeader.Read(reader)!; - - header.Reserved = reader.ReadBytes(0x20); - - for (int i = 0; i < 10; i++) - header.FileHeaders[9 - i].FileHash = reader.ReadBytes(0x20); - - return header; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/MetaFile.cs b/NDecrypt.N3DS/Headers/MetaFile.cs deleted file mode 100644 index a8473ee..0000000 --- a/NDecrypt.N3DS/Headers/MetaFile.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - internal class MetaFile - { - /// - /// Title ID dependency list - Taken from the application's ExHeader - /// - public byte[]? TitleIDDependencyList { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved1 { get; private set; } - - /// - /// Core Version - /// - public uint CoreVersion { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved2 { get; private set; } - - /// - /// Icon Data(.ICN) - Taken from the application's ExeFS - /// - public byte[]? IconData { get; private set; } - - /// - /// Read from a stream and get the Metafile data, if possible - /// - /// BinaryReader representing the input stream - /// Metafile data object, null on error - public static MetaFile? Read(BinaryReader reader) - { - var metaFile = new MetaFile(); - - try - { - metaFile.TitleIDDependencyList = reader.ReadBytes(0x180); - metaFile.Reserved1 = reader.ReadBytes(0x180); - metaFile.CoreVersion = reader.ReadUInt32(); - metaFile.Reserved2 = reader.ReadBytes(0xFC); - metaFile.IconData = reader.ReadBytes(0x36C0); - - return metaFile; - } - catch - { - return null; - } - } - } -} \ No newline at end of file diff --git a/NDecrypt.N3DS/Headers/NCCHHeader.cs b/NDecrypt.N3DS/Headers/NCCHHeader.cs deleted file mode 100644 index 0a79ae1..0000000 --- a/NDecrypt.N3DS/Headers/NCCHHeader.cs +++ /dev/null @@ -1,248 +0,0 @@ -using System.IO; -using System.Linq; -using System.Numerics; - -namespace NDecrypt.N3DS.Headers -{ - internal class NCCHHeader - { - private const string NCCHMagicNumber = "NCCH"; - - /// - /// Partition number for the current partition - /// - public int PartitionNumber { get; set; } - - /// - /// Partition table entry for the current partition - /// - public PartitionTableEntry? Entry { get; set; } - - /// - /// RSA-2048 signature of the NCCH header, using SHA-256. - /// - public byte[]? RSA2048Signature { get; private set; } - - /// - /// Content size, in media units (1 media unit = 0x200 bytes) - /// - public uint ContentSizeInMediaUnits { get; private set; } - - /// - /// Partition ID - /// - public byte[]? PartitionId { get; private set; } - public byte[]? PlainIV { get { return [.. PartitionId, .. Constants.PlainCounter]; } } - public byte[]? ExeFSIV { get { return [.. PartitionId, .. Constants.ExefsCounter]; } } - public byte[]? RomFSIV { get { return [.. PartitionId, .. Constants.RomfsCounter]; } } - - /// - /// Boot rom key - /// - public BigInteger KeyX { get; set; } - - /// - /// NCCH boot rom key - /// - public BigInteger KeyX2C { get; set; } - - /// - /// Kernel9/Process9 key - /// - public BigInteger KeyY { get; set; } - - /// - /// Normal AES key - /// - public BigInteger NormalKey { get; set; } - - /// - /// NCCH AES key - /// - public BigInteger NormalKey2C { get; set; } - - /// - /// Maker code - /// - public ushort MakerCode { get; private set; } - - /// - /// Version - /// - 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 - /// SHA256 hash. The data used for that hash is 0x18-bytes: [0x10-long title-unique content lock seed] - /// [programID from NCCH + 0x118]. This hash is only used for verification of the content lock seed, and - /// is not the actual keyY. - /// - public uint VerificationHash { get; private set; } - - /// - /// Program ID - /// - public byte[]? ProgramId { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved1 { get; private set; } - - /// - /// Logo Region SHA-256 hash. (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11) - /// - public byte[]? LogoRegionHash { get; private set; } - - /// - /// Product code - /// - public byte[]? ProductCode { get; private set; } - - /// - /// Extended header SHA-256 hash (SHA256 of 2x Alignment Size, beginning at 0x0 of ExHeader) - /// - public byte[]? ExtendedHeaderHash { get; private set; } - - /// - /// Extended header size, in bytes - /// - public uint ExtendedHeaderSizeInBytes { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved2 { get; private set; } - - /// - /// Flags - /// - public NCCHHeaderFlags? Flags { get; private set; } - - /// - /// Plain region offset, in media units - /// - public uint PlainRegionOffsetInMediaUnits { get; private set; } - - /// - /// Plain region size, in media units - /// - public uint PlainRegionSizeInMediaUnits { get; private set; } - - /// - /// Logo Region offset, in media units (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11) - /// - public uint LogoRegionOffsetInMediaUnits { get; private set; } - - /// - /// Logo Region size, in media units (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11) - /// - public uint LogoRegionSizeInMediaUnits { get; private set; } - - /// - /// ExeFS offset, in media units - /// - public uint ExeFSOffsetInMediaUnits { get; private set; } - - /// - /// ExeFS size, in media units - /// - public uint ExeFSSizeInMediaUnits { get; private set; } - - /// - /// ExeFS hash region size, in media units - /// - public uint ExeFSHashRegionSizeInMediaUnits { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved3 { get; private set; } - - /// - /// RomFS offset, in media units - /// - public uint RomFSOffsetInMediaUnits { get; private set; } - - /// - /// RomFS size, in media units - /// - public uint RomFSSizeInMediaUnits { get; private set; } - - /// - /// RomFS hash region size, in media units - /// - public uint RomFSHashRegionSizeInMediaUnits { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved4 { get; private set; } - - /// - /// ExeFS superblock SHA-256 hash - (SHA-256 hash, starting at 0x0 of the ExeFS over the number of - /// media units specified in the ExeFS hash region size) - /// - public byte[]? ExeFSSuperblockHash { get; private set; } - - /// - /// RomFS superblock SHA-256 hash - (SHA-256 hash, starting at 0x0 of the RomFS over the number - /// of media units specified in the RomFS hash region size) - /// - public byte[]? RomFSSuperblockHash { get; private set; } - - /// - /// 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, bool readSignature) - { - var header = new NCCHHeader(); - - try - { - if (readSignature) - header.RSA2048Signature = reader.ReadBytes(0x100); - - if (new string(reader.ReadChars(4)) != NCCHMagicNumber) - return null; - - header.ContentSizeInMediaUnits = reader.ReadUInt32(); - header.PartitionId = reader.ReadBytes(8).Reverse().ToArray(); - 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); - header.ProductCode = reader.ReadBytes(0x10); - header.ExtendedHeaderHash = reader.ReadBytes(0x20); - header.ExtendedHeaderSizeInBytes = reader.ReadUInt32(); - header.Reserved2 = reader.ReadBytes(4); - header.Flags = NCCHHeaderFlags.Read(reader); - header.PlainRegionOffsetInMediaUnits = reader.ReadUInt32(); - header.PlainRegionSizeInMediaUnits = reader.ReadUInt32(); - header.LogoRegionOffsetInMediaUnits = reader.ReadUInt32(); - header.LogoRegionSizeInMediaUnits = reader.ReadUInt32(); - header.ExeFSOffsetInMediaUnits = reader.ReadUInt32(); - header.ExeFSSizeInMediaUnits = reader.ReadUInt32(); - header.ExeFSHashRegionSizeInMediaUnits = reader.ReadUInt32(); - header.Reserved3 = reader.ReadBytes(4); - header.RomFSOffsetInMediaUnits = reader.ReadUInt32(); - header.RomFSSizeInMediaUnits = reader.ReadUInt32(); - header.RomFSHashRegionSizeInMediaUnits = reader.ReadUInt32(); - header.Reserved4 = reader.ReadBytes(4); - header.ExeFSSuperblockHash = reader.ReadBytes(0x20); - header.RomFSSuperblockHash = reader.ReadBytes(0x20); - - return header; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/NCCHHeaderFlags.cs b/NDecrypt.N3DS/Headers/NCCHHeaderFlags.cs deleted file mode 100644 index fb5260e..0000000 --- a/NDecrypt.N3DS/Headers/NCCHHeaderFlags.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - internal class NCCHHeaderFlags - { - /// - /// Reserved - /// - public byte Reserved0 { get; private set; } - - /// - /// Reserved - /// - public byte Reserved1 { get; private set; } - - /// - /// Reserved - /// - public byte Reserved2 { get; private set; } - - /// - /// Crypto Method: When this is non-zero, a NCCH crypto method using two keyslots is used. - /// - public CryptoMethod CryptoMethod { get; private set; } - - /// - /// Content Platform: 1 = CTR, 2 = snake (New 3DS). - /// - public ContentPlatform ContentPlatform { get; private set; } - - /// - /// Content Type Bit-masks: Data = 0x1, Executable = 0x2, SystemUpdate = 0x4, Manual = 0x8, - /// Child = (0x4|0x8), Trial = 0x10. When 'Data' is set, but not 'Executable', NCCH is a CFA. - /// Otherwise when 'Executable' is set, NCCH is a CXI. - /// - public ContentType MediaPlatformIndex { get; private set; } - - /// - /// Content Unit Size i.e. u32 ContentUnitSize = 0x200*2^flags[6]; - /// - public byte ContentUnitSize { get; private set; } - - /// - /// Bit-masks: FixedCryptoKey = 0x1, NoMountRomFs = 0x2, NoCrypto = 0x4, using a new keyY - /// generator = 0x20(starting with FIRM 9.6.0-X). - /// - public BitMasks BitMasks { get; private set; } - - /// - /// Get if the NoCrypto bit is set - /// - public bool PossblyDecrypted { get { return BitMasks.HasFlag(BitMasks.NoCrypto); } } - - /// - /// Read from a stream and get an NCCH header flags, if possible - /// - /// BinaryReader representing the input stream - /// NCCH header flags object, null on error - public static NCCHHeaderFlags? Read(BinaryReader reader) - { - var flags = new NCCHHeaderFlags(); - - try - { - flags.Reserved0 = reader.ReadByte(); - flags.Reserved1 = reader.ReadByte(); - flags.Reserved2 = reader.ReadByte(); - flags.CryptoMethod = (CryptoMethod)reader.ReadByte(); - flags.ContentPlatform = (ContentPlatform)reader.ReadByte(); - flags.MediaPlatformIndex = (ContentType)reader.ReadByte(); - flags.ContentUnitSize = reader.ReadByte(); - flags.BitMasks = (BitMasks)reader.ReadByte(); - return flags; - } - catch - { - return null; - } - } - - /// - /// Write NCCH header flags to stream, if possible - /// - /// BinaryWriter representing the output stream - public void Write(BinaryWriter writer) - { - try - { - writer.Write(this.Reserved0); - writer.Write(this.Reserved1); - writer.Write(this.Reserved2); - writer.Write((byte)this.CryptoMethod); - writer.Write((byte)this.ContentPlatform); - writer.Write((byte)this.MediaPlatformIndex); - writer.Write((byte)this.ContentUnitSize); - writer.Write((byte)this.BitMasks); - - } - catch { } - } - } -} diff --git a/NDecrypt.N3DS/Headers/NCSDHeader.cs b/NDecrypt.N3DS/Headers/NCSDHeader.cs deleted file mode 100644 index 71c55f4..0000000 --- a/NDecrypt.N3DS/Headers/NCSDHeader.cs +++ /dev/null @@ -1,326 +0,0 @@ -using System; -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - internal class NCSDHeader - { - private const string NCSDMagicNumber = "NCSD"; - - #region Common to all NCSD files - - /// - /// RSA-2048 SHA-256 signature of the NCSD header - /// - public byte[]? RSA2048Signature { get; private set; } - - /// - /// Size of the NCSD image, in media units (1 media unit = 0x200 bytes) - /// - public uint ImageSizeInMediaUnits { get; private set; } - - /// - /// Media ID - /// - public byte[]? MediaId { get; private set; } - - /// - /// Partitions FS type (0=None, 1=Normal, 3=FIRM, 4=AGB_FIRM save) - /// - public FilesystemType PartitionsFSType { get; private set; } - - /// - /// Partitions crypt type (each byte corresponds to a partition in the partition table) - /// - public byte[]? PartitionsCryptType { get; private set; } - - /// - /// Offset & Length partition table, in media units - /// - 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 CTR Cart Image (CCI) Specific - - /// - /// Exheader SHA-256 hash - /// - public byte[]? ExheaderHash { get; private set; } - - /// - /// Additional header size - /// - public uint AdditionalHeaderSize { get; private set; } - - /// - /// Sector zero offset - /// - public uint SectorZeroOffset { get; private set; } - - /// - /// Partition Flags - /// - 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 - /// - public byte[][]? PartitionIdTable { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved1 { get; private set; } - - /// - /// Reserved? - /// - public byte[]? Reserved2 { get; private set; } - - /// - /// Support for this was implemented with 9.6.0-X FIRM. Bit0=1 enables using bits 1-2, it's unknown - /// what these two bits are actually used for(the value of these two bits get compared with some other - /// value during NCSD verification/loading). This appears to enable a new, likely hardware-based, - /// antipiracy check on cartridges. - /// - public byte FirmUpdateByte1 { get; private set; } - - /// - /// Support for this was implemented with 9.6.0-X FIRM, see below regarding save crypto. - /// - public byte FirmUpdateByte2 { get; private set; } - - #endregion - - #region Raw NAND Format Specific - - /// - /// Unknown - /// - public byte[]? Unknown { get; private set; } - - /// - /// Encrypted MBR partition-table, for the TWL partitions(key-data used for this keyslot is console-unique). - /// - public byte[]? EncryptedMBR { get; private set; } - - #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, bool development) - { - var header = new NCSDHeader(); - - try - { - header.RSA2048Signature = reader.ReadBytes(0x100); - - if (new string(reader.ReadChars(4)) != NCSDMagicNumber) - return null; - - header.ImageSizeInMediaUnits = reader.ReadUInt32(); - header.MediaId = reader.ReadBytes(8); - header.PartitionsFSType = (FilesystemType)reader.ReadUInt64(); - header.PartitionsCryptType = reader.ReadBytes(8); - - header.PartitionsTable = new PartitionTableEntry[8]; - for (int i = 0; i < 8; i++) - header.PartitionsTable[i] = PartitionTableEntry.Read(reader)!; - - if (header.PartitionsFSType == FilesystemType.Normal - || header.PartitionsFSType == FilesystemType.None) - { - header.ExheaderHash = reader.ReadBytes(0x20); - header.AdditionalHeaderSize = reader.ReadUInt32(); - header.SectorZeroOffset = reader.ReadUInt32(); - header.PartitionFlags = reader.ReadBytes(8); - - header.PartitionIdTable = new byte[8][]; - for (int i = 0; i < 8; i++) - header.PartitionIdTable[i] = reader.ReadBytes(8); - - header.Reserved1 = reader.ReadBytes(0x20); - header.Reserved2 = reader.ReadBytes(0xE); - header.FirmUpdateByte1 = 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(0xCEC); // Incorrectly documented as 0xCEE - header.CardSeedKeyY = reader.ReadBytes(0x10); - header.EncryptedCardSeed = reader.ReadBytes(0x10); - header.CardSeedAESMAC = reader.ReadBytes(0x10); - header.CardSeedNonce = reader.ReadBytes(0xC); - header.Reserved5 = reader.ReadBytes(0xC4); - header.BackupHeader = NCCHHeader.Read(reader, readSignature: false); - - if (development) - { - header.CardDeviceReserved1 = reader.ReadBytes(0x200); - header.TitleKey = reader.ReadBytes(0x10); - header.CardDeviceReserved2 = reader.ReadBytes(0xF0); - } - } - else if (header.PartitionsFSType == FilesystemType.FIRM) - { - header.Unknown = reader.ReadBytes(0x5E); - header.EncryptedMBR = reader.ReadBytes(0x42); - } - - return header; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/PartitionTableEntry.cs b/NDecrypt.N3DS/Headers/PartitionTableEntry.cs deleted file mode 100644 index 38c0308..0000000 --- a/NDecrypt.N3DS/Headers/PartitionTableEntry.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - internal class PartitionTableEntry - { - /// - /// Offset - /// - public uint Offset { get; set; } - - /// - /// Length - /// - public uint Length { get; set; } - - /// - /// Read from a stream and get partition table entry, if possible - /// - /// BinaryReader representing the input stream - /// Partition table entry object, null on error - public static PartitionTableEntry? Read(BinaryReader reader) - { - var entry = new PartitionTableEntry(); - - try - { - entry.Offset = reader.ReadUInt32(); - entry.Length = reader.ReadUInt32(); - return entry; - } - catch - { - return null; - } - } - - /// - /// Check for a valid partition - /// - /// True if the offset and length are not 0, false otherwise - public bool IsValid() - { - return Offset != 0 && Length != 0; - } - } -} diff --git a/NDecrypt.N3DS/Headers/RomFSHeader.cs b/NDecrypt.N3DS/Headers/RomFSHeader.cs deleted file mode 100644 index 301b8c9..0000000 --- a/NDecrypt.N3DS/Headers/RomFSHeader.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - // https://www.3dbrew.org/wiki/RomFS - internal class RomFSHeader - { - private const string RomFSMagicNumber = "IVFC"; - private const uint RomFSSecondMagicNumber = 0x10000; - - /// - /// Master hash size - /// - public uint MasterHashSize { get; private set; } - - /// - /// Level 1 logical offset - /// - public ulong Level1LogicalOffset { get; private set; } - - /// - /// Level 1 hashdata size - /// - public ulong Level1HashdataSize { get; private set; } - - /// - /// Level 1 block size, in log2 - /// - public uint Level1BlockSizeLog2 { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved1 { get; private set; } - - /// - /// Level 2 logical offset - /// - public ulong Level2LogicalOffset { get; private set; } - - /// - /// Level 2 hashdata size - /// - public ulong Level2HashdataSize { get; private set; } - - /// - /// Level 2 block size, in log2 - /// - public uint Level2BlockSizeLog2 { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved2 { get; private set; } - - /// - /// Level 3 logical offset - /// - public ulong Level3LogicalOffset { get; private set; } - - /// - /// Level 3 hashdata size - /// - public ulong Level3HashdataSize { get; private set; } - - /// s - /// Level 3 block size, in log2 - /// - public uint Level3BlockSizeLog2 { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved3 { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved4 { get; private set; } - - /// - /// Optional info size. - /// - public uint OptionalInfoSize { get; private set; } - - /// - /// Read from a stream and get a RomFS header, if possible - /// - /// BinaryReader representing the input stream - /// RomFS header object, null on error - public static RomFSHeader? Read(BinaryReader reader) - { - var header = new RomFSHeader(); - - try - { - if (new string(reader.ReadChars(4)) != RomFSMagicNumber) - return null; - - if (reader.ReadUInt32() != RomFSSecondMagicNumber) - return null; - - header.MasterHashSize = reader.ReadUInt32(); - header.Level1LogicalOffset = reader.ReadUInt64(); - header.Level1HashdataSize = reader.ReadUInt64(); - header.Level1BlockSizeLog2 = reader.ReadUInt32(); - header.Reserved1 = reader.ReadBytes(4); - header.Level2LogicalOffset = reader.ReadUInt64(); - header.Level2HashdataSize = reader.ReadUInt64(); - header.Level2BlockSizeLog2 = reader.ReadUInt32(); - header.Reserved2 = reader.ReadBytes(4); - header.Level3LogicalOffset = reader.ReadUInt64(); - header.Level3HashdataSize = reader.ReadUInt64(); - header.Level3BlockSizeLog2 = reader.ReadUInt32(); - header.Reserved3 = reader.ReadBytes(4); - header.Reserved4 = reader.ReadBytes(4); - header.OptionalInfoSize = reader.ReadUInt32(); - - return header; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/StorageInfo.cs b/NDecrypt.N3DS/Headers/StorageInfo.cs deleted file mode 100644 index 83a1028..0000000 --- a/NDecrypt.N3DS/Headers/StorageInfo.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - internal class StorageInfo - { - /// - /// Extdata ID - /// - public byte[]? ExtdataID { get; private set; } - - /// - /// System savedata IDs - /// - public byte[]? SystemSavedataIDs { get; private set; } - - /// - /// Storage accessible unique IDs - /// - public byte[]? StorageAccessibleUniqueIDs { get; private set; } - - /// - /// Filesystem access info - /// - public byte[]? FilesystemAccessInfo { get; private set; } - - /// - /// Other attributes - /// - public StorageInfoOtherAttributes OtherAttributes { get; private set; } - - /// - /// Read from a stream and get storage info, if possible - /// - /// BinaryReader representing the input stream - /// Storage info object, null on error - public static StorageInfo? Read(BinaryReader reader) - { - var si = new StorageInfo(); - - try - { - si.ExtdataID = reader.ReadBytes(8); - si.SystemSavedataIDs = reader.ReadBytes(8); - si.StorageAccessibleUniqueIDs = reader.ReadBytes(8); - si.FilesystemAccessInfo = reader.ReadBytes(7); - si.OtherAttributes = (StorageInfoOtherAttributes)reader.ReadByte(); - return si; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/SystemControlInfo.cs b/NDecrypt.N3DS/Headers/SystemControlInfo.cs deleted file mode 100644 index 55b00cb..0000000 --- a/NDecrypt.N3DS/Headers/SystemControlInfo.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - internal class SystemControlInfo - { - /// - /// Application title (default is "CtrApp") - /// - public char[]? ApplicationTitle { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved1 { get; private set; } - - /// - /// Flag (bit 0: CompressExefsCode, bit 1: SDApplication) - /// - public byte Flag { get; private set; } - - /// - /// Remaster version - /// - public byte[]? RemasterVersion { get; private set; } - - /// - /// Text code set info - /// - public CodeSetInfo? TextCodesetInfo { get; private set; } - - /// - /// Stack size - /// - public uint StackSize { get; private set; } - - /// - /// Read-only code set info - /// - public CodeSetInfo? ReadOnlyCodeSetInfo { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved2 { get; private set; } - - /// - /// Data code set info - /// - public CodeSetInfo? DataCodeSetInfo { get; private set; } - - /// - /// BSS size - /// - public uint BSSSize { get; private set; } - - /// - /// Dependency module (program ID) list - /// - public byte[][]? DependencyModuleList { get; private set; } - - /// - /// SystemInfo - /// - public SystemInfo? SystemInfo { get; private set; } - - /// - /// Read from a stream and get system control info, if possible - /// - /// BinaryReader representing the input stream - /// System control info object, null on error - public static SystemControlInfo? Read(BinaryReader reader) - { - var sci = new SystemControlInfo(); - - try - { - sci.ApplicationTitle = reader.ReadChars(8); - sci.Reserved1 = reader.ReadBytes(5); - sci.Flag = reader.ReadByte(); - sci.RemasterVersion = reader.ReadBytes(2); - sci.TextCodesetInfo = CodeSetInfo.Read(reader); - sci.StackSize = reader.ReadUInt32(); - sci.ReadOnlyCodeSetInfo = CodeSetInfo.Read(reader); - sci.Reserved2 = reader.ReadBytes(4); - sci.DataCodeSetInfo = CodeSetInfo.Read(reader); - sci.BSSSize = reader.ReadUInt32(); - - sci.DependencyModuleList = new byte[48][]; - for (int i = 0; i < 48; i++) - sci.DependencyModuleList[i] = reader.ReadBytes(8); - - sci.SystemInfo = SystemInfo.Read(reader); - return sci; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/SystemInfo.cs b/NDecrypt.N3DS/Headers/SystemInfo.cs deleted file mode 100644 index f72d48c..0000000 --- a/NDecrypt.N3DS/Headers/SystemInfo.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.IO; - -namespace NDecrypt.N3DS.Headers -{ - internal class SystemInfo - { - /// - /// SaveData Size - /// - public ulong SaveDataSize { get; private set; } - - /// - /// Jump ID - /// - public byte[]? JumpID { get; private set; } - - /// - /// Reserved - /// - public byte[]? Reserved { get; private set; } - - /// - /// Read from a stream and get system info, if possible - /// - /// BinaryReader representing the input stream - /// System info object, null on error - public static SystemInfo? Read(BinaryReader reader) - { - var si = new SystemInfo(); - - try - { - si.SaveDataSize = reader.ReadUInt64(); - si.JumpID = reader.ReadBytes(8); - si.Reserved = reader.ReadBytes(0x30); - return si; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/Ticket.cs b/NDecrypt.N3DS/Headers/Ticket.cs deleted file mode 100644 index a526b70..0000000 --- a/NDecrypt.N3DS/Headers/Ticket.cs +++ /dev/null @@ -1,279 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Text; - -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; } - - /// - /// Issuer as a trimmed string - /// - public string? IssuerString => Issuer != null && Issuer.Length > 0 - ? Encoding.ASCII.GetString(Issuer)?.TrimEnd('\0') - : null; - - /// - /// 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 int[]? Limits { get; private set; } - - /// - /// Denotes if the ticket denotes a demo or not - /// - public bool IsDemo => Limits != null && Limits.Length > 0 ? Limits[0] == 0x0004 : false; - - /// - /// Denotes if the max playcount for a demo - /// - public int PlayCount => Limits != null && Limits.Length > 1 ? Limits[1] : 0; - - /// - /// 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 int ContentIndexSize { get; private set; } - - /// - /// Content Index - /// - public byte[]? ContentIndex { get; private set; } - - /// - /// Certificate chain - /// - /// - /// https://www.3dbrew.org/wiki/Ticket#Certificate_Chain - /// - public Certificate[]? CertificateChain { get; 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) - { - var tk = new Ticket(); - - try - { - long startingPosition = reader.BaseStream.Position; - - 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; - default: - return null; - } - - 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 = new int[0x10]; - for (int i = 0; i < 0x10; i++) - { - tk.Limits[i] = 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); - if (reader.BaseStream.Position % 64 != 0) - reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current); - - if (ticketSize > (reader.BaseStream.Position - startingPosition) + (2 * 0x200)) - { - tk.CertificateChain = new Certificate[2]; - tk.CertificateChain[0] = Certificate.Read(reader)!; // Ticket - tk.CertificateChain[1] = Certificate.Read(reader)!; // CA - } - - return tk; - } - catch - { - return null; - } - } - } -} diff --git a/NDecrypt.N3DS/Headers/TitleMetadata.cs b/NDecrypt.N3DS/Headers/TitleMetadata.cs deleted file mode 100644 index 26f8754..0000000 --- a/NDecrypt.N3DS/Headers/TitleMetadata.cs +++ /dev/null @@ -1,241 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Text; - -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; } - - /// - /// Signature Issuer as a trimmed string - /// - public string? SignatureIssuerString => SignatureIssuer != null && SignatureIssuer.Length > 0 - ? Encoding.ASCII.GetString(SignatureIssuer)?.TrimEnd('\0') - : null; - - /// - /// 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; } - - /// - /// Certificate chain - /// - /// - /// https://www.3dbrew.org/wiki/Title_metadata#Certificate_Chain - /// - public Certificate[]? CertificateChain { get; 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) - { - var tm = new TitleMetadata(); - - try - { - long startingPosition = reader.BaseStream.Position; - - 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 = BitConverter.ToUInt16(reader.ReadBytes(2).Reverse().ToArray(), 0); - 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)!; - } - - if (metadataSize > (reader.BaseStream.Position - startingPosition) + (2 * 0x200)) - { - tm.CertificateChain = new Certificate[2]; - tm.CertificateChain[0] = Certificate.Read(reader)!; // TMD - tm.CertificateChain[1] = Certificate.Read(reader)!; // CA - } - - return tm; - } - catch - { - return null; - } - } - } -} \ No newline at end of file diff --git a/NDecrypt.N3DS/NDecrypt.N3DS.csproj b/NDecrypt.N3DS/NDecrypt.N3DS.csproj index 016b4b9..dc199e9 100644 --- a/NDecrypt.N3DS/NDecrypt.N3DS.csproj +++ b/NDecrypt.N3DS/NDecrypt.N3DS.csproj @@ -39,4 +39,8 @@ + + + + diff --git a/NDecrypt.N3DS/Serializer.cs b/NDecrypt.N3DS/Serializer.cs new file mode 100644 index 0000000..046abe8 --- /dev/null +++ b/NDecrypt.N3DS/Serializer.cs @@ -0,0 +1,1048 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using SabreTools.Models.N3DS; + +namespace NDecrypt.N3DS +{ + internal static class Serializer + { + #region Constants + + private const string NCCHMagicNumber = "NCCH"; + private const string NCSDMagicNumber = "NCSD"; + private const string RomFSMagicNumber = "IVFC"; + private const uint RomFSSecondMagicNumber = 0x10000; + + #endregion + + #region Reading + + /// + /// Read from a stream and get access control info, if possible + /// + /// BinaryReader representing the input stream + /// Access control info object, null on error + public static AccessControlInfo? ReadAccessControlInfo(BinaryReader reader) + { + var aci = new AccessControlInfo(); + + try + { + aci.ARM11LocalSystemCapabilities = ReadARM11LocalSystemCapabilities(reader); + aci.ARM11KernelCapabilities = ReadARM11KernelCapabilities(reader); + aci.ARM9AccessControl = ReadARM9AccessControl(reader); + return aci; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get ARM9 access control, if possible + /// + /// BinaryReader representing the input stream + /// ARM9 access control object, null on error + public static ARM9AccessControl? ReadARM9AccessControl(BinaryReader reader) + { + var ac = new ARM9AccessControl(); + + try + { + ac.Descriptors = new byte[15]; // TODO: Implement ARM9AccessControlDescriptors in Models + for (int i = 0; i < 15; i++) + { + ac.Descriptors[i] = reader.ReadByte(); + } + + ac.DescriptorVersion = reader.ReadByte(); + return ac; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get ARM11 kernel capabilities, if possible + /// + /// BinaryReader representing the input stream + /// ARM11 kernel capabilities object, null on error + public static ARM11KernelCapabilities? ReadARM11KernelCapabilities(BinaryReader reader) + { + var kc = new ARM11KernelCapabilities(); + + try + { + kc.Descriptors = new uint[28]; + for (int i = 0; i < 28; i++) + { + kc.Descriptors[i] = reader.ReadUInt32(); + } + + kc.Reserved = reader.ReadBytes(0x10); + return kc; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get ARM11 local system capabilities, if possible + /// + /// BinaryReader representing the input stream + /// ARM11 local system capabilities object, null on error + public static ARM11LocalSystemCapabilities? ReadARM11LocalSystemCapabilities(BinaryReader reader) + { + var lsc = new ARM11LocalSystemCapabilities(); + + try + { + lsc.ProgramID = reader.ReadUInt64(); + lsc.CoreVersion = reader.ReadUInt32(); + lsc.Flag1 = (ARM11LSCFlag1)reader.ReadByte(); + lsc.Flag2 = (ARM11LSCFlag2)reader.ReadByte(); + lsc.Flag0 = (ARM11LSCFlag0)reader.ReadByte(); + lsc.Priority = reader.ReadByte(); + + lsc.ResourceLimitDescriptors = new ushort[16]; + for (int i = 0; i < 16; i++) + { + lsc.ResourceLimitDescriptors[i] = reader.ReadUInt16(); + } + + lsc.StorageInfo = ReadStorageInfo(reader); + + lsc.ServiceAccessControl = new ulong[32]; + for (int i = 0; i < 32; i++) + { + lsc.ServiceAccessControl[i] = reader.ReadUInt64(); + } + + lsc.ExtendedServiceAccessControl = new ulong[2]; + for (int i = 0; i < 2; i++) + { + lsc.ExtendedServiceAccessControl[i] = reader.ReadUInt64(); + } + + lsc.Reserved = reader.ReadBytes(0xF); + lsc.ResourceLimitCategory = (ResourceLimitCategory)reader.ReadByte(); + return lsc; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get N3DS cart image, if possible + /// + /// BinaryReader representing the input stream + /// True if development cart, false otherwise + /// N3DS cart image object, null on error + public static (Cart?, NCCHHeader?) ReadCart(BinaryReader reader, bool development) + { + var cart = new Cart(); + NCCHHeader? backupHeader = null; + + try + { + cart.Header = ReadNCSDHeader(reader); + if (cart.Header == null) + return (null, null); + + if (cart.Header.PartitionsFSType == FilesystemType.Normal + || cart.Header.PartitionsFSType == FilesystemType.None) + { + cart.CardInfoHeader = ReadCardInfoHeader(reader); + if (cart.CardInfoHeader == null) + return (null, null); + + // TODO: Undocumented in current model? + backupHeader = ReadNCCHHeader(reader, readSignature: false); + + if (development) + { + cart.DevelopmentCardInfoHeader = ReadDevelopmentCardInfoHeader(reader); + if (cart.DevelopmentCardInfoHeader == null) + return (null, null); + } + } + + return (cart, backupHeader); + } + catch + { + return (null, null); ; + } + } + + /// + /// Read from a stream and get an CardInfo header, if possible + /// + /// BinaryReader representing the input stream + /// CardInfo header object, null on error + public static CardInfoHeader? ReadCardInfoHeader(BinaryReader reader) + { + var header = new CardInfoHeader(); + + try + { + header.WritableAddressMediaUnits = reader.ReadUInt32(); + header.CardInfoBitmask = reader.ReadUInt32(); + header.Reserved3 = reader.ReadBytes(0x108); + header.TitleVersion = reader.ReadUInt16(); + header.CardRevision = reader.ReadUInt16(); + header.Reserved4 = reader.ReadBytes(0xCEC); // Incorrectly documented as 0xCEE + + // TODO: Undocumented in current model? + _ = reader.ReadBytes(0x10); // header.CardSeedKeyY + _ = reader.ReadBytes(0x10); // header.EncryptedCardSeed + _ = reader.ReadBytes(0x10); // header.CardSeedAESMAC + _ = reader.ReadBytes(0x0C); // header.CardSeedNonce + _ = reader.ReadBytes(0xC4); // header.Reserved5 + + return header; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get certificate, if possible + /// + /// BinaryReader representing the input stream + /// Certificate object, null on error + public static Certificate? ReadCertificate(BinaryReader reader) + { + var 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; + default: + return null; + } + + ct.Signature = reader.ReadBytes(ct.SignatureSize); + reader.ReadBytes(ct.PaddingSize); // Padding + byte[] issuerBytes = reader.ReadBytes(0x40); + ct.Issuer = Encoding.ASCII.GetString(issuerBytes); + ct.KeyType = (PublicKeyType)reader.ReadUInt32(); + byte[] nameBytes = reader.ReadBytes(0x40); + ct.Name = Encoding.ASCII.GetString(nameBytes); + ct.ExpirationTime = reader.ReadUInt32(); + + switch (ct.KeyType) + { + case PublicKeyType.RSA_4096: + ct.RSAModulus = reader.ReadBytes(0x200); + ct.RSAPublicExponent = reader.ReadUInt32(); + ct.RSAPadding = reader.ReadBytes(0x34); + break; + case PublicKeyType.RSA_2048: + ct.RSAModulus = reader.ReadBytes(0x100); + ct.RSAPublicExponent = reader.ReadUInt32(); + ct.RSAPadding = reader.ReadBytes(0x34); + break; + case PublicKeyType.EllipticCurve: + ct.ECCPublicKey = reader.ReadBytes(0x3C); + ct.ECCPadding = reader.ReadBytes(0x3C); + break; + default: + return null; + } + + return ct; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get a CIA header, if possible + /// + /// BinaryReader representing the input stream + /// CIA header object, null on error + public static CIA? ReadCIAHeader(BinaryReader reader) + { + var cia = new CIA(); + + try + { + var header = new CIAHeader(); + + header.HeaderSize = reader.ReadUInt32(); + header.Type = reader.ReadUInt16(); + header.Version = reader.ReadUInt16(); + header.CertificateChainSize = reader.ReadUInt32(); + header.TicketSize = reader.ReadUInt32(); + header.TMDFileSize = reader.ReadUInt32(); + header.MetaSize = reader.ReadUInt32(); + header.ContentSize = reader.ReadUInt64(); + header.ContentIndex = reader.ReadBytes(0x2000); + + cia.Header = header; + + var certificateChain = new Certificate[3]; + if (reader.BaseStream.Position % 64 != 0) + reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current); + + certificateChain[0] = ReadCertificate(reader)!; // CA + certificateChain[1] = ReadCertificate(reader)!; // Ticket + certificateChain[2] = ReadCertificate(reader)!; // TMD + if (reader.BaseStream.Position % 64 != 0) + reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current); + + cia.CertificateChain = certificateChain; + + cia.Ticket = ReadTicket(reader, header.TicketSize); + if (reader.BaseStream.Position % 64 != 0) + reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current); + + cia.TMDFileData = ReadTitleMetadata(reader, header.TMDFileSize); + if (reader.BaseStream.Position % 64 != 0) + reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current); + + long startingPosition = reader.BaseStream.Position; + var headers = new List(); + while ((ulong)reader.BaseStream.Position < (ulong)startingPosition + header.ContentSize) + { + long initPosition = reader.BaseStream.Position; + var ncchHeader = ReadNCCHHeader(reader, readSignature: true); + if (ncchHeader == null) + break; + + headers.Add(ncchHeader); + reader.BaseStream.Seek(initPosition + ncchHeader.ContentSizeInMediaUnits * 0x200, SeekOrigin.Begin); + } + + cia.Partitions = [.. headers]; + if (header.MetaSize > 0) + cia.MetaData = ReadMetaData(reader); + + return cia; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get code set info, if possible + /// + /// BinaryReader representing the input stream + /// Code set info object, null on error + public static CodeSetInfo? ReadCodeSetInfo(BinaryReader reader) + { + var csi = new CodeSetInfo(); + + try + { + csi.Address = reader.ReadUInt32(); + csi.PhysicalRegionSizeInPages = reader.ReadUInt32(); + csi.SizeInBytes = reader.ReadUInt32(); + return csi; + } + catch + { + return null; + } + } + + /// + /// 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? ReadContentChunkRecord(BinaryReader reader) + { + var 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; + } + } + + /// + /// 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? ReadContentInfoRecord(BinaryReader reader) + { + var cir = new ContentInfoRecord(); + + try + { + cir.ContentIndexOffset = reader.ReadUInt16(); + cir.ContentCommandCount = reader.ReadUInt16(); + cir.UnhashedContentRecordsSHA256Hash = reader.ReadBytes(0x20); + return cir; + } + catch + { + return null; + } + } + + // TODO: Create model for this + /* + /// + /// Read from a stream and get a CXI extended header, if possible + /// + /// BinaryReader representing the input stream + /// CXI extended header object, null on error + public static CXIExtendedHeader? ReadCXIExtendedHeader(BinaryReader reader) + { + var header = new CXIExtendedHeader(); + + try + { + header.SCI = ReadSystemControlInfo(reader); + header.ACI = ReadAccessControlInfo(reader); + header.AccessDescSignature = reader.ReadBytes(0x100); + header.NCCHHDRPublicKey = reader.ReadBytes(0x100); + header.ACIForLimitations = ReadAccessControlInfo(reader); + return header; + } + catch + { + return null; + } + } + */ + + /// + /// Read from a stream and get an DevelopmentCardInfo header, if possible + /// + /// BinaryReader representing the input stream + /// DevelopmentCardInfo object, null on error + public static DevelopmentCardInfoHeader? ReadDevelopmentCardInfoHeader(BinaryReader reader) + { + var header = new DevelopmentCardInfoHeader(); + + try + { + header.CardDeviceReserved1 = reader.ReadBytes(0x200); + header.TitleKey = reader.ReadBytes(0x10); + header.CardDeviceReserved2 = reader.ReadBytes(0xF0); + + return header; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get an ExeFS file header, if possible + /// + /// BinaryReader representing the input stream + /// ExeFS file header object, null on error + public static ExeFSFileHeader? ReadExeFSFileHeader(BinaryReader reader) + { + var header = new ExeFSFileHeader(); + + try + { + byte[] fileNameBytes = reader.ReadBytes(8); + header.FileName = Encoding.ASCII.GetString(fileNameBytes); + header.FileOffset = reader.ReadUInt32(); + header.FileSize = reader.ReadUInt32(); + return header; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get an ExeFS header, if possible + /// + /// BinaryReader representing the input stream + /// ExeFS header object, null on error + public static ExeFSHeader? ReadExeFSHeader(BinaryReader reader) + { + var header = new ExeFSHeader(); + + try + { + header.FileHeaders = new ExeFSFileHeader[10]; + for (int i = 0; i < 10; i++) + { + header.FileHeaders[i] = ReadExeFSFileHeader(reader)!; + } + + header.Reserved = reader.ReadBytes(0x20); + + header.FileHashes = new byte[10][]; + for (int i = 0; i < 10; i++) + { + header.FileHashes[9 - i] = reader.ReadBytes(0x20); + } + + return header; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get the Metafile data, if possible + /// + /// BinaryReader representing the input stream + /// Metafile data object, null on error + public static MetaData? ReadMetaData(BinaryReader reader) + { + var metaData = new MetaData(); + + try + { + metaData.TitleIDDependencyList = reader.ReadBytes(0x180); + metaData.Reserved1 = reader.ReadBytes(0x180); + metaData.CoreVersion = reader.ReadUInt32(); + metaData.Reserved2 = reader.ReadBytes(0xFC); + metaData.IconData = reader.ReadBytes(0x36C0); + + return metaData; + } + catch + { + return null; + } + } + + /// + /// 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? ReadNCCHHeader(BinaryReader reader, bool readSignature) + { + var header = new NCCHHeader(); + + try + { + if (readSignature) + header.RSA2048Signature = reader.ReadBytes(0x100); + + if (new string(reader.ReadChars(4)) != NCCHMagicNumber) + return null; + + header.ContentSizeInMediaUnits = reader.ReadUInt32(); + header.PartitionId = reader.ReadUInt64(); + 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); + byte[] productCodeBytes = reader.ReadBytes(0x10); + header.ProductCode = Encoding.ASCII.GetString(productCodeBytes); + header.ExtendedHeaderHash = reader.ReadBytes(0x20); + header.ExtendedHeaderSizeInBytes = reader.ReadUInt32(); + header.Reserved2 = reader.ReadUInt32(); + header.Flags = ReadNCCHHeaderFlags(reader); + header.PlainRegionOffsetInMediaUnits = reader.ReadUInt32(); + header.PlainRegionSizeInMediaUnits = reader.ReadUInt32(); + header.LogoRegionOffsetInMediaUnits = reader.ReadUInt32(); + header.LogoRegionSizeInMediaUnits = reader.ReadUInt32(); + header.ExeFSOffsetInMediaUnits = reader.ReadUInt32(); + header.ExeFSSizeInMediaUnits = reader.ReadUInt32(); + header.ExeFSHashRegionSizeInMediaUnits = reader.ReadUInt32(); + header.Reserved3 = reader.ReadUInt32(); + header.RomFSOffsetInMediaUnits = reader.ReadUInt32(); + header.RomFSSizeInMediaUnits = reader.ReadUInt32(); + header.RomFSHashRegionSizeInMediaUnits = reader.ReadUInt32(); + header.Reserved4 = reader.ReadUInt32(); + header.ExeFSSuperblockHash = reader.ReadBytes(0x20); + header.RomFSSuperblockHash = reader.ReadBytes(0x20); + + return header; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get an NCCH header flags, if possible + /// + /// BinaryReader representing the input stream + /// NCCH header flags object, null on error + public static NCCHHeaderFlags? ReadNCCHHeaderFlags(BinaryReader reader) + { + var flags = new NCCHHeaderFlags(); + + try + { + flags.Reserved0 = reader.ReadByte(); + flags.Reserved1 = reader.ReadByte(); + flags.Reserved2 = reader.ReadByte(); + flags.CryptoMethod = (CryptoMethod)reader.ReadByte(); + flags.ContentPlatform = (ContentPlatform)reader.ReadByte(); + flags.MediaPlatformIndex = (ContentType)reader.ReadByte(); + flags.ContentUnitSize = reader.ReadByte(); + flags.BitMasks = (BitMasks)reader.ReadByte(); + return flags; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get an NCSD header, if possible + /// + /// BinaryReader representing the input stream + /// NCSD header object, null on error + public static NCSDHeader? ReadNCSDHeader(BinaryReader reader) + { + var header = new NCSDHeader(); + + try + { + header.RSA2048Signature = reader.ReadBytes(0x100); + + if (new string(reader.ReadChars(4)) != NCSDMagicNumber) + return null; + + header.ImageSizeInMediaUnits = reader.ReadUInt32(); + header.MediaId = reader.ReadBytes(8); + header.PartitionsFSType = (FilesystemType)reader.ReadUInt64(); + header.PartitionsCryptType = reader.ReadBytes(8); + + header.PartitionsTable = new PartitionTableEntry[8]; + for (int i = 0; i < 8; i++) + { + header.PartitionsTable[i] = ReadPartitionTableEntry(reader)!; + } + + if (header.PartitionsFSType == FilesystemType.Normal + || header.PartitionsFSType == FilesystemType.None) + { + header.ExheaderHash = reader.ReadBytes(0x20); + header.AdditionalHeaderSize = reader.ReadUInt32(); + header.SectorZeroOffset = reader.ReadUInt32(); + header.PartitionFlags = reader.ReadBytes(8); + + header.PartitionIdTable = new ulong[8]; + for (int i = 0; i < 8; i++) + { + header.PartitionIdTable[i] = reader.ReadUInt64(); + } + + header.Reserved1 = reader.ReadBytes(0x20); + header.Reserved2 = reader.ReadBytes(0xE); + header.FirmUpdateByte1 = reader.ReadByte(); + header.FirmUpdateByte2 = reader.ReadByte(); + } + else if (header.PartitionsFSType == FilesystemType.FIRM) + { + header.Unknown = reader.ReadBytes(0x5E); + header.EncryptedMBR = reader.ReadBytes(0x42); + } + + return header; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get partition table entry, if possible + /// + /// BinaryReader representing the input stream + /// Partition table entry object, null on error + public static PartitionTableEntry? ReadPartitionTableEntry(BinaryReader reader) + { + var entry = new PartitionTableEntry(); + + try + { + entry.Offset = reader.ReadUInt32(); + entry.Length = reader.ReadUInt32(); + return entry; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get a RomFS header, if possible + /// + /// BinaryReader representing the input stream + /// RomFS header object, null on error + public static RomFSHeader? ReadRomFSHeader(BinaryReader reader) + { + var header = new RomFSHeader(); + + try + { + if (new string(reader.ReadChars(4)) != RomFSMagicNumber) + return null; + + if (reader.ReadUInt32() != RomFSSecondMagicNumber) + return null; + + header.MasterHashSize = reader.ReadUInt32(); + header.Level1LogicalOffset = reader.ReadUInt64(); + header.Level1HashdataSize = reader.ReadUInt64(); + header.Level1BlockSizeLog2 = reader.ReadUInt32(); + header.Reserved1 = reader.ReadUInt32(); + header.Level2LogicalOffset = reader.ReadUInt64(); + header.Level2HashdataSize = reader.ReadUInt64(); + header.Level2BlockSizeLog2 = reader.ReadUInt32(); + header.Reserved2 = reader.ReadUInt32(); + header.Level3LogicalOffset = reader.ReadUInt64(); + header.Level3HashdataSize = reader.ReadUInt64(); + header.Level3BlockSizeLog2 = reader.ReadUInt32(); + header.Reserved3 = reader.ReadUInt32(); + header.Reserved4 = reader.ReadUInt32(); + header.OptionalInfoSize = reader.ReadUInt32(); + + return header; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get storage info, if possible + /// + /// BinaryReader representing the input stream + /// Storage info object, null on error + public static StorageInfo? ReadStorageInfo(BinaryReader reader) + { + var si = new StorageInfo(); + + try + { + si.ExtdataID = reader.ReadUInt64(); + si.SystemSavedataIDs = reader.ReadBytes(8); + si.StorageAccessibleUniqueIDs = reader.ReadBytes(8); + si.FileSystemAccessInfo = reader.ReadBytes(7); + si.OtherAttributes = (StorageInfoOtherAttributes)reader.ReadByte(); + return si; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get system control info, if possible + /// + /// BinaryReader representing the input stream + /// System control info object, null on error + public static SystemControlInfo? ReadSystemControlInfo(BinaryReader reader) + { + var sci = new SystemControlInfo(); + + try + { + byte[] applicationTitleBytes = reader.ReadBytes(8); + sci.ApplicationTitle = Encoding.ASCII.GetString(applicationTitleBytes); + sci.Reserved1 = reader.ReadBytes(5); + sci.Flag = reader.ReadByte(); + sci.RemasterVersion = reader.ReadUInt16(); + sci.TextCodeSetInfo = ReadCodeSetInfo(reader); + sci.StackSize = reader.ReadUInt32(); + sci.ReadOnlyCodeSetInfo = ReadCodeSetInfo(reader); + sci.Reserved2 = reader.ReadUInt32(); + sci.DataCodeSetInfo = ReadCodeSetInfo(reader); + sci.BSSSize = reader.ReadUInt32(); + + sci.DependencyModuleList = new ulong[48]; + for (int i = 0; i < 48; i++) + { + sci.DependencyModuleList[i] = reader.ReadUInt64(); + } + + sci.SystemInfo = ReadSystemInfo(reader); + return sci; + } + catch + { + return null; + } + } + + /// + /// Read from a stream and get system info, if possible + /// + /// BinaryReader representing the input stream + /// System info object, null on error + public static SystemInfo? ReadSystemInfo(BinaryReader reader) + { + var si = new SystemInfo(); + + try + { + si.SaveDataSize = reader.ReadUInt64(); + si.JumpID = reader.ReadUInt64(); + si.Reserved = reader.ReadBytes(0x30); + return si; + } + catch + { + return null; + } + } + + /// + /// 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? ReadTicket(BinaryReader reader, uint ticketSize) + { + var tk = new Ticket(); + + try + { + long startingPosition = reader.BaseStream.Position; + + 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; + default: + return null; + } + + tk.Signature = reader.ReadBytes(tk.SignatureSize); + reader.ReadBytes(tk.PaddingSize); // Padding + byte[] issuerBytes = reader.ReadBytes(0x40); + tk.Issuer = Encoding.ASCII.GetString(issuerBytes); + 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.ReadBytes(2); + tk.TicketTitleVersion = reader.ReadUInt16(); + tk.Reserved3 = reader.ReadBytes(8); + 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 = new uint[0x10]; + for (int i = 0; i < 0x10; i++) + { + tk.Limits[i] = reader.ReadUInt32(); + } + + reader.ReadBytes(4); // Seek to size in Content Index + tk.ContentIndexSize = BitConverter.ToUInt32(reader.ReadBytes(4).Reverse().ToArray(), 0); + reader.BaseStream.Seek(-8, SeekOrigin.Current); + tk.ContentIndex = reader.ReadBytes((int)tk.ContentIndexSize); + if (reader.BaseStream.Position % 64 != 0) + reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current); + + if (ticketSize > (reader.BaseStream.Position - startingPosition) + (2 * 0x200)) + { + tk.CertificateChain = new Certificate[2]; + tk.CertificateChain[0] = ReadCertificate(reader)!; // Ticket + tk.CertificateChain[1] = ReadCertificate(reader)!; // CA + } + + return tk; + } + catch + { + return null; + } + } + + /// + /// 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? ReadTitleMetadata(BinaryReader reader, uint metadataSize) + { + var tm = new TitleMetadata(); + + try + { + long startingPosition = reader.BaseStream.Position; + + 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); + tm.Padding1 = reader.ReadBytes(tm.PaddingSize); + byte[] issuerBytes = reader.ReadBytes(0x40); + tm.Issuer = Encoding.ASCII.GetString(issuerBytes); + 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.ReadBytes(4); + tm.SRLFlag = reader.ReadByte(); + tm.Reserved3 = reader.ReadBytes(0x31); + tm.AccessRights = reader.ReadUInt32(); + tm.TitleVersion = reader.ReadUInt16(); + tm.ContentCount = BitConverter.ToUInt16(reader.ReadBytes(2).Reverse().ToArray(), 0); + tm.BootContent = reader.ReadUInt16(); + tm.Padding2 = reader.ReadBytes(2); + tm.SHA256HashContentInfoRecords = reader.ReadBytes(0x20); + + tm.ContentInfoRecords = new ContentInfoRecord[64]; + for (int i = 0; i < 64; i++) + { + tm.ContentInfoRecords[i] = ReadContentInfoRecord(reader)!; + } + + tm.ContentChunkRecords = new ContentChunkRecord[tm.ContentCount]; + for (int i = 0; i < tm.ContentCount; i++) + { + tm.ContentChunkRecords[i] = ReadContentChunkRecord(reader)!; + } + + if (metadataSize > (reader.BaseStream.Position - startingPosition) + (2 * 0x200)) + { + tm.CertificateChain = new Certificate[2]; + tm.CertificateChain[0] = ReadCertificate(reader)!; // TMD + tm.CertificateChain[1] = ReadCertificate(reader)!; // CA + } + + return tm; + } + catch + { + return null; + } + } + + #endregion + + #region Writing + + /// + /// Write NCCH header flags to stream, if possible + /// + /// BinaryWriter representing the output stream + public static void Write(NCCHHeaderFlags flags, BinaryWriter writer) + { + try + { + writer.Write(flags.Reserved0); + writer.Write(flags.Reserved1); + writer.Write(flags.Reserved2); + writer.Write((byte)flags.CryptoMethod); + writer.Write((byte)flags.ContentPlatform); + writer.Write((byte)flags.MediaPlatformIndex); + writer.Write(flags.ContentUnitSize); + writer.Write((byte)flags.BitMasks); + + } + catch { } + } + + #endregion + } +} \ No newline at end of file diff --git a/NDecrypt.N3DS/ThreeDSTool.cs b/NDecrypt.N3DS/ThreeDSTool.cs index b3d65db..5e530a2 100644 --- a/NDecrypt.N3DS/ThreeDSTool.cs +++ b/NDecrypt.N3DS/ThreeDSTool.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Numerics; using NDecrypt.Core; -using NDecrypt.N3DS.Headers; +using SabreTools.Models.N3DS; using static NDecrypt.Core.Helper; namespace NDecrypt.N3DS @@ -20,6 +20,31 @@ public class ThreeDSTool : ITool /// private readonly DecryptArgs decryptArgs; + /// + /// Set of all KeyX values + /// + private readonly BigInteger[] KeyX = new BigInteger[8]; + + /// + /// Set of all KeyX2C values + /// + private readonly BigInteger[] KeyX2C = new BigInteger[8]; + + /// + /// Set of all KeyY values + /// + private readonly BigInteger[] KeyY = new BigInteger[8]; + + /// + /// Set of all KeyY values + /// + private readonly BigInteger[] NormalKey = new BigInteger[8]; + + /// + /// Set of all KeyY values + /// + private readonly BigInteger[] NormalKey2C = new BigInteger[8]; + public ThreeDSTool(string filename, DecryptArgs decryptArgs) { this.filename = filename; @@ -43,18 +68,18 @@ public bool ProcessFile() try { // Open the read and write on the same file for inplace processing - using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) - using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))) + using (var reader = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) + using (var writer = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))) { - NCSDHeader? header = NCSDHeader.Read(reader, decryptArgs.Development); - if (header == null) + (var cart, var backupHeader) = Serializer.ReadCart(reader, decryptArgs.Development); + if (cart?.Header == null) { Console.WriteLine("Error: Not a 3DS cart image!"); return false; } // Process all 8 NCCH partitions - ProcessAllPartitions(header, reader, writer); + ProcessAllPartitions(cart.Header, backupHeader, reader, writer); } return true; @@ -71,18 +96,19 @@ public bool ProcessFile() /// Process all partitions in the partition table of an NCSD header /// /// NCSD header representing the 3DS file + /// Backup NCCH header /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void ProcessAllPartitions(NCSDHeader ncsdHeader, BinaryReader reader, BinaryWriter writer) + private void ProcessAllPartitions(NCSDHeader ncsdHeader, NCCHHeader? backupHeader, BinaryReader reader, BinaryWriter writer) { // Iterate over all 8 NCCH partitions for (int p = 0; p < 8; p++) { - NCCHHeader? ncchHeader = GetPartitionHeader(ncsdHeader, reader, p); - if (ncchHeader == null) + (int partitionIndex, var ncchHeader, var tableEntry) = GetPartitionHeader(ncsdHeader, reader, p); + if (partitionIndex < 0 || ncchHeader == null || tableEntry == null) continue; - ProcessPartition(ncsdHeader, ncchHeader, reader, writer); + ProcessPartition(ncsdHeader, partitionIndex, ncchHeader, tableEntry, backupHeader, reader, writer); } } @@ -93,99 +119,120 @@ private void ProcessAllPartitions(NCSDHeader ncsdHeader, BinaryReader reader, Bi /// BinaryReader representing the input stream /// Partition number to attempt to retrieve /// NCCH header for the partition requested, null on error - private NCCHHeader? GetPartitionHeader(NCSDHeader ncsdHeader, BinaryReader reader, int partitionNumber) + private static (int, NCCHHeader?, PartitionTableEntry?) GetPartitionHeader(NCSDHeader ncsdHeader, BinaryReader reader, int partitionNumber) { - if (!ncsdHeader.PartitionsTable![partitionNumber].IsValid()) + // Check the partitions table + if (ncsdHeader.PartitionsTable == null) + { + Console.WriteLine("Invalid partitions table... Skipping..."); + return (-1, null, null); + } + + // Check the partition is valid + if (!ncsdHeader.PartitionsTable[partitionNumber].IsValid()) { Console.WriteLine($"Partition {partitionNumber} Not found... Skipping..."); - return null; + return (-1, null, null); } // Seek to the beginning of the NCCH partition - reader.BaseStream.Seek((ncsdHeader.PartitionsTable[partitionNumber].Offset * ncsdHeader.MediaUnitSize), SeekOrigin.Begin); + long offset = ncsdHeader.PartitionsTable[partitionNumber]!.Offset * ncsdHeader.ImageSizeInMediaUnits; + reader.BaseStream.Seek(offset, SeekOrigin.Begin); - NCCHHeader? partitionHeader = NCCHHeader.Read(reader, readSignature: true); - if (partitionHeader == null) + // Read the NCCH header + var header = Serializer.ReadNCCHHeader(reader, readSignature: true); + if (header == null) { Console.WriteLine($"Partition {partitionNumber} Unable to read NCCH header"); - return null; + return (-1, null, null); } - partitionHeader.PartitionNumber = partitionNumber; - partitionHeader.Entry = ncsdHeader.PartitionsTable[partitionNumber]; - return partitionHeader; + var entry = ncsdHeader.PartitionsTable[partitionNumber]; + return (partitionNumber, header, entry); } /// /// Process a single partition /// /// NCSD header representing the 3DS file + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition + /// Backup NCCH header /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void ProcessPartition(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void ProcessPartition(NCSDHeader ncsdHeader, + int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + NCCHHeader? backupHeader, + BinaryReader reader, + BinaryWriter writer) { // If we're forcing the operation, tell the user if (decryptArgs.Force) { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} is not verified due to force flag being set."); + Console.WriteLine($"Partition {partitionIndex} is not verified due to force flag being set."); } // If we're not forcing the operation, check if the 'NoCrypto' bit is set - else if (ncchHeader.Flags!.PossblyDecrypted ^ decryptArgs.Encrypt) + else if (ncchHeader.Flags!.PossblyDecrypted() ^ decryptArgs.Encrypt) { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber}: Already " + (decryptArgs.Encrypt ? "Encrypted" : "Decrypted") + "?..."); + Console.WriteLine($"Partition {partitionIndex}: Already " + (decryptArgs.Encrypt ? "Encrypted" : "Decrypted") + "?..."); return; } // Determine the Keys to be used - SetEncryptionKeys(ncsdHeader, ncchHeader); + SetEncryptionKeys(partitionIndex, ncchHeader, backupHeader); // Process the extended header - ProcessExtendedHeader(ncsdHeader, ncchHeader, reader, writer); + ProcessExtendedHeader(ncsdHeader, partitionIndex, ncchHeader, tableEntry, reader, writer); // If we're encrypting, encrypt the filesystems and update the flags if (decryptArgs.Encrypt) { - EncryptExeFS(ncsdHeader, ncchHeader, reader, writer); - EncryptRomFS(ncsdHeader, ncchHeader, reader, writer); - UpdateEncryptCryptoAndMasks(ncsdHeader, ncchHeader, writer); + EncryptExeFS(ncsdHeader, partitionIndex, ncchHeader, tableEntry, backupHeader, reader, writer); + EncryptRomFS(ncsdHeader, partitionIndex, ncchHeader, tableEntry, backupHeader, reader, writer); + UpdateEncryptCryptoAndMasks(ncsdHeader, partitionIndex, ncchHeader, tableEntry, backupHeader, writer); } // If we're decrypting, decrypt the filesystems and update the flags else { - DecryptExeFS(ncsdHeader, ncchHeader, reader, writer); - DecryptRomFS(ncsdHeader, ncchHeader, reader, writer); - UpdateDecryptCryptoAndMasks(ncsdHeader, ncchHeader, writer); + DecryptExeFS(ncsdHeader, partitionIndex, ncchHeader, tableEntry, reader, writer); + DecryptRomFS(ncsdHeader, partitionIndex, ncchHeader, tableEntry, reader, writer); + UpdateDecryptCryptoAndMasks(ncsdHeader, ncchHeader, tableEntry, writer); } } /// /// Determine the set of keys to be used for encryption or decryption /// - /// NCSD header representing the 3DS file + /// Index of the partition /// NCCH header representing the partition - private void SetEncryptionKeys(NCSDHeader ncsdHeader, NCCHHeader ncchHeader) + /// Backup NCCH header + private void SetEncryptionKeys(int partitionIndex, + NCCHHeader ncchHeader, + NCCHHeader? backupHeader) { - ncchHeader.KeyX = 0; - ncchHeader.KeyX2C = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C; + KeyX[partitionIndex] = 0; + KeyX2C[partitionIndex] = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C; // Backup headers can't have a KeyY value set if (ncchHeader.RSA2048Signature != null) - ncchHeader.KeyY = new BigInteger(ncchHeader.RSA2048Signature.Take(16).Reverse().ToArray()); + KeyY[partitionIndex] = new BigInteger(ncchHeader.RSA2048Signature.Take(16).Reverse().ToArray()); else - ncchHeader.KeyY = new BigInteger(0); + KeyY[partitionIndex] = new BigInteger(0); - ncchHeader.NormalKey = 0; - ncchHeader.NormalKey2C = RotateLeft((RotateLeft(ncchHeader.KeyX2C, 2, 128) ^ ncchHeader.KeyY) + decryptArgs.AESHardwareConstant, 87, 128); + NormalKey[partitionIndex] = 0x00; + NormalKey2C[partitionIndex] = RotateLeft((RotateLeft(KeyX2C[partitionIndex], 2, 128) ^ KeyY[partitionIndex]) + decryptArgs.AESHardwareConstant, 87, 128); // Set the header to use based on mode BitMasks masks; CryptoMethod method; if (decryptArgs.Encrypt) { - masks = ncsdHeader.BackupHeader!.Flags!.BitMasks; - method = ncsdHeader.BackupHeader.Flags.CryptoMethod; + masks = backupHeader!.Flags!.BitMasks; + method = backupHeader.Flags.CryptoMethod; } else { @@ -195,34 +242,34 @@ private void SetEncryptionKeys(NCSDHeader ncsdHeader, NCCHHeader ncchHeader) if (masks.HasFlag(BitMasks.FixedCryptoKey)) { - ncchHeader.NormalKey = 0x00; - ncchHeader.NormalKey2C = 0x00; + NormalKey[partitionIndex] = 0x00; + NormalKey2C[partitionIndex] = 0x00; Console.WriteLine("Encryption Method: Zero Key"); } else { if (method == CryptoMethod.Original) { - ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C; + KeyX[partitionIndex] = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C; Console.WriteLine("Encryption Method: Key 0x2C"); } else if (method == CryptoMethod.Seven) { - ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x25 : decryptArgs.KeyX0x25; + KeyX[partitionIndex] = decryptArgs.Development ? decryptArgs.DevKeyX0x25 : decryptArgs.KeyX0x25; Console.WriteLine("Encryption Method: Key 0x25"); } else if (method == CryptoMethod.NineThree) { - ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x18 : decryptArgs.KeyX0x18; + KeyX[partitionIndex] = decryptArgs.Development ? decryptArgs.DevKeyX0x18 : decryptArgs.KeyX0x18; Console.WriteLine("Encryption Method: Key 0x18"); } else if (method == CryptoMethod.NineSix) { - ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x1B : decryptArgs.KeyX0x1B; + KeyX[partitionIndex] = decryptArgs.Development ? decryptArgs.DevKeyX0x1B : decryptArgs.KeyX0x1B; Console.WriteLine("Encryption Method: Key 0x1B"); } - ncchHeader.NormalKey = RotateLeft((RotateLeft(ncchHeader.KeyX, 2, 128) ^ ncchHeader.KeyY) + decryptArgs.AESHardwareConstant, 87, 128); + NormalKey[partitionIndex] = RotateLeft((RotateLeft(KeyX[partitionIndex], 2, 128) ^ KeyY[partitionIndex]) + decryptArgs.AESHardwareConstant, 87, 128); } } @@ -230,19 +277,26 @@ private void SetEncryptionKeys(NCSDHeader ncsdHeader, NCCHHeader ncchHeader) /// Process the extended header, if it exists /// /// NCSD header representing the 3DS file + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private bool ProcessExtendedHeader(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private bool ProcessExtendedHeader(NCSDHeader ncsdHeader, + int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryReader reader, + BinaryWriter writer) { if (ncchHeader.ExtendedHeaderSizeInBytes > 0) { - reader.BaseStream.Seek((ncchHeader.Entry!.Offset * ncsdHeader.MediaUnitSize) + 0x200, SeekOrigin.Begin); - writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x200, SeekOrigin.Begin); + reader.BaseStream.Seek((tableEntry.Offset * ncsdHeader.MediaUnitSize()) + 0x200, SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset * ncsdHeader.MediaUnitSize()) + 0x200, SeekOrigin.Begin); - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + ": ExHeader"); + Console.WriteLine($"Partition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + ": ExHeader"); - var cipher = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.PlainIV!, decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey2C[partitionIndex], ncchHeader.PlainIV(), decryptArgs.Encrypt); byte[] readBytes = reader.ReadBytes(Constants.CXTExtendedDataHeaderLength); byte[] processedBytes = cipher.ProcessBytes(readBytes); writer.Write(processedBytes); @@ -251,7 +305,7 @@ private bool ProcessExtendedHeader(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, } else { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Extended Header... Skipping..."); + Console.WriteLine($"Partition {partitionIndex} ExeFS: No Extended Header... Skipping..."); return false; } } @@ -260,38 +314,45 @@ private bool ProcessExtendedHeader(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, /// Process the extended header, if it exists /// /// NCSD header representing the 3DS file + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void ProcessExeFSFileEntries(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void ProcessExeFSFileEntries(NCSDHeader ncsdHeader, + int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryReader reader, + BinaryWriter writer) { - reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin); - ExeFSHeader? exefsHeader = ExeFSHeader.Read(reader); + reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); + var exefsHeader = Serializer.ReadExeFSHeader(reader); // If the header failed to read, log and return if (exefsHeader == null) { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS header could not be read. Skipping..."); + Console.WriteLine($"Partition {partitionIndex} ExeFS header could not be read. Skipping..."); return; } - foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders!) + foreach (var fileHeader in exefsHeader.FileHeaders!) { // Only decrypt a file if it's a code binary - if (!fileHeader.IsCodeBinary) + if (fileHeader == null || !fileHeader.IsCodeBinary()) continue; uint datalenM = ((fileHeader.FileSize) / (1024 * 1024)); uint datalenB = ((fileHeader.FileSize) % (1024 * 1024)); - uint ctroffset = ((fileHeader.FileOffset + ncsdHeader.MediaUnitSize) / 0x10); + uint ctroffset = ((fileHeader.FileOffset + ncsdHeader.MediaUnitSize()) / 0x10); - byte[] exefsIVWithOffsetForHeader = AddToByteArray(ncchHeader.ExeFSIV!, (int)ctroffset); + byte[] exefsIVWithOffsetForHeader = AddToByteArray(ncchHeader.ExeFSIV(), (int)ctroffset); - var firstCipher = CreateAESCipher(ncchHeader.NormalKey, exefsIVWithOffsetForHeader, decryptArgs.Encrypt); - var secondCipher = CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffsetForHeader, !decryptArgs.Encrypt); + var firstCipher = CreateAESCipher(NormalKey[partitionIndex], exefsIVWithOffsetForHeader, decryptArgs.Encrypt); + var secondCipher = CreateAESCipher(NormalKey2C[partitionIndex], exefsIVWithOffsetForHeader, !decryptArgs.Encrypt); - reader.BaseStream.Seek((((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * ncsdHeader.MediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin); - writer.BaseStream.Seek((((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * ncsdHeader.MediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin); + reader.BaseStream.Seek((((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * ncsdHeader.MediaUnitSize()) + fileHeader.FileOffset, SeekOrigin.Begin); + writer.BaseStream.Seek((((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * ncsdHeader.MediaUnitSize()) + fileHeader.FileOffset, SeekOrigin.Begin); if (datalenM > 0) { @@ -302,7 +363,7 @@ private void ProcessExeFSFileEntries(NCSDHeader ncsdHeader, NCCHHeader ncchHeade byte[] secondProcessedBytes = secondCipher.ProcessBytes(firstProcessedBytes); writer.Write(secondProcessedBytes); writer.Flush(); - Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {i} / {datalenM + 1} mb..."); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.FileName}... {i} / {datalenM + 1} mb..."); } } @@ -315,7 +376,7 @@ private void ProcessExeFSFileEntries(NCSDHeader ncsdHeader, NCCHHeader ncchHeade writer.Flush(); } - Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n"); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.FileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n"); } } @@ -323,18 +384,25 @@ private void ProcessExeFSFileEntries(NCSDHeader ncsdHeader, NCCHHeader ncchHeade /// Process the ExeFS Filename Table /// /// NCSD header representing the 3DS file + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void ProcessExeFSFilenameTable(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void ProcessExeFSFilenameTable(NCSDHeader ncsdHeader, + int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryReader reader, + BinaryWriter writer) { - reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin); - writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin); + reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table"); + Console.WriteLine($"Partition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table"); - var cipher = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.ExeFSIV!, decryptArgs.Encrypt); - byte[] readBytes = reader.ReadBytes((int)ncsdHeader.MediaUnitSize); + var cipher = CreateAESCipher(NormalKey2C[partitionIndex], ncchHeader.ExeFSIV(), decryptArgs.Encrypt); + byte[] readBytes = reader.ReadBytes((int)ncsdHeader.MediaUnitSize()); byte[] processedBytes = cipher.ProcessBytes(readBytes); writer.Write(processedBytes); @@ -349,21 +417,28 @@ private void ProcessExeFSFilenameTable(NCSDHeader ncsdHeader, NCCHHeader ncchHea /// Process the ExeFS, if it exists /// /// NCSD header representing the 3DS file + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void ProcessExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void ProcessExeFS(NCSDHeader ncsdHeader, + int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryReader reader, + BinaryWriter writer) { - int exefsSizeM = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * ncsdHeader.MediaUnitSize) / (1024 * 1024)); - int exefsSizeB = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * ncsdHeader.MediaUnitSize) % (1024 * 1024)); - int ctroffsetE = (int)(ncsdHeader.MediaUnitSize / 0x10); + int exefsSizeM = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * ncsdHeader.MediaUnitSize()) / (1024 * 1024)); + int exefsSizeB = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * ncsdHeader.MediaUnitSize()) % (1024 * 1024)); + int ctroffsetE = (int)(ncsdHeader.MediaUnitSize() / 0x10); - byte[] exefsIVWithOffset = AddToByteArray(ncchHeader.ExeFSIV!, ctroffsetE); + byte[] exefsIVWithOffset = AddToByteArray(ncchHeader.ExeFSIV(), ctroffsetE); - var cipher = CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffset, decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey2C[partitionIndex], exefsIVWithOffset, decryptArgs.Encrypt); - reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin); - writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin); + reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); if (exefsSizeM > 0) { for (int i = 0; i < exefsSizeM; i++) @@ -372,7 +447,7 @@ private void ProcessExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryRe byte[] processedBytes = cipher.ProcessBytes(readBytes); writer.Write(processedBytes); writer.Flush(); - Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb"); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb"); } } if (exefsSizeB > 0) @@ -383,7 +458,7 @@ private void ProcessExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryRe writer.Flush(); } - Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n"); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n"); } #endregion @@ -394,53 +469,67 @@ private void ProcessExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryRe /// Decrypt the ExeFS, if it exists /// /// NCSD header representing the 3DS file + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void DecryptExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void DecryptExeFS(NCSDHeader ncsdHeader, + int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryReader reader, + BinaryWriter writer) { // If the ExeFS size is 0, we log and return if (ncchHeader.ExeFSSizeInMediaUnits == 0) { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Data... Skipping..."); + Console.WriteLine($"Partition {partitionIndex} ExeFS: No Data... Skipping..."); return; } // Decrypt the filename table - ProcessExeFSFilenameTable(ncsdHeader, ncchHeader, reader, writer); + ProcessExeFSFilenameTable(ncsdHeader, partitionIndex, ncchHeader, tableEntry, reader, writer); // For all but the original crypto method, process each of the files in the table if (ncchHeader.Flags!.CryptoMethod != CryptoMethod.Original) - ProcessExeFSFileEntries(ncsdHeader, ncchHeader, reader, writer); + ProcessExeFSFileEntries(ncsdHeader, partitionIndex, ncchHeader, tableEntry, reader, writer); // Decrypt the rest of the ExeFS - ProcessExeFS(ncsdHeader, ncchHeader, reader, writer); + ProcessExeFS(ncsdHeader, partitionIndex, ncchHeader, tableEntry, reader, writer); } /// /// Decrypt the RomFS, if it exists /// /// NCSD header representing the 3DS file + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream /// TODO: See how much can be extracted into a common method with Encrypt - private void DecryptRomFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void DecryptRomFS(NCSDHeader ncsdHeader, + int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryReader reader, + BinaryWriter writer) { // If the RomFS offset is 0, we log and return if (ncchHeader.RomFSOffsetInMediaUnits == 0) { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} RomFS: No Data... Skipping..."); + Console.WriteLine($"Partition {partitionIndex} RomFS: No Data... Skipping..."); return; } - long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) / (1024 * 1024)); - int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) % (1024 * 1024)); + long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize()) / (1024 * 1024)); + int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize()) % (1024 * 1024)); - var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV!, decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey[partitionIndex], ncchHeader.RomFSIV(), decryptArgs.Encrypt); - reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin); - writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin); + reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); if (romfsSizeM > 0) { for (int i = 0; i < romfsSizeM; i++) @@ -449,7 +538,7 @@ private void DecryptRomFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryRe byte[] processedBytes = cipher.ProcessBytes(readBytes); writer.Write(processedBytes); writer.Flush(); - Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Decrypting: {i} / {romfsSizeM + 1} mb"); + Console.Write($"\rPartition {partitionIndex} RomFS: Decrypting: {i} / {romfsSizeM + 1} mb"); } } if (romfsSizeB > 0) @@ -460,7 +549,7 @@ private void DecryptRomFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryRe writer.Flush(); } - Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Decrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n"); + Console.Write($"\rPartition {partitionIndex} RomFS: Decrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n"); } /// @@ -468,16 +557,20 @@ private void DecryptRomFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryRe /// /// NCSD header representing the 3DS file /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition /// BinaryWriter representing the output stream - private void UpdateDecryptCryptoAndMasks(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryWriter writer) + private void UpdateDecryptCryptoAndMasks(NCSDHeader ncsdHeader, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + BinaryWriter writer) { // Write the new CryptoMethod - writer.BaseStream.Seek((ncchHeader.Entry!.Offset * ncsdHeader.MediaUnitSize) + 0x18B, SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset * ncsdHeader.MediaUnitSize()) + 0x18B, SeekOrigin.Begin); writer.Write((byte)CryptoMethod.Original); writer.Flush(); // Write the new BitMasks flag - writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18F, SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset * ncsdHeader.MediaUnitSize()) + 0x18F, SeekOrigin.Begin); BitMasks flag = ncchHeader.Flags!.BitMasks; flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF); flag |= BitMasks.NoCrypto; @@ -493,67 +586,85 @@ private void UpdateDecryptCryptoAndMasks(NCSDHeader ncsdHeader, NCCHHeader ncchH /// Encrypt the ExeFS, if it exists /// /// NCSD header representing the 3DS file + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition + /// Backup NCCH header /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void EncryptExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void EncryptExeFS(NCSDHeader ncsdHeader, + int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + NCCHHeader? backupHeader, + BinaryReader reader, + BinaryWriter writer) { // If the ExeFS size is 0, we log and return if (ncchHeader.ExeFSSizeInMediaUnits == 0) { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Data... Skipping..."); + Console.WriteLine($"Partition {partitionIndex} ExeFS: No Data... Skipping..."); return; } // For all but the original crypto method, process each of the files in the table - if (ncsdHeader.BackupHeader!.Flags!.CryptoMethod != CryptoMethod.Original) - ProcessExeFSFileEntries(ncsdHeader, ncchHeader, reader, writer); + if (backupHeader!.Flags!.CryptoMethod != CryptoMethod.Original) + ProcessExeFSFileEntries(ncsdHeader, partitionIndex, ncchHeader, tableEntry, reader, writer); // Encrypt the filename table - ProcessExeFSFilenameTable(ncsdHeader, ncchHeader, reader, writer); + ProcessExeFSFilenameTable(ncsdHeader, partitionIndex, ncchHeader, tableEntry, reader, writer); // Encrypt the rest of the ExeFS - ProcessExeFS(ncsdHeader, ncchHeader, reader, writer); + ProcessExeFS(ncsdHeader, partitionIndex, ncchHeader, tableEntry, reader, writer); } /// /// Encrypt the RomFS, if it exists /// /// NCSD header representing the 3DS file + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition + /// Backup NCCH header /// BinaryReader representing the input stream /// BinaryWriter representing the output stream /// TODO: See how much can be extracted into a common method with Decrypt - private void EncryptRomFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) + private void EncryptRomFS(NCSDHeader ncsdHeader, + int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + NCCHHeader? backupHeader, + BinaryReader reader, + BinaryWriter writer) { // If the RomFS offset is 0, we log and return if (ncchHeader.RomFSOffsetInMediaUnits == 0) { - Console.WriteLine($"Partition {ncchHeader.PartitionNumber} RomFS: No Data... Skipping..."); + Console.WriteLine($"Partition {partitionIndex} RomFS: No Data... Skipping..."); return; } - long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) / (1024 * 1024)); - int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) % (1024 * 1024)); + long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize()) / (1024 * 1024)); + int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize()) % (1024 * 1024)); // Encrypting RomFS for partitions 1 and up always use Key0x2C - if (ncchHeader.PartitionNumber > 0) + if (partitionIndex > 0) { - if (ncsdHeader.BackupHeader!.Flags?.BitMasks.HasFlag(BitMasks.FixedCryptoKey) == true) // except if using zero-key + if (backupHeader!.Flags?.BitMasks.HasFlag(BitMasks.FixedCryptoKey) == true) // except if using zero-key { - ncchHeader.NormalKey = 0x00; + NormalKey[partitionIndex] = 0x00; } else { - ncchHeader.KeyX = (decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C); - ncchHeader.NormalKey = RotateLeft((RotateLeft(ncchHeader.KeyX, 2, 128) ^ ncchHeader.KeyY) + decryptArgs.AESHardwareConstant, 87, 128); + KeyX[partitionIndex] = (decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C); + NormalKey[partitionIndex] = RotateLeft((RotateLeft(KeyX[partitionIndex], 2, 128) ^ KeyY[partitionIndex]) + decryptArgs.AESHardwareConstant, 87, 128); } } - var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV!, decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey[partitionIndex], ncchHeader.RomFSIV(), decryptArgs.Encrypt); - reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin); - writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin); + reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); if (romfsSizeM > 0) { for (int i = 0; i < romfsSizeM; i++) @@ -562,7 +673,7 @@ private void EncryptRomFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryRe byte[] processedBytes = cipher.ProcessBytes(readBytes); writer.Write(processedBytes); writer.Flush(); - Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Encrypting: {i} / {romfsSizeM + 1} mb"); + Console.Write($"\rPartition {partitionIndex} RomFS: Encrypting: {i} / {romfsSizeM + 1} mb"); } } if (romfsSizeB > 0) @@ -573,35 +684,43 @@ private void EncryptRomFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryRe writer.Flush(); } - Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Encrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n"); + Console.Write($"\rPartition {partitionIndex} RomFS: Encrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n"); } /// /// Update the CryptoMethod and BitMasks for the encrypted partition /// /// NCSD header representing the 3DS file + /// Index of the partition /// NCCH header representing the partition + /// PartitionTableEntry header representing the partition + /// Backup NCCH header /// BinaryWriter representing the output stream - private void UpdateEncryptCryptoAndMasks(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryWriter writer) + private static void UpdateEncryptCryptoAndMasks(NCSDHeader ncsdHeader, + int partitionIndex, + NCCHHeader ncchHeader, + PartitionTableEntry tableEntry, + NCCHHeader? backupHeader, + BinaryWriter writer) { // Write the new CryptoMethod - writer.BaseStream.Seek((ncchHeader.Entry!.Offset * ncsdHeader.MediaUnitSize) + 0x18B, SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset * ncsdHeader.MediaUnitSize()) + 0x18B, SeekOrigin.Begin); // For partitions 1 and up, set crypto-method to 0x00 - if (ncchHeader.PartitionNumber > 0) + if (partitionIndex > 0) writer.Write((byte)CryptoMethod.Original); // If partition 0, restore crypto-method from backup flags else - writer.Write((byte)ncsdHeader.BackupHeader!.Flags!.CryptoMethod); + writer.Write((byte)backupHeader!.Flags!.CryptoMethod); writer.Flush(); // Write the new BitMasks flag - writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18F, SeekOrigin.Begin); + writer.BaseStream.Seek((tableEntry.Offset * ncsdHeader.MediaUnitSize()) + 0x18F, SeekOrigin.Begin); BitMasks flag = ncchHeader.Flags!.BitMasks; flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF; - flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & ncsdHeader.BackupHeader!.Flags!.BitMasks; + flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & backupHeader!.Flags!.BitMasks; writer.Write((byte)flag); writer.Flush(); }