mirror of
https://github.com/SabreTools/NDecrypt.git
synced 2026-02-05 05:37:37 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e342448599 | ||
|
|
27431e05de | ||
|
|
56f8869d1d | ||
|
|
9e9efaf491 | ||
|
|
e8a0d706de | ||
|
|
9eb49d170f | ||
|
|
c31f95b85d | ||
|
|
dc1952d6f9 | ||
|
|
6ae797a6eb | ||
|
|
ed8e97aa0c | ||
|
|
cc16770126 | ||
|
|
8c186f969c | ||
|
|
ac542076aa | ||
|
|
94bebddda2 | ||
|
|
8c66c40b48 | ||
|
|
1fbacaffb0 | ||
|
|
0448682934 | ||
|
|
9e563785c9 | ||
|
|
7469c97c6b | ||
|
|
7c909af154 |
@@ -2,20 +2,12 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.Nitro;
|
||||
using SabreTools.Serialization.Deserializers;
|
||||
using SabreTools.Serialization.Wrappers;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
public class DSTool : ITool
|
||||
{
|
||||
#region Encryption process variables
|
||||
|
||||
private uint[] _cardHash = new uint[0x412];
|
||||
private uint[] _arg2 = new uint[3];
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Decryption args to use while processing
|
||||
/// </summary>
|
||||
@@ -29,302 +21,105 @@ namespace NDecrypt.Core
|
||||
#region Encrypt
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool EncryptFile(string filename, bool force)
|
||||
public bool EncryptFile(string input, string? output, bool force)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Open the read and write on the same file for inplace processing
|
||||
using var reader = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var writer = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
// If the output is provided, copy the input file
|
||||
if (output != null)
|
||||
File.Copy(input, output, overwrite: true);
|
||||
else
|
||||
output = input;
|
||||
|
||||
// Open the output file for processing
|
||||
using var reader = File.Open(output, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var commonHeader = Nitro.ParseCommonHeader(reader);
|
||||
if (commonHeader == null)
|
||||
var nitro = Nitro.Create(reader);
|
||||
if (nitro == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a DS or DSi Rom!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset state variables
|
||||
_cardHash = new uint[0x412];
|
||||
_arg2 = new uint[3];
|
||||
// Ensure the secure area was read
|
||||
if (nitro.SecureArea == null)
|
||||
{
|
||||
Console.WriteLine("Error: Invalid secure area!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Encrypt the secure area
|
||||
EncryptSecureArea(commonHeader, force, reader, writer);
|
||||
nitro.EncryptSecureArea(_decryptArgs.NitroEncryptionData, force);
|
||||
|
||||
// Write the encrypted secure area
|
||||
using var writer = File.Open(output, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
writer.Seek(0x4000, SeekOrigin.Begin);
|
||||
writer.Write(nitro.SecureArea);
|
||||
writer.Flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine($"An error has occurred. {filename} may be corrupted if it was partially processed.");
|
||||
Console.WriteLine($"An error has occurred. {output} may be corrupted if it was partially processed.");
|
||||
Console.WriteLine("Please check that the file was a valid DS or DSi file and try again.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt secure area in the DS/DSi file
|
||||
/// </summary>s
|
||||
/// <param name="commonHeader">CommonHeader representing the DS file header</param>
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void EncryptSecureArea(CommonHeader commonHeader, bool force, Stream reader, Stream writer)
|
||||
{
|
||||
// If we're forcing the operation, tell the user
|
||||
if (force)
|
||||
{
|
||||
Console.WriteLine("File is not verified due to force flag being set.");
|
||||
}
|
||||
// If we're not forcing the operation, check to see if we should be proceeding
|
||||
else
|
||||
{
|
||||
bool? isDecrypted = CheckIfDecrypted(reader);
|
||||
if (isDecrypted == null)
|
||||
{
|
||||
Console.WriteLine("File has an empty secure area, cannot proceed");
|
||||
return;
|
||||
}
|
||||
else if (!isDecrypted.Value)
|
||||
{
|
||||
Console.WriteLine("File is already encrypted");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
EncryptARM9(commonHeader, reader, writer);
|
||||
Console.WriteLine("File has been encrypted");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the secure ARM9 region of the file, if possible
|
||||
/// </summary>
|
||||
/// <param name="commonHeader">CommonHeader representing the DS header</param>
|
||||
/// <param name="encrypt">Indicates if the file should be encrypted or decrypted</param>
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void EncryptARM9(CommonHeader commonHeader, Stream reader, Stream writer)
|
||||
{
|
||||
// Seek to the beginning of the secure area
|
||||
reader.Seek(0x4000, SeekOrigin.Begin);
|
||||
writer.Seek(0x4000, SeekOrigin.Begin);
|
||||
|
||||
// Grab the first two blocks
|
||||
uint p0 = reader.ReadUInt32();
|
||||
uint p1 = reader.ReadUInt32();
|
||||
|
||||
// Perform the initialization steps
|
||||
Init1(commonHeader);
|
||||
_arg2[1] <<= 1;
|
||||
_arg2[2] >>= 1;
|
||||
Init2();
|
||||
|
||||
// Ensure alignment
|
||||
reader.Seek(0x4008, SeekOrigin.Begin);
|
||||
writer.Seek(0x4008, SeekOrigin.Begin);
|
||||
|
||||
// Loop throgh the main encryption step
|
||||
uint size = 0x800 - 8;
|
||||
while (size > 0)
|
||||
{
|
||||
p0 = reader.ReadUInt32();
|
||||
p1 = reader.ReadUInt32();
|
||||
|
||||
Encrypt(ref p1, ref p0);
|
||||
|
||||
writer.Write(p0);
|
||||
writer.Write(p1);
|
||||
|
||||
size -= 8;
|
||||
}
|
||||
|
||||
// Replace the header explicitly
|
||||
reader.Seek(0x4000, SeekOrigin.Begin);
|
||||
writer.Seek(0x4000, SeekOrigin.Begin);
|
||||
|
||||
p0 = reader.ReadUInt32();
|
||||
p1 = reader.ReadUInt32();
|
||||
|
||||
if (p0 == 0xE7FFDEFF && p1 == 0xE7FFDEFF)
|
||||
{
|
||||
p0 = Constants.MAGIC30;
|
||||
p1 = Constants.MAGIC34;
|
||||
}
|
||||
|
||||
Encrypt(ref p1, ref p0);
|
||||
Init1(commonHeader);
|
||||
Encrypt(ref p1, ref p0);
|
||||
|
||||
writer.Write(p0);
|
||||
writer.Write(p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform an encryption step
|
||||
/// </summary>
|
||||
/// <param name="arg1">First unsigned value to use in encryption</param>
|
||||
/// <param name="arg2">Second unsigned value to use in encryption</param>
|
||||
private void Encrypt(ref uint arg1, ref uint arg2)
|
||||
{
|
||||
uint a = arg1;
|
||||
uint b = arg2;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
uint c = _cardHash[i] ^ a;
|
||||
a = b ^ Lookup(c);
|
||||
b = c;
|
||||
}
|
||||
|
||||
arg2 = a ^ _cardHash[16];
|
||||
arg1 = b ^ _cardHash[17];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Decrypt
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool DecryptFile(string filename, bool force)
|
||||
public bool DecryptFile(string input, string? output, bool force)
|
||||
{
|
||||
try
|
||||
{
|
||||
// If the output is provided, copy the input file
|
||||
if (output != null)
|
||||
File.Copy(input, output, overwrite: true);
|
||||
else
|
||||
output = input;
|
||||
|
||||
// Open the read and write on the same file for inplace processing
|
||||
using var reader = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var writer = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
using var reader = File.Open(output, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var commonHeader = Nitro.ParseCommonHeader(reader);
|
||||
if (commonHeader == null)
|
||||
var nitro = Nitro.Create(reader);
|
||||
if (nitro == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a DS or DSi Rom!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset state variables
|
||||
_cardHash = new uint[0x412];
|
||||
_arg2 = new uint[3];
|
||||
// Ensure the secure area was read
|
||||
if (nitro.SecureArea == null)
|
||||
{
|
||||
Console.WriteLine("Error: Invalid secure area!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decrypt the secure area
|
||||
DecryptSecureArea(commonHeader, force, reader, writer);
|
||||
nitro.DecryptSecureArea(_decryptArgs.NitroEncryptionData, force);
|
||||
|
||||
// Write the decrypted secure area
|
||||
using var writer = File.Open(output, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
writer.Seek(0x4000, SeekOrigin.Begin);
|
||||
writer.Write(nitro.SecureArea);
|
||||
writer.Flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine($"An error has occurred. {filename} may be corrupted if it was partially processed.");
|
||||
Console.WriteLine($"An error has occurred. {output} may be corrupted if it was partially processed.");
|
||||
Console.WriteLine("Please check that the file was a valid DS or DSi file and try again.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt secure area in the DS/DSi file
|
||||
/// </summary>s
|
||||
/// <param name="commonHeader">CommonHeader representing the DS file header</param>
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void DecryptSecureArea(CommonHeader commonHeader, bool force, Stream reader, Stream writer)
|
||||
{
|
||||
// If we're forcing the operation, tell the user
|
||||
if (force)
|
||||
{
|
||||
Console.WriteLine("File is not verified due to force flag being set.");
|
||||
}
|
||||
// If we're not forcing the operation, check to see if we should be proceeding
|
||||
else
|
||||
{
|
||||
bool? isDecrypted = CheckIfDecrypted(reader);
|
||||
if (isDecrypted == null)
|
||||
{
|
||||
Console.WriteLine("File has an empty secure area, cannot proceed");
|
||||
return;
|
||||
}
|
||||
else if (isDecrypted.Value)
|
||||
{
|
||||
Console.WriteLine("File is already decrypted");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DecryptARM9(commonHeader, reader, writer);
|
||||
Console.WriteLine("File has been decrypted");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the secure ARM9 region of the file, if possible
|
||||
/// </summary>
|
||||
/// <param name="commonHeader">CommonHeader representing the DS header</param>
|
||||
/// <param name="">Indicates if the file should be encrypted or decrypted</param>
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void DecryptARM9(CommonHeader commonHeader, Stream reader, Stream writer)
|
||||
{
|
||||
// Seek to the beginning of the secure area
|
||||
reader.Seek(0x4000, SeekOrigin.Begin);
|
||||
writer.Seek(0x4000, SeekOrigin.Begin);
|
||||
|
||||
// Grab the first two blocks
|
||||
uint p0 = reader.ReadUInt32();
|
||||
uint p1 = reader.ReadUInt32();
|
||||
|
||||
// Perform the initialization steps
|
||||
Init1(commonHeader);
|
||||
Decrypt(ref p1, ref p0);
|
||||
_arg2[1] <<= 1;
|
||||
_arg2[2] >>= 1;
|
||||
Init2();
|
||||
|
||||
// Set the proper flags
|
||||
Decrypt(ref p1, ref p0);
|
||||
if (p0 == Constants.MAGIC30 && p1 == Constants.MAGIC34)
|
||||
{
|
||||
p0 = 0xE7FFDEFF;
|
||||
p1 = 0xE7FFDEFF;
|
||||
}
|
||||
|
||||
writer.Write(p0);
|
||||
writer.Write(p1);
|
||||
|
||||
// Ensure alignment
|
||||
reader.Seek(0x4008, SeekOrigin.Begin);
|
||||
writer.Seek(0x4008, SeekOrigin.Begin);
|
||||
|
||||
// Loop throgh the main encryption step
|
||||
uint size = 0x800 - 8;
|
||||
while (size > 0)
|
||||
{
|
||||
p0 = reader.ReadUInt32();
|
||||
p1 = reader.ReadUInt32();
|
||||
|
||||
Decrypt(ref p1, ref p0);
|
||||
|
||||
writer.Write(p0);
|
||||
writer.Write(p1);
|
||||
|
||||
size -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a decryption step
|
||||
/// </summary>
|
||||
/// <param name="arg1">First unsigned value to use in decryption</param>
|
||||
/// <param name="arg2">Second unsigned value to use in decryption</param>
|
||||
private void Decrypt(ref uint arg1, ref uint arg2)
|
||||
{
|
||||
uint a = arg1;
|
||||
uint b = arg2;
|
||||
for (int i = 17; i > 1; i--)
|
||||
{
|
||||
uint c = _cardHash[i] ^ a;
|
||||
a = b ^ Lookup(c);
|
||||
b = c;
|
||||
}
|
||||
|
||||
arg1 = b ^ _cardHash[0];
|
||||
arg2 = a ^ _cardHash[1];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Info
|
||||
@@ -337,12 +132,17 @@ namespace NDecrypt.Core
|
||||
// Open the file for reading
|
||||
using var input = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = Nitro.Create(input);
|
||||
if (cart?.Model == null)
|
||||
return "Error: Not a DS/DSi cart image!";
|
||||
|
||||
// Get a string builder for the status
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("\tSecure Area: ");
|
||||
|
||||
// Get the encryption status
|
||||
bool? decrypted = CheckIfDecrypted(input);
|
||||
bool? decrypted = cart.CheckIfDecrypted(out _);
|
||||
if (decrypted == null)
|
||||
sb.Append("Empty");
|
||||
else if (decrypted == true)
|
||||
@@ -362,181 +162,5 @@ namespace NDecrypt.Core
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Common
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the current file is already decrypted or not (or has an empty secure area)
|
||||
/// </summary>
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <returns>True if the file has known values for a decrypted file, null if it's empty, false otherwise</returns>
|
||||
private static bool? CheckIfDecrypted(Stream reader)
|
||||
{
|
||||
reader.Seek(0x4000, SeekOrigin.Begin);
|
||||
uint firstValue = reader.ReadUInt32();
|
||||
uint secondValue = reader.ReadUInt32();
|
||||
|
||||
// Empty secure area standard
|
||||
if (firstValue == 0x00000000 && secondValue == 0x00000000)
|
||||
{
|
||||
Console.WriteLine("Empty secure area found. Cannot be encrypted or decrypted.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Improperly decrypted empty secure area (decrypt empty with woodsec)
|
||||
else if ((firstValue == 0xE386C397 && secondValue == 0x82775B7E)
|
||||
|| (firstValue == 0xF98415B8 && secondValue == 0x698068FC)
|
||||
|| (firstValue == 0xA71329EE && secondValue == 0x2A1D4C38)
|
||||
|| (firstValue == 0xC44DCC48 && secondValue == 0x38B6F8CB)
|
||||
|| (firstValue == 0x3A9323B5 && secondValue == 0xC0387241))
|
||||
{
|
||||
Console.WriteLine("Improperly decrypted empty secure area found. Should be encrypted to get proper value.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Improperly encrypted empty secure area (encrypt empty with woodsec)
|
||||
else if ((firstValue == 0x4BCE88BE && secondValue == 0xD3662DD1)
|
||||
|| (firstValue == 0x2543C534 && secondValue == 0xCC4BE38E))
|
||||
{
|
||||
Console.WriteLine("Improperly encrypted empty secure area found. Should be decrypted to get proper value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Properly decrypted nonstandard value (mastering issue)
|
||||
else if ((firstValue == 0xD0D48B67 && secondValue == 0x39392F23) // Dragon Quest 5 (EU)
|
||||
|| (firstValue == 0x014A191A && secondValue == 0xA5C470B9) // Dragon Quest 5 (USA)
|
||||
|| (firstValue == 0x7829BC8D && secondValue == 0x9968EF44) // Dragon Quest 5 (JP)
|
||||
|| (firstValue == 0xC4A15AB8 && secondValue == 0xD2E667C8) // Prince of Persia (EU)
|
||||
|| (firstValue == 0xD5E97D20 && secondValue == 0x21B2A159)) // Prince of Persia (USA)
|
||||
{
|
||||
Console.WriteLine("Decrypted secure area for known, nonstandard value found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Properly decrypted prototype value
|
||||
else if (firstValue == 0xBA35F813 && secondValue == 0xB691AAE8)
|
||||
{
|
||||
Console.WriteLine("Decrypted secure area for prototype found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Strange, unlicenced values that can't determine decryption state
|
||||
else if ((firstValue == 0xE1D830D8 && secondValue == 0xE3530000) // Aquela Ball (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xDC002A02 && secondValue == 0x2900E612) // Bahlz (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE1A03BA3 && secondValue == 0xE2011CFF) // Battle Ship (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE3A01001 && secondValue == 0xE1A02001) // Breakout!! DS (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE793200C && secondValue == 0xE4812004) // Bubble Fusion (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE583C0DC && secondValue == 0x0A00000B) // Carre Rouge (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0x0202453C && secondValue == 0x02060164) // ChainReaction (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xEBFFF218 && secondValue == 0xE31000FF) // Collection (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0x4A6CD003 && secondValue == 0x425B2301) // DiggerDS (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE3A00001 && secondValue == 0xEBFFFF8C) // Double Skill (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0x21043701 && secondValue == 0x45BA448C) // DSChess (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE59D0010 && secondValue == 0xE0833000) // Hexa-Virus (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE5C3A006 && secondValue == 0xE5C39007) // Invasion (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE1D920F4 && secondValue == 0xE06A3000) // JoggleDS (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE59F32EC && secondValue == 0xE5DD7011) // London Underground (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE08A3503 && secondValue == 0xE1D3C4B8) // NumberMinds (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE1A0C001 && secondValue == 0xE0031001) // Paddle Battle (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE1A03005 && secondValue == 0xE88D0180) // Pop the Balls (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE8BD4030 && secondValue == 0xE12FFF1E) // Solitaire DS (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE0A88006 && secondValue == 0xE1A00003) // Squash DS (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE51F3478 && secondValue == 0xEB004A02) // Super Snake DS (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0x1C200052 && secondValue == 0xFD12F013) // Tales of Dagur (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0x601F491E && secondValue == 0x041B880B) // Tetris & Touch (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE1A03843 && secondValue == 0xE0000293) // Tic Tac Toe (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0xE3530000 && secondValue == 0x13A03003) // Warrior Training (World) (Unl) (Datel Games n' Music)
|
||||
|| (firstValue == 0x02054A80 && secondValue == 0x02054B80)) // Zi (World) (Unl) (Datel Games n' Music)
|
||||
{
|
||||
Console.WriteLine("Unlicensed invalid value found. Unknown if encrypted or decrypted.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Standard decryption values
|
||||
return firstValue == 0xE7FFDEFF && secondValue == 0xE7FFDEFF;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// First common initialization step
|
||||
/// </summary>
|
||||
/// <param name="commonHeader">CommonHeader representing the DS file</param>
|
||||
private void Init1(CommonHeader commonHeader)
|
||||
{
|
||||
Buffer.BlockCopy(_decryptArgs.NitroEncryptionData, 0, _cardHash, 0, 4 * (1024 + 18));
|
||||
_arg2 = [commonHeader.GameCode, commonHeader.GameCode >> 1, commonHeader.GameCode << 1];
|
||||
Init2();
|
||||
Init2();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Second common initialization step
|
||||
/// </summary>
|
||||
private void Init2()
|
||||
{
|
||||
Encrypt(ref _arg2[2], ref _arg2[1]);
|
||||
Encrypt(ref _arg2[1], ref _arg2[0]);
|
||||
|
||||
byte[] allBytes =[.. BitConverter.GetBytes(_arg2[0]),
|
||||
.. BitConverter.GetBytes(_arg2[1]),
|
||||
.. BitConverter.GetBytes(_arg2[2])];
|
||||
|
||||
UpdateHashtable(allBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lookup the value from the hashtable
|
||||
/// </summary>
|
||||
/// <param name="v">Value to lookup in the hashtable</param>
|
||||
/// <returns>Processed value through the hashtable</returns>
|
||||
private uint Lookup(uint v)
|
||||
{
|
||||
uint a = (v >> 24) & 0xFF;
|
||||
uint b = (v >> 16) & 0xFF;
|
||||
uint c = (v >> 8) & 0xFF;
|
||||
uint d = (v >> 0) & 0xFF;
|
||||
|
||||
a = _cardHash[a + 18 + 0];
|
||||
b = _cardHash[b + 18 + 256];
|
||||
c = _cardHash[c + 18 + 512];
|
||||
d = _cardHash[d + 18 + 768];
|
||||
|
||||
return d + (c ^ (b + a));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the hashtable
|
||||
/// </summary>
|
||||
/// <param name="arg1">Value to update the hashtable with</param>
|
||||
private void UpdateHashtable(byte[] arg1)
|
||||
{
|
||||
for (int j = 0; j < 18; j++)
|
||||
{
|
||||
uint r3 = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
r3 <<= 8;
|
||||
r3 |= arg1[(j * 4 + i) & 7];
|
||||
}
|
||||
|
||||
_cardHash[j] ^= r3;
|
||||
}
|
||||
|
||||
uint tmp1 = 0;
|
||||
uint tmp2 = 0;
|
||||
for (int i = 0; i < 18; i += 2)
|
||||
{
|
||||
Encrypt(ref tmp1, ref tmp2);
|
||||
_cardHash[i + 0] = tmp1;
|
||||
_cardHash[i + 1] = tmp2;
|
||||
}
|
||||
for (int i = 0; i < 0x400; i += 2)
|
||||
{
|
||||
Encrypt(ref tmp1, ref tmp2);
|
||||
_cardHash[i + 18 + 0] = tmp1;
|
||||
_cardHash[i + 18 + 1] = tmp2;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.IO.Readers;
|
||||
using SabreTools.Matching;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
@@ -24,273 +23,6 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
public byte[] NitroEncryptionData { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Encryption data taken from woodsec
|
||||
/// </summary>
|
||||
private static readonly byte[] _nitroEncryptionData = [
|
||||
0x99,0xD5,0x20,0x5F,0x57,0x44,0xF5,0xB9,0x6E,0x19,0xA4,0xD9,0x9E,0x6A,0x5A,0x94,
|
||||
0xD8,0xAE,0xF1,0xEB,0x41,0x75,0xE2,0x3A,0x93,0x82,0xD0,0x32,0x33,0xEE,0x31,0xD5,
|
||||
0xCC,0x57,0x61,0x9A,0x37,0x06,0xA2,0x1B,0x79,0x39,0x72,0xF5,0x55,0xAE,0xF6,0xBE,
|
||||
0x5F,0x1B,0x69,0xFB,0xE5,0x9D,0xF1,0xE9,0xCE,0x2C,0xD9,0xA1,0x5E,0x32,0x05,0xE6,
|
||||
0xFE,0xD3,0xFE,0xCF,0xD4,0x62,0x04,0x0D,0x8B,0xF5,0xEC,0xB7,0x2B,0x60,0x79,0xBB,
|
||||
0x12,0x95,0x31,0x0D,0x6E,0x3F,0xDA,0x2B,0x88,0x84,0xF0,0xF1,0x3D,0x12,0x7E,0x25,
|
||||
0x45,0x22,0xF1,0xBB,0x24,0x06,0x1A,0x06,0x11,0xAD,0xDF,0x28,0x8B,0x64,0x81,0x34,
|
||||
0x2B,0xEB,0x33,0x29,0x99,0xAA,0xF2,0xBD,0x9C,0x14,0x95,0x9D,0x9F,0xF7,0xF5,0x8C,
|
||||
0x72,0x97,0xA1,0x29,0x9D,0xD1,0x5F,0xCF,0x66,0x4D,0x07,0x1A,0xDE,0xD3,0x4A,0x4B,
|
||||
0x85,0xC9,0xA7,0xA3,0x17,0x95,0x05,0x3A,0x3D,0x49,0x0A,0xBF,0x0A,0x89,0x8B,0xA2,
|
||||
0x4A,0x82,0x49,0xDD,0x27,0x90,0xF1,0x0B,0xE9,0xEB,0x1C,0x6A,0x83,0x76,0x45,0x05,
|
||||
0xBA,0x81,0x70,0x61,0x17,0x3F,0x4B,0xDE,0xAE,0xCF,0xAB,0x39,0x57,0xF2,0x3A,0x56,
|
||||
0x48,0x11,0xAD,0x8A,0x40,0xE1,0x45,0x3F,0xFA,0x9B,0x02,0x54,0xCA,0xA6,0x93,0xFB,
|
||||
0xEF,0x4D,0xFE,0x6F,0xA3,0xD8,0x87,0x9C,0x08,0xBA,0xD5,0x48,0x6A,0x8D,0x2D,0xFD,
|
||||
0x6E,0x15,0xF8,0x74,0xBD,0xBE,0x52,0x8B,0x18,0x22,0x8A,0x9E,0xFB,0x74,0x37,0x07,
|
||||
0x1B,0x36,0x6C,0x4A,0x19,0xBA,0x42,0x62,0xB9,0x79,0x91,0x10,0x7B,0x67,0x65,0x96,
|
||||
0xFE,0x02,0x23,0xE8,0xEE,0x99,0x8C,0x77,0x3E,0x5C,0x86,0x64,0x4D,0x6D,0x78,0x86,
|
||||
0xA5,0x4F,0x65,0xE2,0x1E,0xB2,0xDF,0x5A,0x0A,0xD0,0x7E,0x08,0x14,0xB0,0x71,0xAC,
|
||||
0xBD,0xDB,0x83,0x1C,0xB9,0xD7,0xA1,0x62,0xCD,0xC6,0x63,0x7C,0x52,0x69,0xC3,0xE6,
|
||||
0xBF,0x75,0xCE,0x12,0x44,0x5D,0x21,0x04,0xFA,0xFB,0xD3,0x3C,0x38,0x11,0x63,0xD4,
|
||||
0x95,0x85,0x41,0x49,0x46,0x09,0xF2,0x08,0x43,0x11,0xDC,0x1F,0x76,0xC0,0x15,0x6D,
|
||||
0x1F,0x3C,0x63,0x70,0xEA,0x87,0x80,0x6C,0xC3,0xBD,0x63,0x8B,0xC2,0x37,0x21,0x37,
|
||||
0xDC,0xEE,0x09,0x23,0x2E,0x37,0x6A,0x4D,0x73,0x90,0xF7,0x50,0x30,0xAC,0x1C,0x92,
|
||||
0x04,0x10,0x23,0x91,0x4F,0xD2,0x07,0xAA,0x68,0x3E,0x4F,0x9A,0xC9,0x64,0x60,0x6A,
|
||||
0xC8,0x14,0x21,0xF3,0xD6,0x22,0x41,0x12,0x44,0x24,0xCF,0xE6,0x8A,0x56,0xDD,0x0D,
|
||||
0x53,0x4D,0xE1,0x85,0x1E,0x8C,0x52,0x5A,0x9C,0x19,0x84,0xC2,0x03,0x57,0xF1,0x6F,
|
||||
0xE3,0x00,0xBE,0x58,0xF6,0x4C,0xED,0xD5,0x21,0x64,0x9C,0x1F,0xBE,0x55,0x03,0x3C,
|
||||
0x4A,0xDC,0xFF,0xAA,0xC9,0xDA,0xE0,0x5D,0x5E,0xBF,0xE6,0xDE,0xF5,0xD8,0xB1,0xF8,
|
||||
0xFF,0x36,0xB3,0xB9,0x62,0x67,0x95,0xDB,0x31,0x5F,0x37,0xED,0x4C,0x70,0x67,0x99,
|
||||
0x90,0xB5,0x18,0x31,0x6C,0x3D,0x99,0x99,0xE4,0x42,0xDA,0xD3,0x25,0x42,0x13,0xA0,
|
||||
0xAE,0xD7,0x70,0x6C,0xB1,0x55,0xCF,0xC7,0xD7,0x46,0xD5,0x43,0x61,0x17,0x3D,0x44,
|
||||
0x28,0xE9,0x33,0x85,0xD5,0xD0,0xA2,0x93,0xAA,0x25,0x12,0x1F,0xFB,0xC5,0x0B,0x46,
|
||||
0xF5,0x97,0x76,0x56,0x45,0xA6,0xBE,0x87,0xB1,0x94,0x6B,0xE8,0xB1,0xFE,0x33,0x99,
|
||||
0xAE,0x1F,0x3E,0x6C,0x39,0x71,0x1D,0x09,0x00,0x90,0x37,0xE4,0x10,0x3E,0x75,0x74,
|
||||
0xFF,0x8C,0x83,0x3B,0xB0,0xF1,0xB0,0xF9,0x01,0x05,0x47,0x42,0x95,0xF1,0xD6,0xAC,
|
||||
0x7E,0x38,0xE6,0x9E,0x95,0x74,0x26,0x3F,0xB4,0x68,0x50,0x18,0xD0,0x43,0x30,0xB4,
|
||||
0x4C,0x4B,0xE3,0x68,0xBF,0xE5,0x4D,0xB6,0x95,0x8B,0x0A,0xA0,0x74,0x25,0x32,0x77,
|
||||
0xCF,0xA1,0xF7,0x2C,0xD8,0x71,0x13,0x5A,0xAB,0xEA,0xC9,0x51,0xE8,0x0D,0xEE,0xEF,
|
||||
0xE9,0x93,0x7E,0x19,0xA7,0x1E,0x43,0x38,0x81,0x16,0x2C,0xA1,0x48,0xE3,0x73,0xCC,
|
||||
0x29,0x21,0x6C,0xD3,0x5D,0xCE,0xA0,0xD9,0x61,0x71,0x43,0xA0,0x15,0x13,0xB5,0x64,
|
||||
0x92,0xCF,0x2A,0x19,0xDC,0xAD,0xB7,0xA5,0x9F,0x86,0x65,0xF8,0x1A,0x9F,0xE7,0xFB,
|
||||
0xF7,0xFD,0xB8,0x13,0x6C,0x27,0xDB,0x6F,0xDF,0x35,0x1C,0xF7,0x8D,0x2C,0x5B,0x9B,
|
||||
0x12,0xAB,0x38,0x64,0x06,0xCC,0xDE,0x31,0xE8,0x4E,0x75,0x11,0x64,0xE3,0xFA,0xEA,
|
||||
0xEB,0x34,0x54,0xC2,0xAD,0x3F,0x34,0xEB,0x93,0x2C,0x7D,0x26,0x36,0x9D,0x56,0xF3,
|
||||
0x5A,0xE1,0xF6,0xB3,0x98,0x63,0x4A,0x9E,0x32,0x83,0xE4,0x9A,0x84,0x60,0x7D,0x90,
|
||||
0x2E,0x13,0x0E,0xEE,0x93,0x4B,0x36,0xA2,0x85,0xEC,0x16,0x38,0xE8,0x88,0x06,0x02,
|
||||
0xBF,0xF0,0xA0,0x3A,0xED,0xD7,0x6A,0x9A,0x73,0xE1,0x57,0xCF,0xF8,0x44,0xB8,0xDC,
|
||||
0x2E,0x23,0x59,0xD1,0xDF,0x95,0x52,0x71,0x99,0x61,0xA0,0x4B,0xD5,0x7F,0x6E,0x78,
|
||||
0xBA,0xA9,0xC5,0x30,0xD3,0x40,0x86,0x32,0x9D,0x32,0x0C,0x9C,0x37,0xB7,0x02,0x2F,
|
||||
0xBA,0x54,0x98,0xA9,0xC4,0x13,0x04,0xC9,0x8D,0xBE,0xC8,0xE7,0x5D,0x97,0x50,0x2E,
|
||||
0x93,0xD6,0x22,0x59,0x0C,0x27,0xBC,0x22,0x92,0xE0,0xA7,0x20,0x0F,0x93,0x6F,0x7F,
|
||||
0x4C,0x9F,0xD3,0xB5,0xA6,0x2A,0x0B,0x74,0x67,0x49,0x7D,0x10,0x26,0xCB,0xD1,0xC5,
|
||||
0x86,0x71,0xE7,0x8C,0xA0,0x9C,0xE9,0x5B,0xB2,0x1A,0xF6,0x01,0xEE,0x8C,0x9E,0x5E,
|
||||
0x83,0xF2,0x1A,0xDB,0xE6,0xE5,0xEA,0x84,0x59,0x76,0xD2,0x7C,0xF6,0x8D,0xA5,0x49,
|
||||
0x36,0x48,0xC2,0x16,0x52,0xBB,0x83,0xA3,0x74,0xB9,0x07,0x0C,0x3B,0xFF,0x61,0x28,
|
||||
0xE1,0x61,0xE9,0xE4,0xEF,0x6E,0x15,0xAA,0x4E,0xBA,0xE8,0x5D,0x05,0x96,0xBB,0x32,
|
||||
0x56,0xB0,0xFB,0x72,0x52,0x0F,0x0E,0xC8,0x42,0x25,0x65,0x76,0x89,0xAF,0xF2,0xDE,
|
||||
0x10,0x27,0xF0,0x01,0x4B,0x74,0xA7,0x97,0x07,0xD5,0x26,0x54,0x54,0x09,0x1F,0x82,
|
||||
0x0A,0x86,0x7D,0x30,0x39,0x0E,0xB3,0x26,0x9B,0x0B,0x57,0xBB,0x36,0x06,0x31,0xAF,
|
||||
0xFD,0x79,0xFC,0xD9,0x30,0x10,0x2B,0x0C,0xB3,0xE1,0x9B,0xD7,0x7B,0xDC,0x5F,0xEF,
|
||||
0xD2,0xF8,0x13,0x45,0x4D,0x47,0x75,0xBD,0x46,0x96,0x3C,0x7E,0x75,0xF3,0x3E,0xB5,
|
||||
0x67,0xC5,0x9A,0x3B,0xB0,0x5B,0x29,0x6B,0xDE,0x80,0x5B,0xC8,0x15,0x05,0xB1,0x31,
|
||||
0xB6,0xCE,0x49,0xDD,0xAD,0x84,0xB5,0xAE,0x60,0xDC,0x67,0x31,0x34,0x30,0xFE,0x4E,
|
||||
0xBD,0x80,0x2F,0xA6,0xBF,0x63,0x39,0x21,0x86,0xD9,0x35,0x7F,0x16,0x68,0x22,0x05,
|
||||
0x54,0xE9,0x90,0x26,0x8C,0x07,0x6C,0x51,0xA4,0x31,0x55,0xD7,0x09,0x07,0xA8,0x3E,
|
||||
0x2E,0x53,0x66,0xC1,0xF8,0xF2,0x7B,0xC4,0xF2,0x58,0xCF,0xF1,0x87,0xC5,0xA2,0xE7,
|
||||
0x27,0x8F,0x30,0x87,0x58,0xA0,0x64,0x62,0x23,0x18,0xB9,0x88,0x7C,0xFA,0xCE,0xC4,
|
||||
0x98,0xAE,0xAD,0x17,0xCC,0x4A,0x5B,0xF3,0xE9,0x48,0xD5,0x56,0xD3,0x0D,0xF2,0xC8,
|
||||
0x92,0x73,0x8C,0xDB,0xD7,0x2F,0x56,0xAC,0x81,0xF9,0x92,0x69,0x4D,0xC6,0x32,0xF6,
|
||||
0xE6,0xC0,0x8D,0x21,0xE2,0x76,0x80,0x61,0x11,0xBC,0xDC,0x6C,0x93,0xAF,0x19,0x69,
|
||||
0x9B,0xD0,0xBF,0xB9,0x31,0x9F,0x02,0x67,0xA3,0x51,0xEE,0x83,0x06,0x22,0x7B,0x0C,
|
||||
0xAB,0x49,0x42,0x40,0xB8,0xD5,0x01,0x7D,0xCE,0x5E,0xF7,0x55,0x53,0x39,0xC5,0x99,
|
||||
0x46,0xD8,0x87,0x9F,0xBA,0xF7,0x64,0xB4,0xE3,0x9A,0xFA,0xA1,0x6D,0x90,0x68,0x10,
|
||||
0x30,0xCA,0x8A,0x54,0xA7,0x9F,0x60,0xC3,0x19,0xF5,0x6B,0x0D,0x7A,0x51,0x98,0xE6,
|
||||
0x98,0x43,0x51,0xB4,0xD6,0x35,0xE9,0x4F,0xC3,0xDF,0x0F,0x7B,0xD6,0x2F,0x5C,0xBD,
|
||||
0x3A,0x15,0x61,0x19,0xF1,0x4B,0xCB,0xAA,0xDC,0x6D,0x64,0xC9,0xD3,0xC6,0x1E,0x56,
|
||||
0xEF,0x38,0x4C,0x50,0x71,0x86,0x75,0xCC,0x0D,0x0D,0x4E,0xE9,0x28,0xF6,0x06,0x5D,
|
||||
0x70,0x1B,0xAA,0xD3,0x45,0xCF,0xA8,0x39,0xAC,0x95,0xA6,0x2E,0xB4,0xE4,0x22,0xD4,
|
||||
0x74,0xA8,0x37,0x5F,0x48,0x7A,0x04,0xCC,0xA5,0x4C,0x40,0xD8,0x28,0xB4,0x28,0x08,
|
||||
0x0D,0x1C,0x72,0x52,0x41,0xF0,0x7D,0x47,0x19,0x3A,0x53,0x4E,0x58,0x84,0x62,0x6B,
|
||||
0x93,0xB5,0x8A,0x81,0x21,0x4E,0x0D,0xDC,0xB4,0x3F,0xA2,0xC6,0xFC,0xC9,0x2B,0x40,
|
||||
0xDA,0x38,0x04,0xE9,0x5E,0x5A,0x86,0x6B,0x0C,0x22,0x25,0x85,0x68,0x11,0x8D,0x7C,
|
||||
0x92,0x1D,0x95,0x55,0x4D,0xAB,0x8E,0xBB,0xDA,0xA6,0xE6,0xB7,0x51,0xB6,0x32,0x5A,
|
||||
0x05,0x41,0xDD,0x05,0x2A,0x0A,0x56,0x50,0x91,0x17,0x47,0xCC,0xC9,0xE6,0x7E,0xB5,
|
||||
0x61,0x4A,0xDB,0x73,0x67,0x51,0xC8,0x33,0xF5,0xDA,0x6E,0x74,0x2E,0x54,0xC3,0x37,
|
||||
0x0D,0x6D,0xAF,0x08,0xE8,0x15,0x8A,0x5F,0xE2,0x59,0x21,0xCD,0xA8,0xDE,0x0C,0x06,
|
||||
0x5A,0x77,0x6B,0x5F,0xDB,0x18,0x65,0x3E,0xC8,0x50,0xDE,0x78,0xE0,0xB8,0x82,0xB3,
|
||||
0x5D,0x4E,0x72,0x32,0x07,0x4F,0xC1,0x34,0x23,0xBA,0x96,0xB7,0x67,0x4E,0xA4,0x28,
|
||||
0x1E,0x34,0x62,0xEB,0x2D,0x6A,0x70,0xE9,0x2F,0x42,0xC4,0x70,0x4E,0x5A,0x31,0x9C,
|
||||
0xF9,0x5B,0x47,0x28,0xAA,0xDA,0x71,0x6F,0x38,0x1F,0xB3,0x78,0xC4,0x92,0x6B,0x1C,
|
||||
0x9E,0xF6,0x35,0x9A,0xB7,0x4D,0x0E,0xBF,0xCC,0x18,0x29,0x41,0x03,0x48,0x35,0x5D,
|
||||
0x55,0xD0,0x2B,0xC6,0x29,0xAF,0x5C,0x60,0x74,0x69,0x8E,0x5E,0x9B,0x7C,0xD4,0xBD,
|
||||
0x7B,0x44,0x64,0x7D,0x3F,0x92,0x5D,0x69,0xB6,0x1F,0x00,0x4B,0xD4,0x83,0x35,0xCF,
|
||||
0x7E,0x64,0x4E,0x17,0xAE,0x8D,0xD5,0x2E,0x9A,0x28,0x12,0x4E,0x2E,0x2B,0x49,0x08,
|
||||
0x5C,0xAE,0xC6,0x46,0x85,0xAE,0x41,0x61,0x1E,0x6F,0x82,0xD2,0x51,0x37,0x16,0x1F,
|
||||
0x0B,0xF6,0x59,0xA4,0x9A,0xCA,0x5A,0xAF,0x0D,0xD4,0x33,0x8B,0x20,0x63,0xF1,0x84,
|
||||
0x80,0x5C,0xCB,0xCF,0x08,0xB4,0xB9,0xD3,0x16,0x05,0xBD,0x62,0x83,0x31,0x9B,0x56,
|
||||
0x51,0x98,0x9F,0xBA,0xB2,0x5B,0xAA,0xB2,0x22,0x6B,0x2C,0xB5,0xD4,0x48,0xFA,0x63,
|
||||
0x2B,0x5F,0x58,0xFA,0x61,0xFA,0x64,0x09,0xBB,0x38,0xE0,0xB8,0x9D,0x92,0x60,0xA8,
|
||||
0x0D,0x67,0x6F,0x0E,0x37,0xF5,0x0D,0x01,0x9F,0xC2,0x77,0xD4,0xFE,0xEC,0xF1,0x73,
|
||||
0x30,0x39,0xE0,0x7D,0xF5,0x61,0x98,0xE4,0x2C,0x28,0x55,0x04,0x56,0x55,0xDB,0x2F,
|
||||
0x6B,0xEC,0xE5,0x58,0x06,0xB6,0x64,0x80,0x6A,0x2A,0x1A,0x4E,0x5B,0x0F,0xD8,0xC4,
|
||||
0x0A,0x2E,0x52,0x19,0xD9,0x62,0xF5,0x30,0x48,0xBE,0x8C,0x7B,0x4F,0x38,0x9B,0xA2,
|
||||
0xC3,0xAF,0xC9,0xD3,0xC7,0xC1,0x62,0x41,0x86,0xB9,0x61,0x21,0x57,0x6F,0x99,0x4F,
|
||||
0xC1,0xBA,0xCE,0x7B,0xB5,0x3B,0x4D,0x5E,0x8A,0x8B,0x44,0x57,0x5F,0x13,0x5F,0x70,
|
||||
0x6D,0x5B,0x29,0x47,0xDC,0x38,0xE2,0xEC,0x04,0x55,0x65,0x12,0x2A,0xE8,0x17,0x43,
|
||||
0xE1,0x8E,0xDD,0x2A,0xB3,0xE2,0x94,0xF7,0x09,0x6E,0x5C,0xE6,0xEB,0x8A,0xF8,0x6D,
|
||||
0x89,0x49,0x54,0x48,0xF5,0x2F,0xAD,0xBF,0xEA,0x94,0x4B,0xCA,0xFC,0x39,0x87,0x82,
|
||||
0x5F,0x8A,0x01,0xF2,0x75,0xF2,0xE6,0x71,0xD6,0xD8,0x42,0xDE,0xF1,0x2D,0x1D,0x28,
|
||||
0xA6,0x88,0x7E,0xA3,0xA0,0x47,0x1D,0x30,0xD9,0xA3,0x71,0xDF,0x49,0x1C,0xCB,0x01,
|
||||
0xF8,0x36,0xB1,0xF2,0xF0,0x22,0x58,0x5D,0x45,0x6B,0xBD,0xA0,0xBB,0xB2,0x88,0x42,
|
||||
0xC7,0x8C,0x28,0xCE,0x93,0xE8,0x90,0x63,0x08,0x90,0x7C,0x89,0x3C,0xF5,0x7D,0xB7,
|
||||
0x04,0x2D,0x4F,0x55,0x51,0x16,0xFD,0x7E,0x79,0xE8,0xBE,0xC1,0xF2,0x12,0xD4,0xF8,
|
||||
0xB4,0x84,0x05,0x23,0xA0,0xCC,0xD2,0x2B,0xFD,0xE1,0xAB,0xAD,0x0D,0xD1,0x55,0x6C,
|
||||
0x23,0x41,0x94,0x4D,0x77,0x37,0x4F,0x05,0x28,0x0C,0xBF,0x17,0xB3,0x12,0x67,0x6C,
|
||||
0x8C,0xC3,0x5A,0xF7,0x41,0x84,0x2A,0x6D,0xD0,0x94,0x12,0x27,0x2C,0xB4,0xED,0x9C,
|
||||
0x4D,0xEC,0x47,0x82,0x97,0xD5,0x67,0xB9,0x1B,0x9D,0xC0,0x55,0x07,0x7E,0xE5,0x8E,
|
||||
0xE2,0xA8,0xE7,0x3E,0x12,0xE4,0x0E,0x3A,0x2A,0x45,0x55,0x34,0xA2,0xF9,0x2D,0x5A,
|
||||
0x1B,0xAB,0x52,0x7C,0x83,0x10,0x5F,0x55,0xD2,0xF1,0x5A,0x43,0x2B,0xC6,0xA7,0xA4,
|
||||
0x89,0x15,0x95,0xE8,0xB4,0x4B,0x9D,0xF8,0x75,0xE3,0x9F,0x60,0x78,0x5B,0xD6,0xE6,
|
||||
0x0D,0x44,0xE6,0x21,0x06,0xBD,0x47,0x22,0x53,0xA4,0x00,0xAD,0x8D,0x43,0x13,0x85,
|
||||
0x39,0xF7,0xAA,0xFC,0x38,0xAF,0x7B,0xED,0xFC,0xE4,0x2B,0x54,0x50,0x98,0x4C,0xFC,
|
||||
0x85,0x80,0xF7,0xDF,0x3C,0x80,0x22,0xE1,0x94,0xDA,0xDE,0x24,0xC6,0xB0,0x7A,0x39,
|
||||
0x38,0xDC,0x0F,0xA1,0xA7,0xF4,0xF9,0x6F,0x63,0x18,0x57,0x8B,0x84,0x41,0x2A,0x2E,
|
||||
0xD4,0x53,0xF2,0xD9,0x00,0x0F,0xD0,0xDD,0x99,0x6E,0x19,0xA6,0x0A,0xD0,0xEC,0x5B,
|
||||
0x58,0x24,0xAB,0xC0,0xCB,0x06,0x65,0xEC,0x1A,0x13,0x38,0x94,0x0A,0x67,0x03,0x2F,
|
||||
0x3F,0xF7,0xE3,0x77,0x44,0x77,0x33,0xC6,0x14,0x39,0xD0,0xE3,0xC0,0xA2,0x08,0x79,
|
||||
0xBB,0x40,0x99,0x57,0x41,0x0B,0x01,0x90,0xCD,0xE1,0xCC,0x48,0x67,0xDB,0xB3,0xAF,
|
||||
0x88,0x74,0xF3,0x4C,0x82,0x8F,0x72,0xB1,0xB5,0x23,0x29,0xC4,0x12,0x6C,0x19,0xFC,
|
||||
0x8E,0x46,0xA4,0x9C,0xC4,0x25,0x65,0x87,0xD3,0x6D,0xBE,0x8A,0x93,0x11,0x03,0x38,
|
||||
0xED,0x83,0x2B,0xF3,0x46,0xA4,0x93,0xEA,0x3B,0x53,0x85,0x1D,0xCE,0xD4,0xF1,0x08,
|
||||
0x83,0x27,0xED,0xFC,0x9B,0x1A,0x18,0xBC,0xF9,0x8B,0xAE,0xDC,0x24,0xAB,0x50,0x38,
|
||||
0xE9,0x72,0x4B,0x10,0x22,0x17,0x7B,0x46,0x5D,0xAB,0x59,0x64,0xF3,0x40,0xAE,0xF8,
|
||||
0xBB,0xE5,0xC8,0xF9,0x26,0x03,0x4E,0x55,0x7D,0xEB,0xEB,0xFE,0xF7,0x39,0xE6,0xE0,
|
||||
0x0A,0x11,0xBE,0x2E,0x28,0xFF,0x98,0xED,0xC0,0xC9,0x42,0x56,0x42,0xC3,0xFD,0x00,
|
||||
0xF6,0xAF,0x87,0xA2,0x5B,0x01,0x3F,0x32,0x92,0x47,0x95,0x9A,0x72,0xA5,0x32,0x3D,
|
||||
0xAE,0x6B,0xD0,0x9B,0x07,0xD2,0x49,0x92,0xE3,0x78,0x4A,0xFA,0xA1,0x06,0x7D,0xF2,
|
||||
0x41,0xCF,0x77,0x74,0x04,0x14,0xB2,0x0C,0x86,0x84,0x64,0x16,0xD5,0xBB,0x51,0xA1,
|
||||
0xE5,0x6F,0xF1,0xD1,0xF2,0xE2,0xF7,0x5F,0x58,0x20,0x4D,0xB8,0x57,0xC7,0xCF,0xDD,
|
||||
0xC5,0xD8,0xBE,0x76,0x3D,0xF6,0x5F,0x7E,0xE7,0x2A,0x8B,0x88,0x24,0x1B,0x38,0x3F,
|
||||
0x0E,0x41,0x23,0x77,0xF5,0xF0,0x4B,0xD4,0x0C,0x1F,0xFA,0xA4,0x0B,0x80,0x5F,0xCF,
|
||||
0x45,0xF6,0xE0,0xDA,0x2F,0x34,0x59,0x53,0xFB,0x20,0x3C,0x52,0x62,0x5E,0x35,0xB5,
|
||||
0x62,0xFE,0x8B,0x60,0x63,0xE3,0x86,0x5A,0x15,0x1A,0x6E,0xD1,0x47,0x45,0xBC,0x32,
|
||||
0xB4,0xEB,0x67,0x38,0xAB,0xE4,0x6E,0x33,0x3A,0xB5,0xED,0xA3,0xAD,0x67,0xE0,0x4E,
|
||||
0x41,0x95,0xEE,0x62,0x62,0x71,0x26,0x1D,0x31,0xEF,0x62,0x30,0xAF,0xD7,0x82,0xAC,
|
||||
0xC2,0xDC,0x05,0x04,0xF5,0x97,0x07,0xBF,0x11,0x59,0x23,0x07,0xC0,0x64,0x02,0xE8,
|
||||
0x97,0xE5,0x3E,0xAF,0x18,0xAC,0x59,0xA6,0x8B,0x4A,0x33,0x90,0x1C,0x6E,0x7C,0x9C,
|
||||
0x20,0x7E,0x4C,0x3C,0x3E,0x61,0x64,0xBB,0xC5,0x6B,0x7C,0x7E,0x3E,0x9F,0xC5,0x4C,
|
||||
0x9F,0xEA,0x73,0xF5,0xD7,0x89,0xC0,0x4C,0xF4,0xFB,0xF4,0x2D,0xEC,0x14,0x1B,0x51,
|
||||
0xD5,0xC1,0x12,0xC8,0x10,0xDF,0x0B,0x4A,0x8B,0x9C,0xBC,0x93,0x45,0x6A,0x3E,0x3E,
|
||||
0x7D,0xC1,0xA9,0xBA,0xCD,0xC1,0xB4,0x07,0xE4,0xE1,0x68,0x86,0x43,0xB2,0x6D,0x38,
|
||||
0xF3,0xFB,0x0C,0x5C,0x66,0x37,0x71,0xDE,0x56,0xEF,0x6E,0xA0,0x10,0x40,0x65,0xA7,
|
||||
0x98,0xF7,0xD0,0xBE,0x0E,0xC8,0x37,0x36,0xEC,0x10,0xCA,0x7C,0x9C,0xAB,0x84,0x1E,
|
||||
0x05,0x17,0x76,0x02,0x1C,0x4F,0x52,0xAA,0x5F,0xC1,0xC6,0xA0,0x56,0xB9,0xD8,0x04,
|
||||
0x84,0x44,0x4D,0xA7,0x59,0xD8,0xDE,0x60,0xE6,0x38,0x0E,0x05,0x8F,0x03,0xE1,0x3B,
|
||||
0x6D,0x81,0x04,0x33,0x6F,0x30,0x0B,0xCE,0x69,0x05,0x21,0x33,0xFB,0x26,0xBB,0x89,
|
||||
0x7D,0xB6,0xAE,0x87,0x7E,0x51,0x07,0xE0,0xAC,0xF7,0x96,0x0A,0x6B,0xF9,0xC4,0x5C,
|
||||
0x1D,0xE4,0x44,0x47,0xB8,0x5E,0xFA,0xE3,0x78,0x84,0x55,0x42,0x4B,0x48,0x5E,0xF7,
|
||||
0x7D,0x47,0x35,0x86,0x1D,0x2B,0x43,0x05,0x03,0xEC,0x8A,0xB8,0x1E,0x06,0x3C,0x76,
|
||||
0x0C,0x48,0x1A,0x43,0xA7,0xB7,0x8A,0xED,0x1E,0x13,0xC6,0x43,0xEE,0x10,0xEF,0xDB,
|
||||
0xEC,0xFB,0x3C,0x83,0xB2,0x95,0x44,0xEF,0xD8,0x54,0x51,0x4E,0x2D,0x11,0x44,0x1D,
|
||||
0xFB,0x36,0x59,0x1E,0x7A,0x34,0xC1,0xC3,0xCA,0x57,0x00,0x61,0xEA,0x67,0xA5,0x16,
|
||||
0x9B,0x55,0xD0,0x55,0xE1,0x7F,0xD9,0x36,0xD2,0x40,0x76,0xAE,0xDC,0x01,0xCE,0xB0,
|
||||
0x7A,0x83,0xD5,0xCB,0x20,0x98,0xEC,0x6B,0xC1,0x72,0x92,0x34,0xF3,0x82,0x57,0x37,
|
||||
0x62,0x8A,0x32,0x36,0x0C,0x90,0x43,0xAE,0xAE,0x5C,0x9B,0x78,0x8E,0x13,0x65,0x02,
|
||||
0xFD,0x68,0x71,0xC1,0xFE,0xB0,0x31,0xA0,0x24,0x82,0xB0,0xC3,0xB1,0x79,0x69,0xA7,
|
||||
0xF5,0xD2,0xEB,0xD0,0x82,0xC0,0x32,0xDC,0x9E,0xC7,0x26,0x3C,0x6D,0x8D,0x98,0xC1,
|
||||
0xBB,0x22,0xD4,0xD0,0x0F,0x33,0xEC,0x3E,0xB9,0xCC,0xE1,0xDC,0x6A,0x4C,0x77,0x36,
|
||||
0x14,0x1C,0xF9,0xBF,0x81,0x9F,0x28,0x5F,0x71,0x85,0x32,0x29,0x90,0x75,0x48,0xC4,
|
||||
0xB3,0x4A,0xCE,0xD8,0x44,0x8F,0x14,0x2F,0xFD,0x40,0x57,0xEF,0xAA,0x08,0x75,0xD9,
|
||||
0x46,0xD1,0xD6,0x6E,0x32,0x55,0x1F,0xC3,0x18,0xFE,0x84,0x1F,0xFC,0x84,0xD5,0xFF,
|
||||
0x71,0x5E,0x1B,0x48,0xC3,0x86,0x95,0x0E,0x28,0x08,0x27,0xD3,0x38,0x83,0x71,0x7B,
|
||||
0x4C,0x80,0x63,0x54,0x9A,0x56,0xB0,0xAC,0xCF,0x80,0xCA,0x31,0x09,0xEF,0xFE,0xF3,
|
||||
0xBE,0xAF,0x24,0x7E,0xA6,0xFE,0x53,0x3F,0xC2,0x8D,0x4A,0x33,0x68,0xD1,0x22,0xA6,
|
||||
0x66,0xAD,0x7B,0xEA,0xDE,0xB6,0x43,0xB0,0xA1,0x25,0x95,0x00,0xA3,0x3F,0x75,0x46,
|
||||
0x14,0x11,0x44,0xEC,0xD7,0x95,0xBC,0x92,0xF0,0x4F,0xA9,0x16,0x53,0x62,0x97,0x60,
|
||||
0x2A,0x0F,0x41,0xF1,0x71,0x24,0xBE,0xEE,0x94,0x7F,0x08,0xCD,0x60,0x93,0xB3,0x85,
|
||||
0x5B,0x07,0x00,0x3F,0xD8,0x0F,0x28,0x83,0x9A,0xD1,0x69,0x9F,0xD1,0xDA,0x2E,0xC3,
|
||||
0x90,0x01,0xA2,0xB9,0x6B,0x4E,0x2A,0x66,0x9D,0xDA,0xAE,0xA6,0xEA,0x2A,0xD3,0x68,
|
||||
0x2F,0x0C,0x0C,0x9C,0xD2,0x8C,0x4A,0xED,0xE2,0x9E,0x57,0x65,0x9D,0x09,0x87,0xA3,
|
||||
0xB4,0xC4,0x32,0x5D,0xC9,0xD4,0x32,0x2B,0xB1,0xE0,0x71,0x1E,0x64,0x4D,0xE6,0x90,
|
||||
0x71,0xE3,0x1E,0x40,0xED,0x7D,0xF3,0x84,0x0E,0xED,0xC8,0x78,0x76,0xAE,0xC0,0x71,
|
||||
0x27,0x72,0xBB,0x05,0xEA,0x02,0x64,0xFB,0xF3,0x48,0x6B,0xB5,0x42,0x93,0x3F,0xED,
|
||||
0x9F,0x13,0x53,0xD2,0xF7,0xFE,0x2A,0xEC,0x1D,0x47,0x25,0xDB,0x3C,0x91,0x86,0xC6,
|
||||
0x8E,0xF0,0x11,0xFD,0x23,0x74,0x36,0xF7,0xA4,0xF5,0x9E,0x7A,0x7E,0x53,0x50,0x44,
|
||||
0xD4,0x47,0xCA,0xD3,0xEB,0x38,0x6D,0xE6,0xD9,0x71,0x94,0x7F,0x4A,0xC6,0x69,0x4B,
|
||||
0x11,0xF4,0x52,0xEA,0x22,0xFE,0x8A,0xB0,0x36,0x67,0x8B,0x59,0xE8,0xE6,0x80,0x2A,
|
||||
0xEB,0x65,0x04,0x13,0xEE,0xEC,0xDC,0x9E,0x5F,0xB1,0xEC,0x05,0x6A,0x59,0xE6,0x9F,
|
||||
0x5E,0x59,0x6B,0x89,0xBF,0xF7,0x1A,0xCA,0x44,0xF9,0x5B,0x6A,0x71,0x85,0x03,0xE4,
|
||||
0x29,0x62,0xE0,0x70,0x6F,0x41,0xC4,0xCF,0xB2,0xB1,0xCC,0xE3,0x7E,0xA6,0x07,0xA8,
|
||||
0x87,0xE7,0x7F,0x84,0x93,0xDB,0x52,0x4B,0x6C,0xEC,0x7E,0xDD,0xD4,0x24,0x48,0x10,
|
||||
0x69,0x9F,0x04,0x60,0x74,0xE6,0x48,0x18,0xF3,0xE4,0x2C,0xB9,0x4F,0x2E,0x50,0x7A,
|
||||
0xDF,0xD4,0x54,0x69,0x2B,0x8B,0xA7,0xF3,0xCE,0xFF,0x1F,0xF3,0x3E,0x26,0x01,0x39,
|
||||
0x17,0x95,0x84,0x89,0xB0,0xF0,0x4C,0x4B,0x82,0x91,0x9F,0xC4,0x4B,0xAC,0x9D,0xA5,
|
||||
0x74,0xAF,0x17,0x25,0xC9,0xCA,0x32,0xD3,0xBC,0x89,0x8A,0x84,0x89,0xCC,0x0D,0xAE,
|
||||
0x7C,0xA2,0xDB,0x9C,0x6A,0x78,0x91,0xEE,0xEA,0x76,0x5D,0x4E,0x87,0x60,0xF5,0x69,
|
||||
0x15,0x67,0xD4,0x02,0xCF,0xAF,0x48,0x36,0x07,0xEA,0xBF,0x6F,0x66,0x2D,0x06,0x8F,
|
||||
0xC4,0x9A,0xFE,0xF9,0xF6,0x90,0x87,0x75,0xB8,0xF7,0xAD,0x0F,0x76,0x10,0x5A,0x3D,
|
||||
0x59,0xB0,0x2E,0xB3,0xC7,0x35,0x2C,0xCC,0x70,0x56,0x2B,0xCB,0xE3,0x37,0x96,0xC5,
|
||||
0x2F,0x46,0x1B,0x8A,0x22,0x46,0xC7,0x88,0xA7,0x26,0x32,0x98,0x61,0xDF,0x86,0x22,
|
||||
0x8A,0xF4,0x1C,0x2F,0x87,0xA1,0x09,0xAA,0xCC,0xA9,0xAE,0xD3,0xBD,0x00,0x45,0x1C,
|
||||
0x9A,0x54,0x87,0x86,0x52,0x87,0xEF,0xFF,0x1E,0x8F,0xA1,0x8F,0xC1,0x89,0x5C,0x35,
|
||||
0x1B,0xDA,0x2D,0x3A,0x2C,0x16,0xB2,0xC2,0xF1,0x56,0xE2,0x78,0xC1,0x6B,0x63,0x97,
|
||||
0xC5,0x56,0x8F,0xC9,0x32,0x7F,0x2C,0xAA,0xAF,0xA6,0xA8,0xAC,0x20,0x91,0x22,0x88,
|
||||
0xDE,0xE4,0x60,0x8B,0xF9,0x4B,0x42,0x25,0x1A,0xE3,0x7F,0x9C,0x2C,0x19,0x89,0x3A,
|
||||
0x7E,0x05,0xD4,0x36,0xCC,0x69,0x58,0xC2,0xC1,0x32,0x8B,0x2F,0x90,0x85,0xEB,0x7A,
|
||||
0x39,0x50,0xA5,0xA1,0x27,0x92,0xC5,0x66,0xB0,0x20,0x4F,0x58,0x7E,0x55,0x83,0x43,
|
||||
0x2B,0x45,0xE2,0x9C,0xE4,0xD8,0x12,0x90,0x2C,0x16,0x83,0x56,0x16,0x79,0x03,0xB3,
|
||||
0xAD,0x2D,0x61,0x18,0x1A,0x13,0x1F,0x37,0xE2,0xE1,0x9C,0x73,0x7B,0x80,0xD5,0xFD,
|
||||
0x2D,0x51,0x87,0xFC,0x7B,0xAA,0xD7,0x1F,0x2C,0x7A,0x8E,0xAF,0xF4,0x8D,0xBB,0xCD,
|
||||
0x95,0x11,0x7C,0x72,0x0B,0xEE,0x6F,0xE2,0xB9,0xAF,0xDE,0x37,0x83,0xDE,0x8C,0x8D,
|
||||
0x62,0x05,0x67,0xB7,0x96,0xC6,0x8D,0x56,0xB6,0x0D,0xD7,0x62,0xBA,0xD6,0x46,0x36,
|
||||
0xBD,0x8E,0xC8,0xE6,0xEA,0x2A,0x6C,0x10,0x14,0xFF,0x6B,0x5B,0xFA,0x82,0x3C,0x46,
|
||||
0xB1,0x30,0x43,0x46,0x51,0x8A,0x7D,0x9B,0x92,0x3E,0x83,0x79,0x5B,0x55,0x5D,0xB2,
|
||||
0x6C,0x5E,0xCE,0x90,0x62,0x8E,0x53,0x98,0xC9,0x0D,0x6D,0xE5,0x2D,0x57,0xCD,0xC5,
|
||||
0x81,0x57,0xBA,0xE1,0xE8,0xB8,0x8F,0x72,0xE5,0x4F,0x13,0xDC,0xEA,0x9D,0x71,0x15,
|
||||
0x10,0xB2,0x11,0x88,0xD5,0x09,0xD4,0x7F,0x5B,0x65,0x7F,0x2C,0x3B,0x38,0x4C,0x11,
|
||||
0x68,0x50,0x8D,0xFB,0x9E,0xB0,0x59,0xBF,0x94,0x80,0x89,0x4A,0xC5,0x1A,0x18,0x12,
|
||||
0x89,0x53,0xD1,0x4A,0x10,0x29,0xE8,0x8C,0x1C,0xEC,0xB6,0xEA,0x46,0xC7,0x17,0x8B,
|
||||
0x25,0x15,0x31,0xA8,0xA2,0x6B,0x43,0xB1,0x9D,0xE2,0xDB,0x0B,0x87,0x9B,0xB0,0x11,
|
||||
0x04,0x0E,0x71,0xD2,0x29,0x77,0x89,0x82,0x0A,0x66,0x41,0x7F,0x1D,0x0B,0x48,0xFF,
|
||||
0x72,0xBB,0x24,0xFD,0xC2,0x48,0xA1,0x9B,0xFE,0x7B,0x7F,0xCE,0x88,0xDB,0x86,0xD9,
|
||||
0x85,0x3B,0x1C,0xB0,0xDC,0xA8,0x33,0x07,0xBF,0x51,0x2E,0xE3,0x0E,0x9A,0x00,0x97,
|
||||
0x1E,0x06,0xC0,0x97,0x43,0x9D,0xD8,0xB6,0x45,0xC4,0x86,0x67,0x5F,0x00,0xF8,0x88,
|
||||
0x9A,0xA4,0x52,0x9E,0xC7,0xAA,0x8A,0x83,0x75,0xEC,0xC5,0x18,0xAE,0xCE,0xC3,0x2F,
|
||||
0x1A,0x2B,0xF9,0x18,0xFF,0xAE,0x1A,0xF5,0x53,0x0B,0xB5,0x33,0x51,0xA7,0xFD,0xE8,
|
||||
0xA8,0xE1,0xA2,0x64,0xB6,0x22,0x17,0x43,0x80,0xCC,0x0A,0xD8,0xAE,0x3B,0xBA,0x40,
|
||||
0xD7,0xD9,0x92,0x4A,0x89,0xDF,0x04,0x10,0xEE,0x9B,0x18,0x2B,0x6A,0x77,0x69,0x8A,
|
||||
0x68,0xF4,0xF9,0xB9,0xA2,0x21,0x15,0x6E,0xE6,0x1E,0x3B,0x03,0x62,0x30,0x9B,0x60,
|
||||
0x41,0x7E,0x25,0x9B,0x9E,0x8F,0xC5,0x52,0x10,0x08,0xF8,0xC2,0x69,0xA1,0x21,0x11,
|
||||
0x88,0x37,0x5E,0x79,0x35,0x66,0xFF,0x10,0x42,0x18,0x6E,0xED,0x97,0xB6,0x6B,0x1C,
|
||||
0x4E,0x36,0xE5,0x6D,0x7D,0xB4,0xE4,0xBF,0x20,0xB9,0xE0,0x05,0x3A,0x69,0xD5,0xB8,
|
||||
0xE3,0xD5,0xDC,0xE0,0xB9,0xAC,0x53,0x3E,0x07,0xA4,0x57,0xAD,0x77,0xFF,0x48,0x18,
|
||||
0x76,0x2A,0xAC,0x49,0x2A,0x8E,0x47,0x75,0x6D,0x9F,0x67,0x63,0x30,0x35,0x8C,0x39,
|
||||
0x05,0x39,0xD5,0x6F,0x64,0x3A,0x5B,0xAD,0xCA,0x0B,0xBB,0x82,0x52,0x99,0x45,0xB1,
|
||||
0x93,0x36,0x36,0x99,0xAF,0x13,0x20,0x44,0x36,0xD8,0x02,0x44,0x09,0x39,0x92,0x85,
|
||||
0xFF,0x4A,0x4A,0x97,0x87,0xA6,0x63,0xD7,0xC7,0xB5,0xB5,0x24,0xED,0x0F,0xB4,0x6F,
|
||||
0x0C,0x58,0x52,0x14,0xD9,0xA6,0x7B,0xD3,0x79,0xBC,0x38,0x58,0xA1,0xBD,0x3B,0x84,
|
||||
0x06,0xD8,0x1A,0x06,0xFD,0x6B,0xA8,0xEA,0x4B,0x69,0x28,0x04,0x37,0xAD,0x82,0x99,
|
||||
0xFB,0x0E,0x1B,0x85,0xBD,0xA8,0x5D,0x73,0xCD,0xDC,0x58,0x75,0x0A,0xBE,0x63,0x6C,
|
||||
0x48,0xE7,0x4C,0xE4,0x30,0x2B,0x04,0x60,0xB9,0x15,0xD8,0xDA,0x86,0x81,0x75,0x8F,
|
||||
0x96,0xD4,0x8D,0x1C,0x5D,0x70,0x85,0x7C,0x1C,0x67,0x7B,0xD5,0x08,0x67,0xA6,0xCE,
|
||||
0x4B,0x0A,0x66,0x70,0xB7,0xE5,0x63,0xD4,0x5B,0x8A,0x82,0xEA,0x10,0x67,0xCA,0xE2,
|
||||
0xF4,0xEF,0x17,0x85,0x2F,0x2A,0x5F,0x8A,0x97,0x82,0xF8,0x6A,0xD6,0x34,0x10,0xEA,
|
||||
0xEB,0xC9,0x5C,0x3C,0xE1,0x49,0xF8,0x46,0xEB,0xDE,0xBD,0xF6,0xA9,0x92,0xF1,0xAA,
|
||||
0xA6,0xA0,0x18,0xB0,0x3A,0xD3,0x0F,0x1F,0xF3,0x6F,0xFF,0x31,0x45,0x43,0x44,0xD3,
|
||||
0x50,0x9A,0xF7,0x88,0x09,0x96,0xC1,0xCE,0x76,0xCC,0xF2,0x2C,0x2C,0xBA,0xAD,0x82,
|
||||
0x77,0x8F,0x18,0x84,0xC0,0xD2,0x07,0x9C,0x36,0x90,0x83,0x4E,0x0B,0xA5,0x4F,0x43,
|
||||
0x3E,0x04,0xAB,0x78,0x4F,0xD6,0xFB,0x09,0x01,0x24,0x90,0xDA,0x6F,0x3C,0x3A,0x61,
|
||||
0x0D,0x7F,0x69,0x4A,0xEB,0x2B,0x30,0x02,0xB4,0xDB,0xE0,0x84,0xA9,0xEC,0xD7,0x35,
|
||||
0xBF,0x37,0x7D,0x85,0x58,0xCE,0xA9,0x4E,0xE4,0x80,0xC7,0xA8,0xD3,0x30,0x67,0x48,
|
||||
0xEB,0x29,0xAF,0x2F,0x74,0x6A,0xB4,0xA7,0x3F,0x0F,0x3F,0x92,0xAF,0xF3,0xCA,0xAC,
|
||||
0xAF,0x4B,0xD9,0x94,0xC0,0x43,0xCA,0x81,0x0D,0x2F,0x48,0xA1,0xB0,0x27,0xD5,0xD2,
|
||||
0xEF,0x4B,0x05,0x85,0xA3,0xDE,0x4D,0x93,0x30,0x3C,0xF0,0xBB,0x4A,0x8F,0x30,0x27,
|
||||
0x4C,0xEB,0xE3,0x3E,0x64,0xED,0x9A,0x2F,0x3B,0xF1,0x82,0xF0,0xBA,0xF4,0xCF,0x7F,
|
||||
0x40,0xCB,0xB0,0xE1,0x7F,0xBC,0xAA,0x57,0xD3,0xC9,0x74,0xF2,0xFA,0x43,0x0D,0x22,
|
||||
0xD0,0xF4,0x77,0x4E,0x93,0xD7,0x85,0x70,0x1F,0x99,0xBF,0xB6,0xDE,0x35,0xF1,0x30,
|
||||
0xA7,0x5E,0x71,0xF0,0x6B,0x01,0x2D,0x7B,0x64,0xF0,0x33,0x53,0x0A,0x39,0x88,0xF3,
|
||||
0x6B,0x3A,0xA6,0x6B,0x35,0xD2,0x2F,0x43,0xCD,0x02,0xFD,0xB5,0xE9,0xBC,0x5B,0xAA,
|
||||
0xD8,0xA4,0x19,0x7E,0x0E,0x5D,0x94,0x81,0x9E,0x6F,0x77,0xAD,0xD6,0x0E,0x74,0x93,
|
||||
0x96,0xE7,0xC4,0x18,0x5F,0xAD,0xF5,0x19,
|
||||
];
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3DS-Specific Fields
|
||||
@@ -510,149 +242,6 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="keyfile">Path to the keyfile</param>
|
||||
public DecryptArgs(string? config)
|
||||
{
|
||||
InitConfigJson(config);
|
||||
ValidateKeys();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup all of the necessary constants
|
||||
/// </summary>
|
||||
/// <param name="keyfile">Path to the keyfile</param>
|
||||
/// <param name="useAesKeysTxt">Indicates if the keyfile format is aeskeys.txt</param>
|
||||
public DecryptArgs(string? keyfile, bool useAesKeysTxt)
|
||||
{
|
||||
// Set the default DS values
|
||||
NitroEncryptionData = _nitroEncryptionData;
|
||||
|
||||
// Read the proper keyfile format
|
||||
if (useAesKeysTxt)
|
||||
InitAesKeysTxt(keyfile);
|
||||
else
|
||||
InitKeysBin(keyfile);
|
||||
|
||||
// Perform validation tests
|
||||
ValidateKeys();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup all of the necessary constants from aes_keys.txt
|
||||
/// </summary>
|
||||
/// <param name="keyfile">Path to aes_keys.txt</param>
|
||||
private void InitAesKeysTxt(string? keyfile)
|
||||
{
|
||||
if (keyfile == null || !File.Exists(keyfile))
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var reader = new IniReader(keyfile);
|
||||
while (reader.ReadNextLine())
|
||||
{
|
||||
// Ignore comments in the file
|
||||
if (reader.RowType == IniRowType.Comment)
|
||||
continue;
|
||||
if (reader.KeyValuePair == null || string.IsNullOrEmpty(reader.KeyValuePair?.Key))
|
||||
break;
|
||||
|
||||
var kvp = reader.KeyValuePair!.Value;
|
||||
byte[] value = StringToByteArray(kvp.Value);
|
||||
switch (kvp.Key)
|
||||
{
|
||||
// Hardware constant
|
||||
case "generator":
|
||||
AESHardwareConstant = value;
|
||||
break;
|
||||
|
||||
// Retail Keys
|
||||
case "slot0x18KeyX":
|
||||
KeyX0x18 = value;
|
||||
break;
|
||||
case "slot0x1BKeyX":
|
||||
KeyX0x1B = value;
|
||||
break;
|
||||
case "slot0x25KeyX":
|
||||
KeyX0x25 = value;
|
||||
break;
|
||||
case "slot0x2CKeyX":
|
||||
KeyX0x2C = value;
|
||||
break;
|
||||
|
||||
// Currently Unused KeyX
|
||||
case "slot0x03KeyX":
|
||||
case "slot0x19KeyX":
|
||||
case "slot0x1AKeyX":
|
||||
case "slot0x1CKeyX":
|
||||
case "slot0x1DKeyX":
|
||||
case "slot0x1EKeyX":
|
||||
case "slot0x1FKeyX":
|
||||
case "slot0x2DKeyX":
|
||||
case "slot0x2EKeyX":
|
||||
case "slot0x2FKeyX":
|
||||
case "slot0x30KeyX":
|
||||
case "slot0x31KeyX":
|
||||
case "slot0x32KeyX":
|
||||
case "slot0x33KeyX":
|
||||
case "slot0x34KeyX":
|
||||
case "slot0x35KeyX":
|
||||
case "slot0x36KeyX":
|
||||
case "slot0x37KeyX":
|
||||
case "slot0x38KeyX":
|
||||
case "slot0x3AKeyX":
|
||||
case "slot0x3BKeyX":
|
||||
break;
|
||||
|
||||
// Currently Unused KeyY
|
||||
case "slot0x03KeyY":
|
||||
case "slot0x06KeyY":
|
||||
case "slot0x07KeyY":
|
||||
case "slot0x2EKeyY":
|
||||
case "slot0x2FKeyY":
|
||||
case "slot0x31KeyY":
|
||||
break;
|
||||
|
||||
// Currently Unused KeyN
|
||||
case "slot0x0DKeyN":
|
||||
case "slot0x15KeyN":
|
||||
case "slot0x16KeyN":
|
||||
case "slot0x19KeyN":
|
||||
case "slot0x1AKeyN":
|
||||
case "slot0x1BKeyN":
|
||||
case "slot0x1CKeyN":
|
||||
case "slot0x1DKeyN":
|
||||
case "slot0x1EKeyN":
|
||||
case "slot0x1FKeyN":
|
||||
case "slot0x24KeyN":
|
||||
case "slot0x2DKeyN":
|
||||
case "slot0x2EKeyN":
|
||||
case "slot0x2FKeyN":
|
||||
case "slot0x31KeyN":
|
||||
case "slot0x32KeyN":
|
||||
case "slot0x36KeyN":
|
||||
case "slot0x37KeyN":
|
||||
case "slot0x38KeyN":
|
||||
case "slot0x3BKeyN":
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
IsReady = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup all of the necessary constants from config.json
|
||||
/// </summary>
|
||||
/// <param name="config">Path to config.json</param>
|
||||
private void InitConfigJson(string? config)
|
||||
{
|
||||
if (config == null || !File.Exists(config))
|
||||
{
|
||||
@@ -669,68 +258,19 @@ namespace NDecrypt.Core
|
||||
}
|
||||
|
||||
// Set the fields from the configuration
|
||||
NitroEncryptionData = StringToByteArray(configObj.NitroEncryptionData);
|
||||
AESHardwareConstant = StringToByteArray(configObj.AESHardwareConstant);
|
||||
KeyX0x18 = StringToByteArray(configObj.KeyX0x18);
|
||||
KeyX0x1B = StringToByteArray(configObj.KeyX0x1B);
|
||||
KeyX0x25 = StringToByteArray(configObj.KeyX0x25);
|
||||
KeyX0x2C = StringToByteArray(configObj.KeyX0x2C);
|
||||
DevKeyX0x18 = StringToByteArray(configObj.DevKeyX0x18);
|
||||
DevKeyX0x1B = StringToByteArray(configObj.DevKeyX0x1B);
|
||||
DevKeyX0x25 = StringToByteArray(configObj.DevKeyX0x25);
|
||||
DevKeyX0x2C = StringToByteArray(configObj.DevKeyX0x2C);
|
||||
|
||||
IsReady = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup all of the necessary constants from keys.bin
|
||||
/// </summary>
|
||||
/// <param name="keyfile">Path to keys.bin</param>
|
||||
/// <remarks>keys.bin should be in little endian format</remarks>
|
||||
private void InitKeysBin(string? keyfile)
|
||||
{
|
||||
if (keyfile == null || !File.Exists(keyfile))
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using Stream reader = File.Open(keyfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// Hardware constant
|
||||
AESHardwareConstant = reader.ReadBytes(16);
|
||||
Array.Reverse(AESHardwareConstant);
|
||||
|
||||
// Retail keys
|
||||
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 = 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
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
}
|
||||
NitroEncryptionData = configObj.NitroEncryptionData.FromHexString() ?? [];
|
||||
AESHardwareConstant = configObj.AESHardwareConstant.FromHexString() ?? [];
|
||||
KeyX0x18 = configObj.KeyX0x18.FromHexString() ?? [];
|
||||
KeyX0x1B = configObj.KeyX0x1B.FromHexString() ?? [];
|
||||
KeyX0x25 = configObj.KeyX0x25.FromHexString() ?? [];
|
||||
KeyX0x2C = configObj.KeyX0x2C.FromHexString() ?? [];
|
||||
DevKeyX0x18 = configObj.DevKeyX0x18.FromHexString() ?? [];
|
||||
DevKeyX0x1B = configObj.DevKeyX0x1B.FromHexString() ?? [];
|
||||
DevKeyX0x25 = configObj.DevKeyX0x25.FromHexString() ?? [];
|
||||
DevKeyX0x2C = configObj.DevKeyX0x2C.FromHexString() ?? [];
|
||||
|
||||
IsReady = true;
|
||||
ValidateKeys();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -847,22 +387,5 @@ namespace NDecrypt.Core
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa
|
||||
private static byte[] StringToByteArray(string? hex)
|
||||
{
|
||||
// Handle null values
|
||||
if (hex == null)
|
||||
return [];
|
||||
|
||||
int NumberChars = hex.Length;
|
||||
byte[] bytes = new byte[NumberChars / 2];
|
||||
for (int i = 0; i < NumberChars; i += 2)
|
||||
{
|
||||
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,22 @@
|
||||
/// <summary>
|
||||
/// Attempts to encrypt an input file
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of the file to encrypt</param>
|
||||
/// <param name="input">Name of the file to encrypt</param>
|
||||
/// <param name="output">Optional name of the file to write to</param>
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <returns>True if the file could be encrypted, false otherwise</returns>
|
||||
bool EncryptFile(string filename, bool force);
|
||||
/// <remarks>If an output filename is not provided, the input file will be overwritten</remarks>
|
||||
bool EncryptFile(string input, string? output, bool force);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to decrypt an input file
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of the file to decrypt</param>
|
||||
/// <param name="input">Name of the file to decrypt</param>
|
||||
/// <param name="output">Optional name of the file to write to</param>
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <returns>True if the file could be decrypted, false otherwise</returns>
|
||||
bool DecryptFile(string filename, bool force);
|
||||
/// <remarks>If an output filename is not provided, the input file will be overwritten</remarks>
|
||||
bool DecryptFile(string input, string? output, bool force);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get information on an input file
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
@@ -11,7 +11,7 @@
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>0.3.2</VersionPrefix>
|
||||
<VersionPrefix>0.4.0</VersionPrefix>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
@@ -40,10 +40,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.NetCore" Version="1.9.0" Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))" />
|
||||
<PackageReference Include="BouncyCastle.NetCore" Version="2.2.1" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`))" />
|
||||
<PackageReference Include="SabreTools.Hashing" Version="1.4.2" />
|
||||
<PackageReference Include="SabreTools.IO" Version="1.6.2" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.5.8" />
|
||||
<PackageReference Include="SabreTools.Serialization" Version="1.8.6" />
|
||||
<PackageReference Include="SabreTools.Hashing" Version="1.5.0" />
|
||||
<PackageReference Include="SabreTools.IO" Version="1.7.2" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.7.1" />
|
||||
<PackageReference Include="SabreTools.Serialization" Version="1.9.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -24,7 +24,7 @@ namespace NDecrypt.Core
|
||||
/// <summary>
|
||||
/// Set of all partition keys
|
||||
/// </summary>
|
||||
private readonly PartitionKeys[] KeysMap = new PartitionKeys[8];
|
||||
private readonly PartitionKeys[] _keysMap = new PartitionKeys[8];
|
||||
|
||||
public ThreeDSTool(bool development, DecryptArgs decryptArgs)
|
||||
{
|
||||
@@ -35,7 +35,7 @@ namespace NDecrypt.Core
|
||||
#region Decrypt
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool DecryptFile(string filename, bool force)
|
||||
public bool DecryptFile(string input, string? output, bool force)
|
||||
{
|
||||
// Ensure the constants are all set
|
||||
if (_decryptArgs.IsReady != true)
|
||||
@@ -46,12 +46,18 @@ namespace NDecrypt.Core
|
||||
|
||||
try
|
||||
{
|
||||
// Open the read and write on the same file for inplace processing
|
||||
using var input = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var output = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
// If the output is provided, copy the input file
|
||||
if (output != null)
|
||||
File.Copy(input, output, overwrite: true);
|
||||
else
|
||||
output = input;
|
||||
|
||||
// Open the output file for processing
|
||||
using var reader = File.Open(output, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var writer = File.Open(output, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = N3DS.Create(input);
|
||||
var cart = N3DS.Create(reader);
|
||||
if (cart?.Model == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS cart image!");
|
||||
@@ -59,12 +65,12 @@ namespace NDecrypt.Core
|
||||
}
|
||||
|
||||
// Decrypt all 8 NCCH partitions
|
||||
DecryptAllPartitions(cart, force, input, output);
|
||||
DecryptAllPartitions(cart, force, reader, writer);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine($"An error has occurred. {filename} may be corrupted if it was partially processed.");
|
||||
Console.WriteLine($"An error has occurred. {output} may be corrupted if it was partially processed.");
|
||||
Console.WriteLine("Please check that the file was a valid 3DS or New 3DS cart image and try again.");
|
||||
return false;
|
||||
}
|
||||
@@ -75,9 +81,9 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <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(N3DS cart, bool force, Stream input, Stream output)
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void DecryptAllPartitions(N3DS cart, bool force, Stream reader, Stream writer)
|
||||
{
|
||||
// Check the partitions table
|
||||
if (cart.PartitionsTable == null || cart.Partitions == null)
|
||||
@@ -106,7 +112,7 @@ namespace NDecrypt.Core
|
||||
|
||||
// Decrypt the partition, if possible
|
||||
if (ShouldDecryptPartition(cart, p, force))
|
||||
DecryptPartition(cart, p, input, output);
|
||||
DecryptPartition(cart, p, reader, writer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,20 +143,20 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <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(N3DS cart, int index, Stream input, Stream output)
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void DecryptPartition(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Determine the keys needed for this partition
|
||||
SetDecryptionKeys(cart, index);
|
||||
|
||||
// Decrypt the parts of the partition
|
||||
DecryptExtendedHeader(cart, index, input, output);
|
||||
DecryptExeFS(cart, index, input, output);
|
||||
DecryptRomFS(cart, index, input, output);
|
||||
DecryptExtendedHeader(cart, index, reader, writer);
|
||||
DecryptExeFS(cart, index, reader, writer);
|
||||
DecryptRomFS(cart, index, reader, writer);
|
||||
|
||||
// Update the flags
|
||||
UpdateDecryptCryptoAndMasks(cart, index, output);
|
||||
UpdateDecryptCryptoAndMasks(cart, index, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -171,7 +177,7 @@ namespace NDecrypt.Core
|
||||
CryptoMethod method = cart.GetCryptoMethod(index);
|
||||
|
||||
// Get the partition keys
|
||||
KeysMap[index] = new PartitionKeys(_decryptArgs, rsaSignature, masks, method, _development);
|
||||
_keysMap[index] = new PartitionKeys(_decryptArgs, rsaSignature, masks, method, _development);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -179,13 +185,13 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <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(N3DS cart, int index, Stream input, Stream output)
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private bool DecryptExtendedHeader(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
if (partitionOffset == 0 || partitionOffset > input.Length)
|
||||
if (partitionOffset == 0 || partitionOffset > reader.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} No Data... Skipping...");
|
||||
return false;
|
||||
@@ -199,22 +205,22 @@ namespace NDecrypt.Core
|
||||
}
|
||||
|
||||
// Seek to the extended header
|
||||
input.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
output.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
reader.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
writer.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {index}: Decrypting - ExHeader");
|
||||
|
||||
// Create the Plain AES cipher for this partition
|
||||
var cipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey2C, cart.PlainIV(index));
|
||||
var cipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey2C, cart.PlainIV(index));
|
||||
|
||||
// Process the extended header
|
||||
PerformAESOperation(Constants.CXTExtendedDataHeaderLength, cipher, input, output, null);
|
||||
PerformAESOperation(Constants.CXTExtendedDataHeaderLength, cipher, reader, writer, null);
|
||||
|
||||
#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
|
||||
input.Seek(0, SeekOrigin.Begin);
|
||||
reader.Seek(0, SeekOrigin.Begin);
|
||||
#endif
|
||||
output.Flush();
|
||||
writer.Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -223,13 +229,13 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <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(N3DS cart, int index, Stream input, Stream output)
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private bool DecryptExeFS(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Validate the ExeFS
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsHeaderOffset == 0 || exeFsHeaderOffset > input.Length)
|
||||
if (exeFsHeaderOffset == 0 || exeFsHeaderOffset > reader.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return false;
|
||||
@@ -243,30 +249,30 @@ namespace NDecrypt.Core
|
||||
}
|
||||
|
||||
// Decrypt the filename table
|
||||
DecryptExeFSFilenameTable(cart, index, input, output);
|
||||
DecryptExeFSFilenameTable(cart, index, reader, writer);
|
||||
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
if (cart.GetCryptoMethod(index) != CryptoMethod.Original)
|
||||
DecryptExeFSFileEntries(cart, index, input, output);
|
||||
DecryptExeFSFileEntries(cart, index, reader, writer);
|
||||
|
||||
// Get the ExeFS files offset
|
||||
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
|
||||
|
||||
// Seek to the ExeFS
|
||||
input.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
reader.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
writer.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
uint ctroffsetE = cart.MediaUnitSize / 0x10;
|
||||
byte[] exefsIVWithOffset = Add(cart.ExeFSIV(index), ctroffsetE);
|
||||
var cipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey2C, exefsIVWithOffset);
|
||||
var cipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffset);
|
||||
|
||||
// Setup and perform the decryption
|
||||
exeFsSize -= cart.MediaUnitSize;
|
||||
PerformAESOperation(exeFsSize,
|
||||
cipher,
|
||||
input,
|
||||
output,
|
||||
reader,
|
||||
writer,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting - {s}"));
|
||||
|
||||
return true;
|
||||
@@ -277,37 +283,37 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <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(N3DS cart, int index, Stream input, Stream output)
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void DecryptExeFSFilenameTable(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Get ExeFS offset
|
||||
uint exeFsOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsOffset == 0 || exeFsOffset > input.Length)
|
||||
if (exeFsOffset == 0 || exeFsOffset > reader.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Seek to the ExeFS header
|
||||
input.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
reader.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
writer.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
|
||||
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));
|
||||
var cipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey2C, cart.ExeFSIV(index));
|
||||
|
||||
// Process the filename table
|
||||
byte[] readBytes = input.ReadBytes((int)cart.MediaUnitSize);
|
||||
byte[] readBytes = reader.ReadBytes((int)cart.MediaUnitSize);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
output.Write(processedBytes);
|
||||
writer.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
|
||||
input.Seek(0, SeekOrigin.Begin);
|
||||
reader.Seek(0, SeekOrigin.Begin);
|
||||
#endif
|
||||
output.Flush();
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -315,9 +321,9 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <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(N3DS cart, int index, Stream input, Stream output)
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void DecryptExeFSFileEntries(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
if (cart.ExeFSHeaders == null || index < 0 || index > cart.ExeFSHeaders.Length)
|
||||
{
|
||||
@@ -327,8 +333,8 @@ namespace NDecrypt.Core
|
||||
|
||||
// Reread the decrypted ExeFS header
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
input.Seek(exeFsHeaderOffset, SeekOrigin.Begin);
|
||||
cart.ExeFSHeaders[index] = SabreTools.Serialization.Deserializers.N3DS.ParseExeFSHeader(input);
|
||||
reader.Seek(exeFsHeaderOffset, SeekOrigin.Begin);
|
||||
cart.ExeFSHeaders[index] = SabreTools.Serialization.Deserializers.N3DS.ParseExeFSHeader(reader);
|
||||
|
||||
// Get the ExeFS header
|
||||
var exeFsHeader = cart.ExeFSHeaders[index];
|
||||
@@ -356,19 +362,19 @@ namespace NDecrypt.Core
|
||||
// Create the ExeFS AES ciphers for this partition
|
||||
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);
|
||||
var firstCipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey, exefsIVWithOffsetForHeader);
|
||||
var secondCipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffsetForHeader);
|
||||
|
||||
// Seek to the file entry
|
||||
input.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
reader.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
writer.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
// Setup and perform the encryption
|
||||
PerformAESOperation(fileHeader.FileSize,
|
||||
firstCipher,
|
||||
secondCipher,
|
||||
input,
|
||||
output,
|
||||
reader,
|
||||
writer,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting - {fileHeader.FileName}...{s}"));
|
||||
}
|
||||
}
|
||||
@@ -378,13 +384,13 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <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(N3DS cart, int index, Stream input, Stream output)
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private bool DecryptRomFS(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Validate the RomFS
|
||||
uint romFsOffset = cart.GetRomFSOffset(index);
|
||||
if (romFsOffset == 0 || romFsOffset > input.Length)
|
||||
if (romFsOffset == 0 || romFsOffset > reader.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
|
||||
return false;
|
||||
@@ -398,17 +404,17 @@ namespace NDecrypt.Core
|
||||
}
|
||||
|
||||
// Seek to the RomFS
|
||||
input.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
output.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
reader.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
writer.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the RomFS AES cipher for this partition
|
||||
var cipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey, cart.RomFSIV(index));
|
||||
var cipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey, cart.RomFSIV(index));
|
||||
|
||||
// Setup and perform the decryption
|
||||
PerformAESOperation(romFsSize,
|
||||
cipher,
|
||||
input,
|
||||
output,
|
||||
reader,
|
||||
writer,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} RomFS: Decrypting - {s}"));
|
||||
|
||||
return true;
|
||||
@@ -419,28 +425,28 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <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(N3DS cart, int index, Stream output)
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private static void UpdateDecryptCryptoAndMasks(N3DS cart, int index, Stream writer)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
|
||||
// Seek to the CryptoMethod location
|
||||
output.Seek(partitionOffset + 0x18B, SeekOrigin.Begin);
|
||||
writer.Seek(partitionOffset + 0x18B, SeekOrigin.Begin);
|
||||
|
||||
// Write the new CryptoMethod
|
||||
output.Write((byte)CryptoMethod.Original);
|
||||
output.Flush();
|
||||
writer.Write((byte)CryptoMethod.Original);
|
||||
writer.Flush();
|
||||
|
||||
// Seek to the BitMasks location
|
||||
output.Seek(partitionOffset + 0x18F, SeekOrigin.Begin);
|
||||
writer.Seek(partitionOffset + 0x18F, SeekOrigin.Begin);
|
||||
|
||||
// Write the new BitMasks flag
|
||||
BitMasks flag = cart.GetBitMasks(index);
|
||||
flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
|
||||
flag |= BitMasks.NoCrypto;
|
||||
output.Write((byte)flag);
|
||||
output.Flush();
|
||||
writer.Write((byte)flag);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -448,7 +454,7 @@ namespace NDecrypt.Core
|
||||
#region Encrypt
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool EncryptFile(string filename, bool force)
|
||||
public bool EncryptFile(string input, string? output, bool force)
|
||||
{
|
||||
// Ensure the constants are all set
|
||||
if (_decryptArgs.IsReady != true)
|
||||
@@ -459,12 +465,18 @@ namespace NDecrypt.Core
|
||||
|
||||
try
|
||||
{
|
||||
// Open the read and write on the same file for inplace processing
|
||||
using var input = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var output = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
// If the output is provided, copy the input file
|
||||
if (output != null)
|
||||
File.Copy(input, output, overwrite: true);
|
||||
else
|
||||
output = input;
|
||||
|
||||
// Open the output file for processing
|
||||
using var reader = File.Open(output, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var writer = File.Open(output, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = N3DS.Create(input);
|
||||
var cart = N3DS.Create(reader);
|
||||
if (cart?.Model == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS cart image!");
|
||||
@@ -472,12 +484,12 @@ namespace NDecrypt.Core
|
||||
}
|
||||
|
||||
// Encrypt all 8 NCCH partitions
|
||||
EncryptAllPartitions(cart, force, input, output);
|
||||
EncryptAllPartitions(cart, force, reader, writer);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine($"An error has occurred. {filename} may be corrupted if it was partially processed.");
|
||||
Console.WriteLine($"An error has occurred. {output} may be corrupted if it was partially processed.");
|
||||
Console.WriteLine("Please check that the file was a valid 3DS or New 3DS cart image and try again.");
|
||||
return false;
|
||||
}
|
||||
@@ -488,9 +500,9 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <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(N3DS cart, bool force, Stream input, Stream output)
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void EncryptAllPartitions(N3DS cart, bool force, Stream reader, Stream writer)
|
||||
{
|
||||
// Check the partitions table
|
||||
if (cart.PartitionsTable == null || cart.Partitions == null)
|
||||
@@ -520,7 +532,7 @@ namespace NDecrypt.Core
|
||||
|
||||
// Encrypt the partition, if possible
|
||||
if (ShouldEncryptPartition(cart, p, force))
|
||||
EncryptPartition(cart, p, input, output);
|
||||
EncryptPartition(cart, p, reader, writer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,20 +563,20 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <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(N3DS cart, int index, Stream input, Stream output)
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void EncryptPartition(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Determine the keys needed for this partition
|
||||
SetEncryptionKeys(cart, index);
|
||||
|
||||
// Encrypt the parts of the partition
|
||||
EncryptExtendedHeader(cart, index, input, output);
|
||||
EncryptExeFS(cart, index, input, output);
|
||||
EncryptRomFS(cart, index, input, output);
|
||||
EncryptExtendedHeader(cart, index, reader, writer);
|
||||
EncryptExeFS(cart, index, reader, writer);
|
||||
EncryptRomFS(cart, index, reader, writer);
|
||||
|
||||
// Update the flags
|
||||
UpdateEncryptCryptoAndMasks(cart, index, output);
|
||||
UpdateEncryptCryptoAndMasks(cart, index, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -590,7 +602,7 @@ namespace NDecrypt.Core
|
||||
CryptoMethod method = backupHeader.Flags.CryptoMethod;
|
||||
|
||||
// Get the partition keys
|
||||
KeysMap[index] = new PartitionKeys(_decryptArgs, rsaSignature, masks, method, _development);
|
||||
_keysMap[index] = new PartitionKeys(_decryptArgs, rsaSignature, masks, method, _development);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -598,13 +610,13 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <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(N3DS cart, int index, Stream input, Stream output)
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private bool EncryptExtendedHeader(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
if (partitionOffset == 0 || partitionOffset > input.Length)
|
||||
if (partitionOffset == 0 || partitionOffset > reader.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} No Data... Skipping...");
|
||||
return false;
|
||||
@@ -618,22 +630,22 @@ namespace NDecrypt.Core
|
||||
}
|
||||
|
||||
// Seek to the extended header
|
||||
input.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
output.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
reader.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
writer.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {index}: Encrypting - ExHeader");
|
||||
|
||||
// Create the Plain AES cipher for this partition
|
||||
var cipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey2C, cart.PlainIV(index));
|
||||
var cipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey2C, cart.PlainIV(index));
|
||||
|
||||
// Process the extended header
|
||||
PerformAESOperation(Constants.CXTExtendedDataHeaderLength, cipher, input, output, null);
|
||||
PerformAESOperation(Constants.CXTExtendedDataHeaderLength, cipher, reader, writer, null);
|
||||
|
||||
#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
|
||||
input.Seek(0, SeekOrigin.Begin);
|
||||
reader.Seek(0, SeekOrigin.Begin);
|
||||
#endif
|
||||
output.Flush();
|
||||
writer.Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -642,9 +654,9 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <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(N3DS cart, int index, Stream input, Stream output)
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private bool EncryptExeFS(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
if (cart.ExeFSHeaders == null || index < 0 || index > cart.ExeFSHeaders.Length)
|
||||
{
|
||||
@@ -663,30 +675,30 @@ namespace NDecrypt.Core
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
var backupHeader = cart.BackupHeader;
|
||||
if (backupHeader!.Flags!.CryptoMethod != CryptoMethod.Original)
|
||||
EncryptExeFSFileEntries(cart, index, input, output);
|
||||
EncryptExeFSFileEntries(cart, index, reader, writer);
|
||||
|
||||
// Encrypt the filename table
|
||||
EncryptExeFSFilenameTable(cart, index, input, output);
|
||||
EncryptExeFSFilenameTable(cart, index, reader, writer);
|
||||
|
||||
// Get the ExeFS files offset
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
|
||||
|
||||
// Seek to the ExeFS
|
||||
input.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
reader.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
writer.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
uint ctroffsetE = cart.MediaUnitSize / 0x10;
|
||||
byte[] exefsIVWithOffset = Add(cart.ExeFSIV(index), ctroffsetE);
|
||||
var cipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey2C, exefsIVWithOffset);
|
||||
var cipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffset);
|
||||
|
||||
// Setup and perform the encryption
|
||||
uint exeFsSize = cart.GetExeFSSize(index) - cart.MediaUnitSize;
|
||||
PerformAESOperation(exeFsSize,
|
||||
cipher,
|
||||
input,
|
||||
output,
|
||||
reader,
|
||||
writer,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting - {s}"));
|
||||
|
||||
return true;
|
||||
@@ -697,37 +709,37 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <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(N3DS cart, int index, Stream input, Stream output)
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void EncryptExeFSFilenameTable(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Get ExeFS offset
|
||||
uint exeFsOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsOffset == 0 || exeFsOffset > input.Length)
|
||||
if (exeFsOffset == 0 || exeFsOffset > reader.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Seek to the ExeFS header
|
||||
input.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
reader.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
writer.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
|
||||
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));
|
||||
var cipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey2C, cart.ExeFSIV(index));
|
||||
|
||||
// Process the filename table
|
||||
byte[] readBytes = input.ReadBytes((int)cart.MediaUnitSize);
|
||||
byte[] readBytes = reader.ReadBytes((int)cart.MediaUnitSize);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
output.Write(processedBytes);
|
||||
writer.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
|
||||
input.Seek(0, SeekOrigin.Begin);
|
||||
reader.Seek(0, SeekOrigin.Begin);
|
||||
#endif
|
||||
output.Flush();
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -735,13 +747,13 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <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(N3DS cart, int index, Stream input, Stream output)
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void EncryptExeFSFileEntries(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Get ExeFS offset
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsHeaderOffset == 0 || exeFsHeaderOffset > input.Length)
|
||||
if (exeFsHeaderOffset == 0 || exeFsHeaderOffset > reader.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
@@ -773,19 +785,19 @@ namespace NDecrypt.Core
|
||||
// Create the ExeFS AES ciphers for this partition
|
||||
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);
|
||||
var firstCipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey, exefsIVWithOffsetForHeader);
|
||||
var secondCipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffsetForHeader);
|
||||
|
||||
// Seek to the file entry
|
||||
input.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
reader.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
writer.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
// Setup and perform the encryption
|
||||
PerformAESOperation(fileHeader.FileSize,
|
||||
firstCipher,
|
||||
secondCipher,
|
||||
input,
|
||||
output,
|
||||
reader,
|
||||
writer,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting - {fileHeader.FileName}...{s}"));
|
||||
}
|
||||
}
|
||||
@@ -795,13 +807,13 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <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(N3DS cart, int index, Stream input, Stream output)
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private bool EncryptRomFS(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Validate the RomFS
|
||||
uint romFsOffset = cart.GetRomFSOffset(index);
|
||||
if (romFsOffset == 0 || romFsOffset > input.Length)
|
||||
if (romFsOffset == 0 || romFsOffset > reader.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
|
||||
return false;
|
||||
@@ -815,24 +827,24 @@ namespace NDecrypt.Core
|
||||
}
|
||||
|
||||
// Seek to the RomFS
|
||||
input.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
output.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
reader.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
writer.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
|
||||
// Force setting encryption keys for partitions 1 and above
|
||||
if (index > 0)
|
||||
{
|
||||
var backupHeader = cart.BackupHeader;
|
||||
KeysMap[index].SetRomFSValues(backupHeader!.Flags!.BitMasks);
|
||||
_keysMap[index].SetRomFSValues(backupHeader!.Flags!.BitMasks);
|
||||
}
|
||||
|
||||
// Create the RomFS AES cipher for this partition
|
||||
var cipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey, cart.RomFSIV(index));
|
||||
var cipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey, cart.RomFSIV(index));
|
||||
|
||||
// Setup and perform the decryption
|
||||
PerformAESOperation(romFsSize,
|
||||
cipher,
|
||||
input,
|
||||
output,
|
||||
reader,
|
||||
writer,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} RomFS: Encrypting - {s}"));
|
||||
|
||||
return true;
|
||||
@@ -843,8 +855,8 @@ namespace NDecrypt.Core
|
||||
/// </summary>
|
||||
/// <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(N3DS cart, int index, Stream output)
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private static void UpdateEncryptCryptoAndMasks(N3DS cart, int index, Stream writer)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
@@ -855,24 +867,24 @@ namespace NDecrypt.Core
|
||||
return;
|
||||
|
||||
// Seek to the CryptoMethod location
|
||||
output.Seek(partitionOffset + 0x18B, SeekOrigin.Begin);
|
||||
writer.Seek(partitionOffset + 0x18B, SeekOrigin.Begin);
|
||||
|
||||
// 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;
|
||||
output.Write(cryptoMethod);
|
||||
output.Flush();
|
||||
writer.Write(cryptoMethod);
|
||||
writer.Flush();
|
||||
|
||||
// Seek to the BitMasks location
|
||||
output.Seek(partitionOffset + 0x18F, SeekOrigin.Begin);
|
||||
writer.Seek(partitionOffset + 0x18F, SeekOrigin.Begin);
|
||||
|
||||
// Write the new BitMasks flag
|
||||
BitMasks flag = cart.GetBitMasks(index);
|
||||
flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF;
|
||||
flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & backupHeader.Flags.BitMasks;
|
||||
output.Write((byte)flag);
|
||||
output.Flush();
|
||||
writer.Write((byte)flag);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>0.3.2</VersionPrefix>
|
||||
<VersionPrefix>0.4.0</VersionPrefix>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Title>NDecrypt</Title>
|
||||
|
||||
263
NDecrypt/Options.cs
Normal file
263
NDecrypt/Options.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET20 || NET35 || NET40 || NET452
|
||||
using System.Reflection;
|
||||
#endif
|
||||
|
||||
namespace NDecrypt
|
||||
{
|
||||
/// <summary>
|
||||
/// Set of options for the test executable
|
||||
/// </summary>
|
||||
internal sealed class Options
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Feature to process input files with
|
||||
/// </summary>
|
||||
public Feature Feature { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to config.json
|
||||
/// </summary>
|
||||
public string? ConfigPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable using development keys, if available
|
||||
/// </summary>
|
||||
public bool Development { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Force operation by avoiding sanity checks
|
||||
/// </summary>
|
||||
public bool Force { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Set of input paths to use for operations
|
||||
/// </summary>
|
||||
public List<string> InputPaths { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Output size and hashes to a companion file
|
||||
/// </summary>
|
||||
public bool OutputHashes { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Enable overwriting of the original file
|
||||
/// </summary>
|
||||
/// TODO: Change this to default false when hooked up
|
||||
public bool Overwrite { get; private set; } = true;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Parse commandline arguments into an Options object
|
||||
/// </summary>
|
||||
public static Options? ParseOptions(string[] args)
|
||||
{
|
||||
// If we have invalid arguments
|
||||
if (args == null || args.Length < 2)
|
||||
{
|
||||
Console.WriteLine("Not enough arguments");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create an Options object
|
||||
var options = new Options();
|
||||
|
||||
// Derive the feature
|
||||
switch (args[0])
|
||||
{
|
||||
case "-?":
|
||||
case "-h":
|
||||
case "--help":
|
||||
return null;
|
||||
|
||||
case "d":
|
||||
case "decrypt":
|
||||
options.Feature = Feature.Decrypt;
|
||||
break;
|
||||
|
||||
case "e":
|
||||
case "encrypt":
|
||||
options.Feature = Feature.Encrypt;
|
||||
break;
|
||||
|
||||
case "i":
|
||||
case "info":
|
||||
options.Feature = Feature.Info;
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.WriteLine($"Invalid operation: {args[0]}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse the options and paths
|
||||
for (int index = 1; index < args.Length; index++)
|
||||
{
|
||||
string arg = args[index];
|
||||
switch (arg)
|
||||
{
|
||||
case "-?":
|
||||
case "-h":
|
||||
case "--help":
|
||||
return null;
|
||||
|
||||
case "-c":
|
||||
case "--config":
|
||||
if (index == args.Length - 1)
|
||||
{
|
||||
Console.WriteLine("Invalid config path: no additional arguments found!");
|
||||
continue;
|
||||
}
|
||||
|
||||
index++;
|
||||
options.ConfigPath = args[index];
|
||||
if (string.IsNullOrEmpty(options.ConfigPath))
|
||||
Console.WriteLine($"Invalid config path: null or empty path found!");
|
||||
|
||||
options.ConfigPath = Path.GetFullPath(options.ConfigPath);
|
||||
if (!File.Exists(options.ConfigPath))
|
||||
{
|
||||
Console.WriteLine($"Invalid config path: file {options.ConfigPath} not found!");
|
||||
options.ConfigPath = null;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "-d":
|
||||
case "--development":
|
||||
options.Development = true;
|
||||
break;
|
||||
|
||||
case "-f":
|
||||
case "--force":
|
||||
options.Force = true;
|
||||
break;
|
||||
|
||||
case "--hash":
|
||||
options.Force = true;
|
||||
break;
|
||||
|
||||
case "-o":
|
||||
case "--overwrite":
|
||||
options.Overwrite = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
options.InputPaths.Add(arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate we have any input paths to work on
|
||||
if (options.InputPaths.Count == 0)
|
||||
{
|
||||
Console.WriteLine("At least one path is required!");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Derive the config path based on the runtime folder if not already set
|
||||
options.ConfigPath = DeriveConfigFile(options.ConfigPath);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display help text
|
||||
/// </summary>
|
||||
/// <param name="err">Additional error text to display, can be null to ignore</param>
|
||||
public static void DisplayHelp(string? err = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(err))
|
||||
Console.WriteLine($"Error: {err}");
|
||||
|
||||
Console.WriteLine("Cart Image Encrypt/Decrypt Tool");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("NDecrypt <operation> [options] <path> ...");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Operations:");
|
||||
Console.WriteLine("e, encrypt Encrypt the input files");
|
||||
Console.WriteLine("d, decrypt Decrypt the input files");
|
||||
Console.WriteLine("i, info Output file information");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Options:");
|
||||
Console.WriteLine("-?, -h, --help Display this help text and quit");
|
||||
Console.WriteLine("-c, --config <path> Path to config.json");
|
||||
Console.WriteLine("-d, --development Enable using development keys, if available");
|
||||
Console.WriteLine("-f, --force Force operation by avoiding sanity checks");
|
||||
Console.WriteLine("--hash Output size and hashes to a companion file");
|
||||
// Console.WriteLine("-o, --overwrite Overwrite input files instead of creating new ones"); // TODO: Print this when enabled
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("<path> can be any file or folder that contains uncompressed items.");
|
||||
Console.WriteLine("More than one path can be specified at a time.");
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Derive the full path to the config file, if possible
|
||||
/// </summary>
|
||||
private static string? DeriveConfigFile(string? config)
|
||||
{
|
||||
// If a path is passed in
|
||||
if (!string.IsNullOrEmpty(config))
|
||||
{
|
||||
config = Path.GetFullPath(config);
|
||||
if (File.Exists(config))
|
||||
return config;
|
||||
}
|
||||
|
||||
// Derive the keyfile path, if possible
|
||||
return GetFileLocation("config.json");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search for a file in local and config directories
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to check in local and config directories</param>
|
||||
/// <returns>The full path to the file if found, null otherwise</returns>
|
||||
/// <remarks>
|
||||
/// This method looks in the following locations:
|
||||
/// - %HOME%/.config/ndecrypt
|
||||
/// - Assembly location directory
|
||||
/// - Process runtime directory
|
||||
/// </remarks>
|
||||
private static string? GetFileLocation(string filename)
|
||||
{
|
||||
// User home directory
|
||||
#if NET20 || NET35
|
||||
string homeDir = Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%");
|
||||
homeDir = Path.Combine(Path.Combine(homeDir, ".config"), "ndecrypt");
|
||||
#else
|
||||
string homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
homeDir = Path.Combine(homeDir, ".config", "ndecrypt");
|
||||
#endif
|
||||
if (File.Exists(Path.Combine(homeDir, filename)))
|
||||
return Path.Combine(homeDir, filename);
|
||||
|
||||
// Local directory
|
||||
#if NET20 || NET35 || NET40 || NET452
|
||||
string runtimeDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
#else
|
||||
string runtimeDir = AppContext.BaseDirectory;
|
||||
#endif
|
||||
if (File.Exists(Path.Combine(runtimeDir, filename)))
|
||||
return Path.Combine(runtimeDir, filename);
|
||||
|
||||
// Process directory
|
||||
using var processModule = System.Diagnostics.Process.GetCurrentProcess().MainModule;
|
||||
string applicationDirectory = Path.GetDirectoryName(processModule?.FileName) ?? string.Empty;
|
||||
if (File.Exists(Path.Combine(applicationDirectory, filename)))
|
||||
return Path.Combine(applicationDirectory, filename);
|
||||
|
||||
// No file was found
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET20 || NET35 || NET40 || NET452
|
||||
using System.Reflection;
|
||||
#endif
|
||||
using NDecrypt.Core;
|
||||
|
||||
namespace NDecrypt
|
||||
@@ -17,129 +14,34 @@ namespace NDecrypt
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
// Get the options from the arguments
|
||||
var options = Options.ParseOptions(args);
|
||||
|
||||
// If we have an invalid state
|
||||
if (options == null)
|
||||
{
|
||||
DisplayHelp("Not enough arguments");
|
||||
Options.DisplayHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
Feature feature;
|
||||
if (args[0] == "decrypt" || args[0] == "d")
|
||||
{
|
||||
feature = Feature.Decrypt;
|
||||
}
|
||||
else if (args[0] == "encrypt" || args[0] == "e")
|
||||
{
|
||||
feature = Feature.Encrypt;
|
||||
}
|
||||
else if (args[0] == "info" || args[0] == "i")
|
||||
{
|
||||
feature = Feature.Info;
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayHelp($"Invalid operation: {args[0]}");
|
||||
return;
|
||||
}
|
||||
|
||||
bool development = false,
|
||||
force = false,
|
||||
outputHashes = false,
|
||||
useAesKeysTxt = false;
|
||||
string? config = null;
|
||||
string? keyfile = null;
|
||||
int start = 1;
|
||||
for (; start < args.Length; start++)
|
||||
{
|
||||
if (args[start] == "-a" || args[start] == "--aes-keys")
|
||||
{
|
||||
useAesKeysTxt = true;
|
||||
}
|
||||
else if (args[start] == "-d" || args[start] == "--development")
|
||||
{
|
||||
development = true;
|
||||
}
|
||||
else if (args[start] == "-f" || args[start] == "--force")
|
||||
{
|
||||
force = true;
|
||||
}
|
||||
else if (args[start] == "-h" || args[start] == "--hash")
|
||||
{
|
||||
outputHashes = true;
|
||||
}
|
||||
else if (args[start] == "-k" || args[start] == "--keyfile")
|
||||
{
|
||||
if (start == args.Length - 1)
|
||||
Console.WriteLine("Invalid keyfile path: no additional arguments found!");
|
||||
|
||||
start++;
|
||||
string tempPath = args[start];
|
||||
if (string.IsNullOrEmpty(tempPath))
|
||||
Console.WriteLine($"Invalid keyfile path: null or empty path found!");
|
||||
|
||||
tempPath = Path.GetFullPath(tempPath);
|
||||
if (!File.Exists(tempPath))
|
||||
Console.WriteLine($"Invalid keyfile path: file {tempPath} not found!");
|
||||
else
|
||||
keyfile = tempPath;
|
||||
}
|
||||
else if (args[start] == "-c" || args[start] == "--config")
|
||||
{
|
||||
if (start == args.Length - 1)
|
||||
Console.WriteLine("Invalid config path: no additional arguments found!");
|
||||
|
||||
start++;
|
||||
string tempPath = args[start];
|
||||
if (string.IsNullOrEmpty(tempPath))
|
||||
Console.WriteLine($"Invalid config path: null or empty path found!");
|
||||
|
||||
tempPath = Path.GetFullPath(tempPath);
|
||||
if (!File.Exists(tempPath))
|
||||
Console.WriteLine($"Invalid config path: file {tempPath} not found!");
|
||||
else
|
||||
config = tempPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Derive the config path based on the runtime folder if not already set
|
||||
config = DeriveConfigFile(config);
|
||||
|
||||
// Derive the keyfile path based on the runtime folder if not already set
|
||||
keyfile = DeriveKeyFile(keyfile, useAesKeysTxt);
|
||||
|
||||
// If we are using a Citra keyfile, there are no development keys
|
||||
if (development && useAesKeysTxt)
|
||||
{
|
||||
Console.WriteLine("AES keyfiles don't contain development keys; disabling the option...");
|
||||
development = false;
|
||||
}
|
||||
|
||||
// Initialize the decrypt args, if possible
|
||||
DecryptArgs decryptArgs;
|
||||
if (config != null)
|
||||
decryptArgs = new DecryptArgs(config);
|
||||
else
|
||||
decryptArgs = new DecryptArgs(keyfile, useAesKeysTxt);
|
||||
var decryptArgs = new DecryptArgs(options.ConfigPath); ;
|
||||
|
||||
// Create reusable tools
|
||||
_tools[FileType.NDS] = new DSTool(decryptArgs);
|
||||
_tools[FileType.N3DS] = new ThreeDSTool(development, decryptArgs);
|
||||
_tools[FileType.N3DS] = new ThreeDSTool(options.Development, decryptArgs);
|
||||
|
||||
for (int i = start; i < args.Length; i++)
|
||||
for (int i = 0; i < options.InputPaths.Count; i++)
|
||||
{
|
||||
if (File.Exists(args[i]))
|
||||
{
|
||||
ProcessPath(args[i], feature, force, outputHashes);
|
||||
ProcessFile(args[i], options);
|
||||
}
|
||||
else if (Directory.Exists(args[i]))
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(args[i], "*", SearchOption.AllDirectories))
|
||||
{
|
||||
ProcessPath(file, feature, force, outputHashes);
|
||||
ProcessFile(file, options);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -152,103 +54,44 @@ namespace NDecrypt
|
||||
/// <summary>
|
||||
/// Process a single file path
|
||||
/// </summary>
|
||||
/// <param name="path">File path to process</param>
|
||||
/// <param name="feature">Indicates what should be done to the file</param>
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <param name="outputHashes">Indicates if hashes should be output after a successful operation</param>
|
||||
private static void ProcessPath(string path, Feature feature, bool force, bool outputHashes)
|
||||
/// <param name="input">File path to process</param>
|
||||
/// <param name="options">Options indicating how to process the file</param>
|
||||
private static void ProcessFile(string input, Options options)
|
||||
{
|
||||
// Attempt to derive the tool for the path
|
||||
var tool = DeriveTool(path);
|
||||
var tool = DeriveTool(input);
|
||||
if (tool == null)
|
||||
return;
|
||||
|
||||
Console.WriteLine($"Processing {path}");
|
||||
Console.WriteLine($"Processing {input}");
|
||||
|
||||
// Derive the output filename, if required
|
||||
string? output = null;
|
||||
if (!options.Overwrite)
|
||||
output = GetOutputFile(input, options);
|
||||
|
||||
// Encrypt or decrypt the file as requested
|
||||
if (feature == Feature.Encrypt && !tool.EncryptFile(path, force))
|
||||
if (options.Feature == Feature.Encrypt && !tool.EncryptFile(input, output, options.Force))
|
||||
{
|
||||
Console.WriteLine("Encryption failed!");
|
||||
return;
|
||||
}
|
||||
else if (feature == Feature.Decrypt && !tool.DecryptFile(path, force))
|
||||
else if (options.Feature == Feature.Decrypt && !tool.DecryptFile(input, output, options.Force))
|
||||
{
|
||||
Console.WriteLine("Decryption failed!");
|
||||
return;
|
||||
}
|
||||
else if (feature == Feature.Info)
|
||||
else if (options.Feature == Feature.Info)
|
||||
{
|
||||
string? infoString = tool.GetInformation(path);
|
||||
string? infoString = tool.GetInformation(input);
|
||||
infoString ??= "There was a problem getting file information!";
|
||||
|
||||
Console.WriteLine(infoString);
|
||||
}
|
||||
|
||||
// Output the file hashes, if expected
|
||||
if (outputHashes)
|
||||
WriteHashes(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display a basic help text
|
||||
/// </summary>
|
||||
/// <param name="err">Additional error text to display, can be null to ignore</param>
|
||||
private static void DisplayHelp(string? err = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(err))
|
||||
Console.WriteLine($"Error: {err}");
|
||||
|
||||
Console.WriteLine(@"Usage: NDecrypt <operation> [flags] <path> ...
|
||||
|
||||
Possible values for <operation>:
|
||||
e, encrypt - Encrypt the input files
|
||||
d, decrypt - Decrypt the input files
|
||||
i, info - Output file information
|
||||
|
||||
Possible values for [flags] (one or more can be used):
|
||||
-c, --config <path> Path to config.json
|
||||
-a, --aes-keys Enable using aes_keys.txt instead of keys.bin
|
||||
-k, --keyfile <path> Path to keys.bin or aes_keys.txt
|
||||
-d, --development Enable using development keys, if available
|
||||
-f, --force Force operation by avoiding sanity checks
|
||||
-h, --hash Output size and hashes to a companion file
|
||||
|
||||
<path> can be any file or folder that contains uncompressed items.
|
||||
More than one path can be specified at a time.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Derive the full path to the config file, if possible
|
||||
/// </summary>
|
||||
private static string? DeriveConfigFile(string? config)
|
||||
{
|
||||
// If a path is passed in
|
||||
if (!string.IsNullOrEmpty(config))
|
||||
{
|
||||
config = Path.GetFullPath(config);
|
||||
if (File.Exists(config))
|
||||
return config;
|
||||
}
|
||||
|
||||
// Derive the keyfile path, if possible
|
||||
return GetFileLocation("config.json");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Derive the full path to the keyfile, if possible
|
||||
/// </summary>
|
||||
private static string? DeriveKeyFile(string? keyfile, bool useAesKeysTxt)
|
||||
{
|
||||
// If a path is passed in
|
||||
if (!string.IsNullOrEmpty(keyfile))
|
||||
{
|
||||
keyfile = Path.GetFullPath(keyfile);
|
||||
if (File.Exists(keyfile))
|
||||
return keyfile;
|
||||
}
|
||||
|
||||
// Derive the keyfile path, if possible
|
||||
return GetFileLocation(useAesKeysTxt ? "aes_keys.txt" : "keys.bin");
|
||||
if (options.OutputHashes)
|
||||
WriteHashes(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -313,46 +156,28 @@ More than one path can be specified at a time.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search for a file in local and config directories
|
||||
/// Derive an output filename from the input, if possible
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to check in local and config directories</param>
|
||||
/// <returns>The full path to the file if found, null otherwise</returns>
|
||||
/// <remarks>
|
||||
/// This method looks in the following locations:
|
||||
/// - %HOME%/.config/ndecrypt
|
||||
/// - Assembly location directory
|
||||
/// - Process runtime directory
|
||||
/// </remarks>
|
||||
private static string? GetFileLocation(string filename)
|
||||
/// <param name="filename">Name of the input file to derive from</param>
|
||||
/// <param name="options">Options indicating how to process the file</param>
|
||||
/// <returns>Output filename based on the input</returns>
|
||||
private static string GetOutputFile(string filename, Options options)
|
||||
{
|
||||
// User home directory
|
||||
#if NET20 || NET35
|
||||
string homeDir = Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%");
|
||||
homeDir = Path.Combine(Path.Combine(homeDir, ".config"), "ndecrypt");
|
||||
#else
|
||||
string homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
homeDir = Path.Combine(homeDir, ".config", "ndecrypt");
|
||||
#endif
|
||||
if (File.Exists(Path.Combine(homeDir, filename)))
|
||||
return Path.Combine(homeDir, filename);
|
||||
// Empty filenames are passed back
|
||||
if (filename.Length == 0)
|
||||
return filename;
|
||||
|
||||
// Local directory
|
||||
#if NET20 || NET35 || NET40 || NET452
|
||||
string runtimeDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
#else
|
||||
string runtimeDir = AppContext.BaseDirectory;
|
||||
#endif
|
||||
if (File.Exists(Path.Combine(runtimeDir, filename)))
|
||||
return Path.Combine(runtimeDir, filename);
|
||||
// TODO: Replace the suffix instead of just appending
|
||||
// TODO: Ensure that the input and output aren't the same
|
||||
|
||||
// Process directory
|
||||
using var processModule = System.Diagnostics.Process.GetCurrentProcess().MainModule;
|
||||
string applicationDirectory = Path.GetDirectoryName(processModule?.FileName) ?? string.Empty;
|
||||
if (File.Exists(Path.Combine(applicationDirectory, filename)))
|
||||
return Path.Combine(applicationDirectory, filename);
|
||||
// Append '.enc' or '.dec' based on the feature
|
||||
if (options.Feature == Feature.Decrypt)
|
||||
filename += ".dec";
|
||||
else if (options.Feature == Feature.Encrypt)
|
||||
filename += ".enc";
|
||||
|
||||
// No file was found
|
||||
return null;
|
||||
// Return the reformatted name
|
||||
return filename;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -371,11 +196,9 @@ More than one path can be specified at a time.");
|
||||
return;
|
||||
|
||||
// Open the output file and write the hashes
|
||||
using (var fs = File.Create(Path.GetFullPath(filename) + ".hash"))
|
||||
using (var sw = new StreamWriter(fs))
|
||||
{
|
||||
sw.WriteLine(hashString);
|
||||
}
|
||||
using var fs = File.Create(Path.GetFullPath(filename) + ".hash");
|
||||
using var sw = new StreamWriter(fs);
|
||||
sw.WriteLine(hashString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
README.md
28
README.md
@@ -32,12 +32,11 @@ For the latest WIP build here: [Rolling Release](https://github.com/SabreTools/N
|
||||
i, info - Output file information
|
||||
|
||||
Possible values for [flags] (one or more can be used):
|
||||
-c, --config <path> Path to config.json
|
||||
-a, --aes-keys Enable using aes_keys.txt instead of keys.bin
|
||||
-k, --keyfile <path> Path to keys.bin or aes_keys.txt
|
||||
-d, --development Enable using development keys, if available
|
||||
-f, --force Force operation by avoiding sanity checks
|
||||
-h, --hash Output size and hashes to a companion file
|
||||
-?, -h, --help Display this help text and quit
|
||||
-c, --config <path> Path to config.json
|
||||
-d, --development Enable using development keys, if available
|
||||
-f, --force Force operation by avoiding sanity checks
|
||||
--hash Output size and hashes to a companion file
|
||||
|
||||
<path> can be any file or folder that contains uncompressed items.
|
||||
More than one path can be specified at a time.
|
||||
@@ -47,13 +46,12 @@ For the latest WIP build here: [Rolling Release](https://github.com/SabreTools/N
|
||||
- Input files are overwritten, even if they are only partially processed. You should make backups of the files you're working on if you're worried about this.
|
||||
- Mixed folders or inputs are also accepted, you can decrypt or encrypt multiple files, regardless of their type. This being said, you can only do encrypt _OR_ decrypt at one time.
|
||||
- Required files will automatically be searched for in the application runtime directory as well as `%HOME%/.config/ndecrypt`, also known as `%USERPROFILE%\.config\ndecrypt` on Windows.
|
||||
- If found, `config.json` will take priority over both `keys.bin` and `aes_keys.txt`, even if `-a` and/or `-k` are defined. You've been warned.
|
||||
|
||||
## I feel like something is missing
|
||||
|
||||
There are 3 major files that you can use to give NDecrypt that extra _oomph_ of functionality that it really needs. That is, you can't do any encryption or decryption without at least one of these present. I can't give you the files and I can't generate them for you on the fly with the correct values. Keys are a thorny thing and I just do not want to deal with them. Values are validated, at least, but you'll only get yelled at on run if one of them is wrong. Don't worry, they're just disabled, not removed.
|
||||
There is a major file that you can use to give NDecrypt that extra _oomph_ of functionality that it really needs. That is, you can't do any encryption or decryption without it present. I can't give you the files and I can't generate them for you on the fly with the correct values. Keys are a thorny thing and I just do not want to deal with them. Values are validated, at least, but you'll only get yelled at on run if one of them is wrong. Don't worry, they're just disabled, not removed.
|
||||
|
||||
This convenient table gives an overview of the 3 supported types, the keys that they provide, as well as an even more convenient map to a well-known external tool's configuration format.
|
||||
This convenient table gives an overview of mappings between the current `config.json` type along with the 2 formerly-supported types and a completely unsupported but common type.
|
||||
|
||||
| `config.json` | `keys.bin` order | `aes_keys.txt` | rom-properties `keys.conf` |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -70,27 +68,25 @@ This convenient table gives an overview of the 3 supported types, the keys that
|
||||
|
||||
**Note:** `Dev*` keys are not required for the vast majority of normal operations. They're only used if the `-d` option is included. Working with your own retail carts will pretty much never require these, so don't drive yourself silly dealing with them.
|
||||
|
||||
**Note:** The `NitroEncryptionData` field is also known as the "Blowfish table" for Nintendo DS carts. It's stored in the same hex string format as the other keys. There's some complicated stuff about how it's used and where it's stored, but all you need to know is that it wasn't required for `keys.bin` and `aes_keys.txt` but will be for `config.json`.
|
||||
**Note:** The `NitroEncryptionData` field is also known as the "Blowfish table" for Nintendo DS carts. It's stored in the same hex string format as the other keys. There's some complicated stuff about how it's used and where it's stored, but all you need to know is that it is required.
|
||||
|
||||
**Community Note:** If you would like to try out the new `config.json` format below and already have either `keys.bin` or `aes_keys.txt`, consider using [this helpful community-made script](https://gist.github.com/Dimensional/82f212a0b35bcf9caaa2bc9a70b3a92a) to make your life a bit easier.
|
||||
**Community Note:** If you have used previous versions of NDecrypt and already have either `keys.bin` or `aes_keys.txt`, consider using [this helpful community-made script](https://gist.github.com/Dimensional/82f212a0b35bcf9caaa2bc9a70b3a92a) to make your life a bit easier. It will convert them into the new `config.json` format that will be supported from here on out.
|
||||
|
||||
### `config.json`
|
||||
|
||||
The up-and-coming, shiny, new, exciting, JSON-based format for storing the encryption keys that you need for Nintendo DS, 3DS, and New 3DS. This JSON file is not generated by anything, but maps pretty much one-to-one with the code inside of NDecrypt, making it super convenient to use. Keys provided need to be hex strings (e.g. `"AABBCCDD"`). Any keys that are left with `null` or `""` as the value will be ignored. See [the sample config](https://github.com/SabreTools/NDecrypt/blob/master/config-default.json) that I've nicely generated for you. You're welcome.
|
||||
|
||||
This is used if it's found, even if you have a `keys.bin` file or if you're using the `-a` flag. It's intentionally very bullish about being used because this will be the singular format for keys in the future. I know I mentioned this above as well, but I also know users don't like reading.
|
||||
|
||||
In the future, this file will be automatically generated on first run along with some cutesy little message telling you to fill it out when you get a chance. It's not doing it right now because I don't want to confuse users. Including those reading this. How meta.
|
||||
|
||||
### `keys.bin`
|
||||
### `keys.bin` (Deprecated)
|
||||
|
||||
This is the OG of NDecrypt key file formats. It's a weird, binary blob of a format that is composed of little-endian values (most common extraction methods produce big endian, so keep that in mind). It's only compatible wtih Nintendo 3DS and New 3DS keys and is incredibly inflexible in its layout. The little-endianness of it is a relic of how keys were handled in-code previously and I really can't fix it now. If you don't have a key, it needs to be filled with `0x00` bytes so it doesn't mess up the read. Yeah.
|
||||
|
||||
Oddly, this gets confused with some similar format that GodMode9 works with, but it has nothing to do with it. If you try to use one of those files in place of this one, something will probably break. It wasn't intentional, I just didn't look ahead of time. See the table in the main part of this section for the order the keys need to be stored in.
|
||||
|
||||
### `aes_keys.txt`
|
||||
### `aes_keys.txt` (Deprecated)
|
||||
|
||||
This is an INI-based format that was super popular among 3DS emulators and probably still is. To use this over `keys.bin`, the `-a` flag has to be included or else it won't be found. Yes, even if `keys.bin` isn't even in the folder. Weird thing, I know, but just roll with it please. The one major downside to this is that development keys can't be defined in this format. If you forget this and use `-d` anyway, NDecrypt will disable that flag for you. You're welcome.
|
||||
This is an INI-based format that was super popular among 3DS emulators and probably still is. Weird thing, I know, but just roll with it please.
|
||||
|
||||
## But does it work?
|
||||
|
||||
|
||||
Reference in New Issue
Block a user