From 3f07271e7f20671d66d3a958984bc6bd69a3f1cd Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Sat, 12 Oct 2024 00:59:07 -0400 Subject: [PATCH] Move encrypt flag out of args --- NDecrypt.Core/DecryptArgs.cs | 5 -- NDecrypt.Core/ITool.cs | 12 ++- NDecrypt.N3DS/CIATool.cs | 149 ++++++++++++++++++++--------------- NDecrypt.N3DS/ThreeDSTool.cs | 81 +++++++++++-------- NDecrypt.Nitro/DSTool.cs | 33 +++++--- NDecrypt/Program.cs | 31 ++++++-- 6 files changed, 189 insertions(+), 122 deletions(-) diff --git a/NDecrypt.Core/DecryptArgs.cs b/NDecrypt.Core/DecryptArgs.cs index 6d763e0..7bade87 100644 --- a/NDecrypt.Core/DecryptArgs.cs +++ b/NDecrypt.Core/DecryptArgs.cs @@ -10,11 +10,6 @@ namespace NDecrypt.Core { #region Common Fields - /// - /// Flag to indicate operation - /// - public bool Encrypt { get; set; } = true; - /// /// Flag to indicate forcing the operation /// diff --git a/NDecrypt.Core/ITool.cs b/NDecrypt.Core/ITool.cs index 98419f5..5b09130 100644 --- a/NDecrypt.Core/ITool.cs +++ b/NDecrypt.Core/ITool.cs @@ -2,6 +2,16 @@ { public interface ITool { - bool ProcessFile(); + /// + /// Attempts to encrypt an input file + /// + /// True if the file could be encrypted, false otherwise + bool EncryptFile(); + + /// + /// Attempts to decrypt an input file + /// + /// True if the file could be decrypted, false otherwise + bool DecryptFile(); } } diff --git a/NDecrypt.N3DS/CIATool.cs b/NDecrypt.N3DS/CIATool.cs index 59dfb31..f47c74d 100644 --- a/NDecrypt.N3DS/CIATool.cs +++ b/NDecrypt.N3DS/CIATool.cs @@ -57,10 +57,17 @@ namespace NDecrypt.N3DS #region Common Methods + /// + public bool EncryptFile() => ProcessFile(encrypt: true); + + /// + public bool DecryptFile() => ProcessFile(encrypt: false); + /// /// Process an input file given the input values /// - public bool ProcessFile() + /// Indicates if the file should be encrypted or decrypted + private bool ProcessFile(bool encrypt) { // Ensure the constants are all set if (decryptArgs.IsReady != true) @@ -84,7 +91,7 @@ namespace NDecrypt.N3DS } // Process all NCCH partitions - ProcessAllPartitions(cia, reader, writer); + ProcessAllPartitions(cia, encrypt, reader, writer); return false; } @@ -100,9 +107,10 @@ namespace NDecrypt.N3DS /// Process all partitions in the content file data of a CIA header /// /// CIA representing the 3DS CIA file + /// Indicates if the file should be encrypted or decrypted /// Stream representing the input /// Stream representing the output - private void ProcessAllPartitions(CIA cia, Stream reader, Stream writer) + private void ProcessAllPartitions(CIA cia, bool encrypt, Stream reader, Stream writer) { // Iterate over all NCCH partitions for (int p = 0; p < cia.Partitions!.Length; p++) @@ -111,19 +119,21 @@ namespace NDecrypt.N3DS if (ncchHeader == null) continue; - ProcessPartition(p, ncchHeader, reader, writer); + ProcessPartition(ncchHeader, p, encrypt, reader, writer); } } /// /// Process a single partition /// - /// Index of the partition /// NCCH header representing the partition + /// Index of the partition + /// Indicates if the file should be encrypted or decrypted /// Stream representing the input /// Stream representing the output - private void ProcessPartition(int partitionIndex, - NCCHHeader ncchHeader, + private void ProcessPartition(NCCHHeader ncchHeader, + int partitionIndex, + bool encrypt, Stream reader, Stream writer) { @@ -133,9 +143,9 @@ namespace NDecrypt.N3DS 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() ^ encrypt) { - Console.WriteLine($"Partition {partitionIndex}: Already " + (decryptArgs.Encrypt ? "Encrypted" : "Decrypted") + "?..."); + Console.WriteLine($"Partition {partitionIndex}: Already " + (encrypt ? "Encrypted" : "Decrypted") + "?..."); return; } @@ -143,24 +153,24 @@ namespace NDecrypt.N3DS var tableEntry = new PartitionTableEntry(); // Determine the Keys to be used - SetEncryptionKeys(partitionIndex, ncchHeader); + SetEncryptionKeys(ncchHeader, partitionIndex, encrypt); // Process the extended header - ProcessExtendedHeader(partitionIndex, ncchHeader, tableEntry, reader, writer); + ProcessExtendedHeader(ncchHeader, partitionIndex, tableEntry, encrypt, reader, writer); // If we're encrypting, encrypt the filesystems and update the flags - if (decryptArgs.Encrypt) + if (encrypt) { - EncryptExeFS(partitionIndex, ncchHeader, tableEntry, reader, writer); - EncryptRomFS(partitionIndex, ncchHeader, tableEntry, reader, writer); - UpdateEncryptCryptoAndMasks(partitionIndex, ncchHeader, tableEntry, writer); + EncryptExeFS(ncchHeader, partitionIndex, tableEntry, reader, writer); + EncryptRomFS(ncchHeader, partitionIndex, tableEntry, reader, writer); + UpdateEncryptCryptoAndMasks(ncchHeader, partitionIndex, tableEntry, writer); } // If we're decrypting, decrypt the filesystems and update the flags else { - DecryptExeFS(partitionIndex, ncchHeader, tableEntry, reader, writer); - DecryptRomFS(partitionIndex, ncchHeader, tableEntry, reader, writer); + DecryptExeFS(ncchHeader, partitionIndex, tableEntry, reader, writer); + DecryptRomFS(ncchHeader, partitionIndex, tableEntry, reader, writer); UpdateDecryptCryptoAndMasks(ncchHeader, tableEntry, writer); } } @@ -168,9 +178,10 @@ namespace NDecrypt.N3DS /// /// Determine the set of keys to be used for encryption or decryption /// - /// Index of the partition /// NCCH header representing the partition - private void SetEncryptionKeys(int partitionIndex, NCCHHeader ncchHeader) + /// Index of the partition + /// Indicates if the file should be encrypted or decrypted + private void SetEncryptionKeys(NCCHHeader ncchHeader, int partitionIndex, bool encrypt) { KeyX[partitionIndex] = 0; KeyX2C[partitionIndex] = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C; @@ -188,7 +199,7 @@ namespace NDecrypt.N3DS // Set the header to use based on mode BitMasks masks = BitMasks.NoCrypto; CryptoMethod method = CryptoMethod.Original; - if (decryptArgs.Encrypt) + if (encrypt) { // TODO: Can we actually re-encrypt a CIA? //masks = ciaHeader.BackupHeader.Flags.BitMasks; @@ -236,14 +247,16 @@ namespace NDecrypt.N3DS /// /// Process the extended header, if it exists /// - /// Index of the partition /// NCCH header representing the partition + /// Index of the partition /// PartitionTableEntry header representing the partition + /// Indicates if the file should be encrypted or decrypted /// Stream representing the input /// Stream representing the output - private bool ProcessExtendedHeader(int partitionIndex, - NCCHHeader ncchHeader, + private bool ProcessExtendedHeader(NCCHHeader ncchHeader, + int partitionIndex, PartitionTableEntry tableEntry, + bool encrypt, Stream reader, Stream writer) { @@ -255,9 +268,9 @@ namespace NDecrypt.N3DS reader.Seek((tableEntry.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin); writer.Seek((tableEntry.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin); - Console.WriteLine($"Partition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + ": ExHeader"); + Console.WriteLine($"Partition {partitionIndex} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + ": ExHeader"); - var cipher = CreateAESCipher(NormalKey2C[partitionIndex], ncchHeader.PlainIV(), decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey2C[partitionIndex], ncchHeader.PlainIV(), encrypt); writer.Write(cipher.ProcessBytes(reader.ReadBytes(Constants.CXTExtendedDataHeaderLength))); writer.Flush(); return true; @@ -272,14 +285,16 @@ namespace NDecrypt.N3DS /// /// Process the extended header, if it exists /// - /// Index of the partition /// NCCH header representing the partition + /// Index of the partition /// PartitionTableEntry header representing the partition + /// Indicates if the file should be encrypted or decrypted /// Stream representing the input /// Stream representing the output - private void ProcessExeFSFileEntries(int partitionIndex, - NCCHHeader ncchHeader, + private void ProcessExeFSFileEntries(NCCHHeader ncchHeader, + int partitionIndex, PartitionTableEntry tableEntry, + bool encrypt, Stream reader, Stream writer) { @@ -308,8 +323,8 @@ namespace NDecrypt.N3DS byte[] exefsIVWithOffsetForHeader = AddToByteArray(ncchHeader.ExeFSIV(), (int)ctroffset); - var firstCipher = CreateAESCipher(NormalKey[partitionIndex], exefsIVWithOffsetForHeader, decryptArgs.Encrypt); - var secondCipher = CreateAESCipher(NormalKey2C[partitionIndex], exefsIVWithOffsetForHeader, !decryptArgs.Encrypt); + var firstCipher = CreateAESCipher(NormalKey[partitionIndex], exefsIVWithOffsetForHeader, encrypt); + var secondCipher = CreateAESCipher(NormalKey2C[partitionIndex], exefsIVWithOffsetForHeader, !encrypt); reader.Seek((((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * mediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin); writer.Seek((((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * mediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin); @@ -320,7 +335,7 @@ namespace NDecrypt.N3DS { writer.Write(secondCipher.ProcessBytes(firstCipher.ProcessBytes(reader.ReadBytes(1024 * 1024)))); writer.Flush(); - Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.FileName}... {i} / {datalenM + 1} mb..."); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.FileName}... {i} / {datalenM + 1} mb..."); } } @@ -330,21 +345,23 @@ namespace NDecrypt.N3DS writer.Flush(); } - Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.FileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n"); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (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 + /// Index of the partition /// PartitionTableEntry header representing the partition + /// Indicates if the file should be encrypted or decrypted /// Stream representing the input /// Stream representing the output - private void ProcessExeFSFilenameTable(int partitionIndex, - NCCHHeader ncchHeader, + private void ProcessExeFSFilenameTable(NCCHHeader ncchHeader, + int partitionIndex, PartitionTableEntry tableEntry, + bool encrypt, Stream reader, Stream writer) { @@ -354,9 +371,9 @@ namespace NDecrypt.N3DS reader.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); writer.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); - Console.WriteLine($"Partition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table"); + Console.WriteLine($"Partition {partitionIndex} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table"); - var exeFSFilenameTable = CreateAESCipher(NormalKey2C[partitionIndex], ncchHeader.ExeFSIV(), decryptArgs.Encrypt); + var exeFSFilenameTable = CreateAESCipher(NormalKey2C[partitionIndex], ncchHeader.ExeFSIV(), encrypt); writer.Write(exeFSFilenameTable.ProcessBytes(reader.ReadBytes((int)mediaUnitSize))); writer.Flush(); } @@ -364,14 +381,16 @@ namespace NDecrypt.N3DS /// /// Process the ExeFS, if it exists /// - /// Index of the partition /// NCCH header representing the partition + /// Index of the partition /// PartitionTableEntry header representing the partition + /// Indicates if the file should be encrypted or decrypted /// Stream representing the input /// Stream representing the output - private void ProcessExeFS(int partitionIndex, - NCCHHeader ncchHeader, + private void ProcessExeFS(NCCHHeader ncchHeader, + int partitionIndex, PartitionTableEntry tableEntry, + bool encrypt, Stream reader, Stream writer) { @@ -384,7 +403,7 @@ namespace NDecrypt.N3DS byte[] exefsIVWithOffset = AddToByteArray(ncchHeader.ExeFSIV(), ctroffsetE); - var exeFS = CreateAESCipher(NormalKey2C[partitionIndex], exefsIVWithOffset, decryptArgs.Encrypt); + var exeFS = CreateAESCipher(NormalKey2C[partitionIndex], exefsIVWithOffset, encrypt); reader.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * mediaUnitSize, SeekOrigin.Begin); writer.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * mediaUnitSize, SeekOrigin.Begin); @@ -394,7 +413,7 @@ namespace NDecrypt.N3DS { writer.Write(exeFS.ProcessBytes(reader.ReadBytes(1024 * 1024))); writer.Flush(); - Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb"); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb"); } } if (exefsSizeB > 0) @@ -403,7 +422,7 @@ namespace NDecrypt.N3DS writer.Flush(); } - Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n"); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n"); } #endregion @@ -413,13 +432,13 @@ namespace NDecrypt.N3DS /// /// Decrypt the ExeFS, if it exists /// - /// Index of the partition /// NCCH header representing the partition + /// Index of the partition /// PartitionTableEntry header representing the partition /// Stream representing the input /// Stream representing the output - private void DecryptExeFS(int partitionIndex, - NCCHHeader ncchHeader, + private void DecryptExeFS(NCCHHeader ncchHeader, + int partitionIndex, PartitionTableEntry tableEntry, Stream reader, Stream writer) @@ -432,27 +451,27 @@ namespace NDecrypt.N3DS } // Decrypt the filename table - ProcessExeFSFilenameTable(partitionIndex, ncchHeader, tableEntry, reader, writer); + ProcessExeFSFilenameTable(ncchHeader, partitionIndex, tableEntry, encrypt: false, reader, writer); // For all but the original crypto method, process each of the files in the table if (ncchHeader.Flags!.CryptoMethod != CryptoMethod.Original) - ProcessExeFSFileEntries(partitionIndex, ncchHeader, tableEntry, reader, writer); + ProcessExeFSFileEntries(ncchHeader, partitionIndex, tableEntry, encrypt: false, reader, writer); // Decrypt the rest of the ExeFS - ProcessExeFS(partitionIndex, ncchHeader, tableEntry, reader, writer); + ProcessExeFS(ncchHeader, partitionIndex, tableEntry, encrypt: false, reader, writer); } /// /// Decrypt the RomFS, if it exists /// - /// Index of the partition /// NCCH header representing the partition + /// Index of the partition /// PartitionTableEntry header representing the partition /// Stream representing the input /// Stream representing the output /// TODO: See how much can be extracted into a common method with Encrypt - private void DecryptRomFS(int partitionIndex, - NCCHHeader ncchHeader, + private void DecryptRomFS(NCCHHeader ncchHeader, + int partitionIndex, PartitionTableEntry tableEntry, Stream reader, Stream writer) @@ -470,7 +489,7 @@ namespace NDecrypt.N3DS long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * mediaUnitSize) / (1024 * 1024)); int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * mediaUnitSize) % (1024 * 1024)); - var cipher = CreateAESCipher(NormalKey[partitionIndex], ncchHeader.RomFSIV(), decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey[partitionIndex], ncchHeader.RomFSIV(), encrypt: false); reader.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); writer.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); @@ -526,13 +545,13 @@ namespace NDecrypt.N3DS /// /// Encrypt the ExeFS, if it exists /// - /// Index of the partition /// NCCH header representing the partition + /// Index of the partition /// PartitionTableEntry header representing the partition /// Stream representing the input /// Stream representing the output - private void EncryptExeFS(int partitionIndex, - NCCHHeader ncchHeader, + private void EncryptExeFS(NCCHHeader ncchHeader, + int partitionIndex, PartitionTableEntry tableEntry, Stream reader, Stream writer) @@ -550,23 +569,23 @@ namespace NDecrypt.N3DS // ProcessExeFSFileEntries(ncchHeader, reader, writer); // Encrypt the filename table - ProcessExeFSFilenameTable(partitionIndex, ncchHeader, tableEntry, reader, writer); + ProcessExeFSFilenameTable(ncchHeader, partitionIndex, tableEntry, encrypt: true, reader, writer); // Encrypt the rest of the ExeFS - ProcessExeFS(partitionIndex, ncchHeader, tableEntry, reader, writer); + ProcessExeFS(ncchHeader, partitionIndex, tableEntry, encrypt: true, reader, writer); } /// /// Encrypt the RomFS, if it exists /// - /// Index of the partition /// NCCH header representing the partition + /// Index of the partition /// PartitionTableEntry header representing the partition /// Stream representing the input /// Stream representing the output /// TODO: See how much can be extracted into a common method with Decrypt - private void EncryptRomFS(int partitionIndex, - NCCHHeader ncchHeader, + private void EncryptRomFS(NCCHHeader ncchHeader, + int partitionIndex, PartitionTableEntry tableEntry, Stream reader, Stream writer) @@ -599,7 +618,7 @@ namespace NDecrypt.N3DS //} } - var cipher = CreateAESCipher(NormalKey[partitionIndex], ncchHeader.RomFSIV(), decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey[partitionIndex], ncchHeader.RomFSIV(), encrypt: true); reader.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); writer.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin); @@ -624,12 +643,12 @@ namespace NDecrypt.N3DS /// /// Update the CryptoMethod and BitMasks for the encrypted partition /// - /// Index of the partition /// NCCH header representing the partition + /// Index of the partition /// PartitionTableEntry header representing the partition /// Stream representing the output - private void UpdateEncryptCryptoAndMasks(int partitionIndex, - NCCHHeader ncchHeader, + private void UpdateEncryptCryptoAndMasks(NCCHHeader ncchHeader, + int partitionIndex, PartitionTableEntry tableEntry, Stream writer) { diff --git a/NDecrypt.N3DS/ThreeDSTool.cs b/NDecrypt.N3DS/ThreeDSTool.cs index 57a608e..7e1a786 100644 --- a/NDecrypt.N3DS/ThreeDSTool.cs +++ b/NDecrypt.N3DS/ThreeDSTool.cs @@ -55,10 +55,17 @@ namespace NDecrypt.N3DS #region Common Methods + /// + public bool EncryptFile() => ProcessFile(encrypt: true); + + /// + public bool DecryptFile() => ProcessFile(encrypt: false); + /// /// Process an input file given the input values /// - public bool ProcessFile() + /// Indicates if the file should be encrypted or decrypted + private bool ProcessFile(bool encrypt) { // Ensure the constants are all set if (decryptArgs.IsReady != true) @@ -82,7 +89,7 @@ namespace NDecrypt.N3DS } // Process all 8 NCCH partitions - ProcessAllPartitions(cart, reader, writer); + ProcessAllPartitions(cart, encrypt, reader, writer); return true; } @@ -98,9 +105,10 @@ namespace NDecrypt.N3DS /// Process all partitions in the partition table of an NCSD header /// /// Cart representing the 3DS file + /// Indicates if the file should be encrypted or decrypted /// Stream representing the input /// Stream representing the output - private void ProcessAllPartitions(Cart cart, Stream reader, Stream writer) + private void ProcessAllPartitions(Cart cart, bool encrypt, Stream reader, Stream writer) { // Check the partitions table if (cart.Header?.PartitionsTable == null) @@ -119,7 +127,7 @@ namespace NDecrypt.N3DS continue; } - ProcessPartition(cart, partitionIndex, reader, writer); + ProcessPartition(cart, partitionIndex, encrypt, reader, writer); } } @@ -128,10 +136,12 @@ namespace NDecrypt.N3DS /// /// Cart representing the 3DS file /// Index of the partition + /// Indicates if the file should be encrypted or decrypted /// Stream representing the input /// Stream representing the output private void ProcessPartition(Cart cart, int partitionIndex, + bool encrypt, Stream reader, Stream writer) { @@ -141,20 +151,20 @@ namespace NDecrypt.N3DS 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 (cart.Partitions![partitionIndex]!.Flags!.PossblyDecrypted() ^ decryptArgs.Encrypt) + else if (cart.Partitions![partitionIndex]!.Flags!.PossblyDecrypted() ^ encrypt) { - Console.WriteLine($"Partition {partitionIndex}: Already " + (decryptArgs.Encrypt ? "Encrypted" : "Decrypted") + "?..."); + Console.WriteLine($"Partition {partitionIndex}: Already " + (encrypt ? "Encrypted" : "Decrypted") + "?..."); return; } // Determine the Keys to be used - SetEncryptionKeys(cart, partitionIndex); + SetEncryptionKeys(cart, partitionIndex, encrypt); // Process the extended header - ProcessExtendedHeader(cart, partitionIndex, reader, writer); + ProcessExtendedHeader(cart, partitionIndex, encrypt, reader, writer); // If we're encrypting, encrypt the filesystems and update the flags - if (decryptArgs.Encrypt) + if (encrypt) { EncryptExeFS(cart, partitionIndex, reader, writer); EncryptRomFS(cart, partitionIndex, reader, writer); @@ -175,7 +185,8 @@ namespace NDecrypt.N3DS /// /// Cart representing the 3DS file /// Index of the partition - private void SetEncryptionKeys(Cart cart, int partitionIndex) + /// Indicates if the file should be encrypted or decrypted + private void SetEncryptionKeys(Cart cart, int partitionIndex, bool encrypt) { // Get the backup header var backupHeader = cart.CardInfoHeader!.InitialData!.BackupHeader; @@ -196,7 +207,7 @@ namespace NDecrypt.N3DS // Set the header to use based on mode BitMasks masks; CryptoMethod method; - if (decryptArgs.Encrypt) + if (encrypt) { masks = backupHeader!.Flags!.BitMasks; method = backupHeader.Flags.CryptoMethod; @@ -245,10 +256,12 @@ namespace NDecrypt.N3DS /// /// Cart representing the 3DS file /// Index of the partition + /// Indicates if the file should be encrypted or decrypted /// Stream representing the input /// Stream representing the output private bool ProcessExtendedHeader(Cart cart, int partitionIndex, + bool encrypt, Stream reader, Stream writer) { @@ -262,10 +275,10 @@ namespace NDecrypt.N3DS reader.Seek(partitionOffset + 0x200, SeekOrigin.Begin); writer.Seek(partitionOffset + 0x200, SeekOrigin.Begin); - Console.WriteLine($"Partition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + ": ExHeader"); + Console.WriteLine($"Partition {partitionIndex} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + ": ExHeader"); // Create the Plain AES cipher for this partition - var cipher = CreateAESCipher(NormalKey2C[partitionIndex], cart.PlainIV(partitionIndex), decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey2C[partitionIndex], cart.PlainIV(partitionIndex), encrypt); // Process the extended header byte[] readBytes = reader.ReadBytes(Constants.CXTExtendedDataHeaderLength); @@ -290,11 +303,13 @@ namespace NDecrypt.N3DS /// Process the extended header, if it exists /// /// Cart representing the 3DS file - /// PartitionTableEntry header representing the partition + /// Index of the partition + /// Indicates if the file should be encrypted or decrypted /// Stream representing the input /// Stream representing the output private void ProcessExeFSFileEntries(Cart cart, int partitionIndex, + bool encrypt, Stream reader, Stream writer) { @@ -327,8 +342,8 @@ namespace NDecrypt.N3DS // Create the ExeFS AES ciphers for this partition byte[] exefsIVWithOffsetForHeader = AddToByteArray(cart.ExeFSIV(partitionIndex), (int)ctroffset); - var firstCipher = CreateAESCipher(NormalKey[partitionIndex], exefsIVWithOffsetForHeader, decryptArgs.Encrypt); - var secondCipher = CreateAESCipher(NormalKey2C[partitionIndex], exefsIVWithOffsetForHeader, !decryptArgs.Encrypt); + var firstCipher = CreateAESCipher(NormalKey[partitionIndex], exefsIVWithOffsetForHeader, encrypt); + var secondCipher = CreateAESCipher(NormalKey2C[partitionIndex], exefsIVWithOffsetForHeader, !encrypt); // Seek to the file entry reader.Seek(exeFsOffset + fileHeader.FileOffset, SeekOrigin.Begin); @@ -344,7 +359,7 @@ namespace NDecrypt.N3DS byte[] secondProcessedBytes = secondCipher.ProcessBytes(firstProcessedBytes); writer.Write(secondProcessedBytes); writer.Flush(); - Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.FileName}... {i} / {datalenM + 1} mb..."); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.FileName}... {i} / {datalenM + 1} mb..."); } } @@ -358,7 +373,7 @@ namespace NDecrypt.N3DS writer.Flush(); } - Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.FileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n"); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.FileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n"); } } @@ -367,10 +382,12 @@ namespace NDecrypt.N3DS /// /// Cart representing the 3DS file /// Index of the partition + /// Indicates if the file should be encrypted or decrypted /// Stream representing the input /// Stream representing the output private void ProcessExeFSFilenameTable(Cart cart, int partitionIndex, + bool encrypt, Stream reader, Stream writer) { @@ -383,10 +400,10 @@ namespace NDecrypt.N3DS reader.Seek(exeFsHeaderOffset, SeekOrigin.Begin); writer.Seek(exeFsHeaderOffset, SeekOrigin.Begin); - Console.WriteLine($"Partition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table"); + Console.WriteLine($"Partition {partitionIndex} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table"); // Create the ExeFS AES cipher for this partition - var cipher = CreateAESCipher(NormalKey2C[partitionIndex], cart.ExeFSIV(partitionIndex), decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey2C[partitionIndex], cart.ExeFSIV(partitionIndex), encrypt); // Process the filename table byte[] readBytes = reader.ReadBytes((int)cart.MediaUnitSize()); @@ -405,10 +422,12 @@ namespace NDecrypt.N3DS /// /// Cart representing the 3DS file /// Index of the partition + /// Indicates if the file should be encrypted or decrypted /// Stream representing the input /// Stream representing the output private bool ProcessExeFS(Cart cart, int partitionIndex, + bool encrypt, Stream reader, Stream writer) { @@ -432,7 +451,7 @@ namespace NDecrypt.N3DS // Create the ExeFS AES cipher for this partition byte[] exefsIVWithOffset = AddToByteArray(cart.ExeFSIV(partitionIndex), ctroffsetE); - var cipher = CreateAESCipher(NormalKey2C[partitionIndex], exefsIVWithOffset, decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey2C[partitionIndex], exefsIVWithOffset, encrypt); // Seek to the ExeFS reader.Seek(exeFsOffset, SeekOrigin.Begin); @@ -447,7 +466,7 @@ namespace NDecrypt.N3DS byte[] processedBytes = cipher.ProcessBytes(readBytes); writer.Write(processedBytes); writer.Flush(); - Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb"); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb"); } } @@ -460,7 +479,7 @@ namespace NDecrypt.N3DS writer.Flush(); } - Console.Write($"\rPartition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n"); + Console.Write($"\rPartition {partitionIndex} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n"); return true; } @@ -488,14 +507,14 @@ namespace NDecrypt.N3DS } // Decrypt the filename table - ProcessExeFSFilenameTable(cart, partitionIndex, reader, writer); + ProcessExeFSFilenameTable(cart, partitionIndex, encrypt: false, reader, writer); // For all but the original crypto method, process each of the files in the table if (cart.Partitions![partitionIndex]!.Flags!.CryptoMethod != CryptoMethod.Original) - ProcessExeFSFileEntries(cart, partitionIndex, reader, writer); + ProcessExeFSFileEntries(cart, partitionIndex, encrypt: false, reader, writer); // Decrypt the rest of the ExeFS - ProcessExeFS(cart, partitionIndex, reader, writer); + ProcessExeFS(cart, partitionIndex, encrypt: false, reader, writer); } /// @@ -529,7 +548,7 @@ namespace NDecrypt.N3DS int romfsSizeB = (int)((long)romFsSize % (1024 * 1024)); // Create the RomFS AES cipher for this partition - var cipher = CreateAESCipher(NormalKey[partitionIndex], cart.RomFSIV(partitionIndex), decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey[partitionIndex], cart.RomFSIV(partitionIndex), encrypt: false); // Seek to the RomFS reader.Seek(romFsOffset, SeekOrigin.Begin); @@ -619,13 +638,13 @@ namespace NDecrypt.N3DS // For all but the original crypto method, process each of the files in the table if (backupHeader!.Flags!.CryptoMethod != CryptoMethod.Original) - ProcessExeFSFileEntries(cart, partitionIndex, reader, writer); + ProcessExeFSFileEntries(cart, partitionIndex, encrypt: true, reader, writer); // Encrypt the filename table - ProcessExeFSFilenameTable(cart, partitionIndex, reader, writer); + ProcessExeFSFilenameTable(cart, partitionIndex, encrypt: true, reader, writer); // Encrypt the rest of the ExeFS - ProcessExeFS(cart, partitionIndex, reader, writer); + ProcessExeFS(cart, partitionIndex, encrypt: true, reader, writer); } /// @@ -677,7 +696,7 @@ namespace NDecrypt.N3DS } // Create the RomFS AES cipher for this partition - var cipher = CreateAESCipher(NormalKey[partitionIndex], cart.RomFSIV(partitionIndex), decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey[partitionIndex], cart.RomFSIV(partitionIndex), encrypt: true); // Seek to the RomFS reader.Seek(romFsOffset, SeekOrigin.Begin); diff --git a/NDecrypt.Nitro/DSTool.cs b/NDecrypt.Nitro/DSTool.cs index f72974e..36a92f3 100644 --- a/NDecrypt.Nitro/DSTool.cs +++ b/NDecrypt.Nitro/DSTool.cs @@ -33,10 +33,17 @@ namespace NDecrypt.Nitro this.decryptArgs = decryptArgs; } + /// + public bool EncryptFile() => ProcessFile(encrypt: true); + + /// + public bool DecryptFile() => ProcessFile(encrypt: false); + /// /// Process an input file given the input values /// - public bool ProcessFile() + /// Indicates if the file should be encrypted or decrypted + private bool ProcessFile(bool encrypt) { try { @@ -53,7 +60,7 @@ namespace NDecrypt.Nitro } // Process the secure area - ProcessSecureArea(cart, reader, writer); + ProcessSecureArea(cart, encrypt, reader, writer); return true; } @@ -69,9 +76,10 @@ namespace NDecrypt.Nitro /// Process secure area in the DS/DSi file /// s /// Cart representing the DS file + /// Indicates if the file should be encrypted or decrypted /// Stream representing the input /// Stream representing the output - private void ProcessSecureArea(Cart cart, Stream reader, Stream writer) + private void ProcessSecureArea(Cart cart, bool encrypt, Stream reader, Stream writer) { // If we're forcing the operation, tell the user if (decryptArgs.Force) @@ -87,16 +95,16 @@ namespace NDecrypt.Nitro Console.WriteLine("File has an empty secure area, cannot proceed"); return; } - else if (decryptArgs.Encrypt ^ isDecrypted.Value) + else if (encrypt ^ isDecrypted.Value) { - Console.WriteLine("File is already " + (decryptArgs.Encrypt ? "encrypted" : "decrypted")); + Console.WriteLine("File is already " + (encrypt ? "encrypted" : "decrypted")); return; } } - ProcessARM9(cart.CommonHeader!, reader, writer); + ProcessARM9(cart.CommonHeader!, encrypt, reader, writer); - Console.WriteLine("File has been " + (decryptArgs.Encrypt ? "encrypted" : "decrypted")); + Console.WriteLine("File has been " + (encrypt ? "encrypted" : "decrypted")); } /// @@ -162,9 +170,10 @@ namespace NDecrypt.Nitro /// Process the secure ARM9 region of the file, if possible /// /// CommonHeader representing the DS header + /// Indicates if the file should be encrypted or decrypted /// Stream representing the input /// Stream representing the output - private void ProcessARM9(CommonHeader commonHeader, Stream reader, Stream writer) + private void ProcessARM9(CommonHeader commonHeader, bool encrypt, Stream reader, Stream writer) { // Seek to the beginning of the secure area reader.Seek(0x4000, SeekOrigin.Begin); @@ -176,13 +185,13 @@ namespace NDecrypt.Nitro // Perform the initialization steps Init1(commonHeader); - if (!decryptArgs.Encrypt) Decrypt(ref p1, ref p0); + if (!encrypt) Decrypt(ref p1, ref p0); _arg2[1] <<= 1; _arg2[2] >>= 1; Init2(); // If we're decrypting, set the proper flags - if (!decryptArgs.Encrypt) + if (!encrypt) { Decrypt(ref p1, ref p0); if (p0 == Constants.MAGIC30 && p1 == Constants.MAGIC34) @@ -206,7 +215,7 @@ namespace NDecrypt.Nitro p0 = reader.ReadUInt32(); p1 = reader.ReadUInt32(); - if (decryptArgs.Encrypt) + if (encrypt) Encrypt(ref p1, ref p0); else Decrypt(ref p1, ref p0); @@ -218,7 +227,7 @@ namespace NDecrypt.Nitro } // Replace the header explicitly if we're encrypting - if (decryptArgs.Encrypt) + if (encrypt) { reader.Seek(0x4000, SeekOrigin.Begin); writer.Seek(0x4000, SeekOrigin.Begin); diff --git a/NDecrypt/Program.cs b/NDecrypt/Program.cs index 0645cf6..77c7147 100644 --- a/NDecrypt/Program.cs +++ b/NDecrypt/Program.cs @@ -30,14 +30,15 @@ namespace NDecrypt return; } + bool encrypt; var decryptArgs = new DecryptArgs(); if (args[0] == "decrypt" || args[0] == "d") { - decryptArgs.Encrypt = false; + encrypt = false; } else if (args[0] == "encrypt" || args[0] == "e") { - decryptArgs.Encrypt = true; + encrypt = true; } else { @@ -113,13 +114,13 @@ namespace NDecrypt { if (File.Exists(args[i])) { - ProcessPath(args[i], decryptArgs, outputHashes); + ProcessPath(args[i], encrypt, decryptArgs, outputHashes); } else if (Directory.Exists(args[i])) { foreach (string file in Directory.EnumerateFiles(args[i], "*", SearchOption.AllDirectories)) { - ProcessPath(file, decryptArgs, outputHashes); + ProcessPath(file, encrypt, decryptArgs, outputHashes); } } else @@ -133,15 +134,29 @@ namespace NDecrypt /// Display a basic help text /// /// Path to the file to process + /// Indicates if the file should be encrypted or decrypted /// DecryptArgs to use during processing /// True to write out a hashfile, false otherwise - private static void ProcessPath(string path, DecryptArgs decryptArgs, bool outputHashes) + private static void ProcessPath(string path, bool encrypt, DecryptArgs decryptArgs, bool outputHashes) { Console.WriteLine(path); + ITool? tool = DeriveTool(path, decryptArgs); - if (tool?.ProcessFile() != true) - Console.WriteLine("Processing failed!"); - else if (outputHashes) + if (tool == null) + return; + + if (encrypt && !tool.EncryptFile()) + { + Console.WriteLine("Encryption failed!"); + return; + } + else if (!encrypt && !tool.DecryptFile()) + { + Console.WriteLine("Decryption failed!"); + return; + } + + if (outputHashes) WriteHashes(path); }