diff --git a/3DSDecrypt/ThreeDSTool.cs b/3DSDecrypt/ThreeDSTool.cs index 4bac69e..6d34465 100644 --- a/3DSDecrypt/ThreeDSTool.cs +++ b/3DSDecrypt/ThreeDSTool.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Numerics; +using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; using ThreeDS.Data; @@ -21,6 +22,31 @@ namespace ThreeDS /// private readonly bool development; + /// + /// 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) { this.filename = filename; @@ -74,151 +100,12 @@ namespace ThreeDS } // Determine the Keys to be used - GetEncryptionKeys(partitionHeader.RSA2048Signature, partitionHeader.Flags.BitMasks, partitionHeader.Flags.CryptoMethod, p, - out BigInteger KeyX, out BigInteger KeyX2C, out BigInteger KeyY, out BigInteger NormalKey, out BigInteger NormalKey2C); + SetEncryptionKeys(partitionHeader.RSA2048Signature, partitionHeader.Flags.BitMasks, partitionHeader.Flags.CryptoMethod, p); - // Decrypted extended header, if it exists - if (partitionHeader.ExtendedHeaderSizeInBytes > 0) - { - // Seek to the partition start and skip first part of the header - f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin); - g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin); - - var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); - exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), partitionHeader.PlainIV)); - - Console.WriteLine($"Partition {p} ExeFS: Decrypting: ExHeader"); - - g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(Constants.CXTExtendedDataHeaderLength))); - g.Flush(); - } - - // Decrypt the ExeFS, if it exists - if (partitionHeader.ExeFSSizeInBytes > 0) - { - f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); - g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); - - var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); - exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), partitionHeader.ExeFSIV)); - - g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes((int)header.SectorSize))); - g.Flush(); - - Console.WriteLine($"Partition {p} ExeFS: Decrypting: ExeFS Filename Table"); - - if (partitionHeader.Flags.CryptoMethod != CryptoMethod.Original) - { - f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); - ExeFSHeader exefsHeader = ExeFSHeader.Read(f); - if (exefsHeader != null) - { - foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders) - { - if (!fileHeader.IsCodeBinary) - continue; - - uint datalenM = ((fileHeader.FileSize) / (1024 * 1024)); - uint datalenB = ((fileHeader.FileSize) % (1024 * 1024)); - uint ctroffset = ((fileHeader.FileOffset + header.SectorSize) / 0x10); - - byte[] exefsIVWithOffsetForHeader = AddToByteArray(partitionHeader.ExeFSIV, (int)ctroffset); - - var exefsctrmode = CipherUtilities.GetCipher("AES/CTR"); - exefsctrmode.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), exefsIVWithOffsetForHeader)); - - exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); - exefsctrmode2C.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffsetForHeader)); - - f.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin); - g.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin); - - if (datalenM > 0) - { - for (int i = 0; i < datalenM; i++) - { - g.Write(exefsctrmode2C.ProcessBytes(exefsctrmode.ProcessBytes(f.ReadBytes(1024 * 1024)))); - g.Flush(); - Console.Write($"\rPartition {p} ExeFS: Decrypting: {fileHeader.ReadableFileName}... {i} / {datalenM + 1} mb..."); - } - } - - if (datalenB > 0) - { - g.Write(exefsctrmode2C.DoFinal(exefsctrmode.DoFinal(f.ReadBytes((int)datalenB)))); - g.Flush(); - } - - Console.Write($"\rPartition {p} ExeFS: Decrypting: {fileHeader.ReadableFileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n"); - } - } - } - - // decrypt exefs - int exefsSizeM = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) / (1024 * 1024); - int exefsSizeB = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) % (1024 * 1024); - int ctroffsetE = (int)(header.SectorSize / 0x10); - - byte[] exefsIVWithOffset = AddToByteArray(partitionHeader.ExeFSIV, ctroffsetE); - - exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); - exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffset)); - - f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin); - g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin); - if (exefsSizeM > 0) - { - for (int i = 0; i < exefsSizeM; i++) - { - g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(1024 * 1024))); - g.Flush(); - Console.Write($"\rPartition {p} ExeFS: Decrypting: {i} / {exefsSizeM + 1} mb"); - } - } - if (exefsSizeB > 0) - { - g.Write(exefsctrmode2C.DoFinal(f.ReadBytes(exefsSizeB))); - g.Flush(); - } - - Console.Write($"\rPartition {p} ExeFS: Decrypting: {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n"); - } - else - { - Console.WriteLine($"Partition {p} ExeFS: No Data... Skipping..."); - } - - if (partitionHeader.RomFSOffsetInMediaUnits != 0) - { - int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (1024 * 1024); - int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) % (1024 * 1024); - - var romfsctrmode = CipherUtilities.GetCipher("AES/CTR"); - romfsctrmode.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), partitionHeader.RomFSIV)); - - f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); - g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); - if (romfsSizeM > 0) - { - for (int i = 0; i < romfsSizeM; i++) - { - g.Write(romfsctrmode.ProcessBytes(f.ReadBytes(1024 * 1024))); - g.Flush(); - Console.Write($"\rPartition {p} RomFS: Decrypting: {i} / {romfsSizeM + 1} mb"); - } - } - if (romfsSizeB > 0) - { - g.Write(romfsctrmode.DoFinal(f.ReadBytes(romfsSizeB))); - g.Flush(); - } - - Console.Write($"\rPartition {p} RomFS: Decrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n"); - } - else - { - Console.WriteLine($"Partition {p} RomFS: No Data... Skipping..."); - } + // Decrypt each of the pieces if they exist + ProcessExtendedHeader(f, g, header, p, partitionHeader, false); + ProcessExeFS(f, g, header, p, partitionHeader, false); + ProcessRomFS(f, g, header, p, partitionHeader, false); // Write the new CryptoMethod g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18B, SeekOrigin.Begin); @@ -290,167 +177,12 @@ namespace ThreeDS } // Determine the Keys to be used - GetEncryptionKeys(partitionHeader.RSA2048Signature, backupFlags.BitMasks, backupFlags.CryptoMethod, p, - out BigInteger KeyX, out BigInteger KeyX2C, out BigInteger KeyY, out BigInteger NormalKey, out BigInteger NormalKey2C); + SetEncryptionKeys(partitionHeader.RSA2048Signature, backupFlags.BitMasks, backupFlags.CryptoMethod, p); - // Encrypt extended header, if it exists - if (partitionHeader.ExtendedHeaderSizeInBytes > 0) - { - // Seek to the partition start and skip first part of the header - f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin); - g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin); - - var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); - exefsctrmode2C.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), partitionHeader.PlainIV)); - - Console.WriteLine($"Partition {p} ExeFS: Encrypting: ExHeader"); - - g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(Constants.CXTExtendedDataHeaderLength))); - g.Flush(); - } - - // Encrypt the ExeFS, if it exists - if (partitionHeader.ExeFSSizeInBytes > 0) - { - if (backupFlags.CryptoMethod != CryptoMethod.Original) - { - f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); - ExeFSHeader exefsHeader = ExeFSHeader.Read(f); - if (exefsHeader != null) - { - foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders) - { - if (!fileHeader.IsCodeBinary) - continue; - - uint datalenM = ((fileHeader.FileSize) / (1024 * 1024)); - uint datalenB = ((fileHeader.FileSize) % (1024 * 1024)); - uint ctroffset = ((fileHeader.FileOffset + header.SectorSize) / 0x10); - - byte[] exefsIVWithOffsetForHeader = AddToByteArray(partitionHeader.ExeFSIV, (int)ctroffset); - - var exefsctrmode = CipherUtilities.GetCipher("AES/CTR"); - exefsctrmode.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), exefsIVWithOffsetForHeader)); - - var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); - exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffsetForHeader)); - - f.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin); - g.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin); - - if (datalenM > 0) - { - for (int i = 0; i < datalenM; i++) - { - g.Write(exefsctrmode2C.ProcessBytes(exefsctrmode.ProcessBytes(f.ReadBytes(1024 * 1024)))); - g.Flush(); - Console.Write($"\rPartition {p} ExeFS: Encrypting: {fileHeader.ReadableFileName}... {i} / {datalenM + 1} mb..."); - } - } - - if (datalenB > 0) - { - g.Write(exefsctrmode2C.DoFinal(exefsctrmode.DoFinal(f.ReadBytes((int)datalenB)))); - g.Flush(); - } - - Console.Write($"\rPartition {p} ExeFS: Encrypting: {fileHeader.ReadableFileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n"); - } - } - } - - // encrypt exefs filename table - f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); - g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); - - var exefsctrmode2C_2 = CipherUtilities.GetCipher("AES/CTR"); - exefsctrmode2C_2.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), partitionHeader.ExeFSIV)); - - g.Write(exefsctrmode2C_2.ProcessBytes(f.ReadBytes((int)header.SectorSize))); - g.Flush(); - - Console.WriteLine($"Partition {p} ExeFS: Encrypting: ExeFS Filename Table"); - - // encrypt exefs - int exefsSizeM = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) / (1024 * 1024); - int exefsSizeB = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) % (1024 * 1024); - int ctroffsetE = (int)(header.SectorSize / 0x10); - - byte[] exefsIVWithOffset = AddToByteArray(partitionHeader.ExeFSIV, ctroffsetE); - - exefsctrmode2C_2 = CipherUtilities.GetCipher("AES/CTR"); - exefsctrmode2C_2.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffset)); - - f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin); - g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin); - if (exefsSizeM > 0) - { - for (int i = 0; i < exefsSizeM; i++) - { - g.Write(exefsctrmode2C_2.ProcessBytes(f.ReadBytes(1024 * 1024))); - g.Flush(); - Console.Write($"\rPartition {p} ExeFS: Encrypting: {i} / {exefsSizeM + 1} mb"); - } - } - if (exefsSizeB > 0) - { - g.Write(exefsctrmode2C_2.DoFinal(f.ReadBytes(exefsSizeB))); - g.Flush(); - } - - Console.Write($"\rPartition {p} ExeFS: Encrypting: {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n"); - } - else - { - Console.WriteLine($"Partition {p} ExeFS: No Data... Skipping..."); - } - - if (partitionHeader.RomFSOffsetInMediaUnits != 0) - { - int romfsBlockSize = 16; // block size in mb - int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (romfsBlockSize * (1024 * 1024)); - int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) % (romfsBlockSize * (1024 * 1024)); - int romfsSizeTotalMb = (int)((partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (1024 * 1024) + 1); - - if (p > 0) // RomFS for partitions 1 and up always use Key0x2C - { - if ((backupFlags.BitMasks & BitMasks.FixedCryptoKey) != 0) // except if using zero-key - { - NormalKey = 0x00; - } - else - { - KeyX = KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C); - NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128); - } - } - - var romfsctrmode = CipherUtilities.GetCipher("AES/CTR"); - romfsctrmode.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), partitionHeader.RomFSIV)); - - f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); - g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); - if (romfsSizeM > 0) - { - for (int i = 0; i < romfsSizeM; i++) - { - g.Write(romfsctrmode.ProcessBytes(f.ReadBytes(romfsBlockSize * 1024 * 1024))); - g.Flush(); - Console.Write($"\rPartition {p} RomFS: Encrypting: {i * romfsBlockSize} / {romfsSizeTotalMb} mb"); - } - } - if (romfsSizeB > 0) - { - g.Write(romfsctrmode.DoFinal(f.ReadBytes(romfsSizeB))); - g.Flush(); - } - - Console.Write($"\rPartition {p} RomFS: Encrypting: {romfsSizeTotalMb} / {romfsSizeTotalMb} mb... Done!\r\n"); - } - else - { - Console.WriteLine($"Partition {p} RomFS: No Data... Skipping..."); - } + // Encrypt each of the pieces if they exist + ProcessExtendedHeader(f, g, header, p, partitionHeader, true); + ProcessExeFS(f, g, header, p, partitionHeader, true); + ProcessRomFS(f, g, header, p, partitionHeader, true, backupFlags); // Write the new CryptoMethod g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18B, SeekOrigin.Begin); @@ -498,13 +230,7 @@ namespace ThreeDS /// BitMasks value for a partition header or backup header /// CryptoMethod used for the partition /// Partition number, only used for logging - /// 3DS KeyX value to use - /// 3DS KeyX2C value to use - /// 3DS KeyY value to use - /// 3DS NormalKey value to use - /// 3DS NormalKey2C value to use - private void GetEncryptionKeys(byte[] rsaSignature, BitMasks masks, CryptoMethod method, int partitionNumber, - out BigInteger KeyX, out BigInteger KeyX2C, out BigInteger KeyY, out BigInteger NormalKey, out BigInteger NormalKey2C) + private void SetEncryptionKeys(byte[] rsaSignature, BitMasks masks, CryptoMethod method, int partitionNumber) { KeyX = 0; KeyX2C = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C); @@ -577,6 +303,20 @@ namespace ThreeDS return arr; } + /// + /// Create AES cipher and intialize + /// + /// BigInteger representation of 128-bit encryption key + /// AES initial value for counter + /// True if cipher is created for encryption, false otherwise + /// Initialized AES cipher + private IBufferedCipher CreateAESCipher(BigInteger key, byte[] iv, bool encrypt) + { + var cipher = CipherUtilities.GetCipher("AES/CTR"); + cipher.Init(encrypt, new ParametersWithIV(new KeyParameter(TakeSixteen(key)), iv)); + return cipher; + } + /// /// Get a 16-byte array representation of a BigInteger /// @@ -598,5 +338,220 @@ namespace ThreeDS return arr; } + + /// + /// 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.SectorSize) + 0x200, SeekOrigin.Begin); + writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin); + + Console.WriteLine($"Partition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + ": ExHeader"); + + var cipher = 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.ExeFSSizeInBytes > 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.SectorSize, 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.SectorSize) / 0x10); + + byte[] exefsIVWithOffsetForHeader = AddToByteArray(partitionHeader.ExeFSIV, (int)ctroffset); + + var firstCipher = CreateAESCipher(NormalKey, exefsIVWithOffsetForHeader, encrypt); + var secondCipher = CreateAESCipher(NormalKey2C, exefsIVWithOffsetForHeader, !encrypt); + + reader.BaseStream.Seek((((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin); + writer.BaseStream.Seek((((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + 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.SectorSize) / (1024 * 1024); + int exefsSizeB = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) % (1024 * 1024); + int ctroffsetE = (int)(header.SectorSize / 0x10); + + byte[] exefsIVWithOffset = AddToByteArray(partitionHeader.ExeFSIV, ctroffsetE); + + var exeFS = CreateAESCipher(NormalKey2C, exefsIVWithOffset, encrypt); + + reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin); + writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, 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.SectorSize, SeekOrigin.Begin); + writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); + + Console.WriteLine($"Partition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table"); + + var exeFSFilenameTable = CreateAESCipher(NormalKey2C, partitionHeader.ExeFSIV, encrypt); + writer.Write(exeFSFilenameTable.ProcessBytes(reader.ReadBytes((int)header.SectorSize))); + 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 + /// Optional backup flags, only used for encrypt + private void ProcessRomFS(BinaryReader reader, BinaryWriter writer, NCSDHeader header, int partitionNumber, NCCHHeader partitionHeader, bool encrypt, NCCHHeaderFlags backupFlags = null) + { + if (partitionHeader.RomFSOffsetInMediaUnits != 0) + { + int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (1024 * 1024); + int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) % (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 = RotateLeft((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 = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128); + } + } + + var cipher = CreateAESCipher(NormalKey, partitionHeader.RomFSIV, encrypt); + + reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin); + writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, 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..."); + } + } } }