From 30da008cec930dad6193f67431eb4aaeeeac7c91 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 8 Apr 2019 02:09:33 -0700 Subject: [PATCH] Processing shunting --- 3DSDecrypt/Headers/NCCHHeader.cs | 363 +++++++++++++++++++++++++++- 3DSDecrypt/Headers/NCSDHeader.cs | 68 ++++++ 3DSDecrypt/ThreeDSTool.cs | 396 +------------------------------ 3 files changed, 431 insertions(+), 396 deletions(-) diff --git a/3DSDecrypt/Headers/NCCHHeader.cs b/3DSDecrypt/Headers/NCCHHeader.cs index 55e7829..8764a06 100644 --- a/3DSDecrypt/Headers/NCCHHeader.cs +++ b/3DSDecrypt/Headers/NCCHHeader.cs @@ -1,5 +1,7 @@ -using System.IO; +using System; +using System.IO; using System.Linq; +using System.Numerics; using ThreeDS.Data; namespace ThreeDS.Headers @@ -8,10 +10,20 @@ namespace ThreeDS.Headers { 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 = new byte[0x100]; + public byte[] RSA2048Signature { get; private set; } /// /// Content size, in media units (1 media unit = 0x200 bytes) @@ -26,6 +38,31 @@ namespace ThreeDS.Headers public byte[] ExeFSIV { get { return PartitionId.Concat(Constants.ExefsCounter).ToArray(); } } public byte[] RomFSIV { get { return PartitionId.Concat(Constants.RomfsCounter).ToArray(); } } + /// + /// Boot rom key + /// + private BigInteger KeyX; + + /// + /// NCCH boot rom key + /// + private BigInteger KeyX2C; + + /// + /// Kernel9/Process9 key + /// + private BigInteger KeyY; + + /// + /// Normal AES key + /// + private BigInteger NormalKey; + + /// + /// NCCH AES key + /// + private BigInteger NormalKey2C; + /// /// Maker code /// @@ -209,5 +246,327 @@ namespace ThreeDS.Headers return null; } } + + /// + /// Determine the set of keys to be used for encryption or decryption + /// + /// File backup flags for encryption + /// True if we're encrypting the file, false otherwise + /// True if development keys should be used, false otherwise + public void SetEncryptionKeys(NCCHHeaderFlags backupFlags, bool encrypt, bool development) + { + KeyX = 0; + KeyX2C = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C); + + // Backup headers can't have a KeyY value set + if (RSA2048Signature != null) + KeyY = new BigInteger(RSA2048Signature.Take(16).Reverse().ToArray()); + else + KeyY = new BigInteger(0); + + NormalKey = 0; + NormalKey2C = Helper.RotateLeft((Helper.RotateLeft(KeyX2C, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128); + + // Set the header to use based on mode + BitMasks masks = 0; + CryptoMethod method = 0; + if (encrypt) + { + masks = backupFlags.BitMasks; + method = backupFlags.CryptoMethod; + } + else + { + masks = Flags.BitMasks; + method = Flags.CryptoMethod; + } + + if ((masks & BitMasks.FixedCryptoKey) != 0) + { + NormalKey = 0x00; + NormalKey2C = 0x00; + Console.WriteLine("Encryption Method: Zero Key"); + } + else + { + if (method == CryptoMethod.Original) + { + KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C); + Console.WriteLine("Encryption Method: Key 0x2C"); + } + else if (method == CryptoMethod.Seven) + { + KeyX = (development ? Constants.KeyX0x25 : Constants.KeyX0x25); + Console.WriteLine("Encryption Method: Key 0x25"); + } + else if (method == CryptoMethod.NineThree) + { + KeyX = (development ? Constants.DevKeyX0x18 : Constants.KeyX0x18); + Console.WriteLine("Encryption Method: Key 0x18"); + } + else if (method == CryptoMethod.NineSix) + { + KeyX = (development ? Constants.DevKeyX0x1B : Constants.KeyX0x1B); + Console.WriteLine("Encryption Method: Key 0x1B"); + } + + NormalKey = Helper.RotateLeft((Helper.RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128); + } + } + + /// + /// Process the extended header, if it exists + /// + /// BinaryReader representing the input stream + /// BinaryWriter representing the output stream + /// Number of bytes per media unit + /// True if we want to encrypt the extended header, false otherwise + public bool ProcessExtendedHeader(BinaryReader reader, BinaryWriter writer, uint mediaUnitSize, bool encrypt) + { + if (ExtendedHeaderSizeInBytes > 0) + { + reader.BaseStream.Seek((Entry.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin); + writer.BaseStream.Seek((Entry.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin); + + Console.WriteLine($"Partition {PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + ": ExHeader"); + + var cipher = Helper.CreateAESCipher(NormalKey2C, PlainIV, encrypt); + writer.Write(cipher.ProcessBytes(reader.ReadBytes(Constants.CXTExtendedDataHeaderLength))); + writer.Flush(); + return true; + } + else + { + Console.WriteLine($"Partition {PartitionNumber} ExeFS: No Extended Header... Skipping..."); + return false; + } + } + + /// + /// Process the ExeFS, if it exists + /// + /// BinaryReader representing the input stream + /// BinaryWriter representing the output stream + /// Number of bytes per media unit + /// True if we want to encrypt the extended header, false otherwise + public void ProcessExeFS(BinaryReader reader, BinaryWriter writer, uint mediaUnitSize, bool encrypt) + { + if (ExeFSSizeInMediaUnits > 0) + { + // If we're decrypting, we need to decrypt the filename table first + if (!encrypt) + ProcessExeFSFilenameTable(reader, writer, mediaUnitSize, encrypt); + + // For all but the original crypto method, process each of the files in the table + if (Flags.CryptoMethod != CryptoMethod.Original) + { + reader.BaseStream.Seek((Entry.Offset + ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); + ExeFSHeader exefsHeader = ExeFSHeader.Read(reader); + if (exefsHeader != null) + { + foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders) + { + // Only decrypt a file if it's a code binary + if (!fileHeader.IsCodeBinary) + continue; + + uint datalenM = ((fileHeader.FileSize) / (1024 * 1024)); + uint datalenB = ((fileHeader.FileSize) % (1024 * 1024)); + uint ctroffset = ((fileHeader.FileOffset + mediaUnitSize) / 0x10); + + byte[] exefsIVWithOffsetForHeader = Helper.AddToByteArray(ExeFSIV, (int)ctroffset); + + var firstCipher = Helper.CreateAESCipher(NormalKey, exefsIVWithOffsetForHeader, encrypt); + var secondCipher = Helper.CreateAESCipher(NormalKey2C, exefsIVWithOffsetForHeader, !encrypt); + + reader.BaseStream.Seek((((Entry.Offset + ExeFSOffsetInMediaUnits) + 1) * mediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin); + writer.BaseStream.Seek((((Entry.Offset + ExeFSOffsetInMediaUnits) + 1) * mediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin); + + if (datalenM > 0) + { + for (int i = 0; i < datalenM; i++) + { + writer.Write(secondCipher.ProcessBytes(firstCipher.ProcessBytes(reader.ReadBytes(1024 * 1024)))); + writer.Flush(); + Console.Write($"\rPartition {PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {i} / {datalenM + 1} mb..."); + } + } + + if (datalenB > 0) + { + writer.Write(secondCipher.DoFinal(firstCipher.DoFinal(reader.ReadBytes((int)datalenB)))); + writer.Flush(); + } + + Console.Write($"\rPartition {PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n"); + } + } + } + + // If we're encrypting, we need to encrypt the filename table now + if (encrypt) + ProcessExeFSFilenameTable(reader, writer, mediaUnitSize, encrypt); + + // Process the ExeFS + int exefsSizeM = (int)((ExeFSSizeInMediaUnits - 1) * mediaUnitSize) / (1024 * 1024); + int exefsSizeB = (int)((ExeFSSizeInMediaUnits - 1) * mediaUnitSize) % (1024 * 1024); + int ctroffsetE = (int)(mediaUnitSize / 0x10); + + byte[] exefsIVWithOffset = Helper.AddToByteArray(ExeFSIV, ctroffsetE); + + var exeFS = Helper.CreateAESCipher(NormalKey2C, exefsIVWithOffset, encrypt); + + reader.BaseStream.Seek((Entry.Offset + ExeFSOffsetInMediaUnits + 1) * mediaUnitSize, SeekOrigin.Begin); + writer.BaseStream.Seek((Entry.Offset + 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 {PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb"); + } + } + if (exefsSizeB > 0) + { + writer.Write(exeFS.DoFinal(reader.ReadBytes(exefsSizeB))); + writer.Flush(); + } + + Console.Write($"\rPartition {PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n"); + } + else + { + Console.WriteLine($"Partition {PartitionNumber} ExeFS: No Data... Skipping..."); + } + } + + /// + /// Process the ExeFS Filename Table + /// + /// BinaryReader representing the input stream + /// BinaryWriter representing the output stream + /// Number of bytes per media unit + /// True if we want to encrypt the extended header, false otherwise + private void ProcessExeFSFilenameTable(BinaryReader reader, BinaryWriter writer, uint mediaUnitSize, bool encrypt) + { + reader.BaseStream.Seek((Entry.Offset + ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); + writer.BaseStream.Seek((Entry.Offset + ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); + + Console.WriteLine($"Partition {PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table"); + + var exeFSFilenameTable = Helper.CreateAESCipher(NormalKey2C, ExeFSIV, encrypt); + writer.Write(exeFSFilenameTable.ProcessBytes(reader.ReadBytes((int)mediaUnitSize))); + writer.Flush(); + } + + /// + /// Process the RomFS, if it exists + /// + /// BinaryReader representing the input stream + /// BinaryWriter representing the output stream + /// Number of bytes per media unit + /// File backup flags for encryption + /// True if we want to encrypt the extended header, false otherwise + /// True if development keys should be used, false otherwise + public void ProcessRomFS(BinaryReader reader, BinaryWriter writer, uint mediaUnitSize, NCCHHeaderFlags backupFlags, bool encrypt, bool development) + { + if (RomFSOffsetInMediaUnits != 0) + { + int romfsSizeM = (int)(RomFSSizeInMediaUnits * mediaUnitSize) / (1024 * 1024); + int romfsSizeB = (int)(RomFSSizeInMediaUnits * mediaUnitSize) % (1024 * 1024); + + // Encrypting RomFS for partitions 1 and up always use Key0x2C + if (encrypt && PartitionNumber > 0) + { + // If the backup flags aren't provided and we're encrypting, assume defaults + if (backupFlags == null) + { + KeyX = KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C); + NormalKey = Helper.RotateLeft((Helper.RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128); + } + + if ((backupFlags.BitMasks & BitMasks.FixedCryptoKey) != 0) // except if using zero-key + { + NormalKey = 0x00; + } + else + { + KeyX = KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C); + NormalKey = Helper.RotateLeft((Helper.RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128); + } + } + + var cipher = Helper.CreateAESCipher(NormalKey, RomFSIV, encrypt); + + reader.BaseStream.Seek((Entry.Offset + RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); + writer.BaseStream.Seek((Entry.Offset + 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 {PartitionNumber} RomFS: Decrypting: {i} / {romfsSizeM + 1} mb"); + } + } + if (romfsSizeB > 0) + { + writer.Write(cipher.DoFinal(reader.ReadBytes(romfsSizeB))); + writer.Flush(); + } + + Console.Write($"\rPartition {PartitionNumber} RomFS: Decrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n"); + } + else + { + Console.WriteLine($"Partition {PartitionNumber} RomFS: No Data... Skipping..."); + } + } + + /// + /// Update the CryptoMethod and BitMasks for the partition + /// + /// BinaryReader representing the input stream + /// BinaryWriter representing the output stream + /// NCSD header for the 3DS file + /// True if we're writing encrypted values, false otherwise + public void UpdateCryptoAndMasks(BinaryReader reader, BinaryWriter writer, NCSDHeader header, bool encrypt) + { + // Write the new CryptoMethod + writer.BaseStream.Seek((Entry.Offset * header.MediaUnitSize) + 0x18B, SeekOrigin.Begin); + if (encrypt) + { + // For partitions 1 and up, set crypto-method to 0x00 + if (PartitionNumber > 0) + writer.Write((byte)CryptoMethod.Original); + + // If partition 0, restore crypto-method from backup flags + else + writer.Write((byte)header.BackupHeader.Flags.CryptoMethod); + } + else + { + writer.Write((byte)CryptoMethod.Original); + } + writer.Flush(); + + // Write the new BitMasks flag + writer.BaseStream.Seek((Entry.Offset * header.MediaUnitSize) + 0x18F, SeekOrigin.Begin); + BitMasks flag = Flags.BitMasks; + if (encrypt) + { + flag = (flag & ((BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF)); + flag = (flag | (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & header.BackupHeader.Flags.BitMasks); + } + else + { + flag = flag & (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF); + flag = (flag | BitMasks.NoCrypto); + } + + writer.Write((byte)flag); + writer.Flush(); + } } } diff --git a/3DSDecrypt/Headers/NCSDHeader.cs b/3DSDecrypt/Headers/NCSDHeader.cs index 78a7292..c54d70a 100644 --- a/3DSDecrypt/Headers/NCSDHeader.cs +++ b/3DSDecrypt/Headers/NCSDHeader.cs @@ -322,5 +322,73 @@ namespace ThreeDS.Headers return null; } } + + /// + /// Process all partitions in the partition table + /// + /// BinaryReader representing the input stream + /// BinaryWriter representing the output stream + /// True if we want to encrypt the partitions, false otherwise + /// True if development keys should be used, false otherwise + /// + public bool ProcessAllPartitions(BinaryReader reader, BinaryWriter writer, bool encrypt, bool development) + { + // Iterate over all 8 NCCH partitions + for (int p = 0; p < 8; p++) + { + NCCHHeader partitionHeader = GetPartitionHeader(reader, p); + if (partitionHeader == null) + continue; + + // Check if the 'NoCrypto' bit is set + if (partitionHeader.Flags.PossblyDecrypted ^ encrypt) + { + Console.WriteLine($"Partition {p}: Already " + (encrypt ? "Encrypted" : "Decrypted") + "?..."); + continue; + } + + // Determine the Keys to be used + partitionHeader.SetEncryptionKeys(BackupHeader.Flags, encrypt, development); + + // Process each of the pieces if they exist + partitionHeader.ProcessExtendedHeader(reader, writer, MediaUnitSize, encrypt); + partitionHeader.ProcessExeFS(reader, writer, MediaUnitSize, encrypt); + partitionHeader.ProcessRomFS(reader, writer, MediaUnitSize, BackupHeader.Flags, encrypt, development); + + // Write out new CryptoMethod and BitMask flags + partitionHeader.UpdateCryptoAndMasks(reader, writer, this, encrypt); + } + + return true; + } + + /// + /// Get a specific partition header from the partition table + /// + /// BinaryReader representing the input stream + /// Partition number to attempt to retrieve + /// NCCH header for the partition requested, null on error + public NCCHHeader GetPartitionHeader(BinaryReader reader, int partitionNumber) + { + if (!PartitionsTable[partitionNumber].IsValid()) + { + Console.WriteLine($"Partition {partitionNumber} Not found... Skipping..."); + return null; + } + + // Seek to the beginning of the NCCH partition + reader.BaseStream.Seek((PartitionsTable[partitionNumber].Offset * MediaUnitSize), SeekOrigin.Begin); + + NCCHHeader partitionHeader = NCCHHeader.Read(reader, true); + if (partitionHeader == null) + { + Console.WriteLine($"Partition {partitionNumber} Unable to read NCCH header"); + return null; + } + + partitionHeader.PartitionNumber = partitionNumber; + partitionHeader.Entry = PartitionsTable[partitionNumber]; + return partitionHeader; + } } } diff --git a/3DSDecrypt/ThreeDSTool.cs b/3DSDecrypt/ThreeDSTool.cs index 78f06f1..71938d0 100644 --- a/3DSDecrypt/ThreeDSTool.cs +++ b/3DSDecrypt/ThreeDSTool.cs @@ -1,8 +1,5 @@ using System; using System.IO; -using System.Linq; -using System.Numerics; -using ThreeDS.Data; using ThreeDS.Headers; namespace ThreeDS @@ -24,31 +21,6 @@ namespace ThreeDS /// private readonly bool encrypt; - /// - /// Boot rom key - /// - private BigInteger KeyX; - - /// - /// NCCH boot rom key - /// - private BigInteger KeyX2C; - - /// - /// Kernel9/Process9 key - /// - private BigInteger KeyY; - - /// - /// Normal AES key - /// - private BigInteger NormalKey; - - /// - /// NCCH AES key - /// - private BigInteger NormalKey2C; - public ThreeDSTool(string filename, bool development, bool encrypt) { this.filename = filename; @@ -78,375 +50,11 @@ namespace ThreeDS return false; } - // Iterate over all 8 NCCH partitions - for (int p = 0; p < 8; p++) - { - if (!header.PartitionsTable[p].IsValid()) - { - Console.WriteLine($"Partition {p} Not found... Skipping..."); - continue; - } - - // Seek to the beginning of the NCCH partition - reader.BaseStream.Seek((header.PartitionsTable[p].Offset * header.MediaUnitSize), SeekOrigin.Begin); - - NCCHHeader partitionHeader = NCCHHeader.Read(reader, true); - if (partitionHeader == null) - { - Console.WriteLine($"Partition {p} Unable to read NCCH header"); - continue; - } - - // Check if the 'NoCrypto' bit is set - if (partitionHeader.Flags.PossblyDecrypted ^ encrypt) - { - Console.WriteLine($"Partition {p}: Already " + (encrypt ? "Encrypted" : "Decrypted") + "?..."); - continue; - } - - // Determine the Keys to be used - SetEncryptionKeys(header, p, partitionHeader, encrypt); - - // Process each of the pieces if they exist - ProcessExtendedHeader(reader, writer, header, p, partitionHeader, encrypt); - ProcessExeFS(reader, writer, header, p, partitionHeader, encrypt); - ProcessRomFS(reader, writer, header, p, partitionHeader, encrypt); - - // Write out new CryptoMethod and BitMask flags - UpdateCryptoAndMasks(reader, writer, header, p, partitionHeader, encrypt); - } + // Process all 8 NCCH partitions + header.ProcessAllPartitions(reader, writer, encrypt, development); } return true; - } - - /// - /// Determine the set of keys to be used for encryption or decryption - /// - /// File header for backup information - /// Partition number, only used for logging - /// Current partition header - /// True if we're encrypting the file, false otherwise - private void SetEncryptionKeys(NCSDHeader header, int partitionNumber, NCCHHeader partitionHeader, bool encrypt) - { - KeyX = 0; - KeyX2C = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C); - KeyY = new BigInteger(partitionHeader.RSA2048Signature.Take(16).Reverse().ToArray()); // KeyY is the first 16 bytes of the partition RSA-2048 SHA-256 signature - - NormalKey = 0; - NormalKey2C = Helper.RotateLeft((Helper.RotateLeft(KeyX2C, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128); - - // Set the header to use based on mode - BitMasks masks = 0; - CryptoMethod method = 0; - if (encrypt) - { - masks = header.BackupHeader.Flags.BitMasks; - method = header.BackupHeader.Flags.CryptoMethod; - } - else - { - masks = partitionHeader.Flags.BitMasks; - method = partitionHeader.Flags.CryptoMethod; - } - - if ((masks & BitMasks.FixedCryptoKey) != 0) - { - NormalKey = 0x00; - NormalKey2C = 0x00; - if (partitionNumber == 0) - Console.WriteLine("Encryption Method: Zero Key"); - } - else - { - if (method == CryptoMethod.Original) - { - KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C); - if (partitionNumber == 0) - Console.WriteLine("Encryption Method: Key 0x2C"); - } - else if (method == CryptoMethod.Seven) - { - KeyX = (development ? Constants.KeyX0x25 : Constants.KeyX0x25); - if (partitionNumber == 0) - Console.WriteLine("Encryption Method: Key 0x25"); - } - else if (method == CryptoMethod.NineThree) - { - KeyX = (development ? Constants.DevKeyX0x18 : Constants.KeyX0x18); - if (partitionNumber == 0) - Console.WriteLine("Encryption Method: Key 0x18"); - } - else if (method == CryptoMethod.NineSix) - { - KeyX = (development ? Constants.DevKeyX0x1B : Constants.KeyX0x1B); - if (partitionNumber == 0) - Console.WriteLine("Encryption Method: Key 0x1B"); - } - - NormalKey = Helper.RotateLeft((Helper.RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128); - } } - - /// - /// Process the extended header, if it exists - /// - /// BinaryReader representing the input stream - /// BinaryWriter representing the output stream - /// File header - /// Partition number for logging - /// Partition header - /// True if we want to encrypt the extended header, false otherwise - private void ProcessExtendedHeader(BinaryReader reader, BinaryWriter writer, NCSDHeader header, int partitionNumber, NCCHHeader partitionHeader, bool encrypt) - { - if (partitionHeader.ExtendedHeaderSizeInBytes > 0) - { - reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset * header.MediaUnitSize) + 0x200, SeekOrigin.Begin); - writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset * header.MediaUnitSize) + 0x200, SeekOrigin.Begin); - - Console.WriteLine($"Partition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + ": ExHeader"); - - var cipher = Helper.CreateAESCipher(NormalKey2C, partitionHeader.PlainIV, encrypt); - writer.Write(cipher.ProcessBytes(reader.ReadBytes(Constants.CXTExtendedDataHeaderLength))); - writer.Flush(); - } - else - { - Console.WriteLine($"Partition {partitionNumber} ExeFS: No Extended Header... Skipping..."); - } - } - - /// - /// Process the ExeFS, if it exists - /// - /// BinaryReader representing the input stream - /// BinaryWriter representing the output stream - /// File header - /// Partition number for logging - /// Partition header - /// True if we want to encrypt the extended header, false otherwise - private void ProcessExeFS(BinaryReader reader, BinaryWriter writer, NCSDHeader header, int partitionNumber, NCCHHeader partitionHeader, bool encrypt) - { - if (partitionHeader.ExeFSSizeInMediaUnits > 0) - { - // If we're decrypting, we need to decrypt the filename table first - if (!encrypt) - ProcessExeFSFilenameTable(reader, writer, header, partitionNumber, partitionHeader, encrypt); - - // For all but the original crypto method, process each of the files in the table - if (partitionHeader.Flags.CryptoMethod != CryptoMethod.Original) - { - reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin); - ExeFSHeader exefsHeader = ExeFSHeader.Read(reader); - if (exefsHeader != null) - { - foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders) - { - // Only decrypt a file if it's a code binary - if (!fileHeader.IsCodeBinary) - continue; - - uint datalenM = ((fileHeader.FileSize) / (1024 * 1024)); - uint datalenB = ((fileHeader.FileSize) % (1024 * 1024)); - uint ctroffset = ((fileHeader.FileOffset + header.MediaUnitSize) / 0x10); - - byte[] exefsIVWithOffsetForHeader = Helper.AddToByteArray(partitionHeader.ExeFSIV, (int)ctroffset); - - var firstCipher = Helper.CreateAESCipher(NormalKey, exefsIVWithOffsetForHeader, encrypt); - var secondCipher = Helper.CreateAESCipher(NormalKey2C, exefsIVWithOffsetForHeader, !encrypt); - - reader.BaseStream.Seek((((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.MediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin); - writer.BaseStream.Seek((((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.MediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin); - - if (datalenM > 0) - { - for (int i = 0; i < datalenM; i++) - { - writer.Write(secondCipher.ProcessBytes(firstCipher.ProcessBytes(reader.ReadBytes(1024 * 1024)))); - writer.Flush(); - Console.Write($"\rPartition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {i} / {datalenM + 1} mb..."); - } - } - - if (datalenB > 0) - { - writer.Write(secondCipher.DoFinal(firstCipher.DoFinal(reader.ReadBytes((int)datalenB)))); - writer.Flush(); - } - - Console.Write($"\rPartition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n"); - } - } - } - - // If we're encrypting, we need to encrypt the filename table now - if (encrypt) - ProcessExeFSFilenameTable(reader, writer, header, partitionNumber, partitionHeader, encrypt); - - // Process the ExeFS - int exefsSizeM = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.MediaUnitSize) / (1024 * 1024); - int exefsSizeB = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.MediaUnitSize) % (1024 * 1024); - int ctroffsetE = (int)(header.MediaUnitSize / 0x10); - - byte[] exefsIVWithOffset = Helper.AddToByteArray(partitionHeader.ExeFSIV, ctroffsetE); - - var exeFS = Helper.CreateAESCipher(NormalKey2C, exefsIVWithOffset, encrypt); - - reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.MediaUnitSize, SeekOrigin.Begin); - writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.MediaUnitSize, SeekOrigin.Begin); - if (exefsSizeM > 0) - { - for (int i = 0; i < exefsSizeM; i++) - { - writer.Write(exeFS.ProcessBytes(reader.ReadBytes(1024 * 1024))); - writer.Flush(); - Console.Write($"\rPartition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb"); - } - } - if (exefsSizeB > 0) - { - writer.Write(exeFS.DoFinal(reader.ReadBytes(exefsSizeB))); - writer.Flush(); - } - - Console.Write($"\rPartition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n"); - } - else - { - Console.WriteLine($"Partition {partitionNumber} ExeFS: No Data... Skipping..."); - } - } - - /// - /// Process the ExeFS Filename Table - /// - /// BinaryReader representing the input stream - /// BinaryWriter representing the output stream - /// File header - /// Partition number for logging - /// Partition header - /// True if we want to encrypt the extended header, false otherwise - private void ProcessExeFSFilenameTable(BinaryReader reader, BinaryWriter writer, NCSDHeader header, int partitionNumber, NCCHHeader partitionHeader, bool encrypt) - { - reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin); - writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin); - - Console.WriteLine($"Partition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table"); - - var exeFSFilenameTable = Helper.CreateAESCipher(NormalKey2C, partitionHeader.ExeFSIV, encrypt); - writer.Write(exeFSFilenameTable.ProcessBytes(reader.ReadBytes((int)header.MediaUnitSize))); - writer.Flush(); - } - - /// - /// Process the RomFS, if it exists - /// - /// BinaryReader representing the input stream - /// BinaryWriter representing the output stream - /// File header - /// Partition number for logging - /// Partition header - /// True if we want to encrypt the extended header, false otherwise - private void ProcessRomFS(BinaryReader reader, BinaryWriter writer, NCSDHeader header, int partitionNumber, NCCHHeader partitionHeader, bool encrypt) - { - if (partitionHeader.RomFSOffsetInMediaUnits != 0) - { - int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.MediaUnitSize) / (1024 * 1024); - int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.MediaUnitSize) % (1024 * 1024); - - // Encrypting RomFS for partitions 1 and up always use Key0x2C - if (encrypt && partitionNumber > 0) - { - // If the backup flags aren't provided and we're encrypting, assume defaults - if (header.BackupHeader.Flags == null) - { - KeyX = KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C); - NormalKey = Helper.RotateLeft((Helper.RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128); - } - - if ((header.BackupHeader.Flags.BitMasks & BitMasks.FixedCryptoKey) != 0) // except if using zero-key - { - NormalKey = 0x00; - } - else - { - KeyX = KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C); - NormalKey = Helper.RotateLeft((Helper.RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128); - } - } - - var cipher = Helper.CreateAESCipher(NormalKey, partitionHeader.RomFSIV, encrypt); - - reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin); - writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin); - if (romfsSizeM > 0) - { - for (int i = 0; i < romfsSizeM; i++) - { - writer.Write(cipher.ProcessBytes(reader.ReadBytes(1024 * 1024))); - writer.Flush(); - Console.Write($"\rPartition {partitionNumber} RomFS: Decrypting: {i} / {romfsSizeM + 1} mb"); - } - } - if (romfsSizeB > 0) - { - writer.Write(cipher.DoFinal(reader.ReadBytes(romfsSizeB))); - writer.Flush(); - } - - Console.Write($"\rPartition {partitionNumber} RomFS: Decrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n"); - } - else - { - Console.WriteLine($"Partition {partitionNumber} RomFS: No Data... Skipping..."); - } - } - - /// - /// Attempt to decrypt a 3DS file - /// - /// BinaryReader representing the input stream - /// BinaryWriter representing the output stream - /// NCSD header for the 3DS file - /// Partition number for seeking in the table - /// Current partition header value - /// True if we're writing encrypted values, false otherwise - private void UpdateCryptoAndMasks(BinaryReader reader, BinaryWriter writer, NCSDHeader header, int partitionNumber, NCCHHeader partitionHeader, bool encrypt) - { - // Write the new CryptoMethod - writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset * header.MediaUnitSize) + 0x18B, SeekOrigin.Begin); - if (encrypt) - { - // For partitions 1 and up, set crypto-method to 0x00 - if (partitionNumber > 0) - writer.Write((byte)CryptoMethod.Original); - - // If partition 0, restore crypto-method from backup flags - else - writer.Write((byte)header.BackupHeader.Flags.CryptoMethod); - } - else - { - writer.Write((byte)CryptoMethod.Original); - } - writer.Flush(); - - // Write the new BitMasks flag - writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset * header.MediaUnitSize) + 0x18F, SeekOrigin.Begin); - BitMasks flag = partitionHeader.Flags.BitMasks; - if (encrypt) - { - flag = (flag & ((BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF)); - flag = (flag | (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & header.BackupHeader.Flags.BitMasks); - } - else - { - flag = flag & (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF); - flag = (flag | BitMasks.NoCrypto); - } - - writer.Write((byte)flag); - writer.Flush(); - } } }