mirror of
https://github.com/SabreTools/NDecrypt.git
synced 2026-02-06 13:54:47 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a094b2dd8 | ||
|
|
0e3d22020b | ||
|
|
695f0f44b6 | ||
|
|
dc4bd15e04 | ||
|
|
ce54eb24e2 | ||
|
|
15961e7047 | ||
|
|
edd8ebc048 | ||
|
|
23e9edbf69 | ||
|
|
00ac5e1ca2 | ||
|
|
c79781a6d7 | ||
|
|
5aa50ee252 | ||
|
|
3fbaedd7d5 | ||
|
|
bc31cb0f6a | ||
|
|
3ef32748e9 | ||
|
|
41293ab7c5 |
File diff suppressed because it is too large
Load Diff
@@ -1,39 +1,28 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.N3DS;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
internal static class CommonOperations
|
||||
public static class CommonOperations
|
||||
{
|
||||
#region AES
|
||||
|
||||
/// <summary>
|
||||
/// Create AES cipher and intialize
|
||||
/// </summary>
|
||||
/// <param name="key">BigInteger representation of 128-bit encryption key</param>
|
||||
/// <param name="iv">AES initial value for counter</param>
|
||||
/// <param name="encrypt">True if cipher is created for encryption, false otherwise</param>
|
||||
/// <returns>Initialized AES cipher</returns>
|
||||
public static IBufferedCipher CreateAESCipher(BigInteger key, byte[] iv, bool encrypt)
|
||||
{
|
||||
return encrypt ? CreateAESEncryptionCipher(key, iv) : CreateAESDecryptionCipher(key, iv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create AES decryption cipher and intialize
|
||||
/// </summary>
|
||||
/// <param name="key">BigInteger representation of 128-bit encryption key</param>
|
||||
/// <param name="key">Byte array representation of 128-bit encryption key</param>
|
||||
/// <param name="iv">AES initial value for counter</param>
|
||||
/// <returns>Initialized AES cipher</returns>
|
||||
public static IBufferedCipher CreateAESDecryptionCipher(BigInteger key, byte[] iv)
|
||||
public static IBufferedCipher CreateAESDecryptionCipher(byte[] key, byte[] iv)
|
||||
{
|
||||
var keyParam = new KeyParameter(TakeSixteen(key));
|
||||
if (key.Length != 16)
|
||||
throw new ArgumentOutOfRangeException(nameof(key));
|
||||
|
||||
var keyParam = new KeyParameter(key);
|
||||
var cipher = CipherUtilities.GetCipher("AES/CTR");
|
||||
cipher.Init(forEncryption: false, new ParametersWithIV(keyParam, iv));
|
||||
return cipher;
|
||||
@@ -42,12 +31,15 @@ namespace NDecrypt.Core
|
||||
/// <summary>
|
||||
/// Create AES encryption cipher and intialize
|
||||
/// </summary>
|
||||
/// <param name="key">BigInteger representation of 128-bit encryption key</param>
|
||||
/// <param name="key">Byte array representation of 128-bit encryption key</param>
|
||||
/// <param name="iv">AES initial value for counter</param>
|
||||
/// <returns>Initialized AES cipher</returns>
|
||||
public static IBufferedCipher CreateAESEncryptionCipher(BigInteger key, byte[] iv)
|
||||
public static IBufferedCipher CreateAESEncryptionCipher(byte[] key, byte[] iv)
|
||||
{
|
||||
var keyParam = new KeyParameter(TakeSixteen(key));
|
||||
if (key.Length != 16)
|
||||
throw new ArgumentOutOfRangeException(nameof(key));
|
||||
|
||||
var keyParam = new KeyParameter(key);
|
||||
var cipher = CipherUtilities.GetCipher("AES/CTR");
|
||||
cipher.Init(forEncryption: true, new ParametersWithIV(keyParam, iv));
|
||||
return cipher;
|
||||
@@ -132,31 +124,6 @@ namespace NDecrypt.Core
|
||||
progress($"{blockCount + 1} / {blockCount + 1} MB... Done!\r\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a 16-byte array representation of a BigInteger
|
||||
/// </summary>
|
||||
/// <param name="input">BigInteger value to convert</param>
|
||||
/// <returns>16-byte array representing the BigInteger</returns>
|
||||
private static byte[] TakeSixteen(BigInteger input)
|
||||
{
|
||||
var inputArr = input.ToByteArray();
|
||||
var arr = new byte[16];
|
||||
Array.Copy(inputArr, arr, Math.Min(inputArr.Length, 16));
|
||||
Array.Reverse(arr);
|
||||
|
||||
if (arr.Length < 16)
|
||||
{
|
||||
byte[] temp = new byte[16];
|
||||
for (int i = 0; i < (16 - arr.Length); i++)
|
||||
temp[i] = 0x00;
|
||||
|
||||
Array.Copy(arr, 0, temp, 16 - arr.Length, arr.Length);
|
||||
arr = temp;
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Byte Arrays
|
||||
@@ -167,275 +134,120 @@ namespace NDecrypt.Core
|
||||
/// <param name="input">Byte array to add to</param>
|
||||
/// <param name="add">Amount to add</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
public static byte[] AddToByteArray(byte[] input, int add)
|
||||
public static byte[] Add(byte[] input, uint add)
|
||||
{
|
||||
int len = input.Length;
|
||||
Array.Reverse(input);
|
||||
var bigint = new BigInteger(input);
|
||||
byte[] addBytes = BitConverter.GetBytes(add);
|
||||
Array.Reverse(addBytes);
|
||||
byte[] paddedBytes = new byte[16];
|
||||
Array.Copy(addBytes, 0, paddedBytes, 12, 4);
|
||||
return Add(input, paddedBytes);
|
||||
}
|
||||
|
||||
bigint += add;
|
||||
var arr = bigint.ToByteArray();
|
||||
Array.Reverse(arr);
|
||||
/// <summary>
|
||||
/// Add two numbers represented by byte arrays
|
||||
/// </summary>
|
||||
/// <param name="left">Byte array to add to</param>
|
||||
/// <param name="right">Amount to add</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
public static byte[] Add(byte[] left, byte[] right)
|
||||
{
|
||||
int addBytes = Math.Min(left.Length, right.Length);
|
||||
int outLength = Math.Max(left.Length, right.Length);
|
||||
|
||||
if (arr.Length < len)
|
||||
byte[] output = new byte[outLength];
|
||||
|
||||
uint carry = 0;
|
||||
for (int i = addBytes - 1; i >= 0; i--)
|
||||
{
|
||||
byte[] temp = new byte[len];
|
||||
for (int i = 0; i < (len - arr.Length); i++)
|
||||
temp[i] = 0x00;
|
||||
|
||||
Array.Copy(arr, 0, temp, len - arr.Length, arr.Length);
|
||||
arr = temp;
|
||||
uint addValue = (uint)(left[i] + right[i]) + carry;
|
||||
output[i] = (byte)addValue;
|
||||
carry = addValue >> 8;
|
||||
}
|
||||
|
||||
return arr;
|
||||
if (outLength != addBytes && left.Length == outLength)
|
||||
Array.Copy(left, addBytes, output, addBytes, outLength - addBytes);
|
||||
else if (outLength != addBytes && right.Length == outLength)
|
||||
Array.Copy(right, addBytes, output, addBytes, outLength - addBytes);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a rotate left on a BigInteger
|
||||
/// Perform a rotate left on a byte array
|
||||
/// </summary>
|
||||
/// <param name="val">BigInteger value to rotate</param>
|
||||
/// <param name="val">Byte array value to rotate</param>
|
||||
/// <param name="r_bits">Number of bits to rotate</param>
|
||||
/// <param name="max_bits">Maximum number of bits to rotate on</param>
|
||||
/// <returns>Rotated BigInteger value</returns>
|
||||
public static BigInteger RotateLeft(BigInteger val, int r_bits, int max_bits)
|
||||
/// <returns>Rotated byte array value</returns>
|
||||
public static byte[] RotateLeft(byte[] val, int r_bits)
|
||||
{
|
||||
return (val << r_bits % max_bits) & (BigInteger.Pow(2, max_bits) - 1) | ((val & (BigInteger.Pow(2, max_bits) - 1)) >> (max_bits - (r_bits % max_bits)));
|
||||
}
|
||||
byte[] output = new byte[val.Length];
|
||||
Array.Copy(val, output, output.Length);
|
||||
|
||||
#endregion
|
||||
// Shift by bytes
|
||||
while (r_bits >= 8)
|
||||
{
|
||||
byte temp = output[0];
|
||||
for (int i = 0; i < output.Length - 1; i++)
|
||||
{
|
||||
output[i] = output[i + 1];
|
||||
}
|
||||
|
||||
#region Offsets
|
||||
output[output.Length - 1] = temp;
|
||||
r_bits -= 8;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the offset of a partition ExeFS
|
||||
/// </summary>
|
||||
/// <returns>Offset to the ExeFS of the partition, 0 on error</returns>
|
||||
public static uint GetExeFSOffset(Cart cart, int index)
|
||||
{
|
||||
// Empty partitions table means no size is available
|
||||
var partitionsTable = cart.Header?.PartitionsTable;
|
||||
if (partitionsTable == null)
|
||||
return 0;
|
||||
// Shift by bits
|
||||
if (r_bits > 0)
|
||||
{
|
||||
byte bitMask = (byte)(8 - r_bits), carry, wrap = 0;
|
||||
for (int i = 0; i < output.Length; i++)
|
||||
{
|
||||
carry = (byte)((255 << bitMask & output[i]) >> bitMask);
|
||||
|
||||
// Invalid partition table entry means no size is available
|
||||
var entry = partitionsTable[index];
|
||||
if (entry == null)
|
||||
return 0;
|
||||
// Make sure the first byte carries to the end
|
||||
if (i == 0)
|
||||
wrap = carry;
|
||||
|
||||
// Empty partitions array means no size is available
|
||||
var partitions = cart.Partitions;
|
||||
if (partitions == null)
|
||||
return 0;
|
||||
// Otherwise, move to the last byte
|
||||
else
|
||||
output[i - 1] |= carry;
|
||||
|
||||
// Invalid partition means no size is available
|
||||
var header = partitions[index];
|
||||
if (header == null)
|
||||
return 0;
|
||||
// Shift the current bits
|
||||
output[i] <<= r_bits;
|
||||
}
|
||||
|
||||
// If the offset is 0, return 0
|
||||
uint exeFsOffsetMU = header.ExeFSOffsetInMediaUnits;
|
||||
if (exeFsOffsetMU == 0)
|
||||
return 0;
|
||||
// Make sure the wrap happens
|
||||
output[output.Length - 1] |= wrap;
|
||||
}
|
||||
|
||||
// Return the adjusted offset
|
||||
uint partitionOffsetMU = entry.Offset;
|
||||
return (partitionOffsetMU + exeFsOffsetMU) * cart.MediaUnitSize();
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the offset of a partition ExeFS
|
||||
/// XOR two numbers represented by byte arrays
|
||||
/// </summary>
|
||||
/// <returns>Offset to the ExeFS of the partition, 0 on error</returns>
|
||||
public static uint GetExeFSOffset(NCCHHeader header,
|
||||
PartitionTableEntry entry,
|
||||
uint mediaUnitSize)
|
||||
/// <param name="left">Byte array to XOR to</param>
|
||||
/// <param name="right">Amount to XOR</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
public static byte[] Xor(byte[] left, byte[] right)
|
||||
{
|
||||
// If the offset is 0, return 0
|
||||
uint exeFsOffsetMU = header.ExeFSOffsetInMediaUnits;
|
||||
if (exeFsOffsetMU == 0)
|
||||
return 0;
|
||||
int xorBytes = Math.Min(left.Length, right.Length);
|
||||
int outLength = Math.Max(left.Length, right.Length);
|
||||
|
||||
// Return the adjusted offset
|
||||
uint partitionOffsetMU = entry.Offset;
|
||||
return (partitionOffsetMU + exeFsOffsetMU) * mediaUnitSize;
|
||||
byte[] output = new byte[outLength];
|
||||
for (int i = 0; i < xorBytes; i++)
|
||||
{
|
||||
output[i] = (byte)(left[i] ^ right[i]);
|
||||
}
|
||||
|
||||
if (outLength != xorBytes && left.Length == outLength)
|
||||
Array.Copy(left, xorBytes, output, xorBytes, outLength - xorBytes);
|
||||
else if (outLength != xorBytes && right.Length == outLength)
|
||||
Array.Copy(right, xorBytes, output, xorBytes, outLength - xorBytes);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the offset of a partition
|
||||
/// </summary>
|
||||
/// <returns>Offset to the partition, 0 on error</returns>
|
||||
public static uint GetPartitionOffset(Cart cart, int index)
|
||||
{
|
||||
// Empty partitions table means no size is available
|
||||
var partitionsTable = cart.Header?.PartitionsTable;
|
||||
if (partitionsTable == null)
|
||||
return 0;
|
||||
|
||||
// Invalid partition table entry means no size is available
|
||||
var entry = partitionsTable[index];
|
||||
if (entry == null)
|
||||
return 0;
|
||||
|
||||
// Return the adjusted offset
|
||||
uint partitionOffsetMU = entry.Offset;
|
||||
return partitionOffsetMU * cart.MediaUnitSize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the offset of a partition
|
||||
/// </summary>
|
||||
/// <returns>Offset to the partition, 0 on error</returns>
|
||||
public static uint GetPartitionOffset(PartitionTableEntry entry,
|
||||
uint mediaUnitSize)
|
||||
{
|
||||
// Invalid partition table entry means no size is available
|
||||
if (entry.Offset == 0)
|
||||
return 0;
|
||||
|
||||
// Return the adjusted offset
|
||||
uint partitionOffsetMU = entry.Offset;
|
||||
return partitionOffsetMU * mediaUnitSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the offset of a partition RomFS
|
||||
/// </summary>
|
||||
/// <returns>Offset to the RomFS of the partition, 0 on error</returns>
|
||||
public static uint GetRomFSOffset(Cart cart, int index)
|
||||
{
|
||||
// Empty partitions table means no size is available
|
||||
var partitionsTable = cart.Header?.PartitionsTable;
|
||||
if (partitionsTable == null)
|
||||
return 0;
|
||||
|
||||
// Invalid partition table entry means no size is available
|
||||
var entry = partitionsTable[index];
|
||||
if (entry == null)
|
||||
return 0;
|
||||
|
||||
// Empty partitions array means no size is available
|
||||
var partitions = cart.Partitions;
|
||||
if (partitions == null)
|
||||
return 0;
|
||||
|
||||
// Invalid partition means no size is available
|
||||
var header = partitions[index];
|
||||
if (header == null)
|
||||
return 0;
|
||||
|
||||
// If the offset is 0, return 0
|
||||
uint romFsOffsetMU = header.RomFSOffsetInMediaUnits;
|
||||
if (romFsOffsetMU == 0)
|
||||
return 0;
|
||||
|
||||
// Return the adjusted offset
|
||||
uint partitionOffsetMU = entry.Offset;
|
||||
return (partitionOffsetMU + romFsOffsetMU) * cart.MediaUnitSize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the offset of a partition RomFS
|
||||
/// </summary>
|
||||
/// <returns>Offset to the RomFS of the partition, 0 on error</returns>
|
||||
public static uint GetRomFSOffset(NCCHHeader header,
|
||||
PartitionTableEntry entry,
|
||||
uint mediaUnitSize)
|
||||
{
|
||||
// If the offset is 0, return 0
|
||||
uint romFsOffsetMU = header.RomFSOffsetInMediaUnits;
|
||||
if (romFsOffsetMU == 0)
|
||||
return 0;
|
||||
|
||||
// Return the adjusted offset
|
||||
uint partitionOffsetMU = entry.Offset;
|
||||
return (partitionOffsetMU + romFsOffsetMU - 1) * mediaUnitSize;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sizes
|
||||
|
||||
/// <summary>
|
||||
/// Get the size of a partition ExeFS
|
||||
/// </summary>
|
||||
/// <returns>Size of the partition ExeFS in bytes, 0 on error</returns>
|
||||
public static uint GetExeFSSize(Cart cart, int index)
|
||||
{
|
||||
// Empty partitions array means no size is available
|
||||
var partitions = cart.Partitions;
|
||||
if (partitions == null)
|
||||
return 0;
|
||||
|
||||
// Invalid partition header means no size is available
|
||||
var header = partitions[index];
|
||||
if (header == null)
|
||||
return 0;
|
||||
|
||||
// Return the adjusted size
|
||||
return GetExeFSSize(header, cart.MediaUnitSize());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the size of a partition ExeFS
|
||||
/// </summary>
|
||||
/// <returns>Size of the partition ExeFS in bytes, 0 on error</returns>
|
||||
public static uint GetExeFSSize(NCCHHeader header, uint mediaUnitSize)
|
||||
=> header.ExeFSSizeInMediaUnits * mediaUnitSize;
|
||||
|
||||
/// <summary>
|
||||
/// Get the size of a partition extended header
|
||||
/// </summary>
|
||||
/// <returns>Size of the partition extended header in bytes, 0 on error</returns>
|
||||
public static uint GetExtendedHeaderSize(Cart cart, int index)
|
||||
{
|
||||
// Empty partitions array means no size is available
|
||||
var partitions = cart.Partitions;
|
||||
if (partitions == null)
|
||||
return 0;
|
||||
|
||||
// Invalid partition header means no size is available
|
||||
var header = partitions[index];
|
||||
if (header == null)
|
||||
return 0;
|
||||
|
||||
// Return the adjusted size
|
||||
return GetExtendedHeaderSize(header);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the size of a partition extended header
|
||||
/// </summary>
|
||||
/// <returns>Size of the partition extended header in bytes, 0 on error</returns>
|
||||
public static uint GetExtendedHeaderSize(NCCHHeader header)
|
||||
=> header.ExtendedHeaderSizeInBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Get the size of a partition RomFS
|
||||
/// </summary>
|
||||
/// <returns>Size of the partition RomFS in bytes, 0 on error</returns>
|
||||
public static uint GetRomFSSize(Cart cart, int index)
|
||||
{
|
||||
// Empty partitions array means no size is available
|
||||
var partitions = cart.Partitions;
|
||||
if (partitions == null)
|
||||
return 0;
|
||||
|
||||
// Invalid partition header means no size is available
|
||||
var header = partitions[index];
|
||||
if (header == null)
|
||||
return 0;
|
||||
|
||||
// Return the adjusted size
|
||||
return GetRomFSSize(header, cart.MediaUnitSize());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the size of a partition RomFS
|
||||
/// </summary>
|
||||
/// <returns>Size of the partition RomFS in bytes, 0 on error</returns>
|
||||
public static uint GetRomFSSize(NCCHHeader header, uint mediaUnitSize)
|
||||
=> header.RomFSSizeInMediaUnits * mediaUnitSize;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.IO.Readers;
|
||||
|
||||
@@ -22,29 +21,29 @@ namespace NDecrypt.Core
|
||||
/// <summary>
|
||||
/// AES Hardware Constant
|
||||
/// </summary>
|
||||
public BigInteger AESHardwareConstant { get; private set; }
|
||||
public byte[] AESHardwareConstant { get; private set; } = [];
|
||||
|
||||
#region Retail Keys
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x18 (New 3DS 9.3)
|
||||
/// </summary>
|
||||
public BigInteger KeyX0x18 { get; private set; }
|
||||
public byte[] KeyX0x18 { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x1B (New 3DS 9.6)
|
||||
/// </summary>
|
||||
public BigInteger KeyX0x1B { get; private set; }
|
||||
public byte[] KeyX0x1B { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x25 (> 7.x)
|
||||
/// </summary>
|
||||
public BigInteger KeyX0x25 { get; private set; }
|
||||
public byte[] KeyX0x25 { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x2C (< 6.x)
|
||||
/// </summary>
|
||||
public BigInteger KeyX0x2C { get; private set; }
|
||||
public byte[] KeyX0x2C { get; private set; } = [];
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -53,22 +52,22 @@ namespace NDecrypt.Core
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x18 (New 3DS 9.3)
|
||||
/// </summary>
|
||||
public BigInteger DevKeyX0x18 { get; private set; }
|
||||
public byte[] DevKeyX0x18 { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x1B New 3DS 9.6)
|
||||
/// </summary>
|
||||
public BigInteger DevKeyX0x1B { get; private set; }
|
||||
public byte[] DevKeyX0x1B { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x25 (> 7.x)
|
||||
/// </summary>
|
||||
public BigInteger DevKeyX0x25 { get; private set; }
|
||||
public byte[] DevKeyX0x25 { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x2C (< 6.x)
|
||||
/// </summary>
|
||||
public BigInteger DevKeyX0x2C { get; private set; }
|
||||
public byte[] DevKeyX0x2C { get; private set; } = [];
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -103,10 +102,6 @@ namespace NDecrypt.Core
|
||||
try
|
||||
{
|
||||
using var reader = new IniReader(keyfile);
|
||||
|
||||
// This is required to preserve sign for BigInteger
|
||||
byte[] signByte = [0x00];
|
||||
|
||||
while (reader.ReadNextLine())
|
||||
{
|
||||
// Ignore comments in the file
|
||||
@@ -117,28 +112,25 @@ namespace NDecrypt.Core
|
||||
|
||||
var kvp = reader.KeyValuePair!.Value;
|
||||
byte[] value = StringToByteArray(kvp.Value);
|
||||
Array.Reverse(value);
|
||||
byte[] valueWithSign = [.. value, .. signByte];
|
||||
|
||||
switch (kvp.Key)
|
||||
{
|
||||
// Hardware constant
|
||||
case "generator":
|
||||
AESHardwareConstant = new BigInteger(value);
|
||||
AESHardwareConstant = value;
|
||||
break;
|
||||
|
||||
// Retail Keys
|
||||
case "slot0x18KeyX":
|
||||
KeyX0x18 = new BigInteger(valueWithSign);
|
||||
KeyX0x18 = value;
|
||||
break;
|
||||
case "slot0x1BKeyX":
|
||||
KeyX0x1B = new BigInteger(valueWithSign);
|
||||
KeyX0x1B = value;
|
||||
break;
|
||||
case "slot0x25KeyX":
|
||||
KeyX0x25 = new BigInteger(valueWithSign);
|
||||
KeyX0x25 = value;
|
||||
break;
|
||||
case "slot0x2CKeyX":
|
||||
KeyX0x2C = new BigInteger(valueWithSign);
|
||||
KeyX0x2C = value;
|
||||
break;
|
||||
|
||||
// Currently Unused KeyX
|
||||
@@ -225,23 +217,29 @@ namespace NDecrypt.Core
|
||||
{
|
||||
using Stream reader = File.Open(keyfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// This is required to preserve sign for BigInteger
|
||||
byte[] signByte = [0x00];
|
||||
|
||||
// Hardware constant
|
||||
AESHardwareConstant = new BigInteger(reader.ReadBytes(16));
|
||||
AESHardwareConstant = reader.ReadBytes(16);
|
||||
Array.Reverse(AESHardwareConstant);
|
||||
|
||||
// Retail keys
|
||||
KeyX0x18 = new BigInteger([.. reader.ReadBytes(16), .. signByte]);
|
||||
KeyX0x1B = new BigInteger([.. reader.ReadBytes(16), .. signByte]);
|
||||
KeyX0x25 = new BigInteger([.. reader.ReadBytes(16), .. signByte]);
|
||||
KeyX0x2C = new BigInteger([.. reader.ReadBytes(16), .. signByte]);
|
||||
KeyX0x18 = reader.ReadBytes(16);
|
||||
Array.Reverse(KeyX0x18);
|
||||
KeyX0x1B = reader.ReadBytes(16);
|
||||
Array.Reverse(KeyX0x1B);
|
||||
KeyX0x25 = reader.ReadBytes(16);
|
||||
Array.Reverse(KeyX0x25);
|
||||
KeyX0x2C = reader.ReadBytes(16);
|
||||
Array.Reverse(KeyX0x2C);
|
||||
|
||||
// Development keys
|
||||
DevKeyX0x18 = new BigInteger([.. reader.ReadBytes(16), .. signByte]);
|
||||
DevKeyX0x1B = new BigInteger([.. reader.ReadBytes(16), .. signByte]);
|
||||
DevKeyX0x25 = new BigInteger([.. reader.ReadBytes(16), .. signByte]);
|
||||
DevKeyX0x2C = new BigInteger([.. reader.ReadBytes(16), .. signByte]);
|
||||
DevKeyX0x18 = reader.ReadBytes(16);
|
||||
Array.Reverse(DevKeyX0x18);
|
||||
DevKeyX0x1B = reader.ReadBytes(16);
|
||||
Array.Reverse(DevKeyX0x1B);
|
||||
DevKeyX0x25 = reader.ReadBytes(16);
|
||||
Array.Reverse(DevKeyX0x25);
|
||||
DevKeyX0x2C = reader.ReadBytes(16);
|
||||
Array.Reverse(DevKeyX0x2C);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
using System;
|
||||
using SabreTools.Models.N3DS;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
internal static class Extensions
|
||||
{
|
||||
#region Constants
|
||||
|
||||
// Setup Keys and IVs
|
||||
public static byte[] PlainCounter = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
||||
public static byte[] ExefsCounter = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
||||
public static byte[] RomfsCounter = [0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
||||
|
||||
#endregion
|
||||
|
||||
#region ExeFSFileHeader
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a file header represents a CODE block
|
||||
/// </summary>
|
||||
public static bool IsCodeBinary(this ExeFSFileHeader? header)
|
||||
{
|
||||
if (header == null)
|
||||
return false;
|
||||
|
||||
return header.FileName == ".code\0\0\0";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region NCCHHeader
|
||||
|
||||
/// <summary>
|
||||
/// Get the initial value for the plain counter
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the initial value for the plain counter
|
||||
/// </summary>
|
||||
public static byte[] PlainIV(this NCCHHeader? header)
|
||||
{
|
||||
if (header == null)
|
||||
return [];
|
||||
|
||||
byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId);
|
||||
return [.. partitionIdBytes, .. PlainCounter];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the initial value for the ExeFS counter
|
||||
/// </summary>
|
||||
public static byte[] ExeFSIV(this Cart cart, int partitionIndex)
|
||||
{
|
||||
if (cart.Partitions == null)
|
||||
return [];
|
||||
if (partitionIndex < 0 || partitionIndex >= cart.Partitions.Length)
|
||||
return [];
|
||||
|
||||
var header = cart.Partitions[partitionIndex];
|
||||
return ExeFSIV(header);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the initial value for the ExeFS counter
|
||||
/// </summary>
|
||||
public static byte[] ExeFSIV(this NCCHHeader? header)
|
||||
{
|
||||
if (header == null)
|
||||
return [];
|
||||
|
||||
byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId);
|
||||
return [.. partitionIdBytes, .. ExefsCounter];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the initial value for the RomFS counter
|
||||
/// </summary>
|
||||
public static byte[] RomFSIV(this Cart cart, int partitionIndex)
|
||||
{
|
||||
if (cart.Partitions == null)
|
||||
return [];
|
||||
if (partitionIndex < 0 || partitionIndex >= cart.Partitions.Length)
|
||||
return [];
|
||||
|
||||
var header = cart.Partitions[partitionIndex];
|
||||
return RomFSIV(header);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the initial value for the RomFS counter
|
||||
/// </summary>
|
||||
public static byte[] RomFSIV(this NCCHHeader? header)
|
||||
{
|
||||
if (header == null)
|
||||
return [];
|
||||
|
||||
byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId);
|
||||
return [.. partitionIdBytes, .. RomfsCounter];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region NCCHHeaderFlags
|
||||
|
||||
/// <summary>
|
||||
/// Get if the NoCrypto bit is set
|
||||
/// </summary>
|
||||
public static bool PossblyDecrypted(this NCCHHeaderFlags flags)
|
||||
{
|
||||
if (flags == null)
|
||||
return false;
|
||||
|
||||
return flags.BitMasks.HasFlag(BitMasks.NoCrypto);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region NCSDHeader
|
||||
|
||||
//// <summary>
|
||||
/// Media Unit Size i.e. u32 MediaUnitSize = 0x200*2^flags[6];
|
||||
/// </summary>
|
||||
public static uint MediaUnitSize(this Cart cart)
|
||||
{
|
||||
return cart.Header.MediaUnitSize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Media Unit Size i.e. u32 MediaUnitSize = 0x200*2^flags[6];
|
||||
/// </summary>
|
||||
public static uint MediaUnitSize(this NCSDHeader? header)
|
||||
{
|
||||
if (header?.PartitionFlags == null)
|
||||
return default;
|
||||
|
||||
return (uint)(0x200 * Math.Pow(2, header.PartitionFlags[(int)NCSDFlags.MediaUnitSize]));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>0.3.0</VersionPrefix>
|
||||
<VersionPrefix>0.3.1</VersionPrefix>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
@@ -46,8 +46,8 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.Hashing" Version="1.4.0" />
|
||||
<PackageReference Include="SabreTools.IO" Version="1.5.0" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.5.0" />
|
||||
<PackageReference Include="SabreTools.Serialization" Version="1.7.0" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.5.1" />
|
||||
<PackageReference Include="SabreTools.Serialization" Version="1.7.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using SabreTools.Models.N3DS;
|
||||
using static NDecrypt.Core.CommonOperations;
|
||||
@@ -11,15 +10,15 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
public class PartitionKeys
|
||||
{
|
||||
public BigInteger KeyX { get; private set; }
|
||||
public byte[] KeyX { get; private set; }
|
||||
|
||||
public BigInteger KeyX2C { get; private set; }
|
||||
public byte[] KeyX2C { get; }
|
||||
|
||||
public BigInteger KeyY { get; private set; }
|
||||
public byte[] KeyY { get; }
|
||||
|
||||
public BigInteger NormalKey { get; private set; }
|
||||
public byte[] NormalKey { get; private set; }
|
||||
|
||||
public BigInteger NormalKey2C { get; private set; }
|
||||
public byte[] NormalKey2C { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Decryption args to use while processing
|
||||
@@ -52,32 +51,28 @@ namespace NDecrypt.Core
|
||||
_development = development;
|
||||
|
||||
// Set the standard KeyX values
|
||||
KeyX = 0;
|
||||
KeyX = new byte[16];
|
||||
KeyX2C = development ? args.DevKeyX0x2C : args.KeyX0x2C;
|
||||
|
||||
// Backup headers can't have a KeyY value set
|
||||
KeyY = new byte[16];
|
||||
if (signature != null)
|
||||
{
|
||||
byte[] signature16 = new byte[16];
|
||||
Array.Copy(signature, signature16, 16);
|
||||
Array.Reverse(signature16);
|
||||
KeyY = new BigInteger(signature16);
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyY = new BigInteger(0);
|
||||
}
|
||||
Array.Copy(signature, KeyY, 16);
|
||||
|
||||
// Set the standard normal key values
|
||||
NormalKey = 0x00;
|
||||
NormalKey2C = RotateLeft((RotateLeft(KeyX2C, 2, 128) ^ KeyY) + args.AESHardwareConstant, 87, 128);
|
||||
NormalKey = new byte[16];
|
||||
|
||||
NormalKey2C = RotateLeft(KeyX2C, 2);
|
||||
NormalKey2C = Xor(NormalKey2C, KeyY);
|
||||
NormalKey2C = Add(NormalKey2C, args.AESHardwareConstant);
|
||||
NormalKey2C = RotateLeft(NormalKey2C, 87);
|
||||
|
||||
// Special case for zero-key
|
||||
if (masks.HasFlag(BitMasks.FixedCryptoKey))
|
||||
{
|
||||
Console.WriteLine("Encryption Method: Zero Key");
|
||||
NormalKey = 0x00;
|
||||
NormalKey2C = 0x00;
|
||||
NormalKey = new byte[16];
|
||||
NormalKey2C = new byte[16];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -106,7 +101,10 @@ namespace NDecrypt.Core
|
||||
}
|
||||
|
||||
// Set the normal key based on the new KeyX value
|
||||
NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + args.AESHardwareConstant, 87, 128);
|
||||
NormalKey = RotateLeft(KeyX, 2);
|
||||
NormalKey = Xor(NormalKey, KeyY);
|
||||
NormalKey = Add(NormalKey, args.AESHardwareConstant);
|
||||
NormalKey = RotateLeft(NormalKey, 87);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -117,13 +115,17 @@ namespace NDecrypt.Core
|
||||
// NormalKey has a constant value for zero-key
|
||||
if (masks.HasFlag(BitMasks.FixedCryptoKey))
|
||||
{
|
||||
NormalKey = 0x00;
|
||||
NormalKey = new byte[16];
|
||||
return;
|
||||
}
|
||||
|
||||
// Encrypting RomFS for partitions 1 and up always use Key0x2C
|
||||
KeyX = _development ? _decryptArgs.DevKeyX0x2C : _decryptArgs.KeyX0x2C;
|
||||
NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + _decryptArgs.AESHardwareConstant, 87, 128);
|
||||
|
||||
NormalKey = RotateLeft(KeyX, 2);
|
||||
NormalKey = Xor(NormalKey, KeyY);
|
||||
NormalKey = Add(NormalKey, _decryptArgs.AESHardwareConstant);
|
||||
NormalKey = RotateLeft(NormalKey, 87);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,9 @@
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.N3DS;
|
||||
using SabreTools.Serialization.Wrappers;
|
||||
using static NDecrypt.Core.CommonOperations;
|
||||
using N3DSDeserializer = SabreTools.Serialization.Deserializers.N3DS;
|
||||
using static SabreTools.Models.N3DS.Constants;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
@@ -47,8 +48,8 @@ namespace NDecrypt.Core
|
||||
using var output = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = N3DSDeserializer.DeserializeStream(input);
|
||||
if (cart?.Header == null || cart?.CardInfoHeader?.InitialData?.BackupHeader == null)
|
||||
var cart = N3DS.Create(input);
|
||||
if (cart?.Model == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS cart image!");
|
||||
return false;
|
||||
@@ -83,8 +84,8 @@ namespace NDecrypt.Core
|
||||
using var output = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = N3DSDeserializer.DeserializeStream(input);
|
||||
if (cart?.Header == null || cart?.CardInfoHeader?.InitialData?.BackupHeader == null)
|
||||
var cart = N3DS.Create(input);
|
||||
if (cart?.Model == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS cart image!");
|
||||
return false;
|
||||
@@ -111,10 +112,10 @@ namespace NDecrypt.Core
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void DecryptAllPartitions(Cart cart, bool force, Stream input, Stream output)
|
||||
private void DecryptAllPartitions(N3DS cart, bool force, Stream input, Stream output)
|
||||
{
|
||||
// Check the partitions table
|
||||
if (cart.Header?.PartitionsTable == null || cart.Partitions == null)
|
||||
if (cart.PartitionsTable == null || cart.Partitions == null)
|
||||
{
|
||||
Console.WriteLine("Invalid partitions table!");
|
||||
return;
|
||||
@@ -123,13 +124,21 @@ namespace NDecrypt.Core
|
||||
// Iterate over all 8 NCCH partitions
|
||||
for (int p = 0; p < 8; p++)
|
||||
{
|
||||
// Check the partition exists
|
||||
if (cart.Partitions[p] == null)
|
||||
var partition = cart.Partitions[p];
|
||||
if (partition == null || partition.MagicID != NCCHMagicNumber)
|
||||
{
|
||||
Console.WriteLine($"Partition {p} Not found... Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check the partition has data
|
||||
var partitionEntry = cart.PartitionsTable[p];
|
||||
if (partitionEntry == null || partitionEntry.Length == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {p} No data... Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Decrypt the partition, if possible
|
||||
if (ShouldDecryptPartition(cart, p, force))
|
||||
DecryptPartition(cart, p, input, output);
|
||||
@@ -139,7 +148,7 @@ namespace NDecrypt.Core
|
||||
/// <summary>
|
||||
/// Determine if the current partition should be decrypted
|
||||
/// </summary>s
|
||||
private static bool ShouldDecryptPartition(Cart cart, int index, bool force)
|
||||
private static bool ShouldDecryptPartition(N3DS cart, int index, bool force)
|
||||
{
|
||||
// If we're forcing the operation, tell the user
|
||||
if (force)
|
||||
@@ -148,7 +157,7 @@ namespace NDecrypt.Core
|
||||
return true;
|
||||
}
|
||||
// If we're not forcing the operation, check if the 'NoCrypto' bit is set
|
||||
else if (cart.Partitions![index]!.Flags!.PossblyDecrypted())
|
||||
else if (cart.PossiblyDecrypted(index))
|
||||
{
|
||||
Console.WriteLine($"Partition {index}: Already Decrypted?...");
|
||||
return false;
|
||||
@@ -165,7 +174,7 @@ namespace NDecrypt.Core
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void DecryptPartition(Cart cart, int index, Stream input, Stream output)
|
||||
private void DecryptPartition(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Determine the keys needed for this partition
|
||||
SetDecryptionKeys(cart, index);
|
||||
@@ -184,19 +193,17 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
private void SetDecryptionKeys(Cart cart, int index)
|
||||
private void SetDecryptionKeys(N3DS cart, int index)
|
||||
{
|
||||
// Get the partition
|
||||
var partition = cart.Partitions?[index];
|
||||
if (partition == null)
|
||||
if (partition?.Flags == null)
|
||||
return;
|
||||
|
||||
// Get partition-specific values
|
||||
byte[]? rsaSignature = partition.RSA2048Signature;
|
||||
|
||||
// Set the header to use based on mode
|
||||
BitMasks masks = partition.Flags!.BitMasks;
|
||||
CryptoMethod method = partition.Flags!.CryptoMethod;
|
||||
BitMasks masks = cart.GetBitMasks(index);
|
||||
CryptoMethod method = cart.GetCryptoMethod(index);
|
||||
|
||||
// Get the partition keys
|
||||
KeysMap[index] = new PartitionKeys(_decryptArgs, rsaSignature, masks, method, _development);
|
||||
@@ -209,20 +216,20 @@ namespace NDecrypt.Core
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private bool DecryptExtendedHeader(Cart cart, int index, Stream input, Stream output)
|
||||
private bool DecryptExtendedHeader(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = GetPartitionOffset(cart, index);
|
||||
if (partitionOffset == 0)
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
if (partitionOffset == 0 || partitionOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
Console.WriteLine($"Partition {index} No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint extHeaderSize = GetExtendedHeaderSize(cart, index);
|
||||
uint extHeaderSize = cart.GetExtendedHeaderSize(index);
|
||||
if (extHeaderSize == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} RomFS: No Extended Header... Skipping...");
|
||||
Console.WriteLine($"Partition {index} No Extended Header... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -230,7 +237,7 @@ namespace NDecrypt.Core
|
||||
input.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
output.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {index} ExeFS: Decrypting: ExHeader");
|
||||
Console.WriteLine($"Partition {index}: Decrypting - ExHeader");
|
||||
|
||||
// Create the Plain AES cipher for this partition
|
||||
var cipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey2C, cart.PlainIV(index));
|
||||
@@ -253,17 +260,17 @@ namespace NDecrypt.Core
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private bool DecryptExeFS(Cart cart, int index, Stream input, Stream output)
|
||||
private bool DecryptExeFS(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Validate the ExeFS
|
||||
uint exeFsOffset = GetExeFSOffset(cart, index);
|
||||
if (exeFsOffset == 0)
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsHeaderOffset == 0 || exeFsHeaderOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint exeFsSize = GetExeFSSize(cart, index);
|
||||
uint exeFsSize = cart.GetExeFSSize(index);
|
||||
if (exeFsSize == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
@@ -274,24 +281,28 @@ namespace NDecrypt.Core
|
||||
DecryptExeFSFilenameTable(cart, index, input, output);
|
||||
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
if (cart.Partitions![index]!.Flags!.CryptoMethod != CryptoMethod.Original)
|
||||
if (cart.GetCryptoMethod(index) != CryptoMethod.Original)
|
||||
DecryptExeFSFileEntries(cart, index, input, output);
|
||||
|
||||
// Get the ExeFS files offset
|
||||
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
|
||||
|
||||
// Seek to the ExeFS
|
||||
input.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
input.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
int ctroffsetE = (int)(cart.MediaUnitSize() / 0x10);
|
||||
byte[] exefsIVWithOffset = AddToByteArray(cart.ExeFSIV(index), ctroffsetE);
|
||||
uint ctroffsetE = cart.MediaUnitSize / 0x10;
|
||||
byte[] exefsIVWithOffset = Add(cart.ExeFSIV(index), ctroffsetE);
|
||||
var cipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey2C, exefsIVWithOffset);
|
||||
|
||||
// Setup and perform the decryption
|
||||
exeFsSize -= cart.MediaUnitSize;
|
||||
PerformAESOperation(exeFsSize,
|
||||
cipher,
|
||||
input,
|
||||
output,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting: {s}"));
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting - {s}"));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -303,11 +314,11 @@ namespace NDecrypt.Core
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void DecryptExeFSFilenameTable(Cart cart, int index, Stream input, Stream output)
|
||||
private void DecryptExeFSFilenameTable(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Get ExeFS offset
|
||||
uint exeFsOffset = GetExeFSOffset(cart, index);
|
||||
if (exeFsOffset == 0)
|
||||
uint exeFsOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsOffset == 0 || exeFsOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
@@ -317,13 +328,15 @@ namespace NDecrypt.Core
|
||||
input.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {index} ExeFS: Decrypting: ExeFS Filename Table");
|
||||
Console.WriteLine($"Partition {index} ExeFS: Decrypting - ExeFS Filename Table");
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
var cipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey2C, cart.ExeFSIV(index));
|
||||
|
||||
// Process the filename table
|
||||
PerformAESOperation(cart.MediaUnitSize(), cipher, input, output, null);
|
||||
byte[] readBytes = input.ReadBytes((int)cart.MediaUnitSize);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
output.Write(processedBytes);
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
// In .NET 6.0, this operation is not picked up by the reader, so we have to force it to reload its buffer
|
||||
@@ -339,37 +352,41 @@ namespace NDecrypt.Core
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void DecryptExeFSFileEntries(Cart cart, int index, Stream input, Stream output)
|
||||
private void DecryptExeFSFileEntries(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Get ExeFS offset
|
||||
uint exeFsHeaderOffset = GetExeFSOffset(cart, index);
|
||||
if (exeFsHeaderOffset == 0)
|
||||
if (cart.ExeFSHeaders == null || index < 0 || index > cart.ExeFSHeaders.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get to the start of the files
|
||||
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize();
|
||||
input.Seek(exeFsHeaderOffset, SeekOrigin.Begin);
|
||||
var exefsHeader = N3DSDeserializer.ParseExeFSHeader(input);
|
||||
|
||||
// If the header failed to read, log and return
|
||||
if (exefsHeader == null)
|
||||
// Get the ExeFS header
|
||||
var exeFsHeader = cart.ExeFSHeaders[index];
|
||||
if (exeFsHeader?.FileHeaders == null)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS header could not be read. Skipping...");
|
||||
Console.WriteLine($"Partition {index} ExeFS header does not exist. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var fileHeader in exefsHeader.FileHeaders!)
|
||||
// Get the ExeFS offset
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
|
||||
|
||||
// Loop through and process all headers
|
||||
for (int i = 0; i < exeFsHeader.FileHeaders.Length; i++)
|
||||
{
|
||||
// Only decrypt a file if it's a code binary
|
||||
if (fileHeader == null || !fileHeader.IsCodeBinary())
|
||||
// Only attempt to process code binary files
|
||||
if (!cart.IsCodeBinary(index, i))
|
||||
continue;
|
||||
|
||||
// Get the file header
|
||||
var fileHeader = exeFsHeader.FileHeaders[i];
|
||||
if (fileHeader == null)
|
||||
continue;
|
||||
|
||||
// Create the ExeFS AES ciphers for this partition
|
||||
uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize()) / 0x10;
|
||||
byte[] exefsIVWithOffsetForHeader = AddToByteArray(cart.ExeFSIV(index), (int)ctroffset);
|
||||
uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize) / 0x10;
|
||||
byte[] exefsIVWithOffsetForHeader = Add(cart.ExeFSIV(index), ctroffset);
|
||||
var firstCipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey, exefsIVWithOffsetForHeader);
|
||||
var secondCipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey2C, exefsIVWithOffsetForHeader);
|
||||
|
||||
@@ -378,13 +395,12 @@ namespace NDecrypt.Core
|
||||
output.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
// Setup and perform the encryption
|
||||
uint exeFsSize = GetExeFSSize(cart, index);
|
||||
PerformAESOperation(exeFsSize,
|
||||
PerformAESOperation(fileHeader.FileSize,
|
||||
firstCipher,
|
||||
secondCipher,
|
||||
input,
|
||||
output,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting: {fileHeader.FileName}...{s}"));
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting - {fileHeader.FileName}...{s}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,17 +411,17 @@ namespace NDecrypt.Core
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private bool DecryptRomFS(Cart cart, int index, Stream input, Stream output)
|
||||
private bool DecryptRomFS(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Validate the RomFS
|
||||
uint romFsOffset = GetRomFSOffset(cart, index);
|
||||
if (romFsOffset == 0)
|
||||
uint romFsOffset = cart.GetRomFSOffset(index);
|
||||
if (romFsOffset == 0 || romFsOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint romFsSize = GetRomFSSize(cart, index);
|
||||
uint romFsSize = cart.GetRomFSSize(index);
|
||||
if (romFsSize == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
|
||||
@@ -424,7 +440,7 @@ namespace NDecrypt.Core
|
||||
cipher,
|
||||
input,
|
||||
output,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} RomFS: Decrypting: {s}"));
|
||||
(string s) => Console.WriteLine($"\rPartition {index} RomFS: Decrypting - {s}"));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -435,10 +451,10 @@ namespace NDecrypt.Core
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private static void UpdateDecryptCryptoAndMasks(Cart cart, int index, Stream output)
|
||||
private static void UpdateDecryptCryptoAndMasks(N3DS cart, int index, Stream output)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = GetPartitionOffset(cart, index);
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
|
||||
// Seek to the CryptoMethod location
|
||||
output.Seek(partitionOffset + 0x18B, SeekOrigin.Begin);
|
||||
@@ -451,7 +467,7 @@ namespace NDecrypt.Core
|
||||
output.Seek(partitionOffset + 0x18F, SeekOrigin.Begin);
|
||||
|
||||
// Write the new BitMasks flag
|
||||
BitMasks flag = cart.Partitions![index]!.Flags!.BitMasks;
|
||||
BitMasks flag = cart.GetBitMasks(index);
|
||||
flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
|
||||
flag |= BitMasks.NoCrypto;
|
||||
output.Write((byte)flag);
|
||||
@@ -469,10 +485,10 @@ namespace NDecrypt.Core
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void EncryptAllPartitions(Cart cart, bool force, Stream input, Stream output)
|
||||
private void EncryptAllPartitions(N3DS cart, bool force, Stream input, Stream output)
|
||||
{
|
||||
// Check the partitions table
|
||||
if (cart.Header?.PartitionsTable == null || cart.Partitions == null)
|
||||
if (cart.PartitionsTable == null || cart.Partitions == null)
|
||||
{
|
||||
Console.WriteLine("Invalid partitions table!");
|
||||
return;
|
||||
@@ -482,12 +498,21 @@ namespace NDecrypt.Core
|
||||
for (int p = 0; p < 8; p++)
|
||||
{
|
||||
// Check the partition exists
|
||||
if (cart.Partitions[p] == null)
|
||||
var partition = cart.Partitions[p];
|
||||
if (partition == null || partition.MagicID != NCCHMagicNumber)
|
||||
{
|
||||
Console.WriteLine($"Partition {p} Not found... Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check the partition has data
|
||||
var partitionEntry = cart.PartitionsTable[p];
|
||||
if (partitionEntry == null || partitionEntry.Length == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {p} No data... Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Encrypt the partition, if possible
|
||||
if (ShouldEncryptPartition(cart, p, force))
|
||||
EncryptPartition(cart, p, input, output);
|
||||
@@ -497,7 +522,7 @@ namespace NDecrypt.Core
|
||||
/// <summary>
|
||||
/// Determine if the current partition should be encrypted
|
||||
/// </summary>
|
||||
private static bool ShouldEncryptPartition(Cart cart, int index, bool force)
|
||||
private static bool ShouldEncryptPartition(N3DS cart, int index, bool force)
|
||||
{
|
||||
// If we're forcing the operation, tell the user
|
||||
if (force)
|
||||
@@ -506,7 +531,7 @@ namespace NDecrypt.Core
|
||||
return true;
|
||||
}
|
||||
// If we're not forcing the operation, check if the 'NoCrypto' bit is set
|
||||
else if (!cart.Partitions![index]!.Flags!.PossblyDecrypted())
|
||||
else if (!cart.PossiblyDecrypted(index))
|
||||
{
|
||||
Console.WriteLine($"Partition {index}: Already Encrypted?...");
|
||||
return false;
|
||||
@@ -523,7 +548,7 @@ namespace NDecrypt.Core
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void EncryptPartition(Cart cart, int index, Stream input, Stream output)
|
||||
private void EncryptPartition(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Determine the keys needed for this partition
|
||||
SetEncryptionKeys(cart, index);
|
||||
@@ -542,19 +567,21 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
private void SetEncryptionKeys(Cart cart, int index)
|
||||
private void SetEncryptionKeys(N3DS cart, int index)
|
||||
{
|
||||
// Get the partition
|
||||
var partition = cart.Partitions?[index];
|
||||
if (partition == null)
|
||||
return;
|
||||
|
||||
// Get the backup header
|
||||
var backupHeader = cart.BackupHeader;
|
||||
if (backupHeader?.Flags == null)
|
||||
return;
|
||||
|
||||
// Get partition-specific values
|
||||
byte[]? rsaSignature = partition.RSA2048Signature;
|
||||
|
||||
// Set the header to use based on mode
|
||||
var backupHeader = cart.CardInfoHeader!.InitialData!.BackupHeader;
|
||||
BitMasks masks = backupHeader!.Flags!.BitMasks;
|
||||
BitMasks masks = backupHeader.Flags.BitMasks;
|
||||
CryptoMethod method = backupHeader.Flags.CryptoMethod;
|
||||
|
||||
// Get the partition keys
|
||||
@@ -568,20 +595,20 @@ namespace NDecrypt.Core
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private bool EncryptExtendedHeader(Cart cart, int index, Stream input, Stream output)
|
||||
private bool EncryptExtendedHeader(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = GetPartitionOffset(cart, index);
|
||||
if (partitionOffset == 0)
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
if (partitionOffset == 0 || partitionOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
Console.WriteLine($"Partition {index} No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint extHeaderSize = GetExtendedHeaderSize(cart, index);
|
||||
uint extHeaderSize = cart.GetExtendedHeaderSize(index);
|
||||
if (extHeaderSize == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} RomFS: No Extended Header... Skipping...");
|
||||
Console.WriteLine($"Partition {index} No Extended Header... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -589,7 +616,7 @@ namespace NDecrypt.Core
|
||||
input.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
output.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {index} ExeFS: Encrypting: ExHeader");
|
||||
Console.WriteLine($"Partition {index}: Encrypting - ExHeader");
|
||||
|
||||
// Create the Plain AES cipher for this partition
|
||||
var cipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey2C, cart.PlainIV(index));
|
||||
@@ -612,46 +639,50 @@ namespace NDecrypt.Core
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private bool EncryptExeFS(Cart cart, int index, Stream input, Stream output)
|
||||
private bool EncryptExeFS(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Validate the ExeFS
|
||||
uint exeFsOffset = GetExeFSOffset(cart, index);
|
||||
if (exeFsOffset == 0)
|
||||
if (cart.ExeFSHeaders == null || index < 0 || index > cart.ExeFSHeaders.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint exeFsSize = GetExeFSSize(cart, index);
|
||||
if (exeFsSize == 0)
|
||||
// Get the ExeFS header
|
||||
var exefsHeader = cart.ExeFSHeaders[index];
|
||||
if (exefsHeader == null)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
Console.WriteLine($"Partition {index} ExeFS header does not exist. Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
var backupHeader = cart.CardInfoHeader!.InitialData!.BackupHeader;
|
||||
var backupHeader = cart.BackupHeader;
|
||||
if (backupHeader!.Flags!.CryptoMethod != CryptoMethod.Original)
|
||||
EncryptExeFSFileEntries(cart, index, input, output);
|
||||
|
||||
// Encrypt the filename table
|
||||
EncryptExeFSFilenameTable(cart, index, input, output);
|
||||
|
||||
// Get the ExeFS files offset
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
|
||||
|
||||
// Seek to the ExeFS
|
||||
input.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
input.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
int ctroffsetE = (int)(cart.MediaUnitSize() / 0x10);
|
||||
byte[] exefsIVWithOffset = AddToByteArray(cart.ExeFSIV(index), ctroffsetE);
|
||||
uint ctroffsetE = cart.MediaUnitSize / 0x10;
|
||||
byte[] exefsIVWithOffset = Add(cart.ExeFSIV(index), ctroffsetE);
|
||||
var cipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey2C, exefsIVWithOffset);
|
||||
|
||||
// Setup and perform the encryption
|
||||
uint exeFsSize = cart.GetExeFSSize(index) - cart.MediaUnitSize;
|
||||
PerformAESOperation(exeFsSize,
|
||||
cipher,
|
||||
input,
|
||||
output,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting: {s}"));
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting - {s}"));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -663,11 +694,11 @@ namespace NDecrypt.Core
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void EncryptExeFSFilenameTable(Cart cart, int index, Stream input, Stream output)
|
||||
private void EncryptExeFSFilenameTable(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Get ExeFS offset
|
||||
uint exeFsOffset = GetExeFSOffset(cart, index);
|
||||
if (exeFsOffset == 0)
|
||||
uint exeFsOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsOffset == 0 || exeFsOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
@@ -677,13 +708,15 @@ namespace NDecrypt.Core
|
||||
input.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {index} ExeFS: Encrypting: ExeFS Filename Table");
|
||||
Console.WriteLine($"Partition {index} ExeFS: Encrypting - ExeFS Filename Table");
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
var cipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey2C, cart.ExeFSIV(index));
|
||||
|
||||
// Process the filename table
|
||||
PerformAESOperation(cart.MediaUnitSize(), cipher, input, output, null);
|
||||
byte[] readBytes = input.ReadBytes((int)cart.MediaUnitSize);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
output.Write(processedBytes);
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
// In .NET 6.0, this operation is not picked up by the reader, so we have to force it to reload its buffer
|
||||
@@ -699,37 +732,42 @@ namespace NDecrypt.Core
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void EncryptExeFSFileEntries(Cart cart, int index, Stream input, Stream output)
|
||||
private void EncryptExeFSFileEntries(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Get ExeFS offset
|
||||
uint exeFsHeaderOffset = GetExeFSOffset(cart, index);
|
||||
if (exeFsHeaderOffset == 0)
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsHeaderOffset == 0 || exeFsHeaderOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get to the start of the files
|
||||
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize();
|
||||
input.Seek(exeFsHeaderOffset, SeekOrigin.Begin);
|
||||
var exefsHeader = N3DSDeserializer.ParseExeFSHeader(input);
|
||||
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
|
||||
|
||||
// If the header failed to read, log and return
|
||||
if (exefsHeader == null)
|
||||
var exeFsHeader = cart.ExeFSHeaders?[index];
|
||||
if (exeFsHeader?.FileHeaders == null)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS header could not be read. Skipping...");
|
||||
Console.WriteLine($"Partition {index} ExeFS header does not exist. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var fileHeader in exefsHeader.FileHeaders!)
|
||||
// Loop through and process all headers
|
||||
for (int i = 0; i < exeFsHeader.FileHeaders.Length; i++)
|
||||
{
|
||||
// Only decrypt a file if it's a code binary
|
||||
if (fileHeader == null || !fileHeader.IsCodeBinary())
|
||||
// Only attempt to process code binary files
|
||||
if (!cart.IsCodeBinary(index, i))
|
||||
continue;
|
||||
|
||||
// Get the file header
|
||||
var fileHeader = exeFsHeader.FileHeaders[i];
|
||||
if (fileHeader == null)
|
||||
continue;
|
||||
|
||||
// Create the ExeFS AES ciphers for this partition
|
||||
uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize()) / 0x10;
|
||||
byte[] exefsIVWithOffsetForHeader = AddToByteArray(cart.ExeFSIV(index), (int)ctroffset);
|
||||
uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize) / 0x10;
|
||||
byte[] exefsIVWithOffsetForHeader = Add(cart.ExeFSIV(index), ctroffset);
|
||||
var firstCipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey, exefsIVWithOffsetForHeader);
|
||||
var secondCipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey2C, exefsIVWithOffsetForHeader);
|
||||
|
||||
@@ -738,13 +776,12 @@ namespace NDecrypt.Core
|
||||
output.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
// Setup and perform the encryption
|
||||
uint exeFsSize = GetExeFSSize(cart, index);
|
||||
PerformAESOperation(exeFsSize,
|
||||
PerformAESOperation(fileHeader.FileSize,
|
||||
firstCipher,
|
||||
secondCipher,
|
||||
input,
|
||||
output,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting: {fileHeader.FileName}...{s}"));
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting - {fileHeader.FileName}...{s}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,17 +792,17 @@ namespace NDecrypt.Core
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private bool EncryptRomFS(Cart cart, int index, Stream input, Stream output)
|
||||
private bool EncryptRomFS(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Validate the RomFS
|
||||
uint romFsOffset = GetRomFSOffset(cart, index);
|
||||
if (romFsOffset == 0)
|
||||
uint romFsOffset = cart.GetRomFSOffset(index);
|
||||
if (romFsOffset == 0 || romFsOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint romFsSize = GetRomFSSize(cart, index);
|
||||
uint romFsSize = cart.GetRomFSSize(index);
|
||||
if (romFsSize == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
|
||||
@@ -779,7 +816,7 @@ namespace NDecrypt.Core
|
||||
// Force setting encryption keys for partitions 1 and above
|
||||
if (index > 0)
|
||||
{
|
||||
var backupHeader = cart.CardInfoHeader!.InitialData!.BackupHeader;
|
||||
var backupHeader = cart.BackupHeader;
|
||||
KeysMap[index].SetRomFSValues(backupHeader!.Flags!.BitMasks);
|
||||
}
|
||||
|
||||
@@ -791,7 +828,7 @@ namespace NDecrypt.Core
|
||||
cipher,
|
||||
input,
|
||||
output,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} RomFS: Encrypting: {s}"));
|
||||
(string s) => Console.WriteLine($"\rPartition {index} RomFS: Encrypting - {s}"));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -802,13 +839,15 @@ namespace NDecrypt.Core
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private static void UpdateEncryptCryptoAndMasks(Cart cart, int index, Stream output)
|
||||
private static void UpdateEncryptCryptoAndMasks(N3DS cart, int index, Stream output)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = GetPartitionOffset(cart, index);
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
|
||||
// Get the backup header
|
||||
var backupHeader = cart.CardInfoHeader!.InitialData!.BackupHeader;
|
||||
var backupHeader = cart.BackupHeader;
|
||||
if (backupHeader?.Flags == null)
|
||||
return;
|
||||
|
||||
// Seek to the CryptoMethod location
|
||||
output.Seek(partitionOffset + 0x18B, SeekOrigin.Begin);
|
||||
@@ -816,7 +855,7 @@ namespace NDecrypt.Core
|
||||
// Write the new CryptoMethod
|
||||
// - For partitions 1 and up, set crypto-method to 0x00
|
||||
// - If partition 0, restore crypto-method from backup flags
|
||||
byte cryptoMethod = index > 0 ? (byte)CryptoMethod.Original : (byte)backupHeader!.Flags!.CryptoMethod;
|
||||
byte cryptoMethod = index > 0 ? (byte)CryptoMethod.Original : (byte)backupHeader.Flags.CryptoMethod;
|
||||
output.Write(cryptoMethod);
|
||||
output.Flush();
|
||||
|
||||
@@ -824,9 +863,9 @@ namespace NDecrypt.Core
|
||||
output.Seek(partitionOffset + 0x18F, SeekOrigin.Begin);
|
||||
|
||||
// Write the new BitMasks flag
|
||||
BitMasks flag = cart.Partitions![index]!.Flags!.BitMasks;
|
||||
BitMasks flag = cart.GetBitMasks(index);
|
||||
flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF;
|
||||
flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & backupHeader!.Flags!.BitMasks;
|
||||
flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & backupHeader.Flags.BitMasks;
|
||||
output.Write((byte)flag);
|
||||
output.Flush();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>0.3.0</VersionPrefix>
|
||||
<VersionPrefix>0.3.1</VersionPrefix>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Title>NDecrypt</Title>
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace NDecrypt
|
||||
// Create reusable tools
|
||||
_tools[FileType.NDS] = new DSTool();
|
||||
_tools[FileType.N3DS] = new ThreeDSTool(development, decryptArgs);
|
||||
_tools[FileType.N3DSCIA] = new CIATool(development, decryptArgs);
|
||||
//_tools[FileType.N3DSCIA] = new CIATool(development, decryptArgs);
|
||||
|
||||
for (int i = start; i < args.Length; i++)
|
||||
{
|
||||
@@ -225,7 +225,7 @@ More than one path can be specified at a time.");
|
||||
FileType.NDSi => _tools[FileType.NDS],
|
||||
FileType.iQueDS => _tools[FileType.NDS],
|
||||
FileType.N3DS => _tools[FileType.N3DS],
|
||||
FileType.N3DSCIA => _tools[FileType.N3DSCIA],
|
||||
//FileType.N3DSCIA => _tools[FileType.N3DSCIA],
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
@@ -262,11 +262,11 @@ More than one path can be specified at a time.");
|
||||
Console.WriteLine("File recognized as Nintendo 3DS");
|
||||
return FileType.N3DS;
|
||||
}
|
||||
else if (filename.EndsWith(".cia", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteLine("File recognized as Nintendo 3DS CIA [CAUTION: NOT WORKING CURRENTLY]");
|
||||
return FileType.N3DSCIA;
|
||||
}
|
||||
// else if (filename.EndsWith(".cia", StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// Console.WriteLine("File recognized as Nintendo 3DS CIA [CAUTION: NOT WORKING CURRENTLY]");
|
||||
// return FileType.N3DSCIA;
|
||||
// }
|
||||
|
||||
Console.WriteLine($"Unrecognized file format for {filename}. Expected *.nds, *.srl, *.dsi, *.3ds");
|
||||
return FileType.NULL;
|
||||
|
||||
Reference in New Issue
Block a user