mirror of
https://github.com/SabreTools/NDecrypt.git
synced 2026-02-06 05:35:23 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7415a21d5c | ||
|
|
f349389994 | ||
|
|
a046bd4152 | ||
|
|
51a8f0c9df | ||
|
|
0449c16a01 | ||
|
|
7638d7dbf8 | ||
|
|
387bf46e5a | ||
|
|
6d257dc1e3 | ||
|
|
91d816e359 | ||
|
|
e90f7a76af | ||
|
|
9750ae5a1e | ||
|
|
fa518ed5a5 | ||
|
|
350acd7be4 | ||
|
|
dde0c96e6c | ||
|
|
36e6e803cc | ||
|
|
e669936e08 | ||
|
|
dc55511a84 | ||
|
|
b9b8c76e84 | ||
|
|
10fcd51e10 | ||
|
|
a0878d0bf4 | ||
|
|
ed55f76d1e | ||
|
|
a726fc26d9 | ||
|
|
414df7808e | ||
|
|
d61e03687b |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -258,4 +258,7 @@ paket-files/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyc
|
||||
|
||||
# VSCode
|
||||
/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}"
|
||||
}
|
||||
]
|
||||
}
|
||||
24
.vscode/tasks.json
vendored
Normal file
24
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "shell",
|
||||
"args": [
|
||||
"build",
|
||||
// Ask dotnet build to generate full paths for file names.
|
||||
"/property:GenerateFullPaths=true",
|
||||
// Do not generate summary otherwise it leads to duplicate errors in Problems panel
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
NDecrypt.sln
15
NDecrypt.sln
@@ -3,7 +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}"
|
||||
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", "NDecrypt\NDecrypt.csproj", "{91C54370-5741-4742-B2E9-EC498551AD1C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -11,10 +16,10 @@ 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
|
||||
{91C54370-5741-4742-B2E9-EC498551AD1C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using NDecrypt.Headers;
|
||||
using System.Linq;
|
||||
using NDecrypt.NDS;
|
||||
using NDecrypt.NDS.Headers;
|
||||
|
||||
namespace NDecrypt
|
||||
{
|
||||
@@ -16,10 +18,23 @@ namespace NDecrypt
|
||||
/// </summary>
|
||||
private readonly bool encrypt;
|
||||
|
||||
public DSTool(string filename, 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>
|
||||
@@ -32,22 +47,329 @@ namespace NDecrypt
|
||||
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)))
|
||||
try
|
||||
{
|
||||
NDSHeader header = NDSHeader.Read(reader);
|
||||
if (header == null)
|
||||
// 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)))
|
||||
{
|
||||
Console.WriteLine("Error: Not a DS or DSi Rom!");
|
||||
return false;
|
||||
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);
|
||||
}
|
||||
|
||||
// Process the secure area
|
||||
header.ProcessSecureArea(reader, writer, encrypt);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine($"An error has occurred. {filename} may be corrupted if it was partially processed.");
|
||||
Console.WriteLine("Please check that the file was a valid DS or DSi file and try again.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
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)
|
||||
|| (firstValue == 0xA71329EE && secondValue == 0x2A1D4C38)
|
||||
|| (firstValue == 0xC44DCC48 && secondValue == 0x38B6F8CB)
|
||||
|| (firstValue == 0x3A9323B5 && secondValue == 0xC0387241))
|
||||
{
|
||||
Console.WriteLine("Improperly decrypted empty secure area found. Should be encrypted to get proper value.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Improperly encrypted empty secure area (encrypt empty with woodsec)
|
||||
else if ((firstValue == 0x4BCE88BE && secondValue == 0xD3662DD1)
|
||||
|| (firstValue == 0x2543C534 && secondValue == 0xCC4BE38E))
|
||||
{
|
||||
Console.WriteLine("Improperly encrypted empty secure area found. Should be decrypted to get proper value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Properly decrypted nonstandard value (mastering issue)
|
||||
else if ((firstValue == 0xD0D48B67 && secondValue == 0x39392F23) // Dragon Quest 5 (EU)
|
||||
|| (firstValue == 0x014A191A && secondValue == 0xA5C470B9) // Dragon Quest 5 (USA)
|
||||
|| (firstValue == 0x7829BC8D && secondValue == 0x9968EF44) // Dragon Quest 5 (JP)
|
||||
|| (firstValue == 0xC4A15AB8 && secondValue == 0xD2E667C8) // Prince of Persia (EU)
|
||||
|| (firstValue == 0xD5E97D20 && secondValue == 0x21B2A159)) // Prince of Persia (USA)
|
||||
{
|
||||
Console.WriteLine("Decrypted secure area for known, nonstandard value found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Properly decrypted prototype value
|
||||
else if (firstValue == 0xBA35F813 && secondValue == 0xB691AAE8)
|
||||
{
|
||||
Console.WriteLine("Decrypted secure area for prototype found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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,601 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using NDecrypt.Data;
|
||||
|
||||
namespace NDecrypt.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>
|
||||
private BigInteger KeyX;
|
||||
|
||||
/// <summary>
|
||||
/// NCCH boot rom key
|
||||
/// </summary>
|
||||
private BigInteger KeyX2C;
|
||||
|
||||
/// <summary>
|
||||
/// Kernel9/Process9 key
|
||||
/// </summary>
|
||||
private BigInteger KeyY;
|
||||
|
||||
/// <summary>
|
||||
/// Normal AES key
|
||||
/// </summary>
|
||||
private BigInteger NormalKey;
|
||||
|
||||
/// <summary>
|
||||
/// NCCH AES key
|
||||
/// </summary>
|
||||
private BigInteger NormalKey2C;
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public void ProcessPartition(BinaryReader reader, BinaryWriter writer, NCSDHeader header, bool encrypt, bool development)
|
||||
{
|
||||
// Check if the 'NoCrypto' bit is set
|
||||
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, encrypt);
|
||||
ProcessRomFS(reader, writer, header.MediaUnitSize, header.BackupHeader.Flags, encrypt, development);
|
||||
|
||||
// Write out new CryptoMethod and BitMask flags
|
||||
UpdateCryptoAndMasks(reader, 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 = 0;
|
||||
CryptoMethod method = 0;
|
||||
if (encrypt)
|
||||
{
|
||||
masks = backupFlags.BitMasks;
|
||||
method = backupFlags.CryptoMethod;
|
||||
}
|
||||
else
|
||||
{
|
||||
masks = Flags.BitMasks;
|
||||
method = Flags.CryptoMethod;
|
||||
}
|
||||
|
||||
if ((masks & BitMasks.FixedCryptoKey) != 0)
|
||||
{
|
||||
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.KeyX0x25 : 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="encrypt">True if we want to encrypt the extended header, false otherwise</param>
|
||||
private void ProcessExeFS(BinaryReader reader, BinaryWriter writer, uint mediaUnitSize, 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 (Flags.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)((ExeFSSizeInMediaUnits - 1) * mediaUnitSize) / (1024 * 1024);
|
||||
int exefsSizeB = (int)((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)
|
||||
{
|
||||
int romfsSizeM = (int)(RomFSSizeInMediaUnits * mediaUnitSize) / (1024 * 1024);
|
||||
int romfsSizeB = (int)(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 = KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
NormalKey = Helper.RotateLeft((Helper.RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
|
||||
if ((backupFlags.BitMasks & BitMasks.FixedCryptoKey) != 0) // except if using zero-key
|
||||
{
|
||||
NormalKey = 0x00;
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyX = 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: Decrypting: {i} / {romfsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
writer.Write(cipher.DoFinal(reader.ReadBytes(romfsSizeB)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {PartitionNumber} RomFS: 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="reader">BinaryReader representing the input stream</param>
|
||||
/// <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(BinaryReader reader, 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 = (flag & ((BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF));
|
||||
flag = (flag | (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & header.BackupHeader.Flags.BitMasks);
|
||||
}
|
||||
else
|
||||
{
|
||||
flag = flag & (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
|
||||
flag = (flag | BitMasks.NoCrypto);
|
||||
}
|
||||
|
||||
writer.Write((byte)flag);
|
||||
writer.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
105
NDecrypt/N3DS/Constants.cs
Normal file
105
NDecrypt/N3DS/Constants.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace NDecrypt.N3DS
|
||||
{
|
||||
public class Constants
|
||||
{
|
||||
// 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;
|
||||
|
||||
/// <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>
|
||||
public static void Init()
|
||||
{
|
||||
// If we're already attempted to set the constants, don't try to again
|
||||
if (IsReady != null)
|
||||
return;
|
||||
|
||||
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,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace NDecrypt.Data
|
||||
namespace NDecrypt.N3DS
|
||||
{
|
||||
[Flags]
|
||||
public enum ARM9AccessControlDescriptors : byte
|
||||
@@ -165,13 +165,6 @@ namespace NDecrypt.Data
|
||||
MediaCardDevice2X = 0x07,
|
||||
}
|
||||
|
||||
public enum NDSUnitcode : byte
|
||||
{
|
||||
NDS = 0x00,
|
||||
NDSPlusDSi = 0x02,
|
||||
DSi = 0x03,
|
||||
}
|
||||
|
||||
public enum ResourceLimitCategory
|
||||
{
|
||||
APPLICATION = 0,
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
public class ARM11KernelCapabilities
|
||||
{
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.IO;
|
||||
using NDecrypt.Data;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
public class ARM11LocalSystemCapabilities
|
||||
{
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.IO;
|
||||
using NDecrypt.Data;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
public class ARM9AccessControl
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
public class AccessControlInfo
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
public class CXIExtendedHeader
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
public class CodeSetInfo
|
||||
{
|
||||
@@ -2,11 +2,11 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
public class ExeFSFileHeader
|
||||
{
|
||||
private const string codeSegment = ".code\0\0\0";
|
||||
// .code\0\0\0
|
||||
private readonly byte[] codeSegmentBytes = new byte[] { 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x00, 0x00, 0x00 };
|
||||
|
||||
/// <summary>
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
public class ExeFSHeader
|
||||
{
|
||||
248
NDecrypt/N3DS/Headers/NCCHHeader.cs
Normal file
248
NDecrypt/N3DS/Headers/NCCHHeader.cs
Normal file
@@ -0,0 +1,248 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace NDecrypt.N3DS.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,8 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using NDecrypt.Data;
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
public class NCCHHeaderFlags
|
||||
{
|
||||
@@ -52,7 +50,7 @@ namespace NDecrypt.Headers
|
||||
/// <summary>
|
||||
/// Get if the NoCrypto bit is set
|
||||
/// </summary>
|
||||
public bool PossblyDecrypted { get { return (BitMasks & BitMasks.NoCrypto) != 0; } }
|
||||
public bool PossblyDecrypted { get { return BitMasks.HasFlag(BitMasks.NoCrypto); } }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an NCCH header flags, if possible
|
||||
@@ -1,8 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using NDecrypt.Data;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
public class NCSDHeader
|
||||
{
|
||||
@@ -322,54 +321,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>
|
||||
public void ProcessAllPartitions(BinaryReader reader, BinaryWriter writer, bool encrypt, bool development)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
public class PartitionTableEntry
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
// https://www.3dbrew.org/wiki/RomFS
|
||||
public class RomFSHeader
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.IO;
|
||||
using NDecrypt.Data;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
public class StorageInfo
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
public class SystemControlInfo
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
public class SystemInfo
|
||||
{
|
||||
@@ -1,14 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace NDecrypt.Data
|
||||
namespace NDecrypt.NDS
|
||||
{
|
||||
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,
|
||||
@@ -274,102 +267,11 @@ namespace NDecrypt.Data
|
||||
0x96,0xE7,0xC4,0x18,0x5F,0xAD,0xF5,0x19,
|
||||
};
|
||||
|
||||
// ARM9 decryption check values
|
||||
#region 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; }
|
||||
|
||||
// Dev 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;
|
||||
|
||||
public static bool IsReady { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Setup all of the necessary constants
|
||||
/// </summary>
|
||||
/// <remarks>keys.bin should be in little endian format</remarks>
|
||||
static Constants()
|
||||
{
|
||||
string keyfile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "keys.bin");
|
||||
if (!File.Exists(keyfile))
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (BinaryReader reader = new BinaryReader(File.Open(keyfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
|
||||
{
|
||||
// This is required to preserve sign for BigInteger
|
||||
byte[] signByte = new byte[] { 0x00 };
|
||||
|
||||
// Hardware constant
|
||||
AESHardwareConstant = new BigInteger(reader.ReadBytes(16));
|
||||
|
||||
// Retail keys
|
||||
KeyX0x18 = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
KeyX0x1B = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
KeyX0x25 = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
KeyX0x2C = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
|
||||
// Development keys
|
||||
DevKeyX0x18 = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
DevKeyX0x1B = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
DevKeyX0x25 = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
DevKeyX0x2C = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
IsReady = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
NDecrypt/NDS/Enums.cs
Normal file
11
NDecrypt/NDS/Enums.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace NDecrypt.NDS
|
||||
{
|
||||
public enum NDSUnitcode : byte
|
||||
{
|
||||
NDS = 0x00,
|
||||
NDSPlusDSi = 0x02,
|
||||
DSi = 0x03,
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,10 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NDecrypt.Data;
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Headers
|
||||
namespace NDecrypt.NDS.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
|
||||
#region Common
|
||||
|
||||
/// <summary>
|
||||
/// Game Title
|
||||
@@ -584,267 +574,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>
|
||||
public void ProcessSecureArea(BinaryReader reader, BinaryWriter writer, bool encrypt)
|
||||
{
|
||||
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)
|
||||
return null;
|
||||
|
||||
// Improperly decrypted empty secure area (decrypt empty with woodsec)
|
||||
else if ((firstValue == 0xE386C397 && secondValue == 0x82775B7E)
|
||||
|| (firstValue == 0xF98415B8 && secondValue == 0x698068FC))
|
||||
return true;
|
||||
|
||||
// Improperly encrypted empty secure area (encrypt empty with woodsec)
|
||||
else if ((firstValue == 0x4BCE88BE && secondValue == 0xD3662DD1)
|
||||
|| (firstValue == 0x2543C534 && secondValue == 0xCC4BE38E))
|
||||
return false;
|
||||
|
||||
// Properly decrypted nonstandard value (mastering issue)
|
||||
else if (firstValue == 0xD0D48B67 && secondValue == 0x39392F23)
|
||||
return true;
|
||||
|
||||
// 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.6.1</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,6 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using NDecrypt.Data;
|
||||
|
||||
namespace NDecrypt
|
||||
{
|
||||
@@ -14,7 +13,7 @@ namespace NDecrypt
|
||||
return;
|
||||
}
|
||||
|
||||
bool? encrypt = null;
|
||||
bool? encrypt;
|
||||
if (args[0] == "decrypt" || args[0] == "d")
|
||||
{
|
||||
encrypt = false;
|
||||
@@ -29,27 +28,25 @@ namespace NDecrypt
|
||||
return;
|
||||
}
|
||||
|
||||
bool development = false;
|
||||
bool development = false, force = false;
|
||||
int start = 1;
|
||||
if (args[1] == "-dev")
|
||||
for ( ; start < args.Length; start++)
|
||||
{
|
||||
development = true;
|
||||
start = 2;
|
||||
}
|
||||
if (args[start] == "-dev" || args[start] == "--development")
|
||||
development = true;
|
||||
|
||||
// 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;
|
||||
else if (args[start] == "-f" || args[start] == "--force")
|
||||
force = true;
|
||||
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = start; i < args.Length; i++)
|
||||
{
|
||||
if (File.Exists(args[i]))
|
||||
{
|
||||
ITool tool = DeriveTool(args[i], encrypt.Value, development);
|
||||
ITool tool = DeriveTool(args[i], encrypt.Value, development, force);
|
||||
if (tool?.ProcessFile() != true)
|
||||
Console.WriteLine("Processing failed!");
|
||||
}
|
||||
@@ -57,15 +54,16 @@ namespace NDecrypt
|
||||
{
|
||||
foreach (string file in Directory.EnumerateFiles(args[i], "*", SearchOption.AllDirectories))
|
||||
{
|
||||
ITool tool = DeriveTool(file, encrypt.Value, development);
|
||||
ITool tool = DeriveTool(file, encrypt.Value, development, force);
|
||||
if (tool?.ProcessFile() != true)
|
||||
Console.WriteLine("Processing failed!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"{args[i]} is not a file or folder. Please check your spelling and formatting and try again.");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Press Enter to Exit...");
|
||||
Console.Read();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -77,7 +75,18 @@ namespace NDecrypt
|
||||
if (!string.IsNullOrWhiteSpace(err))
|
||||
Console.WriteLine($"Error: {err}");
|
||||
|
||||
Console.WriteLine("Usage: NDecrypt.exe (decrypt|encrypt) [-dev] <file|dir> ...");
|
||||
Console.WriteLine(@"Usage: NDecrypt.exe <opeation> [flags] <path> ...
|
||||
|
||||
Possible values for <operation>:
|
||||
e, encrypt - Encrypt the input files
|
||||
d, decrypt - Decrypt the input files
|
||||
|
||||
Possible values for [flags] (one or more can be used):
|
||||
-dev, --development - Enable using development keys, if available
|
||||
-f, --force - Force operation by avoiding sanity checks
|
||||
|
||||
<path> can be any file or folder that contains uncompressed items.
|
||||
More than one path can be specified at a time.");
|
||||
}
|
||||
|
||||
private enum RomType
|
||||
@@ -93,18 +102,19 @@ namespace NDecrypt
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to derive the tool from</param>
|
||||
/// <param name="encrypt">True if we are encrypting the file, false otherwise</param>
|
||||
/// <param name="development">rue if we are using development keys, false otherwise</param>
|
||||
/// <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)
|
||||
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);
|
||||
return new DSTool(filename, encrypt, force);
|
||||
case RomType.N3DS:
|
||||
return new ThreeDSTool(filename, development, encrypt);
|
||||
return new ThreeDSTool(filename, development, encrypt, force);
|
||||
case RomType.NULL:
|
||||
default:
|
||||
Console.WriteLine($"Unrecognized file format for {filename}. Expected *.nds, *.srl, *.dsi, *.3ds");
|
||||
@@ -119,8 +129,9 @@ namespace NDecrypt
|
||||
/// <returns>RomType value, if possible</returns>
|
||||
private static RomType DetermineRomType(string filename)
|
||||
{
|
||||
if (filename.EndsWith(".nds", StringComparison.OrdinalIgnoreCase)
|
||||
|| filename.EndsWith(".srl", StringComparison.OrdinalIgnoreCase))
|
||||
if (filename.EndsWith(".nds", StringComparison.OrdinalIgnoreCase) // Standard carts
|
||||
|| filename.EndsWith(".srl", StringComparison.OrdinalIgnoreCase) // Development carts/images
|
||||
|| filename.EndsWith(".ids", StringComparison.OrdinalIgnoreCase)) // iQue DS Carts
|
||||
return RomType.NDS;
|
||||
|
||||
else if (filename.EndsWith(".dsi", StringComparison.OrdinalIgnoreCase))
|
||||
|
||||
@@ -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,6 +1,9 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using NDecrypt.Headers;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using NDecrypt.N3DS;
|
||||
using NDecrypt.N3DS.Headers;
|
||||
|
||||
namespace NDecrypt
|
||||
{
|
||||
@@ -21,39 +24,578 @@ namespace NDecrypt
|
||||
/// </summary>
|
||||
private readonly bool encrypt;
|
||||
|
||||
public ThreeDSTool(string filename, bool development, 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()
|
||||
{
|
||||
// Ensure the constants are all set
|
||||
Constants.Init();
|
||||
if (Constants.IsReady != true)
|
||||
{
|
||||
Console.WriteLine("Could not read keys from keys.bin. Please make sure the file exists and try again.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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)))
|
||||
try
|
||||
{
|
||||
NCSDHeader header = NCSDHeader.Read(reader, development);
|
||||
if (header == null)
|
||||
// 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)))
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS Rom!");
|
||||
return false;
|
||||
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);
|
||||
}
|
||||
|
||||
// Process all 8 NCCH partitions
|
||||
header.ProcessAllPartitions(reader, writer, encrypt, development);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine($"An error has occurred. {filename} may be corrupted if it was partially processed.");
|
||||
Console.WriteLine("Please check that the file was a valid 3DS or New 3DS file and try again.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
// 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>
|
||||
72
README.md
Normal file
72
README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# NDecrypt
|
||||
|
||||
A simple tool for simple people.
|
||||
|
||||
## What is this?
|
||||
|
||||
This is a code port of 3 different programs:
|
||||
|
||||
- `3ds_encrypt.py`
|
||||
- `3ds_decrypt.py`
|
||||
- `woodsec` (part of [wooddumper](https://github.com/TuxSH/wooddumper))
|
||||
|
||||
## No really, what is this?
|
||||
|
||||
This tool allows you to encrypt and decrypt your personally dumped NDS and N3DS roms with minimal hassle. The only caveat right now is that you need a `keys.bin` file for your personally obtained encryption keys.
|
||||
|
||||
## So how do I use this?
|
||||
|
||||
NDecrypt.exe <operation> [flags] <path> ...
|
||||
|
||||
Possible values for <operation>:
|
||||
e, encrypt - Encrypt the input files
|
||||
d, decrypt - Decrypt the input files
|
||||
|
||||
Possible values for [flags] (one or more can be used):
|
||||
-dev, --development - Enable using development keys, if available
|
||||
-f, --force - Force operation by avoiding sanity checks
|
||||
|
||||
<path> can be any file or folder that contains uncompressed items.
|
||||
More than one path can be specified at a time.
|
||||
|
||||
|
||||
**Note:** This overwrites the input files, so make backups if you're working on your original, personal dumps.
|
||||
|
||||
**Note:** Mixed folders or inputs are also accepted, you can decrypt or encrypt multiple files, regardless of their type. This being said, you can only do encrypt OR decrypt at one time.
|
||||
|
||||
## I feel like something is missing...
|
||||
|
||||
You are! In fact, you may be asking, "Hey, what was that `keys.bin` you mentioned??". I'm glad you asked. It's used only for Nintendo 3DS and New 3DS files. Since some people don't like reading code, you need the 9 16-bit keys in little endian format (most common extraction methods produce big endian, so keep that in mind). It's recommended that you fill with 0x00 if you don't have access to a particular value so it doesn't mess up the read. They need to be in the following order:
|
||||
|
||||
- Hardware constant
|
||||
- KeyX0x10
|
||||
- KeyX0x1B
|
||||
- KeyX0x25
|
||||
- KeyX0x2C
|
||||
- DevKeyX0x10
|
||||
- DevKeyX0x1B
|
||||
- DevKeyX0x25
|
||||
- DevKeyX0x2C
|
||||
|
||||
The last 4 are only required if you use the `-dev` flag. Once again, don't ask for these, please. If you're missing a required key, then things won't work. Don't blame me, blame society. Or something. And yes, I'll fix this being required across the board at some point.
|
||||
|
||||
## But does it work?
|
||||
|
||||
As much as I'd like to think that this program is entirely without flaws, numbers need to speak for themselves sometimes. Here's a list of the supported sets and their current compatibility percentages with woodsec and the Python scripts (as of 2020-12-16):
|
||||
|
||||
- **Nintendo DS** - >99% compatible (Both encryption and decryption)
|
||||
- **Nintendo DSi** - 100% compatible (Both encryption and decryption)
|
||||
- **Nintendo 3DS** - 100% compatible (Decryption only, encryption is currently being tested)
|
||||
- **Nintendo New 3DS** - 100% compatible (Both encryption and decryption)
|
||||
|
||||
Please note the above numbers are based on the current, documented values, not necessarily what is correct. For example, in the Nintendo DS set, there are issues with prototype and unlicensed cartridges, all of which have strange, potentially hacky values in their secure areas.
|
||||
|
||||
## Anything else?
|
||||
|
||||
I'd like to thank the developers of the original programs for doing the actual hard work to figure things out. I'd also like to thank everyone who helped to test this against the original programs and made code suggestions.
|
||||
|
||||
Unofficially, this is entirely, 100% FOSS, no strings attached. I keep forgetting what license that is.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This program is **ONLY** for use with personally dumped files and keys and is not meant for enabling illegal activity. I do not condone using this program for anything other than personal use and research. If this program is used for anything other than that, I cannot be held liable for anything that happens.
|
||||
Reference in New Issue
Block a user