From 76511a2f352a24d125226fadc699f38649c31402 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Sun, 22 Mar 2026 01:06:36 -0400 Subject: [PATCH] Partition keys don't need to be cached --- NDecrypt.Core/ThreeDSProcessor.cs | 109 +++++++++++++++++------------- 1 file changed, 62 insertions(+), 47 deletions(-) diff --git a/NDecrypt.Core/ThreeDSProcessor.cs b/NDecrypt.Core/ThreeDSProcessor.cs index 419f8cf..197f0c1 100644 --- a/NDecrypt.Core/ThreeDSProcessor.cs +++ b/NDecrypt.Core/ThreeDSProcessor.cs @@ -206,11 +206,6 @@ namespace NDecrypt.Core /// private readonly bool _development; - /// - /// Set of all partition keys - /// - private readonly PartitionKeys[] _keysMap = new PartitionKeys[8]; - #region Internal Test Values /// @@ -507,12 +502,17 @@ namespace NDecrypt.Core private void DecryptPartition(N3DS cart, int index, Stream reader, Stream writer) { // Determine the keys needed for this partition - SetDecryptionKeys(cart, index); + PartitionKeys? keys = GetDecryptionKeys(cart, index); + if (keys == null) + { + Console.WriteLine($"Partition {index} could not generate keys. Skipping..."); + return; + } // Decrypt the parts of the partition - DecryptExtendedHeader(cart, index, reader, writer); - DecryptExeFS(cart, index, reader, writer); - DecryptRomFS(cart, index, reader, writer); + DecryptExtendedHeader(cart, index, keys, reader, writer); + DecryptExeFS(cart, index, keys, reader, writer); + DecryptRomFS(cart, index, keys, reader, writer); // Update the flags UpdateDecryptCryptoAndMasks(cart, index, writer); @@ -523,12 +523,12 @@ namespace NDecrypt.Core /// /// Cart representing the 3DS file /// Index of the partition - private void SetDecryptionKeys(N3DS cart, int index) + private PartitionKeys? GetDecryptionKeys(N3DS cart, int index) { // Get the partition var partition = cart.Partitions?[index]; if (partition?.Flags is null) - return; + return null; // Get partition-specific values byte[]? signature = partition.RSA2048Signature; @@ -538,7 +538,7 @@ namespace NDecrypt.Core // Get the partition keys byte[] keyX = GetKeyXForCryptoMethod(method); byte[] keyX0x2C = _development ? DevKeyX0x2C : KeyX0x2C; - _keysMap[index] = new PartitionKeys(signature, masks, AESHardwareConstant, keyX, keyX0x2C); + return new PartitionKeys(signature, masks, AESHardwareConstant, keyX, keyX0x2C); } /// @@ -546,9 +546,10 @@ namespace NDecrypt.Core /// /// Cart representing the 3DS file /// Index of the partition + /// Keys for the partition /// Stream representing the input /// Stream representing the output - private bool DecryptExtendedHeader(N3DS cart, int index, Stream reader, Stream writer) + private bool DecryptExtendedHeader(N3DS cart, int index, PartitionKeys keys, Stream reader, Stream writer) { // Get required offsets uint partitionOffset = cart.GetPartitionOffset(index); @@ -572,7 +573,7 @@ namespace NDecrypt.Core Console.WriteLine($"Partition {index}: Decrypting - ExHeader"); // Create the Plain AES cipher for this partition - var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, cart.PlainIV(index)); + var cipher = AESCTR.CreateDecryptionCipher(keys.NormalKey2C, cart.PlainIV(index)); // Process the extended header AESCTR.PerformOperation(CXTExtendedDataHeaderLength, cipher, reader, writer, null); @@ -590,9 +591,10 @@ namespace NDecrypt.Core /// /// Cart representing the 3DS file /// Index of the partition + /// Keys for the partition /// Stream representing the input /// Stream representing the output - private bool DecryptExeFS(N3DS cart, int index, Stream reader, Stream writer) + private bool DecryptExeFS(N3DS cart, int index, PartitionKeys keys, Stream reader, Stream writer) { // Validate the ExeFS uint exeFsHeaderOffset = cart.GetExeFSOffset(index); @@ -610,11 +612,11 @@ namespace NDecrypt.Core } // Decrypt the filename table - DecryptExeFSFilenameTable(cart, index, reader, writer); + DecryptExeFSFilenameTable(cart, index, keys, reader, writer); // For all but the original crypto method, process each of the files in the table if (cart.GetCryptoMethod(index) != CryptoMethod.Original) - DecryptExeFSFileEntries(cart, index, reader, writer); + DecryptExeFSFileEntries(cart, index, keys, reader, writer); // Get the ExeFS files offset uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize; @@ -626,7 +628,7 @@ namespace NDecrypt.Core // Create the ExeFS AES cipher for this partition uint ctroffsetE = cart.MediaUnitSize / 0x10; byte[] exefsIVWithOffset = cart.ExeFSIV(index).Add(ctroffsetE); - var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffset); + var cipher = AESCTR.CreateDecryptionCipher(keys.NormalKey2C, exefsIVWithOffset); // Setup and perform the decryption exeFsSize -= cart.MediaUnitSize; @@ -644,9 +646,10 @@ namespace NDecrypt.Core /// /// Cart representing the 3DS file /// Index of the partition + /// Keys for the partition /// Stream representing the input /// Stream representing the output - private void DecryptExeFSFilenameTable(N3DS cart, int index, Stream reader, Stream writer) + private void DecryptExeFSFilenameTable(N3DS cart, int index, PartitionKeys keys, Stream reader, Stream writer) { // Get ExeFS offset uint exeFsOffset = cart.GetExeFSOffset(index); @@ -663,7 +666,7 @@ namespace NDecrypt.Core Console.WriteLine($"Partition {index} ExeFS: Decrypting - ExeFS Filename Table"); // Create the ExeFS AES cipher for this partition - var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, cart.ExeFSIV(index)); + var cipher = AESCTR.CreateDecryptionCipher(keys.NormalKey2C, cart.ExeFSIV(index)); // Process the filename table byte[] readBytes = reader.ReadBytes((int)cart.MediaUnitSize); @@ -682,9 +685,10 @@ namespace NDecrypt.Core /// /// Cart representing the 3DS file /// Index of the partition + /// Keys for the partition /// Stream representing the input /// Stream representing the output - private void DecryptExeFSFileEntries(N3DS cart, int index, Stream reader, Stream writer) + private void DecryptExeFSFileEntries(N3DS cart, int index, PartitionKeys keys, Stream reader, Stream writer) { if (cart.ExeFSHeaders is null || index < 0 || index > cart.ExeFSHeaders.Length) { @@ -723,8 +727,8 @@ namespace NDecrypt.Core // Create the ExeFS AES ciphers for this partition uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize) / 0x10; byte[] exefsIVWithOffsetForHeader = cart.ExeFSIV(index).Add(ctroffset); - var firstCipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey, exefsIVWithOffsetForHeader); - var secondCipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffsetForHeader); + var firstCipher = AESCTR.CreateDecryptionCipher(keys.NormalKey, exefsIVWithOffsetForHeader); + var secondCipher = AESCTR.CreateEncryptionCipher(keys.NormalKey2C, exefsIVWithOffsetForHeader); // Seek to the file entry reader.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin); @@ -744,10 +748,11 @@ namespace NDecrypt.Core /// Decrypt the RomFS, if it exists /// /// Cart representing the 3DS file + /// Keys for the partition /// Index of the partition /// Stream representing the input /// Stream representing the output - private bool DecryptRomFS(N3DS cart, int index, Stream reader, Stream writer) + private bool DecryptRomFS(N3DS cart, int index, PartitionKeys keys, Stream reader, Stream writer) { // Validate the RomFS uint romFsOffset = cart.GetRomFSOffset(index); @@ -769,7 +774,7 @@ namespace NDecrypt.Core writer.Seek(romFsOffset, SeekOrigin.Begin); // Create the RomFS AES cipher for this partition - var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey, cart.RomFSIV(index)); + var cipher = AESCTR.CreateDecryptionCipher(keys.NormalKey, cart.RomFSIV(index)); // Setup and perform the decryption AESCTR.PerformOperation(romFsSize, @@ -922,12 +927,17 @@ namespace NDecrypt.Core private void EncryptPartition(N3DS cart, int index, Stream reader, Stream writer) { // Determine the keys needed for this partition - SetEncryptionKeys(cart, index); + PartitionKeys? keys = GetEncryptionKeys(cart, index); + if (keys == null) + { + Console.WriteLine($"Partition {index} could not generate keys. Skipping..."); + return; + } // Encrypt the parts of the partition - EncryptExtendedHeader(cart, index, reader, writer); - EncryptExeFS(cart, index, reader, writer); - EncryptRomFS(cart, index, reader, writer); + EncryptExtendedHeader(cart, index, keys, reader, writer); + EncryptExeFS(cart, index, keys, reader, writer); + EncryptRomFS(cart, index, keys, reader, writer); // Update the flags UpdateEncryptCryptoAndMasks(cart, index, writer); @@ -938,17 +948,17 @@ namespace NDecrypt.Core /// /// Cart representing the 3DS file /// Index of the partition - private void SetEncryptionKeys(N3DS cart, int index) + private PartitionKeys? GetEncryptionKeys(N3DS cart, int index) { // Get the partition var partition = cart.Partitions?[index]; if (partition is null) - return; + return null; // Get the backup header var backupHeader = cart.BackupHeader; if (backupHeader?.Flags is null) - return; + return null; // Get partition-specific values byte[]? signature = partition.RSA2048Signature; @@ -958,7 +968,7 @@ namespace NDecrypt.Core // Get the partition keys byte[] keyX = GetKeyXForCryptoMethod(method); byte[] keyX0x2C = _development ? DevKeyX0x2C : KeyX0x2C; - _keysMap[index] = new PartitionKeys(signature, masks, AESHardwareConstant, keyX, keyX0x2C); + return new PartitionKeys(signature, masks, AESHardwareConstant, keyX, keyX0x2C); } /// @@ -966,9 +976,10 @@ namespace NDecrypt.Core /// /// Cart representing the 3DS file /// Index of the partition + /// Keys for the partition /// Stream representing the input /// Stream representing the output - private bool EncryptExtendedHeader(N3DS cart, int index, Stream reader, Stream writer) + private bool EncryptExtendedHeader(N3DS cart, int index, PartitionKeys keys, Stream reader, Stream writer) { // Get required offsets uint partitionOffset = cart.GetPartitionOffset(index); @@ -992,7 +1003,7 @@ namespace NDecrypt.Core Console.WriteLine($"Partition {index}: Encrypting - ExHeader"); // Create the Plain AES cipher for this partition - var cipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey2C, cart.PlainIV(index)); + var cipher = AESCTR.CreateEncryptionCipher(keys.NormalKey2C, cart.PlainIV(index)); // Process the extended header AESCTR.PerformOperation(CXTExtendedDataHeaderLength, cipher, reader, writer, null); @@ -1010,9 +1021,10 @@ namespace NDecrypt.Core /// /// Cart representing the 3DS file /// Index of the partition + /// Keys for the partition /// Stream representing the input /// Stream representing the output - private bool EncryptExeFS(N3DS cart, int index, Stream reader, Stream writer) + private bool EncryptExeFS(N3DS cart, int index, PartitionKeys keys, Stream reader, Stream writer) { if (cart.ExeFSHeaders is null || index < 0 || index > cart.ExeFSHeaders.Length) { @@ -1031,10 +1043,10 @@ namespace NDecrypt.Core // For all but the original crypto method, process each of the files in the table var backupHeader = cart.BackupHeader; if (backupHeader!.Flags!.CryptoMethod != CryptoMethod.Original) - EncryptExeFSFileEntries(cart, index, reader, writer); + EncryptExeFSFileEntries(cart, index, keys, reader, writer); // Encrypt the filename table - EncryptExeFSFilenameTable(cart, index, reader, writer); + EncryptExeFSFilenameTable(cart, index, keys, reader, writer); // Get the ExeFS files offset uint exeFsHeaderOffset = cart.GetExeFSOffset(index); @@ -1047,7 +1059,7 @@ namespace NDecrypt.Core // Create the ExeFS AES cipher for this partition uint ctroffsetE = cart.MediaUnitSize / 0x10; byte[] exefsIVWithOffset = cart.ExeFSIV(index).Add(ctroffsetE); - var cipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffset); + var cipher = AESCTR.CreateEncryptionCipher(keys.NormalKey2C, exefsIVWithOffset); // Setup and perform the encryption uint exeFsSize = cart.GetExeFSSize(index) - cart.MediaUnitSize; @@ -1065,9 +1077,10 @@ namespace NDecrypt.Core /// /// Cart representing the 3DS file /// Index of the partition + /// Keys for the partition /// Stream representing the input /// Stream representing the output - private void EncryptExeFSFilenameTable(N3DS cart, int index, Stream reader, Stream writer) + private void EncryptExeFSFilenameTable(N3DS cart, int index, PartitionKeys keys, Stream reader, Stream writer) { // Get ExeFS offset uint exeFsOffset = cart.GetExeFSOffset(index); @@ -1084,7 +1097,7 @@ namespace NDecrypt.Core Console.WriteLine($"Partition {index} ExeFS: Encrypting - ExeFS Filename Table"); // Create the ExeFS AES cipher for this partition - var cipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey2C, cart.ExeFSIV(index)); + var cipher = AESCTR.CreateEncryptionCipher(keys.NormalKey2C, cart.ExeFSIV(index)); // Process the filename table byte[] readBytes = reader.ReadBytes((int)cart.MediaUnitSize); @@ -1103,9 +1116,10 @@ namespace NDecrypt.Core /// /// Cart representing the 3DS file /// Index of the partition + /// Keys for the partition /// Stream representing the input /// Stream representing the output - private void EncryptExeFSFileEntries(N3DS cart, int index, Stream reader, Stream writer) + private void EncryptExeFSFileEntries(N3DS cart, int index, PartitionKeys keys, Stream reader, Stream writer) { // Get ExeFS offset uint exeFsHeaderOffset = cart.GetExeFSOffset(index); @@ -1141,8 +1155,8 @@ namespace NDecrypt.Core // Create the ExeFS AES ciphers for this partition uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize) / 0x10; byte[] exefsIVWithOffsetForHeader = cart.ExeFSIV(index).Add(ctroffset); - var firstCipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey, exefsIVWithOffsetForHeader); - var secondCipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffsetForHeader); + var firstCipher = AESCTR.CreateEncryptionCipher(keys.NormalKey, exefsIVWithOffsetForHeader); + var secondCipher = AESCTR.CreateDecryptionCipher(keys.NormalKey2C, exefsIVWithOffsetForHeader); // Seek to the file entry reader.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin); @@ -1163,9 +1177,10 @@ namespace NDecrypt.Core /// /// Cart representing the 3DS file /// Index of the partition + /// Keys for the partition /// Stream representing the input /// Stream representing the output - private bool EncryptRomFS(N3DS cart, int index, Stream reader, Stream writer) + private bool EncryptRomFS(N3DS cart, int index, PartitionKeys keys, Stream reader, Stream writer) { // Validate the RomFS uint romFsOffset = cart.GetRomFSOffset(index); @@ -1190,13 +1205,13 @@ namespace NDecrypt.Core if (index > 0) { var backupHeader = cart.BackupHeader; - _keysMap[index].SetRomFSValues(backupHeader.Flags.BitMasks, + keys.SetRomFSValues(backupHeader.Flags.BitMasks, hardwareConstant: AESHardwareConstant, keyX0x2C: _development ? DevKeyX0x2C : KeyX0x2C); } // Create the RomFS AES cipher for this partition - var cipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey, cart.RomFSIV(index)); + var cipher = AESCTR.CreateEncryptionCipher(keys.NormalKey, cart.RomFSIV(index)); // Setup and perform the decryption AESCTR.PerformOperation(romFsSize,