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();
- }
}
}