/// <summary> /// Process an input file given the input values /// </summary> public bool ProcessFile() { // Ensure the constants are all set if (decryptArgs.IsReady != true) { Console.WriteLine("Could not read keys. Please make sure the file exists and try again."); return(false); } try { // Open the read and write on the same file for inplace processing using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))) { CIAHeader header = CIAHeader.Read(reader); if (header == null) { Console.WriteLine("Error: Not a 3DS CIA!"); return(false); } // Process all NCCH partitions ProcessAllPartitions(header, reader, writer); } return(false); } catch { Console.WriteLine($"An error has occurred. {filename} may be corrupted if it was partially processed."); Console.WriteLine("Please check that the file was a valid 3DS CIA file and try again."); return(false); } }
/// <summary> /// Update the CryptoMethod and BitMasks for the encrypted partition /// </summary> /// <param name="ciaHeader">CIA header representing the 3DS CIA file</param> /// <param name="ncchHeader">NCCH header representing the partition</param> /// <param name="writer">BinaryWriter representing the output stream</param> private void UpdateEncryptCryptoAndMasks(CIAHeader ciaHeader, NCCHHeader ncchHeader, BinaryWriter writer) { // TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value? uint mediaUnitSize = 0x200; // ncsdHeader.MediaUnitSize; // Write the new CryptoMethod writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x18B, SeekOrigin.Begin); // For partitions 1 and up, set crypto-method to 0x00 if (ncchHeader.PartitionNumber > 0) { writer.Write((byte)CryptoMethod.Original); } // TODO: Determine how to figure out the original crypto method, if possible // If partition 0, restore crypto-method from backup flags //else // writer.Write((byte)ciaHeader.BackupHeader.Flags.CryptoMethod); writer.Flush(); // Write the new BitMasks flag writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x18F, SeekOrigin.Begin); BitMasks flag = ncchHeader.Flags.BitMasks; flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF; // TODO: Determine how to figure out the original crypto method, if possible //flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & ciaHeader.BackupHeader.Flags.BitMasks; writer.Write((byte)flag); writer.Flush(); }
/// <summary> /// Process all partitions in the content file data of a CIA header /// </summary> /// <param name="ciaHeader">CIA header representing the 3DS CIA file</param> /// <param name="reader">BinaryReader representing the input stream</param> /// <param name="writer">BinaryWriter representing the output stream</param> private void ProcessAllPartitions(CIAHeader ciaHeader, BinaryReader reader, BinaryWriter writer) { // Iterate over all NCCH partitions for (int p = 0; p < ciaHeader.Partitions.Length; p++) { NCCHHeader ncchHeader = ciaHeader.Partitions[0]; ProcessPartition(ciaHeader, ncchHeader, reader, writer); } }
/// <summary> /// Process a single partition /// </summary> /// <param name="ciaHeader">CIA header representing the 3DS CIA file</param> /// <param name="ncchHeader">NCCH header representing the partition</param> /// <param name="reader">BinaryReader representing the input stream</param> /// <param name="writer">BinaryWriter representing the output stream</param> private void ProcessPartition(CIAHeader ciaHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer) { // If we're forcing the operation, tell the user if (decryptArgs.Force) { Console.WriteLine($"Partition {ncchHeader.PartitionNumber} is not verified due to force flag being set."); } // If we're not forcing the operation, check if the 'NoCrypto' bit is set else if (ncchHeader.Flags.PossblyDecrypted ^ decryptArgs.Encrypt) { Console.WriteLine($"Partition {ncchHeader.PartitionNumber}: Already " + (decryptArgs.Encrypt ? "Encrypted" : "Decrypted") + "?..."); return; } // Determine the Keys to be used SetEncryptionKeys(ciaHeader, ncchHeader); // Process the extended header ProcessExtendedHeader(ncchHeader, reader, writer); // If we're encrypting, encrypt the filesystems and update the flags if (decryptArgs.Encrypt) { EncryptExeFS(ncchHeader, reader, writer); EncryptRomFS(ncchHeader, reader, writer); UpdateEncryptCryptoAndMasks(ciaHeader, ncchHeader, writer); } // If we're decrypting, decrypt the filesystems and update the flags else { DecryptExeFS(ncchHeader, reader, writer); DecryptRomFS(ncchHeader, reader, writer); UpdateDecryptCryptoAndMasks(ncchHeader, writer); } }
/// <summary> /// Determine the set of keys to be used for encryption or decryption /// </summary> /// <param name="ciaHeader">NCSD header representing the 3DS CIA file</param> /// <param name="ncchHeader">NCCH header representing the partition</param> private void SetEncryptionKeys(CIAHeader ciaHeader, NCCHHeader ncchHeader) { ncchHeader.KeyX = 0; ncchHeader.KeyX2C = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C; // Backup headers can't have a KeyY value set if (ncchHeader.RSA2048Signature != null) { ncchHeader.KeyY = new BigInteger(ncchHeader.RSA2048Signature.Take(16).Reverse().ToArray()); } else { ncchHeader.KeyY = new BigInteger(0); } ncchHeader.NormalKey = 0; ncchHeader.NormalKey2C = RotateLeft((RotateLeft(ncchHeader.KeyX2C, 2, 128) ^ ncchHeader.KeyY) + decryptArgs.AESHardwareConstant, 87, 128); // TODO: Figure out what sane defaults for these values are // Set the header to use based on mode BitMasks masks = BitMasks.NoCrypto; CryptoMethod method = CryptoMethod.Original; if (decryptArgs.Encrypt) { // TODO: Can we actually re-encrypt a CIA? //masks = ciaHeader.BackupHeader.Flags.BitMasks; //method = ciaHeader.BackupHeader.Flags.CryptoMethod; } else { masks = ncchHeader.Flags.BitMasks; method = ncchHeader.Flags.CryptoMethod; } if (masks.HasFlag(BitMasks.FixedCryptoKey)) { ncchHeader.NormalKey = 0x00; ncchHeader.NormalKey2C = 0x00; Console.WriteLine("Encryption Method: Zero Key"); } else { if (method == CryptoMethod.Original) { ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C; Console.WriteLine("Encryption Method: Key 0x2C"); } else if (method == CryptoMethod.Seven) { ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x25 : decryptArgs.KeyX0x25; Console.WriteLine("Encryption Method: Key 0x25"); } else if (method == CryptoMethod.NineThree) { ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x18 : decryptArgs.KeyX0x18; Console.WriteLine("Encryption Method: Key 0x18"); } else if (method == CryptoMethod.NineSix) { ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x1B : decryptArgs.KeyX0x1B; Console.WriteLine("Encryption Method: Key 0x1B"); } ncchHeader.NormalKey = RotateLeft((RotateLeft(ncchHeader.KeyX, 2, 128) ^ ncchHeader.KeyY) + decryptArgs.AESHardwareConstant, 87, 128); } }