mirror of
https://github.com/SabreTools/NDecrypt.git
synced 2026-02-03 21:29:31 +00:00
Re-merge projects
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -261,4 +261,4 @@ __pycache__/
|
||||
*.pyc
|
||||
|
||||
# VSCode
|
||||
/NDecrypt.Core/Properties/launchSettings.json
|
||||
/NDecrypt/Properties/launchSettings.json
|
||||
|
||||
27
.vscode/launch.json
vendored
Normal file
27
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": ".NET Core Launch (console)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/NDecrypt/bin/Debug/netcoreapp3.1/NDecrypt.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/NDecrypt",
|
||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,366 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NDecrypt.Core.Data;
|
||||
using NDecrypt.Core.Headers;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
public class DSTool : ITool
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the input DS/DSi file
|
||||
/// </summary>
|
||||
private readonly string filename;
|
||||
|
||||
/// <summary>
|
||||
/// Flag to determine if encrypting or decrypting
|
||||
/// </summary>
|
||||
private readonly bool encrypt;
|
||||
|
||||
/// <summary>
|
||||
/// Flag to determine if forcing operations
|
||||
/// </summary>
|
||||
private readonly bool force;
|
||||
|
||||
#region Encryption process variables
|
||||
|
||||
private uint[] cardHash = new uint[0x412];
|
||||
private uint[] arg2 = new uint[3];
|
||||
|
||||
#endregion
|
||||
|
||||
public DSTool(string filename, bool encrypt, bool force)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.encrypt = encrypt;
|
||||
this.force = force;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process an input file given the input values
|
||||
/// </summary>
|
||||
public bool ProcessFile()
|
||||
{
|
||||
// Make sure we have a file to process first
|
||||
Console.WriteLine(filename);
|
||||
if (!File.Exists(filename))
|
||||
return false;
|
||||
|
||||
// Open the read and write on the same file for inplace processing
|
||||
using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
|
||||
using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)))
|
||||
{
|
||||
NDSHeader header = NDSHeader.Read(reader);
|
||||
if (header == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a DS or DSi Rom!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process the secure area
|
||||
ProcessSecureArea(header, reader, writer);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process secure area in the DS/DSi file
|
||||
/// </summary>
|
||||
/// <param name="ndsHeader">NDS header representing the DS file</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
public void ProcessSecureArea(NDSHeader ndsHeader, BinaryReader reader, BinaryWriter 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 (encrypt ^ isDecrypted.Value)
|
||||
{
|
||||
Console.WriteLine("File is already " + (encrypt ? "encrypted" : "decrypted"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessARM9(ndsHeader, reader, writer);
|
||||
|
||||
Console.WriteLine("File has been " + (encrypt ? "encrypted" : "decrypted"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the current file is already decrypted or not (or has an empty secure area)
|
||||
/// </summary>
|
||||
/// <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();
|
||||
|
||||
// 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))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// Properly encrypted prototype value
|
||||
else if (firstValue == 0xA71329EE && secondValue == 0x2A1D4C38)
|
||||
{
|
||||
Console.WriteLine("Encrypted secure area for prototype found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Standard decryption values
|
||||
return (firstValue == 0xE7FFDEFF && secondValue == 0xE7FFDEFF);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the secure ARM9 region of the file, if possible
|
||||
/// </summary>
|
||||
/// <param name="ndsHeader">NDS header representing the DS file</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessARM9(NDSHeader ndsHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// 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(ndsHeader);
|
||||
if (!encrypt) Decrypt(ref p1, ref p0);
|
||||
arg2[1] <<= 1;
|
||||
arg2[2] >>= 1;
|
||||
Init2();
|
||||
|
||||
// If we're decrypting, set the proper flags
|
||||
if (!encrypt)
|
||||
{
|
||||
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.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(ndsHeader);
|
||||
Encrypt(ref p1, ref p0);
|
||||
|
||||
writer.Write(p0);
|
||||
writer.Write(p1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// First common initialization step
|
||||
/// </summary>
|
||||
/// <param name="ndsHeader">NDS header representing the DS file</param>
|
||||
private void Init1(NDSHeader ndsHeader)
|
||||
{
|
||||
Buffer.BlockCopy(Constants.NDSEncryptionData, 0, cardHash, 0, 4 * (1024 + 18));
|
||||
arg2 = new uint[] { ndsHeader.Gamecode, ndsHeader.Gamecode >> 1, ndsHeader.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">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];
|
||||
}
|
||||
|
||||
/// <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];
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace NDecrypt.Core.Data
|
||||
{
|
||||
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,
|
||||
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,
|
||||
};
|
||||
|
||||
// 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 };
|
||||
|
||||
public static BigInteger AESHardwareConstant { get; private set; }
|
||||
|
||||
#region Retail 3DS keys
|
||||
|
||||
// KeyX 0x18 (New 3DS 9.3)
|
||||
public static BigInteger KeyX0x18 { get; private set; }
|
||||
|
||||
// KeyX 0x1B (New 3DS 9.6)
|
||||
public static BigInteger KeyX0x1B { get; private set; }
|
||||
|
||||
// KeyX 0x25 (> 7.x)
|
||||
public static BigInteger KeyX0x25 { get; private set; }
|
||||
|
||||
// KeyX 0x2C (< 6.x)
|
||||
public static BigInteger KeyX0x2C { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dev 3DS Keys
|
||||
|
||||
// Dev KeyX 0x18 (New 3DS 9.3)
|
||||
public static BigInteger DevKeyX0x18 { get; private set; }
|
||||
|
||||
// Dev KeyX 0x1B New 3DS 9.6)
|
||||
public static BigInteger DevKeyX0x1B { get; private set; }
|
||||
|
||||
// Dev KeyX 0x25 (> 7.x)
|
||||
public static BigInteger DevKeyX0x25 { get; private set; }
|
||||
|
||||
// Dev KeyX 0x2C (< 6.x)
|
||||
public static BigInteger DevKeyX0x2C { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
public const int CXTExtendedDataHeaderLength = 0x800;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Represents if all of the keys have been initialized properly
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace NDecrypt.Core.Data
|
||||
{
|
||||
[Flags]
|
||||
public enum ARM9AccessControlDescriptors : byte
|
||||
{
|
||||
MountNandRoot = 0x01,
|
||||
MountNandroWriteAccess = 0x02,
|
||||
MountTwlnRoot = 0x04,
|
||||
MountWnandRoot = 0x08,
|
||||
MountCardSPI = 0x0F,
|
||||
UseSDIF3 = 0x10,
|
||||
CreateSeed = 0x20,
|
||||
UseCardSPI = 0x40,
|
||||
SDApplication = 0x80,
|
||||
MoundSdmcWriteAccess = 0xF0,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ARM11LSCFlag0 : byte
|
||||
{
|
||||
IdealProcessor = 0x01 | 0x02,
|
||||
AffinityMask = 0x04 | 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// Value Description
|
||||
/// 0 Prod (64MB of usable application memory)
|
||||
/// 1 Undefined (unusable)
|
||||
/// 2 Dev1 (96MB of usable application memory)
|
||||
/// 3 Dev2 (80MB of usable application memory)
|
||||
/// 4 Dev3 (72MB of usable application memory)
|
||||
/// 5 Dev4 (32MB of usable application memory)
|
||||
/// 6-7 Undefined Same as Prod?
|
||||
/// </summary>
|
||||
Old3DSSystemMode = 0x0F | 0x10 | 0x20 | 0x40,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ARM11LSCFlag1 : byte
|
||||
{
|
||||
EnableL2Cache = 0x01,
|
||||
Cpuspeed_804MHz = 0x02,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ARM11LSCFlag2 : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Value Description
|
||||
/// 0 Legacy (use Old3DS system mode)
|
||||
/// 1 Prod (124MB of usable application memory)
|
||||
/// 2 Dev1 (178MB of usable application memory)
|
||||
/// 3 Dev2 (124MB of usable application memory)
|
||||
/// 4-7 Undefined Same as Prod?
|
||||
/// </summary>
|
||||
New3DSSystemMode = 0x01 | 0x02 | 0x04 | 0x08,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum BitMasks : byte
|
||||
{
|
||||
FixedCryptoKey = 0x01,
|
||||
NoMountRomFs = 0x02,
|
||||
NoCrypto = 0x04,
|
||||
NewKeyYGenerator = 0x20,
|
||||
}
|
||||
|
||||
public enum ContentPlatform : byte
|
||||
{
|
||||
CTR = 0x01,
|
||||
Snake = 0x02, // New3DS
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ContentType : byte
|
||||
{
|
||||
Data = 0x01,
|
||||
Executable = 0x02,
|
||||
SystemUpdate = 0x04,
|
||||
Manual = 0x08,
|
||||
Child = 0x04 | 0x08,
|
||||
Trial = 0x10,
|
||||
}
|
||||
|
||||
public enum CryptoMethod : byte
|
||||
{
|
||||
Original = 0x00,
|
||||
Seven = 0x01,
|
||||
NineThree = 0x0A,
|
||||
NineSix = 0x0B,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum FilesystemAccessInfo : ulong
|
||||
{
|
||||
CategorySystemApplication = 0x1,
|
||||
CategoryHardwareCheck = 0x2,
|
||||
CategoryFilesystemTool = 0x4,
|
||||
Debug = 0x8,
|
||||
TWLCardBackup = 0x10,
|
||||
TWLNANDData = 0x20,
|
||||
BOSS = 0x40,
|
||||
sdmcRoot = 0x80,
|
||||
Core = 0x100,
|
||||
nandRootroReadOnly = 0x200,
|
||||
nandRootrw = 0x400,
|
||||
nandrootroWriteAccess = 0x800,
|
||||
CategorySystemSettings = 0x1000,
|
||||
Cardboard = 0x2000,
|
||||
ExportImportIVS = 0x4000,
|
||||
sdmcRootWriteOnly = 0x8000,
|
||||
SwitchCleanup = 0x10000, // Introduced in 3.0.0?
|
||||
SavedataMove = 0x20000, // Introduced in 5.0.0
|
||||
Shop = 0x40000, // Introduced in 5.0.0
|
||||
Shell = 0x80000, // Introduced in 5.0.0
|
||||
CategoryHomeMenu = 0x100000, // Introduced in 6.0.0
|
||||
SeedDB = 0x200000, // Introduced in 9.6.0-X FIRM. Home Menu has this bit set starting with 9.6.0-X.
|
||||
}
|
||||
|
||||
public enum FilesystemType : ulong
|
||||
{
|
||||
None = 0,
|
||||
Normal = 1,
|
||||
FIRM = 3,
|
||||
AGB_FIRMSave = 4,
|
||||
}
|
||||
|
||||
public enum MediaCardDeviceType : byte
|
||||
{
|
||||
NORFlash = 0x01,
|
||||
None = 0x02,
|
||||
BT = 0x03,
|
||||
}
|
||||
|
||||
public enum MediaPlatformIndex : byte
|
||||
{
|
||||
CTR = 0x01,
|
||||
}
|
||||
|
||||
public enum MediaTypeIndex : byte
|
||||
{
|
||||
InnerDevice = 0x00,
|
||||
Card1 = 0x01,
|
||||
Card2 = 0x02,
|
||||
ExtendedDevice = 0x03,
|
||||
}
|
||||
|
||||
public enum NCCHFlags
|
||||
{
|
||||
CryptoMethod = 0x03,
|
||||
ContentPlatform = 0x04,
|
||||
ContentTypeBitMask = 0x05,
|
||||
ContentUnitSize = 0x06,
|
||||
BitMasks = 0x07,
|
||||
}
|
||||
|
||||
public enum NCSDFlags
|
||||
{
|
||||
BackupWriteWaitTime = 0x00,
|
||||
MediaCardDevice3X = 0x03,
|
||||
MediaPlatformIndex = 0x04,
|
||||
MediaTypeIndex = 0x05,
|
||||
MediaUnitSize = 0x06,
|
||||
MediaCardDevice2X = 0x07,
|
||||
}
|
||||
|
||||
public enum NDSUnitcode : byte
|
||||
{
|
||||
NDS = 0x00,
|
||||
NDSPlusDSi = 0x02,
|
||||
DSi = 0x03,
|
||||
}
|
||||
|
||||
public enum ResourceLimitCategory
|
||||
{
|
||||
APPLICATION = 0,
|
||||
SYS_APPLET = 1,
|
||||
LIB_APPLET = 2,
|
||||
OTHER = 3,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum StorageInfoOtherAttributes : byte
|
||||
{
|
||||
NotUseROMFS = 0x01,
|
||||
UseExtendedSavedataAccess = 0x02,
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class ARM11KernelCapabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Descriptors
|
||||
/// -------------------
|
||||
/// Pattern of bits 20-31 Type Fields
|
||||
/// 0b1110xxxxxxxx Interrupt info
|
||||
/// 0b11110xxxxxxx System call mask Bits 24-26: System call mask table index; Bits 0-23: mask
|
||||
/// 0b1111110xxxxx Kernel release version Bits 8-15: Major version; Bits 0-7: Minor version
|
||||
/// 0b11111110xxxx Handle table size Bits 0-18: size
|
||||
/// 0b111111110xxx Kernel flags
|
||||
/// 0b11111111100x Map address range Describes a memory mapping like the 0b111111111110 descriptor, but an entire range rather than a single page is mapped.Another 0b11111111100x descriptor must follow this one to denote the(exclusive) end of the address range to map.
|
||||
/// 0b111111111110 Map memory page Bits 0-19: page index to map(virtual address >> 12; the physical address is determined per-page according to Memory layout); Bit 20: Map read-only(otherwise read-write)
|
||||
///
|
||||
/// ARM11 Kernel Flags
|
||||
/// -------------------
|
||||
/// Bit Description
|
||||
/// 0 Allow debug
|
||||
/// 1 Force debug
|
||||
/// 2 Allow non-alphanum
|
||||
/// 3 Shared page writing
|
||||
/// 4 Privilege priority
|
||||
/// 5 Allow main() args
|
||||
/// 6 Shared device memory
|
||||
/// 7 Runnable on sleep
|
||||
/// 8-11 Memory type(1: application, 2: system, 3: base)
|
||||
/// 12 Special memory
|
||||
/// 13 Process has access to CPU core 2 (New3DS only)
|
||||
/// </summary>
|
||||
public byte[][] Descriptors { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get ARM11 kernel capabilities, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ARM11 kernel capabilities object, null on error</returns>
|
||||
public static ARM11KernelCapabilities Read(BinaryReader reader)
|
||||
{
|
||||
ARM11KernelCapabilities kc = new ARM11KernelCapabilities();
|
||||
|
||||
try
|
||||
{
|
||||
kc.Descriptors = new byte[28][];
|
||||
for (int i = 0; i < 28; i++)
|
||||
kc.Descriptors[i] = reader.ReadBytes(4);
|
||||
|
||||
kc.Reserved = reader.ReadBytes(0x10);
|
||||
return kc;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
using System.IO;
|
||||
using NDecrypt.Core.Data;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class ARM11LocalSystemCapabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Program ID
|
||||
/// </summary>
|
||||
public byte[] ProgramID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Core version (The Title ID low of the required FIRM)
|
||||
/// </summary>
|
||||
public uint CoreVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag1 (implemented starting from 8.0.0-18).
|
||||
/// </summary>
|
||||
public ARM11LSCFlag1 Flag1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag2 (implemented starting from 8.0.0-18).
|
||||
/// </summary>
|
||||
public ARM11LSCFlag2 Flag2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag0
|
||||
/// </summary>
|
||||
public ARM11LSCFlag0 Flag0 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Priority
|
||||
/// </summary>
|
||||
public byte Priority { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Resource limit descriptors. The first byte here controls the maximum allowed CpuTime.
|
||||
/// </summary>
|
||||
public byte[][] ResourceLimitDescriptors { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Storage info
|
||||
/// </summary>
|
||||
public StorageInfo StorageInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Service access control
|
||||
/// </summary>
|
||||
public byte[][] ServiceAccessControl { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Extended service access control, support for this was implemented with 9.3.0-X.
|
||||
/// </summary>
|
||||
public byte[][] ExtendedServiceAccessControl { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Resource limit category. (0 = APPLICATION, 1 = SYS_APPLET, 2 = LIB_APPLET, 3 = OTHER (sysmodules running under the BASE memregion))
|
||||
/// </summary>
|
||||
public ResourceLimitCategory ResourceLimitCategory { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get ARM11 local system capabilities, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ARM11 local system capabilities object, null on error</returns>
|
||||
public static ARM11LocalSystemCapabilities Read(BinaryReader reader)
|
||||
{
|
||||
ARM11LocalSystemCapabilities lsc = new ARM11LocalSystemCapabilities();
|
||||
|
||||
try
|
||||
{
|
||||
lsc.ProgramID = reader.ReadBytes(8);
|
||||
lsc.CoreVersion = reader.ReadUInt32();
|
||||
lsc.Flag1 = (ARM11LSCFlag1)reader.ReadByte();
|
||||
lsc.Flag2 = (ARM11LSCFlag2)reader.ReadByte();
|
||||
lsc.Flag0 = (ARM11LSCFlag0)reader.ReadByte();
|
||||
lsc.Priority = reader.ReadByte();
|
||||
|
||||
lsc.ResourceLimitDescriptors = new byte[16][];
|
||||
for (int i = 0; i < 16; i++)
|
||||
lsc.ResourceLimitDescriptors[i] = reader.ReadBytes(2);
|
||||
|
||||
lsc.StorageInfo = StorageInfo.Read(reader);
|
||||
|
||||
lsc.ServiceAccessControl = new byte[32][];
|
||||
for (int i = 0; i < 32; i++)
|
||||
lsc.ServiceAccessControl[i] = reader.ReadBytes(8);
|
||||
|
||||
lsc.ExtendedServiceAccessControl = new byte[2][];
|
||||
for (int i = 0; i < 2; i++)
|
||||
lsc.ExtendedServiceAccessControl[i] = reader.ReadBytes(8);
|
||||
|
||||
lsc.Reserved = reader.ReadBytes(0xF);
|
||||
lsc.ResourceLimitCategory = (ResourceLimitCategory)reader.ReadByte();
|
||||
return lsc;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System.IO;
|
||||
using NDecrypt.Core.Data;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class ARM9AccessControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Descriptors
|
||||
/// </summary>
|
||||
public ARM9AccessControlDescriptors[] Descriptors { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 Descriptor Version. Originally this value had to be ≥ 2. Starting with 9.3.0-X this value has to be either value 2 or value 3.
|
||||
/// </summary>
|
||||
public byte DescriptorVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get ARM9 access control, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ARM9 access control object, null on error</returns>
|
||||
public static ARM9AccessControl Read(BinaryReader reader)
|
||||
{
|
||||
ARM9AccessControl ac = new ARM9AccessControl();
|
||||
|
||||
try
|
||||
{
|
||||
ac.Descriptors = new ARM9AccessControlDescriptors[15];
|
||||
for (int i = 0; i < 15; i++)
|
||||
ac.Descriptors[i] = (ARM9AccessControlDescriptors)reader.ReadByte();
|
||||
ac.DescriptorVersion = reader.ReadByte();
|
||||
return ac;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class AccessControlInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// ARM11 local system capabilities
|
||||
/// </summary>
|
||||
public ARM11LocalSystemCapabilities ARM11LocalSystemCapabilities { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM11 kernel capabilities
|
||||
/// </summary>
|
||||
public ARM11KernelCapabilities ARM11KernelCapabilities { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 access control
|
||||
/// </summary>
|
||||
public ARM9AccessControl ARM9AccessControl { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get access control info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Access control info object, null on error</returns>
|
||||
public static AccessControlInfo Read(BinaryReader reader)
|
||||
{
|
||||
AccessControlInfo aci = new AccessControlInfo();
|
||||
|
||||
try
|
||||
{
|
||||
aci.ARM11LocalSystemCapabilities = ARM11LocalSystemCapabilities.Read(reader);
|
||||
aci.ARM11KernelCapabilities = ARM11KernelCapabilities.Read(reader);
|
||||
aci.ARM9AccessControl = ARM9AccessControl.Read(reader);
|
||||
return aci;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class CXIExtendedHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// SCI
|
||||
/// </summary>
|
||||
public SystemControlInfo SCI { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ACI
|
||||
/// </summary>
|
||||
public AccessControlInfo ACI { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// AccessDesc signature (RSA-2048-SHA256)
|
||||
/// </summary>
|
||||
public byte[] AccessDescSignature { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// NCCH HDR RSA-2048 public key
|
||||
/// </summary>
|
||||
public byte[] NCCHHDRPublicKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ACI (for limitation of first ACI)
|
||||
/// </summary>
|
||||
public AccessControlInfo ACIForLimitations { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get a CXI extended header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>CXI extended header object, null on error</returns>
|
||||
public static CXIExtendedHeader Read(BinaryReader reader)
|
||||
{
|
||||
CXIExtendedHeader header = new CXIExtendedHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.SCI = SystemControlInfo.Read(reader);
|
||||
header.ACI = AccessControlInfo.Read(reader);
|
||||
header.AccessDescSignature = reader.ReadBytes(0x100);
|
||||
header.NCCHHDRPublicKey = reader.ReadBytes(0x100);
|
||||
header.ACIForLimitations = AccessControlInfo.Read(reader);
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class CodeSetInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Address
|
||||
/// </summary>
|
||||
public byte[] Address { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Physical region size (in page-multiples)
|
||||
/// </summary>
|
||||
public uint PhysicalRegionSizeInPages { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size (in bytes)
|
||||
/// </summary>
|
||||
public uint SizeInBytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get code set info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Code set info object, null on error</returns>
|
||||
public static CodeSetInfo Read(BinaryReader reader)
|
||||
{
|
||||
CodeSetInfo csi = new CodeSetInfo();
|
||||
|
||||
try
|
||||
{
|
||||
csi.Address = reader.ReadBytes(4);
|
||||
csi.PhysicalRegionSizeInPages = reader.ReadUInt32();
|
||||
csi.SizeInBytes = reader.ReadUInt32();
|
||||
return csi;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class ExeFSFileHeader
|
||||
{
|
||||
// .code\0\0\0
|
||||
private readonly byte[] codeSegmentBytes = new byte[] { 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x00, 0x00, 0x00 };
|
||||
|
||||
/// <summary>
|
||||
/// File name
|
||||
/// </summary>
|
||||
public byte[] FileName { get; private set; }
|
||||
public string ReadableFileName { get { return Encoding.ASCII.GetString(FileName); } }
|
||||
public bool IsCodeBinary { get { return Enumerable.SequenceEqual(FileName, codeSegmentBytes); } }
|
||||
|
||||
/// <summary>
|
||||
/// File offset
|
||||
/// </summary>
|
||||
public uint FileOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File size
|
||||
/// </summary>
|
||||
public uint FileSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SHA256 hash calculated over the entire file contents
|
||||
/// </summary>
|
||||
public byte[] FileHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an ExeFS file header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ExeFS file header object, null on error</returns>
|
||||
public static ExeFSFileHeader Read(BinaryReader reader)
|
||||
{
|
||||
ExeFSFileHeader header = new ExeFSFileHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.FileName = reader.ReadBytes(8);
|
||||
header.FileOffset = reader.ReadUInt32();
|
||||
header.FileSize = reader.ReadUInt32();
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class ExeFSHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// File headers (10 headers maximum, 16 bytes each)
|
||||
/// </summary>
|
||||
public ExeFSFileHeader[] FileHeaders { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an ExeFS header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ExeFS header object, null on error</returns>
|
||||
public static ExeFSHeader Read(BinaryReader reader)
|
||||
{
|
||||
ExeFSHeader header = new ExeFSHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.FileHeaders = new ExeFSFileHeader[10];
|
||||
for (int i = 0; i < 10; i++)
|
||||
header.FileHeaders[i] = ExeFSFileHeader.Read(reader);
|
||||
|
||||
header.Reserved = reader.ReadBytes(0x20);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
header.FileHeaders[9 - i].FileHash = reader.ReadBytes(0x20);
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using NDecrypt.Core.Data;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class NCCHHeader
|
||||
{
|
||||
private const string NCCHMagicNumber = "NCCH";
|
||||
|
||||
/// <summary>
|
||||
/// Partition number for the current partition
|
||||
/// </summary>
|
||||
public int PartitionNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partition table entry for the current partition
|
||||
/// </summary>
|
||||
public PartitionTableEntry Entry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// RSA-2048 signature of the NCCH header, using SHA-256.
|
||||
/// </summary>
|
||||
public byte[] RSA2048Signature { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content size, in media units (1 media unit = 0x200 bytes)
|
||||
/// </summary>
|
||||
public uint ContentSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partition ID
|
||||
/// </summary>
|
||||
public byte[] PartitionId { get; private set; }
|
||||
public byte[] PlainIV { get { return PartitionId.Concat(Constants.PlainCounter).ToArray(); } }
|
||||
public byte[] ExeFSIV { get { return PartitionId.Concat(Constants.ExefsCounter).ToArray(); } }
|
||||
public byte[] RomFSIV { get { return PartitionId.Concat(Constants.RomfsCounter).ToArray(); } }
|
||||
|
||||
/// <summary>
|
||||
/// Boot rom key
|
||||
/// </summary>
|
||||
public BigInteger KeyX { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// NCCH boot rom key
|
||||
/// </summary>
|
||||
public BigInteger KeyX2C { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Kernel9/Process9 key
|
||||
/// </summary>
|
||||
public BigInteger KeyY { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Normal AES key
|
||||
/// </summary>
|
||||
public BigInteger NormalKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// NCCH AES key
|
||||
/// </summary>
|
||||
public BigInteger NormalKey2C { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maker code
|
||||
/// </summary>
|
||||
public byte[] MakerCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Version
|
||||
/// </summary>
|
||||
public byte[] Version { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// When ncchflag[7] = 0x20 starting with FIRM 9.6.0-X, this is compared with the first output u32 from a
|
||||
/// SHA256 hash. The data used for that hash is 0x18-bytes: [0x10-long title-unique content lock seed]
|
||||
/// [programID from NCCH + 0x118]. This hash is only used for verification of the content lock seed, and
|
||||
/// is not the actual keyY.
|
||||
/// </summary>
|
||||
public byte[] VerificationHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Program ID
|
||||
/// </summary>
|
||||
public byte[] ProgramId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logo Region SHA-256 hash. (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11)
|
||||
/// </summary>
|
||||
public byte[] LogoRegionHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Product code
|
||||
/// </summary>
|
||||
public byte[] ProductCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Extended header SHA-256 hash (SHA256 of 2x Alignment Size, beginning at 0x0 of ExHeader)
|
||||
/// </summary>
|
||||
public byte[] ExtendedHeaderHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Extended header size, in bytes
|
||||
/// </summary>
|
||||
public uint ExtendedHeaderSizeInBytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flags
|
||||
/// </summary>
|
||||
public NCCHHeaderFlags Flags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plain region offset, in media units
|
||||
/// </summary>
|
||||
public uint PlainRegionOffsetInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plain region size, in media units
|
||||
/// </summary>
|
||||
public uint PlainRegionSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logo Region offset, in media units (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11)
|
||||
/// </summary>
|
||||
public uint LogoRegionOffsetInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logo Region size, in media units (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11)
|
||||
/// </summary>
|
||||
public uint LogoRegionSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS offset, in media units
|
||||
/// </summary>
|
||||
public uint ExeFSOffsetInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS size, in media units
|
||||
/// </summary>
|
||||
public uint ExeFSSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS hash region size, in media units
|
||||
/// </summary>
|
||||
public uint ExeFSHashRegionSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved3 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RomFS offset, in media units
|
||||
/// </summary>
|
||||
public uint RomFSOffsetInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RomFS size, in media units
|
||||
/// </summary>
|
||||
public uint RomFSSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RomFS hash region size, in media units
|
||||
/// </summary>
|
||||
public uint RomFSHashRegionSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved4 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS superblock SHA-256 hash - (SHA-256 hash, starting at 0x0 of the ExeFS over the number of
|
||||
/// media units specified in the ExeFS hash region size)
|
||||
/// </summary>
|
||||
public byte[] ExeFSSuperblockHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RomFS superblock SHA-256 hash - (SHA-256 hash, starting at 0x0 of the RomFS over the number
|
||||
/// of media units specified in the RomFS hash region size)
|
||||
/// </summary>
|
||||
public byte[] RomFSSuperblockHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an NCCH header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="readSignature">True if the RSA signature is read, false otherwise</param>
|
||||
/// <returns>NCCH header object, null on error</returns>
|
||||
public static NCCHHeader Read(BinaryReader reader, bool readSignature)
|
||||
{
|
||||
NCCHHeader header = new NCCHHeader();
|
||||
|
||||
try
|
||||
{
|
||||
if (readSignature)
|
||||
header.RSA2048Signature = reader.ReadBytes(0x100);
|
||||
|
||||
if (new string(reader.ReadChars(4)) != NCCHMagicNumber)
|
||||
return null;
|
||||
|
||||
header.ContentSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.PartitionId = reader.ReadBytes(8).Reverse().ToArray();
|
||||
header.MakerCode = reader.ReadBytes(2);
|
||||
header.Version = reader.ReadBytes(2);
|
||||
header.VerificationHash = reader.ReadBytes(4);
|
||||
header.ProgramId = reader.ReadBytes(8);
|
||||
header.Reserved1 = reader.ReadBytes(0x10);
|
||||
header.LogoRegionHash = reader.ReadBytes(0x20);
|
||||
header.ProductCode = reader.ReadBytes(0x10);
|
||||
header.ExtendedHeaderHash = reader.ReadBytes(0x20);
|
||||
header.ExtendedHeaderSizeInBytes = reader.ReadUInt32();
|
||||
header.Reserved2 = reader.ReadBytes(4);
|
||||
header.Flags = NCCHHeaderFlags.Read(reader);
|
||||
header.PlainRegionOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.PlainRegionSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.LogoRegionOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.LogoRegionSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.ExeFSOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.ExeFSSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.ExeFSHashRegionSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.Reserved3 = reader.ReadBytes(4);
|
||||
header.RomFSOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.RomFSSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.RomFSHashRegionSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.Reserved4 = reader.ReadBytes(4);
|
||||
header.ExeFSSuperblockHash = reader.ReadBytes(0x20);
|
||||
header.RomFSSuperblockHash = reader.ReadBytes(0x20);
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
using System.IO;
|
||||
using NDecrypt.Core.Data;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class NCCHHeaderFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved0 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Crypto Method: When this is non-zero, a NCCH crypto method using two keyslots is used.
|
||||
/// </summary>
|
||||
public CryptoMethod CryptoMethod { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content Platform: 1 = CTR, 2 = snake (New 3DS).
|
||||
/// </summary>
|
||||
public ContentPlatform ContentPlatform { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content Type Bit-masks: Data = 0x1, Executable = 0x2, SystemUpdate = 0x4, Manual = 0x8,
|
||||
/// Child = (0x4|0x8), Trial = 0x10. When 'Data' is set, but not 'Executable', NCCH is a CFA.
|
||||
/// Otherwise when 'Executable' is set, NCCH is a CXI.
|
||||
/// </summary>
|
||||
public ContentType MediaPlatformIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content Unit Size i.e. u32 ContentUnitSize = 0x200*2^flags[6];
|
||||
/// </summary>
|
||||
public byte ContentUnitSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bit-masks: FixedCryptoKey = 0x1, NoMountRomFs = 0x2, NoCrypto = 0x4, using a new keyY
|
||||
/// generator = 0x20(starting with FIRM 9.6.0-X).
|
||||
/// </summary>
|
||||
public BitMasks BitMasks { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get if the NoCrypto bit is set
|
||||
/// </summary>
|
||||
public bool PossblyDecrypted { get { return BitMasks.HasFlag(BitMasks.NoCrypto); } }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an NCCH header flags, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>NCCH header flags object, null on error</returns>
|
||||
public static NCCHHeaderFlags Read(BinaryReader reader)
|
||||
{
|
||||
NCCHHeaderFlags flags = new NCCHHeaderFlags();
|
||||
|
||||
try
|
||||
{
|
||||
flags.Reserved0 = reader.ReadByte();
|
||||
flags.Reserved1 = reader.ReadByte();
|
||||
flags.Reserved2 = reader.ReadByte();
|
||||
flags.CryptoMethod = (CryptoMethod)reader.ReadByte();
|
||||
flags.ContentPlatform = (ContentPlatform)reader.ReadByte();
|
||||
flags.MediaPlatformIndex = (ContentType)reader.ReadByte();
|
||||
flags.ContentUnitSize = reader.ReadByte();
|
||||
flags.BitMasks = (BitMasks)reader.ReadByte();
|
||||
return flags;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write NCCH header flags to stream, if possible
|
||||
/// </summary>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
try
|
||||
{
|
||||
writer.Write(this.Reserved0);
|
||||
writer.Write(this.Reserved1);
|
||||
writer.Write(this.Reserved2);
|
||||
writer.Write((byte)this.CryptoMethod);
|
||||
writer.Write((byte)this.ContentPlatform);
|
||||
writer.Write((byte)this.MediaPlatformIndex);
|
||||
writer.Write((byte)this.ContentUnitSize);
|
||||
writer.Write((byte)this.BitMasks);
|
||||
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,326 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using NDecrypt.Core.Data;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class NCSDHeader
|
||||
{
|
||||
private const string NCSDMagicNumber = "NCSD";
|
||||
|
||||
#region Common to all NCSD files
|
||||
|
||||
/// <summary>
|
||||
/// RSA-2048 SHA-256 signature of the NCSD header
|
||||
/// </summary>
|
||||
public byte[] RSA2048Signature { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the NCSD image, in media units (1 media unit = 0x200 bytes)
|
||||
/// </summary>
|
||||
public uint ImageSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Media ID
|
||||
/// </summary>
|
||||
public byte[] MediaId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partitions FS type (0=None, 1=Normal, 3=FIRM, 4=AGB_FIRM save)
|
||||
/// </summary>
|
||||
public FilesystemType PartitionsFSType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partitions crypt type (each byte corresponds to a partition in the partition table)
|
||||
/// </summary>
|
||||
public byte[] PartitionsCryptType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset & Length partition table, in media units
|
||||
/// </summary>
|
||||
public PartitionTableEntry[] PartitionsTable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partition table entry for Executable Content (CXI)
|
||||
/// </summary>
|
||||
public PartitionTableEntry ExecutableContent { get { return PartitionsTable[0]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Partition table entry for E-Manual (CFA)
|
||||
/// </summary>
|
||||
public PartitionTableEntry EManual { get { return PartitionsTable[1]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Partition table entry for Download Play Child container (CFA)
|
||||
/// </summary>
|
||||
public PartitionTableEntry DownloadPlayChildContainer { get { return PartitionsTable[2]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Partition table entry for New3DS Update Data (CFA)
|
||||
/// </summary>
|
||||
public PartitionTableEntry New3DSUpdateData { get { return PartitionsTable[6]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Partition table entry for Update Data (CFA)
|
||||
/// </summary>
|
||||
public PartitionTableEntry UpdateData { get { return PartitionsTable[7]; } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region CTR Cart Image (CCI) Specific
|
||||
|
||||
/// <summary>
|
||||
/// Exheader SHA-256 hash
|
||||
/// </summary>
|
||||
public byte[] ExheaderHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional header size
|
||||
/// </summary>
|
||||
public uint AdditionalHeaderSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sector zero offset
|
||||
/// </summary>
|
||||
public uint SectorZeroOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partition Flags
|
||||
/// </summary>
|
||||
public byte[] PartitionFlags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Backup Write Wait Time (The time to wait to write save to backup after the card is recognized (0-255
|
||||
/// seconds)).NATIVE_FIRM loads this flag from the gamecard NCSD header starting with 6.0.0-11.
|
||||
/// </summary>
|
||||
public byte BackupWriteWaitTime { get { return PartitionFlags[(int)NCSDFlags.BackupWriteWaitTime]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (SDK 3.X+)
|
||||
/// </summary>
|
||||
public MediaCardDeviceType MediaCardDevice3X { get { return (MediaCardDeviceType)PartitionFlags[(int)NCSDFlags.MediaCardDevice3X]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Media Platform Index (1 = CTR)
|
||||
/// </summary>
|
||||
public MediaPlatformIndex MediaPlatformIndex { get { return (MediaPlatformIndex)PartitionFlags[(int)NCSDFlags.MediaPlatformIndex]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Media Type Index (0 = Inner Device, 1 = Card1, 2 = Card2, 3 = Extended Device)
|
||||
/// </summary>
|
||||
public MediaTypeIndex MediaTypeIndex { get { return (MediaTypeIndex)PartitionFlags[(int)NCSDFlags.MediaTypeIndex]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Media Unit Size i.e. u32 MediaUnitSize = 0x200*2^flags[6];
|
||||
/// </summary>
|
||||
public uint MediaUnitSize { get { return (uint)(0x200 * Math.Pow(2, PartitionFlags[(int)NCSDFlags.MediaUnitSize])); } }
|
||||
|
||||
/// <summary>
|
||||
/// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (Only SDK 2.X)
|
||||
/// </summary>
|
||||
public MediaCardDeviceType MediaCardDevice2X { get { return (MediaCardDeviceType)PartitionFlags[(int)NCSDFlags.MediaCardDevice2X]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Partition ID table
|
||||
/// </summary>
|
||||
public byte[][] PartitionIdTable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved?
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Support for this was implemented with 9.6.0-X FIRM. Bit0=1 enables using bits 1-2, it's unknown
|
||||
/// what these two bits are actually used for(the value of these two bits get compared with some other
|
||||
/// value during NCSD verification/loading). This appears to enable a new, likely hardware-based,
|
||||
/// antipiracy check on cartridges.
|
||||
/// </summary>
|
||||
public byte FirmUpdateByte1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Support for this was implemented with 9.6.0-X FIRM, see below regarding save crypto.
|
||||
/// </summary>
|
||||
public byte FirmUpdateByte2 { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Raw NAND Format Specific
|
||||
|
||||
/// <summary>
|
||||
/// Unknown
|
||||
/// </summary>
|
||||
public byte[] Unknown { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Encrypted MBR partition-table, for the TWL partitions(key-data used for this keyslot is console-unique).
|
||||
/// </summary>
|
||||
public byte[] EncryptedMBR { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Card Info Header
|
||||
|
||||
/// <summary>
|
||||
/// CARD2: Writable Address In Media Units (For 'On-Chip' Savedata). CARD1: Always 0xFFFFFFFF.
|
||||
/// </summary>
|
||||
public byte[] CARD2WritableAddressMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Card Info Bitmask
|
||||
/// </summary>
|
||||
public byte[] CardInfoBytemask { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved1
|
||||
/// </summary>
|
||||
public byte[] Reserved3 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Title version
|
||||
/// </summary>
|
||||
public ushort TitleVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Card revision
|
||||
/// </summary>
|
||||
public ushort CardRevision { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved2
|
||||
/// </summary>
|
||||
public byte[] Reserved4 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Card seed keyY (first u64 is Media ID (same as first NCCH partitionId))
|
||||
/// </summary>
|
||||
public byte[] CardSeedKeyY { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Encrypted card seed (AES-CCM, keyslot 0x3B for retail cards, see CTRCARD_SECSEED) /// </summary>
|
||||
public byte[] EncryptedCardSeed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Card seed AES-MAC
|
||||
/// </summary>
|
||||
public byte[] CardSeedAESMAC { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Card seed nonce
|
||||
/// </summary>
|
||||
public byte[] CardSeedNonce { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved3
|
||||
/// </summary>
|
||||
public byte[] Reserved5 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Copy of first NCCH header (excluding RSA signature)
|
||||
/// </summary>
|
||||
public NCCHHeader BackupHeader { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Development Card Info Header Extension
|
||||
|
||||
/// <summary>
|
||||
/// CardDeviceReserved1
|
||||
/// </summary>
|
||||
public byte[] CardDeviceReserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// TitleKey
|
||||
/// </summary>
|
||||
public byte[] TitleKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// CardDeviceReserved2
|
||||
/// </summary>
|
||||
public byte[] CardDeviceReserved2 { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an NCSD header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="development">True if development cart, false otherwise</param>
|
||||
/// <returns>NCSD header object, null on error</returns>
|
||||
public static NCSDHeader Read(BinaryReader reader, bool development)
|
||||
{
|
||||
NCSDHeader header = new NCSDHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.RSA2048Signature = reader.ReadBytes(0x100);
|
||||
|
||||
if (new string(reader.ReadChars(4)) != NCSDMagicNumber)
|
||||
return null;
|
||||
|
||||
header.ImageSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.MediaId = reader.ReadBytes(8);
|
||||
header.PartitionsFSType = (FilesystemType)reader.ReadUInt64();
|
||||
header.PartitionsCryptType = reader.ReadBytes(8);
|
||||
|
||||
header.PartitionsTable = new PartitionTableEntry[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
header.PartitionsTable[i] = PartitionTableEntry.Read(reader);
|
||||
|
||||
if (header.PartitionsFSType == FilesystemType.Normal
|
||||
|| header.PartitionsFSType == FilesystemType.None)
|
||||
{
|
||||
header.ExheaderHash = reader.ReadBytes(0x20);
|
||||
header.AdditionalHeaderSize = reader.ReadUInt32();
|
||||
header.SectorZeroOffset = reader.ReadUInt32();
|
||||
header.PartitionFlags = reader.ReadBytes(8);
|
||||
|
||||
header.PartitionIdTable = new byte[8][];
|
||||
for (int i = 0; i < 8; i++)
|
||||
header.PartitionIdTable[i] = reader.ReadBytes(8);
|
||||
|
||||
header.Reserved1 = reader.ReadBytes(0x20);
|
||||
header.Reserved2 = reader.ReadBytes(0xE);
|
||||
header.FirmUpdateByte1 = reader.ReadByte();
|
||||
header.FirmUpdateByte2 = reader.ReadByte();
|
||||
|
||||
header.CARD2WritableAddressMediaUnits = reader.ReadBytes(4);
|
||||
header.CardInfoBytemask = reader.ReadBytes(4);
|
||||
header.Reserved3 = reader.ReadBytes(0x108);
|
||||
header.TitleVersion = reader.ReadUInt16();
|
||||
header.CardRevision = reader.ReadUInt16();
|
||||
header.Reserved4 = reader.ReadBytes(0xCEC); // Incorrectly documented as 0xCEE
|
||||
header.CardSeedKeyY = reader.ReadBytes(0x10);
|
||||
header.EncryptedCardSeed = reader.ReadBytes(0x10);
|
||||
header.CardSeedAESMAC = reader.ReadBytes(0x10);
|
||||
header.CardSeedNonce = reader.ReadBytes(0xC);
|
||||
header.Reserved5 = reader.ReadBytes(0xC4);
|
||||
header.BackupHeader = NCCHHeader.Read(reader, false);
|
||||
|
||||
if (development)
|
||||
{
|
||||
header.CardDeviceReserved1 = reader.ReadBytes(0x200);
|
||||
header.TitleKey = reader.ReadBytes(0x10);
|
||||
header.CardDeviceReserved2 = reader.ReadBytes(0xF0);
|
||||
}
|
||||
}
|
||||
else if (header.PartitionsFSType == FilesystemType.FIRM)
|
||||
{
|
||||
header.Unknown = reader.ReadBytes(0x5E);
|
||||
header.EncryptedMBR = reader.ReadBytes(0x42);
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,588 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NDecrypt.Core.Data;
|
||||
|
||||
namespace NDecrypt.Core.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>
|
||||
/// Game Title
|
||||
/// </summary>
|
||||
public char[] GameTitle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gamecode
|
||||
/// </summary>
|
||||
public uint Gamecode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Makercode
|
||||
/// </summary>
|
||||
public char[] Makercode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unitcode
|
||||
/// </summary>
|
||||
public NDSUnitcode Unitcode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Encryption seed select (device code. 0 = normal)
|
||||
/// </summary>
|
||||
public byte EncryptionSeedSelect { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Devicecapacity
|
||||
/// </summary>
|
||||
public byte Devicecapacity { get; private set; }
|
||||
public int DeviceCapacityInBytes { get { return (1 << Devicecapacity) * (1024 * 1024); } }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Game Revision (used by DSi titles)
|
||||
/// </summary>
|
||||
public ushort GameRevision { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ROM Version
|
||||
/// </summary>
|
||||
public byte RomVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Internal flags, (Bit2: Autostart)
|
||||
/// </summary>
|
||||
public byte InternalFlags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 rom offset
|
||||
/// </summary>
|
||||
public uint ARM9RomOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 entry address
|
||||
/// </summary>
|
||||
public uint ARM9EntryAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 load address
|
||||
/// </summary>
|
||||
public uint ARM9LoadAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 size
|
||||
/// </summary>
|
||||
public uint ARM9Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7 rom offset
|
||||
/// </summary>
|
||||
public uint ARM7RomOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7 entry address
|
||||
/// </summary>
|
||||
public uint ARM7EntryAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7 load address
|
||||
/// </summary>
|
||||
public uint ARM7LoadAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7 size
|
||||
/// </summary>
|
||||
public uint ARM7Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Name Table (FNT) offset
|
||||
/// </summary>
|
||||
public uint FileNameTableOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Name Table (FNT) length
|
||||
/// </summary>
|
||||
public uint FileNameTableLength { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Allocation Table (FNT) offset
|
||||
/// </summary>
|
||||
public uint FileAllocationTableOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Allocation Table (FNT) length
|
||||
/// </summary>
|
||||
public uint FileAllocationTableLength { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Name Table (FNT) offset
|
||||
/// </summary>
|
||||
public uint ARM9OverlayOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Name Table (FNT) length
|
||||
/// </summary>
|
||||
public uint ARM9OverlayLength { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Name Table (FNT) offset
|
||||
/// </summary>
|
||||
public uint ARM7OverlayOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Name Table (FNT) length
|
||||
/// </summary>
|
||||
public uint ARM7OverlayLength { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Normal card control register settings (0x00416657 for OneTimePROM)
|
||||
/// </summary>
|
||||
public byte[] NormalCardControlRegisterSettings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Secure card control register settings (0x081808F8 for OneTimePROM)
|
||||
/// </summary>
|
||||
public byte[] SecureCardControlRegisterSettings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Icon Banner offset (NDSi same as NDS, but with new extra entries)
|
||||
/// </summary>
|
||||
public uint IconBannerOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Secure area (2K) CRC
|
||||
/// </summary>
|
||||
public ushort SecureAreaCRC { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Secure transfer timeout (0x0D7E for OneTimePROM)
|
||||
/// </summary>
|
||||
public ushort SecureTransferTimeout { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 autoload
|
||||
/// </summary>
|
||||
public byte[] ARM9Autoload { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7 autoload
|
||||
/// </summary>
|
||||
public byte[] ARM7Autoload { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Secure disable
|
||||
/// </summary>
|
||||
public byte[] SecureDisable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// NTR region ROM size (excluding DSi area)
|
||||
/// </summary>
|
||||
public uint NTRRegionRomSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Header size
|
||||
/// </summary>
|
||||
public uint HeaderSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///Reserved (0x88, 0x8C, 0x90 = Unknown, used by DSi)
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Nintendo Logo
|
||||
/// </summary>
|
||||
public byte[] NintendoLogo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Nintendo Logo CRC
|
||||
/// </summary>
|
||||
public ushort NintendoLogoCRC { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Header CRC
|
||||
/// </summary>
|
||||
public ushort HeaderCRC { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Debugger reserved
|
||||
/// </summary>
|
||||
public byte[] DebuggerReserved { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extended DSi
|
||||
|
||||
/// <summary>
|
||||
/// Global MBK1..MBK5 Settings
|
||||
/// </summary>
|
||||
public byte[] GlobalMBK15Settings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Local MBK6..MBK8 Settings for ARM9
|
||||
/// </summary>
|
||||
public byte[] LocalMBK68SettingsARM9 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Local MBK6..MBK8 Settings for ARM7
|
||||
/// </summary>
|
||||
public byte[] LocalMBK68SettingsARM7 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Global MBK9 Setting
|
||||
/// </summary>
|
||||
public byte[] GlobalMBK9Setting { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public byte[] RegionFlags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Access control
|
||||
/// </summary>
|
||||
public byte[] AccessControl { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7 SCFG EXT mask (controls which devices to enable)
|
||||
/// </summary>
|
||||
public byte[] ARM7SCFGEXTMask { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved/flags? When bit2 of byte 0x1bf is set, usage of banner.sav from the title data dir is enabled.(additional banner data)
|
||||
/// </summary>
|
||||
public byte[] ReservedFlags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9i rom offset
|
||||
/// </summary>
|
||||
public uint ARM9iRomOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved3 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9i load address
|
||||
/// </summary>
|
||||
public uint ARM9iLoadAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9i size;
|
||||
/// </summary>
|
||||
public uint ARM9iSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7i rom offset
|
||||
/// </summary>
|
||||
public uint ARM7iRomOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to base address where various structures and parameters are passed to the title - what is that???
|
||||
/// </summary>
|
||||
public byte[] Reserved4 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7i load address
|
||||
/// </summary>
|
||||
public uint ARM7iLoadAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7i size;
|
||||
/// </summary>
|
||||
public uint ARM7iSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest NTR region offset
|
||||
/// </summary>
|
||||
public uint DigestNTRRegionOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest NTR region length
|
||||
/// </summary>
|
||||
public uint DigestNTRRegionLength { get; private set; }
|
||||
|
||||
// <summary>
|
||||
/// Digest TWL region offset
|
||||
/// </summary>
|
||||
public uint DigestTWLRegionOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest TWL region length
|
||||
/// </summary>
|
||||
public uint DigestTWLRegionLength { get; private set; }
|
||||
|
||||
// <summary>
|
||||
/// Digest Sector Hashtable region offset
|
||||
/// </summary>
|
||||
public uint DigestSectorHashtableRegionOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest Sector Hashtable region length
|
||||
/// </summary>
|
||||
public uint DigestSectorHashtableRegionLength { get; private set; }
|
||||
|
||||
// <summary>
|
||||
/// Digest Block Hashtable region offset
|
||||
/// </summary>
|
||||
public uint DigestBlockHashtableRegionOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest Block Hashtable region length
|
||||
/// </summary>
|
||||
public uint DigestBlockHashtableRegionLength { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest Sector size
|
||||
/// </summary>
|
||||
public uint DigestSectorSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digeset Block Sectorount
|
||||
/// </summary>
|
||||
public uint DigestBlockSectorCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Icon Banner Size (usually 0x23C0)
|
||||
/// </summary>
|
||||
public uint IconBannerSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unknown (used by DSi)
|
||||
/// </summary>
|
||||
public byte[] Unknown1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// NTR+TWL region ROM size (total size including DSi area)
|
||||
/// </summary>
|
||||
public uint NTRTWLRegionRomSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unknown (used by DSi)
|
||||
/// </summary>
|
||||
public byte[] Unknown2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modcrypt area 1 offset
|
||||
/// </summary>
|
||||
public uint ModcryptArea1Offset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modcrypt area 1 size
|
||||
/// </summary>
|
||||
public uint ModcryptArea1Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modcrypt area 2 offset
|
||||
/// </summary>
|
||||
public uint ModcryptArea2Offset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modcrypt area 2 size
|
||||
/// </summary>
|
||||
public uint ModcryptArea2Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Title ID
|
||||
/// </summary>
|
||||
public byte[] TitleID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// DSiWare: "public.sav" size
|
||||
/// </summary>
|
||||
public uint DSiWarePublicSavSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// DSiWare: "private.sav" size
|
||||
/// </summary>
|
||||
public uint DSiWarePrivateSavSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved (zero)
|
||||
/// </summary>
|
||||
public byte[] ReservedZero { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unknown (used by DSi)
|
||||
/// </summary>
|
||||
public byte[] Unknown3 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 (with encrypted secure area) SHA1 HMAC hash
|
||||
/// </summary>
|
||||
public byte[] ARM9WithSecureAreaSHA1HMACHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7 SHA1 HMAC hash
|
||||
/// </summary>
|
||||
public byte[] ARM7SHA1HMACHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest master SHA1 HMAC hash
|
||||
/// </summary>
|
||||
public byte[] DigestMasterSHA1HMACHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Banner SHA1 HMAC hash
|
||||
/// </summary>
|
||||
public byte[] BannerSHA1HMACHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9i (decrypted) SHA1 HMAC hash
|
||||
/// </summary>
|
||||
public byte[] ARM9iDecryptedSHA1HMACHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7i (decrypted) SHA1 HMAC hash
|
||||
/// </summary>
|
||||
public byte[] ARM7iDecryptedSHA1HMACHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved5 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 (without secure area) SHA1 HMAC hash
|
||||
/// </summary>
|
||||
public byte[] ARM9NoSecureAreaSHA1HMACHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved6 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved and unchecked region, always zero. Used for passing arguments in debug environment.
|
||||
/// </summary>
|
||||
public byte[] ReservedAndUnchecked { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RSA signature (the first 0xE00 bytes of the header are signed with an 1024-bit RSA signature).
|
||||
/// </summary>
|
||||
public byte[] RSASignature { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an NDS/NDSi header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>NDS/NDSi header object, null on error</returns>
|
||||
public static NDSHeader Read(BinaryReader reader)
|
||||
{
|
||||
NDSHeader header = new NDSHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.GameTitle = reader.ReadChars(0xC);
|
||||
header.Gamecode = reader.ReadUInt32();
|
||||
header.Makercode = reader.ReadChars(2);
|
||||
header.Unitcode = (NDSUnitcode)reader.ReadByte();
|
||||
header.EncryptionSeedSelect = reader.ReadByte();
|
||||
header.Devicecapacity = reader.ReadByte();
|
||||
header.Reserved1 = reader.ReadBytes(7);
|
||||
header.GameRevision = reader.ReadUInt16();
|
||||
header.RomVersion = reader.ReadByte();
|
||||
header.InternalFlags = reader.ReadByte();
|
||||
header.ARM9RomOffset = reader.ReadUInt32();
|
||||
header.ARM9EntryAddress = reader.ReadUInt32();
|
||||
header.ARM9LoadAddress = reader.ReadUInt32();
|
||||
header.ARM9Size = reader.ReadUInt32();
|
||||
header.ARM7RomOffset = reader.ReadUInt32();
|
||||
header.ARM7EntryAddress = reader.ReadUInt32();
|
||||
header.ARM7LoadAddress = reader.ReadUInt32();
|
||||
header.ARM7Size = reader.ReadUInt32();
|
||||
header.FileNameTableOffset = reader.ReadUInt32();
|
||||
header.FileNameTableLength = reader.ReadUInt32();
|
||||
header.FileAllocationTableOffset = reader.ReadUInt32();
|
||||
header.FileAllocationTableLength = reader.ReadUInt32();
|
||||
header.ARM9OverlayOffset = reader.ReadUInt32();
|
||||
header.ARM9OverlayLength = reader.ReadUInt32();
|
||||
header.ARM7OverlayOffset = reader.ReadUInt32();
|
||||
header.ARM7OverlayLength = reader.ReadUInt32();
|
||||
header.SecureDisable = reader.ReadBytes(8);
|
||||
header.NTRRegionRomSize = reader.ReadUInt32();
|
||||
header.HeaderSize = reader.ReadUInt32();
|
||||
header.Reserved2 = reader.ReadBytes(56);
|
||||
header.NintendoLogo = reader.ReadBytes(156);
|
||||
header.NintendoLogoCRC = reader.ReadUInt16();
|
||||
header.DebuggerReserved = reader.ReadBytes(0x20);
|
||||
|
||||
// If we have a DSi compatible title
|
||||
if (header.Unitcode == NDSUnitcode.NDSPlusDSi
|
||||
|| header.Unitcode == NDSUnitcode.DSi)
|
||||
{
|
||||
header.GlobalMBK15Settings = reader.ReadBytes(20);
|
||||
header.LocalMBK68SettingsARM9 = reader.ReadBytes(12);
|
||||
header.LocalMBK68SettingsARM7 = reader.ReadBytes(12);
|
||||
header.GlobalMBK9Setting = reader.ReadBytes(4);
|
||||
header.RegionFlags = reader.ReadBytes(4);
|
||||
header.AccessControl = reader.ReadBytes(4);
|
||||
header.ARM7SCFGEXTMask = reader.ReadBytes(4);
|
||||
header.ReservedFlags = reader.ReadBytes(4);
|
||||
header.ARM9iRomOffset = reader.ReadUInt32();
|
||||
header.Reserved3 = reader.ReadBytes(4);
|
||||
header.ARM9iLoadAddress = reader.ReadUInt32();
|
||||
header.ARM9iSize = reader.ReadUInt32();
|
||||
header.ARM7iRomOffset = reader.ReadUInt32();
|
||||
header.Reserved4 = reader.ReadBytes(4);
|
||||
header.ARM7iLoadAddress = reader.ReadUInt32();
|
||||
header.ARM7iSize = reader.ReadUInt32();
|
||||
header.DigestNTRRegionOffset = reader.ReadUInt32();
|
||||
header.DigestNTRRegionLength = reader.ReadUInt32();
|
||||
header.DigestTWLRegionOffset = reader.ReadUInt32();
|
||||
header.DigestTWLRegionLength = reader.ReadUInt32();
|
||||
header.DigestSectorHashtableRegionOffset = reader.ReadUInt32();
|
||||
header.DigestSectorHashtableRegionLength = reader.ReadUInt32();
|
||||
header.DigestBlockHashtableRegionOffset = reader.ReadUInt32();
|
||||
header.DigestBlockHashtableRegionLength = reader.ReadUInt32();
|
||||
header.DigestSectorSize = reader.ReadUInt32();
|
||||
header.DigestBlockSectorCount = reader.ReadUInt32();
|
||||
header.IconBannerSize = reader.ReadUInt32();
|
||||
header.Unknown1 = reader.ReadBytes(4);
|
||||
header.ModcryptArea1Offset = reader.ReadUInt32();
|
||||
header.ModcryptArea1Size = reader.ReadUInt32();
|
||||
header.ModcryptArea2Offset = reader.ReadUInt32();
|
||||
header.ModcryptArea2Size = reader.ReadUInt32();
|
||||
header.TitleID = reader.ReadBytes(8);
|
||||
header.DSiWarePublicSavSize = reader.ReadUInt32();
|
||||
header.DSiWarePrivateSavSize = reader.ReadUInt32();
|
||||
header.ReservedZero = reader.ReadBytes(176);
|
||||
header.Unknown2 = reader.ReadBytes(0x10);
|
||||
header.ARM9WithSecureAreaSHA1HMACHash = reader.ReadBytes(20);
|
||||
header.ARM7SHA1HMACHash = reader.ReadBytes(20);
|
||||
header.DigestMasterSHA1HMACHash = reader.ReadBytes(20);
|
||||
header.BannerSHA1HMACHash = reader.ReadBytes(20);
|
||||
header.ARM9iDecryptedSHA1HMACHash = reader.ReadBytes(20);
|
||||
header.ARM7iDecryptedSHA1HMACHash = reader.ReadBytes(20);
|
||||
header.Reserved5 = reader.ReadBytes(40);
|
||||
header.ARM9NoSecureAreaSHA1HMACHash = reader.ReadBytes(20);
|
||||
header.Reserved6 = reader.ReadBytes(2636);
|
||||
header.ReservedAndUnchecked = reader.ReadBytes(0x180);
|
||||
header.RSASignature = reader.ReadBytes(0x80);
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class PartitionTableEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset
|
||||
/// </summary>
|
||||
public uint Offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Length
|
||||
/// </summary>
|
||||
public uint Length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get partition table entry, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Partition table entry object, null on error</returns>
|
||||
public static PartitionTableEntry Read(BinaryReader reader)
|
||||
{
|
||||
PartitionTableEntry entry = new PartitionTableEntry();
|
||||
|
||||
try
|
||||
{
|
||||
entry.Offset = reader.ReadUInt32();
|
||||
entry.Length = reader.ReadUInt32();
|
||||
return entry;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for a valid partition
|
||||
/// </summary>
|
||||
/// <returns>True if the offset and length are not 0, false otherwise</returns>
|
||||
public bool IsValid()
|
||||
{
|
||||
return Offset != 0 && Length != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
// https://www.3dbrew.org/wiki/RomFS
|
||||
public class RomFSHeader
|
||||
{
|
||||
private const string RomFSMagicNumber = "IVFC";
|
||||
private const uint RomFSSecondMagicNumber = 0x10000;
|
||||
|
||||
/// <summary>
|
||||
/// Master hash size
|
||||
/// </summary>
|
||||
public uint MasterHashSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 1 logical offset
|
||||
/// </summary>
|
||||
public ulong Level1LogicalOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 1 hashdata size
|
||||
/// </summary>
|
||||
public ulong Level1HashdataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 1 block size, in log2
|
||||
/// </summary>
|
||||
public uint Level1BlockSizeLog2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 2 logical offset
|
||||
/// </summary>
|
||||
public ulong Level2LogicalOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 2 hashdata size
|
||||
/// </summary>
|
||||
public ulong Level2HashdataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 2 block size, in log2
|
||||
/// </summary>
|
||||
public uint Level2BlockSizeLog2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 3 logical offset
|
||||
/// </summary>
|
||||
public ulong Level3LogicalOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 3 hashdata size
|
||||
/// </summary>
|
||||
public ulong Level3HashdataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 3 block size, in log2
|
||||
/// </summary>
|
||||
public uint Level3BlockSizeLog2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved3 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved4 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional info size.
|
||||
/// </summary>
|
||||
public uint OptionalInfoSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get a RomFS header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>RomFS header object, null on error</returns>
|
||||
public static RomFSHeader Read(BinaryReader reader)
|
||||
{
|
||||
RomFSHeader header = new RomFSHeader();
|
||||
|
||||
try
|
||||
{
|
||||
if (new string(reader.ReadChars(4)) != RomFSMagicNumber)
|
||||
return null;
|
||||
|
||||
if (reader.ReadUInt32() != RomFSSecondMagicNumber)
|
||||
return null;
|
||||
|
||||
header.MasterHashSize = reader.ReadUInt32();
|
||||
header.Level1LogicalOffset = reader.ReadUInt64();
|
||||
header.Level1HashdataSize = reader.ReadUInt64();
|
||||
header.Level1BlockSizeLog2 = reader.ReadUInt32();
|
||||
header.Reserved1 = reader.ReadBytes(4);
|
||||
header.Level2LogicalOffset = reader.ReadUInt64();
|
||||
header.Level2HashdataSize = reader.ReadUInt64();
|
||||
header.Level2BlockSizeLog2 = reader.ReadUInt32();
|
||||
header.Reserved2 = reader.ReadBytes(4);
|
||||
header.Level3LogicalOffset = reader.ReadUInt64();
|
||||
header.Level3HashdataSize = reader.ReadUInt64();
|
||||
header.Level3BlockSizeLog2 = reader.ReadUInt32();
|
||||
header.Reserved3 = reader.ReadBytes(4);
|
||||
header.Reserved4 = reader.ReadBytes(4);
|
||||
header.OptionalInfoSize = reader.ReadUInt32();
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using System.IO;
|
||||
using NDecrypt.Core.Data;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class StorageInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Extdata ID
|
||||
/// </summary>
|
||||
public byte[] ExtdataID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// System savedata IDs
|
||||
/// </summary>
|
||||
public byte[] SystemSavedataIDs { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Storage accessible unique IDs
|
||||
/// </summary>
|
||||
public byte[] StorageAccessibleUniqueIDs { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Filesystem access info
|
||||
/// </summary>
|
||||
public byte[] FilesystemAccessInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Other attributes
|
||||
/// </summary>
|
||||
public StorageInfoOtherAttributes OtherAttributes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get storage info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Storage info object, null on error</returns>
|
||||
public static StorageInfo Read(BinaryReader reader)
|
||||
{
|
||||
StorageInfo si = new StorageInfo();
|
||||
|
||||
try
|
||||
{
|
||||
si.ExtdataID = reader.ReadBytes(8);
|
||||
si.SystemSavedataIDs = reader.ReadBytes(8);
|
||||
si.StorageAccessibleUniqueIDs = reader.ReadBytes(8);
|
||||
si.FilesystemAccessInfo = reader.ReadBytes(7);
|
||||
si.OtherAttributes = (StorageInfoOtherAttributes)reader.ReadByte();
|
||||
return si;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class SystemControlInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Application title (default is "CtrApp")
|
||||
/// </summary>
|
||||
public char[] ApplicationTitle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag (bit 0: CompressExefsCode, bit 1: SDApplication)
|
||||
/// </summary>
|
||||
public byte Flag { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Remaster version
|
||||
/// </summary>
|
||||
public byte[] RemasterVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Text code set info
|
||||
/// </summary>
|
||||
public CodeSetInfo TextCodesetInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stack size
|
||||
/// </summary>
|
||||
public uint StackSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read-only code set info
|
||||
/// </summary>
|
||||
public CodeSetInfo ReadOnlyCodeSetInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Data code set info
|
||||
/// </summary>
|
||||
public CodeSetInfo DataCodeSetInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// BSS size
|
||||
/// </summary>
|
||||
public uint BSSSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dependency module (program ID) list
|
||||
/// </summary>
|
||||
public byte[][] DependencyModuleList { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SystemInfo
|
||||
/// </summary>
|
||||
public SystemInfo SystemInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get system control info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>System control info object, null on error</returns>
|
||||
public static SystemControlInfo Read(BinaryReader reader)
|
||||
{
|
||||
SystemControlInfo sci = new SystemControlInfo();
|
||||
|
||||
try
|
||||
{
|
||||
sci.ApplicationTitle = reader.ReadChars(8);
|
||||
sci.Reserved1 = reader.ReadBytes(5);
|
||||
sci.Flag = reader.ReadByte();
|
||||
sci.RemasterVersion = reader.ReadBytes(2);
|
||||
sci.TextCodesetInfo = CodeSetInfo.Read(reader);
|
||||
sci.StackSize = reader.ReadUInt32();
|
||||
sci.ReadOnlyCodeSetInfo = CodeSetInfo.Read(reader);
|
||||
sci.Reserved2 = reader.ReadBytes(4);
|
||||
sci.DataCodeSetInfo = CodeSetInfo.Read(reader);
|
||||
sci.BSSSize = reader.ReadUInt32();
|
||||
|
||||
sci.DependencyModuleList = new byte[48][];
|
||||
for (int i = 0; i < 48; i++)
|
||||
sci.DependencyModuleList[i] = reader.ReadBytes(8);
|
||||
|
||||
sci.SystemInfo = SystemInfo.Read(reader);
|
||||
return sci;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Core.Headers
|
||||
{
|
||||
public class SystemInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// SaveData Size
|
||||
/// </summary>
|
||||
public ulong SaveDataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Jump ID
|
||||
/// </summary>
|
||||
public byte[] JumpID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get system info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>System info object, null on error</returns>
|
||||
public static SystemInfo Read(BinaryReader reader)
|
||||
{
|
||||
SystemInfo si = new SystemInfo();
|
||||
|
||||
try
|
||||
{
|
||||
si.SaveDataSize = reader.ReadUInt64();
|
||||
si.JumpID = reader.ReadBytes(8);
|
||||
si.Reserved = reader.ReadBytes(0x30);
|
||||
return si;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
public static class Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Add an integer value to a number represented by a byte array
|
||||
/// </summary>
|
||||
/// <param name="input">Byte array to add to</param>
|
||||
/// <param name="add">Amount to add</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
public static byte[] AddToByteArray(byte[] input, int add)
|
||||
{
|
||||
int len = input.Length;
|
||||
var bigint = new BigInteger(input.Reverse().ToArray());
|
||||
bigint += add;
|
||||
var arr = bigint.ToByteArray().Reverse().ToArray();
|
||||
|
||||
if (arr.Length < len)
|
||||
{
|
||||
byte[] temp = new byte[len];
|
||||
for (int i = 0; i < (len - arr.Length); i++)
|
||||
temp[i] = 0x00;
|
||||
|
||||
Array.Copy(arr, 0, temp, len - arr.Length, arr.Length);
|
||||
arr = temp;
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create AES cipher and intialize
|
||||
/// </summary>
|
||||
/// <param name="key">BigInteger representation of 128-bit encryption key</param>
|
||||
/// <param name="iv">AES initial value for counter</param>
|
||||
/// <param name="encrypt">True if cipher is created for encryption, false otherwise</param>
|
||||
/// <returns>Initialized AES cipher</returns>
|
||||
public static IBufferedCipher CreateAESCipher(BigInteger key, byte[] iv, bool encrypt)
|
||||
{
|
||||
var cipher = CipherUtilities.GetCipher("AES/CTR");
|
||||
cipher.Init(encrypt, new ParametersWithIV(new KeyParameter(TakeSixteen(key)), iv));
|
||||
return cipher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a rotate left on a BigInteger
|
||||
/// </summary>
|
||||
/// <param name="val">BigInteger value to rotate</param>
|
||||
/// <param name="r_bits">Number of bits to rotate</param>
|
||||
/// <param name="max_bits">Maximum number of bits to rotate on</param>
|
||||
/// <returns>Rotated BigInteger value</returns>
|
||||
public static BigInteger RotateLeft(BigInteger val, int r_bits, int max_bits)
|
||||
{
|
||||
return (val << r_bits % max_bits) & (BigInteger.Pow(2, max_bits) - 1) | ((val & (BigInteger.Pow(2, max_bits) - 1)) >> (max_bits - (r_bits % max_bits)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a 16-byte array representation of a BigInteger
|
||||
/// </summary>
|
||||
/// <param name="input">BigInteger value to convert</param>
|
||||
/// <returns>16-byte array representing the BigInteger</returns>
|
||||
private static byte[] TakeSixteen(BigInteger input)
|
||||
{
|
||||
var arr = input.ToByteArray().Take(16).Reverse().ToArray();
|
||||
|
||||
if (arr.Length < 16)
|
||||
{
|
||||
byte[] temp = new byte[16];
|
||||
for (int i = 0; i < (16 - arr.Length); i++)
|
||||
temp[i] = 0x00;
|
||||
|
||||
Array.Copy(arr, 0, temp, 16 - arr.Length, arr.Length);
|
||||
arr = temp;
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
public interface ITool
|
||||
{
|
||||
bool ProcessFile();
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>netcoreapp3.1;net5.0</TargetFrameworks>
|
||||
<RuntimeIdentifiers>win10-x64;win7-x86</RuntimeIdentifiers>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.NetCore" Version="1.8.8" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,142 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using NDecrypt.Core.Data;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
DisplayHelp("Not enough arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
bool? encrypt;
|
||||
if (args[0] == "decrypt" || args[0] == "d")
|
||||
{
|
||||
encrypt = false;
|
||||
}
|
||||
else if (args[0] == "encrypt" || args[0] == "e")
|
||||
{
|
||||
encrypt = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayHelp($"Invalid operation: {args[0]}");
|
||||
return;
|
||||
}
|
||||
|
||||
bool development = false, force = false;
|
||||
int start = 1;
|
||||
for ( ; start < args.Length; start++)
|
||||
{
|
||||
if (args[start] == "-dev" || args[start] == "--development")
|
||||
development = true;
|
||||
|
||||
else if (args[start] == "-f" || args[start] == "--force")
|
||||
force = true;
|
||||
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
// 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]))
|
||||
{
|
||||
ITool tool = DeriveTool(args[i], encrypt.Value, development, force);
|
||||
if (tool?.ProcessFile() != true)
|
||||
Console.WriteLine("Processing failed!");
|
||||
}
|
||||
else if (Directory.Exists(args[i]))
|
||||
{
|
||||
foreach (string file in Directory.EnumerateFiles(args[i], "*", SearchOption.AllDirectories))
|
||||
{
|
||||
ITool tool = DeriveTool(file, encrypt.Value, development, force);
|
||||
if (tool?.ProcessFile() != true)
|
||||
Console.WriteLine("Processing failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Press Enter to Exit...");
|
||||
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: NDecrypt.exe (decrypt|encrypt) [-dev] [-f] <file|dir> ...");
|
||||
}
|
||||
|
||||
private enum RomType
|
||||
{
|
||||
NULL,
|
||||
NDS,
|
||||
NDSi,
|
||||
N3DS,
|
||||
}
|
||||
|
||||
/// <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">True if we are using development keys, false otherwise</param>
|
||||
/// <param name="force">True if operations should be forced, false otherwise</param>
|
||||
/// <returns></returns>
|
||||
private static ITool DeriveTool(string filename, bool encrypt, bool development, bool force)
|
||||
{
|
||||
RomType type = DetermineRomType(filename);
|
||||
switch(type)
|
||||
{
|
||||
case RomType.NDS:
|
||||
case RomType.NDSi:
|
||||
return new DSTool(filename, encrypt, force);
|
||||
case RomType.N3DS:
|
||||
return new ThreeDSTool(filename, development, encrypt, force);
|
||||
case RomType.NULL:
|
||||
default:
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,584 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using NDecrypt.Core.Data;
|
||||
using NDecrypt.Core.Headers;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
public class ThreeDSTool : ITool
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the input 3DS file
|
||||
/// </summary>
|
||||
private readonly string filename;
|
||||
|
||||
/// <summary>
|
||||
/// Flag to detrmine if development keys should be used
|
||||
/// </summary>
|
||||
private readonly bool development;
|
||||
|
||||
/// <summary>
|
||||
/// Flag to determine if encrypting or decrypting
|
||||
/// </summary>
|
||||
private readonly bool encrypt;
|
||||
|
||||
/// <summary>
|
||||
/// Flag to determine if forcing operations
|
||||
/// </summary>
|
||||
private readonly bool force;
|
||||
|
||||
public ThreeDSTool(string filename, bool development, bool encrypt, bool force)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.development = development;
|
||||
this.encrypt = encrypt;
|
||||
this.force = force;
|
||||
}
|
||||
|
||||
#region Common Methods
|
||||
|
||||
/// <summary>
|
||||
/// Process an input file given the input values
|
||||
/// </summary>
|
||||
public bool ProcessFile()
|
||||
{
|
||||
// Make sure we have a file to process first
|
||||
Console.WriteLine(filename);
|
||||
if (!File.Exists(filename))
|
||||
return false;
|
||||
|
||||
// Open the read and write on the same file for inplace processing
|
||||
using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
|
||||
using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)))
|
||||
{
|
||||
NCSDHeader header = NCSDHeader.Read(reader, development);
|
||||
if (header == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS Rom!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process all 8 NCCH partitions
|
||||
ProcessAllPartitions(header, reader, writer);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process all partitions in the partition table of an NCSD header
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessAllPartitions(NCSDHeader ncsdHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// Iterate over all 8 NCCH partitions
|
||||
for (int p = 0; p < 8; p++)
|
||||
{
|
||||
NCCHHeader ncchHeader = GetPartitionHeader(ncsdHeader, reader, p);
|
||||
if (ncchHeader == null)
|
||||
continue;
|
||||
|
||||
ProcessPartition(ncsdHeader, ncchHeader, reader, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific partition header from the partition table
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="partitionNumber">Partition number to attempt to retrieve</param>
|
||||
/// <returns>NCCH header for the partition requested, null on error</returns>
|
||||
private NCCHHeader GetPartitionHeader(NCSDHeader ncsdHeader, BinaryReader reader, int partitionNumber)
|
||||
{
|
||||
if (!ncsdHeader.PartitionsTable[partitionNumber].IsValid())
|
||||
{
|
||||
Console.WriteLine($"Partition {partitionNumber} Not found... Skipping...");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Seek to the beginning of the NCCH partition
|
||||
reader.BaseStream.Seek((ncsdHeader.PartitionsTable[partitionNumber].Offset * ncsdHeader.MediaUnitSize), SeekOrigin.Begin);
|
||||
|
||||
NCCHHeader partitionHeader = NCCHHeader.Read(reader, true);
|
||||
if (partitionHeader == null)
|
||||
{
|
||||
Console.WriteLine($"Partition {partitionNumber} Unable to read NCCH header");
|
||||
return null;
|
||||
}
|
||||
|
||||
partitionHeader.PartitionNumber = partitionNumber;
|
||||
partitionHeader.Entry = ncsdHeader.PartitionsTable[partitionNumber];
|
||||
return partitionHeader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a single partition
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessPartition(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If we're forcing the operation, tell the user
|
||||
if (force)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} is not verified due to force flag being set.");
|
||||
}
|
||||
// If we're not forcing the operation, check if the 'NoCrypto' bit is set
|
||||
else if (ncchHeader.Flags.PossblyDecrypted ^ encrypt)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber}: Already " + (encrypt ? "Encrypted" : "Decrypted") + "?...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the Keys to be used
|
||||
SetEncryptionKeys(ncsdHeader, ncchHeader);
|
||||
|
||||
// Process the extended header
|
||||
ProcessExtendedHeader(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// If we're encrypting, encrypt the filesystems and update the flags
|
||||
if (encrypt)
|
||||
{
|
||||
EncryptExeFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
EncryptRomFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
UpdateEncryptCryptoAndMasks(ncsdHeader, ncchHeader, writer);
|
||||
}
|
||||
|
||||
// If we're decrypting, decrypt the filesystems and update the flags
|
||||
else
|
||||
{
|
||||
DecryptExeFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
DecryptRomFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
UpdateDecryptCryptoAndMasks(ncsdHeader, ncchHeader, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine the set of keys to be used for encryption or decryption
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
private void SetEncryptionKeys(NCSDHeader ncsdHeader, NCCHHeader ncchHeader)
|
||||
{
|
||||
ncchHeader.KeyX = 0;
|
||||
ncchHeader.KeyX2C = development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C;
|
||||
|
||||
// Backup headers can't have a KeyY value set
|
||||
if (ncchHeader.RSA2048Signature != null)
|
||||
ncchHeader.KeyY = new BigInteger(ncchHeader.RSA2048Signature.Take(16).Reverse().ToArray());
|
||||
else
|
||||
ncchHeader.KeyY = new BigInteger(0);
|
||||
|
||||
ncchHeader.NormalKey = 0;
|
||||
ncchHeader.NormalKey2C = Helper.RotateLeft((Helper.RotateLeft(ncchHeader.KeyX2C, 2, 128) ^ ncchHeader.KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
|
||||
// Set the header to use based on mode
|
||||
BitMasks masks;
|
||||
CryptoMethod method;
|
||||
if (encrypt)
|
||||
{
|
||||
masks = ncsdHeader.BackupHeader.Flags.BitMasks;
|
||||
method = ncsdHeader.BackupHeader.Flags.CryptoMethod;
|
||||
}
|
||||
else
|
||||
{
|
||||
masks = ncchHeader.Flags.BitMasks;
|
||||
method = ncchHeader.Flags.CryptoMethod;
|
||||
}
|
||||
|
||||
if (masks.HasFlag(BitMasks.FixedCryptoKey))
|
||||
{
|
||||
ncchHeader.NormalKey = 0x00;
|
||||
ncchHeader.NormalKey2C = 0x00;
|
||||
Console.WriteLine("Encryption Method: Zero Key");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (method == CryptoMethod.Original)
|
||||
{
|
||||
ncchHeader.KeyX = development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C;
|
||||
Console.WriteLine("Encryption Method: Key 0x2C");
|
||||
}
|
||||
else if (method == CryptoMethod.Seven)
|
||||
{
|
||||
ncchHeader.KeyX = development ? Constants.DevKeyX0x25 : Constants.KeyX0x25;
|
||||
Console.WriteLine("Encryption Method: Key 0x25");
|
||||
}
|
||||
else if (method == CryptoMethod.NineThree)
|
||||
{
|
||||
ncchHeader.KeyX = development ? Constants.DevKeyX0x18 : Constants.KeyX0x18;
|
||||
Console.WriteLine("Encryption Method: Key 0x18");
|
||||
}
|
||||
else if (method == CryptoMethod.NineSix)
|
||||
{
|
||||
ncchHeader.KeyX = development ? Constants.DevKeyX0x1B : Constants.KeyX0x1B;
|
||||
Console.WriteLine("Encryption Method: Key 0x1B");
|
||||
}
|
||||
|
||||
ncchHeader.NormalKey = Helper.RotateLeft((Helper.RotateLeft(ncchHeader.KeyX, 2, 128) ^ ncchHeader.KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the extended header, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private bool ProcessExtendedHeader(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
if (ncchHeader.ExtendedHeaderSizeInBytes > 0)
|
||||
{
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x200, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x200, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + ": ExHeader");
|
||||
|
||||
var cipher = Helper.CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.PlainIV, encrypt);
|
||||
writer.Write(cipher.ProcessBytes(reader.ReadBytes(Constants.CXTExtendedDataHeaderLength)));
|
||||
writer.Flush();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Extended Header... Skipping...");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the extended header, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessExeFSFileEntries(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
ExeFSHeader exefsHeader = ExeFSHeader.Read(reader);
|
||||
|
||||
// If the header failed to read, log and return
|
||||
if (exefsHeader == null)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS header could not be read. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders)
|
||||
{
|
||||
// Only decrypt a file if it's a code binary
|
||||
if (!fileHeader.IsCodeBinary)
|
||||
continue;
|
||||
|
||||
uint datalenM = ((fileHeader.FileSize) / (1024 * 1024));
|
||||
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
|
||||
uint ctroffset = ((fileHeader.FileOffset + ncsdHeader.MediaUnitSize) / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffsetForHeader = Helper.AddToByteArray(ncchHeader.ExeFSIV, (int)ctroffset);
|
||||
|
||||
var firstCipher = Helper.CreateAESCipher(ncchHeader.NormalKey, exefsIVWithOffsetForHeader, encrypt);
|
||||
var secondCipher = Helper.CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffsetForHeader, !encrypt);
|
||||
|
||||
reader.BaseStream.Seek((((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * ncsdHeader.MediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * ncsdHeader.MediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
if (datalenM > 0)
|
||||
{
|
||||
for (int i = 0; i < datalenM; i++)
|
||||
{
|
||||
writer.Write(secondCipher.ProcessBytes(firstCipher.ProcessBytes(reader.ReadBytes(1024 * 1024))));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {i} / {datalenM + 1} mb...");
|
||||
}
|
||||
}
|
||||
|
||||
if (datalenB > 0)
|
||||
{
|
||||
writer.Write(secondCipher.DoFinal(firstCipher.DoFinal(reader.ReadBytes((int)datalenB))));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the ExeFS Filename Table
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessExeFSFilenameTable(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table");
|
||||
|
||||
var exeFSFilenameTable = Helper.CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.ExeFSIV, encrypt);
|
||||
writer.Write(exeFSFilenameTable.ProcessBytes(reader.ReadBytes((int)ncsdHeader.MediaUnitSize)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
int exefsSizeM = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * ncsdHeader.MediaUnitSize) / (1024 * 1024));
|
||||
int exefsSizeB = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * ncsdHeader.MediaUnitSize) % (1024 * 1024));
|
||||
int ctroffsetE = (int)(ncsdHeader.MediaUnitSize / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffset = Helper.AddToByteArray(ncchHeader.ExeFSIV, ctroffsetE);
|
||||
|
||||
var exeFS = Helper.CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffset, encrypt);
|
||||
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
if (exefsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < exefsSizeM; i++)
|
||||
{
|
||||
writer.Write(exeFS.ProcessBytes(reader.ReadBytes(1024 * 1024)));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (exefsSizeB > 0)
|
||||
{
|
||||
writer.Write(exeFS.DoFinal(reader.ReadBytes(exefsSizeB)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Decrypt
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void DecryptExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If the ExeFS size is 0, we log and return
|
||||
if (ncchHeader.ExeFSSizeInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrypt the filename table
|
||||
ProcessExeFSFilenameTable(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
if (ncchHeader.Flags.CryptoMethod != CryptoMethod.Original)
|
||||
ProcessExeFSFileEntries(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// Decrypt the rest of the ExeFS
|
||||
ProcessExeFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the RomFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// TODO: See how much can be extracted into a common method with Encrypt
|
||||
private void DecryptRomFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If the RomFS offset is 0, we log and return
|
||||
if (ncchHeader.RomFSOffsetInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} RomFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) / (1024 * 1024));
|
||||
int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) % (1024 * 1024));
|
||||
|
||||
var cipher = Helper.CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV, encrypt);
|
||||
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
if (romfsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < romfsSizeM; i++)
|
||||
{
|
||||
writer.Write(cipher.ProcessBytes(reader.ReadBytes(1024 * 1024)));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Decrypting: {i} / {romfsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
writer.Write(cipher.DoFinal(reader.ReadBytes(romfsSizeB)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Decrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the CryptoMethod and BitMasks for the decrypted partition
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void UpdateDecryptCryptoAndMasks(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryWriter writer)
|
||||
{
|
||||
// Write the new CryptoMethod
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18B, SeekOrigin.Begin);
|
||||
writer.Write((byte)CryptoMethod.Original);
|
||||
writer.Flush();
|
||||
|
||||
// Write the new BitMasks flag
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18F, SeekOrigin.Begin);
|
||||
BitMasks flag = ncchHeader.Flags.BitMasks;
|
||||
flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
|
||||
flag |= BitMasks.NoCrypto;
|
||||
writer.Write((byte)flag);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Encrypt
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void EncryptExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If the ExeFS size is 0, we log and return
|
||||
if (ncchHeader.ExeFSSizeInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
if (ncsdHeader.BackupHeader.Flags.CryptoMethod != CryptoMethod.Original)
|
||||
ProcessExeFSFileEntries(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// Encrypt the filename table
|
||||
ProcessExeFSFilenameTable(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// Encrypt the rest of the ExeFS
|
||||
ProcessExeFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the RomFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// TODO: See how much can be extracted into a common method with Decrypt
|
||||
private void EncryptRomFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If the RomFS offset is 0, we log and return
|
||||
if (ncchHeader.RomFSOffsetInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} RomFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) / (1024 * 1024));
|
||||
int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) % (1024 * 1024));
|
||||
|
||||
// Encrypting RomFS for partitions 1 and up always use Key0x2C
|
||||
if (ncchHeader.PartitionNumber > 0)
|
||||
{
|
||||
if (ncsdHeader.BackupHeader.Flags?.BitMasks.HasFlag(BitMasks.FixedCryptoKey) == true) // except if using zero-key
|
||||
{
|
||||
ncchHeader.NormalKey = 0x00;
|
||||
}
|
||||
else
|
||||
{
|
||||
ncchHeader.KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
ncchHeader.NormalKey = Helper.RotateLeft((Helper.RotateLeft(ncchHeader.KeyX, 2, 128) ^ ncchHeader.KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
}
|
||||
|
||||
var cipher = Helper.CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV, encrypt);
|
||||
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
if (romfsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < romfsSizeM; i++)
|
||||
{
|
||||
writer.Write(cipher.ProcessBytes(reader.ReadBytes(1024 * 1024)));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Encrypting: {i} / {romfsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
writer.Write(cipher.DoFinal(reader.ReadBytes(romfsSizeB)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Encrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the CryptoMethod and BitMasks for the encrypted partition
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void UpdateEncryptCryptoAndMasks(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryWriter writer)
|
||||
{
|
||||
// Write the new CryptoMethod
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18B, SeekOrigin.Begin);
|
||||
|
||||
// For partitions 1 and up, set crypto-method to 0x00
|
||||
if (ncchHeader.PartitionNumber > 0)
|
||||
writer.Write((byte)CryptoMethod.Original);
|
||||
|
||||
// If partition 0, restore crypto-method from backup flags
|
||||
else
|
||||
writer.Write((byte)ncsdHeader.BackupHeader.Flags.CryptoMethod);
|
||||
|
||||
writer.Flush();
|
||||
|
||||
// Write the new BitMasks flag
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18F, SeekOrigin.Begin);
|
||||
BitMasks flag = ncchHeader.Flags.BitMasks;
|
||||
flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF;
|
||||
flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & ncsdHeader.BackupHeader.Flags.BitMasks;
|
||||
writer.Write((byte)flag);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,12 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.28803.156
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NDecrypt", "NDecrypt\NDecrypt.csproj", "{2E30006A-3C60-4576-A262-937B21C83C06}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{31CE0445-F693-4C9A-B6CD-499C38CFF7FE}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NDecrypt.Core", "NDecrypt.Core\NDecrypt.Core.csproj", "{91C54370-5741-4742-B2E9-EC498551AD1C}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NDecrypt", "NDecrypt\NDecrypt.csproj", "{91C54370-5741-4742-B2E9-EC498551AD1C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -18,10 +16,6 @@ Global
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{2E30006A-3C60-4576-A262-937B21C83C06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2E30006A-3C60-4576-A262-937B21C83C06}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2E30006A-3C60-4576-A262-937B21C83C06}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2E30006A-3C60-4576-A262-937B21C83C06}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{91C54370-5741-4742-B2E9-EC498551AD1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{91C54370-5741-4742-B2E9-EC498551AD1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{91C54370-5741-4742-B2E9-EC498551AD1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NDecrypt.Data;
|
||||
using NDecrypt.Headers;
|
||||
|
||||
namespace NDecrypt
|
||||
@@ -21,6 +23,13 @@ namespace NDecrypt
|
||||
/// </summary>
|
||||
private readonly bool force;
|
||||
|
||||
#region Encryption process variables
|
||||
|
||||
private uint[] cardHash = new uint[0x412];
|
||||
private uint[] arg2 = new uint[3];
|
||||
|
||||
#endregion
|
||||
|
||||
public DSTool(string filename, bool encrypt, bool force)
|
||||
{
|
||||
this.filename = filename;
|
||||
@@ -50,10 +59,308 @@ namespace NDecrypt
|
||||
}
|
||||
|
||||
// Process the secure area
|
||||
header.ProcessSecureArea(reader, writer, encrypt, force);
|
||||
ProcessSecureArea(header, reader, writer);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process secure area in the DS/DSi file
|
||||
/// </summary>
|
||||
/// <param name="ndsHeader">NDS header representing the DS file</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
public void ProcessSecureArea(NDSHeader ndsHeader, BinaryReader reader, BinaryWriter 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 (encrypt ^ isDecrypted.Value)
|
||||
{
|
||||
Console.WriteLine("File is already " + (encrypt ? "encrypted" : "decrypted"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessARM9(ndsHeader, reader, writer);
|
||||
|
||||
Console.WriteLine("File has been " + (encrypt ? "encrypted" : "decrypted"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the current file is already decrypted or not (or has an empty secure area)
|
||||
/// </summary>
|
||||
/// <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();
|
||||
|
||||
// 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))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// Properly encrypted prototype value
|
||||
else if (firstValue == 0xA71329EE && secondValue == 0x2A1D4C38)
|
||||
{
|
||||
Console.WriteLine("Encrypted secure area for prototype found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Standard decryption values
|
||||
return (firstValue == 0xE7FFDEFF && secondValue == 0xE7FFDEFF);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the secure ARM9 region of the file, if possible
|
||||
/// </summary>
|
||||
/// <param name="ndsHeader">NDS header representing the DS file</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessARM9(NDSHeader ndsHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// 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(ndsHeader);
|
||||
if (!encrypt) Decrypt(ref p1, ref p0);
|
||||
arg2[1] <<= 1;
|
||||
arg2[2] >>= 1;
|
||||
Init2();
|
||||
|
||||
// If we're decrypting, set the proper flags
|
||||
if (!encrypt)
|
||||
{
|
||||
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.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(ndsHeader);
|
||||
Encrypt(ref p1, ref p0);
|
||||
|
||||
writer.Write(p0);
|
||||
writer.Write(p1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// First common initialization step
|
||||
/// </summary>
|
||||
/// <param name="ndsHeader">NDS header representing the DS file</param>
|
||||
private void Init1(NDSHeader ndsHeader)
|
||||
{
|
||||
Buffer.BlockCopy(Constants.NDSEncryptionData, 0, cardHash, 0, 4 * (1024 + 18));
|
||||
arg2 = new uint[] { ndsHeader.Gamecode, ndsHeader.Gamecode >> 1, ndsHeader.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">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];
|
||||
}
|
||||
|
||||
/// <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];
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,27 +41,27 @@ namespace NDecrypt.Headers
|
||||
/// <summary>
|
||||
/// Boot rom key
|
||||
/// </summary>
|
||||
private BigInteger KeyX;
|
||||
public BigInteger KeyX { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// NCCH boot rom key
|
||||
/// </summary>
|
||||
private BigInteger KeyX2C;
|
||||
public BigInteger KeyX2C { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Kernel9/Process9 key
|
||||
/// </summary>
|
||||
private BigInteger KeyY;
|
||||
public BigInteger KeyY { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Normal AES key
|
||||
/// </summary>
|
||||
private BigInteger NormalKey;
|
||||
public BigInteger NormalKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// NCCH AES key
|
||||
/// </summary>
|
||||
private BigInteger NormalKey2C;
|
||||
public BigInteger NormalKey2C { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maker code
|
||||
@@ -246,363 +246,5 @@ namespace NDecrypt.Headers
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a single partition
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// <param name="header">NCSD header representing the 3DS file</param>
|
||||
/// <param name="encrypt">True if we want to encrypt the partitions, false otherwise</param>
|
||||
/// <param name="development">True if development keys should be used, false otherwise</param>
|
||||
/// <param name="force">True if we want to force the operation, false otherwise</param>
|
||||
public void ProcessPartition(BinaryReader reader, BinaryWriter writer, NCSDHeader header, bool encrypt, bool development, bool force)
|
||||
{
|
||||
// If we're forcing the operation, tell the user
|
||||
if (force)
|
||||
{
|
||||
Console.WriteLine($"Partition {PartitionNumber} is not verified due to force flag being set.");
|
||||
}
|
||||
// If we're not forcing the operation, check if the 'NoCrypto' bit is set
|
||||
else if (Flags.PossblyDecrypted ^ encrypt)
|
||||
{
|
||||
Console.WriteLine($"Partition {PartitionNumber}: Already " + (encrypt ? "Encrypted" : "Decrypted") + "?...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the Keys to be used
|
||||
SetEncryptionKeys(header.BackupHeader.Flags, encrypt, development);
|
||||
|
||||
// Process each of the pieces if they exist
|
||||
ProcessExtendedHeader(reader, writer, header.MediaUnitSize, encrypt);
|
||||
ProcessExeFS(reader, writer, header.MediaUnitSize, header.BackupHeader.Flags, encrypt);
|
||||
ProcessRomFS(reader, writer, header.MediaUnitSize, header.BackupHeader.Flags, encrypt, development);
|
||||
|
||||
// Write out new CryptoMethod and BitMask flags
|
||||
UpdateCryptoAndMasks(writer, header, encrypt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine the set of keys to be used for encryption or decryption
|
||||
/// </summary>
|
||||
/// <param name="backupFlags">File backup flags for encryption</param>
|
||||
/// <param name="encrypt">True if we're encrypting the file, false otherwise</param>
|
||||
/// <param name="development">True if development keys should be used, false otherwise</param>
|
||||
private void SetEncryptionKeys(NCCHHeaderFlags backupFlags, bool encrypt, bool development)
|
||||
{
|
||||
KeyX = 0;
|
||||
KeyX2C = development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C;
|
||||
|
||||
// Backup headers can't have a KeyY value set
|
||||
if (RSA2048Signature != null)
|
||||
KeyY = new BigInteger(RSA2048Signature.Take(16).Reverse().ToArray());
|
||||
else
|
||||
KeyY = new BigInteger(0);
|
||||
|
||||
NormalKey = 0;
|
||||
NormalKey2C = Helper.RotateLeft((Helper.RotateLeft(KeyX2C, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
|
||||
// Set the header to use based on mode
|
||||
BitMasks masks;
|
||||
CryptoMethod method;
|
||||
if (encrypt)
|
||||
{
|
||||
masks = backupFlags.BitMasks;
|
||||
method = backupFlags.CryptoMethod;
|
||||
}
|
||||
else
|
||||
{
|
||||
masks = Flags.BitMasks;
|
||||
method = Flags.CryptoMethod;
|
||||
}
|
||||
|
||||
if (masks.HasFlag(BitMasks.FixedCryptoKey))
|
||||
{
|
||||
NormalKey = 0x00;
|
||||
NormalKey2C = 0x00;
|
||||
Console.WriteLine("Encryption Method: Zero Key");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (method == CryptoMethod.Original)
|
||||
{
|
||||
KeyX = development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C;
|
||||
Console.WriteLine("Encryption Method: Key 0x2C");
|
||||
}
|
||||
else if (method == CryptoMethod.Seven)
|
||||
{
|
||||
KeyX = development ? Constants.DevKeyX0x25 : Constants.KeyX0x25;
|
||||
Console.WriteLine("Encryption Method: Key 0x25");
|
||||
}
|
||||
else if (method == CryptoMethod.NineThree)
|
||||
{
|
||||
KeyX = development ? Constants.DevKeyX0x18 : Constants.KeyX0x18;
|
||||
Console.WriteLine("Encryption Method: Key 0x18");
|
||||
}
|
||||
else if (method == CryptoMethod.NineSix)
|
||||
{
|
||||
KeyX = development ? Constants.DevKeyX0x1B : Constants.KeyX0x1B;
|
||||
Console.WriteLine("Encryption Method: Key 0x1B");
|
||||
}
|
||||
|
||||
NormalKey = Helper.RotateLeft((Helper.RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the extended header, if it exists
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// <param name="mediaUnitSize">Number of bytes per media unit</param>
|
||||
/// <param name="encrypt">True if we want to encrypt the extended header, false otherwise</param>
|
||||
private bool ProcessExtendedHeader(BinaryReader reader, BinaryWriter writer, uint mediaUnitSize, bool encrypt)
|
||||
{
|
||||
if (ExtendedHeaderSizeInBytes > 0)
|
||||
{
|
||||
reader.BaseStream.Seek((Entry.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((Entry.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + ": ExHeader");
|
||||
|
||||
var cipher = Helper.CreateAESCipher(NormalKey2C, PlainIV, encrypt);
|
||||
writer.Write(cipher.ProcessBytes(reader.ReadBytes(Constants.CXTExtendedDataHeaderLength)));
|
||||
writer.Flush();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Partition {PartitionNumber} ExeFS: No Extended Header... Skipping...");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// <param name="mediaUnitSize">Number of bytes per media unit</param>
|
||||
/// <param name="backupFlags">File backup flags for encryption</param>
|
||||
/// <param name="encrypt">True if we want to encrypt the extended header, false otherwise</param>
|
||||
private void ProcessExeFS(BinaryReader reader, BinaryWriter writer, uint mediaUnitSize, NCCHHeaderFlags backupFlags, bool encrypt)
|
||||
{
|
||||
if (ExeFSSizeInMediaUnits > 0)
|
||||
{
|
||||
// If we're decrypting, we need to decrypt the filename table first
|
||||
if (!encrypt)
|
||||
ProcessExeFSFilenameTable(reader, writer, mediaUnitSize, encrypt);
|
||||
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
if ((!encrypt && Flags.CryptoMethod != CryptoMethod.Original)
|
||||
|| (encrypt && backupFlags.CryptoMethod != CryptoMethod.Original))
|
||||
{
|
||||
reader.BaseStream.Seek((Entry.Offset + ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
|
||||
ExeFSHeader exefsHeader = ExeFSHeader.Read(reader);
|
||||
if (exefsHeader != null)
|
||||
{
|
||||
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders)
|
||||
{
|
||||
// Only decrypt a file if it's a code binary
|
||||
if (!fileHeader.IsCodeBinary)
|
||||
continue;
|
||||
|
||||
uint datalenM = ((fileHeader.FileSize) / (1024 * 1024));
|
||||
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
|
||||
uint ctroffset = ((fileHeader.FileOffset + mediaUnitSize) / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffsetForHeader = Helper.AddToByteArray(ExeFSIV, (int)ctroffset);
|
||||
|
||||
var firstCipher = Helper.CreateAESCipher(NormalKey, exefsIVWithOffsetForHeader, encrypt);
|
||||
var secondCipher = Helper.CreateAESCipher(NormalKey2C, exefsIVWithOffsetForHeader, !encrypt);
|
||||
|
||||
reader.BaseStream.Seek((((Entry.Offset + ExeFSOffsetInMediaUnits) + 1) * mediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((((Entry.Offset + ExeFSOffsetInMediaUnits) + 1) * mediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
if (datalenM > 0)
|
||||
{
|
||||
for (int i = 0; i < datalenM; i++)
|
||||
{
|
||||
writer.Write(secondCipher.ProcessBytes(firstCipher.ProcessBytes(reader.ReadBytes(1024 * 1024))));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {i} / {datalenM + 1} mb...");
|
||||
}
|
||||
}
|
||||
|
||||
if (datalenB > 0)
|
||||
{
|
||||
writer.Write(secondCipher.DoFinal(firstCipher.DoFinal(reader.ReadBytes((int)datalenB))));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're encrypting, we need to encrypt the filename table now
|
||||
if (encrypt)
|
||||
ProcessExeFSFilenameTable(reader, writer, mediaUnitSize, encrypt);
|
||||
|
||||
// Process the ExeFS
|
||||
int exefsSizeM = (int)((long)((ExeFSSizeInMediaUnits - 1) * mediaUnitSize) / (1024 * 1024));
|
||||
int exefsSizeB = (int)((long)((ExeFSSizeInMediaUnits - 1) * mediaUnitSize) % (1024 * 1024));
|
||||
int ctroffsetE = (int)(mediaUnitSize / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffset = Helper.AddToByteArray(ExeFSIV, ctroffsetE);
|
||||
|
||||
var exeFS = Helper.CreateAESCipher(NormalKey2C, exefsIVWithOffset, encrypt);
|
||||
|
||||
reader.BaseStream.Seek((Entry.Offset + ExeFSOffsetInMediaUnits + 1) * mediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((Entry.Offset + ExeFSOffsetInMediaUnits + 1) * mediaUnitSize, SeekOrigin.Begin);
|
||||
if (exefsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < exefsSizeM; i++)
|
||||
{
|
||||
writer.Write(exeFS.ProcessBytes(reader.ReadBytes(1024 * 1024)));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (exefsSizeB > 0)
|
||||
{
|
||||
writer.Write(exeFS.DoFinal(reader.ReadBytes(exefsSizeB)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Partition {PartitionNumber} ExeFS: No Data... Skipping...");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the ExeFS Filename Table
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// <param name="mediaUnitSize">Number of bytes per media unit</param>
|
||||
/// <param name="encrypt">True if we want to encrypt the extended header, false otherwise</param>
|
||||
private void ProcessExeFSFilenameTable(BinaryReader reader, BinaryWriter writer, uint mediaUnitSize, bool encrypt)
|
||||
{
|
||||
reader.BaseStream.Seek((Entry.Offset + ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((Entry.Offset + ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table");
|
||||
|
||||
var exeFSFilenameTable = Helper.CreateAESCipher(NormalKey2C, ExeFSIV, encrypt);
|
||||
writer.Write(exeFSFilenameTable.ProcessBytes(reader.ReadBytes((int)mediaUnitSize)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the RomFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// <param name="mediaUnitSize">Number of bytes per media unit</param>
|
||||
/// <param name="backupFlags">File backup flags for encryption</param>
|
||||
/// <param name="encrypt">True if we want to encrypt the extended header, false otherwise</param>
|
||||
/// <param name="development">True if development keys should be used, false otherwise</param>
|
||||
private void ProcessRomFS(BinaryReader reader, BinaryWriter writer, uint mediaUnitSize, NCCHHeaderFlags backupFlags, bool encrypt, bool development)
|
||||
{
|
||||
if (RomFSOffsetInMediaUnits != 0)
|
||||
{
|
||||
long romfsSizeM = (int)((long)(RomFSSizeInMediaUnits * mediaUnitSize) / (1024 * 1024));
|
||||
int romfsSizeB = (int)((long)(RomFSSizeInMediaUnits * mediaUnitSize) % (1024 * 1024));
|
||||
|
||||
// Encrypting RomFS for partitions 1 and up always use Key0x2C
|
||||
if (encrypt && PartitionNumber > 0)
|
||||
{
|
||||
// If the backup flags aren't provided and we're encrypting, assume defaults
|
||||
if (backupFlags == null)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
NormalKey = Helper.RotateLeft((Helper.RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
|
||||
if (backupFlags.BitMasks.HasFlag(BitMasks.FixedCryptoKey)) // except if using zero-key
|
||||
{
|
||||
NormalKey = 0x00;
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
NormalKey = Helper.RotateLeft((Helper.RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
}
|
||||
|
||||
var cipher = Helper.CreateAESCipher(NormalKey, RomFSIV, encrypt);
|
||||
|
||||
reader.BaseStream.Seek((Entry.Offset + RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((Entry.Offset + RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
|
||||
if (romfsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < romfsSizeM; i++)
|
||||
{
|
||||
writer.Write(cipher.ProcessBytes(reader.ReadBytes(1024 * 1024)));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {PartitionNumber} RomFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {i} / {romfsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
writer.Write(cipher.DoFinal(reader.ReadBytes(romfsSizeB)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {PartitionNumber} RomFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Partition {PartitionNumber} RomFS: No Data... Skipping...");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the CryptoMethod and BitMasks for the partition
|
||||
/// </summary>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// <param name="header">NCSD header for the 3DS file</param>
|
||||
/// <param name="encrypt">True if we're writing encrypted values, false otherwise</param>
|
||||
private void UpdateCryptoAndMasks(BinaryWriter writer, NCSDHeader header, bool encrypt)
|
||||
{
|
||||
// Write the new CryptoMethod
|
||||
writer.BaseStream.Seek((Entry.Offset * header.MediaUnitSize) + 0x18B, SeekOrigin.Begin);
|
||||
if (encrypt)
|
||||
{
|
||||
// For partitions 1 and up, set crypto-method to 0x00
|
||||
if (PartitionNumber > 0)
|
||||
writer.Write((byte)CryptoMethod.Original);
|
||||
|
||||
// If partition 0, restore crypto-method from backup flags
|
||||
else
|
||||
writer.Write((byte)header.BackupHeader.Flags.CryptoMethod);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write((byte)CryptoMethod.Original);
|
||||
}
|
||||
writer.Flush();
|
||||
|
||||
// Write the new BitMasks flag
|
||||
writer.BaseStream.Seek((Entry.Offset * header.MediaUnitSize) + 0x18F, SeekOrigin.Begin);
|
||||
BitMasks flag = Flags.BitMasks;
|
||||
if (encrypt)
|
||||
{
|
||||
flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF;
|
||||
flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & header.BackupHeader.Flags.BitMasks;
|
||||
}
|
||||
else
|
||||
{
|
||||
flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
|
||||
flag |= BitMasks.NoCrypto;
|
||||
}
|
||||
|
||||
writer.Write((byte)flag);
|
||||
writer.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,55 +322,5 @@ namespace NDecrypt.Headers
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process all partitions in the partition table
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// <param name="development">True if development keys should be used, false otherwise</param>
|
||||
/// <param name="force">True if we want to force the operation, false otherwise</param>
|
||||
public void ProcessAllPartitions(BinaryReader reader, BinaryWriter writer, bool encrypt, bool development, bool force)
|
||||
{
|
||||
// Iterate over all 8 NCCH partitions
|
||||
for (int p = 0; p < 8; p++)
|
||||
{
|
||||
NCCHHeader partitionHeader = GetPartitionHeader(reader, p);
|
||||
if (partitionHeader == null)
|
||||
continue;
|
||||
|
||||
partitionHeader.ProcessPartition(reader, writer, this, encrypt, development, force);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific partition header from the partition table
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="partitionNumber">Partition number to attempt to retrieve</param>
|
||||
/// <returns>NCCH header for the partition requested, null on error</returns>
|
||||
public NCCHHeader GetPartitionHeader(BinaryReader reader, int partitionNumber)
|
||||
{
|
||||
if (!PartitionsTable[partitionNumber].IsValid())
|
||||
{
|
||||
Console.WriteLine($"Partition {partitionNumber} Not found... Skipping...");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Seek to the beginning of the NCCH partition
|
||||
reader.BaseStream.Seek((PartitionsTable[partitionNumber].Offset * MediaUnitSize), SeekOrigin.Begin);
|
||||
|
||||
NCCHHeader partitionHeader = NCCHHeader.Read(reader, true);
|
||||
if (partitionHeader == null)
|
||||
{
|
||||
Console.WriteLine($"Partition {partitionNumber} Unable to read NCCH header");
|
||||
return null;
|
||||
}
|
||||
|
||||
partitionHeader.PartitionNumber = partitionNumber;
|
||||
partitionHeader.Entry = PartitionsTable[partitionNumber];
|
||||
return partitionHeader;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,303 +584,5 @@ namespace NDecrypt.Headers
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process secure area in the DS/DSi file
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// <param name="force">True if we want to force the operation, false otherwise</param>
|
||||
public void ProcessSecureArea(BinaryReader reader, BinaryWriter writer, bool encrypt, bool force)
|
||||
{
|
||||
// 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 (encrypt ^ isDecrypted.Value)
|
||||
{
|
||||
Console.WriteLine("File is already " + (encrypt ? "encrypted" : "decrypted"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessARM9(reader, writer, encrypt);
|
||||
|
||||
Console.WriteLine("File has been " + (encrypt ? "encrypted" : "decrypted"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the current file is already decrypted or not (or has an empty secure area)
|
||||
/// </summary>
|
||||
/// <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();
|
||||
|
||||
// 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))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// Properly encrypted prototype value
|
||||
else if (firstValue == 0xA71329EE && secondValue == 0x2A1D4C38)
|
||||
{
|
||||
Console.WriteLine("Encrypted secure area for prototype found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Standard decryption values
|
||||
return (firstValue == 0xE7FFDEFF && secondValue == 0xE7FFDEFF);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the secure ARM9 region of the file, if possible
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
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.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">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];
|
||||
}
|
||||
|
||||
/// <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];
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{2E30006A-3C60-4576-A262-937B21C83C06}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>NDecrypt</RootNamespace>
|
||||
<AssemblyName>NDecrypt</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject />
|
||||
<TargetFrameworks>net48;netcoreapp3.1;net5.0</TargetFrameworks>
|
||||
<RuntimeIdentifiers>win10-x64;win7-x86</RuntimeIdentifiers>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="BouncyCastle.Crypto, Version=1.8.4.0, Culture=neutral, PublicKeyToken=0e99375e54769942">
|
||||
<HintPath>..\packages\BouncyCastle.1.8.4\lib\BouncyCastle.Crypto.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<PackageReference Include="BouncyCastle.NetCore" Version="1.8.8" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Data\Constants.cs" />
|
||||
<Compile Include="DSTool.cs" />
|
||||
<Compile Include="Headers\NCCHHeaderFlags.cs" />
|
||||
<Compile Include="Headers\NDSHeader.cs" />
|
||||
<Compile Include="Headers\RomFSHeader.cs" />
|
||||
<Compile Include="Helper.cs" />
|
||||
<Compile Include="ITool.cs" />
|
||||
<Compile Include="ThreeDSTool.cs" />
|
||||
<Compile Include="Data\Enums.cs" />
|
||||
<Compile Include="Headers\AccessControlInfo.cs" />
|
||||
<Compile Include="Headers\ARM11KernelCapabilities.cs" />
|
||||
<Compile Include="Headers\ARM11LocalSystemCapabilities.cs" />
|
||||
<Compile Include="Headers\ARM9AccessControl.cs" />
|
||||
<Compile Include="Headers\CodeSetInfo.cs" />
|
||||
<Compile Include="Headers\CXIExtendedHeader.cs" />
|
||||
<Compile Include="Headers\ExeFSFileHeader.cs" />
|
||||
<Compile Include="Headers\ExeFSHeader.cs" />
|
||||
<Compile Include="Headers\NCCHHeader.cs" />
|
||||
<Compile Include="Headers\NCSDHeader.cs" />
|
||||
<Compile Include="Headers\PartitionTableEntry.cs" />
|
||||
<Compile Include="Headers\StorageInfo.cs" />
|
||||
<Compile Include="Headers\SystemControlInfo.cs" />
|
||||
<Compile Include="Headers\SystemInfo.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("3DSDecrypt")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("3DSDecrypt")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2018")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("2e30006a-3c60-4576-a262-937b21c83c06")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using NDecrypt.Data;
|
||||
using NDecrypt.Headers;
|
||||
|
||||
namespace NDecrypt
|
||||
@@ -34,6 +37,8 @@ namespace NDecrypt
|
||||
this.force = force;
|
||||
}
|
||||
|
||||
#region Common Methods
|
||||
|
||||
/// <summary>
|
||||
/// Process an input file given the input values
|
||||
/// </summary>
|
||||
@@ -56,10 +61,524 @@ namespace NDecrypt
|
||||
}
|
||||
|
||||
// Process all 8 NCCH partitions
|
||||
header.ProcessAllPartitions(reader, writer, encrypt, development, force);
|
||||
ProcessAllPartitions(header, reader, writer);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process all partitions in the partition table of an NCSD header
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessAllPartitions(NCSDHeader ncsdHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// Iterate over all 8 NCCH partitions
|
||||
for (int p = 0; p < 8; p++)
|
||||
{
|
||||
NCCHHeader ncchHeader = GetPartitionHeader(ncsdHeader, reader, p);
|
||||
if (ncchHeader == null)
|
||||
continue;
|
||||
|
||||
ProcessPartition(ncsdHeader, ncchHeader, reader, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific partition header from the partition table
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="partitionNumber">Partition number to attempt to retrieve</param>
|
||||
/// <returns>NCCH header for the partition requested, null on error</returns>
|
||||
private NCCHHeader GetPartitionHeader(NCSDHeader ncsdHeader, BinaryReader reader, int partitionNumber)
|
||||
{
|
||||
if (!ncsdHeader.PartitionsTable[partitionNumber].IsValid())
|
||||
{
|
||||
Console.WriteLine($"Partition {partitionNumber} Not found... Skipping...");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Seek to the beginning of the NCCH partition
|
||||
reader.BaseStream.Seek((ncsdHeader.PartitionsTable[partitionNumber].Offset * ncsdHeader.MediaUnitSize), SeekOrigin.Begin);
|
||||
|
||||
NCCHHeader partitionHeader = NCCHHeader.Read(reader, true);
|
||||
if (partitionHeader == null)
|
||||
{
|
||||
Console.WriteLine($"Partition {partitionNumber} Unable to read NCCH header");
|
||||
return null;
|
||||
}
|
||||
|
||||
partitionHeader.PartitionNumber = partitionNumber;
|
||||
partitionHeader.Entry = ncsdHeader.PartitionsTable[partitionNumber];
|
||||
return partitionHeader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a single partition
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessPartition(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If we're forcing the operation, tell the user
|
||||
if (force)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} is not verified due to force flag being set.");
|
||||
}
|
||||
// If we're not forcing the operation, check if the 'NoCrypto' bit is set
|
||||
else if (ncchHeader.Flags.PossblyDecrypted ^ encrypt)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber}: Already " + (encrypt ? "Encrypted" : "Decrypted") + "?...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the Keys to be used
|
||||
SetEncryptionKeys(ncsdHeader, ncchHeader);
|
||||
|
||||
// Process the extended header
|
||||
ProcessExtendedHeader(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// If we're encrypting, encrypt the filesystems and update the flags
|
||||
if (encrypt)
|
||||
{
|
||||
EncryptExeFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
EncryptRomFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
UpdateEncryptCryptoAndMasks(ncsdHeader, ncchHeader, writer);
|
||||
}
|
||||
|
||||
// If we're decrypting, decrypt the filesystems and update the flags
|
||||
else
|
||||
{
|
||||
DecryptExeFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
DecryptRomFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
UpdateDecryptCryptoAndMasks(ncsdHeader, ncchHeader, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine the set of keys to be used for encryption or decryption
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
private void SetEncryptionKeys(NCSDHeader ncsdHeader, NCCHHeader ncchHeader)
|
||||
{
|
||||
ncchHeader.KeyX = 0;
|
||||
ncchHeader.KeyX2C = development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C;
|
||||
|
||||
// Backup headers can't have a KeyY value set
|
||||
if (ncchHeader.RSA2048Signature != null)
|
||||
ncchHeader.KeyY = new BigInteger(ncchHeader.RSA2048Signature.Take(16).Reverse().ToArray());
|
||||
else
|
||||
ncchHeader.KeyY = new BigInteger(0);
|
||||
|
||||
ncchHeader.NormalKey = 0;
|
||||
ncchHeader.NormalKey2C = Helper.RotateLeft((Helper.RotateLeft(ncchHeader.KeyX2C, 2, 128) ^ ncchHeader.KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
|
||||
// Set the header to use based on mode
|
||||
BitMasks masks;
|
||||
CryptoMethod method;
|
||||
if (encrypt)
|
||||
{
|
||||
masks = ncsdHeader.BackupHeader.Flags.BitMasks;
|
||||
method = ncsdHeader.BackupHeader.Flags.CryptoMethod;
|
||||
}
|
||||
else
|
||||
{
|
||||
masks = ncchHeader.Flags.BitMasks;
|
||||
method = ncchHeader.Flags.CryptoMethod;
|
||||
}
|
||||
|
||||
if (masks.HasFlag(BitMasks.FixedCryptoKey))
|
||||
{
|
||||
ncchHeader.NormalKey = 0x00;
|
||||
ncchHeader.NormalKey2C = 0x00;
|
||||
Console.WriteLine("Encryption Method: Zero Key");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (method == CryptoMethod.Original)
|
||||
{
|
||||
ncchHeader.KeyX = development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C;
|
||||
Console.WriteLine("Encryption Method: Key 0x2C");
|
||||
}
|
||||
else if (method == CryptoMethod.Seven)
|
||||
{
|
||||
ncchHeader.KeyX = development ? Constants.DevKeyX0x25 : Constants.KeyX0x25;
|
||||
Console.WriteLine("Encryption Method: Key 0x25");
|
||||
}
|
||||
else if (method == CryptoMethod.NineThree)
|
||||
{
|
||||
ncchHeader.KeyX = development ? Constants.DevKeyX0x18 : Constants.KeyX0x18;
|
||||
Console.WriteLine("Encryption Method: Key 0x18");
|
||||
}
|
||||
else if (method == CryptoMethod.NineSix)
|
||||
{
|
||||
ncchHeader.KeyX = development ? Constants.DevKeyX0x1B : Constants.KeyX0x1B;
|
||||
Console.WriteLine("Encryption Method: Key 0x1B");
|
||||
}
|
||||
|
||||
ncchHeader.NormalKey = Helper.RotateLeft((Helper.RotateLeft(ncchHeader.KeyX, 2, 128) ^ ncchHeader.KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the extended header, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private bool ProcessExtendedHeader(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
if (ncchHeader.ExtendedHeaderSizeInBytes > 0)
|
||||
{
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x200, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x200, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + ": ExHeader");
|
||||
|
||||
var cipher = Helper.CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.PlainIV, encrypt);
|
||||
writer.Write(cipher.ProcessBytes(reader.ReadBytes(Constants.CXTExtendedDataHeaderLength)));
|
||||
writer.Flush();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Extended Header... Skipping...");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the extended header, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessExeFSFileEntries(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
ExeFSHeader exefsHeader = ExeFSHeader.Read(reader);
|
||||
|
||||
// If the header failed to read, log and return
|
||||
if (exefsHeader == null)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS header could not be read. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders)
|
||||
{
|
||||
// Only decrypt a file if it's a code binary
|
||||
if (!fileHeader.IsCodeBinary)
|
||||
continue;
|
||||
|
||||
uint datalenM = ((fileHeader.FileSize) / (1024 * 1024));
|
||||
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
|
||||
uint ctroffset = ((fileHeader.FileOffset + ncsdHeader.MediaUnitSize) / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffsetForHeader = Helper.AddToByteArray(ncchHeader.ExeFSIV, (int)ctroffset);
|
||||
|
||||
var firstCipher = Helper.CreateAESCipher(ncchHeader.NormalKey, exefsIVWithOffsetForHeader, encrypt);
|
||||
var secondCipher = Helper.CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffsetForHeader, !encrypt);
|
||||
|
||||
reader.BaseStream.Seek((((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * ncsdHeader.MediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * ncsdHeader.MediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
if (datalenM > 0)
|
||||
{
|
||||
for (int i = 0; i < datalenM; i++)
|
||||
{
|
||||
writer.Write(secondCipher.ProcessBytes(firstCipher.ProcessBytes(reader.ReadBytes(1024 * 1024))));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {i} / {datalenM + 1} mb...");
|
||||
}
|
||||
}
|
||||
|
||||
if (datalenB > 0)
|
||||
{
|
||||
writer.Write(secondCipher.DoFinal(firstCipher.DoFinal(reader.ReadBytes((int)datalenB))));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the ExeFS Filename Table
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessExeFSFilenameTable(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table");
|
||||
|
||||
var exeFSFilenameTable = Helper.CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.ExeFSIV, encrypt);
|
||||
writer.Write(exeFSFilenameTable.ProcessBytes(reader.ReadBytes((int)ncsdHeader.MediaUnitSize)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
int exefsSizeM = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * ncsdHeader.MediaUnitSize) / (1024 * 1024));
|
||||
int exefsSizeB = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * ncsdHeader.MediaUnitSize) % (1024 * 1024));
|
||||
int ctroffsetE = (int)(ncsdHeader.MediaUnitSize / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffset = Helper.AddToByteArray(ncchHeader.ExeFSIV, ctroffsetE);
|
||||
|
||||
var exeFS = Helper.CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffset, encrypt);
|
||||
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
if (exefsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < exefsSizeM; i++)
|
||||
{
|
||||
writer.Write(exeFS.ProcessBytes(reader.ReadBytes(1024 * 1024)));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (exefsSizeB > 0)
|
||||
{
|
||||
writer.Write(exeFS.DoFinal(reader.ReadBytes(exefsSizeB)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Decrypt
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void DecryptExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If the ExeFS size is 0, we log and return
|
||||
if (ncchHeader.ExeFSSizeInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrypt the filename table
|
||||
ProcessExeFSFilenameTable(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
if (ncchHeader.Flags.CryptoMethod != CryptoMethod.Original)
|
||||
ProcessExeFSFileEntries(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// Decrypt the rest of the ExeFS
|
||||
ProcessExeFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the RomFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// TODO: See how much can be extracted into a common method with Encrypt
|
||||
private void DecryptRomFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If the RomFS offset is 0, we log and return
|
||||
if (ncchHeader.RomFSOffsetInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} RomFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) / (1024 * 1024));
|
||||
int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) % (1024 * 1024));
|
||||
|
||||
var cipher = Helper.CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV, encrypt);
|
||||
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
if (romfsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < romfsSizeM; i++)
|
||||
{
|
||||
writer.Write(cipher.ProcessBytes(reader.ReadBytes(1024 * 1024)));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Decrypting: {i} / {romfsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
writer.Write(cipher.DoFinal(reader.ReadBytes(romfsSizeB)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Decrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the CryptoMethod and BitMasks for the decrypted partition
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void UpdateDecryptCryptoAndMasks(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryWriter writer)
|
||||
{
|
||||
// Write the new CryptoMethod
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18B, SeekOrigin.Begin);
|
||||
writer.Write((byte)CryptoMethod.Original);
|
||||
writer.Flush();
|
||||
|
||||
// Write the new BitMasks flag
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18F, SeekOrigin.Begin);
|
||||
BitMasks flag = ncchHeader.Flags.BitMasks;
|
||||
flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
|
||||
flag |= BitMasks.NoCrypto;
|
||||
writer.Write((byte)flag);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Encrypt
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void EncryptExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If the ExeFS size is 0, we log and return
|
||||
if (ncchHeader.ExeFSSizeInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
if (ncsdHeader.BackupHeader.Flags.CryptoMethod != CryptoMethod.Original)
|
||||
ProcessExeFSFileEntries(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// Encrypt the filename table
|
||||
ProcessExeFSFilenameTable(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// Encrypt the rest of the ExeFS
|
||||
ProcessExeFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the RomFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// TODO: See how much can be extracted into a common method with Decrypt
|
||||
private void EncryptRomFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If the RomFS offset is 0, we log and return
|
||||
if (ncchHeader.RomFSOffsetInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} RomFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) / (1024 * 1024));
|
||||
int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) % (1024 * 1024));
|
||||
|
||||
// Encrypting RomFS for partitions 1 and up always use Key0x2C
|
||||
if (ncchHeader.PartitionNumber > 0)
|
||||
{
|
||||
if (ncsdHeader.BackupHeader.Flags?.BitMasks.HasFlag(BitMasks.FixedCryptoKey) == true) // except if using zero-key
|
||||
{
|
||||
ncchHeader.NormalKey = 0x00;
|
||||
}
|
||||
else
|
||||
{
|
||||
ncchHeader.KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
ncchHeader.NormalKey = Helper.RotateLeft((Helper.RotateLeft(ncchHeader.KeyX, 2, 128) ^ ncchHeader.KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
}
|
||||
|
||||
var cipher = Helper.CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV, encrypt);
|
||||
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
if (romfsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < romfsSizeM; i++)
|
||||
{
|
||||
writer.Write(cipher.ProcessBytes(reader.ReadBytes(1024 * 1024)));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Encrypting: {i} / {romfsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
writer.Write(cipher.DoFinal(reader.ReadBytes(romfsSizeB)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Encrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the CryptoMethod and BitMasks for the encrypted partition
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void UpdateEncryptCryptoAndMasks(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryWriter writer)
|
||||
{
|
||||
// Write the new CryptoMethod
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18B, SeekOrigin.Begin);
|
||||
|
||||
// For partitions 1 and up, set crypto-method to 0x00
|
||||
if (ncchHeader.PartitionNumber > 0)
|
||||
writer.Write((byte)CryptoMethod.Original);
|
||||
|
||||
// If partition 0, restore crypto-method from backup flags
|
||||
else
|
||||
writer.Write((byte)ncsdHeader.BackupHeader.Flags.CryptoMethod);
|
||||
|
||||
writer.Flush();
|
||||
|
||||
// Write the new BitMasks flag
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18F, SeekOrigin.Begin);
|
||||
BitMasks flag = ncchHeader.Flags.BitMasks;
|
||||
flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF;
|
||||
flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & ncsdHeader.BackupHeader.Flags.BitMasks;
|
||||
writer.Write((byte)flag);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="BouncyCastle" version="1.8.4" targetFramework="net461" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user