10 Commits

Author SHA1 Message Date
Matt Nadareski
cccb4e6261 Remove constants from code 2019-04-11 16:58:02 -07:00
Matt Nadareski
df7ff2b95a Make main program a little better 2019-04-11 15:42:52 -07:00
Matt Nadareski
52a928638b Add other known blank values 2019-04-11 15:28:10 -07:00
Matt Nadareski
5962fc6072 Better comments 2019-04-11 15:02:01 -07:00
Matt Nadareski
5807d4ddc2 Handle empty secure area, better 2019-04-11 14:59:05 -07:00
Matt Nadareski
6fa8867f93 Empty secure area 2019-04-11 14:43:39 -07:00
Matt Nadareski
ed45d2d14e Add edge case decrypt values 2019-04-11 14:15:11 -07:00
Matt Nadareski
69ecb0d379 Fix name 2019-04-11 12:55:41 -07:00
Matt Nadareski
5974e2371e Rename stuff, add SRL extension 2019-04-11 11:17:21 -07:00
Matt Nadareski
1e5e3badbc Start of cleanup 2019-04-11 10:02:38 -07:00
28 changed files with 313 additions and 242 deletions

View File

@@ -1,9 +1,9 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2006
# Visual Studio Version 16
VisualStudioVersion = 16.0.28803.156
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "3DSDecrypt", "3DSDecrypt\3DSDecrypt.csproj", "{2E30006A-3C60-4576-A262-937B21C83C06}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NDecrypt", "NDecrypt\NDecrypt.csproj", "{2E30006A-3C60-4576-A262-937B21C83C06}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@@ -1,9 +1,14 @@
using System.Numerics;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Reflection;
namespace NDecrypt.Data
{
public static class Constants
public class Constants
{
#region NDS
public static byte[] NDSEncryptionData = new byte[]
{
0x99,0xD5,0x20,0x5F,0x57,0x44,0xF5,0xB9,0x6E,0x19,0xA4,0xD9,0x9E,0x6A,0x5A,0x94,
@@ -269,66 +274,102 @@ namespace NDecrypt.Data
0x96,0xE7,0xC4,0x18,0x5F,0xAD,0xF5,0x19,
};
// ARM9 decryption check values
public const uint MAGIC30 = 0x72636E65;
public const uint MAGIC34 = 0x6A624F79;
#endregion
#region 3DS
// Setup Keys and IVs
public static byte[] PlainCounter = new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static byte[] ExefsCounter = new byte[] { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static byte[] RomfsCounter = new byte[] { 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
// Note: BigInteger requires the 0x00 at the end of each string in order to preserve the sign value
// Note: BigInteger requires that the values be in little endian format, the values in
// Big Endian - 0x1F, 0xF9, 0xE9, 0xAA, 0xC5, 0xFE, 0x04, 0x08, 0x02, 0x45, 0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A
// Little Endian - 0x8A, 0x76, 0x52, 0x5D, 0xDC, 0x91, 0x45, 0x02, 0x08, 0x04, 0xFE, 0xC5, 0xAA, 0xE9, 0xF9, 0x1F
public static BigInteger AESHardwareConstant = new BigInteger(new byte[] { 0x8A, 0x76, 0x52, 0x5D, 0xDC, 0x91, 0x45, 0x02, 0x08, 0x04, 0xFE, 0xC5, 0xAA, 0xE9, 0xF9, 0x1F });
public static BigInteger AESHardwareConstant { get; private set; }
#region Retail 3DS keys
// KeyX 0x18 (New 3DS 9.3)
// Big Endian - 0x82, 0xE9, 0xC9, 0xBE, 0xBF, 0xB8, 0xBD, 0xB8, 0x75, 0xEC, 0xC0, 0xA0, 0x7D, 0x47, 0x43, 0x74
// Little Endian - 0x74, 0x43, 0x47, 0x7D, 0xA0, 0xC0, 0xEC, 0x75, 0xB8, 0xBD, 0xB8, 0xBF, 0xBE, 0xC9, 0xE9, 0x82
public static BigInteger KeyX0x18 = new BigInteger(new byte[] { 0x74, 0x43, 0x47, 0x7D, 0xA0, 0xC0, 0xEC, 0x75, 0xB8, 0xBD, 0xB8, 0xBF, 0xBE, 0xC9, 0xE9, 0x82, 0x00 });
public static BigInteger KeyX0x18 { get; private set; }
// KeyX 0x1B (New 3DS 9.6)
// Big Endian - 0x45, 0xAD, 0x04, 0x95, 0x39, 0x92, 0xC7, 0xC8, 0x93, 0x72, 0x4A, 0x9A, 0x7B, 0xCE, 0x61, 0x82
// Little Endian - 0x82, 0x61, 0xCE, 0x7B, 0x9A, 0x4A, 0x72, 0x93, 0xC8, 0xC7, 0x92, 0x39, 0x95, 0x04, 0xAD, 0x45
public static BigInteger KeyX0x1B = new BigInteger(new byte[] { 0x82, 0x61, 0xCE, 0x7B, 0x9A, 0x4A, 0x72, 0x93, 0xC8, 0xC7, 0x92, 0x39, 0x95, 0x04, 0xAD, 0x45, 0x00 });
public static BigInteger KeyX0x1B { get; private set; }
// KeyX 0x25 (> 7.x)
// Big Endian - 0xCE, 0xE7, 0xD8, 0xAB, 0x30, 0xC0, 0x0D, 0xAE, 0x85, 0x0E, 0xF5, 0xE3, 0x82, 0xAC, 0x5A, 0xF3
// Little Endian - 0xF3, 0x5A, 0xAC, 0x82, 0xE3, 0xF5, 0x0E, 0x85, 0xAE, 0x0D, 0xC0, 0x30, 0xAB, 0xD8, 0xE7, 0xCE
public static BigInteger KeyX0x25 = new BigInteger(new byte[] { 0xF3, 0x5A, 0xAC, 0x82, 0xE3, 0xF5, 0x0E, 0x85, 0xAE, 0x0D, 0xC0, 0x30, 0xAB, 0xD8, 0xE7, 0xCE, 0x00 });
public static BigInteger KeyX0x25 { get; private set; }
// Dev KeyX 0x2C (< 6.x)
// Big Endian - 0xB9, 0x8E, 0x95, 0xCE, 0xCA, 0x3E, 0x4D, 0x17, 0x1F, 0x76, 0xA9, 0x4D, 0xE9, 0x34, 0xC0, 0x53
// Little Endian - 0x53, 0xC0, 0x34, 0xE9, 0x4D, 0xA9, 0x76, 0x1F, 0x17, 0x4D, 0x3E, 0xCA, 0xCE, 0x95, 0x8E, 0xB9
public static BigInteger KeyX0x2C = new BigInteger(new byte[] { 0x53, 0xC0, 0x34, 0xE9, 0x4D, 0xA9, 0x76, 0x1F, 0x17, 0x4D, 0x3E, 0xCA, 0xCE, 0x95, 0x8E, 0xB9, 0x00 });
public static BigInteger KeyX0x2C { get; private set; }
#endregion
#region Dev 3DS Keys
// Dev KeyX 0x18 (New 3DS 9.3)
// Big Endian - 0x30, 0x4B, 0xF1, 0x46, 0x83, 0x72, 0xEE, 0x64, 0x11, 0x5E, 0xBD, 0x40, 0x93, 0xD8, 0x42, 0x76
// Little Endian - 0x76, 0x42, 0xD8, 0x93, 0x40, 0xBD, 0x5E, 0x11, 0x64, 0xEE, 0x72, 0x83, 0x46, 0xF1, 0x4B, 0x30
public static BigInteger DevKeyX0x18 = new BigInteger(new byte[] { 0x76, 0x42, 0xD8, 0x93, 0x40, 0xBD, 0x5E, 0x11, 0x64, 0xEE, 0x72, 0x83, 0x46, 0xF1, 0x4B, 0x30, 0x00 });
public static BigInteger DevKeyX0x18 { get; private set; }
// Dev KeyX 0x1B New 3DS 9.6)
// Big Endian - 0x6C, 0x8B, 0x29, 0x44, 0xA0, 0x72, 0x60, 0x35, 0xF9, 0x41, 0xDF, 0xC0, 0x18, 0x52, 0x4F, 0xB6
// Little Endian - 0xB6, 0x4F, 0x52, 0x18, 0xC0, 0xDF, 0x41, 0xF9, 0x35, 0x60, 0x72, 0xA0, 0x44, 0x29, 0x8B, 0x6C
public static BigInteger DevKeyX0x1B = new BigInteger(new byte[] { 0xB6, 0x4F, 0x52, 0x18, 0xC0, 0xDF, 0x41, 0xF9, 0x35, 0x60, 0x72, 0xA0, 0x44, 0x29, 0x8B, 0x6C, 0x00 });
public static BigInteger DevKeyX0x1B { get; private set; }
// Dev KeyX 0x25 (> 7.x)
// Big Endian - 0x81, 0x90, 0x7A, 0x4B, 0x6F, 0x1B, 0x47, 0x32, 0x3A, 0x67, 0x79, 0x74, 0xCE, 0x4A, 0xD7, 0x1B
// Little Endian - 0x1B, 0xD7, 0x4A, 0xCE, 0x74, 0x79, 0x67, 0x3A, 0x32, 0x47, 0x1B, 0x6F, 0x4B, 0x7A, 0x90, 0x81
public static BigInteger DevKeyX0x25 = new BigInteger(new byte[] { 0x1B, 0xD7, 0x4A, 0xCE, 0x74, 0x79, 0x67, 0x3A, 0x32, 0x47, 0x1B, 0x6F, 0x4B, 0x7A, 0x90, 0x81, 0x00 });
public static BigInteger DevKeyX0x25 { get; private set; }
// Dev KeyX 0x2C (< 6.x)
// Big Endian - 0x51, 0x02, 0x07, 0x51, 0x55, 0x07, 0xCB, 0xB1, 0x8E, 0x24, 0x3D, 0xCB, 0x85, 0xE2, 0x3A, 0x1D
// Little Endian - 0x1D, 0x3A, 0xE2, 0x85, 0xCB, 0x3D, 0x24, 0x8E, 0xB1, 0xCB, 0x07, 0x55, 0x51, 0x07, 0x02, 0x51
public static BigInteger DevKeyX0x2C = new BigInteger(new byte[] { 0x1D, 0x3A, 0xE2, 0x85, 0xCB, 0x3D, 0x24, 0x8E, 0xB1, 0xCB, 0x07, 0x55, 0x51, 0x07, 0x02, 0x51, 0x00 });
public static BigInteger DevKeyX0x2C { get; private set; }
#endregion
public const int CXTExtendedDataHeaderLength = 0x800;
public static bool IsReady { get; private set; }
/// <summary>
/// Setup all of the necessary constants
/// </summary>
/// <remarks>keys.bin should be in little endian format</remarks>
static Constants()
{
string keyfile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "keys.bin");
if (!File.Exists(keyfile))
{
IsReady = false;
return;
}
try
{
using (BinaryReader reader = new BinaryReader(File.Open(keyfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
{
// This is required to preserve sign for BigInteger
byte[] signByte = new byte[] { 0x00 };
// Hardware constant
AESHardwareConstant = new BigInteger(reader.ReadBytes(16));
// Retail keys
KeyX0x18 = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
KeyX0x1B = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
KeyX0x25 = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
KeyX0x2C = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
// Development keys
DevKeyX0x18 = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
DevKeyX0x1B = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
DevKeyX0x25 = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
DevKeyX0x2C = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
}
}
catch
{
IsReady = false;
return;
}
IsReady = true;
}
#endregion
}
}

View File

@@ -1,11 +1,19 @@
using System;
using System.IO;
using System.Linq;
using NDecrypt.Data;
namespace NDecrypt.Headers
{
public class NDSHeader
{
#region Encryption process variables
private uint[] cardHash = new uint[0x412];
private uint[] arg2 = new uint[3];
#endregion
#region Common to all NDS files
/// <summary>
@@ -585,102 +593,230 @@ namespace NDecrypt.Headers
/// <param name="encrypt">True if we want to encrypt the partitions, false otherwise</param>
public void ProcessSecureArea(BinaryReader reader, BinaryWriter writer, bool encrypt)
{
bool isDecrypted = CheckIfDecrypted(reader);
if (encrypt ^ isDecrypted)
bool? isDecrypted = CheckIfDecrypted(reader);
if (isDecrypted == null)
{
Console.WriteLine("File has an empty secure area, cannot proceed");
return;
}
else if (encrypt ^ isDecrypted.Value)
{
Console.WriteLine("File is already " + (encrypt ? "encrypted" : "decrypted"));
return;
}
if (encrypt)
EncryptARM9(reader, writer);
else
DecryptARM9(reader, writer);
ProcessARM9(reader, writer, encrypt);
Console.WriteLine("File has been " + (encrypt ? "encrypted" : "decrypted"));
}
/// <summary>
/// Determine if the current file is already decrypted or not
/// Determine if the current file is already decrypted or not (or has an empty secure area)
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
private bool CheckIfDecrypted(BinaryReader reader)
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>True if the file has known values for a decrypted file, null if it's empty, false otherwise</returns>
private bool? CheckIfDecrypted(BinaryReader reader)
{
reader.BaseStream.Seek(0x4000, SeekOrigin.Begin);
uint firstValue = reader.ReadUInt32();
uint secondValue = reader.ReadUInt32();
return (firstValue == 0xE7FFDEFF) && (secondValue == 0xE7FFDEFF);
}
// Empty secure area standard
if (firstValue == 0x00000000 && secondValue == 0x00000000)
return null;
private uint[] card_hash = new uint[0x412];
// Improperly decrypted empty secure area (decrypt empty with woodsec)
else if ((firstValue == 0xE386C397 && secondValue == 0x82775B7E)
|| (firstValue == 0xF98415B8 && secondValue == 0x698068FC))
return true;
/// <summary>
/// Lookup the value from the magic table
/// </summary>
/// <param name="magic"></param>
/// <param name="v"></param>
/// <returns></returns>
private uint Lookup(ref uint[] magic, uint v)
{
uint a = (v >> 24) & 0xFF;
uint b = (v >> 16) & 0xFF;
uint c = (v >> 8) & 0xFF;
uint d = (v >> 0) & 0xFF;
// Improperly encrypted empty secure area (encrypt empty with woodsec)
else if ((firstValue == 0x4BCE88BE && secondValue == 0xD3662DD1)
|| (firstValue == 0x2543C534 && secondValue == 0xCC4BE38E))
return false;
a = magic[a + 18 + 0];
b = magic[b + 18 + 256];
c = magic[c + 18 + 512];
d = magic[d + 18 + 768];
// Properly decrypted nonstandard value (mastering issue)
else if (firstValue == 0xD0D48B67 && secondValue == 0x39392F23)
return true;
return d + (c ^ (b + a));
// Standard decryption values
return (firstValue == 0xE7FFDEFF && secondValue == 0xE7FFDEFF);
}
/// <summary>
/// Perform an encryption step
/// Process the secure ARM9 region of the file, if possible
/// </summary>
/// <param name="arg1"></param>
/// <param name="arg2"></param>
private void Encrypt(ref uint arg1, ref uint arg2)
/// <param name="reader">BinaryReader representing the input stream</param>
/// <param name="writer">BinaryWriter representing the output stream</param>
/// <param name="encrypt">True if we want to encrypt the partitions, false otherwise</param>
private void ProcessARM9(BinaryReader reader, BinaryWriter writer, bool encrypt)
{
uint a = arg1;
uint b = arg2;
for (int i = 0; i < 16; i++)
// Seek to the beginning of the secure area
reader.BaseStream.Seek(0x4000, SeekOrigin.Begin);
writer.BaseStream.Seek(0x4000, SeekOrigin.Begin);
// Grab the first two blocks
uint p0 = reader.ReadUInt32();
uint p1 = reader.ReadUInt32();
// Perform the initialization steps
Init1();
if (!encrypt) Decrypt(ref p1, ref p0);
arg2[1] <<= 1;
arg2[2] >>= 1;
Init2();
// If we're decrypting, set the proper flags
if (!encrypt)
{
uint c = card_hash[i] ^ a;
a = b ^ Lookup(ref card_hash, c);
b = c;
Decrypt(ref p1, ref p0);
if (p0 == Constants.MAGIC30 && p1 == Constants.MAGIC34)
{
p0 = 0xE7FFDEFF;
p1 = 0xE7FFDEFF;
}
writer.Write(p0);
writer.Write(p1);
}
arg2 = a ^ card_hash[16];
arg1 = b ^ card_hash[17];
// Ensure alignment
reader.BaseStream.Seek(0x4008, SeekOrigin.Begin);
writer.BaseStream.Seek(0x4008, SeekOrigin.Begin);
// Loop throgh the main encryption step
uint size = 0x800 - 8;
while (size > 0)
{
p0 = reader.ReadUInt32();
p1 = reader.ReadUInt32();
if (encrypt)
Encrypt(ref p1, ref p0);
else
Decrypt(ref p1, ref p0);
writer.Write(p0);
writer.Write(p1);
size -= 8;
}
// Replace the header explicitly if we're encrypting
if (encrypt)
{
reader.BaseStream.Seek(0x4000, SeekOrigin.Begin);
writer.BaseStream.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();
Encrypt(ref p1, ref p0);
writer.Write(p0);
writer.Write(p1);
}
}
/// <summary>
/// First common initialization step
/// </summary>
private void Init1()
{
Buffer.BlockCopy(Constants.NDSEncryptionData, 0, cardHash, 0, 4 * (1024 + 18));
arg2 = new uint[] { Gamecode, Gamecode >> 1, 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])
.Concat(BitConverter.GetBytes(arg2[1]))
.Concat(BitConverter.GetBytes(arg2[2]))
.ToArray();
UpdateHashtable(allBytes);
}
/// <summary>
/// Perform a decryption step
/// </summary>
/// <param name="arg1"></param>
/// <param name="arg2"></param>
/// <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 = card_hash[i] ^ a;
a = b ^ Lookup(ref card_hash, c);
uint c = cardHash[i] ^ a;
a = b ^ Lookup(c);
b = c;
}
arg1 = b ^ card_hash[0];
arg2 = a ^ card_hash[1];
arg1 = b ^ cardHash[0];
arg2 = a ^ cardHash[1];
}
/// <summary>
/// Update the magic hashtable
/// Perform an encryption step
/// </summary>
/// <param name="arg1"></param>
/// <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];
}
/// <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++)
@@ -692,7 +828,7 @@ namespace NDecrypt.Headers
r3 |= arg1[(j * 4 + i) & 7];
}
card_hash[j] ^= r3;
cardHash[j] ^= r3;
}
uint tmp1 = 0;
@@ -700,147 +836,15 @@ namespace NDecrypt.Headers
for (int i = 0; i < 18; i += 2)
{
Encrypt(ref tmp1, ref tmp2);
card_hash[i + 0] = tmp1;
card_hash[i + 1] = tmp2;
cardHash[i + 0] = tmp1;
cardHash[i + 1] = tmp2;
}
for (int i = 0; i < 0x400; i += 2)
{
Encrypt(ref tmp1, ref tmp2);
card_hash[i + 18 + 0] = tmp1;
card_hash[i + 18 + 1] = tmp2;
cardHash[i + 18 + 0] = tmp1;
cardHash[i + 18 + 1] = tmp2;
}
}
private uint[] arg2 = new uint[3];
private void Init2(uint[] a)
{
Encrypt(ref a[2], ref a[1]);
Encrypt(ref a[1], ref a[0]);
byte[] a0bytes = BitConverter.GetBytes(a[0]);
byte[] a1bytes = BitConverter.GetBytes(a[1]);
byte[] a2bytes = BitConverter.GetBytes(a[2]);
byte[] allbytes = new byte[3 * sizeof(uint)];
a0bytes.CopyTo(allbytes, 0);
a1bytes.CopyTo(allbytes, 4);
a2bytes.CopyTo(allbytes, 8);
UpdateHashtable(allbytes);
}
private void Init1()
{
Buffer.BlockCopy(Constants.NDSEncryptionData, 0, card_hash, 0, 4 * (1024 + 18));
arg2[0] = Gamecode;
arg2[1] = Gamecode >> 1;
arg2[2] = Gamecode << 1;
Init2(arg2);
Init2(arg2);
}
// ARM9 decryption check values
private const uint MAGIC30 = 0x72636E65;
private const uint MAGIC34 = 0x6A624F79;
/// <summary>
/// Decrypt the secure ARM9 area of the file, if possible
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <param name="writer">BinaryWriter representing the output stream</param>
private void DecryptARM9(BinaryReader reader, BinaryWriter writer)
{
// Seek to the beginning of the secure area
reader.BaseStream.Seek(0x4000, SeekOrigin.Begin);
writer.BaseStream.Seek(0x4000, SeekOrigin.Begin);
uint p0 = reader.ReadUInt32();
uint p1 = reader.ReadUInt32();
Init1();
Decrypt(ref p1, ref p0);
arg2[1] <<= 1;
arg2[2] >>= 1;
Init2(arg2);
Decrypt(ref p1, ref p0);
if (p0 == MAGIC30 && p1 == MAGIC34)
{
p0 = 0xE7FFDEFF;
p1 = 0xE7FFDEFF;
}
writer.Write(p0);
writer.Write(p1);
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>
/// Encrypt the secure ARM9 area of the file, if possible
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <param name="writer">BinaryWriter representing the output stream</param>
private void EncryptARM9(BinaryReader reader, BinaryWriter writer)
{
// Seek to the beginning of the secure area (skip first 2 UInt32s)
reader.BaseStream.Seek(0x4008, SeekOrigin.Begin);
writer.BaseStream.Seek(0x4008, SeekOrigin.Begin);
Init1();
arg2[1] <<= 1;
arg2[2] >>= 1;
Init2(arg2);
uint p0, p1;
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;
}
// place header
reader.BaseStream.Seek(0x4000, SeekOrigin.Begin);
writer.BaseStream.Seek(0x4000, SeekOrigin.Begin);
p0 = reader.ReadUInt32();
p1 = reader.ReadUInt32();
if (p0 == 0xE7FFDEFF && p1 == 0xE7FFDEFF)
{
p0 = MAGIC30;
p1 = MAGIC34;
}
Encrypt(ref p1, ref p0);
Init1();
Encrypt(ref p1, ref p0);
writer.Write(p0);
writer.Write(p1);
}
}
}

View File

@@ -8,7 +8,7 @@
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>NDecrypt</RootNamespace>
<AssemblyName>3DSDecrypt</AssemblyName>
<AssemblyName>NDecrypt</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using NDecrypt.Data;
namespace NDecrypt
{
@@ -14,11 +15,11 @@ namespace NDecrypt
}
bool? encrypt = null;
if (args[0] == "decrypt")
if (args[0] == "decrypt" || args[0] == "d")
{
encrypt = false;
}
else if (args[0] == "encrypt")
else if (args[0] == "encrypt" || args[0] == "e")
{
encrypt = true;
}
@@ -36,6 +37,14 @@ namespace NDecrypt
start = 2;
}
// Ensure the constants are all set
new Constants();
if (!Constants.IsReady)
{
Console.WriteLine("Could not read keys from keys.bin. Please make sure the file exists and try again");
return;
}
for (int i = start; i < args.Length; i++)
{
if (File.Exists(args[i]))
@@ -59,12 +68,16 @@ namespace NDecrypt
Console.Read();
}
/// <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.IsNullOrWhiteSpace(err))
Console.WriteLine($"Error: {err}");
Console.WriteLine("Usage: 3dsdecrypt.exe (decrypt|encrypt) [-dev] <file|dir> ...");
Console.WriteLine("Usage: NDecrypt.exe (decrypt|encrypt) [-dev] <file|dir> ...");
}
private enum RomType
@@ -75,20 +88,13 @@ namespace NDecrypt
N3DS,
}
private static RomType DetermineRomType(string filename)
{
if (filename.EndsWith(".nds", StringComparison.OrdinalIgnoreCase))
return RomType.NDS;
else if (filename.EndsWith(".dsi", StringComparison.OrdinalIgnoreCase))
return RomType.NDSi;
else if (filename.EndsWith(".3ds", StringComparison.OrdinalIgnoreCase))
return RomType.N3DS;
return RomType.NULL;
}
/// <summary>
/// Derive the encryption tool to be used for the given file
/// </summary>
/// <param name="filename">Filename to derive the tool from</param>
/// <param name="encrypt">True if we are encrypting the file, false otherwise</param>
/// <param name="development">rue if we are using development keys, false otherwise</param>
/// <returns></returns>
private static ITool DeriveTool(string filename, bool encrypt, bool development)
{
RomType type = DetermineRomType(filename);
@@ -101,9 +107,29 @@ namespace NDecrypt
return new ThreeDSTool(filename, development, encrypt);
case RomType.NULL:
default:
Console.WriteLine($"Unrecognized file format for {filename}. Expected *.nds, *.dsi, *.3ds");
Console.WriteLine($"Unrecognized file format for {filename}. Expected *.nds, *.srl, *.dsi, *.3ds");
return null;
}
}
/// <summary>
/// Determine the rom type from the filename extension
/// </summary>
/// <param name="filename">Filename to derive the type from</param>
/// <returns>RomType value, if possible</returns>
private static RomType DetermineRomType(string filename)
{
if (filename.EndsWith(".nds", StringComparison.OrdinalIgnoreCase)
|| filename.EndsWith(".srl", StringComparison.OrdinalIgnoreCase))
return RomType.NDS;
else if (filename.EndsWith(".dsi", StringComparison.OrdinalIgnoreCase))
return RomType.NDSi;
else if (filename.EndsWith(".3ds", StringComparison.OrdinalIgnoreCase))
return RomType.N3DS;
return RomType.NULL;
}
}
}