diff --git a/NDecrypt.N3DS/Extensions.cs b/NDecrypt.N3DS/Extensions.cs index b268fd1..9d0b9ed 100644 --- a/NDecrypt.N3DS/Extensions.cs +++ b/NDecrypt.N3DS/Extensions.cs @@ -23,6 +23,20 @@ public static bool IsCodeBinary(this ExeFSFileHeader? header) #region NCCHHeader + /// + /// Get the initial value for the plain counter + /// + public static byte[] PlainIV(this Cart cart, int partitionIndex) + { + if (cart.Partitions == null) + return []; + if (partitionIndex < 0 || partitionIndex >= cart.Partitions.Length) + return []; + + var header = cart.Partitions[partitionIndex]; + return PlainIV(header); + } + /// /// Get the initial value for the plain counter /// @@ -38,41 +52,54 @@ public static byte[] PlainIV(this NCCHHeader? header) /// /// Get the initial value for the ExeFS counter /// - public static byte[] ExeFSIV(this NCCHHeader header) + public static byte[] ExeFSIV(this Cart cart, int partitionIndex) { - if (header == null) + if (cart.Partitions == null) + return []; + if (partitionIndex < 0 || partitionIndex >= cart.Partitions.Length) return []; - byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId); - return [.. partitionIdBytes, .. Constants.ExefsCounter]; + var header = cart.Partitions[partitionIndex]; + return ExeFSIV(header); } /// - /// Get the initial value for the RomFS counter + /// Get the initial value for the ExeFS counter /// - public static byte[] RomFSIV(this NCCHHeader header) + public static byte[] ExeFSIV(this NCCHHeader? header) { if (header == null) return []; byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId); - return [.. partitionIdBytes, .. Constants.RomfsCounter]; + return [.. partitionIdBytes, .. Constants.ExefsCounter]; } /// - /// NCCH boot rom key + /// Get the initial value for the RomFS counter /// - public static BigInteger KeyX2C { get; set; } + public static byte[] RomFSIV(this Cart cart, int partitionIndex) + { + if (cart.Partitions == null) + return []; + if (partitionIndex < 0 || partitionIndex >= cart.Partitions.Length) + return []; - /// - /// Normal AES key - /// - public static BigInteger NormalKey { get; set; } + var header = cart.Partitions[partitionIndex]; + return RomFSIV(header); + } /// - /// NCCH AES key + /// Get the initial value for the RomFS counter /// - public static BigInteger NormalKey2C { get; set; } + public static byte[] RomFSIV(this NCCHHeader? header) + { + if (header == null) + return []; + + byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId); + return [.. partitionIdBytes, .. Constants.RomfsCounter]; + } #endregion @@ -193,6 +220,14 @@ public static MediaTypeIndex MediaTypeIndex(this NCSDHeader? header) return (MediaTypeIndex)header.PartitionFlags[(int)NCSDFlags.MediaTypeIndex]; } + /// + /// Media Unit Size i.e. u32 MediaUnitSize = 0x200*2^flags[6]; + /// + public static uint MediaUnitSize(this Cart cart) + { + return cart.Header.MediaUnitSize(); + } + /// /// Media Unit Size i.e. u32 MediaUnitSize = 0x200*2^flags[6]; /// @@ -217,22 +252,6 @@ public static MediaCardDeviceType MediaCardDevice2X(this NCSDHeader? header) #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 /// diff --git a/NDecrypt.N3DS/Serializer.cs b/NDecrypt.N3DS/Serializer.cs index 55728a3..2bb9ac6 100644 --- a/NDecrypt.N3DS/Serializer.cs +++ b/NDecrypt.N3DS/Serializer.cs @@ -49,6 +49,21 @@ internal static class Serializer } } + cart.Partitions = new NCCHHeader[8]; + for (int i = 0; i < 8; i++) + { + // Check the entry is valid + var tableEntry = cart.Header.PartitionsTable![i]; + if (tableEntry == null || tableEntry.Offset == 0 || tableEntry.Length == 0) + continue; + + // Seek to the beginning of the NCCH partition + long offset = tableEntry.Offset * cart.Header.ImageSizeInMediaUnits; + reader.BaseStream.Seek(offset, SeekOrigin.Begin); + + cart.Partitions[i] = ReadNCCHHeader(reader, readSignature: true); + } + return cart; } catch diff --git a/NDecrypt.N3DS/ThreeDSTool.cs b/NDecrypt.N3DS/ThreeDSTool.cs index b35bcd1..5f3f195 100644 --- a/NDecrypt.N3DS/ThreeDSTool.cs +++ b/NDecrypt.N3DS/ThreeDSTool.cs @@ -70,7 +70,7 @@ public bool ProcessFile() // Open the read and write on the same file for inplace processing 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)); - + // Deserialize the cart information var cart = Serializer.ReadCart(reader, decryptArgs.Development); if (cart?.Header == null || cart?.CardInfoHeader?.InitialData?.BackupHeader == null) @@ -99,55 +99,26 @@ public bool ProcessFile() /// BinaryReader representing the input stream /// BinaryWriter representing the output stream private void ProcessAllPartitions(Cart cart, BinaryReader reader, BinaryWriter writer) - { - // Iterate over all 8 NCCH partitions - for (int p = 0; p < 8; p++) - { - (int partitionIndex, var ncchHeader, var tableEntry) = GetPartitionHeader(cart.Header, reader, p); - if (partitionIndex < 0 || ncchHeader == null || tableEntry == null) - continue; - - ProcessPartition(cart, partitionIndex, ncchHeader, tableEntry, reader, writer); - } - } - - /// - /// Get a specific partition header from the partition table - /// - /// NCSD header representing the 3DS file - /// BinaryReader representing the input stream - /// Partition number to attempt to retrieve - /// NCCH header for the partition requested, null on error - private static (int, NCCHHeader?, PartitionTableEntry?) GetPartitionHeader(NCSDHeader? ncsdHeader, BinaryReader reader, int partitionNumber) { // Check the partitions table - if (ncsdHeader?.PartitionsTable == null) + if (cart.Header?.PartitionsTable == null) { - Console.WriteLine("Invalid partitions table... Skipping..."); - return (-1, null, null); + Console.WriteLine("Invalid partitions table!"); + return; } - // Check the partition is valid - if (!ncsdHeader.PartitionsTable[partitionNumber].IsValid()) + // Iterate over all 8 NCCH partitions + for (int partitionIndex = 0; partitionIndex < 8; partitionIndex++) { - Console.WriteLine($"Partition {partitionNumber} Not found... Skipping..."); - return (-1, null, null); - } - - // Seek to the beginning of the NCCH partition - long offset = ncsdHeader.PartitionsTable[partitionNumber]!.Offset * ncsdHeader.ImageSizeInMediaUnits; - reader.BaseStream.Seek(offset, SeekOrigin.Begin); + // Check the partition exists + if (cart.Partitions![partitionIndex] == null) + { + Console.WriteLine($"Partition {partitionIndex} Not found... Skipping..."); + continue; + } - // Read the NCCH header - var header = Serializer.ReadNCCHHeader(reader, readSignature: true); - if (header == null) - { - Console.WriteLine($"Partition {partitionNumber} Unable to read NCCH header"); - return (-1, null, null); + ProcessPartition(cart, partitionIndex, reader, writer); } - - var entry = ncsdHeader.PartitionsTable[partitionNumber]; - return (partitionNumber, header, entry); } /// @@ -155,14 +126,10 @@ private static (int, NCCHHeader?, PartitionTableEntry?) GetPartitionHeader(NCSDH /// /// Cart 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 ProcessPartition(Cart cart, int partitionIndex, - NCCHHeader ncchHeader, - PartitionTableEntry tableEntry, BinaryReader reader, BinaryWriter writer) { @@ -172,32 +139,32 @@ private void ProcessPartition(Cart cart, 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 (cart.Partitions![partitionIndex]!.Flags!.PossblyDecrypted() ^ decryptArgs.Encrypt) { Console.WriteLine($"Partition {partitionIndex}: Already " + (decryptArgs.Encrypt ? "Encrypted" : "Decrypted") + "?..."); return; } // Determine the Keys to be used - SetEncryptionKeys(cart, partitionIndex, ncchHeader); + SetEncryptionKeys(cart, partitionIndex); // Process the extended header - ProcessExtendedHeader(cart.Header!, partitionIndex, ncchHeader, tableEntry, reader, writer); + ProcessExtendedHeader(cart, partitionIndex, reader, writer); // If we're encrypting, encrypt the filesystems and update the flags if (decryptArgs.Encrypt) { - EncryptExeFS(cart, partitionIndex, ncchHeader, tableEntry, reader, writer); - EncryptRomFS(cart, partitionIndex, ncchHeader, tableEntry, reader, writer); - UpdateEncryptCryptoAndMasks(cart, partitionIndex, ncchHeader, tableEntry, writer); + EncryptExeFS(cart, partitionIndex, reader, writer); + EncryptRomFS(cart, partitionIndex, reader, writer); + UpdateEncryptCryptoAndMasks(cart, partitionIndex, writer); } // If we're decrypting, decrypt the filesystems and update the flags else { - DecryptExeFS(cart.Header!, partitionIndex, ncchHeader, tableEntry, reader, writer); - DecryptRomFS(cart.Header!, partitionIndex, ncchHeader, tableEntry, reader, writer); - UpdateDecryptCryptoAndMasks(cart.Header!, ncchHeader, tableEntry, writer); + DecryptExeFS(cart, partitionIndex, reader, writer); + DecryptRomFS(cart, partitionIndex, reader, writer); + UpdateDecryptCryptoAndMasks(cart, partitionIndex, writer); } } @@ -206,17 +173,18 @@ private void ProcessPartition(Cart cart, /// /// Cart representing the 3DS file /// Index of the partition - /// NCCH header representing the partition - private void SetEncryptionKeys(Cart cart, - int partitionIndex, - NCCHHeader ncchHeader) + private void SetEncryptionKeys(Cart cart, int partitionIndex) { + // Get the backup header + var backupHeader = cart.CardInfoHeader!.InitialData!.BackupHeader; + KeyX[partitionIndex] = 0; KeyX2C[partitionIndex] = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C; // Backup headers can't have a KeyY value set - if (ncchHeader.RSA2048Signature != null) - KeyY[partitionIndex] = new BigInteger(ncchHeader.RSA2048Signature.Take(16).Reverse().ToArray()); + byte[]? rsaSignature = cart.Partitions![partitionIndex]!.RSA2048Signature; + if (rsaSignature != null) + KeyY[partitionIndex] = new BigInteger(rsaSignature.Take(16).Reverse().ToArray()); else KeyY[partitionIndex] = new BigInteger(0); @@ -228,13 +196,13 @@ private void SetEncryptionKeys(Cart cart, CryptoMethod method; if (decryptArgs.Encrypt) { - masks = cart.CardInfoHeader!.InitialData!.BackupHeader!.Flags!.BitMasks; - method = cart.CardInfoHeader.InitialData.BackupHeader.Flags.CryptoMethod; + masks = backupHeader!.Flags!.BitMasks; + method = backupHeader.Flags.CryptoMethod; } else { - masks = ncchHeader.Flags!.BitMasks; - method = ncchHeader.Flags.CryptoMethod; + masks = cart.Partitions![partitionIndex]!.Flags!.BitMasks; + method = cart.Partitions![partitionIndex]!.Flags!.CryptoMethod; } if (masks.HasFlag(BitMasks.FixedCryptoKey)) @@ -273,27 +241,27 @@ private void SetEncryptionKeys(Cart cart, /// /// Process the extended header, if it exists /// - /// NCSD header representing the 3DS file + /// Cart 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, + private bool ProcessExtendedHeader(Cart cart, int partitionIndex, - NCCHHeader ncchHeader, - PartitionTableEntry tableEntry, BinaryReader reader, BinaryWriter writer) { - if (ncchHeader.ExtendedHeaderSizeInBytes > 0) + // Get required offsets + uint partitionOffsetMU = cart.Header!.PartitionsTable![partitionIndex]!.Offset; + uint partitionOffset = partitionOffsetMU * cart.MediaUnitSize(); + + if (cart.Partitions![partitionIndex]!.ExtendedHeaderSizeInBytes > 0) { - reader.BaseStream.Seek((tableEntry.Offset * ncsdHeader.MediaUnitSize()) + 0x200, SeekOrigin.Begin); - writer.BaseStream.Seek((tableEntry.Offset * ncsdHeader.MediaUnitSize()) + 0x200, SeekOrigin.Begin); + reader.BaseStream.Seek(partitionOffset + 0x200, SeekOrigin.Begin); + writer.BaseStream.Seek(partitionOffset + 0x200, SeekOrigin.Begin); Console.WriteLine($"Partition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + ": ExHeader"); - var cipher = CreateAESCipher(NormalKey2C[partitionIndex], ncchHeader.PlainIV(), decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey2C[partitionIndex], cart.PlainIV(partitionIndex), decryptArgs.Encrypt); byte[] readBytes = reader.ReadBytes(Constants.CXTExtendedDataHeaderLength); byte[] processedBytes = cipher.ProcessBytes(readBytes); writer.Write(processedBytes); @@ -306,24 +274,24 @@ private bool ProcessExtendedHeader(NCSDHeader ncsdHeader, return false; } } - + /// /// Process the extended header, if it exists /// - /// NCSD header representing the 3DS file - /// Index of the partition - /// NCCH header representing the partition + /// Cart representing the 3DS file /// PartitionTableEntry header representing the partition /// BinaryReader representing the input stream /// BinaryWriter representing the output stream - private void ProcessExeFSFileEntries(NCSDHeader ncsdHeader, + private void ProcessExeFSFileEntries(Cart cart, int partitionIndex, - NCCHHeader ncchHeader, - PartitionTableEntry tableEntry, BinaryReader reader, BinaryWriter writer) { - reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); + // Get required offsets + uint partitionOffsetMU = cart.Header!.PartitionsTable![partitionIndex]!.Offset; + uint exeFsOffset = cart.Partitions![partitionIndex]!.ExeFSOffsetInMediaUnits; + + reader.BaseStream.Seek((partitionOffsetMU + exeFsOffset) * cart.MediaUnitSize(), SeekOrigin.Begin); var exefsHeader = Serializer.ReadExeFSHeader(reader); // If the header failed to read, log and return @@ -341,15 +309,15 @@ private void ProcessExeFSFileEntries(NCSDHeader ncsdHeader, uint datalenM = ((fileHeader.FileSize) / (1024 * 1024)); uint datalenB = ((fileHeader.FileSize) % (1024 * 1024)); - uint ctroffset = ((fileHeader.FileOffset + ncsdHeader.MediaUnitSize()) / 0x10); + uint ctroffset = ((fileHeader.FileOffset + cart.MediaUnitSize()) / 0x10); - byte[] exefsIVWithOffsetForHeader = AddToByteArray(ncchHeader.ExeFSIV(), (int)ctroffset); + byte[] exefsIVWithOffsetForHeader = AddToByteArray(cart.ExeFSIV(partitionIndex), (int)ctroffset); var firstCipher = CreateAESCipher(NormalKey[partitionIndex], exefsIVWithOffsetForHeader, decryptArgs.Encrypt); var secondCipher = CreateAESCipher(NormalKey2C[partitionIndex], exefsIVWithOffsetForHeader, !decryptArgs.Encrypt); - 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); + reader.BaseStream.Seek(((partitionOffsetMU + exeFsOffset + 1) * cart.MediaUnitSize()) + fileHeader.FileOffset, SeekOrigin.Begin); + writer.BaseStream.Seek(((partitionOffsetMU + exeFsOffset + 1) * cart.MediaUnitSize()) + fileHeader.FileOffset, SeekOrigin.Begin); if (datalenM > 0) { @@ -380,26 +348,26 @@ private void ProcessExeFSFileEntries(NCSDHeader ncsdHeader, /// /// Process the ExeFS Filename Table /// - /// NCSD header representing the 3DS file + /// Cart 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, + private void ProcessExeFSFilenameTable(Cart cart, int partitionIndex, - NCCHHeader ncchHeader, - PartitionTableEntry tableEntry, BinaryReader reader, BinaryWriter writer) { - reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); - writer.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); + // Get required offsets + uint partitionOffsetMU = cart.Header!.PartitionsTable![partitionIndex]!.Offset; + uint exeFsOffset = cart.Partitions![partitionIndex]!.ExeFSOffsetInMediaUnits; + + reader.BaseStream.Seek((partitionOffsetMU + exeFsOffset) * cart.MediaUnitSize(), SeekOrigin.Begin); + writer.BaseStream.Seek((partitionOffsetMU + exeFsOffset) * cart.MediaUnitSize(), SeekOrigin.Begin); Console.WriteLine($"Partition {partitionIndex} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table"); - var cipher = CreateAESCipher(NormalKey2C[partitionIndex], ncchHeader.ExeFSIV(), decryptArgs.Encrypt); - byte[] readBytes = reader.ReadBytes((int)ncsdHeader.MediaUnitSize()); + var cipher = CreateAESCipher(NormalKey2C[partitionIndex], cart.ExeFSIV(partitionIndex), decryptArgs.Encrypt); + byte[] readBytes = reader.ReadBytes((int)cart.MediaUnitSize()); byte[] processedBytes = cipher.ProcessBytes(readBytes); writer.Write(processedBytes); @@ -413,29 +381,29 @@ private void ProcessExeFSFilenameTable(NCSDHeader ncsdHeader, /// /// Process the ExeFS, if it exists /// - /// NCSD header representing the 3DS file + /// Cart 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, + private void ProcessExeFS(Cart cart, 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); + // Get required offsets + uint partitionOffsetMU = cart.Header!.PartitionsTable![partitionIndex]!.Offset; + uint exeFsOffset = cart.Partitions![partitionIndex]!.ExeFSOffsetInMediaUnits; + + int exefsSizeM = (int)((long)((cart.Partitions![partitionIndex]!.ExeFSSizeInMediaUnits - 1) * cart.MediaUnitSize()) / (1024 * 1024)); + int exefsSizeB = (int)((long)((cart.Partitions![partitionIndex]!.ExeFSSizeInMediaUnits - 1) * cart.MediaUnitSize()) % (1024 * 1024)); + int ctroffsetE = (int)(cart.MediaUnitSize() / 0x10); - byte[] exefsIVWithOffset = AddToByteArray(ncchHeader.ExeFSIV(), ctroffsetE); + byte[] exefsIVWithOffset = AddToByteArray(cart.ExeFSIV(partitionIndex), ctroffsetE); var cipher = CreateAESCipher(NormalKey2C[partitionIndex], exefsIVWithOffset, decryptArgs.Encrypt); - reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); - writer.BaseStream.Seek((tableEntry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); + reader.BaseStream.Seek((partitionOffsetMU + exeFsOffset + 1) * cart.MediaUnitSize(), SeekOrigin.Begin); + writer.BaseStream.Seek((partitionOffsetMU + exeFsOffset + 1) * cart.MediaUnitSize(), SeekOrigin.Begin); if (exefsSizeM > 0) { for (int i = 0; i < exefsSizeM; i++) @@ -465,68 +433,66 @@ private void ProcessExeFS(NCSDHeader ncsdHeader, /// /// Decrypt the ExeFS, if it exists /// - /// NCSD header representing the 3DS file + /// Cart 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, + private void DecryptExeFS(Cart cart, int partitionIndex, - NCCHHeader ncchHeader, - PartitionTableEntry tableEntry, BinaryReader reader, BinaryWriter writer) { // If the ExeFS size is 0, we log and return - if (ncchHeader.ExeFSSizeInMediaUnits == 0) + if (cart.Partitions![partitionIndex]!.ExeFSSizeInMediaUnits == 0) { Console.WriteLine($"Partition {partitionIndex} ExeFS: No Data... Skipping..."); return; } // Decrypt the filename table - ProcessExeFSFilenameTable(ncsdHeader, partitionIndex, ncchHeader, tableEntry, reader, writer); + ProcessExeFSFilenameTable(cart, partitionIndex, 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, partitionIndex, ncchHeader, tableEntry, reader, writer); + if (cart.Partitions![partitionIndex]!.Flags!.CryptoMethod != CryptoMethod.Original) + ProcessExeFSFileEntries(cart, partitionIndex, reader, writer); // Decrypt the rest of the ExeFS - ProcessExeFS(ncsdHeader, partitionIndex, ncchHeader, tableEntry, reader, writer); + ProcessExeFS(cart, partitionIndex, reader, writer); } /// /// Decrypt the RomFS, if it exists /// - /// NCSD header representing the 3DS file + /// Cart 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, + private void DecryptRomFS(Cart cart, int partitionIndex, - NCCHHeader ncchHeader, - PartitionTableEntry tableEntry, BinaryReader reader, BinaryWriter writer) { + // Get required offsets + uint partitionOffsetMU = cart.Header!.PartitionsTable![partitionIndex]!.Offset; + uint romFsOffsetMU = cart.Partitions![partitionIndex]!.RomFSOffsetInMediaUnits; + uint romFsOffset = (partitionOffsetMU + romFsOffsetMU) * cart.MediaUnitSize(); + // If the RomFS offset is 0, we log and return - if (ncchHeader.RomFSOffsetInMediaUnits == 0) + if (romFsOffsetMU == 0) { 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)); + uint romFsSize = cart.Partitions![partitionIndex]!.RomFSSizeInMediaUnits * cart.MediaUnitSize(); + long romfsSizeM = (int)((long)romFsSize / (1024 * 1024)); + int romfsSizeB = (int)((long)romFsSize % (1024 * 1024)); - var cipher = CreateAESCipher(NormalKey[partitionIndex], ncchHeader.RomFSIV(), decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey[partitionIndex], cart.RomFSIV(partitionIndex), decryptArgs.Encrypt); - reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); - writer.BaseStream.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize(), SeekOrigin.Begin); + reader.BaseStream.Seek(romFsOffset, SeekOrigin.Begin); + writer.BaseStream.Seek(romFsOffset, SeekOrigin.Begin); if (romfsSizeM > 0) { for (int i = 0; i < romfsSizeM; i++) @@ -552,23 +518,23 @@ private void DecryptRomFS(NCSDHeader ncsdHeader, /// /// Update the CryptoMethod and BitMasks for the decrypted partition /// - /// NCSD header representing the 3DS file - /// NCCH header representing the partition - /// PartitionTableEntry header representing the partition + /// Cart representing the 3DS file + /// Index of the partition /// BinaryWriter representing the output stream - private void UpdateDecryptCryptoAndMasks(NCSDHeader ncsdHeader, - NCCHHeader ncchHeader, - PartitionTableEntry tableEntry, - BinaryWriter writer) + private void UpdateDecryptCryptoAndMasks(Cart cart, int partitionIndex, BinaryWriter writer) { + // Get required offsets + uint partitionOffsetMU = cart.Header!.PartitionsTable![partitionIndex]!.Offset; + uint partitionOffset = partitionOffsetMU * cart.MediaUnitSize(); + // Write the new CryptoMethod - writer.BaseStream.Seek((tableEntry.Offset * ncsdHeader.MediaUnitSize()) + 0x18B, SeekOrigin.Begin); + writer.BaseStream.Seek(partitionOffset + 0x18B, SeekOrigin.Begin); writer.Write((byte)CryptoMethod.Original); writer.Flush(); // Write the new BitMasks flag - writer.BaseStream.Seek((tableEntry.Offset * ncsdHeader.MediaUnitSize()) + 0x18F, SeekOrigin.Begin); - BitMasks flag = ncchHeader.Flags!.BitMasks; + writer.BaseStream.Seek(partitionOffset + 0x18F, SeekOrigin.Begin); + BitMasks flag = cart.Partitions![partitionIndex]!.Flags!.BitMasks; flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF); flag |= BitMasks.NoCrypto; writer.Write((byte)flag); @@ -584,33 +550,32 @@ private void UpdateDecryptCryptoAndMasks(NCSDHeader ncsdHeader, /// /// Cart 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 EncryptExeFS(Cart cart, int partitionIndex, - NCCHHeader ncchHeader, - PartitionTableEntry tableEntry, BinaryReader reader, BinaryWriter writer) { // If the ExeFS size is 0, we log and return - if (ncchHeader.ExeFSSizeInMediaUnits == 0) + if (cart.Partitions![partitionIndex]!.ExeFSSizeInMediaUnits == 0) { Console.WriteLine($"Partition {partitionIndex} ExeFS: No Data... Skipping..."); return; } + // Get the backup header + var backupHeader = cart.CardInfoHeader!.InitialData!.BackupHeader; + // For all but the original crypto method, process each of the files in the table - if (cart.CardInfoHeader!.InitialData!.BackupHeader!.Flags!.CryptoMethod != CryptoMethod.Original) - ProcessExeFSFileEntries(cart.Header!, partitionIndex, ncchHeader, tableEntry, reader, writer); + if (backupHeader!.Flags!.CryptoMethod != CryptoMethod.Original) + ProcessExeFSFileEntries(cart, partitionIndex, reader, writer); // Encrypt the filename table - ProcessExeFSFilenameTable(cart.Header!, partitionIndex, ncchHeader, tableEntry, reader, writer); + ProcessExeFSFilenameTable(cart, partitionIndex, reader, writer); // Encrypt the rest of the ExeFS - ProcessExeFS(cart.Header!, partitionIndex, ncchHeader, tableEntry, reader, writer); + ProcessExeFS(cart, partitionIndex, reader, writer); } /// @@ -618,33 +583,37 @@ private void EncryptExeFS(Cart cart, /// /// Cart 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(Cart cart, int partitionIndex, - NCCHHeader ncchHeader, - PartitionTableEntry tableEntry, BinaryReader reader, BinaryWriter writer) { + // Get required offsets + uint partitionOffsetMU = cart.Header!.PartitionsTable![partitionIndex]!.Offset; + uint romFsOffsetMU = cart.Partitions![partitionIndex]!.RomFSOffsetInMediaUnits; + uint romFsOffset = (partitionOffsetMU + romFsOffsetMU) * cart.MediaUnitSize(); + + // Get the backup header + var backupHeader = cart.CardInfoHeader!.InitialData!.BackupHeader; + // If the RomFS offset is 0, we log and return - if (ncchHeader.RomFSOffsetInMediaUnits == 0) + if (romFsOffsetMU == 0) { Console.WriteLine($"Partition {partitionIndex} RomFS: No Data... Skipping..."); return; } - long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * cart.Header.MediaUnitSize()) / (1024 * 1024)); - int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * cart.Header.MediaUnitSize()) % (1024 * 1024)); + uint romFsSize = cart.Partitions![partitionIndex]!.RomFSSizeInMediaUnits * cart.MediaUnitSize(); + long romfsSizeM = (int)((long)romFsSize / (1024 * 1024)); + int romfsSizeB = (int)((long)romFsSize % (1024 * 1024)); // Encrypting RomFS for partitions 1 and up always use Key0x2C if (partitionIndex > 0) { - if (cart.CardInfoHeader!.InitialData!.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 { NormalKey[partitionIndex] = 0x00; } @@ -655,10 +624,10 @@ private void EncryptRomFS(Cart cart, } } - var cipher = CreateAESCipher(NormalKey[partitionIndex], ncchHeader.RomFSIV(), decryptArgs.Encrypt); + var cipher = CreateAESCipher(NormalKey[partitionIndex], cart.RomFSIV(partitionIndex), decryptArgs.Encrypt); - reader.BaseStream.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * cart.Header.MediaUnitSize(), SeekOrigin.Begin); - writer.BaseStream.Seek((tableEntry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * cart.Header.MediaUnitSize(), SeekOrigin.Begin); + reader.BaseStream.Seek(romFsOffset, SeekOrigin.Begin); + writer.BaseStream.Seek(romFsOffset, SeekOrigin.Begin); if (romfsSizeM > 0) { for (int i = 0; i < romfsSizeM; i++) @@ -686,34 +655,34 @@ private void EncryptRomFS(Cart cart, /// /// Cart 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 static void UpdateEncryptCryptoAndMasks(Cart cart, - int partitionIndex, - NCCHHeader ncchHeader, - PartitionTableEntry tableEntry, - BinaryWriter writer) + private static void UpdateEncryptCryptoAndMasks(Cart cart, int partitionIndex, BinaryWriter writer) { + // Get required offsets + uint partitionOffsetMU = cart.Header!.PartitionsTable![partitionIndex]!.Offset; + uint partitionOffset = partitionOffsetMU * cart.MediaUnitSize(); + + // Get the backup header + var backupHeader = cart.CardInfoHeader!.InitialData!.BackupHeader; + // Write the new CryptoMethod - writer.BaseStream.Seek((tableEntry.Offset * cart.Header.MediaUnitSize()) + 0x18B, SeekOrigin.Begin); - + writer.BaseStream.Seek(partitionOffset + 0x18B, SeekOrigin.Begin); + // For partitions 1 and up, set crypto-method to 0x00 if (partitionIndex > 0) writer.Write((byte)CryptoMethod.Original); // If partition 0, restore crypto-method from backup flags else - writer.Write((byte)cart.CardInfoHeader!.InitialData!.BackupHeader!.Flags!.CryptoMethod); + writer.Write((byte)backupHeader!.Flags!.CryptoMethod); writer.Flush(); // Write the new BitMasks flag - writer.BaseStream.Seek((tableEntry.Offset * cart.Header.MediaUnitSize()) + 0x18F, SeekOrigin.Begin); - BitMasks flag = ncchHeader.Flags!.BitMasks; + writer.BaseStream.Seek(partitionOffset + 0x18F, SeekOrigin.Begin); + BitMasks flag = cart.Partitions![partitionIndex]!.Flags!.BitMasks; flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF; - flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & cart.CardInfoHeader!.InitialData!.BackupHeader!.Flags!.BitMasks; + flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & backupHeader!.Flags!.BitMasks; writer.Write((byte)flag); writer.Flush(); }