From a518f1fd9428e9cc2cb87aba081dd714998a69c9 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Sun, 22 Mar 2026 00:04:51 -0400 Subject: [PATCH] Configuration should not be a Core class --- NDecrypt.Core/DSTool.cs | 70 +++- NDecrypt.Core/PartitionKeys.cs | 20 +- .../{DecryptArgs.cs => ThreeDSDecryptArgs.cs} | 356 ++++++++---------- NDecrypt.Core/ThreeDSTool.cs | 18 +- {NDecrypt.Core => NDecrypt}/Configuration.cs | 2 +- NDecrypt/Features/BaseFeature.cs | 40 +- 6 files changed, 264 insertions(+), 242 deletions(-) rename NDecrypt.Core/{DecryptArgs.cs => ThreeDSDecryptArgs.cs} (61%) rename {NDecrypt.Core => NDecrypt}/Configuration.cs (98%) diff --git a/NDecrypt.Core/DSTool.cs b/NDecrypt.Core/DSTool.cs index 167aab1..b9c948d 100644 --- a/NDecrypt.Core/DSTool.cs +++ b/NDecrypt.Core/DSTool.cs @@ -1,30 +1,71 @@ using System; using System.IO; using System.Text; -#if NETFRAMEWORK || NETSTANDARD2_0_OR_GREATER +using SabreTools.Hashing; using SabreTools.IO.Extensions; -#endif using SabreTools.Serialization.Wrappers; namespace NDecrypt.Core { public class DSTool : ITool { - /// - /// Decryption args to use while processing - /// - private readonly DecryptArgs _decryptArgs; + #region Properties - public DSTool(DecryptArgs decryptArgs) + /// + /// Blowfish Table + /// + public byte[] BlowfishTable { - _decryptArgs = decryptArgs; - } + get; + set + { + // Ignore missing encryption data + if (value.Length == 0) + return; + + // Validate the blowfish table data + byte[]? actual = HashTool.GetByteArrayHashArray(value, HashType.SHA512); + if (actual is null || !actual.EqualsExactly(ExpectedNitroSha512Hash)) + return; + + // Assign the validated value + field = value; + } + } = []; + + #endregion + + #region Internal Test Values + + /// + /// Expected hash for NitroEncryptionData + /// + private static readonly byte[] ExpectedNitroSha512Hash = + [ + 0x1A, 0xD6, 0x40, 0x21, 0xFC, 0x3D, 0x1A, 0x9A, + 0x9B, 0xC0, 0x88, 0x8E, 0x2E, 0x68, 0xDE, 0x4E, + 0x8A, 0x60, 0x6B, 0x86, 0x63, 0x22, 0xD2, 0xC7, + 0xC6, 0xD7, 0xD6, 0xCE, 0x65, 0xF5, 0xBA, 0xA7, + 0xEA, 0x69, 0x63, 0x7E, 0xC9, 0xE4, 0x57, 0x7B, + 0x01, 0xFD, 0xCE, 0xC2, 0x26, 0x3B, 0xD9, 0x0D, + 0x84, 0x57, 0xC2, 0x00, 0xB8, 0x56, 0x9F, 0xE5, + 0x56, 0xDA, 0x8D, 0xDE, 0x84, 0xB8, 0x8E, 0xE4, + ]; + + #endregion #region Encrypt /// public bool EncryptFile(string input, string? output, bool force) { + // If the blowfish table is not set, do not process + if (BlowfishTable.Length == 0) + { + Console.WriteLine("Error: Nitro encryption data not provided!"); + return false; + } + try { // If the output is provided, copy the input file @@ -52,7 +93,7 @@ namespace NDecrypt.Core } // Encrypt the secure area - nitro.EncryptSecureArea(_decryptArgs.NitroEncryptionData, force); + nitro.EncryptSecureArea(BlowfishTable, force); // Write the encrypted secure area using var writer = File.Open(output, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); @@ -77,6 +118,13 @@ namespace NDecrypt.Core /// public bool DecryptFile(string input, string? output, bool force) { + // If the blowfish table is not set, do not process + if (BlowfishTable.Length == 0) + { + Console.WriteLine("Error: Nitro encryption data not provided!"); + return false; + } + try { // If the output is provided, copy the input file @@ -104,7 +152,7 @@ namespace NDecrypt.Core } // Decrypt the secure area - nitro.DecryptSecureArea(_decryptArgs.NitroEncryptionData, force); + nitro.DecryptSecureArea(BlowfishTable, force); // Write the decrypted secure area using var writer = File.Open(output, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); diff --git a/NDecrypt.Core/PartitionKeys.cs b/NDecrypt.Core/PartitionKeys.cs index 929bcd3..75f300a 100644 --- a/NDecrypt.Core/PartitionKeys.cs +++ b/NDecrypt.Core/PartitionKeys.cs @@ -22,7 +22,7 @@ namespace NDecrypt.Core /// /// Decryption args to use while processing /// - private readonly DecryptArgs _decryptArgs; + private readonly ThreeDSDecryptArgs _decryptArgs; /// /// Indicates if development images are expected @@ -37,11 +37,9 @@ namespace NDecrypt.Core /// BitMasks from the partition or backup header /// CryptoMethod from the partition or backup header /// Determine if development keys are used - public PartitionKeys(DecryptArgs args, byte[]? signature, BitMasks masks, CryptoMethod method, bool development) + public PartitionKeys(ThreeDSDecryptArgs args, byte[]? signature, BitMasks masks, CryptoMethod method, bool development) { // Validate inputs - if (args.IsReady != true) - throw new InvalidOperationException($"{nameof(args)} must be initialized before use"); if (signature is not null && signature.Length < 16) throw new ArgumentOutOfRangeException(nameof(signature), $"{nameof(signature)} must be at least 16 bytes"); @@ -51,7 +49,7 @@ namespace NDecrypt.Core // Set the standard KeyX values KeyX = new byte[16]; - KeyX2C = development ? args.DevKeyX0x2C : args.KeyX0x2C; + KeyX2C = development ? _decryptArgs.DevKeyX0x2C : _decryptArgs.KeyX0x2C; // Backup headers can't have a KeyY value set KeyY = new byte[16]; @@ -63,7 +61,7 @@ namespace NDecrypt.Core NormalKey2C = KeyX2C.RotateLeft(2); NormalKey2C = NormalKey2C.Xor(KeyY); - NormalKey2C = NormalKey2C.Add(add: args.AESHardwareConstant); + NormalKey2C = NormalKey2C.Add(add: _decryptArgs.AESHardwareConstant); NormalKey2C = NormalKey2C.RotateLeft(87); // Special case for zero-key @@ -84,22 +82,22 @@ namespace NDecrypt.Core { case CryptoMethod.Original: Console.WriteLine("Encryption Method: Key 0x2C"); - KeyX = development ? args.DevKeyX0x2C : args.KeyX0x2C; + KeyX = development ? _decryptArgs.DevKeyX0x2C : _decryptArgs.KeyX0x2C; break; case CryptoMethod.Seven: Console.WriteLine("Encryption Method: Key 0x25"); - KeyX = development ? args.DevKeyX0x25 : args.KeyX0x25; + KeyX = development ? _decryptArgs.DevKeyX0x25 : _decryptArgs.KeyX0x25; break; case CryptoMethod.NineThree: Console.WriteLine("Encryption Method: Key 0x18"); - KeyX = development ? args.DevKeyX0x18 : args.KeyX0x18; + KeyX = development ? _decryptArgs.DevKeyX0x18 : _decryptArgs.KeyX0x18; break; case CryptoMethod.NineSix: Console.WriteLine("Encryption Method: Key 0x1B"); - KeyX = development ? args.DevKeyX0x1B : args.KeyX0x1B; + KeyX = development ? _decryptArgs.DevKeyX0x1B : _decryptArgs.KeyX0x1B; break; // This should never happen @@ -111,7 +109,7 @@ namespace NDecrypt.Core // Set the normal key based on the new KeyX value NormalKey = KeyX.RotateLeft(2); NormalKey = NormalKey.Xor(KeyY); - NormalKey = NormalKey.Add(args.AESHardwareConstant); + NormalKey = NormalKey.Add(_decryptArgs.AESHardwareConstant); NormalKey = NormalKey.RotateLeft(87); } diff --git a/NDecrypt.Core/DecryptArgs.cs b/NDecrypt.Core/ThreeDSDecryptArgs.cs similarity index 61% rename from NDecrypt.Core/DecryptArgs.cs rename to NDecrypt.Core/ThreeDSDecryptArgs.cs index 347c839..7cfe218 100644 --- a/NDecrypt.Core/DecryptArgs.cs +++ b/NDecrypt.Core/ThreeDSDecryptArgs.cs @@ -1,97 +1,205 @@ -using System; -using System.IO; -using SabreTools.Hashing; using SabreTools.IO.Encryption; using SabreTools.IO.Extensions; namespace NDecrypt.Core { - public class DecryptArgs + /// + /// Nintendo 3DS encryption/decryption values + /// + public class ThreeDSDecryptArgs { - #region Common Fields - - /// - /// Represents if all of the keys have been initialized properly - /// - public bool? IsReady { get; private set; } - - #endregion - - #region DS-Specific Fields - - /// - /// Blowfish Table - /// - public byte[] NitroEncryptionData { get; private set; } = []; - - #endregion - - #region 3DS-Specific Fields - /// /// AES Hardware Constant /// - public byte[] AESHardwareConstant { get; private set; } = []; + /// TODO: Validate this value on assignment + public byte[] AESHardwareConstant { get; set; } = []; /// /// KeyX 0x18 (New 3DS 9.3) /// - public byte[] KeyX0x18 { get; private set; } = []; + public byte[] KeyX0x18 + { + get; + set + { + // Ignore missing key data + if (value.Length == 0) + return; + + // Validate the key data + var cipher = AESCTR.CreateEncryptionCipher(KeyX0x18, TestIV); + byte[] actual = cipher.ProcessBytes(TestPattern); + if (!actual.EqualsExactly(ExpectedKeyX0x18)) + return; + + // Assign the validated value + field = value; + } + } = []; /// /// Dev KeyX 0x18 (New 3DS 9.3) /// - public byte[] DevKeyX0x18 { get; private set; } = []; + public byte[] DevKeyX0x18 + { + get; + set + { + // Ignore missing key data + if (value.Length == 0) + return; + + // Validate the key data + var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x18, TestIV); + byte[] actual = cipher.ProcessBytes(TestPattern); + if (!actual.EqualsExactly(ExpectedDevKeyX0x18)) + return; + + // Assign the validated value + field = value; + } + } = []; /// /// KeyX 0x1B (New 3DS 9.6) /// - public byte[] KeyX0x1B { get; private set; } = []; + public byte[] KeyX0x1B + { + get; + set + { + // Ignore missing key data + if (value.Length == 0) + return; + + // Validate the key data + var cipher = AESCTR.CreateEncryptionCipher(KeyX0x1B, TestIV); + byte[] actual = cipher.ProcessBytes(TestPattern); + if (!actual.EqualsExactly(ExpectedKeyX0x1B)) + return; + + // Assign the validated value + field = value; + } + } = []; /// /// Dev KeyX 0x1B New 3DS 9.6) /// - public byte[] DevKeyX0x1B { get; private set; } = []; + public byte[] DevKeyX0x1B + { + get; + set + { + // Ignore missing key data + if (value.Length == 0) + return; + + // Validate the key data + var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x1B, TestIV); + byte[] actual = cipher.ProcessBytes(TestPattern); + if (!actual.EqualsExactly(ExpectedDevKeyX0x1B)) + return; + + // Assign the validated value + field = value; + } + } = []; /// /// KeyX 0x25 (> 7.x) /// - public byte[] KeyX0x25 { get; private set; } = []; + public byte[] KeyX0x25 + { + get; + set + { + // Ignore missing key data + if (value.Length == 0) + return; + + // Validate the key data + var cipher = AESCTR.CreateEncryptionCipher(KeyX0x25, TestIV); + byte[] actual = cipher.ProcessBytes(TestPattern); + if (!actual.EqualsExactly(ExpectedKeyX0x25)) + return; + + // Assign the validated value + field = value; + } + } = []; /// /// Dev KeyX 0x25 (> 7.x) /// - public byte[] DevKeyX0x25 { get; private set; } = []; + public byte[] DevKeyX0x25 + { + get; + set + { + // Ignore missing key data + if (value.Length == 0) + return; + + // Validate the key data + var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x25, TestIV); + byte[] actual = cipher.ProcessBytes(TestPattern); + if (!actual.EqualsExactly(ExpectedDevKeyX0x25)) + return; + + // Assign the validated value + field = value; + } + } = []; /// /// KeyX 0x2C (< 6.x) /// - public byte[] KeyX0x2C { get; private set; } = []; + public byte[] KeyX0x2C + { + get; + set + { + // Ignore missing key data + if (value.Length == 0) + return; + + // Validate the key data + var cipher = AESCTR.CreateEncryptionCipher(KeyX0x2C, TestIV); + byte[] actual = cipher.ProcessBytes(TestPattern); + if (!actual.EqualsExactly(ExpectedKeyX0x2C)) + return; + + // Assign the validated value + field = value; + } + } = []; /// /// Dev KeyX 0x2C (< 6.x) /// - public byte[] DevKeyX0x2C { get; private set; } = []; + public byte[] DevKeyX0x2C + { + get; + set + { + // Ignore missing key data + if (value.Length == 0) + return; - #endregion + // Validate the key data + var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x2C, TestIV); + byte[] actual = cipher.ProcessBytes(TestPattern); + if (!actual.EqualsExactly(ExpectedDevKeyX0x2C)) + return; + + // Assign the validated value + field = value; + } + } = []; #region Internal Test Values - /// - /// Expected hash for NitroEncryptionData - /// - private static readonly byte[] ExpectedNitroSha512Hash = - [ - 0x1A, 0xD6, 0x40, 0x21, 0xFC, 0x3D, 0x1A, 0x9A, - 0x9B, 0xC0, 0x88, 0x8E, 0x2E, 0x68, 0xDE, 0x4E, - 0x8A, 0x60, 0x6B, 0x86, 0x63, 0x22, 0xD2, 0xC7, - 0xC6, 0xD7, 0xD6, 0xCE, 0x65, 0xF5, 0xBA, 0xA7, - 0xEA, 0x69, 0x63, 0x7E, 0xC9, 0xE4, 0x57, 0x7B, - 0x01, 0xFD, 0xCE, 0xC2, 0x26, 0x3B, 0xD9, 0x0D, - 0x84, 0x57, 0xC2, 0x00, 0xB8, 0x56, 0x9F, 0xE5, - 0x56, 0xDA, 0x8D, 0xDE, 0x84, 0xB8, 0x8E, 0xE4, - ]; - /// /// Initial value for key validation tests /// @@ -237,155 +345,5 @@ namespace NDecrypt.Core ]; #endregion - - /// - /// Setup all of the necessary constants - /// - /// Path to the keyfile - public DecryptArgs(string? config) - { - if (config is null || !File.Exists(config)) - { - IsReady = false; - return; - } - - // Try to read the configuration file - var configObj = Configuration.Create(config); - if (configObj is null) - { - IsReady = false; - return; - } - - // Set the fields from the configuration - 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(); - } - - /// - /// Validate that all keys provided are going to be valid - /// - /// Does not know what the keys are, just the result - private void ValidateKeys() - { - // NitroEncryptionData - if (NitroEncryptionData.Length > 0) - { - byte[]? actual = HashTool.GetByteArrayHashArray(NitroEncryptionData, HashType.SHA512); - if (actual is null || !actual.EqualsExactly(ExpectedNitroSha512Hash)) - { - Console.WriteLine($"NitroEncryptionData invalid value, disabling..."); - NitroEncryptionData = []; - } - } - - // KeyX0x18 - if (KeyX0x18.Length > 0) - { - var cipher = AESCTR.CreateEncryptionCipher(KeyX0x18, TestIV); - byte[] actual = cipher.ProcessBytes(TestPattern); - if (!actual.EqualsExactly(ExpectedKeyX0x18)) - { - Console.WriteLine($"KeyX0x18 invalid value, disabling..."); - KeyX0x18 = []; - } - } - - // DevKeyX0x18 - if (DevKeyX0x18.Length > 0) - { - var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x18, TestIV); - byte[] actual = cipher.ProcessBytes(TestPattern); - if (!actual.EqualsExactly(ExpectedDevKeyX0x18)) - { - Console.WriteLine($"DevKeyX0x18 invalid value, disabling..."); - DevKeyX0x18 = []; - } - } - - // KeyX0x1B - if (KeyX0x1B.Length > 0) - { - var cipher = AESCTR.CreateEncryptionCipher(KeyX0x1B, TestIV); - byte[] actual = cipher.ProcessBytes(TestPattern); - if (!actual.EqualsExactly(ExpectedKeyX0x1B)) - { - Console.WriteLine($"KeyX0x1B invalid value, disabling..."); - KeyX0x1B = []; - } - } - - // DevKeyX0x1B - if (DevKeyX0x1B.Length > 0) - { - var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x1B, TestIV); - byte[] actual = cipher.ProcessBytes(TestPattern); - if (!actual.EqualsExactly(ExpectedDevKeyX0x1B)) - { - Console.WriteLine($"DevKeyX0x1B invalid value, disabling..."); - DevKeyX0x1B = []; - } - } - - // KeyX0x25 - if (KeyX0x25.Length > 0) - { - var cipher = AESCTR.CreateEncryptionCipher(KeyX0x25, TestIV); - byte[] actual = cipher.ProcessBytes(TestPattern); - if (!actual.EqualsExactly(ExpectedKeyX0x25)) - { - Console.WriteLine($"KeyX0x25 invalid value, disabling..."); - KeyX0x25 = []; - } - } - - // DevKeyX0x25 - if (DevKeyX0x25.Length > 0) - { - var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x25, TestIV); - byte[] actual = cipher.ProcessBytes(TestPattern); - if (!actual.EqualsExactly(ExpectedDevKeyX0x25)) - { - Console.WriteLine($"DevKeyX0x25 invalid value, disabling..."); - DevKeyX0x25 = []; - } - } - - // KeyX0x2C - if (KeyX0x2C.Length > 0) - { - var cipher = AESCTR.CreateEncryptionCipher(KeyX0x2C, TestIV); - byte[] actual = cipher.ProcessBytes(TestPattern); - if (!actual.EqualsExactly(ExpectedKeyX0x2C)) - { - Console.WriteLine($"KeyX0x2C invalid value, disabling..."); - KeyX0x2C = []; - } - } - - // DevKeyX0x2C - if (DevKeyX0x2C.Length > 0) - { - var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x2C, TestIV); - byte[] actual = cipher.ProcessBytes(TestPattern); - if (!actual.EqualsExactly(ExpectedDevKeyX0x2C)) - { - Console.WriteLine($"DevKeyX0x2C invalid value, disabling..."); - DevKeyX0x2C = []; - } - } - } } } diff --git a/NDecrypt.Core/ThreeDSTool.cs b/NDecrypt.Core/ThreeDSTool.cs index f07c1f9..f2d2cfa 100644 --- a/NDecrypt.Core/ThreeDSTool.cs +++ b/NDecrypt.Core/ThreeDSTool.cs @@ -14,7 +14,7 @@ namespace NDecrypt.Core /// /// Decryption args to use while processing /// - private readonly DecryptArgs _decryptArgs; + private readonly ThreeDSDecryptArgs _decryptArgs; /// /// Indicates if development images are expected @@ -26,7 +26,7 @@ namespace NDecrypt.Core /// private readonly PartitionKeys[] _keysMap = new PartitionKeys[8]; - public ThreeDSTool(bool development, DecryptArgs decryptArgs) + public ThreeDSTool(bool development, ThreeDSDecryptArgs decryptArgs) { _development = development; _decryptArgs = decryptArgs; @@ -37,13 +37,6 @@ namespace NDecrypt.Core /// public bool DecryptFile(string input, string? output, bool force) { - // Ensure the constants are all set - if (_decryptArgs.IsReady != true) - { - Console.WriteLine("Could not read keys. Please make sure the file exists and try again."); - return false; - } - try { // If the output is provided, copy the input file @@ -456,13 +449,6 @@ namespace NDecrypt.Core /// public bool EncryptFile(string input, string? output, bool force) { - // Ensure the constants are all set - if (_decryptArgs.IsReady != true) - { - Console.WriteLine("Could not read keys. Please make sure the file exists and try again."); - return false; - } - try { // If the output is provided, copy the input file diff --git a/NDecrypt.Core/Configuration.cs b/NDecrypt/Configuration.cs similarity index 98% rename from NDecrypt.Core/Configuration.cs rename to NDecrypt/Configuration.cs index 69a5e64..7a257c6 100644 --- a/NDecrypt.Core/Configuration.cs +++ b/NDecrypt/Configuration.cs @@ -1,7 +1,7 @@ using System.IO; using Newtonsoft.Json; -namespace NDecrypt.Core +namespace NDecrypt { internal class Configuration { diff --git a/NDecrypt/Features/BaseFeature.cs b/NDecrypt/Features/BaseFeature.cs index 0a6d975..18588bc 100644 --- a/NDecrypt/Features/BaseFeature.cs +++ b/NDecrypt/Features/BaseFeature.cs @@ -4,6 +4,7 @@ using System.IO; using NDecrypt.Core; using SabreTools.CommandLine; using SabreTools.CommandLine.Inputs; +using SabreTools.IO.Extensions; namespace NDecrypt.Features { @@ -31,7 +32,7 @@ namespace NDecrypt.Features /// /// Mapping of reusable tools /// - private readonly Dictionary _tools = []; + private readonly Dictionary _tools = []; protected BaseFeature(string name, string[] flags, string description, string? detailed = null) : base(name, flags, description, detailed) @@ -80,10 +81,41 @@ namespace NDecrypt.Features /// private void InitializeTools() { + // Set default values for tools + _tools[FileType.NDS] = null; + _tools[FileType.N3DS] = null; - var decryptArgs = new DecryptArgs(GetString(ConfigName, "config.json")); - _tools[FileType.NDS] = new DSTool(decryptArgs); - _tools[FileType.N3DS] = new ThreeDSTool(GetBoolean(DevelopmentName), decryptArgs); + // Check the configuration path + string? configPath = GetString(ConfigName, "config.json"); + if (configPath is null || !File.Exists(configPath)) + return; + + // Try to read the configuration file + var config = Configuration.Create(configPath); + if (config is null) + return; + + // Create the DS tool + _tools[FileType.NDS] = new DSTool + { + BlowfishTable = config.NitroEncryptionData.FromHexString() ?? [], + }; + + // Create the 3DS tool + bool development = GetBoolean(DevelopmentName); + var decryptArgs = new ThreeDSDecryptArgs + { + AESHardwareConstant = config.AESHardwareConstant.FromHexString() ?? [], + KeyX0x18 = config.KeyX0x18.FromHexString() ?? [], + KeyX0x1B = config.KeyX0x1B.FromHexString() ?? [], + KeyX0x25 = config.KeyX0x25.FromHexString() ?? [], + KeyX0x2C = config.KeyX0x2C.FromHexString() ?? [], + DevKeyX0x18 = config.DevKeyX0x18.FromHexString() ?? [], + DevKeyX0x1B = config.DevKeyX0x1B.FromHexString() ?? [], + DevKeyX0x25 = config.DevKeyX0x25.FromHexString() ?? [], + DevKeyX0x2C = config.DevKeyX0x2C.FromHexString() ?? [] + }; + _tools[FileType.N3DS] = new ThreeDSTool(development, decryptArgs); } ///