8 Commits

Author SHA1 Message Date
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 203 additions and 194 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,11 +1,23 @@
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];
// ARM9 decryption check values
private const uint MAGIC30 = 0x72636E65;
private const uint MAGIC34 = 0x6A624F79;
#endregion
#region Common to all NDS files
/// <summary>
@@ -585,102 +597,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 == MAGIC30 && p1 == 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 = MAGIC30;
p1 = 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 +832,7 @@ namespace NDecrypt.Headers
r3 |= arg1[(j * 4 + i) & 7];
}
card_hash[j] ^= r3;
cardHash[j] ^= r3;
}
uint tmp1 = 0;
@@ -700,147 +840,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

@@ -64,7 +64,7 @@ namespace NDecrypt
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
@@ -77,7 +77,8 @@ namespace NDecrypt
private static RomType DetermineRomType(string filename)
{
if (filename.EndsWith(".nds", StringComparison.OrdinalIgnoreCase))
if (filename.EndsWith(".nds", StringComparison.OrdinalIgnoreCase)
|| filename.EndsWith(".srl", StringComparison.OrdinalIgnoreCase))
return RomType.NDS;
else if (filename.EndsWith(".dsi", StringComparison.OrdinalIgnoreCase))