mirror of
https://github.com/SabreTools/NDecrypt.git
synced 2026-02-06 13:54:47 +00:00
Compare commits
196 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a094b2dd8 | ||
|
|
0e3d22020b | ||
|
|
695f0f44b6 | ||
|
|
dc4bd15e04 | ||
|
|
ce54eb24e2 | ||
|
|
15961e7047 | ||
|
|
edd8ebc048 | ||
|
|
23e9edbf69 | ||
|
|
00ac5e1ca2 | ||
|
|
c79781a6d7 | ||
|
|
5aa50ee252 | ||
|
|
3fbaedd7d5 | ||
|
|
bc31cb0f6a | ||
|
|
3ef32748e9 | ||
|
|
41293ab7c5 | ||
|
|
7a5593dd72 | ||
|
|
326b747204 | ||
|
|
ac9fc92e06 | ||
|
|
8a2d91aace | ||
|
|
add3d28488 | ||
|
|
bd078e4585 | ||
|
|
9f5ffaf035 | ||
|
|
3785426789 | ||
|
|
ee48155c77 | ||
|
|
c67277caa6 | ||
|
|
cced3b5b3d | ||
|
|
4c56377d7e | ||
|
|
201098e32c | ||
|
|
8ec0307e1c | ||
|
|
864cb57d30 | ||
|
|
7576608111 | ||
|
|
e3f740e115 | ||
|
|
4eaa56212f | ||
|
|
6ef543ee1a | ||
|
|
5a4e747329 | ||
|
|
4a35d490d5 | ||
|
|
2983e21330 | ||
|
|
ead4f52f3c | ||
|
|
72afaeb010 | ||
|
|
5148ba5d83 | ||
|
|
ccf746ca7c | ||
|
|
656b8158e9 | ||
|
|
263ca9b6bf | ||
|
|
158fcfff36 | ||
|
|
6e13e9e4c4 | ||
|
|
6d5bc3dbcf | ||
|
|
c73e971704 | ||
|
|
6c575d0157 | ||
|
|
5be07157e8 | ||
|
|
7d069d6bf7 | ||
|
|
46b60559a2 | ||
|
|
849315076c | ||
|
|
f6f3cd062e | ||
|
|
3dac146451 | ||
|
|
af226e347c | ||
|
|
37f27e41cf | ||
|
|
3742a5c29e | ||
|
|
2e7bc64e26 | ||
|
|
af14f829b0 | ||
|
|
93d34c3b9c | ||
|
|
51b690bb6d | ||
|
|
bd1d6935d8 | ||
|
|
08a1dc5e42 | ||
|
|
77407f54fb | ||
|
|
2eb969b6ac | ||
|
|
747625c2b8 | ||
|
|
993ce492b7 | ||
|
|
3b33e3d370 | ||
|
|
36ae14a997 | ||
|
|
4e0a582269 | ||
|
|
9f018ca73b | ||
|
|
3f07271e7f | ||
|
|
84b62e2c52 | ||
|
|
041d5b633e | ||
|
|
54080b3ef1 | ||
|
|
32b430fafa | ||
|
|
450f6fb267 | ||
|
|
96f9715dff | ||
|
|
96ed279db5 | ||
|
|
344d69ca52 | ||
|
|
0aa946378a | ||
|
|
6ec5c69e27 | ||
|
|
45de58d2f1 | ||
|
|
10f1532bb1 | ||
|
|
45f5e1ffff | ||
|
|
9fad439ab8 | ||
|
|
179b53f462 | ||
|
|
c52d3de426 | ||
|
|
36393f7950 | ||
|
|
af8a8eb72d | ||
|
|
5040de04e5 | ||
|
|
78b168e4ce | ||
|
|
2df30d095b | ||
|
|
e2c2c804a5 | ||
|
|
ce13c34b0a | ||
|
|
71199ee94a | ||
|
|
1159b677bc | ||
|
|
6527d85f29 | ||
|
|
ff815299fb | ||
|
|
c04de5700a | ||
|
|
ab9d32f56a | ||
|
|
8151f306ec | ||
|
|
fd9b94d428 | ||
|
|
9cc2b6befe | ||
|
|
8c2d4d701a | ||
|
|
593e8b2b4d | ||
|
|
71f3295a2b | ||
|
|
a29071e697 | ||
|
|
96c3c3732c | ||
|
|
c8f8a7d99c | ||
|
|
3ee8c2e79b | ||
|
|
7d263886da | ||
|
|
6d1b23ffec | ||
|
|
92d1c3c5e5 | ||
|
|
855a353e90 | ||
|
|
6dac45f973 | ||
|
|
a6337e0864 | ||
|
|
778d5d4ffc | ||
|
|
16a84f0dc6 | ||
|
|
7fbe044ab8 | ||
|
|
7c9a035d7c | ||
|
|
964e6f243c | ||
|
|
b454e42f8e | ||
|
|
9f70eae8be | ||
|
|
20987573f4 | ||
|
|
2fb05b577e | ||
|
|
f72b8c3506 | ||
|
|
8b32b21475 | ||
|
|
2779f5ef72 | ||
|
|
bbbf71a603 | ||
|
|
bea979dca5 | ||
|
|
5a0169310b | ||
|
|
67c83bd126 | ||
|
|
6fdcc2effe | ||
|
|
9ff0205e15 | ||
|
|
c91d7f2708 | ||
|
|
d9b333e3ba | ||
|
|
ef5a01edf7 | ||
|
|
012787a5b1 | ||
|
|
df5664fa1f | ||
|
|
bef252f57f | ||
|
|
b78db22464 | ||
|
|
6d980c94fd | ||
|
|
8452cb4fc0 | ||
|
|
0deaa0aed3 | ||
|
|
9b5f90a750 | ||
|
|
7415a21d5c | ||
|
|
f349389994 | ||
|
|
a046bd4152 | ||
|
|
51a8f0c9df | ||
|
|
0449c16a01 | ||
|
|
7638d7dbf8 | ||
|
|
387bf46e5a | ||
|
|
6d257dc1e3 | ||
|
|
91d816e359 | ||
|
|
e90f7a76af | ||
|
|
9750ae5a1e | ||
|
|
fa518ed5a5 | ||
|
|
350acd7be4 | ||
|
|
dde0c96e6c | ||
|
|
36e6e803cc | ||
|
|
e669936e08 | ||
|
|
dc55511a84 | ||
|
|
b9b8c76e84 | ||
|
|
10fcd51e10 | ||
|
|
a0878d0bf4 | ||
|
|
ed55f76d1e | ||
|
|
a726fc26d9 | ||
|
|
414df7808e | ||
|
|
d61e03687b | ||
|
|
cccb4e6261 | ||
|
|
df7ff2b95a | ||
|
|
52a928638b | ||
|
|
5962fc6072 | ||
|
|
5807d4ddc2 | ||
|
|
6fa8867f93 | ||
|
|
ed45d2d14e | ||
|
|
69ecb0d379 | ||
|
|
5974e2371e | ||
|
|
1e5e3badbc | ||
|
|
ef02b1c33a | ||
|
|
32e8bab2a6 | ||
|
|
bd1c6f8b51 | ||
|
|
30da008cec | ||
|
|
aa7566c312 | ||
|
|
56a5f4951b | ||
|
|
4b71cd621f | ||
|
|
4042b1c216 | ||
|
|
26c7a34e98 | ||
|
|
dc68aa5046 | ||
|
|
ff486d8613 | ||
|
|
941f7d5191 | ||
|
|
b83293e8c9 | ||
|
|
0190f3d316 | ||
|
|
5c6b303d41 | ||
|
|
0800d93d22 |
55
.github/workflows/build_program.yml
vendored
Normal file
55
.github/workflows/build_program.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Build Program
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
project: [NDecrypt]
|
||||
runtime: [win-x86, win-x64, win-arm64, linux-x64, linux-arm64, osx-x64]
|
||||
framework: [net9.0] #[net20, net35, net40, net452, net472, net48, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0, net9.0]
|
||||
conf: [Debug] #[Release, Debug]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c ${{ matrix.conf == 'Release' && 'Release -p:DebugType=None -p:DebugSymbols=false' || 'Debug'}} --self-contained true --version-suffix ${{ github.sha }} ${{ (startsWith(matrix.framework, 'net5') || startsWith(matrix.framework, 'net6') || startsWith(matrix.framework, 'net7') || startsWith(matrix.framework, 'net8') || startsWith(matrix.framework, 'net9')) && '-p:PublishSingleFile=true' || ''}}
|
||||
|
||||
- name: Archive build
|
||||
run: |
|
||||
cd ${{ matrix.project }}/bin/${{ matrix.conf }}/${{ matrix.framework }}/${{ matrix.runtime }}/publish/
|
||||
zip -r ${{ github.workspace }}/${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.zip ./
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}
|
||||
path: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.zip
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.zip
|
||||
body: 'Last built commit: ${{ github.sha }}'
|
||||
name: 'Rolling Release'
|
||||
prerelease: True
|
||||
replacesArtifacts: True
|
||||
tag: "rolling"
|
||||
updateOnlyUnreleased: True
|
||||
17
.github/workflows/check_pr.yml
vendored
Normal file
17
.github/workflows/check_pr.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: Build PR
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
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/net9.0/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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27004.2006
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "3DSDecrypt", "3DSDecrypt\3DSDecrypt.csproj", "{2E30006A-3C60-4576-A262-937B21C83C06}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
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
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5AB50D9B-BA18-4F96-804B-52E7E0845B37}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,75 +0,0 @@
|
||||
<?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')" />
|
||||
<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>_3DSDecrypt</RootNamespace>
|
||||
<AssemblyName>3DSDecrypt</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 />
|
||||
</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" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Data\Constants.cs" />
|
||||
<Compile Include="Headers\NCCHHeaderFlags.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>
|
||||
@@ -1,66 +0,0 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace ThreeDS.Data
|
||||
{
|
||||
public static 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 };
|
||||
|
||||
// Big Endian - 0x1F, 0xF9, 0xE9, 0xAA, 0xC5, 0xFE, 0x04, 0x08, 0x02, 0x45, 0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A
|
||||
// Little Endian - 0x8A, 0x76, 0x52, 0x5D, 0xDC, 0x91, 0x45, 0x02, 0x08, 0x04, 0xFE, 0xC5, 0xAA, 0xE9, 0xF9, 0x1F
|
||||
public static BigInteger AESHardwareConstant = new BigInteger(new byte[] { 0x8A, 0x76, 0x52, 0x5D, 0xDC, 0x91, 0x45, 0x02, 0x08, 0x04, 0xFE, 0xC5, 0xAA, 0xE9, 0xF9, 0x1F });
|
||||
|
||||
#region Retail keys
|
||||
|
||||
// KeyX 0x18 (New 3DS 9.3)
|
||||
// Big Endian - 0x82, 0xE9, 0xC9, 0xBE, 0xBF, 0xB8, 0xBD, 0xB8, 0x75, 0xEC, 0xC0, 0xA0, 0x7D, 0x47, 0x43, 0x74
|
||||
// Little Endian - 0x74, 0x43, 0x47, 0x7D, 0xA0, 0xC0, 0xEC, 0x75, 0xB8, 0xBD, 0xB8, 0xBF, 0xBE, 0xC9, 0xE9, 0x82
|
||||
public static BigInteger KeyX0x18 = new BigInteger(new byte[] { 0x74, 0x43, 0x47, 0x7D, 0xA0, 0xC0, 0xEC, 0x75, 0xB8, 0xBD, 0xB8, 0xBF, 0xBE, 0xC9, 0xE9, 0x82, 0x00 });
|
||||
|
||||
// KeyX 0x1B (New 3DS 9.6)
|
||||
// Big Endian - 0x45, 0xAD, 0x04, 0x95, 0x39, 0x92, 0xC7, 0xC8, 0x93, 0x72, 0x4A, 0x9A, 0x7B, 0xCE, 0x61, 0x82
|
||||
// Little Endian - 0x82, 0x61, 0xCE, 0x7B, 0x9A, 0x4A, 0x72, 0x93, 0xC8, 0xC7, 0x92, 0x39, 0x95, 0x04, 0xAD, 0x45
|
||||
public static BigInteger KeyX0x1B = new BigInteger(new byte[] { 0x82, 0x61, 0xCE, 0x7B, 0x9A, 0x4A, 0x72, 0x93, 0xC8, 0xC7, 0x92, 0x39, 0x95, 0x04, 0xAD, 0x45, 0x00 });
|
||||
|
||||
// KeyX 0x25 (> 7.x)
|
||||
// Big Endian - 0xCE, 0xE7, 0xD8, 0xAB, 0x30, 0xC0, 0x0D, 0xAE, 0x85, 0x0E, 0xF5, 0xE3, 0x82, 0xAC, 0x5A, 0xF3
|
||||
// Little Endian - 0xF3, 0x5A, 0xAC, 0x82, 0xE3, 0xF5, 0x0E, 0x85, 0xAE, 0x0D, 0xC0, 0x30, 0xAB, 0xD8, 0xE7, 0xCE
|
||||
public static BigInteger KeyX0x25 = new BigInteger(new byte[] { 0xF3, 0x5A, 0xAC, 0x82, 0xE3, 0xF5, 0x0E, 0x85, 0xAE, 0x0D, 0xC0, 0x30, 0xAB, 0xD8, 0xE7, 0xCE, 0x00 });
|
||||
|
||||
// Dev KeyX 0x2C (< 6.x)
|
||||
// Big Endian - 0xB9, 0x8E, 0x95, 0xCE, 0xCA, 0x3E, 0x4D, 0x17, 0x1F, 0x76, 0xA9, 0x4D, 0xE9, 0x34, 0xC0, 0x53
|
||||
// Little Endian - 0x53, 0xC0, 0x34, 0xE9, 0x4D, 0xA9, 0x76, 0x1F, 0x17, 0x4D, 0x3E, 0xCA, 0xCE, 0x95, 0x8E, 0xB9
|
||||
public static BigInteger KeyX0x2C = new BigInteger(new byte[] { 0x53, 0xC0, 0x34, 0xE9, 0x4D, 0xA9, 0x76, 0x1F, 0x17, 0x4D, 0x3E, 0xCA, 0xCE, 0x95, 0x8E, 0xB9, 0x00 });
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dev Keys (Must be converted to LE)
|
||||
|
||||
// Dev KeyX 0x18 (New 3DS 9.3)
|
||||
// Big Endian - 0x30, 0x4B, 0xF1, 0x46, 0x83, 0x72, 0xEE, 0x64, 0x11, 0x5E, 0xBD, 0x40, 0x93, 0xD8, 0x42, 0x76
|
||||
// Little Endian - 0x76, 0x42, 0xD8, 0x93, 0x40, 0xBD, 0x5E, 0x11, 0x64, 0xEE, 0x72, 0x83, 0x46, 0xF1, 0x4B, 0x30
|
||||
public static BigInteger DevKeyX0x18 = new BigInteger(new byte[] { 0x76, 0x42, 0xD8, 0x93, 0x40, 0xBD, 0x5E, 0x11, 0x64, 0xEE, 0x72, 0x83, 0x46, 0xF1, 0x4B, 0x30, 0x00 });
|
||||
|
||||
// Dev KeyX 0x1B New 3DS 9.6)
|
||||
// Big Endian - 0x6C, 0x8B, 0x29, 0x44, 0xA0, 0x72, 0x60, 0x35, 0xF9, 0x41, 0xDF, 0xC0, 0x18, 0x52, 0x4F, 0xB6
|
||||
// Little Endian - 0xB6, 0x4F, 0x52, 0x18, 0xC0, 0xDF, 0x41, 0xF9, 0x35, 0x60, 0x72, 0xA0, 0x44, 0x29, 0x8B, 0x6C
|
||||
public static BigInteger DevKeyX0x1B = new BigInteger(new byte[] { 0xB6, 0x4F, 0x52, 0x18, 0xC0, 0xDF, 0x41, 0xF9, 0x35, 0x60, 0x72, 0xA0, 0x44, 0x29, 0x8B, 0x6C, 0x00 });
|
||||
|
||||
// Dev KeyX 0x25 (> 7.x)
|
||||
// Big Endian - 0x81, 0x90, 0x7A, 0x4B, 0x6F, 0x1B, 0x47, 0x32, 0x3A, 0x67, 0x79, 0x74, 0xCE, 0x4A, 0xD7, 0x1B
|
||||
// Little Endian - 0x1B, 0xD7, 0x4A, 0xCE, 0x74, 0x79, 0x67, 0x3A, 0x32, 0x47, 0x1B, 0x6F, 0x4B, 0x7A, 0x90, 0x81
|
||||
public static BigInteger DevKeyX0x25 = new BigInteger(new byte[] { 0x1B, 0xD7, 0x4A, 0xCE, 0x74, 0x79, 0x67, 0x3A, 0x32, 0x47, 0x1B, 0x6F, 0x4B, 0x7A, 0x90, 0x81, 0x00 });
|
||||
|
||||
// Dev KeyX 0x2C (< 6.x)
|
||||
// Big Endian - 0x51, 0x02, 0x07, 0x51, 0x55, 0x07, 0xCB, 0xB1, 0x8E, 0x24, 0x3D, 0xCB, 0x85, 0xE2, 0x3A, 0x1D
|
||||
// Little Endian - 0x1D, 0x3A, 0xE2, 0x85, 0xCB, 0x3D, 0x24, 0x8E, 0xB1, 0xCB, 0x07, 0x55, 0x51, 0x07, 0x02, 0x51
|
||||
public static BigInteger DevKeyX0x2C = new BigInteger(new byte[] { 0x1D, 0x3A, 0xE2, 0x85, 0xCB, 0x3D, 0x24, 0x8E, 0xB1, 0xCB, 0x07, 0x55, 0x51, 0x07, 0x02, 0x51, 0x00 });
|
||||
|
||||
#endregion
|
||||
|
||||
public const int CXTExtendedDataHeaderLength = 0x800;
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ThreeDS.Data
|
||||
{
|
||||
[Flags]
|
||||
public enum BitMasks : byte
|
||||
{
|
||||
FixedCryptoKey = 0x01,
|
||||
NoMountRomFs = 0x02,
|
||||
NoCrypto = 0x04,
|
||||
NewKeyYGenerator = 0x20,
|
||||
}
|
||||
|
||||
public enum ContentPlatform : byte
|
||||
{
|
||||
CTR = 0x01,
|
||||
Snake = 0x02, // New3DS
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ContentType : byte
|
||||
{
|
||||
Data = 0x01,
|
||||
Executable = 0x02,
|
||||
SystemUpdate = 0x04,
|
||||
Manual = 0x08,
|
||||
Child = 0x04 | 0x08,
|
||||
Trial = 0x10,
|
||||
}
|
||||
|
||||
public enum CryptoMethod : byte
|
||||
{
|
||||
Original = 0x00,
|
||||
Seven = 0x01,
|
||||
NineThree = 0x0A,
|
||||
NineSix = 0x0B,
|
||||
}
|
||||
|
||||
public enum FilesystemType : ulong
|
||||
{
|
||||
None = 0,
|
||||
Normal = 1,
|
||||
FIRM = 3,
|
||||
AGB_FIRMSave = 4,
|
||||
}
|
||||
|
||||
public enum MediaCardDeviceType : byte
|
||||
{
|
||||
NORFlash = 0x01,
|
||||
None = 0x02,
|
||||
BT = 0x03,
|
||||
}
|
||||
|
||||
public enum MediaPlatformIndex : byte
|
||||
{
|
||||
CTR = 0x01,
|
||||
}
|
||||
|
||||
public enum MediaTypeIndex : byte
|
||||
{
|
||||
InnerDevice = 0x00,
|
||||
Card1 = 0x01,
|
||||
Card2 = 0x02,
|
||||
ExtendedDevice = 0x03,
|
||||
}
|
||||
|
||||
public enum NCCHFlags
|
||||
{
|
||||
CryptoMethod = 0x03,
|
||||
ContentPlatform = 0x04,
|
||||
ContentTypeBitMask = 0x05,
|
||||
ContentUnitSize = 0x06,
|
||||
BitMasks = 0x07,
|
||||
}
|
||||
|
||||
public enum NCSDFlags
|
||||
{
|
||||
BackupWriteWaitTime = 0x00,
|
||||
MediaCardDevice3X = 0x03,
|
||||
MediaPlatformIndex = 0x04,
|
||||
MediaTypeIndex = 0x05,
|
||||
MediaUnitSize = 0x06,
|
||||
MediaCardDevice2X = 0x07,
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class ARM11KernelCapabilities
|
||||
{
|
||||
public byte[][] Descriptors = new byte[28][];
|
||||
public byte[] Reserved = new byte[0x10];
|
||||
|
||||
public static ARM11KernelCapabilities Read(BinaryReader reader)
|
||||
{
|
||||
ARM11KernelCapabilities kc = new ARM11KernelCapabilities();
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < 28; i++)
|
||||
kc.Descriptors[i] = reader.ReadBytes(4);
|
||||
|
||||
kc.Reserved = reader.ReadBytes(0x10);
|
||||
return kc;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class ARM11LocalSystemCapabilities
|
||||
{
|
||||
public byte[] ProgramID = new byte[8];
|
||||
public uint CoreVersion;
|
||||
public byte Flag1;
|
||||
public byte Flag2;
|
||||
public byte Flag0;
|
||||
public byte Priority;
|
||||
public byte[][] ResourceLimitDescriptors = new byte[16][];
|
||||
public StorageInfo StorageInfo;
|
||||
public byte[][] ServiceAccessControl = new byte[32][];
|
||||
public byte[][] ExtendedServiceAccessControl = new byte[2][];
|
||||
public byte[] Reserved = new byte[0xF];
|
||||
public byte ResourceLimitCategory;
|
||||
|
||||
public static ARM11LocalSystemCapabilities Read(BinaryReader reader)
|
||||
{
|
||||
ARM11LocalSystemCapabilities lsc = new ARM11LocalSystemCapabilities();
|
||||
|
||||
try
|
||||
{
|
||||
lsc.ProgramID = reader.ReadBytes(8);
|
||||
lsc.CoreVersion = reader.ReadUInt32();
|
||||
lsc.Flag1 = reader.ReadByte();
|
||||
lsc.Flag2 = reader.ReadByte();
|
||||
lsc.Flag0 = reader.ReadByte();
|
||||
lsc.Priority = reader.ReadByte();
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
lsc.ResourceLimitDescriptors[i] = reader.ReadBytes(2);
|
||||
|
||||
lsc.StorageInfo = StorageInfo.Read(reader);
|
||||
|
||||
for (int i = 0; i < 32; i++)
|
||||
lsc.ServiceAccessControl[i] = reader.ReadBytes(8);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
lsc.ExtendedServiceAccessControl[i] = reader.ReadBytes(8);
|
||||
|
||||
lsc.Reserved = reader.ReadBytes(0xF);
|
||||
lsc.ResourceLimitCategory = reader.ReadByte();
|
||||
return lsc;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class ARM9AccessControl
|
||||
{
|
||||
public byte[] Descriptors = new byte[15];
|
||||
public byte DescriptorVersion;
|
||||
|
||||
public static ARM9AccessControl Read(BinaryReader reader)
|
||||
{
|
||||
ARM9AccessControl ac = new ARM9AccessControl();
|
||||
|
||||
try
|
||||
{
|
||||
ac.Descriptors = reader.ReadBytes(15);
|
||||
ac.DescriptorVersion = reader.ReadByte();
|
||||
return ac;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class AccessControlInfo
|
||||
{
|
||||
public ARM11LocalSystemCapabilities ARM11LocalSystemCapabilities;
|
||||
public ARM11KernelCapabilities ARM11KernelCapabilities;
|
||||
public ARM9AccessControl ARM9AccessControl;
|
||||
|
||||
public static AccessControlInfo Read(BinaryReader reader)
|
||||
{
|
||||
AccessControlInfo aci = new AccessControlInfo();
|
||||
|
||||
try
|
||||
{
|
||||
aci.ARM11LocalSystemCapabilities = ARM11LocalSystemCapabilities.Read(reader);
|
||||
aci.ARM11KernelCapabilities = ARM11KernelCapabilities.Read(reader);
|
||||
aci.ARM9AccessControl = ARM9AccessControl.Read(reader);
|
||||
return aci;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class CXIExtendedHeader
|
||||
{
|
||||
public SystemControlInfo SCI;
|
||||
public AccessControlInfo ACI;
|
||||
public byte[] AccessDescSignature = new byte[0x100];
|
||||
public byte[] NCCHHDRPublicKey = new byte[0x100];
|
||||
public AccessControlInfo ACIForLimitations;
|
||||
|
||||
public static CXIExtendedHeader Read(BinaryReader reader)
|
||||
{
|
||||
CXIExtendedHeader header = new CXIExtendedHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.SCI = SystemControlInfo.Read(reader);
|
||||
header.ACI = AccessControlInfo.Read(reader);
|
||||
header.AccessDescSignature = reader.ReadBytes(0x100);
|
||||
header.NCCHHDRPublicKey = reader.ReadBytes(0x100);
|
||||
header.ACIForLimitations = AccessControlInfo.Read(reader);
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class CodeSetInfo
|
||||
{
|
||||
public byte[] Address = new byte[0x04];
|
||||
public uint PhysicalRegionSizeInPages;
|
||||
public uint SizeInBytes;
|
||||
|
||||
public static CodeSetInfo Read(BinaryReader reader)
|
||||
{
|
||||
CodeSetInfo csi = new CodeSetInfo();
|
||||
|
||||
try
|
||||
{
|
||||
csi.Address = reader.ReadBytes(4);
|
||||
csi.PhysicalRegionSizeInPages = reader.ReadUInt32();
|
||||
csi.SizeInBytes = reader.ReadUInt32();
|
||||
return csi;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class ExeFSFileHeader
|
||||
{
|
||||
private const string codeSegment = ".code\0\0\0";
|
||||
|
||||
public string FileName;
|
||||
public bool IsCodeBinary { get { return FileName == codeSegment; } }
|
||||
public uint FileOffset;
|
||||
public uint FileSize;
|
||||
public byte[] FileHash = new byte[0x20];
|
||||
|
||||
public static ExeFSFileHeader Read(BinaryReader reader)
|
||||
{
|
||||
ExeFSFileHeader header = new ExeFSFileHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.FileName = new string(reader.ReadChars(8));
|
||||
header.FileOffset = reader.ReadUInt32();
|
||||
header.FileSize = reader.ReadUInt32();
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class ExeFSHeader
|
||||
{
|
||||
public ExeFSFileHeader[] FileHeaders = new ExeFSFileHeader[10];
|
||||
public byte[] Reserved = new byte[0x20];
|
||||
|
||||
public static ExeFSHeader Read(BinaryReader reader)
|
||||
{
|
||||
ExeFSHeader header = new ExeFSHeader();
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
header.FileHeaders[i] = ExeFSFileHeader.Read(reader);
|
||||
|
||||
header.Reserved = reader.ReadBytes(0x20);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
byte[] fileHash = reader.ReadBytes(0x20);
|
||||
header.FileHeaders[9 - i].FileHash = fileHash;
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ThreeDS.Data;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class NCCHHeader
|
||||
{
|
||||
private const string NCCHMagicNumber = "NCCH";
|
||||
|
||||
public byte[] RSA2048Signature = new byte[0x100];
|
||||
public uint ContentSizeInMediaUnits;
|
||||
public uint ContentSizeInBytes { get { return ContentSizeInMediaUnits * 0x200; } }
|
||||
public byte[] PartitionId = new byte[8];
|
||||
public byte[] MakerCode = new byte[2];
|
||||
public byte[] Version = new byte[2];
|
||||
public byte[] VerificationHash = new byte[4];
|
||||
public byte[] ProgramId = new byte[4];
|
||||
public byte[] Reserved1 = new byte[0x10];
|
||||
public byte[] LogoRegionHash = new byte[0x20];
|
||||
public byte[] ProductCode = new byte[0x10];
|
||||
public byte[] ExtendedHeaderHash = new byte[0x20];
|
||||
public uint ExtendedHeaderSizeInBytes;
|
||||
public byte[] Reserved2 = new byte[4];
|
||||
public NCCHHeaderFlags Flags;
|
||||
public uint PlainRegionOffsetInMediaUnits;
|
||||
public uint PlainRegionOffsetInBytes { get { return PlainRegionOffsetInMediaUnits * 0x200; } }
|
||||
public uint PlainRegionSizeInMediaUnits;
|
||||
public uint PlainRegionSizeInBytes { get { return PlainRegionSizeInMediaUnits * 0x200; } }
|
||||
public uint LogoRegionOffsetInMediaUnits;
|
||||
public uint LogoRegionOffsetInBytes { get { return LogoRegionOffsetInMediaUnits * 0x200; } }
|
||||
public uint LogoRegionSizeInMediaUnits;
|
||||
public uint LogoRegionSizeInBytes { get { return LogoRegionSizeInMediaUnits * 0x200; } }
|
||||
public uint ExeFSOffsetInMediaUnits;
|
||||
public uint ExeFSOffsetInBytes { get { return ExeFSOffsetInMediaUnits * 0x200; } }
|
||||
public uint ExeFSSizeInMediaUnits;
|
||||
public uint ExeFSSizeInBytes { get { return ExeFSSizeInMediaUnits * 0x200; } }
|
||||
public uint ExeFSHashRegionOffsetInMediaUnits;
|
||||
public uint ExeFSHashRegionOffsetInBytes { get { return ExeFSHashRegionOffsetInMediaUnits * 0x200; } }
|
||||
public uint ExeFSHashRegionSizeInMediaUnits;
|
||||
public uint ExeFSHashRegionSizeInBytes { get { return ExeFSHashRegionSizeInMediaUnits * 0x200; } }
|
||||
public uint RomFSOffsetInMediaUnits;
|
||||
public uint RomFSOffsetInBytes { get { return RomFSOffsetInMediaUnits * 0x200; } }
|
||||
public uint RomFSSizeInMediaUnits;
|
||||
public uint RomFSSizeInBytes { get { return RomFSSizeInMediaUnits * 0x200; } }
|
||||
public uint RomFSHashRegionOffsetInMediaUnits;
|
||||
public uint RomFSHashRegionOffsetInBytes { get { return RomFSHashRegionOffsetInMediaUnits * 0x200; } }
|
||||
public uint RomFSHashRegionSizeInMediaUnits;
|
||||
public uint RomFSHashRegionSizeInBytes { get { return RomFSHashRegionSizeInMediaUnits * 0x200; } }
|
||||
public byte[] ExeFSSuperblockHash = new byte[0x20];
|
||||
public byte[] RomFSSuperblockHash = new byte[0x20];
|
||||
|
||||
public static NCCHHeader Read(BinaryReader reader)
|
||||
{
|
||||
NCCHHeader header = new NCCHHeader();
|
||||
|
||||
try
|
||||
{
|
||||
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.ExeFSHashRegionOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.ExeFSHashRegionSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.RomFSOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.RomFSSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.RomFSHashRegionOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.RomFSHashRegionSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.ExeFSSuperblockHash = reader.ReadBytes(0x20);
|
||||
header.RomFSSuperblockHash = reader.ReadBytes(0x20);
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using ThreeDS.Data;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class NCCHHeaderFlags
|
||||
{
|
||||
public byte Reserved0;
|
||||
public byte Reserved1;
|
||||
public byte Reserved2;
|
||||
public CryptoMethod CryptoMethod;
|
||||
public ContentPlatform ContentPlatform;
|
||||
public ContentType MediaPlatformIndex;
|
||||
public byte ContentUnitSize;
|
||||
public uint ContentUnitSizeInBytes { get { return (uint)(0x200 * Math.Pow(2, this.ContentUnitSize)); } }
|
||||
public BitMasks BitMasks;
|
||||
|
||||
public static NCCHHeaderFlags Read(BinaryReader reader)
|
||||
{
|
||||
NCCHHeaderFlags flags = new NCCHHeaderFlags();
|
||||
|
||||
try
|
||||
{
|
||||
flags.Reserved0 = reader.ReadByte();
|
||||
flags.Reserved1 = reader.ReadByte();
|
||||
flags.Reserved2 = reader.ReadByte();
|
||||
flags.CryptoMethod = (CryptoMethod)reader.ReadByte();
|
||||
flags.ContentPlatform = (ContentPlatform)reader.ReadByte();
|
||||
flags.MediaPlatformIndex = (ContentType)reader.ReadByte();
|
||||
flags.ContentUnitSize = reader.ReadByte();
|
||||
flags.BitMasks = (BitMasks)reader.ReadByte();
|
||||
return flags;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
try
|
||||
{
|
||||
writer.Write(this.Reserved0);
|
||||
writer.Write(this.Reserved1);
|
||||
writer.Write(this.Reserved2);
|
||||
writer.Write((byte)this.CryptoMethod);
|
||||
writer.Write((byte)this.ContentPlatform);
|
||||
writer.Write((byte)this.MediaPlatformIndex);
|
||||
writer.Write((byte)this.ContentUnitSize);
|
||||
writer.Write((byte)this.BitMasks);
|
||||
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using ThreeDS.Data;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class NCSDHeader
|
||||
{
|
||||
private const string NCSDMagicNumber = "NCSD";
|
||||
|
||||
// Common to all NCSD files
|
||||
public byte[] RSA2048Signature = new byte[0x100];
|
||||
public uint ImageSizeInMediaUnits;
|
||||
public uint ImageSizeInBytes { get { return ImageSizeInMediaUnits * 0x200; } }
|
||||
public byte[] MediaId = new byte[8];
|
||||
public FilesystemType PartitionsFSType;
|
||||
public byte[] PartitionsCryptType = new byte[8];
|
||||
public PartitionTableEntry[] PartitionsTable = new PartitionTableEntry[8];
|
||||
|
||||
// For carts
|
||||
public byte[] ExheaderHash = new byte[0x20];
|
||||
public uint AdditionalHeaderSize;
|
||||
public uint SectorZeroOffset;
|
||||
private byte[] partitionFlags = new byte[8];
|
||||
public byte BackupWriteWaitTime { get { return partitionFlags[(int)NCSDFlags.BackupWriteWaitTime]; } }
|
||||
public MediaCardDeviceType MediaCardDevice3X { get { return (MediaCardDeviceType)partitionFlags[(int)NCSDFlags.MediaCardDevice3X]; } }
|
||||
public MediaPlatformIndex MediaPlatformIndex { get { return (MediaPlatformIndex)partitionFlags[(int)NCSDFlags.MediaPlatformIndex]; } }
|
||||
public MediaTypeIndex MediaTypeIndex { get { return (MediaTypeIndex)partitionFlags[(int)NCSDFlags.MediaTypeIndex]; } }
|
||||
public uint SectorSize { get { return (uint)(0x200 * Math.Pow(2, partitionFlags[(int)NCSDFlags.MediaUnitSize])); } }
|
||||
public MediaCardDeviceType MediaCardDevice2X { get { return (MediaCardDeviceType)partitionFlags[(int)NCSDFlags.MediaCardDevice2X]; } }
|
||||
public byte[][] PartitionIdTable = new byte[8][];
|
||||
public byte[] ReservedBlock1 = new byte[0x20];
|
||||
public byte[] ReservedBlock2 = new byte[0xE];
|
||||
public byte FirmUpdateByte1;
|
||||
public byte FIrmUpdateByte2;
|
||||
|
||||
// For NAND
|
||||
public byte[] Unknown = new byte[0x5E];
|
||||
public byte[] EncryptedMBR = new byte[0x42];
|
||||
|
||||
public static NCSDHeader Read(BinaryReader reader)
|
||||
{
|
||||
NCSDHeader header = new NCSDHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.RSA2048Signature = reader.ReadBytes(0x100);
|
||||
|
||||
if (new string(reader.ReadChars(4)) != NCSDMagicNumber)
|
||||
return null;
|
||||
|
||||
header.ImageSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.MediaId = reader.ReadBytes(8);
|
||||
header.PartitionsFSType = (FilesystemType)reader.ReadUInt64();
|
||||
header.PartitionsCryptType = reader.ReadBytes(8);
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
header.PartitionsTable[i] = PartitionTableEntry.Read(reader);
|
||||
|
||||
if (header.PartitionsFSType == FilesystemType.Normal
|
||||
|| header.PartitionsFSType == FilesystemType.None)
|
||||
{
|
||||
header.ExheaderHash = reader.ReadBytes(0x20);
|
||||
header.AdditionalHeaderSize = reader.ReadUInt32();
|
||||
header.SectorZeroOffset = reader.ReadUInt32();
|
||||
header.partitionFlags = reader.ReadBytes(8);
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
header.PartitionIdTable[i] = reader.ReadBytes(8);
|
||||
|
||||
header.ReservedBlock1 = reader.ReadBytes(0x20);
|
||||
header.ReservedBlock2 = reader.ReadBytes(0xE);
|
||||
header.FirmUpdateByte1 = reader.ReadByte();
|
||||
header.FIrmUpdateByte2 = reader.ReadByte();
|
||||
}
|
||||
else if (header.PartitionsFSType == FilesystemType.FIRM)
|
||||
{
|
||||
header.Unknown = reader.ReadBytes(0x5E);
|
||||
header.EncryptedMBR = reader.ReadBytes(0x42);
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class PartitionTableEntry
|
||||
{
|
||||
public uint Offset { get; set; }
|
||||
public uint Length { get; set; }
|
||||
|
||||
public static PartitionTableEntry Read(BinaryReader reader)
|
||||
{
|
||||
PartitionTableEntry entry = new PartitionTableEntry();
|
||||
|
||||
try
|
||||
{
|
||||
entry.Offset = reader.ReadUInt32();
|
||||
entry.Length = reader.ReadUInt32();
|
||||
return entry;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
return Offset != 0 && Length != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class StorageInfo
|
||||
{
|
||||
public byte[] ExtdataID = new byte[8];
|
||||
public byte[] SystemSavedataIDs = new byte[8];
|
||||
public byte[] StorageAccessibleUniqueIDs = new byte[8];
|
||||
public byte[] FilesystemAccessInfo = new byte[7];
|
||||
public byte OtherAttributes;
|
||||
|
||||
public static StorageInfo Read(BinaryReader reader)
|
||||
{
|
||||
StorageInfo si = new StorageInfo();
|
||||
|
||||
try
|
||||
{
|
||||
si.ExtdataID = reader.ReadBytes(8);
|
||||
si.SystemSavedataIDs = reader.ReadBytes(8);
|
||||
si.StorageAccessibleUniqueIDs = reader.ReadBytes(8);
|
||||
si.FilesystemAccessInfo = reader.ReadBytes(7);
|
||||
si.OtherAttributes = reader.ReadByte();
|
||||
return si;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class SystemControlInfo
|
||||
{
|
||||
public char[] ApplicationTitle = new char[8];
|
||||
public byte[] Reserved1 = new byte[5];
|
||||
public byte Flag;
|
||||
public byte[] RemasterVersion = new byte[2];
|
||||
public CodeSetInfo TextCodesetInfo;
|
||||
public uint StackSize;
|
||||
public CodeSetInfo ReadOnlyCodeSetInfo;
|
||||
public byte[] Reserved2 = new byte[4];
|
||||
public CodeSetInfo DataCodeSetInfo;
|
||||
public uint BSSSize;
|
||||
public byte[][] DependencyModuleList = new byte[48][];
|
||||
public SystemInfo SystemInfo;
|
||||
|
||||
public static SystemControlInfo Read(BinaryReader reader)
|
||||
{
|
||||
SystemControlInfo sci = new SystemControlInfo();
|
||||
|
||||
try
|
||||
{
|
||||
sci.ApplicationTitle = reader.ReadChars(8);
|
||||
sci.Reserved1 = reader.ReadBytes(5);
|
||||
sci.Flag = reader.ReadByte();
|
||||
sci.RemasterVersion = reader.ReadBytes(2);
|
||||
sci.TextCodesetInfo = CodeSetInfo.Read(reader);
|
||||
sci.StackSize = reader.ReadUInt32();
|
||||
sci.ReadOnlyCodeSetInfo = CodeSetInfo.Read(reader);
|
||||
sci.Reserved2 = reader.ReadBytes(4);
|
||||
sci.DataCodeSetInfo = CodeSetInfo.Read(reader);
|
||||
sci.BSSSize = reader.ReadUInt32();
|
||||
|
||||
for (int i = 0; i < 48; i++)
|
||||
sci.DependencyModuleList[i] = reader.ReadBytes(8);
|
||||
|
||||
sci.SystemInfo = SystemInfo.Read(reader);
|
||||
return sci;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class SystemInfo
|
||||
{
|
||||
public ulong SaveDataSize;
|
||||
public byte[] JumpID = new byte[8];
|
||||
public byte[] Reserved = new byte[0x30];
|
||||
|
||||
public static SystemInfo Read(BinaryReader reader)
|
||||
{
|
||||
SystemInfo si = new SystemInfo();
|
||||
|
||||
try
|
||||
{
|
||||
si.SaveDataSize = reader.ReadUInt64();
|
||||
si.JumpID = reader.ReadBytes(8);
|
||||
si.Reserved = reader.ReadBytes(0x30);
|
||||
return si;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ThreeDS
|
||||
{
|
||||
class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length < 2 || (args[0] != "encrypt" && args[0] != "decrypt"))
|
||||
{
|
||||
Console.WriteLine("Usage: 3dsdecrypt.exe (decrypt|encrypt) [-dev] <file|dir> ...");
|
||||
return;
|
||||
}
|
||||
|
||||
bool development = false;
|
||||
int start = 1;
|
||||
if (args[1] == "-dev")
|
||||
{
|
||||
development = true;
|
||||
start = 2;
|
||||
}
|
||||
|
||||
for (int i = start; i < args.Length; i++)
|
||||
{
|
||||
if (File.Exists(args[i]))
|
||||
{
|
||||
ThreeDSTool tool = new ThreeDSTool(args[i], development);
|
||||
if (args[0] == "decrypt")
|
||||
tool.Decrypt();
|
||||
else if (args[0] == "encrypt")
|
||||
tool.Encrypt();
|
||||
}
|
||||
else if (Directory.Exists(args[i]))
|
||||
{
|
||||
foreach (string file in Directory.EnumerateFiles(args[i], "*", SearchOption.AllDirectories))
|
||||
{
|
||||
ThreeDSTool tool = new ThreeDSTool(file, development);
|
||||
if (args[0] == "decrypt")
|
||||
tool.Decrypt();
|
||||
else if (args[0] == "encrypt")
|
||||
tool.Encrypt();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,623 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
using ThreeDS.Data;
|
||||
using ThreeDS.Headers;
|
||||
|
||||
namespace ThreeDS
|
||||
{
|
||||
public class ThreeDSTool
|
||||
{
|
||||
private readonly string filename;
|
||||
private readonly bool development;
|
||||
|
||||
public ThreeDSTool(string filename, bool development)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.development = development;
|
||||
}
|
||||
|
||||
public void Decrypt()
|
||||
{
|
||||
if (!File.Exists(filename))
|
||||
return;
|
||||
|
||||
Console.WriteLine(filename);
|
||||
|
||||
using (BinaryReader f = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
|
||||
using (BinaryWriter g = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)))
|
||||
{
|
||||
NCSDHeader header = NCSDHeader.Read(f);
|
||||
if (header == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS Rom!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate over all 8 NCCH partitions
|
||||
for (int p = 0; p < 8; p++)
|
||||
{
|
||||
if (!header.PartitionsTable[p].IsValid())
|
||||
{
|
||||
Console.WriteLine("Partition {0} Not found... Skipping...", p);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Seek to the beginning of the NCCH partition
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize), SeekOrigin.Begin);
|
||||
|
||||
NCCHHeader partitionHeader = NCCHHeader.Read(f);
|
||||
if (partitionHeader == null)
|
||||
{
|
||||
Console.WriteLine("Partition {0} Unable to read NCCH header", p);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the 'NoCrypto' bit is set
|
||||
if ((partitionHeader.Flags.BitMasks & BitMasks.NoCrypto) != 0)
|
||||
{
|
||||
Console.WriteLine("Partition {0:d}: Already Decrypted?...", p);
|
||||
continue;
|
||||
}
|
||||
|
||||
// PartitionID is used as IV joined with the content type.
|
||||
byte[] plainIV = partitionHeader.PartitionId.Concat(Constants.PlainCounter).ToArray(); // Get the IV for plain sector (TitleID + Plain Counter)
|
||||
byte[] exefsIV = partitionHeader.PartitionId.Concat(Constants.ExefsCounter).ToArray(); // Get the IV for ExeFS (TitleID + ExeFS Counter)
|
||||
byte[] romfsIV = partitionHeader.PartitionId.Concat(Constants.RomfsCounter).ToArray(); // Get the IV for RomFS (TitleID + RomFS Counter)
|
||||
|
||||
BigInteger KeyX = 0;
|
||||
BigInteger KeyX2C = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
BigInteger KeyY = new BigInteger(partitionHeader.RSA2048Signature.Take(16).Reverse().ToArray()); // KeyY is the first 16 bytes of the partition RSA-2048 SHA-256 signature
|
||||
|
||||
BigInteger NormalKey = 0;
|
||||
BigInteger NormalKey2C = RotateLeft((RotateLeft(KeyX2C, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
|
||||
// Determine the Keys to be used
|
||||
if ((partitionHeader.Flags.BitMasks & BitMasks.FixedCryptoKey) != 0)
|
||||
{
|
||||
NormalKey = 0x00;
|
||||
NormalKey2C = 0x00;
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Zero Key");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (partitionHeader.Flags.CryptoMethod == CryptoMethod.Original)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x2C");
|
||||
}
|
||||
else if (partitionHeader.Flags.CryptoMethod == CryptoMethod.Seven)
|
||||
{
|
||||
KeyX = (development ? Constants.KeyX0x25 : Constants.KeyX0x25);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x25");
|
||||
}
|
||||
else if (partitionHeader.Flags.CryptoMethod == CryptoMethod.NineThree)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x18 : Constants.KeyX0x18);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x18");
|
||||
}
|
||||
else if (partitionHeader.Flags.CryptoMethod == CryptoMethod.NineSix)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x1B : Constants.KeyX0x1B);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x1B");
|
||||
}
|
||||
|
||||
NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
|
||||
// Decrypted extended header, if it exists
|
||||
if (partitionHeader.ExtendedHeaderSizeInBytes > 0)
|
||||
{
|
||||
// Seek to the partition start and skip first part of the header
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin);
|
||||
|
||||
var str = BitConverter.ToString(plainIV).Replace("-", "");
|
||||
|
||||
var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
|
||||
exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), plainIV));
|
||||
|
||||
Console.WriteLine("Partition {0} ExeFS: Decrypting: ExHeader", p);
|
||||
|
||||
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(Constants.CXTExtendedDataHeaderLength)));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
// Decrypt the ExeFS, if it exists
|
||||
if (partitionHeader.ExeFSSizeInBytes > 0)
|
||||
{
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
|
||||
var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
|
||||
exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIV));
|
||||
|
||||
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes((int)header.SectorSize)));
|
||||
g.Flush();
|
||||
|
||||
Console.WriteLine("Partition {0} ExeFS: Decrypting: ExeFS Filename Table", p);
|
||||
|
||||
if (partitionHeader.Flags.CryptoMethod != CryptoMethod.Original)
|
||||
{
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
ExeFSHeader exefsHeader = ExeFSHeader.Read(f);
|
||||
if (exefsHeader != null)
|
||||
{
|
||||
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders)
|
||||
{
|
||||
if (!fileHeader.IsCodeBinary)
|
||||
continue;
|
||||
|
||||
uint datalenM = ((fileHeader.FileSize) / (1024 * 1024));
|
||||
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
|
||||
uint ctroffset = ((fileHeader.FileOffset + header.SectorSize) / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffsetForHeader = AddToByteArray(exefsIV, (int)ctroffset);
|
||||
|
||||
var exefsctrmode = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
|
||||
exefsctrmode.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), exefsIVWithOffsetForHeader));
|
||||
|
||||
exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
|
||||
exefsctrmode2C.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffsetForHeader));
|
||||
|
||||
f.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
if (datalenM > 0)
|
||||
{
|
||||
for (int i = 0; i < datalenM; i++)
|
||||
{
|
||||
g.Write(exefsctrmode2C.ProcessBytes(exefsctrmode.ProcessBytes(f.ReadBytes(1024 * 1024))));
|
||||
g.Flush();
|
||||
Console.Write("\rPartition {0} ExeFS: Decrypting: {1}... {2} / {3} mb...", p, fileHeader.FileName, i, datalenM + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (datalenB > 0)
|
||||
{
|
||||
g.Write(exefsctrmode2C.ProcessBytes(exefsctrmode.ProcessBytes(f.ReadBytes((int)datalenB))));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
Console.Write("\rPartition {0} ExeFS: Decrypting: {1}... {2} / {3} mb... Done!\r\n", p, fileHeader.FileName, datalenM + 1, datalenM + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// decrypt exefs
|
||||
int exefsSizeM = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) / (1024 * 1024);
|
||||
int exefsSizeB = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) % (1024 * 1024);
|
||||
int ctroffsetE = (int)(header.SectorSize / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffset = AddToByteArray(exefsIV, ctroffsetE);
|
||||
|
||||
exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
|
||||
exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffset));
|
||||
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin);
|
||||
if (exefsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < exefsSizeM; i++)
|
||||
{
|
||||
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(1024 * 1024)));
|
||||
g.Flush();
|
||||
Console.Write("\rPartition {0} ExeFS: Decrypting: {1} / {2} mb", p, i, exefsSizeM + 1);
|
||||
}
|
||||
}
|
||||
if (exefsSizeB > 0)
|
||||
{
|
||||
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(exefsSizeB)));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
Console.Write("\rPartition {0} ExeFS: Decrypting: {1} / {2} mb... Done!\r\n", p, exefsSizeM + 1, exefsSizeM + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Partition {0} ExeFS: No Data... Skipping...", p);
|
||||
}
|
||||
|
||||
if (partitionHeader.RomFSOffsetInMediaUnits != 0)
|
||||
{
|
||||
int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (1024 * 1024);
|
||||
int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) % (1024 * 1024);
|
||||
|
||||
var romfsctrmode = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
|
||||
romfsctrmode.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), romfsIV));
|
||||
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
if (romfsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < romfsSizeM; i++)
|
||||
{
|
||||
g.Write(romfsctrmode.ProcessBytes(f.ReadBytes(1024 * 1024)));
|
||||
g.Flush();
|
||||
Console.Write("\rPartition {0} RomFS: Decrypting: {1} / {2} mb", p, i, romfsSizeM + 1);
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
g.Write(romfsctrmode.ProcessBytes(f.ReadBytes(romfsSizeB)));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
Console.Write("\rPartition {0} RomFS: Decrypting: {1} / {2} mb... Done!\r\n", p, romfsSizeM + 1, romfsSizeM + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Partition {0} RomFS: No Data... Skipping...", p);
|
||||
}
|
||||
|
||||
// Write the new CryptoMethod
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18B, SeekOrigin.Begin);
|
||||
g.Write((byte)CryptoMethod.Original);
|
||||
g.Flush();
|
||||
|
||||
// Write the new BitMasks flag
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18F, SeekOrigin.Begin);
|
||||
BitMasks flag = partitionHeader.Flags.BitMasks;
|
||||
flag = flag & (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
|
||||
flag = (flag | BitMasks.NoCrypto);
|
||||
g.Write((byte)flag);
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
Console.WriteLine("Press Enter to Exit...");
|
||||
Console.Read();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes wrong header values
|
||||
/// </summary>
|
||||
public void Encrypt()
|
||||
{
|
||||
if (!File.Exists(filename))
|
||||
return;
|
||||
|
||||
Console.WriteLine(filename);
|
||||
|
||||
using (BinaryReader f = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
|
||||
using (BinaryWriter g = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)))
|
||||
{
|
||||
NCSDHeader header = NCSDHeader.Read(f);
|
||||
if (header == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS Rom!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate over all 8 NCCH partitions
|
||||
for (int p = 0; p < 8; p++)
|
||||
{
|
||||
if (!header.PartitionsTable[p].IsValid())
|
||||
{
|
||||
Console.WriteLine("Partition {0} Not found... Skipping...", p);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Seek to the beginning of the NCCH partition
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize), SeekOrigin.Begin);
|
||||
|
||||
NCCHHeader partitionHeader = NCCHHeader.Read(f);
|
||||
if (partitionHeader == null)
|
||||
{
|
||||
Console.WriteLine("Partition {0} Unable to read NCCH header", p);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the backup flags
|
||||
f.BaseStream.Seek(0x1188, SeekOrigin.Begin);
|
||||
NCCHHeaderFlags backupFlags = NCCHHeaderFlags.Read(f);
|
||||
|
||||
// Check if the 'NoCrypto' bit is not set
|
||||
if ((partitionHeader.Flags.BitMasks & BitMasks.NoCrypto) == 0)
|
||||
{
|
||||
Console.WriteLine("Partition {0:d}: Already Encrypted?...", p);
|
||||
continue;
|
||||
}
|
||||
|
||||
// PartitionID is used as IV joined with the content type.
|
||||
byte[] plainIV = partitionHeader.PartitionId.Concat(Constants.PlainCounter).ToArray(); // Get the IV for plain sector (TitleID + Plain Counter)
|
||||
byte[] exefsIV = partitionHeader.PartitionId.Concat(Constants.ExefsCounter).ToArray(); // Get the IV for ExeFS (TitleID + ExeFS Counter)
|
||||
byte[] romfsIV = partitionHeader.PartitionId.Concat(Constants.RomfsCounter).ToArray(); // Get the IV for RomFS (TitleID + RomFS Counter)
|
||||
|
||||
BigInteger KeyX = 0;
|
||||
BigInteger KeyX2C = Constants.KeyX0x2C;
|
||||
BigInteger KeyY = new BigInteger(partitionHeader.RSA2048Signature.Take(16).Reverse().ToArray()); // KeyY is the first 16 bytes of the partition RSA-2048 SHA-256 signature
|
||||
|
||||
BigInteger NormalKey = 0;
|
||||
BigInteger NormalKey2C = RotateLeft((RotateLeft(KeyX2C, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
|
||||
// Determine the Keys to be used
|
||||
if ((backupFlags.BitMasks & BitMasks.FixedCryptoKey) != 0)
|
||||
{
|
||||
NormalKey = 0x00;
|
||||
NormalKey2C = 0x00;
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Zero Key");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (backupFlags.CryptoMethod == CryptoMethod.Original)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x2C");
|
||||
}
|
||||
else if (backupFlags.CryptoMethod == CryptoMethod.Seven)
|
||||
{
|
||||
KeyX = (development ? Constants.KeyX0x25 : Constants.KeyX0x25);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x25");
|
||||
}
|
||||
else if (backupFlags.CryptoMethod == CryptoMethod.NineThree)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x18 : Constants.KeyX0x18);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x18");
|
||||
}
|
||||
else if (backupFlags.CryptoMethod == CryptoMethod.NineSix)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x1B : Constants.KeyX0x1B);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x1B");
|
||||
}
|
||||
|
||||
NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
|
||||
// Encrypt extended header, if it exists
|
||||
if (partitionHeader.ExtendedHeaderSizeInBytes > 0)
|
||||
{
|
||||
// Seek to the partition start and skip first part of the header
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin);
|
||||
|
||||
var str = BitConverter.ToString(plainIV).Replace("-", "");
|
||||
|
||||
var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
|
||||
exefsctrmode2C.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), plainIV));
|
||||
|
||||
Console.WriteLine("Partition {0} ExeFS: Encrypting: ExHeader", p);
|
||||
|
||||
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(Constants.CXTExtendedDataHeaderLength)));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
// Encrypt the ExeFS, if it exists
|
||||
if (partitionHeader.ExeFSSizeInBytes > 0)
|
||||
{
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
|
||||
var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
|
||||
exefsctrmode2C.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIV));
|
||||
|
||||
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes((int)header.SectorSize)));
|
||||
g.Flush();
|
||||
|
||||
Console.WriteLine("Partition {0} ExeFS: Encrypting: ExeFS Filename Table", p);
|
||||
|
||||
if (backupFlags.CryptoMethod != CryptoMethod.Original)
|
||||
{
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
ExeFSHeader exefsHeader = ExeFSHeader.Read(f);
|
||||
if (exefsHeader != null)
|
||||
{
|
||||
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders)
|
||||
{
|
||||
if (!fileHeader.IsCodeBinary)
|
||||
continue;
|
||||
|
||||
uint datalenM = ((fileHeader.FileSize) / (1024 * 1024));
|
||||
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
|
||||
uint ctroffset = ((fileHeader.FileOffset + header.SectorSize) / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffsetForHeader = AddToByteArray(exefsIV, (int)ctroffset);
|
||||
|
||||
var exefsctrmode = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
|
||||
exefsctrmode.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), exefsIVWithOffsetForHeader));
|
||||
|
||||
exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
|
||||
exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffsetForHeader));
|
||||
|
||||
f.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
if (datalenM > 0)
|
||||
{
|
||||
for (int i = 0; i < datalenM; i++)
|
||||
{
|
||||
g.Write(exefsctrmode2C.ProcessBytes(exefsctrmode.ProcessBytes(f.ReadBytes(1024 * 1024))));
|
||||
g.Flush();
|
||||
Console.Write("\rPartition {0} ExeFS: Encrypting: {1}... {2} / {3} mb...", p, fileHeader.FileName, i, datalenM + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (datalenB > 0)
|
||||
{
|
||||
g.Write(exefsctrmode2C.ProcessBytes(exefsctrmode.ProcessBytes(f.ReadBytes((int)datalenB))));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
Console.Write("\rPartition {0} ExeFS: Encrypting: {1}... {2} / {3} mb... Done!\r\n", p, fileHeader.FileName, datalenM + 1, datalenM + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// decrypt exefs
|
||||
int exefsSizeM = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) / (1024 * 1024);
|
||||
int exefsSizeB = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) % (1024 * 1024);
|
||||
int ctroffsetE = (int)(header.SectorSize / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffset = AddToByteArray(exefsIV, ctroffsetE);
|
||||
|
||||
exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
|
||||
exefsctrmode2C.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffset));
|
||||
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin);
|
||||
if (exefsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < exefsSizeM; i++)
|
||||
{
|
||||
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(1024 * 1024)));
|
||||
g.Flush();
|
||||
Console.Write("\rPartition {0} ExeFS: Encrypting: {1} / {2} mb", p, i, exefsSizeM + 1);
|
||||
}
|
||||
}
|
||||
if (exefsSizeB > 0)
|
||||
{
|
||||
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(exefsSizeB)));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
Console.Write("\rPartition {0} ExeFS: Encrypting: {1} / {2} mb... Done!\r\n", p, exefsSizeM + 1, exefsSizeM + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Partition {0} ExeFS: No Data... Skipping...", p);
|
||||
}
|
||||
|
||||
if (partitionHeader.RomFSOffsetInMediaUnits != 0)
|
||||
{
|
||||
int romfsBlockSize = 16; // block size in mb
|
||||
int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (romfsBlockSize * (1024 * 1024));
|
||||
int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) % (romfsBlockSize * (1024 * 1024));
|
||||
int romfsSizeTotalMb = (int)((partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (1024 * 1024) + 1);
|
||||
|
||||
if (p > 0) // RomFS for partitions 1 and up always use Key0x2C
|
||||
{
|
||||
if ((backupFlags.BitMasks & BitMasks.FixedCryptoKey) != 0) // except if using zero-key
|
||||
{
|
||||
NormalKey = 0x00;
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyX = KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
}
|
||||
|
||||
var romfsctrmode = CipherUtilities.GetCipher("AES/CTR"); // ("AES/CTR/NoPadding")
|
||||
romfsctrmode.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), romfsIV));
|
||||
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
if (romfsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < romfsSizeM; i++)
|
||||
{
|
||||
g.Write(romfsctrmode.ProcessBytes(f.ReadBytes(romfsBlockSize * 1024 * 1024)));
|
||||
g.Flush();
|
||||
Console.Write("\rPartition {0} RomFS: Encrypting: {1} / {2} mb", p, i * romfsBlockSize, romfsSizeTotalMb);
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
g.Write(romfsctrmode.ProcessBytes(f.ReadBytes(romfsSizeB)));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
Console.Write("\rPartition {0} RomFS: Encrypting: {1} / {2} mb... Done!\r\n", p, romfsSizeTotalMb, romfsSizeTotalMb);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Partition {0} RomFS: No Data... Skipping...", p);
|
||||
}
|
||||
|
||||
// Write the new CryptoMethod
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18B, SeekOrigin.Begin);
|
||||
if (p > 0)
|
||||
{
|
||||
g.Write((byte)CryptoMethod.Original); // For partitions 1 and up, set crypto-method to 0x00
|
||||
g.Flush();
|
||||
}
|
||||
else
|
||||
{
|
||||
g.Write((byte)backupFlags.CryptoMethod); // If partition 0, restore crypto-method from backup flags
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
// Write the new BitMasks flag
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18F, SeekOrigin.Begin);
|
||||
BitMasks flag = partitionHeader.Flags.BitMasks;
|
||||
flag = (flag & ((BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF));
|
||||
flag = (flag | (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & backupFlags.BitMasks);
|
||||
g.Write((byte)flag);
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
Console.WriteLine("Press Enter to Exit...");
|
||||
Console.Read();
|
||||
}
|
||||
}
|
||||
|
||||
private static BigInteger RotateLeft(BigInteger val, int r_bits, int max_bits)
|
||||
{
|
||||
return (val << r_bits % max_bits) & (BigInteger.Pow(2, max_bits) - 1) | ((val & (BigInteger.Pow(2, max_bits) - 1)) >> (max_bits - (r_bits % max_bits)));
|
||||
}
|
||||
|
||||
private static string ToBytes(int num)
|
||||
{
|
||||
string numstr = "";
|
||||
int tmp = num;
|
||||
|
||||
while (numstr.Length < 16)
|
||||
{
|
||||
numstr += (char)(tmp & 0xFF);
|
||||
tmp >>= 8;
|
||||
}
|
||||
|
||||
return numstr;
|
||||
}
|
||||
|
||||
private static byte[] AddToByteArray(byte[] input, int add)
|
||||
{
|
||||
int len = input.Length;
|
||||
var bigint = new BigInteger(input.Reverse().ToArray());
|
||||
bigint += add;
|
||||
var arr = bigint.ToByteArray().Reverse().ToArray();
|
||||
|
||||
if (arr.Length < len)
|
||||
{
|
||||
byte[] temp = new byte[len];
|
||||
for (int i = 0; i < (len - arr.Length); i++)
|
||||
temp[i] = 0x00;
|
||||
|
||||
Array.Copy(arr, 0, temp, len - arr.Length, arr.Length);
|
||||
arr = temp;
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static byte[] TakeSixteen(BigInteger input)
|
||||
{
|
||||
var arr = input.ToByteArray().Take(16).Reverse().ToArray();
|
||||
|
||||
if (arr.Length < 16)
|
||||
{
|
||||
byte[] temp = new byte[16];
|
||||
for (int i = 0; i < (16 - arr.Length); i++)
|
||||
temp[i] = 0x00;
|
||||
|
||||
Array.Copy(arr, 0, temp, 16 - arr.Length, arr.Length);
|
||||
arr = temp;
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="BouncyCastle" version="1.8.4" targetFramework="net461" />
|
||||
</packages>
|
||||
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright (c) 2018-2024 Matt Nadareski
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
912
NDecrypt.Core/CIATool.cs
Normal file
912
NDecrypt.Core/CIATool.cs
Normal file
@@ -0,0 +1,912 @@
|
||||
// using System;
|
||||
// using System.IO;
|
||||
// using SabreTools.IO.Extensions;
|
||||
// using SabreTools.Models.N3DS;
|
||||
// using SabreTools.Serialization.Wrappers;
|
||||
// using static NDecrypt.Core.CommonOperations;
|
||||
|
||||
// namespace NDecrypt.Core
|
||||
// {
|
||||
// // https://www.3dbrew.org/wiki/CIA
|
||||
// public class CIATool : ITool
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// Decryption args to use while processing
|
||||
// /// </summary>
|
||||
// private readonly DecryptArgs _decryptArgs;
|
||||
|
||||
// /// <summary>
|
||||
// /// Indicates if development images are expected
|
||||
// /// </summary>
|
||||
// private readonly bool _development;
|
||||
|
||||
// /// <summary>
|
||||
// /// Set of all partition keys
|
||||
// /// </summary>
|
||||
// private readonly PartitionKeys[] KeysMap = new PartitionKeys[8];
|
||||
|
||||
// public CIATool(bool development, DecryptArgs decryptArgs)
|
||||
// {
|
||||
// _development = development;
|
||||
// _decryptArgs = decryptArgs;
|
||||
// }
|
||||
|
||||
// /// <inheritdoc/>
|
||||
// public bool EncryptFile(string filename, bool force)
|
||||
// {
|
||||
// // Ensure the constants are all set
|
||||
// if (_decryptArgs.IsReady != true)
|
||||
// {
|
||||
// Console.WriteLine("Could not read keys. Please make sure the file exists and try again.");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// try
|
||||
// {
|
||||
// // Open the read and write on the same file for inplace processing
|
||||
// using var input = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
// using var output = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
|
||||
// // Deserialize the CIA information
|
||||
// var cia = ReadCIA(input);
|
||||
// if (cia == null)
|
||||
// {
|
||||
// Console.WriteLine("Error: Not a 3DS CIA!");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // Encrypt all 8 NCCH partitions
|
||||
// EncryptAllPartitions(cia, force, input, output);
|
||||
// return false;
|
||||
// }
|
||||
// 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 CIA file and try again.");
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <inheritdoc/>
|
||||
// public bool DecryptFile(string filename, bool force)
|
||||
// {
|
||||
// // Ensure the constants are all set
|
||||
// if (_decryptArgs.IsReady != true)
|
||||
// {
|
||||
// Console.WriteLine("Could not read keys. Please make sure the file exists and try again.");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// try
|
||||
// {
|
||||
// // Open the read and write on the same file for inplace processing
|
||||
// using var input = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
// using var output = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
|
||||
// // Deserialize the CIA information
|
||||
// var cia = ReadCIA(input);
|
||||
// if (cia == null)
|
||||
// {
|
||||
// Console.WriteLine("Error: Not a 3DS CIA!");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // Decrypt all 8 NCCH partitions
|
||||
// DecryptAllPartitions(cia, force, input, output);
|
||||
// return false;
|
||||
// }
|
||||
// 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 CIA file and try again.");
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// #region Decrypt
|
||||
|
||||
// /// <summary>
|
||||
// /// Decrypt all partitions in the content file data of a CIA header
|
||||
// /// </summary>
|
||||
// /// <param name="cia">CIA representing the 3DS CIA file</param>
|
||||
// /// <param name="force">Indicates if the operation should be forced</param>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private void DecryptAllPartitions(SabreTools.Serialization.Wrappers.CIA cia, bool force, Stream input, Stream output)
|
||||
// {
|
||||
// // Check the partitions table
|
||||
// if (cia.Model.Partitions == null)
|
||||
// {
|
||||
// Console.WriteLine("Invalid partitions table!");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Iterate over all 8 NCCH partitions
|
||||
// for (int p = 0; p < cia.Model.Partitions.Length; p++)
|
||||
// {
|
||||
// // Check the partition exists
|
||||
// var header = cia.Model.Partitions[0];
|
||||
// if (header == null)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {p} Not found... Skipping...");
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// // Decrypt the partition, if possible
|
||||
// if (ShouldDecryptPartition(cia, p, force))
|
||||
// DecryptPartition(header, p, input, output);
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Determine if the current partition should be decrypted
|
||||
// /// </summary>
|
||||
// private static bool ShouldDecryptPartition(SabreTools.Serialization.Wrappers.CIA cia, int index, bool force)
|
||||
// {
|
||||
// // If we're forcing the operation, tell the user
|
||||
// if (force)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} is not verified due to force flag being set.");
|
||||
// return true;
|
||||
// }
|
||||
// // If we're not forcing the operation, check if the 'NoCrypto' bit is set
|
||||
// else if (cia.Model.Partitions![index]!.Flags!.PossblyDecrypted())
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index}: Already Decrypted?...");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // By default, it passes
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Decrypt a single partition
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private void DecryptPartition(NCCHHeader header, int index, Stream input, Stream output)
|
||||
// {
|
||||
// // Get the table entry -- TODO: Fix this to get the real entry
|
||||
// var tableEntry = new PartitionTableEntry();
|
||||
|
||||
// // Determine the keys needed for this partition
|
||||
// SetDecryptionKeys(header, index);
|
||||
|
||||
// // Decrypt the parts of the partition
|
||||
// DecryptExtendedHeader(header, index, tableEntry, input, output);
|
||||
// DecryptExeFS(header, index, tableEntry, input, output);
|
||||
// DecryptRomFS(header, index, tableEntry, input, output);
|
||||
|
||||
// // Update the flags
|
||||
// UpdateDecryptCryptoAndMasks(header, tableEntry, output);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Determine the set of keys to be used for decryption
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// private void SetDecryptionKeys(NCCHHeader header, int index)
|
||||
// {
|
||||
// // Get partition-specific values
|
||||
// byte[]? rsaSignature = header.RSA2048Signature;
|
||||
|
||||
// // Set the header to use based on mode
|
||||
// BitMasks masks = header.Flags!.BitMasks;
|
||||
// CryptoMethod method = header.Flags.CryptoMethod;
|
||||
|
||||
// // Get the partition keys
|
||||
// KeysMap[index] = new PartitionKeys(_decryptArgs, rsaSignature, masks, method, _development);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Decrypt the extended header, if it exists
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// /// <param name="tableEntry">PartitionTableEntry header representing the partition</param>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private bool DecryptExtendedHeader(NCCHHeader header,
|
||||
// int index,
|
||||
// PartitionTableEntry tableEntry,
|
||||
// Stream input,
|
||||
// Stream output)
|
||||
// {
|
||||
// // Get required offsets
|
||||
// uint mediaUnitSize = 0x200;
|
||||
// uint partitionOffset = GetPartitionOffset(tableEntry, mediaUnitSize);
|
||||
// if (partitionOffset == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// uint extHeaderSize = GetExtendedHeaderSize(header);
|
||||
// if (extHeaderSize == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} RomFS: No Extended Header... Skipping...");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // Seek to the extended header
|
||||
// input.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
// output.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
|
||||
// Console.WriteLine($"Partition {index} ExeFS: Decrypting: ExHeader");
|
||||
|
||||
// // Create the Plain AES cipher for this partition
|
||||
// var cipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey2C!, header.PlainIV());
|
||||
|
||||
// // Process the extended header
|
||||
// PerformAESOperation(Constants.CXTExtendedDataHeaderLength, cipher, input, output, null);
|
||||
|
||||
// #if NET6_0_OR_GREATER
|
||||
// // In .NET 6.0, this operation is not picked up by the reader, so we have to force it to reload its buffer
|
||||
// input.Seek(0, SeekOrigin.Begin);
|
||||
// #endif
|
||||
// output.Flush();
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Decrypt the ExeFS, if it exists
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// /// <param name="tableEntry">PartitionTableEntry header representing the partition</param>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private bool DecryptExeFS(NCCHHeader header,
|
||||
// int index,
|
||||
// PartitionTableEntry tableEntry,
|
||||
// Stream input,
|
||||
// Stream output)
|
||||
// {
|
||||
// // Validate the ExeFS
|
||||
// uint mediaUnitSize = 0x200;
|
||||
// uint exeFsOffset = GetExeFSOffset(header, tableEntry, mediaUnitSize) - mediaUnitSize;
|
||||
// if (exeFsOffset == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// uint exeFsSize = GetExeFSSize(header, mediaUnitSize);
|
||||
// if (exeFsSize == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // Decrypt the filename table
|
||||
// DecryptExeFSFilenameTable(header, index, tableEntry, input, output);
|
||||
|
||||
// // For all but the original crypto method, process each of the files in the table
|
||||
// if (header.Flags!.CryptoMethod != CryptoMethod.Original)
|
||||
// DecryptExeFSFileEntries(header, index, tableEntry, input, output);
|
||||
|
||||
// // Seek to the ExeFS
|
||||
// input.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
// output.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
|
||||
// // Create the ExeFS AES cipher for this partition
|
||||
// int ctroffsetE = (int)(mediaUnitSize / 0x10);
|
||||
// byte[] exefsIVWithOffset = Add(header.ExeFSIV(), ctroffsetE);
|
||||
// var cipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey2C!, exefsIVWithOffset);
|
||||
|
||||
// // Setup and perform the decryption
|
||||
// PerformAESOperation(exeFsSize - mediaUnitSize,
|
||||
// cipher,
|
||||
// input,
|
||||
// output,
|
||||
// (string s) => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting: {s}"));
|
||||
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Decrypt the ExeFS Filename Table
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// /// <param name="tableEntry">PartitionTableEntry header representing the partition</param>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private void DecryptExeFSFilenameTable(NCCHHeader header,
|
||||
// int index,
|
||||
// PartitionTableEntry tableEntry,
|
||||
// Stream input,
|
||||
// Stream output)
|
||||
// {
|
||||
// // Get ExeFS offset
|
||||
// uint mediaUnitSize = 0x200;
|
||||
// uint exeFsOffset = GetExeFSOffset(header, tableEntry, mediaUnitSize);
|
||||
// if (exeFsOffset == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Seek to the ExeFS header
|
||||
// input.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
// output.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
|
||||
// Console.WriteLine($"Partition {index} ExeFS: Decrypting: ExeFS Filename Table");
|
||||
|
||||
// // Create the ExeFS AES cipher for this partition
|
||||
// var cipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey2C!, header.ExeFSIV());
|
||||
|
||||
// // Process the filename table
|
||||
// PerformAESOperation(mediaUnitSize, cipher, input, output, null);
|
||||
|
||||
// #if NET6_0_OR_GREATER
|
||||
// // In .NET 6.0, this operation is not picked up by the reader, so we have to force it to reload its buffer
|
||||
// input.Seek(0, SeekOrigin.Begin);
|
||||
// #endif
|
||||
// output.Flush();
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Decrypt the ExeFS file entries
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// /// <param name="tableEntry">PartitionTableEntry header representing the partition</param>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private void DecryptExeFSFileEntries(NCCHHeader header,
|
||||
// int index,
|
||||
// PartitionTableEntry tableEntry,
|
||||
// Stream input,
|
||||
// Stream output)
|
||||
// {
|
||||
// // Get ExeFS offset
|
||||
// uint mediaUnitSize = 0x200;
|
||||
// uint exeFsHeaderOffset = GetExeFSOffset(header, tableEntry, mediaUnitSize);
|
||||
// if (exeFsHeaderOffset == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Get to the start of the files
|
||||
// uint exeFsFilesOffset = exeFsHeaderOffset + mediaUnitSize;
|
||||
// input.Seek(exeFsHeaderOffset, SeekOrigin.Begin);
|
||||
// var exefsHeader = SabreTools.Serialization.Deserializers.N3DS.ParseExeFSHeader(input);
|
||||
|
||||
// // If the header failed to read, log and return
|
||||
// if (exefsHeader == null)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} ExeFS header could not be read. Skipping...");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// foreach (var fileHeader in exefsHeader.FileHeaders!)
|
||||
// {
|
||||
// // Only decrypt a file if it's a code binary
|
||||
// if (fileHeader == null || !fileHeader.IsCodeBinary())
|
||||
// continue;
|
||||
|
||||
// // Create the ExeFS AES ciphers for this partition
|
||||
// uint ctroffset = (fileHeader.FileOffset + mediaUnitSize) / 0x10;
|
||||
// byte[] exefsIVWithOffsetForHeader = Add(header.ExeFSIV(), (int)ctroffset);
|
||||
// var firstCipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey!, exefsIVWithOffsetForHeader);
|
||||
// var secondCipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey2C!, exefsIVWithOffsetForHeader);
|
||||
|
||||
// // Seek to the file entry
|
||||
// input.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
// output.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
// // Setup and perform the encryption
|
||||
// uint exeFsSize = GetExeFSSize(header, mediaUnitSize);
|
||||
// PerformAESOperation(exeFsSize,
|
||||
// firstCipher,
|
||||
// secondCipher,
|
||||
// input,
|
||||
// output,
|
||||
// (string s) => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting: {fileHeader.FileName}...{s}"));
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Decrypt the RomFS, if it exists
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// /// <param name="tableEntry">PartitionTableEntry header representing the partition</param>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private bool DecryptRomFS(NCCHHeader header,
|
||||
// int index,
|
||||
// PartitionTableEntry tableEntry,
|
||||
// Stream input,
|
||||
// Stream output)
|
||||
// {
|
||||
// // Validate the RomFS
|
||||
// uint mediaUnitSize = 0x200;
|
||||
// uint romFsOffset = GetRomFSOffset(header, tableEntry, mediaUnitSize);
|
||||
// if (romFsOffset == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// uint romFsSize = GetRomFSSize(header, mediaUnitSize);
|
||||
// if (romFsSize == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // Seek to the RomFS
|
||||
// input.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
// output.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
|
||||
// // Create the RomFS AES cipher for this partition
|
||||
// var cipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey!, header.RomFSIV());
|
||||
|
||||
// // Setup and perform the decryption
|
||||
// PerformAESOperation(romFsSize,
|
||||
// cipher,
|
||||
// input,
|
||||
// output,
|
||||
// (string s) => Console.WriteLine($"\rPartition {index} RomFS: Decrypting: {s}"));
|
||||
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Update the CryptoMethod and BitMasks for the decrypted partition
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="tableEntry">PartitionTableEntry header representing the partition</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private void UpdateDecryptCryptoAndMasks(NCCHHeader header,
|
||||
// PartitionTableEntry tableEntry,
|
||||
// Stream output)
|
||||
// {
|
||||
// // TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value?
|
||||
// uint mediaUnitSize = 0x200; // ncsdHeader.MediaUnitSize;
|
||||
|
||||
// // Write the new CryptoMethod
|
||||
// output.Seek((tableEntry.Offset * mediaUnitSize) + 0x18B, SeekOrigin.Begin);
|
||||
// output.Write((byte)CryptoMethod.Original);
|
||||
// output.Flush();
|
||||
|
||||
// // Write the new BitMasks flag
|
||||
// output.Seek((tableEntry.Offset * mediaUnitSize) + 0x18F, SeekOrigin.Begin);
|
||||
// BitMasks flag = header.Flags!.BitMasks;
|
||||
// flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
|
||||
// flag |= BitMasks.NoCrypto;
|
||||
// output.Write((byte)flag);
|
||||
// output.Flush();
|
||||
// }
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Encrypt
|
||||
|
||||
// /// <summary>
|
||||
// /// Encrypt all partitions in the content file data of a CIA header
|
||||
// /// </summary>
|
||||
// /// <param name="cia">CIA representing the 3DS CIA file</param>
|
||||
// /// <param name="force">Indicates if the operation should be forced</param>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private void EncryptAllPartitions(SabreTools.Serialization.Wrappers.CIA cia, bool force, Stream input, Stream output)
|
||||
// {
|
||||
// // Check the partitions table
|
||||
// if (cia.Model.Partitions == null)
|
||||
// {
|
||||
// Console.WriteLine("Invalid partitions table!");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Iterate over all 8 NCCH partitions
|
||||
// for (int p = 0; p < cia.Model.Partitions.Length; p++)
|
||||
// {
|
||||
// // Check the partition exists
|
||||
// var header = cia.Model.Partitions[0];
|
||||
// if (header == null)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {p} Not found... Skipping...");
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// // Encrypt the partition, if possible
|
||||
// if (ShouldEncryptPartition(cia, p, force))
|
||||
// EncryptPartition(header, p, input, output);
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Determine if the current partition should be encrypted
|
||||
// /// </summary>
|
||||
// private static bool ShouldEncryptPartition(SabreTools.Serialization.Wrappers.CIA cia, int index, bool force)
|
||||
// {
|
||||
// // If we're forcing the operation, tell the user
|
||||
// if (force)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} is not verified due to force flag being set.");
|
||||
// return true;
|
||||
// }
|
||||
// // If we're not forcing the operation, check if the 'NoCrypto' bit is set
|
||||
// else if (!cia.Model.Partitions![index]!.Flags!.PossblyDecrypted())
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index}: Already Encrypted?...");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // By default, it passes
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Encrypt a single partition
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private void EncryptPartition(NCCHHeader header, int index, Stream input, Stream output)
|
||||
// {
|
||||
// // Get the table entry -- TODO: Fix this to get the real entry
|
||||
// var tableEntry = new PartitionTableEntry();
|
||||
|
||||
// // Determine the keys needed for this partition
|
||||
// SetEncryptionKeys(header, index);
|
||||
|
||||
// // Encrypt the parts of the partition
|
||||
// EncryptExtendedHeader(header, index, tableEntry, input, output);
|
||||
// EncryptExeFS(header, index, tableEntry, input, output);
|
||||
// EncryptRomFS(header, index, tableEntry, input, output);
|
||||
|
||||
// // Update the flags
|
||||
// UpdateEncryptCryptoAndMasks(header, index, tableEntry, output);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Determine the set of keys to be used for encryption
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// private void SetEncryptionKeys(NCCHHeader header, int index)
|
||||
// {
|
||||
// // Get partition-specific values
|
||||
// byte[]? rsaSignature = header.RSA2048Signature;
|
||||
|
||||
// // TODO: Figure out what sane defaults for these values are
|
||||
// // TODO: Can we actually re-encrypt a CIA?
|
||||
|
||||
// // Set the header to use based on mode
|
||||
// BitMasks masks = BitMasks.NoCrypto; // ciaHeader.BackupHeader.Flags.BitMasks;
|
||||
// CryptoMethod method = CryptoMethod.Original; // ciaHeader.BackupHeader.Flags.CryptoMethod;
|
||||
|
||||
// // Get the partition keys
|
||||
// KeysMap[index] = new PartitionKeys(_decryptArgs, rsaSignature, masks, method, _development);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Encrypt the extended header, if it exists
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// /// <param name="tableEntry">PartitionTableEntry header representing the partition</param>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private bool EncryptExtendedHeader(NCCHHeader header,
|
||||
// int index,
|
||||
// PartitionTableEntry tableEntry,
|
||||
// Stream input,
|
||||
// Stream output)
|
||||
// {
|
||||
// // Get required offsets
|
||||
// uint mediaUnitSize = 0x200;
|
||||
// uint partitionOffset = GetPartitionOffset(tableEntry, mediaUnitSize);
|
||||
// if (partitionOffset == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// uint extHeaderSize = GetExtendedHeaderSize(header);
|
||||
// if (extHeaderSize == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} RomFS: No Extended Header... Skipping...");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // Seek to the extended header
|
||||
// input.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
// output.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
|
||||
// Console.WriteLine($"Partition {index} ExeFS: Encrypting: ExHeader");
|
||||
|
||||
// // Create the Plain AES cipher for this partition
|
||||
// var cipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey2C!, header.PlainIV());
|
||||
|
||||
// // Process the extended header
|
||||
// PerformAESOperation(Constants.CXTExtendedDataHeaderLength, cipher, input, output, null);
|
||||
|
||||
// #if NET6_0_OR_GREATER
|
||||
// // In .NET 6.0, this operation is not picked up by the reader, so we have to force it to reload its buffer
|
||||
// input.Seek(0, SeekOrigin.Begin);
|
||||
// #endif
|
||||
// output.Flush();
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Encrypt the ExeFS, if it exists
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// /// <param name="tableEntry">PartitionTableEntry header representing the partition</param>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private bool EncryptExeFS(NCCHHeader header,
|
||||
// int index,
|
||||
// PartitionTableEntry tableEntry,
|
||||
// Stream input,
|
||||
// Stream output)
|
||||
// {
|
||||
// // Validate the ExeFS
|
||||
// uint mediaUnitSize = 0x200;
|
||||
// uint exeFsOffset = GetExeFSOffset(header, tableEntry, mediaUnitSize) - mediaUnitSize;
|
||||
// if (exeFsOffset == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// uint exeFsSize = GetExeFSSize(header, mediaUnitSize);
|
||||
// if (exeFsSize == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // TODO: Determine how to figure out the original crypto method, if possible
|
||||
// // For all but the original crypto method, process each of the files in the table
|
||||
// //if (ciaHeader.BackupHeader.Flags.CryptoMethod != CryptoMethod.Original)
|
||||
// // EncryptExeFSFileEntries(header, index, tableEntry, reader, writer);
|
||||
|
||||
// // Encrypt the filename table
|
||||
// EncryptExeFSFilenameTable(header, index, tableEntry, input, output);
|
||||
|
||||
// // Seek to the ExeFS
|
||||
// input.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
// output.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
|
||||
// // Create the ExeFS AES cipher for this partition
|
||||
// int ctroffsetE = (int)(mediaUnitSize / 0x10);
|
||||
// byte[] exefsIVWithOffset = Add(header.ExeFSIV(), ctroffsetE);
|
||||
// var cipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey2C!, exefsIVWithOffset);
|
||||
|
||||
// // Setup and perform the decryption
|
||||
// PerformAESOperation(exeFsSize - mediaUnitSize,
|
||||
// cipher,
|
||||
// input,
|
||||
// output,
|
||||
// (string s) => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting: {s}"));
|
||||
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Encrypt the ExeFS Filename Table
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// /// <param name="tableEntry">PartitionTableEntry header representing the partition</param>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private void EncryptExeFSFilenameTable(NCCHHeader header,
|
||||
// int index,
|
||||
// PartitionTableEntry tableEntry,
|
||||
// Stream input,
|
||||
// Stream output)
|
||||
// {
|
||||
// // Get ExeFS offset
|
||||
// uint mediaUnitSize = 0x200;
|
||||
// uint exeFsOffset = GetExeFSOffset(header, tableEntry, mediaUnitSize);
|
||||
// if (exeFsOffset == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Seek to the ExeFS header
|
||||
// input.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
// output.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
|
||||
// Console.WriteLine($"Partition {index} ExeFS: Encrypting: ExeFS Filename Table");
|
||||
|
||||
// // Create the ExeFS AES cipher for this partition
|
||||
// var cipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey2C!, header.ExeFSIV());
|
||||
|
||||
// // Process the filename table
|
||||
// PerformAESOperation(mediaUnitSize, cipher, input, output, null);
|
||||
|
||||
// #if NET6_0_OR_GREATER
|
||||
// // In .NET 6.0, this operation is not picked up by the reader, so we have to force it to reload its buffer
|
||||
// input.Seek(0, SeekOrigin.Begin);
|
||||
// #endif
|
||||
// output.Flush();
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Encrypt the ExeFS file entries
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// /// <param name="tableEntry">PartitionTableEntry header representing the partition</param>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private void EncryptExeFSFileEntries(NCCHHeader header,
|
||||
// int index,
|
||||
// PartitionTableEntry tableEntry,
|
||||
// Stream input,
|
||||
// Stream output)
|
||||
// {
|
||||
// // Get ExeFS offset
|
||||
// uint mediaUnitSize = 0x200;
|
||||
// uint exeFsHeaderOffset = GetExeFSOffset(header, tableEntry, mediaUnitSize);
|
||||
// if (exeFsHeaderOffset == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Get to the start of the files
|
||||
// uint exeFsFilesOffset = exeFsHeaderOffset + mediaUnitSize;
|
||||
// input.Seek(exeFsHeaderOffset, SeekOrigin.Begin);
|
||||
// var exefsHeader = SabreTools.Serialization.Deserializers.N3DS.ParseExeFSHeader(input);
|
||||
|
||||
// // If the header failed to read, log and return
|
||||
// if (exefsHeader == null)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} ExeFS header could not be read. Skipping...");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// foreach (var fileHeader in exefsHeader.FileHeaders!)
|
||||
// {
|
||||
// // Only decrypt a file if it's a code binary
|
||||
// if (fileHeader == null || !fileHeader.IsCodeBinary())
|
||||
// continue;
|
||||
|
||||
// // Create the ExeFS AES ciphers for this partition
|
||||
// uint ctroffset = (fileHeader.FileOffset + mediaUnitSize) / 0x10;
|
||||
// byte[] exefsIVWithOffsetForHeader = Add(header.ExeFSIV(), (int)ctroffset);
|
||||
// var firstCipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey!, exefsIVWithOffsetForHeader);
|
||||
// var secondCipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey2C!, exefsIVWithOffsetForHeader);
|
||||
|
||||
// // Seek to the file entry
|
||||
// input.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
// output.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
// // Setup and perform the encryption
|
||||
// uint exeFsSize = GetExeFSSize(header, mediaUnitSize);
|
||||
// PerformAESOperation(exeFsSize,
|
||||
// firstCipher,
|
||||
// secondCipher,
|
||||
// input,
|
||||
// output,
|
||||
// (string s) => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting: {fileHeader.FileName}...{s}"));
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Encrypt the RomFS, if it exists
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// /// <param name="tableEntry">PartitionTableEntry header representing the partition</param>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private bool EncryptRomFS(NCCHHeader header,
|
||||
// int index,
|
||||
// PartitionTableEntry tableEntry,
|
||||
// Stream input,
|
||||
// Stream output)
|
||||
// {
|
||||
// // Validate the RomFS
|
||||
// uint mediaUnitSize = 0x200;
|
||||
// uint romFsOffset = GetRomFSOffset(header, tableEntry, mediaUnitSize);
|
||||
// if (romFsOffset == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// uint romFsSize = GetRomFSSize(header, mediaUnitSize);
|
||||
// if (romFsSize == 0)
|
||||
// {
|
||||
// Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // Seek to the RomFS
|
||||
// input.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
// output.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
|
||||
// // Force setting encryption keys for partitions 1 and above
|
||||
// if (index > 0)
|
||||
// {
|
||||
// //var backupHeader = ciaHeader.BackupHeader;
|
||||
// KeysMap[index].SetRomFSValues((BitMasks)0x00);
|
||||
// }
|
||||
|
||||
// // Create the RomFS AES cipher for this partition
|
||||
// var cipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey!, header.RomFSIV());
|
||||
|
||||
// // Setup and perform the decryption
|
||||
// PerformAESOperation(romFsSize,
|
||||
// cipher,
|
||||
// input,
|
||||
// output,
|
||||
// (string s) => Console.WriteLine($"\rPartition {index} RomFS: Encrypting: {s}"));
|
||||
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Update the CryptoMethod and BitMasks for the encrypted partition
|
||||
// /// </summary>
|
||||
// /// <param name="header">NCCH header representing the partition</param>
|
||||
// /// <param name="index">Index of the partition</param>
|
||||
// /// <param name="tableEntry">PartitionTableEntry header representing the partition</param>
|
||||
// /// <param name="output">Stream representing the output</param>
|
||||
// private void UpdateEncryptCryptoAndMasks(NCCHHeader header,
|
||||
// int index,
|
||||
// PartitionTableEntry tableEntry,
|
||||
// Stream output)
|
||||
// {
|
||||
// // TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value?
|
||||
// uint mediaUnitSize = 0x200; // ncsdHeader.MediaUnitSize;
|
||||
|
||||
// // Write the new CryptoMethod
|
||||
// output.Seek((tableEntry.Offset * mediaUnitSize) + 0x18B, SeekOrigin.Begin);
|
||||
|
||||
// // For partitions 1 and up, set crypto-method to 0x00
|
||||
// if (index > 0)
|
||||
// output.Write((byte)CryptoMethod.Original);
|
||||
|
||||
// // TODO: Determine how to figure out the original crypto method, if possible
|
||||
// // If partition 0, restore crypto-method from backup flags
|
||||
// //else
|
||||
// // writer.Write((byte)ciaHeader.BackupHeader.Flags.CryptoMethod);
|
||||
|
||||
// output.Flush();
|
||||
|
||||
// // Write the new BitMasks flag
|
||||
// output.Seek((tableEntry.Offset * mediaUnitSize) + 0x18F, SeekOrigin.Begin);
|
||||
// BitMasks flag = header.Flags!.BitMasks;
|
||||
// flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF;
|
||||
|
||||
// // TODO: Determine how to figure out the original crypto method, if possible
|
||||
// //flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & ciaHeader.BackupHeader.Flags.BitMasks;
|
||||
// output.Write((byte)flag);
|
||||
// output.Flush();
|
||||
// }
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Serialization
|
||||
|
||||
// /// <summary>
|
||||
// /// Read from a stream and get a CIA header, if possible
|
||||
// /// </summary>
|
||||
// /// <param name="input">Stream representing the input</param>
|
||||
// /// <returns>CIA header object, null on error</returns>
|
||||
// private static SabreTools.Serialization.Wrappers.CIA? ReadCIA(Stream input)
|
||||
// => SabreTools.Serialization.Wrappers.CIA.Create(input);
|
||||
|
||||
// #endregion
|
||||
// }
|
||||
// }
|
||||
253
NDecrypt.Core/CommonOperations.cs
Normal file
253
NDecrypt.Core/CommonOperations.cs
Normal file
@@ -0,0 +1,253 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
public static class CommonOperations
|
||||
{
|
||||
#region AES
|
||||
|
||||
/// <summary>
|
||||
/// Create AES decryption cipher and intialize
|
||||
/// </summary>
|
||||
/// <param name="key">Byte array representation of 128-bit encryption key</param>
|
||||
/// <param name="iv">AES initial value for counter</param>
|
||||
/// <returns>Initialized AES cipher</returns>
|
||||
public static IBufferedCipher CreateAESDecryptionCipher(byte[] key, byte[] iv)
|
||||
{
|
||||
if (key.Length != 16)
|
||||
throw new ArgumentOutOfRangeException(nameof(key));
|
||||
|
||||
var keyParam = new KeyParameter(key);
|
||||
var cipher = CipherUtilities.GetCipher("AES/CTR");
|
||||
cipher.Init(forEncryption: false, new ParametersWithIV(keyParam, iv));
|
||||
return cipher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create AES encryption cipher and intialize
|
||||
/// </summary>
|
||||
/// <param name="key">Byte array representation of 128-bit encryption key</param>
|
||||
/// <param name="iv">AES initial value for counter</param>
|
||||
/// <returns>Initialized AES cipher</returns>
|
||||
public static IBufferedCipher CreateAESEncryptionCipher(byte[] key, byte[] iv)
|
||||
{
|
||||
if (key.Length != 16)
|
||||
throw new ArgumentOutOfRangeException(nameof(key));
|
||||
|
||||
var keyParam = new KeyParameter(key);
|
||||
var cipher = CipherUtilities.GetCipher("AES/CTR");
|
||||
cipher.Init(forEncryption: true, new ParametersWithIV(keyParam, iv));
|
||||
return cipher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform an AES operation using an existing cipher
|
||||
/// </summary>
|
||||
public static void PerformAESOperation(uint size,
|
||||
IBufferedCipher cipher,
|
||||
Stream input,
|
||||
Stream output,
|
||||
Action<string>? progress)
|
||||
{
|
||||
// Get MiB-aligned block count and extra byte count
|
||||
int blockCount = (int)((long)size / (1024 * 1024));
|
||||
int extraBytes = (int)((long)size % (1024 * 1024));
|
||||
|
||||
// Process MiB-aligned data
|
||||
if (blockCount > 0)
|
||||
{
|
||||
for (int i = 0; i < blockCount; i++)
|
||||
{
|
||||
byte[] readBytes = input.ReadBytes(1024 * 1024);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
output.Write(processedBytes);
|
||||
output.Flush();
|
||||
progress?.Invoke($"{i} / {blockCount + 1} MB");
|
||||
}
|
||||
}
|
||||
|
||||
// Process additional data
|
||||
if (extraBytes > 0)
|
||||
{
|
||||
byte[] readBytes = input.ReadBytes(extraBytes);
|
||||
byte[] finalBytes = cipher.DoFinal(readBytes);
|
||||
output.Write(finalBytes);
|
||||
output.Flush();
|
||||
}
|
||||
|
||||
progress?.Invoke($"{blockCount + 1} / {blockCount + 1} MB... Done!\r\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform an AES operation using two existing ciphers
|
||||
/// </summary>
|
||||
public static void PerformAESOperation(uint size,
|
||||
IBufferedCipher firstCipher,
|
||||
IBufferedCipher secondCipher,
|
||||
Stream input,
|
||||
Stream output,
|
||||
Action<string> progress)
|
||||
{
|
||||
// Get MiB-aligned block count and extra byte count
|
||||
int blockCount = (int)((long)size / (1024 * 1024));
|
||||
int extraBytes = (int)((long)size % (1024 * 1024));
|
||||
|
||||
// Process MiB-aligned data
|
||||
if (blockCount > 0)
|
||||
{
|
||||
for (int i = 0; i < blockCount; i++)
|
||||
{
|
||||
byte[] readBytes = input.ReadBytes(1024 * 1024);
|
||||
byte[] firstProcessedBytes = firstCipher.ProcessBytes(readBytes);
|
||||
byte[] secondProcessedBytes = secondCipher.ProcessBytes(firstProcessedBytes);
|
||||
output.Write(secondProcessedBytes);
|
||||
output.Flush();
|
||||
progress($"{i} / {blockCount + 1} MB");
|
||||
}
|
||||
}
|
||||
|
||||
// Process additional data
|
||||
if (extraBytes > 0)
|
||||
{
|
||||
byte[] readBytes = input.ReadBytes(extraBytes);
|
||||
byte[] firstFinalBytes = firstCipher.DoFinal(readBytes);
|
||||
byte[] secondFinalBytes = secondCipher.DoFinal(firstFinalBytes);
|
||||
output.Write(secondFinalBytes);
|
||||
output.Flush();
|
||||
}
|
||||
|
||||
progress($"{blockCount + 1} / {blockCount + 1} MB... Done!\r\n");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Byte Arrays
|
||||
|
||||
/// <summary>
|
||||
/// Add an integer value to a number represented by a byte array
|
||||
/// </summary>
|
||||
/// <param name="input">Byte array to add to</param>
|
||||
/// <param name="add">Amount to add</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
public static byte[] Add(byte[] input, uint add)
|
||||
{
|
||||
byte[] addBytes = BitConverter.GetBytes(add);
|
||||
Array.Reverse(addBytes);
|
||||
byte[] paddedBytes = new byte[16];
|
||||
Array.Copy(addBytes, 0, paddedBytes, 12, 4);
|
||||
return Add(input, paddedBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add two numbers represented by byte arrays
|
||||
/// </summary>
|
||||
/// <param name="left">Byte array to add to</param>
|
||||
/// <param name="right">Amount to add</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
public static byte[] Add(byte[] left, byte[] right)
|
||||
{
|
||||
int addBytes = Math.Min(left.Length, right.Length);
|
||||
int outLength = Math.Max(left.Length, right.Length);
|
||||
|
||||
byte[] output = new byte[outLength];
|
||||
|
||||
uint carry = 0;
|
||||
for (int i = addBytes - 1; i >= 0; i--)
|
||||
{
|
||||
uint addValue = (uint)(left[i] + right[i]) + carry;
|
||||
output[i] = (byte)addValue;
|
||||
carry = addValue >> 8;
|
||||
}
|
||||
|
||||
if (outLength != addBytes && left.Length == outLength)
|
||||
Array.Copy(left, addBytes, output, addBytes, outLength - addBytes);
|
||||
else if (outLength != addBytes && right.Length == outLength)
|
||||
Array.Copy(right, addBytes, output, addBytes, outLength - addBytes);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a rotate left on a byte array
|
||||
/// </summary>
|
||||
/// <param name="val">Byte array value to rotate</param>
|
||||
/// <param name="r_bits">Number of bits to rotate</param>
|
||||
/// <returns>Rotated byte array value</returns>
|
||||
public static byte[] RotateLeft(byte[] val, int r_bits)
|
||||
{
|
||||
byte[] output = new byte[val.Length];
|
||||
Array.Copy(val, output, output.Length);
|
||||
|
||||
// Shift by bytes
|
||||
while (r_bits >= 8)
|
||||
{
|
||||
byte temp = output[0];
|
||||
for (int i = 0; i < output.Length - 1; i++)
|
||||
{
|
||||
output[i] = output[i + 1];
|
||||
}
|
||||
|
||||
output[output.Length - 1] = temp;
|
||||
r_bits -= 8;
|
||||
}
|
||||
|
||||
// Shift by bits
|
||||
if (r_bits > 0)
|
||||
{
|
||||
byte bitMask = (byte)(8 - r_bits), carry, wrap = 0;
|
||||
for (int i = 0; i < output.Length; i++)
|
||||
{
|
||||
carry = (byte)((255 << bitMask & output[i]) >> bitMask);
|
||||
|
||||
// Make sure the first byte carries to the end
|
||||
if (i == 0)
|
||||
wrap = carry;
|
||||
|
||||
// Otherwise, move to the last byte
|
||||
else
|
||||
output[i - 1] |= carry;
|
||||
|
||||
// Shift the current bits
|
||||
output[i] <<= r_bits;
|
||||
}
|
||||
|
||||
// Make sure the wrap happens
|
||||
output[output.Length - 1] |= wrap;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// XOR two numbers represented by byte arrays
|
||||
/// </summary>
|
||||
/// <param name="left">Byte array to XOR to</param>
|
||||
/// <param name="right">Amount to XOR</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
public static byte[] Xor(byte[] left, byte[] right)
|
||||
{
|
||||
int xorBytes = Math.Min(left.Length, right.Length);
|
||||
int outLength = Math.Max(left.Length, right.Length);
|
||||
|
||||
byte[] output = new byte[outLength];
|
||||
for (int i = 0; i < xorBytes; i++)
|
||||
{
|
||||
output[i] = (byte)(left[i] ^ right[i]);
|
||||
}
|
||||
|
||||
if (outLength != xorBytes && left.Length == outLength)
|
||||
Array.Copy(left, xorBytes, output, xorBytes, outLength - xorBytes);
|
||||
else if (outLength != xorBytes && right.Length == outLength)
|
||||
Array.Copy(right, xorBytes, output, xorBytes, outLength - xorBytes);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
732
NDecrypt.Core/DSTool.cs
Normal file
732
NDecrypt.Core/DSTool.cs
Normal file
@@ -0,0 +1,732 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.Nitro;
|
||||
using NitroDeserializer = SabreTools.Serialization.Deserializers.Nitro;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
public class DSTool : ITool
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private static readonly byte[] NitroEncryptionData =
|
||||
[
|
||||
0x99,0xD5,0x20,0x5F,0x57,0x44,0xF5,0xB9,0x6E,0x19,0xA4,0xD9,0x9E,0x6A,0x5A,0x94,
|
||||
0xD8,0xAE,0xF1,0xEB,0x41,0x75,0xE2,0x3A,0x93,0x82,0xD0,0x32,0x33,0xEE,0x31,0xD5,
|
||||
0xCC,0x57,0x61,0x9A,0x37,0x06,0xA2,0x1B,0x79,0x39,0x72,0xF5,0x55,0xAE,0xF6,0xBE,
|
||||
0x5F,0x1B,0x69,0xFB,0xE5,0x9D,0xF1,0xE9,0xCE,0x2C,0xD9,0xA1,0x5E,0x32,0x05,0xE6,
|
||||
0xFE,0xD3,0xFE,0xCF,0xD4,0x62,0x04,0x0D,0x8B,0xF5,0xEC,0xB7,0x2B,0x60,0x79,0xBB,
|
||||
0x12,0x95,0x31,0x0D,0x6E,0x3F,0xDA,0x2B,0x88,0x84,0xF0,0xF1,0x3D,0x12,0x7E,0x25,
|
||||
0x45,0x22,0xF1,0xBB,0x24,0x06,0x1A,0x06,0x11,0xAD,0xDF,0x28,0x8B,0x64,0x81,0x34,
|
||||
0x2B,0xEB,0x33,0x29,0x99,0xAA,0xF2,0xBD,0x9C,0x14,0x95,0x9D,0x9F,0xF7,0xF5,0x8C,
|
||||
0x72,0x97,0xA1,0x29,0x9D,0xD1,0x5F,0xCF,0x66,0x4D,0x07,0x1A,0xDE,0xD3,0x4A,0x4B,
|
||||
0x85,0xC9,0xA7,0xA3,0x17,0x95,0x05,0x3A,0x3D,0x49,0x0A,0xBF,0x0A,0x89,0x8B,0xA2,
|
||||
0x4A,0x82,0x49,0xDD,0x27,0x90,0xF1,0x0B,0xE9,0xEB,0x1C,0x6A,0x83,0x76,0x45,0x05,
|
||||
0xBA,0x81,0x70,0x61,0x17,0x3F,0x4B,0xDE,0xAE,0xCF,0xAB,0x39,0x57,0xF2,0x3A,0x56,
|
||||
0x48,0x11,0xAD,0x8A,0x40,0xE1,0x45,0x3F,0xFA,0x9B,0x02,0x54,0xCA,0xA6,0x93,0xFB,
|
||||
0xEF,0x4D,0xFE,0x6F,0xA3,0xD8,0x87,0x9C,0x08,0xBA,0xD5,0x48,0x6A,0x8D,0x2D,0xFD,
|
||||
0x6E,0x15,0xF8,0x74,0xBD,0xBE,0x52,0x8B,0x18,0x22,0x8A,0x9E,0xFB,0x74,0x37,0x07,
|
||||
0x1B,0x36,0x6C,0x4A,0x19,0xBA,0x42,0x62,0xB9,0x79,0x91,0x10,0x7B,0x67,0x65,0x96,
|
||||
0xFE,0x02,0x23,0xE8,0xEE,0x99,0x8C,0x77,0x3E,0x5C,0x86,0x64,0x4D,0x6D,0x78,0x86,
|
||||
0xA5,0x4F,0x65,0xE2,0x1E,0xB2,0xDF,0x5A,0x0A,0xD0,0x7E,0x08,0x14,0xB0,0x71,0xAC,
|
||||
0xBD,0xDB,0x83,0x1C,0xB9,0xD7,0xA1,0x62,0xCD,0xC6,0x63,0x7C,0x52,0x69,0xC3,0xE6,
|
||||
0xBF,0x75,0xCE,0x12,0x44,0x5D,0x21,0x04,0xFA,0xFB,0xD3,0x3C,0x38,0x11,0x63,0xD4,
|
||||
0x95,0x85,0x41,0x49,0x46,0x09,0xF2,0x08,0x43,0x11,0xDC,0x1F,0x76,0xC0,0x15,0x6D,
|
||||
0x1F,0x3C,0x63,0x70,0xEA,0x87,0x80,0x6C,0xC3,0xBD,0x63,0x8B,0xC2,0x37,0x21,0x37,
|
||||
0xDC,0xEE,0x09,0x23,0x2E,0x37,0x6A,0x4D,0x73,0x90,0xF7,0x50,0x30,0xAC,0x1C,0x92,
|
||||
0x04,0x10,0x23,0x91,0x4F,0xD2,0x07,0xAA,0x68,0x3E,0x4F,0x9A,0xC9,0x64,0x60,0x6A,
|
||||
0xC8,0x14,0x21,0xF3,0xD6,0x22,0x41,0x12,0x44,0x24,0xCF,0xE6,0x8A,0x56,0xDD,0x0D,
|
||||
0x53,0x4D,0xE1,0x85,0x1E,0x8C,0x52,0x5A,0x9C,0x19,0x84,0xC2,0x03,0x57,0xF1,0x6F,
|
||||
0xE3,0x00,0xBE,0x58,0xF6,0x4C,0xED,0xD5,0x21,0x64,0x9C,0x1F,0xBE,0x55,0x03,0x3C,
|
||||
0x4A,0xDC,0xFF,0xAA,0xC9,0xDA,0xE0,0x5D,0x5E,0xBF,0xE6,0xDE,0xF5,0xD8,0xB1,0xF8,
|
||||
0xFF,0x36,0xB3,0xB9,0x62,0x67,0x95,0xDB,0x31,0x5F,0x37,0xED,0x4C,0x70,0x67,0x99,
|
||||
0x90,0xB5,0x18,0x31,0x6C,0x3D,0x99,0x99,0xE4,0x42,0xDA,0xD3,0x25,0x42,0x13,0xA0,
|
||||
0xAE,0xD7,0x70,0x6C,0xB1,0x55,0xCF,0xC7,0xD7,0x46,0xD5,0x43,0x61,0x17,0x3D,0x44,
|
||||
0x28,0xE9,0x33,0x85,0xD5,0xD0,0xA2,0x93,0xAA,0x25,0x12,0x1F,0xFB,0xC5,0x0B,0x46,
|
||||
0xF5,0x97,0x76,0x56,0x45,0xA6,0xBE,0x87,0xB1,0x94,0x6B,0xE8,0xB1,0xFE,0x33,0x99,
|
||||
0xAE,0x1F,0x3E,0x6C,0x39,0x71,0x1D,0x09,0x00,0x90,0x37,0xE4,0x10,0x3E,0x75,0x74,
|
||||
0xFF,0x8C,0x83,0x3B,0xB0,0xF1,0xB0,0xF9,0x01,0x05,0x47,0x42,0x95,0xF1,0xD6,0xAC,
|
||||
0x7E,0x38,0xE6,0x9E,0x95,0x74,0x26,0x3F,0xB4,0x68,0x50,0x18,0xD0,0x43,0x30,0xB4,
|
||||
0x4C,0x4B,0xE3,0x68,0xBF,0xE5,0x4D,0xB6,0x95,0x8B,0x0A,0xA0,0x74,0x25,0x32,0x77,
|
||||
0xCF,0xA1,0xF7,0x2C,0xD8,0x71,0x13,0x5A,0xAB,0xEA,0xC9,0x51,0xE8,0x0D,0xEE,0xEF,
|
||||
0xE9,0x93,0x7E,0x19,0xA7,0x1E,0x43,0x38,0x81,0x16,0x2C,0xA1,0x48,0xE3,0x73,0xCC,
|
||||
0x29,0x21,0x6C,0xD3,0x5D,0xCE,0xA0,0xD9,0x61,0x71,0x43,0xA0,0x15,0x13,0xB5,0x64,
|
||||
0x92,0xCF,0x2A,0x19,0xDC,0xAD,0xB7,0xA5,0x9F,0x86,0x65,0xF8,0x1A,0x9F,0xE7,0xFB,
|
||||
0xF7,0xFD,0xB8,0x13,0x6C,0x27,0xDB,0x6F,0xDF,0x35,0x1C,0xF7,0x8D,0x2C,0x5B,0x9B,
|
||||
0x12,0xAB,0x38,0x64,0x06,0xCC,0xDE,0x31,0xE8,0x4E,0x75,0x11,0x64,0xE3,0xFA,0xEA,
|
||||
0xEB,0x34,0x54,0xC2,0xAD,0x3F,0x34,0xEB,0x93,0x2C,0x7D,0x26,0x36,0x9D,0x56,0xF3,
|
||||
0x5A,0xE1,0xF6,0xB3,0x98,0x63,0x4A,0x9E,0x32,0x83,0xE4,0x9A,0x84,0x60,0x7D,0x90,
|
||||
0x2E,0x13,0x0E,0xEE,0x93,0x4B,0x36,0xA2,0x85,0xEC,0x16,0x38,0xE8,0x88,0x06,0x02,
|
||||
0xBF,0xF0,0xA0,0x3A,0xED,0xD7,0x6A,0x9A,0x73,0xE1,0x57,0xCF,0xF8,0x44,0xB8,0xDC,
|
||||
0x2E,0x23,0x59,0xD1,0xDF,0x95,0x52,0x71,0x99,0x61,0xA0,0x4B,0xD5,0x7F,0x6E,0x78,
|
||||
0xBA,0xA9,0xC5,0x30,0xD3,0x40,0x86,0x32,0x9D,0x32,0x0C,0x9C,0x37,0xB7,0x02,0x2F,
|
||||
0xBA,0x54,0x98,0xA9,0xC4,0x13,0x04,0xC9,0x8D,0xBE,0xC8,0xE7,0x5D,0x97,0x50,0x2E,
|
||||
0x93,0xD6,0x22,0x59,0x0C,0x27,0xBC,0x22,0x92,0xE0,0xA7,0x20,0x0F,0x93,0x6F,0x7F,
|
||||
0x4C,0x9F,0xD3,0xB5,0xA6,0x2A,0x0B,0x74,0x67,0x49,0x7D,0x10,0x26,0xCB,0xD1,0xC5,
|
||||
0x86,0x71,0xE7,0x8C,0xA0,0x9C,0xE9,0x5B,0xB2,0x1A,0xF6,0x01,0xEE,0x8C,0x9E,0x5E,
|
||||
0x83,0xF2,0x1A,0xDB,0xE6,0xE5,0xEA,0x84,0x59,0x76,0xD2,0x7C,0xF6,0x8D,0xA5,0x49,
|
||||
0x36,0x48,0xC2,0x16,0x52,0xBB,0x83,0xA3,0x74,0xB9,0x07,0x0C,0x3B,0xFF,0x61,0x28,
|
||||
0xE1,0x61,0xE9,0xE4,0xEF,0x6E,0x15,0xAA,0x4E,0xBA,0xE8,0x5D,0x05,0x96,0xBB,0x32,
|
||||
0x56,0xB0,0xFB,0x72,0x52,0x0F,0x0E,0xC8,0x42,0x25,0x65,0x76,0x89,0xAF,0xF2,0xDE,
|
||||
0x10,0x27,0xF0,0x01,0x4B,0x74,0xA7,0x97,0x07,0xD5,0x26,0x54,0x54,0x09,0x1F,0x82,
|
||||
0x0A,0x86,0x7D,0x30,0x39,0x0E,0xB3,0x26,0x9B,0x0B,0x57,0xBB,0x36,0x06,0x31,0xAF,
|
||||
0xFD,0x79,0xFC,0xD9,0x30,0x10,0x2B,0x0C,0xB3,0xE1,0x9B,0xD7,0x7B,0xDC,0x5F,0xEF,
|
||||
0xD2,0xF8,0x13,0x45,0x4D,0x47,0x75,0xBD,0x46,0x96,0x3C,0x7E,0x75,0xF3,0x3E,0xB5,
|
||||
0x67,0xC5,0x9A,0x3B,0xB0,0x5B,0x29,0x6B,0xDE,0x80,0x5B,0xC8,0x15,0x05,0xB1,0x31,
|
||||
0xB6,0xCE,0x49,0xDD,0xAD,0x84,0xB5,0xAE,0x60,0xDC,0x67,0x31,0x34,0x30,0xFE,0x4E,
|
||||
0xBD,0x80,0x2F,0xA6,0xBF,0x63,0x39,0x21,0x86,0xD9,0x35,0x7F,0x16,0x68,0x22,0x05,
|
||||
0x54,0xE9,0x90,0x26,0x8C,0x07,0x6C,0x51,0xA4,0x31,0x55,0xD7,0x09,0x07,0xA8,0x3E,
|
||||
0x2E,0x53,0x66,0xC1,0xF8,0xF2,0x7B,0xC4,0xF2,0x58,0xCF,0xF1,0x87,0xC5,0xA2,0xE7,
|
||||
0x27,0x8F,0x30,0x87,0x58,0xA0,0x64,0x62,0x23,0x18,0xB9,0x88,0x7C,0xFA,0xCE,0xC4,
|
||||
0x98,0xAE,0xAD,0x17,0xCC,0x4A,0x5B,0xF3,0xE9,0x48,0xD5,0x56,0xD3,0x0D,0xF2,0xC8,
|
||||
0x92,0x73,0x8C,0xDB,0xD7,0x2F,0x56,0xAC,0x81,0xF9,0x92,0x69,0x4D,0xC6,0x32,0xF6,
|
||||
0xE6,0xC0,0x8D,0x21,0xE2,0x76,0x80,0x61,0x11,0xBC,0xDC,0x6C,0x93,0xAF,0x19,0x69,
|
||||
0x9B,0xD0,0xBF,0xB9,0x31,0x9F,0x02,0x67,0xA3,0x51,0xEE,0x83,0x06,0x22,0x7B,0x0C,
|
||||
0xAB,0x49,0x42,0x40,0xB8,0xD5,0x01,0x7D,0xCE,0x5E,0xF7,0x55,0x53,0x39,0xC5,0x99,
|
||||
0x46,0xD8,0x87,0x9F,0xBA,0xF7,0x64,0xB4,0xE3,0x9A,0xFA,0xA1,0x6D,0x90,0x68,0x10,
|
||||
0x30,0xCA,0x8A,0x54,0xA7,0x9F,0x60,0xC3,0x19,0xF5,0x6B,0x0D,0x7A,0x51,0x98,0xE6,
|
||||
0x98,0x43,0x51,0xB4,0xD6,0x35,0xE9,0x4F,0xC3,0xDF,0x0F,0x7B,0xD6,0x2F,0x5C,0xBD,
|
||||
0x3A,0x15,0x61,0x19,0xF1,0x4B,0xCB,0xAA,0xDC,0x6D,0x64,0xC9,0xD3,0xC6,0x1E,0x56,
|
||||
0xEF,0x38,0x4C,0x50,0x71,0x86,0x75,0xCC,0x0D,0x0D,0x4E,0xE9,0x28,0xF6,0x06,0x5D,
|
||||
0x70,0x1B,0xAA,0xD3,0x45,0xCF,0xA8,0x39,0xAC,0x95,0xA6,0x2E,0xB4,0xE4,0x22,0xD4,
|
||||
0x74,0xA8,0x37,0x5F,0x48,0x7A,0x04,0xCC,0xA5,0x4C,0x40,0xD8,0x28,0xB4,0x28,0x08,
|
||||
0x0D,0x1C,0x72,0x52,0x41,0xF0,0x7D,0x47,0x19,0x3A,0x53,0x4E,0x58,0x84,0x62,0x6B,
|
||||
0x93,0xB5,0x8A,0x81,0x21,0x4E,0x0D,0xDC,0xB4,0x3F,0xA2,0xC6,0xFC,0xC9,0x2B,0x40,
|
||||
0xDA,0x38,0x04,0xE9,0x5E,0x5A,0x86,0x6B,0x0C,0x22,0x25,0x85,0x68,0x11,0x8D,0x7C,
|
||||
0x92,0x1D,0x95,0x55,0x4D,0xAB,0x8E,0xBB,0xDA,0xA6,0xE6,0xB7,0x51,0xB6,0x32,0x5A,
|
||||
0x05,0x41,0xDD,0x05,0x2A,0x0A,0x56,0x50,0x91,0x17,0x47,0xCC,0xC9,0xE6,0x7E,0xB5,
|
||||
0x61,0x4A,0xDB,0x73,0x67,0x51,0xC8,0x33,0xF5,0xDA,0x6E,0x74,0x2E,0x54,0xC3,0x37,
|
||||
0x0D,0x6D,0xAF,0x08,0xE8,0x15,0x8A,0x5F,0xE2,0x59,0x21,0xCD,0xA8,0xDE,0x0C,0x06,
|
||||
0x5A,0x77,0x6B,0x5F,0xDB,0x18,0x65,0x3E,0xC8,0x50,0xDE,0x78,0xE0,0xB8,0x82,0xB3,
|
||||
0x5D,0x4E,0x72,0x32,0x07,0x4F,0xC1,0x34,0x23,0xBA,0x96,0xB7,0x67,0x4E,0xA4,0x28,
|
||||
0x1E,0x34,0x62,0xEB,0x2D,0x6A,0x70,0xE9,0x2F,0x42,0xC4,0x70,0x4E,0x5A,0x31,0x9C,
|
||||
0xF9,0x5B,0x47,0x28,0xAA,0xDA,0x71,0x6F,0x38,0x1F,0xB3,0x78,0xC4,0x92,0x6B,0x1C,
|
||||
0x9E,0xF6,0x35,0x9A,0xB7,0x4D,0x0E,0xBF,0xCC,0x18,0x29,0x41,0x03,0x48,0x35,0x5D,
|
||||
0x55,0xD0,0x2B,0xC6,0x29,0xAF,0x5C,0x60,0x74,0x69,0x8E,0x5E,0x9B,0x7C,0xD4,0xBD,
|
||||
0x7B,0x44,0x64,0x7D,0x3F,0x92,0x5D,0x69,0xB6,0x1F,0x00,0x4B,0xD4,0x83,0x35,0xCF,
|
||||
0x7E,0x64,0x4E,0x17,0xAE,0x8D,0xD5,0x2E,0x9A,0x28,0x12,0x4E,0x2E,0x2B,0x49,0x08,
|
||||
0x5C,0xAE,0xC6,0x46,0x85,0xAE,0x41,0x61,0x1E,0x6F,0x82,0xD2,0x51,0x37,0x16,0x1F,
|
||||
0x0B,0xF6,0x59,0xA4,0x9A,0xCA,0x5A,0xAF,0x0D,0xD4,0x33,0x8B,0x20,0x63,0xF1,0x84,
|
||||
0x80,0x5C,0xCB,0xCF,0x08,0xB4,0xB9,0xD3,0x16,0x05,0xBD,0x62,0x83,0x31,0x9B,0x56,
|
||||
0x51,0x98,0x9F,0xBA,0xB2,0x5B,0xAA,0xB2,0x22,0x6B,0x2C,0xB5,0xD4,0x48,0xFA,0x63,
|
||||
0x2B,0x5F,0x58,0xFA,0x61,0xFA,0x64,0x09,0xBB,0x38,0xE0,0xB8,0x9D,0x92,0x60,0xA8,
|
||||
0x0D,0x67,0x6F,0x0E,0x37,0xF5,0x0D,0x01,0x9F,0xC2,0x77,0xD4,0xFE,0xEC,0xF1,0x73,
|
||||
0x30,0x39,0xE0,0x7D,0xF5,0x61,0x98,0xE4,0x2C,0x28,0x55,0x04,0x56,0x55,0xDB,0x2F,
|
||||
0x6B,0xEC,0xE5,0x58,0x06,0xB6,0x64,0x80,0x6A,0x2A,0x1A,0x4E,0x5B,0x0F,0xD8,0xC4,
|
||||
0x0A,0x2E,0x52,0x19,0xD9,0x62,0xF5,0x30,0x48,0xBE,0x8C,0x7B,0x4F,0x38,0x9B,0xA2,
|
||||
0xC3,0xAF,0xC9,0xD3,0xC7,0xC1,0x62,0x41,0x86,0xB9,0x61,0x21,0x57,0x6F,0x99,0x4F,
|
||||
0xC1,0xBA,0xCE,0x7B,0xB5,0x3B,0x4D,0x5E,0x8A,0x8B,0x44,0x57,0x5F,0x13,0x5F,0x70,
|
||||
0x6D,0x5B,0x29,0x47,0xDC,0x38,0xE2,0xEC,0x04,0x55,0x65,0x12,0x2A,0xE8,0x17,0x43,
|
||||
0xE1,0x8E,0xDD,0x2A,0xB3,0xE2,0x94,0xF7,0x09,0x6E,0x5C,0xE6,0xEB,0x8A,0xF8,0x6D,
|
||||
0x89,0x49,0x54,0x48,0xF5,0x2F,0xAD,0xBF,0xEA,0x94,0x4B,0xCA,0xFC,0x39,0x87,0x82,
|
||||
0x5F,0x8A,0x01,0xF2,0x75,0xF2,0xE6,0x71,0xD6,0xD8,0x42,0xDE,0xF1,0x2D,0x1D,0x28,
|
||||
0xA6,0x88,0x7E,0xA3,0xA0,0x47,0x1D,0x30,0xD9,0xA3,0x71,0xDF,0x49,0x1C,0xCB,0x01,
|
||||
0xF8,0x36,0xB1,0xF2,0xF0,0x22,0x58,0x5D,0x45,0x6B,0xBD,0xA0,0xBB,0xB2,0x88,0x42,
|
||||
0xC7,0x8C,0x28,0xCE,0x93,0xE8,0x90,0x63,0x08,0x90,0x7C,0x89,0x3C,0xF5,0x7D,0xB7,
|
||||
0x04,0x2D,0x4F,0x55,0x51,0x16,0xFD,0x7E,0x79,0xE8,0xBE,0xC1,0xF2,0x12,0xD4,0xF8,
|
||||
0xB4,0x84,0x05,0x23,0xA0,0xCC,0xD2,0x2B,0xFD,0xE1,0xAB,0xAD,0x0D,0xD1,0x55,0x6C,
|
||||
0x23,0x41,0x94,0x4D,0x77,0x37,0x4F,0x05,0x28,0x0C,0xBF,0x17,0xB3,0x12,0x67,0x6C,
|
||||
0x8C,0xC3,0x5A,0xF7,0x41,0x84,0x2A,0x6D,0xD0,0x94,0x12,0x27,0x2C,0xB4,0xED,0x9C,
|
||||
0x4D,0xEC,0x47,0x82,0x97,0xD5,0x67,0xB9,0x1B,0x9D,0xC0,0x55,0x07,0x7E,0xE5,0x8E,
|
||||
0xE2,0xA8,0xE7,0x3E,0x12,0xE4,0x0E,0x3A,0x2A,0x45,0x55,0x34,0xA2,0xF9,0x2D,0x5A,
|
||||
0x1B,0xAB,0x52,0x7C,0x83,0x10,0x5F,0x55,0xD2,0xF1,0x5A,0x43,0x2B,0xC6,0xA7,0xA4,
|
||||
0x89,0x15,0x95,0xE8,0xB4,0x4B,0x9D,0xF8,0x75,0xE3,0x9F,0x60,0x78,0x5B,0xD6,0xE6,
|
||||
0x0D,0x44,0xE6,0x21,0x06,0xBD,0x47,0x22,0x53,0xA4,0x00,0xAD,0x8D,0x43,0x13,0x85,
|
||||
0x39,0xF7,0xAA,0xFC,0x38,0xAF,0x7B,0xED,0xFC,0xE4,0x2B,0x54,0x50,0x98,0x4C,0xFC,
|
||||
0x85,0x80,0xF7,0xDF,0x3C,0x80,0x22,0xE1,0x94,0xDA,0xDE,0x24,0xC6,0xB0,0x7A,0x39,
|
||||
0x38,0xDC,0x0F,0xA1,0xA7,0xF4,0xF9,0x6F,0x63,0x18,0x57,0x8B,0x84,0x41,0x2A,0x2E,
|
||||
0xD4,0x53,0xF2,0xD9,0x00,0x0F,0xD0,0xDD,0x99,0x6E,0x19,0xA6,0x0A,0xD0,0xEC,0x5B,
|
||||
0x58,0x24,0xAB,0xC0,0xCB,0x06,0x65,0xEC,0x1A,0x13,0x38,0x94,0x0A,0x67,0x03,0x2F,
|
||||
0x3F,0xF7,0xE3,0x77,0x44,0x77,0x33,0xC6,0x14,0x39,0xD0,0xE3,0xC0,0xA2,0x08,0x79,
|
||||
0xBB,0x40,0x99,0x57,0x41,0x0B,0x01,0x90,0xCD,0xE1,0xCC,0x48,0x67,0xDB,0xB3,0xAF,
|
||||
0x88,0x74,0xF3,0x4C,0x82,0x8F,0x72,0xB1,0xB5,0x23,0x29,0xC4,0x12,0x6C,0x19,0xFC,
|
||||
0x8E,0x46,0xA4,0x9C,0xC4,0x25,0x65,0x87,0xD3,0x6D,0xBE,0x8A,0x93,0x11,0x03,0x38,
|
||||
0xED,0x83,0x2B,0xF3,0x46,0xA4,0x93,0xEA,0x3B,0x53,0x85,0x1D,0xCE,0xD4,0xF1,0x08,
|
||||
0x83,0x27,0xED,0xFC,0x9B,0x1A,0x18,0xBC,0xF9,0x8B,0xAE,0xDC,0x24,0xAB,0x50,0x38,
|
||||
0xE9,0x72,0x4B,0x10,0x22,0x17,0x7B,0x46,0x5D,0xAB,0x59,0x64,0xF3,0x40,0xAE,0xF8,
|
||||
0xBB,0xE5,0xC8,0xF9,0x26,0x03,0x4E,0x55,0x7D,0xEB,0xEB,0xFE,0xF7,0x39,0xE6,0xE0,
|
||||
0x0A,0x11,0xBE,0x2E,0x28,0xFF,0x98,0xED,0xC0,0xC9,0x42,0x56,0x42,0xC3,0xFD,0x00,
|
||||
0xF6,0xAF,0x87,0xA2,0x5B,0x01,0x3F,0x32,0x92,0x47,0x95,0x9A,0x72,0xA5,0x32,0x3D,
|
||||
0xAE,0x6B,0xD0,0x9B,0x07,0xD2,0x49,0x92,0xE3,0x78,0x4A,0xFA,0xA1,0x06,0x7D,0xF2,
|
||||
0x41,0xCF,0x77,0x74,0x04,0x14,0xB2,0x0C,0x86,0x84,0x64,0x16,0xD5,0xBB,0x51,0xA1,
|
||||
0xE5,0x6F,0xF1,0xD1,0xF2,0xE2,0xF7,0x5F,0x58,0x20,0x4D,0xB8,0x57,0xC7,0xCF,0xDD,
|
||||
0xC5,0xD8,0xBE,0x76,0x3D,0xF6,0x5F,0x7E,0xE7,0x2A,0x8B,0x88,0x24,0x1B,0x38,0x3F,
|
||||
0x0E,0x41,0x23,0x77,0xF5,0xF0,0x4B,0xD4,0x0C,0x1F,0xFA,0xA4,0x0B,0x80,0x5F,0xCF,
|
||||
0x45,0xF6,0xE0,0xDA,0x2F,0x34,0x59,0x53,0xFB,0x20,0x3C,0x52,0x62,0x5E,0x35,0xB5,
|
||||
0x62,0xFE,0x8B,0x60,0x63,0xE3,0x86,0x5A,0x15,0x1A,0x6E,0xD1,0x47,0x45,0xBC,0x32,
|
||||
0xB4,0xEB,0x67,0x38,0xAB,0xE4,0x6E,0x33,0x3A,0xB5,0xED,0xA3,0xAD,0x67,0xE0,0x4E,
|
||||
0x41,0x95,0xEE,0x62,0x62,0x71,0x26,0x1D,0x31,0xEF,0x62,0x30,0xAF,0xD7,0x82,0xAC,
|
||||
0xC2,0xDC,0x05,0x04,0xF5,0x97,0x07,0xBF,0x11,0x59,0x23,0x07,0xC0,0x64,0x02,0xE8,
|
||||
0x97,0xE5,0x3E,0xAF,0x18,0xAC,0x59,0xA6,0x8B,0x4A,0x33,0x90,0x1C,0x6E,0x7C,0x9C,
|
||||
0x20,0x7E,0x4C,0x3C,0x3E,0x61,0x64,0xBB,0xC5,0x6B,0x7C,0x7E,0x3E,0x9F,0xC5,0x4C,
|
||||
0x9F,0xEA,0x73,0xF5,0xD7,0x89,0xC0,0x4C,0xF4,0xFB,0xF4,0x2D,0xEC,0x14,0x1B,0x51,
|
||||
0xD5,0xC1,0x12,0xC8,0x10,0xDF,0x0B,0x4A,0x8B,0x9C,0xBC,0x93,0x45,0x6A,0x3E,0x3E,
|
||||
0x7D,0xC1,0xA9,0xBA,0xCD,0xC1,0xB4,0x07,0xE4,0xE1,0x68,0x86,0x43,0xB2,0x6D,0x38,
|
||||
0xF3,0xFB,0x0C,0x5C,0x66,0x37,0x71,0xDE,0x56,0xEF,0x6E,0xA0,0x10,0x40,0x65,0xA7,
|
||||
0x98,0xF7,0xD0,0xBE,0x0E,0xC8,0x37,0x36,0xEC,0x10,0xCA,0x7C,0x9C,0xAB,0x84,0x1E,
|
||||
0x05,0x17,0x76,0x02,0x1C,0x4F,0x52,0xAA,0x5F,0xC1,0xC6,0xA0,0x56,0xB9,0xD8,0x04,
|
||||
0x84,0x44,0x4D,0xA7,0x59,0xD8,0xDE,0x60,0xE6,0x38,0x0E,0x05,0x8F,0x03,0xE1,0x3B,
|
||||
0x6D,0x81,0x04,0x33,0x6F,0x30,0x0B,0xCE,0x69,0x05,0x21,0x33,0xFB,0x26,0xBB,0x89,
|
||||
0x7D,0xB6,0xAE,0x87,0x7E,0x51,0x07,0xE0,0xAC,0xF7,0x96,0x0A,0x6B,0xF9,0xC4,0x5C,
|
||||
0x1D,0xE4,0x44,0x47,0xB8,0x5E,0xFA,0xE3,0x78,0x84,0x55,0x42,0x4B,0x48,0x5E,0xF7,
|
||||
0x7D,0x47,0x35,0x86,0x1D,0x2B,0x43,0x05,0x03,0xEC,0x8A,0xB8,0x1E,0x06,0x3C,0x76,
|
||||
0x0C,0x48,0x1A,0x43,0xA7,0xB7,0x8A,0xED,0x1E,0x13,0xC6,0x43,0xEE,0x10,0xEF,0xDB,
|
||||
0xEC,0xFB,0x3C,0x83,0xB2,0x95,0x44,0xEF,0xD8,0x54,0x51,0x4E,0x2D,0x11,0x44,0x1D,
|
||||
0xFB,0x36,0x59,0x1E,0x7A,0x34,0xC1,0xC3,0xCA,0x57,0x00,0x61,0xEA,0x67,0xA5,0x16,
|
||||
0x9B,0x55,0xD0,0x55,0xE1,0x7F,0xD9,0x36,0xD2,0x40,0x76,0xAE,0xDC,0x01,0xCE,0xB0,
|
||||
0x7A,0x83,0xD5,0xCB,0x20,0x98,0xEC,0x6B,0xC1,0x72,0x92,0x34,0xF3,0x82,0x57,0x37,
|
||||
0x62,0x8A,0x32,0x36,0x0C,0x90,0x43,0xAE,0xAE,0x5C,0x9B,0x78,0x8E,0x13,0x65,0x02,
|
||||
0xFD,0x68,0x71,0xC1,0xFE,0xB0,0x31,0xA0,0x24,0x82,0xB0,0xC3,0xB1,0x79,0x69,0xA7,
|
||||
0xF5,0xD2,0xEB,0xD0,0x82,0xC0,0x32,0xDC,0x9E,0xC7,0x26,0x3C,0x6D,0x8D,0x98,0xC1,
|
||||
0xBB,0x22,0xD4,0xD0,0x0F,0x33,0xEC,0x3E,0xB9,0xCC,0xE1,0xDC,0x6A,0x4C,0x77,0x36,
|
||||
0x14,0x1C,0xF9,0xBF,0x81,0x9F,0x28,0x5F,0x71,0x85,0x32,0x29,0x90,0x75,0x48,0xC4,
|
||||
0xB3,0x4A,0xCE,0xD8,0x44,0x8F,0x14,0x2F,0xFD,0x40,0x57,0xEF,0xAA,0x08,0x75,0xD9,
|
||||
0x46,0xD1,0xD6,0x6E,0x32,0x55,0x1F,0xC3,0x18,0xFE,0x84,0x1F,0xFC,0x84,0xD5,0xFF,
|
||||
0x71,0x5E,0x1B,0x48,0xC3,0x86,0x95,0x0E,0x28,0x08,0x27,0xD3,0x38,0x83,0x71,0x7B,
|
||||
0x4C,0x80,0x63,0x54,0x9A,0x56,0xB0,0xAC,0xCF,0x80,0xCA,0x31,0x09,0xEF,0xFE,0xF3,
|
||||
0xBE,0xAF,0x24,0x7E,0xA6,0xFE,0x53,0x3F,0xC2,0x8D,0x4A,0x33,0x68,0xD1,0x22,0xA6,
|
||||
0x66,0xAD,0x7B,0xEA,0xDE,0xB6,0x43,0xB0,0xA1,0x25,0x95,0x00,0xA3,0x3F,0x75,0x46,
|
||||
0x14,0x11,0x44,0xEC,0xD7,0x95,0xBC,0x92,0xF0,0x4F,0xA9,0x16,0x53,0x62,0x97,0x60,
|
||||
0x2A,0x0F,0x41,0xF1,0x71,0x24,0xBE,0xEE,0x94,0x7F,0x08,0xCD,0x60,0x93,0xB3,0x85,
|
||||
0x5B,0x07,0x00,0x3F,0xD8,0x0F,0x28,0x83,0x9A,0xD1,0x69,0x9F,0xD1,0xDA,0x2E,0xC3,
|
||||
0x90,0x01,0xA2,0xB9,0x6B,0x4E,0x2A,0x66,0x9D,0xDA,0xAE,0xA6,0xEA,0x2A,0xD3,0x68,
|
||||
0x2F,0x0C,0x0C,0x9C,0xD2,0x8C,0x4A,0xED,0xE2,0x9E,0x57,0x65,0x9D,0x09,0x87,0xA3,
|
||||
0xB4,0xC4,0x32,0x5D,0xC9,0xD4,0x32,0x2B,0xB1,0xE0,0x71,0x1E,0x64,0x4D,0xE6,0x90,
|
||||
0x71,0xE3,0x1E,0x40,0xED,0x7D,0xF3,0x84,0x0E,0xED,0xC8,0x78,0x76,0xAE,0xC0,0x71,
|
||||
0x27,0x72,0xBB,0x05,0xEA,0x02,0x64,0xFB,0xF3,0x48,0x6B,0xB5,0x42,0x93,0x3F,0xED,
|
||||
0x9F,0x13,0x53,0xD2,0xF7,0xFE,0x2A,0xEC,0x1D,0x47,0x25,0xDB,0x3C,0x91,0x86,0xC6,
|
||||
0x8E,0xF0,0x11,0xFD,0x23,0x74,0x36,0xF7,0xA4,0xF5,0x9E,0x7A,0x7E,0x53,0x50,0x44,
|
||||
0xD4,0x47,0xCA,0xD3,0xEB,0x38,0x6D,0xE6,0xD9,0x71,0x94,0x7F,0x4A,0xC6,0x69,0x4B,
|
||||
0x11,0xF4,0x52,0xEA,0x22,0xFE,0x8A,0xB0,0x36,0x67,0x8B,0x59,0xE8,0xE6,0x80,0x2A,
|
||||
0xEB,0x65,0x04,0x13,0xEE,0xEC,0xDC,0x9E,0x5F,0xB1,0xEC,0x05,0x6A,0x59,0xE6,0x9F,
|
||||
0x5E,0x59,0x6B,0x89,0xBF,0xF7,0x1A,0xCA,0x44,0xF9,0x5B,0x6A,0x71,0x85,0x03,0xE4,
|
||||
0x29,0x62,0xE0,0x70,0x6F,0x41,0xC4,0xCF,0xB2,0xB1,0xCC,0xE3,0x7E,0xA6,0x07,0xA8,
|
||||
0x87,0xE7,0x7F,0x84,0x93,0xDB,0x52,0x4B,0x6C,0xEC,0x7E,0xDD,0xD4,0x24,0x48,0x10,
|
||||
0x69,0x9F,0x04,0x60,0x74,0xE6,0x48,0x18,0xF3,0xE4,0x2C,0xB9,0x4F,0x2E,0x50,0x7A,
|
||||
0xDF,0xD4,0x54,0x69,0x2B,0x8B,0xA7,0xF3,0xCE,0xFF,0x1F,0xF3,0x3E,0x26,0x01,0x39,
|
||||
0x17,0x95,0x84,0x89,0xB0,0xF0,0x4C,0x4B,0x82,0x91,0x9F,0xC4,0x4B,0xAC,0x9D,0xA5,
|
||||
0x74,0xAF,0x17,0x25,0xC9,0xCA,0x32,0xD3,0xBC,0x89,0x8A,0x84,0x89,0xCC,0x0D,0xAE,
|
||||
0x7C,0xA2,0xDB,0x9C,0x6A,0x78,0x91,0xEE,0xEA,0x76,0x5D,0x4E,0x87,0x60,0xF5,0x69,
|
||||
0x15,0x67,0xD4,0x02,0xCF,0xAF,0x48,0x36,0x07,0xEA,0xBF,0x6F,0x66,0x2D,0x06,0x8F,
|
||||
0xC4,0x9A,0xFE,0xF9,0xF6,0x90,0x87,0x75,0xB8,0xF7,0xAD,0x0F,0x76,0x10,0x5A,0x3D,
|
||||
0x59,0xB0,0x2E,0xB3,0xC7,0x35,0x2C,0xCC,0x70,0x56,0x2B,0xCB,0xE3,0x37,0x96,0xC5,
|
||||
0x2F,0x46,0x1B,0x8A,0x22,0x46,0xC7,0x88,0xA7,0x26,0x32,0x98,0x61,0xDF,0x86,0x22,
|
||||
0x8A,0xF4,0x1C,0x2F,0x87,0xA1,0x09,0xAA,0xCC,0xA9,0xAE,0xD3,0xBD,0x00,0x45,0x1C,
|
||||
0x9A,0x54,0x87,0x86,0x52,0x87,0xEF,0xFF,0x1E,0x8F,0xA1,0x8F,0xC1,0x89,0x5C,0x35,
|
||||
0x1B,0xDA,0x2D,0x3A,0x2C,0x16,0xB2,0xC2,0xF1,0x56,0xE2,0x78,0xC1,0x6B,0x63,0x97,
|
||||
0xC5,0x56,0x8F,0xC9,0x32,0x7F,0x2C,0xAA,0xAF,0xA6,0xA8,0xAC,0x20,0x91,0x22,0x88,
|
||||
0xDE,0xE4,0x60,0x8B,0xF9,0x4B,0x42,0x25,0x1A,0xE3,0x7F,0x9C,0x2C,0x19,0x89,0x3A,
|
||||
0x7E,0x05,0xD4,0x36,0xCC,0x69,0x58,0xC2,0xC1,0x32,0x8B,0x2F,0x90,0x85,0xEB,0x7A,
|
||||
0x39,0x50,0xA5,0xA1,0x27,0x92,0xC5,0x66,0xB0,0x20,0x4F,0x58,0x7E,0x55,0x83,0x43,
|
||||
0x2B,0x45,0xE2,0x9C,0xE4,0xD8,0x12,0x90,0x2C,0x16,0x83,0x56,0x16,0x79,0x03,0xB3,
|
||||
0xAD,0x2D,0x61,0x18,0x1A,0x13,0x1F,0x37,0xE2,0xE1,0x9C,0x73,0x7B,0x80,0xD5,0xFD,
|
||||
0x2D,0x51,0x87,0xFC,0x7B,0xAA,0xD7,0x1F,0x2C,0x7A,0x8E,0xAF,0xF4,0x8D,0xBB,0xCD,
|
||||
0x95,0x11,0x7C,0x72,0x0B,0xEE,0x6F,0xE2,0xB9,0xAF,0xDE,0x37,0x83,0xDE,0x8C,0x8D,
|
||||
0x62,0x05,0x67,0xB7,0x96,0xC6,0x8D,0x56,0xB6,0x0D,0xD7,0x62,0xBA,0xD6,0x46,0x36,
|
||||
0xBD,0x8E,0xC8,0xE6,0xEA,0x2A,0x6C,0x10,0x14,0xFF,0x6B,0x5B,0xFA,0x82,0x3C,0x46,
|
||||
0xB1,0x30,0x43,0x46,0x51,0x8A,0x7D,0x9B,0x92,0x3E,0x83,0x79,0x5B,0x55,0x5D,0xB2,
|
||||
0x6C,0x5E,0xCE,0x90,0x62,0x8E,0x53,0x98,0xC9,0x0D,0x6D,0xE5,0x2D,0x57,0xCD,0xC5,
|
||||
0x81,0x57,0xBA,0xE1,0xE8,0xB8,0x8F,0x72,0xE5,0x4F,0x13,0xDC,0xEA,0x9D,0x71,0x15,
|
||||
0x10,0xB2,0x11,0x88,0xD5,0x09,0xD4,0x7F,0x5B,0x65,0x7F,0x2C,0x3B,0x38,0x4C,0x11,
|
||||
0x68,0x50,0x8D,0xFB,0x9E,0xB0,0x59,0xBF,0x94,0x80,0x89,0x4A,0xC5,0x1A,0x18,0x12,
|
||||
0x89,0x53,0xD1,0x4A,0x10,0x29,0xE8,0x8C,0x1C,0xEC,0xB6,0xEA,0x46,0xC7,0x17,0x8B,
|
||||
0x25,0x15,0x31,0xA8,0xA2,0x6B,0x43,0xB1,0x9D,0xE2,0xDB,0x0B,0x87,0x9B,0xB0,0x11,
|
||||
0x04,0x0E,0x71,0xD2,0x29,0x77,0x89,0x82,0x0A,0x66,0x41,0x7F,0x1D,0x0B,0x48,0xFF,
|
||||
0x72,0xBB,0x24,0xFD,0xC2,0x48,0xA1,0x9B,0xFE,0x7B,0x7F,0xCE,0x88,0xDB,0x86,0xD9,
|
||||
0x85,0x3B,0x1C,0xB0,0xDC,0xA8,0x33,0x07,0xBF,0x51,0x2E,0xE3,0x0E,0x9A,0x00,0x97,
|
||||
0x1E,0x06,0xC0,0x97,0x43,0x9D,0xD8,0xB6,0x45,0xC4,0x86,0x67,0x5F,0x00,0xF8,0x88,
|
||||
0x9A,0xA4,0x52,0x9E,0xC7,0xAA,0x8A,0x83,0x75,0xEC,0xC5,0x18,0xAE,0xCE,0xC3,0x2F,
|
||||
0x1A,0x2B,0xF9,0x18,0xFF,0xAE,0x1A,0xF5,0x53,0x0B,0xB5,0x33,0x51,0xA7,0xFD,0xE8,
|
||||
0xA8,0xE1,0xA2,0x64,0xB6,0x22,0x17,0x43,0x80,0xCC,0x0A,0xD8,0xAE,0x3B,0xBA,0x40,
|
||||
0xD7,0xD9,0x92,0x4A,0x89,0xDF,0x04,0x10,0xEE,0x9B,0x18,0x2B,0x6A,0x77,0x69,0x8A,
|
||||
0x68,0xF4,0xF9,0xB9,0xA2,0x21,0x15,0x6E,0xE6,0x1E,0x3B,0x03,0x62,0x30,0x9B,0x60,
|
||||
0x41,0x7E,0x25,0x9B,0x9E,0x8F,0xC5,0x52,0x10,0x08,0xF8,0xC2,0x69,0xA1,0x21,0x11,
|
||||
0x88,0x37,0x5E,0x79,0x35,0x66,0xFF,0x10,0x42,0x18,0x6E,0xED,0x97,0xB6,0x6B,0x1C,
|
||||
0x4E,0x36,0xE5,0x6D,0x7D,0xB4,0xE4,0xBF,0x20,0xB9,0xE0,0x05,0x3A,0x69,0xD5,0xB8,
|
||||
0xE3,0xD5,0xDC,0xE0,0xB9,0xAC,0x53,0x3E,0x07,0xA4,0x57,0xAD,0x77,0xFF,0x48,0x18,
|
||||
0x76,0x2A,0xAC,0x49,0x2A,0x8E,0x47,0x75,0x6D,0x9F,0x67,0x63,0x30,0x35,0x8C,0x39,
|
||||
0x05,0x39,0xD5,0x6F,0x64,0x3A,0x5B,0xAD,0xCA,0x0B,0xBB,0x82,0x52,0x99,0x45,0xB1,
|
||||
0x93,0x36,0x36,0x99,0xAF,0x13,0x20,0x44,0x36,0xD8,0x02,0x44,0x09,0x39,0x92,0x85,
|
||||
0xFF,0x4A,0x4A,0x97,0x87,0xA6,0x63,0xD7,0xC7,0xB5,0xB5,0x24,0xED,0x0F,0xB4,0x6F,
|
||||
0x0C,0x58,0x52,0x14,0xD9,0xA6,0x7B,0xD3,0x79,0xBC,0x38,0x58,0xA1,0xBD,0x3B,0x84,
|
||||
0x06,0xD8,0x1A,0x06,0xFD,0x6B,0xA8,0xEA,0x4B,0x69,0x28,0x04,0x37,0xAD,0x82,0x99,
|
||||
0xFB,0x0E,0x1B,0x85,0xBD,0xA8,0x5D,0x73,0xCD,0xDC,0x58,0x75,0x0A,0xBE,0x63,0x6C,
|
||||
0x48,0xE7,0x4C,0xE4,0x30,0x2B,0x04,0x60,0xB9,0x15,0xD8,0xDA,0x86,0x81,0x75,0x8F,
|
||||
0x96,0xD4,0x8D,0x1C,0x5D,0x70,0x85,0x7C,0x1C,0x67,0x7B,0xD5,0x08,0x67,0xA6,0xCE,
|
||||
0x4B,0x0A,0x66,0x70,0xB7,0xE5,0x63,0xD4,0x5B,0x8A,0x82,0xEA,0x10,0x67,0xCA,0xE2,
|
||||
0xF4,0xEF,0x17,0x85,0x2F,0x2A,0x5F,0x8A,0x97,0x82,0xF8,0x6A,0xD6,0x34,0x10,0xEA,
|
||||
0xEB,0xC9,0x5C,0x3C,0xE1,0x49,0xF8,0x46,0xEB,0xDE,0xBD,0xF6,0xA9,0x92,0xF1,0xAA,
|
||||
0xA6,0xA0,0x18,0xB0,0x3A,0xD3,0x0F,0x1F,0xF3,0x6F,0xFF,0x31,0x45,0x43,0x44,0xD3,
|
||||
0x50,0x9A,0xF7,0x88,0x09,0x96,0xC1,0xCE,0x76,0xCC,0xF2,0x2C,0x2C,0xBA,0xAD,0x82,
|
||||
0x77,0x8F,0x18,0x84,0xC0,0xD2,0x07,0x9C,0x36,0x90,0x83,0x4E,0x0B,0xA5,0x4F,0x43,
|
||||
0x3E,0x04,0xAB,0x78,0x4F,0xD6,0xFB,0x09,0x01,0x24,0x90,0xDA,0x6F,0x3C,0x3A,0x61,
|
||||
0x0D,0x7F,0x69,0x4A,0xEB,0x2B,0x30,0x02,0xB4,0xDB,0xE0,0x84,0xA9,0xEC,0xD7,0x35,
|
||||
0xBF,0x37,0x7D,0x85,0x58,0xCE,0xA9,0x4E,0xE4,0x80,0xC7,0xA8,0xD3,0x30,0x67,0x48,
|
||||
0xEB,0x29,0xAF,0x2F,0x74,0x6A,0xB4,0xA7,0x3F,0x0F,0x3F,0x92,0xAF,0xF3,0xCA,0xAC,
|
||||
0xAF,0x4B,0xD9,0x94,0xC0,0x43,0xCA,0x81,0x0D,0x2F,0x48,0xA1,0xB0,0x27,0xD5,0xD2,
|
||||
0xEF,0x4B,0x05,0x85,0xA3,0xDE,0x4D,0x93,0x30,0x3C,0xF0,0xBB,0x4A,0x8F,0x30,0x27,
|
||||
0x4C,0xEB,0xE3,0x3E,0x64,0xED,0x9A,0x2F,0x3B,0xF1,0x82,0xF0,0xBA,0xF4,0xCF,0x7F,
|
||||
0x40,0xCB,0xB0,0xE1,0x7F,0xBC,0xAA,0x57,0xD3,0xC9,0x74,0xF2,0xFA,0x43,0x0D,0x22,
|
||||
0xD0,0xF4,0x77,0x4E,0x93,0xD7,0x85,0x70,0x1F,0x99,0xBF,0xB6,0xDE,0x35,0xF1,0x30,
|
||||
0xA7,0x5E,0x71,0xF0,0x6B,0x01,0x2D,0x7B,0x64,0xF0,0x33,0x53,0x0A,0x39,0x88,0xF3,
|
||||
0x6B,0x3A,0xA6,0x6B,0x35,0xD2,0x2F,0x43,0xCD,0x02,0xFD,0xB5,0xE9,0xBC,0x5B,0xAA,
|
||||
0xD8,0xA4,0x19,0x7E,0x0E,0x5D,0x94,0x81,0x9E,0x6F,0x77,0xAD,0xD6,0x0E,0x74,0x93,
|
||||
0x96,0xE7,0xC4,0x18,0x5F,0xAD,0xF5,0x19,
|
||||
];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Encryption process variables
|
||||
|
||||
private uint[] _cardHash = new uint[0x412];
|
||||
private uint[] _arg2 = new uint[3];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Encrypt
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool EncryptFile(string filename, bool force)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Open the read and write on the same file for inplace processing
|
||||
using var reader = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var writer = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = NitroDeserializer.DeserializeStream(reader);
|
||||
if (cart == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a DS or DSi Rom!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset state variables
|
||||
_cardHash = new uint[0x412];
|
||||
_arg2 = new uint[3];
|
||||
|
||||
// Encrypt the secure area
|
||||
EncryptSecureArea(cart, force, reader, writer);
|
||||
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>
|
||||
/// Encrypt secure area in the DS/DSi file
|
||||
/// </summary>s
|
||||
/// <param name="cart">Cart representing the DS file</param>
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void EncryptSecureArea(Cart cart, bool force, Stream reader, Stream 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 (!isDecrypted.Value)
|
||||
{
|
||||
Console.WriteLine("File is already encrypted");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
EncryptARM9(cart.CommonHeader!, reader, writer);
|
||||
Console.WriteLine("File has been encrypted");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the secure ARM9 region of the file, if possible
|
||||
/// </summary>
|
||||
/// <param name="commonHeader">CommonHeader representing the DS header</param>
|
||||
/// <param name="encrypt">Indicates if the file should be encrypted or decrypted</param>
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void EncryptARM9(CommonHeader commonHeader, Stream reader, Stream writer)
|
||||
{
|
||||
// Seek to the beginning of the secure area
|
||||
reader.Seek(0x4000, SeekOrigin.Begin);
|
||||
writer.Seek(0x4000, SeekOrigin.Begin);
|
||||
|
||||
// Grab the first two blocks
|
||||
uint p0 = reader.ReadUInt32();
|
||||
uint p1 = reader.ReadUInt32();
|
||||
|
||||
// Perform the initialization steps
|
||||
Init1(commonHeader);
|
||||
_arg2[1] <<= 1;
|
||||
_arg2[2] >>= 1;
|
||||
Init2();
|
||||
|
||||
// Ensure alignment
|
||||
reader.Seek(0x4008, SeekOrigin.Begin);
|
||||
writer.Seek(0x4008, SeekOrigin.Begin);
|
||||
|
||||
// Loop throgh the main encryption step
|
||||
uint size = 0x800 - 8;
|
||||
while (size > 0)
|
||||
{
|
||||
p0 = reader.ReadUInt32();
|
||||
p1 = reader.ReadUInt32();
|
||||
|
||||
Encrypt(ref p1, ref p0);
|
||||
|
||||
writer.Write(p0);
|
||||
writer.Write(p1);
|
||||
|
||||
size -= 8;
|
||||
}
|
||||
|
||||
// Replace the header explicitly
|
||||
reader.Seek(0x4000, SeekOrigin.Begin);
|
||||
writer.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(commonHeader);
|
||||
Encrypt(ref p1, ref p0);
|
||||
|
||||
writer.Write(p0);
|
||||
writer.Write(p1);
|
||||
}
|
||||
|
||||
/// <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];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Decrypt
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool DecryptFile(string filename, bool force)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Open the read and write on the same file for inplace processing
|
||||
using var reader = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var writer = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = NitroDeserializer.DeserializeStream(reader);
|
||||
if (cart == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a DS or DSi Rom!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset state variables
|
||||
_cardHash = new uint[0x412];
|
||||
_arg2 = new uint[3];
|
||||
|
||||
// Decrypt the secure area
|
||||
DecryptSecureArea(cart, force, reader, writer);
|
||||
|
||||
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>
|
||||
/// Decrypt secure area in the DS/DSi file
|
||||
/// </summary>s
|
||||
/// <param name="cart">Cart representing the DS file</param>
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void DecryptSecureArea(Cart cart, bool force, Stream reader, Stream 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 (isDecrypted.Value)
|
||||
{
|
||||
Console.WriteLine("File is already decrypted");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DecryptARM9(cart.CommonHeader!, reader, writer);
|
||||
Console.WriteLine("File has been decrypted");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the secure ARM9 region of the file, if possible
|
||||
/// </summary>
|
||||
/// <param name="commonHeader">CommonHeader representing the DS header</param>
|
||||
/// <param name="">Indicates if the file should be encrypted or decrypted</param>
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void DecryptARM9(CommonHeader commonHeader, Stream reader, Stream writer)
|
||||
{
|
||||
// Seek to the beginning of the secure area
|
||||
reader.Seek(0x4000, SeekOrigin.Begin);
|
||||
writer.Seek(0x4000, SeekOrigin.Begin);
|
||||
|
||||
// Grab the first two blocks
|
||||
uint p0 = reader.ReadUInt32();
|
||||
uint p1 = reader.ReadUInt32();
|
||||
|
||||
// Perform the initialization steps
|
||||
Init1(commonHeader);
|
||||
Decrypt(ref p1, ref p0);
|
||||
_arg2[1] <<= 1;
|
||||
_arg2[2] >>= 1;
|
||||
Init2();
|
||||
|
||||
// Set the proper flags
|
||||
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.Seek(0x4008, SeekOrigin.Begin);
|
||||
writer.Seek(0x4008, SeekOrigin.Begin);
|
||||
|
||||
// Loop throgh the main encryption step
|
||||
uint size = 0x800 - 8;
|
||||
while (size > 0)
|
||||
{
|
||||
p0 = reader.ReadUInt32();
|
||||
p1 = reader.ReadUInt32();
|
||||
|
||||
Decrypt(ref p1, ref p0);
|
||||
|
||||
writer.Write(p0);
|
||||
writer.Write(p1);
|
||||
|
||||
size -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Common
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the current file is already decrypted or not (or has an empty secure area)
|
||||
/// </summary>
|
||||
/// <param name="reader">Stream representing the input</param>
|
||||
/// <returns>True if the file has known values for a decrypted file, null if it's empty, false otherwise</returns>
|
||||
private static bool? CheckIfDecrypted(Stream reader)
|
||||
{
|
||||
reader.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>
|
||||
/// First common initialization step
|
||||
/// </summary>
|
||||
/// <param name="commonHeader">CommonHeader representing the DS file</param>
|
||||
private void Init1(CommonHeader commonHeader)
|
||||
{
|
||||
Buffer.BlockCopy(NitroEncryptionData, 0, _cardHash, 0, 4 * (1024 + 18));
|
||||
_arg2 = [commonHeader.GameCode, commonHeader.GameCode >> 1, commonHeader.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]),
|
||||
.. BitConverter.GetBytes(_arg2[1]),
|
||||
.. BitConverter.GetBytes(_arg2[2])];
|
||||
|
||||
UpdateHashtable(allBytes);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
266
NDecrypt.Core/DecryptArgs.cs
Normal file
266
NDecrypt.Core/DecryptArgs.cs
Normal file
@@ -0,0 +1,266 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.IO.Readers;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
public class DecryptArgs
|
||||
{
|
||||
#region Common Fields
|
||||
|
||||
/// <summary>
|
||||
/// Represents if all of the keys have been initialized properly
|
||||
/// </summary>
|
||||
public bool? IsReady { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3DS-Specific Fields
|
||||
|
||||
/// <summary>
|
||||
/// AES Hardware Constant
|
||||
/// </summary>
|
||||
public byte[] AESHardwareConstant { get; private set; } = [];
|
||||
|
||||
#region Retail Keys
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x18 (New 3DS 9.3)
|
||||
/// </summary>
|
||||
public byte[] KeyX0x18 { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x1B (New 3DS 9.6)
|
||||
/// </summary>
|
||||
public byte[] KeyX0x1B { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x25 (> 7.x)
|
||||
/// </summary>
|
||||
public byte[] KeyX0x25 { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x2C (< 6.x)
|
||||
/// </summary>
|
||||
public byte[] KeyX0x2C { get; private set; } = [];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Development Keys
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x18 (New 3DS 9.3)
|
||||
/// </summary>
|
||||
public byte[] DevKeyX0x18 { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x1B New 3DS 9.6)
|
||||
/// </summary>
|
||||
public byte[] DevKeyX0x1B { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x25 (> 7.x)
|
||||
/// </summary>
|
||||
public byte[] DevKeyX0x25 { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x2C (< 6.x)
|
||||
/// </summary>
|
||||
public byte[] DevKeyX0x2C { get; private set; } = [];
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Setup all of the necessary constants
|
||||
/// </summary>
|
||||
/// <param name="keyfile">Path to the keyfile</param>
|
||||
/// <param name="useAesKeysTxt">Indicates if the keyfile format is aeskeys.txt</param>
|
||||
public DecryptArgs(string? keyfile, bool useAesKeysTxt)
|
||||
{
|
||||
// Read the proper keyfile format
|
||||
if (useAesKeysTxt)
|
||||
InitAesKeysTxt(keyfile);
|
||||
else
|
||||
InitKeysBin(keyfile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup all of the necessary constants from aes_keys.txt
|
||||
/// </summary>
|
||||
/// <param name="keyfile">Path to aes_keys.txt</param>
|
||||
private void InitAesKeysTxt(string? keyfile)
|
||||
{
|
||||
if (keyfile == null || !File.Exists(keyfile))
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var reader = new IniReader(keyfile);
|
||||
while (reader.ReadNextLine())
|
||||
{
|
||||
// Ignore comments in the file
|
||||
if (reader.RowType == IniRowType.Comment)
|
||||
continue;
|
||||
if (reader.KeyValuePair == null || string.IsNullOrWhiteSpace(reader.KeyValuePair?.Key))
|
||||
break;
|
||||
|
||||
var kvp = reader.KeyValuePair!.Value;
|
||||
byte[] value = StringToByteArray(kvp.Value);
|
||||
switch (kvp.Key)
|
||||
{
|
||||
// Hardware constant
|
||||
case "generator":
|
||||
AESHardwareConstant = value;
|
||||
break;
|
||||
|
||||
// Retail Keys
|
||||
case "slot0x18KeyX":
|
||||
KeyX0x18 = value;
|
||||
break;
|
||||
case "slot0x1BKeyX":
|
||||
KeyX0x1B = value;
|
||||
break;
|
||||
case "slot0x25KeyX":
|
||||
KeyX0x25 = value;
|
||||
break;
|
||||
case "slot0x2CKeyX":
|
||||
KeyX0x2C = value;
|
||||
break;
|
||||
|
||||
// Currently Unused KeyX
|
||||
case "slot0x03KeyX":
|
||||
case "slot0x19KeyX":
|
||||
case "slot0x1AKeyX":
|
||||
case "slot0x1CKeyX":
|
||||
case "slot0x1DKeyX":
|
||||
case "slot0x1EKeyX":
|
||||
case "slot0x1FKeyX":
|
||||
case "slot0x2DKeyX":
|
||||
case "slot0x2EKeyX":
|
||||
case "slot0x2FKeyX":
|
||||
case "slot0x30KeyX":
|
||||
case "slot0x31KeyX":
|
||||
case "slot0x32KeyX":
|
||||
case "slot0x33KeyX":
|
||||
case "slot0x34KeyX":
|
||||
case "slot0x35KeyX":
|
||||
case "slot0x36KeyX":
|
||||
case "slot0x37KeyX":
|
||||
case "slot0x38KeyX":
|
||||
case "slot0x3AKeyX":
|
||||
case "slot0x3BKeyX":
|
||||
break;
|
||||
|
||||
// Currently Unused KeyY
|
||||
case "slot0x03KeyY":
|
||||
case "slot0x06KeyY":
|
||||
case "slot0x07KeyY":
|
||||
case "slot0x2EKeyY":
|
||||
case "slot0x2FKeyY":
|
||||
case "slot0x31KeyY":
|
||||
break;
|
||||
|
||||
// Currently Unused KeyN
|
||||
case "slot0x0DKeyN":
|
||||
case "slot0x15KeyN":
|
||||
case "slot0x16KeyN":
|
||||
case "slot0x19KeyN":
|
||||
case "slot0x1AKeyN":
|
||||
case "slot0x1BKeyN":
|
||||
case "slot0x1CKeyN":
|
||||
case "slot0x1DKeyN":
|
||||
case "slot0x1EKeyN":
|
||||
case "slot0x1FKeyN":
|
||||
case "slot0x24KeyN":
|
||||
case "slot0x2DKeyN":
|
||||
case "slot0x2EKeyN":
|
||||
case "slot0x2FKeyN":
|
||||
case "slot0x31KeyN":
|
||||
case "slot0x32KeyN":
|
||||
case "slot0x36KeyN":
|
||||
case "slot0x37KeyN":
|
||||
case "slot0x38KeyN":
|
||||
case "slot0x3BKeyN":
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
IsReady = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup all of the necessary constants from keys.bin
|
||||
/// </summary>
|
||||
/// <param name="keyfile">Path to keys.bin</param>
|
||||
/// <remarks>keys.bin should be in little endian format</remarks>
|
||||
private void InitKeysBin(string? keyfile)
|
||||
{
|
||||
if (keyfile == null || !File.Exists(keyfile))
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using Stream reader = File.Open(keyfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// Hardware constant
|
||||
AESHardwareConstant = reader.ReadBytes(16);
|
||||
Array.Reverse(AESHardwareConstant);
|
||||
|
||||
// Retail keys
|
||||
KeyX0x18 = reader.ReadBytes(16);
|
||||
Array.Reverse(KeyX0x18);
|
||||
KeyX0x1B = reader.ReadBytes(16);
|
||||
Array.Reverse(KeyX0x1B);
|
||||
KeyX0x25 = reader.ReadBytes(16);
|
||||
Array.Reverse(KeyX0x25);
|
||||
KeyX0x2C = reader.ReadBytes(16);
|
||||
Array.Reverse(KeyX0x2C);
|
||||
|
||||
// Development keys
|
||||
DevKeyX0x18 = reader.ReadBytes(16);
|
||||
Array.Reverse(DevKeyX0x18);
|
||||
DevKeyX0x1B = reader.ReadBytes(16);
|
||||
Array.Reverse(DevKeyX0x1B);
|
||||
DevKeyX0x25 = reader.ReadBytes(16);
|
||||
Array.Reverse(DevKeyX0x25);
|
||||
DevKeyX0x2C = reader.ReadBytes(16);
|
||||
Array.Reverse(DevKeyX0x2C);
|
||||
}
|
||||
catch
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
IsReady = true;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa
|
||||
private static byte[] StringToByteArray(string hex)
|
||||
{
|
||||
int NumberChars = hex.Length;
|
||||
byte[] bytes = new byte[NumberChars / 2];
|
||||
for (int i = 0; i < NumberChars; i += 2)
|
||||
{
|
||||
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
NDecrypt.Core/ITool.cs
Normal file
21
NDecrypt.Core/ITool.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
public interface ITool
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to encrypt an input file
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of the file to encrypt</param>
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <returns>True if the file could be encrypted, false otherwise</returns>
|
||||
bool EncryptFile(string filename, bool force);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to decrypt an input file
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of the file to decrypt</param>
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <returns>True if the file could be decrypted, false otherwise</returns>
|
||||
bool DecryptFile(string filename, bool force);
|
||||
}
|
||||
}
|
||||
53
NDecrypt.Core/NDecrypt.Core.csproj
Normal file
53
NDecrypt.Core/NDecrypt.Core.csproj
Normal file
@@ -0,0 +1,53 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>0.3.1</VersionPrefix>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Description>Common code for all NDecrypt processors</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/SabreTools/NDecrypt</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Support All Frameworks -->
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`)) OR $(TargetFramework.StartsWith(`net9`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="BouncyCastle.NetCore" Version="1.9.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="BouncyCastle.NetCore" Version="2.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.Hashing" Version="1.4.0" />
|
||||
<PackageReference Include="SabreTools.IO" Version="1.5.0" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.5.1" />
|
||||
<PackageReference Include="SabreTools.Serialization" Version="1.7.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
131
NDecrypt.Core/PartitionKeys.cs
Normal file
131
NDecrypt.Core/PartitionKeys.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using SabreTools.Models.N3DS;
|
||||
using static NDecrypt.Core.CommonOperations;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Set of all keys associated with a partition
|
||||
/// </summary>
|
||||
public class PartitionKeys
|
||||
{
|
||||
public byte[] KeyX { get; private set; }
|
||||
|
||||
public byte[] KeyX2C { get; }
|
||||
|
||||
public byte[] KeyY { get; }
|
||||
|
||||
public byte[] NormalKey { get; private set; }
|
||||
|
||||
public byte[] NormalKey2C { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Decryption args to use while processing
|
||||
/// </summary>
|
||||
private readonly DecryptArgs _decryptArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if development images are expected
|
||||
/// </summary>
|
||||
private readonly bool _development;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new set of keys for a given partition
|
||||
/// </summary>
|
||||
/// <param name="args">Decryption args representing available keys</param>
|
||||
/// <param name="signature">RSA-2048 signature from the partition</param>
|
||||
/// <param name="masks">BitMasks from the partition or backup header</param>
|
||||
/// <param name="method">CryptoMethod from the partition or backup header</param>
|
||||
/// <param name="development">Determine if development keys are used</param>
|
||||
public PartitionKeys(DecryptArgs args, byte[]? signature, BitMasks masks, CryptoMethod method, bool development)
|
||||
{
|
||||
// Validate inputs
|
||||
if (args.IsReady != true)
|
||||
throw new InvalidOperationException($"{nameof(args)} must be initialized before use");
|
||||
if (signature != null && signature.Length < 16)
|
||||
throw new DataLengthException($"{nameof(signature)} must be at least 16 bytes");
|
||||
|
||||
// Set fields for future use
|
||||
_decryptArgs = args;
|
||||
_development = development;
|
||||
|
||||
// Set the standard KeyX values
|
||||
KeyX = new byte[16];
|
||||
KeyX2C = development ? args.DevKeyX0x2C : args.KeyX0x2C;
|
||||
|
||||
// Backup headers can't have a KeyY value set
|
||||
KeyY = new byte[16];
|
||||
if (signature != null)
|
||||
Array.Copy(signature, KeyY, 16);
|
||||
|
||||
// Set the standard normal key values
|
||||
NormalKey = new byte[16];
|
||||
|
||||
NormalKey2C = RotateLeft(KeyX2C, 2);
|
||||
NormalKey2C = Xor(NormalKey2C, KeyY);
|
||||
NormalKey2C = Add(NormalKey2C, args.AESHardwareConstant);
|
||||
NormalKey2C = RotateLeft(NormalKey2C, 87);
|
||||
|
||||
// Special case for zero-key
|
||||
if (masks.HasFlag(BitMasks.FixedCryptoKey))
|
||||
{
|
||||
Console.WriteLine("Encryption Method: Zero Key");
|
||||
NormalKey = new byte[16];
|
||||
NormalKey2C = new byte[16];
|
||||
return;
|
||||
}
|
||||
|
||||
// Set KeyX values based on crypto method
|
||||
switch (method)
|
||||
{
|
||||
case CryptoMethod.Original:
|
||||
Console.WriteLine("Encryption Method: Key 0x2C");
|
||||
KeyX = development ? args.DevKeyX0x2C : args.KeyX0x2C;
|
||||
break;
|
||||
|
||||
case CryptoMethod.Seven:
|
||||
Console.WriteLine("Encryption Method: Key 0x25");
|
||||
KeyX = development ? args.DevKeyX0x25 : args.KeyX0x25;
|
||||
break;
|
||||
|
||||
case CryptoMethod.NineThree:
|
||||
Console.WriteLine("Encryption Method: Key 0x18");
|
||||
KeyX = development ? args.DevKeyX0x18 : args.KeyX0x18;
|
||||
break;
|
||||
|
||||
case CryptoMethod.NineSix:
|
||||
Console.WriteLine("Encryption Method: Key 0x1B");
|
||||
KeyX = development ? args.DevKeyX0x1B : args.KeyX0x1B;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the normal key based on the new KeyX value
|
||||
NormalKey = RotateLeft(KeyX, 2);
|
||||
NormalKey = Xor(NormalKey, KeyY);
|
||||
NormalKey = Add(NormalKey, args.AESHardwareConstant);
|
||||
NormalKey = RotateLeft(NormalKey, 87);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set RomFS values based on the bit masks
|
||||
/// </summary>
|
||||
public void SetRomFSValues(BitMasks masks)
|
||||
{
|
||||
// NormalKey has a constant value for zero-key
|
||||
if (masks.HasFlag(BitMasks.FixedCryptoKey))
|
||||
{
|
||||
NormalKey = new byte[16];
|
||||
return;
|
||||
}
|
||||
|
||||
// Encrypting RomFS for partitions 1 and up always use Key0x2C
|
||||
KeyX = _development ? _decryptArgs.DevKeyX0x2C : _decryptArgs.KeyX0x2C;
|
||||
|
||||
NormalKey = RotateLeft(KeyX, 2);
|
||||
NormalKey = Xor(NormalKey, KeyY);
|
||||
NormalKey = Add(NormalKey, _decryptArgs.AESHardwareConstant);
|
||||
NormalKey = RotateLeft(NormalKey, 87);
|
||||
}
|
||||
}
|
||||
}
|
||||
875
NDecrypt.Core/ThreeDSTool.cs
Normal file
875
NDecrypt.Core/ThreeDSTool.cs
Normal file
@@ -0,0 +1,875 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.N3DS;
|
||||
using SabreTools.Serialization.Wrappers;
|
||||
using static NDecrypt.Core.CommonOperations;
|
||||
using static SabreTools.Models.N3DS.Constants;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
public class ThreeDSTool : ITool
|
||||
{
|
||||
/// <summary>
|
||||
/// Decryption args to use while processing
|
||||
/// </summary>
|
||||
private readonly DecryptArgs _decryptArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if development images are expected
|
||||
/// </summary>
|
||||
private readonly bool _development;
|
||||
|
||||
/// <summary>
|
||||
/// Set of all partition keys
|
||||
/// </summary>
|
||||
private readonly PartitionKeys[] KeysMap = new PartitionKeys[8];
|
||||
|
||||
public ThreeDSTool(bool development, DecryptArgs decryptArgs)
|
||||
{
|
||||
_development = development;
|
||||
_decryptArgs = decryptArgs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool EncryptFile(string filename, bool force)
|
||||
{
|
||||
// Ensure the constants are all set
|
||||
if (_decryptArgs.IsReady != true)
|
||||
{
|
||||
Console.WriteLine("Could not read keys. Please make sure the file exists and try again.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Open the read and write on the same file for inplace processing
|
||||
using var input = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var output = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = N3DS.Create(input);
|
||||
if (cart?.Model == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS cart image!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Encrypt all 8 NCCH partitions
|
||||
EncryptAllPartitions(cart, force, input, output);
|
||||
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 cart image and try again.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool DecryptFile(string filename, bool force)
|
||||
{
|
||||
// Ensure the constants are all set
|
||||
if (_decryptArgs.IsReady != true)
|
||||
{
|
||||
Console.WriteLine("Could not read keys. Please make sure the file exists and try again.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Open the read and write on the same file for inplace processing
|
||||
using var input = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var output = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = N3DS.Create(input);
|
||||
if (cart?.Model == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS cart image!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decrypt all 8 NCCH partitions
|
||||
DecryptAllPartitions(cart, force, input, output);
|
||||
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 cart image and try again.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#region Decrypt
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt all partitions in the partition table of an NCSD header
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void DecryptAllPartitions(N3DS cart, bool force, Stream input, Stream output)
|
||||
{
|
||||
// Check the partitions table
|
||||
if (cart.PartitionsTable == null || cart.Partitions == null)
|
||||
{
|
||||
Console.WriteLine("Invalid partitions table!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate over all 8 NCCH partitions
|
||||
for (int p = 0; p < 8; p++)
|
||||
{
|
||||
var partition = cart.Partitions[p];
|
||||
if (partition == null || partition.MagicID != NCCHMagicNumber)
|
||||
{
|
||||
Console.WriteLine($"Partition {p} Not found... Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check the partition has data
|
||||
var partitionEntry = cart.PartitionsTable[p];
|
||||
if (partitionEntry == null || partitionEntry.Length == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {p} No data... Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Decrypt the partition, if possible
|
||||
if (ShouldDecryptPartition(cart, p, force))
|
||||
DecryptPartition(cart, p, input, output);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the current partition should be decrypted
|
||||
/// </summary>s
|
||||
private static bool ShouldDecryptPartition(N3DS cart, int index, bool force)
|
||||
{
|
||||
// If we're forcing the operation, tell the user
|
||||
if (force)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} is not verified due to force flag being set.");
|
||||
return true;
|
||||
}
|
||||
// If we're not forcing the operation, check if the 'NoCrypto' bit is set
|
||||
else if (cart.PossiblyDecrypted(index))
|
||||
{
|
||||
Console.WriteLine($"Partition {index}: Already Decrypted?...");
|
||||
return false;
|
||||
}
|
||||
|
||||
// By default, it passes
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt a single partition
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void DecryptPartition(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Determine the keys needed for this partition
|
||||
SetDecryptionKeys(cart, index);
|
||||
|
||||
// Decrypt the parts of the partition
|
||||
DecryptExtendedHeader(cart, index, input, output);
|
||||
DecryptExeFS(cart, index, input, output);
|
||||
DecryptRomFS(cart, index, input, output);
|
||||
|
||||
// Update the flags
|
||||
UpdateDecryptCryptoAndMasks(cart, index, output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine the set of keys to be used for decryption
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
private void SetDecryptionKeys(N3DS cart, int index)
|
||||
{
|
||||
// Get the partition
|
||||
var partition = cart.Partitions?[index];
|
||||
if (partition?.Flags == null)
|
||||
return;
|
||||
|
||||
// Get partition-specific values
|
||||
byte[]? rsaSignature = partition.RSA2048Signature;
|
||||
BitMasks masks = cart.GetBitMasks(index);
|
||||
CryptoMethod method = cart.GetCryptoMethod(index);
|
||||
|
||||
// Get the partition keys
|
||||
KeysMap[index] = new PartitionKeys(_decryptArgs, rsaSignature, masks, method, _development);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the extended header, if it exists
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private bool DecryptExtendedHeader(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
if (partitionOffset == 0 || partitionOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint extHeaderSize = cart.GetExtendedHeaderSize(index);
|
||||
if (extHeaderSize == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} No Extended Header... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Seek to the extended header
|
||||
input.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
output.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {index}: Decrypting - ExHeader");
|
||||
|
||||
// Create the Plain AES cipher for this partition
|
||||
var cipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey2C, cart.PlainIV(index));
|
||||
|
||||
// Process the extended header
|
||||
PerformAESOperation(Constants.CXTExtendedDataHeaderLength, cipher, input, output, null);
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
// In .NET 6.0, this operation is not picked up by the reader, so we have to force it to reload its buffer
|
||||
input.Seek(0, SeekOrigin.Begin);
|
||||
#endif
|
||||
output.Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private bool DecryptExeFS(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Validate the ExeFS
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsHeaderOffset == 0 || exeFsHeaderOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint exeFsSize = cart.GetExeFSSize(index);
|
||||
if (exeFsSize == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decrypt the filename table
|
||||
DecryptExeFSFilenameTable(cart, index, input, output);
|
||||
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
if (cart.GetCryptoMethod(index) != CryptoMethod.Original)
|
||||
DecryptExeFSFileEntries(cart, index, input, output);
|
||||
|
||||
// Get the ExeFS files offset
|
||||
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
|
||||
|
||||
// Seek to the ExeFS
|
||||
input.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
uint ctroffsetE = cart.MediaUnitSize / 0x10;
|
||||
byte[] exefsIVWithOffset = Add(cart.ExeFSIV(index), ctroffsetE);
|
||||
var cipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey2C, exefsIVWithOffset);
|
||||
|
||||
// Setup and perform the decryption
|
||||
exeFsSize -= cart.MediaUnitSize;
|
||||
PerformAESOperation(exeFsSize,
|
||||
cipher,
|
||||
input,
|
||||
output,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting - {s}"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the ExeFS Filename Table
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void DecryptExeFSFilenameTable(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Get ExeFS offset
|
||||
uint exeFsOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsOffset == 0 || exeFsOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Seek to the ExeFS header
|
||||
input.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {index} ExeFS: Decrypting - ExeFS Filename Table");
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
var cipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey2C, cart.ExeFSIV(index));
|
||||
|
||||
// Process the filename table
|
||||
byte[] readBytes = input.ReadBytes((int)cart.MediaUnitSize);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
output.Write(processedBytes);
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
// In .NET 6.0, this operation is not picked up by the reader, so we have to force it to reload its buffer
|
||||
input.Seek(0, SeekOrigin.Begin);
|
||||
#endif
|
||||
output.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the ExeFS file entries
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void DecryptExeFSFileEntries(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
if (cart.ExeFSHeaders == null || index < 0 || index > cart.ExeFSHeaders.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the ExeFS header
|
||||
var exeFsHeader = cart.ExeFSHeaders[index];
|
||||
if (exeFsHeader?.FileHeaders == null)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS header does not exist. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the ExeFS offset
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
|
||||
|
||||
// Loop through and process all headers
|
||||
for (int i = 0; i < exeFsHeader.FileHeaders.Length; i++)
|
||||
{
|
||||
// Only attempt to process code binary files
|
||||
if (!cart.IsCodeBinary(index, i))
|
||||
continue;
|
||||
|
||||
// Get the file header
|
||||
var fileHeader = exeFsHeader.FileHeaders[i];
|
||||
if (fileHeader == null)
|
||||
continue;
|
||||
|
||||
// Create the ExeFS AES ciphers for this partition
|
||||
uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize) / 0x10;
|
||||
byte[] exefsIVWithOffsetForHeader = Add(cart.ExeFSIV(index), ctroffset);
|
||||
var firstCipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey, exefsIVWithOffsetForHeader);
|
||||
var secondCipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey2C, exefsIVWithOffsetForHeader);
|
||||
|
||||
// Seek to the file entry
|
||||
input.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
// Setup and perform the encryption
|
||||
PerformAESOperation(fileHeader.FileSize,
|
||||
firstCipher,
|
||||
secondCipher,
|
||||
input,
|
||||
output,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting - {fileHeader.FileName}...{s}"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the RomFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private bool DecryptRomFS(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Validate the RomFS
|
||||
uint romFsOffset = cart.GetRomFSOffset(index);
|
||||
if (romFsOffset == 0 || romFsOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint romFsSize = cart.GetRomFSSize(index);
|
||||
if (romFsSize == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Seek to the RomFS
|
||||
input.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
output.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the RomFS AES cipher for this partition
|
||||
var cipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey, cart.RomFSIV(index));
|
||||
|
||||
// Setup and perform the decryption
|
||||
PerformAESOperation(romFsSize,
|
||||
cipher,
|
||||
input,
|
||||
output,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} RomFS: Decrypting - {s}"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the CryptoMethod and BitMasks for the decrypted partition
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private static void UpdateDecryptCryptoAndMasks(N3DS cart, int index, Stream output)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
|
||||
// Seek to the CryptoMethod location
|
||||
output.Seek(partitionOffset + 0x18B, SeekOrigin.Begin);
|
||||
|
||||
// Write the new CryptoMethod
|
||||
output.Write((byte)CryptoMethod.Original);
|
||||
output.Flush();
|
||||
|
||||
// Seek to the BitMasks location
|
||||
output.Seek(partitionOffset + 0x18F, SeekOrigin.Begin);
|
||||
|
||||
// Write the new BitMasks flag
|
||||
BitMasks flag = cart.GetBitMasks(index);
|
||||
flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
|
||||
flag |= BitMasks.NoCrypto;
|
||||
output.Write((byte)flag);
|
||||
output.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Encrypt
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt all partitions in the partition table of an NCSD header
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void EncryptAllPartitions(N3DS cart, bool force, Stream input, Stream output)
|
||||
{
|
||||
// Check the partitions table
|
||||
if (cart.PartitionsTable == null || cart.Partitions == null)
|
||||
{
|
||||
Console.WriteLine("Invalid partitions table!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate over all 8 NCCH partitions
|
||||
for (int p = 0; p < 8; p++)
|
||||
{
|
||||
// Check the partition exists
|
||||
var partition = cart.Partitions[p];
|
||||
if (partition == null || partition.MagicID != NCCHMagicNumber)
|
||||
{
|
||||
Console.WriteLine($"Partition {p} Not found... Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check the partition has data
|
||||
var partitionEntry = cart.PartitionsTable[p];
|
||||
if (partitionEntry == null || partitionEntry.Length == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {p} No data... Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Encrypt the partition, if possible
|
||||
if (ShouldEncryptPartition(cart, p, force))
|
||||
EncryptPartition(cart, p, input, output);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the current partition should be encrypted
|
||||
/// </summary>
|
||||
private static bool ShouldEncryptPartition(N3DS cart, int index, bool force)
|
||||
{
|
||||
// If we're forcing the operation, tell the user
|
||||
if (force)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} is not verified due to force flag being set.");
|
||||
return true;
|
||||
}
|
||||
// If we're not forcing the operation, check if the 'NoCrypto' bit is set
|
||||
else if (!cart.PossiblyDecrypted(index))
|
||||
{
|
||||
Console.WriteLine($"Partition {index}: Already Encrypted?...");
|
||||
return false;
|
||||
}
|
||||
|
||||
// By default, it passes
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt a single partition
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void EncryptPartition(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Determine the keys needed for this partition
|
||||
SetEncryptionKeys(cart, index);
|
||||
|
||||
// Encrypt the parts of the partition
|
||||
EncryptExtendedHeader(cart, index, input, output);
|
||||
EncryptExeFS(cart, index, input, output);
|
||||
EncryptRomFS(cart, index, input, output);
|
||||
|
||||
// Update the flags
|
||||
UpdateEncryptCryptoAndMasks(cart, index, output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine the set of keys to be used for encryption
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
private void SetEncryptionKeys(N3DS cart, int index)
|
||||
{
|
||||
// Get the partition
|
||||
var partition = cart.Partitions?[index];
|
||||
if (partition == null)
|
||||
return;
|
||||
|
||||
// Get the backup header
|
||||
var backupHeader = cart.BackupHeader;
|
||||
if (backupHeader?.Flags == null)
|
||||
return;
|
||||
|
||||
// Get partition-specific values
|
||||
byte[]? rsaSignature = partition.RSA2048Signature;
|
||||
BitMasks masks = backupHeader.Flags.BitMasks;
|
||||
CryptoMethod method = backupHeader.Flags.CryptoMethod;
|
||||
|
||||
// Get the partition keys
|
||||
KeysMap[index] = new PartitionKeys(_decryptArgs, rsaSignature, masks, method, _development);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the extended header, if it exists
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private bool EncryptExtendedHeader(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
if (partitionOffset == 0 || partitionOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint extHeaderSize = cart.GetExtendedHeaderSize(index);
|
||||
if (extHeaderSize == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} No Extended Header... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Seek to the extended header
|
||||
input.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
output.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {index}: Encrypting - ExHeader");
|
||||
|
||||
// Create the Plain AES cipher for this partition
|
||||
var cipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey2C, cart.PlainIV(index));
|
||||
|
||||
// Process the extended header
|
||||
PerformAESOperation(Constants.CXTExtendedDataHeaderLength, cipher, input, output, null);
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
// In .NET 6.0, this operation is not picked up by the reader, so we have to force it to reload its buffer
|
||||
input.Seek(0, SeekOrigin.Begin);
|
||||
#endif
|
||||
output.Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private bool EncryptExeFS(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
if (cart.ExeFSHeaders == null || index < 0 || index > cart.ExeFSHeaders.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the ExeFS header
|
||||
var exefsHeader = cart.ExeFSHeaders[index];
|
||||
if (exefsHeader == null)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS header does not exist. Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
var backupHeader = cart.BackupHeader;
|
||||
if (backupHeader!.Flags!.CryptoMethod != CryptoMethod.Original)
|
||||
EncryptExeFSFileEntries(cart, index, input, output);
|
||||
|
||||
// Encrypt the filename table
|
||||
EncryptExeFSFilenameTable(cart, index, input, output);
|
||||
|
||||
// Get the ExeFS files offset
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
|
||||
|
||||
// Seek to the ExeFS
|
||||
input.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
uint ctroffsetE = cart.MediaUnitSize / 0x10;
|
||||
byte[] exefsIVWithOffset = Add(cart.ExeFSIV(index), ctroffsetE);
|
||||
var cipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey2C, exefsIVWithOffset);
|
||||
|
||||
// Setup and perform the encryption
|
||||
uint exeFsSize = cart.GetExeFSSize(index) - cart.MediaUnitSize;
|
||||
PerformAESOperation(exeFsSize,
|
||||
cipher,
|
||||
input,
|
||||
output,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting - {s}"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the ExeFS Filename Table
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void EncryptExeFSFilenameTable(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Get ExeFS offset
|
||||
uint exeFsOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsOffset == 0 || exeFsOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Seek to the ExeFS header
|
||||
input.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {index} ExeFS: Encrypting - ExeFS Filename Table");
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
var cipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey2C, cart.ExeFSIV(index));
|
||||
|
||||
// Process the filename table
|
||||
byte[] readBytes = input.ReadBytes((int)cart.MediaUnitSize);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
output.Write(processedBytes);
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
// In .NET 6.0, this operation is not picked up by the reader, so we have to force it to reload its buffer
|
||||
input.Seek(0, SeekOrigin.Begin);
|
||||
#endif
|
||||
output.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the ExeFS file entries
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private void EncryptExeFSFileEntries(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Get ExeFS offset
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsHeaderOffset == 0 || exeFsHeaderOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get to the start of the files
|
||||
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
|
||||
|
||||
// If the header failed to read, log and return
|
||||
var exeFsHeader = cart.ExeFSHeaders?[index];
|
||||
if (exeFsHeader?.FileHeaders == null)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS header does not exist. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop through and process all headers
|
||||
for (int i = 0; i < exeFsHeader.FileHeaders.Length; i++)
|
||||
{
|
||||
// Only attempt to process code binary files
|
||||
if (!cart.IsCodeBinary(index, i))
|
||||
continue;
|
||||
|
||||
// Get the file header
|
||||
var fileHeader = exeFsHeader.FileHeaders[i];
|
||||
if (fileHeader == null)
|
||||
continue;
|
||||
|
||||
// Create the ExeFS AES ciphers for this partition
|
||||
uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize) / 0x10;
|
||||
byte[] exefsIVWithOffsetForHeader = Add(cart.ExeFSIV(index), ctroffset);
|
||||
var firstCipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey, exefsIVWithOffsetForHeader);
|
||||
var secondCipher = CreateAESDecryptionCipher(KeysMap[index].NormalKey2C, exefsIVWithOffsetForHeader);
|
||||
|
||||
// Seek to the file entry
|
||||
input.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
output.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
// Setup and perform the encryption
|
||||
PerformAESOperation(fileHeader.FileSize,
|
||||
firstCipher,
|
||||
secondCipher,
|
||||
input,
|
||||
output,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting - {fileHeader.FileName}...{s}"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the RomFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="input">Stream representing the input</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private bool EncryptRomFS(N3DS cart, int index, Stream input, Stream output)
|
||||
{
|
||||
// Validate the RomFS
|
||||
uint romFsOffset = cart.GetRomFSOffset(index);
|
||||
if (romFsOffset == 0 || romFsOffset > input.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint romFsSize = cart.GetRomFSSize(index);
|
||||
if (romFsSize == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} RomFS: No Data... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Seek to the RomFS
|
||||
input.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
output.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
|
||||
// Force setting encryption keys for partitions 1 and above
|
||||
if (index > 0)
|
||||
{
|
||||
var backupHeader = cart.BackupHeader;
|
||||
KeysMap[index].SetRomFSValues(backupHeader!.Flags!.BitMasks);
|
||||
}
|
||||
|
||||
// Create the RomFS AES cipher for this partition
|
||||
var cipher = CreateAESEncryptionCipher(KeysMap[index].NormalKey, cart.RomFSIV(index));
|
||||
|
||||
// Setup and perform the decryption
|
||||
PerformAESOperation(romFsSize,
|
||||
cipher,
|
||||
input,
|
||||
output,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} RomFS: Encrypting - {s}"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the CryptoMethod and BitMasks for the encrypted partition
|
||||
/// </summary>
|
||||
/// <param name="cart">Cart representing the 3DS file</param>
|
||||
/// <param name="index">Index of the partition</param>
|
||||
/// <param name="output">Stream representing the output</param>
|
||||
private static void UpdateEncryptCryptoAndMasks(N3DS cart, int index, Stream output)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
|
||||
// Get the backup header
|
||||
var backupHeader = cart.BackupHeader;
|
||||
if (backupHeader?.Flags == null)
|
||||
return;
|
||||
|
||||
// Seek to the CryptoMethod location
|
||||
output.Seek(partitionOffset + 0x18B, SeekOrigin.Begin);
|
||||
|
||||
// Write the new CryptoMethod
|
||||
// - For partitions 1 and up, set crypto-method to 0x00
|
||||
// - If partition 0, restore crypto-method from backup flags
|
||||
byte cryptoMethod = index > 0 ? (byte)CryptoMethod.Original : (byte)backupHeader.Flags.CryptoMethod;
|
||||
output.Write(cryptoMethod);
|
||||
output.Flush();
|
||||
|
||||
// Seek to the BitMasks location
|
||||
output.Seek(partitionOffset + 0x18F, SeekOrigin.Begin);
|
||||
|
||||
// Write the new BitMasks flag
|
||||
BitMasks flag = cart.GetBitMasks(index);
|
||||
flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF;
|
||||
flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & backupHeader.Flags.BitMasks;
|
||||
output.Write((byte)flag);
|
||||
output.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
36
NDecrypt.sln
Normal file
36
NDecrypt.sln
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.3.32922.545
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NDecrypt", "NDecrypt\NDecrypt.csproj", "{05566793-831F-4AE1-A6D2-F9214F36618E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NDecrypt.Core", "NDecrypt.Core\NDecrypt.Core.csproj", "{91C54370-5741-4742-B2E9-EC498551AD1C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{05566793-831F-4AE1-A6D2-F9214F36618E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{05566793-831F-4AE1-A6D2-F9214F36618E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{05566793-831F-4AE1-A6D2-F9214F36618E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{05566793-831F-4AE1-A6D2-F9214F36618E}.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
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5AB50D9B-BA18-4F96-804B-52E7E0845B37}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
16
NDecrypt/FileType.cs
Normal file
16
NDecrypt/FileType.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace NDecrypt
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of the detected file
|
||||
/// </summary>
|
||||
internal enum FileType
|
||||
{
|
||||
NULL,
|
||||
NDS,
|
||||
NDSi,
|
||||
iQueDS,
|
||||
N3DS,
|
||||
iQue3DS,
|
||||
N3DSCIA,
|
||||
}
|
||||
}
|
||||
33
NDecrypt/HashingHelper.cs
Normal file
33
NDecrypt/HashingHelper.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.IO;
|
||||
using SabreTools.Hashing;
|
||||
|
||||
namespace NDecrypt
|
||||
{
|
||||
internal static class HashingHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieve file information for a single file
|
||||
/// </summary>
|
||||
/// <param name="input">Filename to get information from</param>
|
||||
/// <returns>Formatted string representing the hashes, null on error</returns>
|
||||
public static string? GetInfo(string input)
|
||||
{
|
||||
// If the file doesn't exist, return null
|
||||
if (!File.Exists(input))
|
||||
return null;
|
||||
|
||||
// Get the file information, if possible
|
||||
HashType[] hashTypes = [HashType.CRC32, HashType.MD5, HashType.SHA1, HashType.SHA256];
|
||||
var hashDict = HashTool.GetFileHashesAndSize(input, hashTypes, out long size);
|
||||
if (hashDict == null)
|
||||
return null;
|
||||
|
||||
// Get the results
|
||||
return $"Size: {size}\n"
|
||||
+ $"CRC-32: {(hashDict.ContainsKey(HashType.CRC32) ? hashDict[HashType.CRC32] : string.Empty)}\n"
|
||||
+ $"MD5: {(hashDict.ContainsKey(HashType.MD5) ? hashDict[HashType.MD5] : string.Empty)}\n"
|
||||
+ $"SHA-1: {(hashDict.ContainsKey(HashType.SHA1) ? hashDict[HashType.SHA1] : string.Empty)}\n"
|
||||
+ $"CSHA-256: {(hashDict.ContainsKey(HashType.SHA256) ? hashDict[HashType.SHA256] : string.Empty)}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
44
NDecrypt/NDecrypt.csproj
Normal file
44
NDecrypt/NDecrypt.csproj
Normal file
@@ -0,0 +1,44 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>0.3.1</VersionPrefix>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Title>NDecrypt</Title>
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Description>DS/3DS Encryption Tool</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2018-2024</Copyright>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/SabreTools/NDecrypt</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Support All Frameworks -->
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`)) OR $(TargetFramework.StartsWith(`net9`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NDecrypt.Core\NDecrypt.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
298
NDecrypt/Program.cs
Normal file
298
NDecrypt/Program.cs
Normal file
@@ -0,0 +1,298 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NDecrypt.Core;
|
||||
|
||||
namespace NDecrypt
|
||||
{
|
||||
class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// Mapping of reusable tools
|
||||
/// </summary>
|
||||
private static readonly Dictionary<FileType, ITool> _tools = [];
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
DisplayHelp("Not enough arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
bool encrypt;
|
||||
if (args[0] == "decrypt" || args[0] == "d")
|
||||
{
|
||||
encrypt = false;
|
||||
}
|
||||
else if (args[0] == "encrypt" || args[0] == "e")
|
||||
{
|
||||
encrypt = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayHelp($"Invalid operation: {args[0]}");
|
||||
return;
|
||||
}
|
||||
|
||||
bool development = false,
|
||||
force = false,
|
||||
outputHashes = false,
|
||||
useAesKeysTxt = false;
|
||||
string? keyfile = null;
|
||||
int start = 1;
|
||||
for (; start < args.Length; start++)
|
||||
{
|
||||
if (args[start] == "-a" || args[start] == "--aes-keys"
|
||||
|| args[start] == "-c" || args[start] == "--citra")
|
||||
{
|
||||
useAesKeysTxt = true;
|
||||
}
|
||||
else if (args[start] == "-dev" || args[start] == "--development")
|
||||
{
|
||||
development = true;
|
||||
}
|
||||
else if (args[start] == "-f" || args[start] == "--force")
|
||||
{
|
||||
force = true;
|
||||
}
|
||||
else if (args[start] == "-h" || args[start] == "--hash")
|
||||
{
|
||||
outputHashes = true;
|
||||
}
|
||||
else if (args[start] == "-k" || args[start] == "--keyfile")
|
||||
{
|
||||
if (start == args.Length - 1)
|
||||
Console.WriteLine("Invalid keyfile path: no additional arguments found!");
|
||||
|
||||
start++;
|
||||
string tempPath = args[start];
|
||||
if (string.IsNullOrWhiteSpace(tempPath))
|
||||
Console.WriteLine($"Invalid keyfile path: null or empty path found!");
|
||||
|
||||
tempPath = Path.GetFullPath(tempPath);
|
||||
if (!File.Exists(tempPath))
|
||||
Console.WriteLine($"Invalid keyfile path: file {tempPath} not found!");
|
||||
else
|
||||
keyfile = tempPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Derive the keyfile path based on the runtime folder if not already set
|
||||
keyfile = DeriveKeyFile(keyfile, useAesKeysTxt);
|
||||
|
||||
// If we are using a Citra keyfile, there are no development keys
|
||||
if (development && useAesKeysTxt)
|
||||
{
|
||||
Console.WriteLine("AES keyfiles don't contain development keys; disabling the option...");
|
||||
development = false;
|
||||
}
|
||||
|
||||
// Initialize the decrypt args, if possible
|
||||
var decryptArgs = new DecryptArgs(keyfile, useAesKeysTxt);
|
||||
|
||||
// Create reusable tools
|
||||
_tools[FileType.NDS] = new DSTool();
|
||||
_tools[FileType.N3DS] = new ThreeDSTool(development, decryptArgs);
|
||||
//_tools[FileType.N3DSCIA] = new CIATool(development, decryptArgs);
|
||||
|
||||
for (int i = start; i < args.Length; i++)
|
||||
{
|
||||
if (File.Exists(args[i]))
|
||||
{
|
||||
ProcessPath(args[i], encrypt, force, outputHashes);
|
||||
}
|
||||
else if (Directory.Exists(args[i]))
|
||||
{
|
||||
foreach (string file in Directory.EnumerateFiles(args[i], "*", SearchOption.AllDirectories))
|
||||
{
|
||||
ProcessPath(file, encrypt, force, outputHashes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"{args[i]} is not a file or folder. Please check your spelling and formatting and try again.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a single file path
|
||||
/// </summary>
|
||||
/// <param name="path">File path to process</param>
|
||||
/// <param name="encrypt">Indicates if the file should be encrypted or decrypted</param>
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <param name="outputHashes">Indicates if hashes should be output after a successful operation</param>
|
||||
private static void ProcessPath(string path, bool encrypt, bool force, bool outputHashes)
|
||||
{
|
||||
// Attempt to derive the tool for the path
|
||||
var tool = DeriveTool(path);
|
||||
if (tool == null)
|
||||
return;
|
||||
|
||||
Console.WriteLine($"Processing {path}");
|
||||
|
||||
// Encrypt or decrypt the file as requested
|
||||
if (encrypt && !tool.EncryptFile(path, force))
|
||||
{
|
||||
Console.WriteLine("Encryption failed!");
|
||||
return;
|
||||
}
|
||||
else if (!encrypt && !tool.DecryptFile(path, force))
|
||||
{
|
||||
Console.WriteLine("Decryption failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Output the file hashes, if expected
|
||||
if (outputHashes)
|
||||
WriteHashes(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display a basic help text
|
||||
/// </summary>
|
||||
/// <param name="err">Additional error text to display, can be null to ignore</param>
|
||||
private static void DisplayHelp(string? err = null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(err))
|
||||
Console.WriteLine($"Error: {err}");
|
||||
|
||||
Console.WriteLine(@"Usage: NDecrypt <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):
|
||||
-a, --aes-keys Enable using aes_keys.txt instead of keys.bin
|
||||
-dev, --development Enable using development keys, if available
|
||||
-f, --force Force operation by avoiding sanity checks
|
||||
-h, --hash Output size and hashes to a companion file
|
||||
-k, --keyfile <path> Path to keys.bin or aes_keys.txt
|
||||
|
||||
<path> can be any file or folder that contains uncompressed items.
|
||||
More than one path can be specified at a time.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Derive the full path to the keyfile, if possible
|
||||
/// </summary>
|
||||
private static string? DeriveKeyFile(string? keyfile, bool useAesKeysTxt)
|
||||
{
|
||||
// If a path is passed in
|
||||
if (!string.IsNullOrEmpty(keyfile))
|
||||
{
|
||||
keyfile = Path.GetFullPath(keyfile);
|
||||
if (File.Exists(keyfile))
|
||||
return keyfile;
|
||||
}
|
||||
|
||||
// Derive the keyfile path based on the runtime folder if not already set
|
||||
using var processModule = System.Diagnostics.Process.GetCurrentProcess().MainModule;
|
||||
string applicationDirectory = Path.GetDirectoryName(processModule?.FileName) ?? string.Empty;
|
||||
|
||||
// Use the proper default name for the type
|
||||
if (useAesKeysTxt)
|
||||
keyfile = Path.Combine(applicationDirectory, "aes_keys.txt");
|
||||
else
|
||||
keyfile = Path.Combine(applicationDirectory, "keys.bin");
|
||||
|
||||
// Only return the path if the file exists
|
||||
return File.Exists(keyfile) ? keyfile : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Derive the encryption tool to be used for the given file
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to derive the tool from</param>
|
||||
private static ITool? DeriveTool(string filename)
|
||||
{
|
||||
if (!File.Exists(filename))
|
||||
{
|
||||
Console.WriteLine($"{filename} does not exist! Skipping...");
|
||||
return null;
|
||||
}
|
||||
|
||||
FileType type = DetermineFileType(filename);
|
||||
return type switch
|
||||
{
|
||||
FileType.NDS => _tools[FileType.NDS],
|
||||
FileType.NDSi => _tools[FileType.NDS],
|
||||
FileType.iQueDS => _tools[FileType.NDS],
|
||||
FileType.N3DS => _tools[FileType.N3DS],
|
||||
//FileType.N3DSCIA => _tools[FileType.N3DSCIA],
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine the file type from the filename extension
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to derive the type from</param>
|
||||
/// <returns>FileType value, if possible</returns>
|
||||
private static FileType DetermineFileType(string filename)
|
||||
{
|
||||
if (filename.EndsWith(".nds", StringComparison.OrdinalIgnoreCase) // Standard carts
|
||||
|| filename.EndsWith(".nds.dec", StringComparison.OrdinalIgnoreCase) // Carts/images with secure area decrypted
|
||||
|| filename.EndsWith(".nds.enc", StringComparison.OrdinalIgnoreCase) // Carts/images with secure area encrypted
|
||||
|| filename.EndsWith(".srl", StringComparison.OrdinalIgnoreCase)) // Development carts/images
|
||||
{
|
||||
Console.WriteLine("File recognized as Nintendo DS");
|
||||
return FileType.NDS;
|
||||
}
|
||||
else if (filename.EndsWith(".dsi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteLine("File recognized as Nintendo DSi");
|
||||
return FileType.NDSi;
|
||||
}
|
||||
else if (filename.EndsWith(".ids", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteLine("File recognized as iQue DS");
|
||||
return FileType.iQueDS;
|
||||
}
|
||||
else if (filename.EndsWith(".3ds", StringComparison.OrdinalIgnoreCase) // Standard carts
|
||||
|| filename.EndsWith(".3ds.dec", StringComparison.OrdinalIgnoreCase) // Decrypted carts/images
|
||||
|| filename.EndsWith(".3ds.enc", StringComparison.OrdinalIgnoreCase)) // Encrypted carts/images
|
||||
{
|
||||
Console.WriteLine("File recognized as Nintendo 3DS");
|
||||
return FileType.N3DS;
|
||||
}
|
||||
// else if (filename.EndsWith(".cia", StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// Console.WriteLine("File recognized as Nintendo 3DS CIA [CAUTION: NOT WORKING CURRENTLY]");
|
||||
// return FileType.N3DSCIA;
|
||||
// }
|
||||
|
||||
Console.WriteLine($"Unrecognized file format for {filename}. Expected *.nds, *.srl, *.dsi, *.3ds");
|
||||
return FileType.NULL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write out the hashes of a file to a named file
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to get hashes for/param>
|
||||
private static void WriteHashes(string filename)
|
||||
{
|
||||
// If the file doesn't exist, don't try anything
|
||||
if (!File.Exists(filename))
|
||||
return;
|
||||
|
||||
// Get the hash string from the file
|
||||
string? hashString = HashingHelper.GetInfo(filename);
|
||||
if (hashString == null)
|
||||
return;
|
||||
|
||||
// Open the output file and write the hashes
|
||||
using (var fs = File.Create(Path.GetFullPath(filename) + ".hash"))
|
||||
using (var sw = new StreamWriter(fs))
|
||||
{
|
||||
sw.WriteLine(hashString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
README.md
Normal file
85
README.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# NDecrypt
|
||||
|
||||
[](https://ci.appveyor.com/project/mnadareski/ndecrypt)
|
||||
[](https://github.com/SabreTools/NDecrypt/actions/workflows/build_program.yml)
|
||||
|
||||
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` or `aes_keys.txt` file for your personally obtained encryption keys.
|
||||
|
||||
## Where do I find it?
|
||||
|
||||
For the most recent stable build, download the latest release here: [Releases Page](https://github.com/SabreTools/NDecrypt/releases)
|
||||
|
||||
For the latest WIP build here: [Rolling Release](https://github.com/SabreTools/NDecrypt/releases/tag/rolling)
|
||||
|
||||
## 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):
|
||||
-c, --citra - Enable using aes_keys.txt instead of keys.bin
|
||||
-dev, --development - Enable using development keys, if available
|
||||
-f, --force - Force operation by avoiding sanity checks
|
||||
-h, --hash - Output size and hashes to a companion file
|
||||
-k, --keyfile <path> - Path to keys.bin or aes_keys.txt
|
||||
|
||||
<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 (possibly*) 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:
|
||||
|
||||
| Name | `aes_keys.txt` Entry |
|
||||
| --- | --- |
|
||||
| Hardware constant | `generator` |
|
||||
| KeyX0x18 | `slot0x18KeyX` |
|
||||
| KeyX0x1B | `slot0x1BKeyX` |
|
||||
| KeyX0x25 | `slot0x25KeyX` |
|
||||
| KeyX0x2C | `slot0x2CKeyX` |
|
||||
| DevKeyX0x18 | **UNMAPPED** |
|
||||
| DevKeyX0x1B | **UNMAPPED** |
|
||||
| DevKeyX0x25 | **UNMAPPED** |
|
||||
| DevKeyX0x2C | **UNMAPPED** |
|
||||
|
||||
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.
|
||||
|
||||
*If you choose to use the `-c` option, you can instead provide your personally filled out `aes_keys.txt` file in the same folder as NDecrypt and that can be used instead. Please note that if you choose to use this file, you will not be able to use the `-dev` flag. If you forget and happen to use them together, NDecrypt will disable that flag for you. You're welcome.
|
||||
|
||||
## 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-19):
|
||||
|
||||
- **Nintendo DS** - >99% compatible (Both encryption and decryption)
|
||||
- **Nintendo DSi** - 100% compatible (Both encryption and decryption)
|
||||
- **Nintendo 3DS** - 100% compatible (Both encryption and decryption)
|
||||
- **Nintendo New 3DS** - 100% compatible (Both encryption and decryption)
|
||||
|
||||
Please note the above numbers are based on the current, documented values. The notable exceptions to this tend to be unlicensed carts which may be dumped incorrectly or have odd information stored in their secure area.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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.
|
||||
26
appveyor.yml
Normal file
26
appveyor.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
# version format
|
||||
version: 0.2.5-{build}
|
||||
|
||||
# pull request template
|
||||
pull_requests:
|
||||
do_not_increment_build_number: true
|
||||
|
||||
# vm template
|
||||
image: Visual Studio 2022
|
||||
|
||||
# install dependencies
|
||||
install:
|
||||
- cd %APPVEYOR_BUILD_FOLDER%
|
||||
- git submodule update --init --recursive
|
||||
|
||||
# build step
|
||||
build_script:
|
||||
- dotnet build
|
||||
|
||||
# success/failure tracking
|
||||
on_success:
|
||||
- ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1
|
||||
- ps: ./send.ps1 success $env:WEBHOOK_URL
|
||||
on_failure:
|
||||
- ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1
|
||||
- ps: ./send.ps1 failure $env:WEBHOOK_URL
|
||||
143
publish-nix.sh
Normal file
143
publish-nix.sh
Normal file
@@ -0,0 +1,143 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This batch file assumes the following:
|
||||
# - .NET 9.0 (or newer) SDK is installed and in PATH
|
||||
# - zip is installed and in PATH
|
||||
# - Git is installed and in PATH
|
||||
#
|
||||
# If any of these are not satisfied, the operation may fail
|
||||
# in an unpredictable way and result in an incomplete output.
|
||||
|
||||
# Optional parameters
|
||||
USE_ALL=false
|
||||
NO_BUILD=false
|
||||
NO_ARCHIVE=false
|
||||
while getopts "uba" OPTION; do
|
||||
case $OPTION in
|
||||
u)
|
||||
USE_ALL=true
|
||||
;;
|
||||
b)
|
||||
NO_BUILD=true
|
||||
;;
|
||||
a)
|
||||
NO_ARCHIVE=true
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option provided"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Set the current directory as a variable
|
||||
BUILD_FOLDER=$PWD
|
||||
|
||||
# Set the current commit hash
|
||||
COMMIT=$(git log --pretty=%H -1)
|
||||
|
||||
# Output the selected options
|
||||
echo "Selected Options:"
|
||||
echo " Use all frameworks (-u) $USE_ALL"
|
||||
echo " No build (-b) $NO_BUILD"
|
||||
echo " No archive (-a) $NO_ARCHIVE"
|
||||
echo " "
|
||||
|
||||
# Create the build matrix arrays
|
||||
FRAMEWORKS=("net9.0")
|
||||
RUNTIMES=("win-x86" "win-x64" "win-arm64" "linux-x64" "linux-arm64" "osx-x64" "osx-arm64")
|
||||
|
||||
# Use expanded lists, if requested
|
||||
if [ $USE_ALL = true ]; then
|
||||
FRAMEWORKS=("net40" "net452" "net462" "net472" "net48" "netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0" "net9.0")
|
||||
fi
|
||||
|
||||
# Create the filter arrays
|
||||
SINGLE_FILE_CAPABLE=("net5.0" "net6.0" "net7.0" "net8.0" "net9.0")
|
||||
VALID_APPLE_FRAMEWORKS=("net6.0" "net7.0" "net8.0" "net9.0")
|
||||
VALID_CROSS_PLATFORM_FRAMEWORKS=("netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0" "net9.0")
|
||||
VALID_CROSS_PLATFORM_RUNTIMES=("win-arm64" "linux-x64" "linux-arm64" "osx-x64" "osx-arm64")
|
||||
|
||||
# Only build if requested
|
||||
if [ $NO_BUILD = false ]; then
|
||||
# Restore Nuget packages for all builds
|
||||
echo "Restoring Nuget packages"
|
||||
dotnet restore
|
||||
|
||||
# Build Program
|
||||
for FRAMEWORK in "${FRAMEWORKS[@]}"; do
|
||||
for RUNTIME in "${RUNTIMES[@]}"; do
|
||||
# Output the current build
|
||||
echo "===== Build Program - $FRAMEWORK, $RUNTIME ====="
|
||||
|
||||
# If we have an invalid combination of framework and runtime
|
||||
if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
|
||||
if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]; then
|
||||
echo "Skipped due to invalid combination"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# If we have Apple silicon but an unsupported framework
|
||||
if [[ ! $(echo ${VALID_APPLE_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
|
||||
if [ $RUNTIME = "osx-arm64" ]; then
|
||||
echo "Skipped due to no Apple Silicon support"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# Only .NET 5 and above can publish to a single file
|
||||
if [[ $(echo ${SINGLE_FILE_CAPABLE[@]} | fgrep -w $FRAMEWORK) ]]; then
|
||||
# Only include Debug if building all
|
||||
if [ $USE_ALL = true ]; then
|
||||
dotnet publish NDecrypt/NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
|
||||
fi
|
||||
dotnet publish NDecrypt/NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
|
||||
else
|
||||
# Only include Debug if building all
|
||||
if [ $USE_ALL = true ]; then
|
||||
dotnet publish NDecrypt/NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
|
||||
fi
|
||||
dotnet publish NDecrypt/NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
# Only create archives if requested
|
||||
if [ $NO_ARCHIVE = false ]; then
|
||||
# Create Test archives
|
||||
for FRAMEWORK in "${FRAMEWORKS[@]}"; do
|
||||
for RUNTIME in "${RUNTIMES[@]}"; do
|
||||
# Output the current build
|
||||
echo "===== Archive Program - $FRAMEWORK, $RUNTIME ====="
|
||||
|
||||
# If we have an invalid combination of framework and runtime
|
||||
if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
|
||||
if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]; then
|
||||
echo "Skipped due to invalid combination"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# If we have Apple silicon but an unsupported framework
|
||||
if [[ ! $(echo ${VALID_APPLE_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
|
||||
if [ $RUNTIME = "osx-arm64" ]; then
|
||||
echo "Skipped due to no Apple Silicon support"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# Only include Debug if building all
|
||||
if [ $USE_ALL = true ]; then
|
||||
cd $BUILD_FOLDER/NDecrypt/bin/Debug/${FRAMEWORK}/${RUNTIME}/publish/
|
||||
zip -r $BUILD_FOLDER/NDecrypt_${FRAMEWORK}_${RUNTIME}_debug.zip .
|
||||
fi
|
||||
cd $BUILD_FOLDER/NDecrypt/bin/Release/${FRAMEWORK}/${RUNTIME}/publish/
|
||||
zip -r $BUILD_FOLDER/NDecrypt_${FRAMEWORK}_${RUNTIME}_release.zip .
|
||||
done
|
||||
done
|
||||
|
||||
# Reset the directory
|
||||
cd $BUILD_FOLDER
|
||||
fi
|
||||
128
publish-win.ps1
Normal file
128
publish-win.ps1
Normal file
@@ -0,0 +1,128 @@
|
||||
# This batch file assumes the following:
|
||||
# - .NET 9.0 (or newer) SDK is installed and in PATH
|
||||
# - 7-zip commandline (7z.exe) is installed and in PATH
|
||||
# - Git for Windows is installed and in PATH
|
||||
#
|
||||
# If any of these are not satisfied, the operation may fail
|
||||
# in an unpredictable way and result in an incomplete output.
|
||||
|
||||
# Optional parameters
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[Alias("UseAll")]
|
||||
[switch]$USE_ALL,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[Alias("NoBuild")]
|
||||
[switch]$NO_BUILD,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[Alias("NoArchive")]
|
||||
[switch]$NO_ARCHIVE
|
||||
)
|
||||
|
||||
# Set the current directory as a variable
|
||||
$BUILD_FOLDER = $PSScriptRoot
|
||||
|
||||
# Set the current commit hash
|
||||
$COMMIT = git log --pretty=format:"%H" -1
|
||||
|
||||
# Output the selected options
|
||||
Write-Host "Selected Options:"
|
||||
Write-Host " Use all frameworks (-UseAll) $USE_ALL"
|
||||
Write-Host " No build (-NoBuild) $NO_BUILD"
|
||||
Write-Host " No archive (-NoArchive) $NO_ARCHIVE"
|
||||
Write-Host " "
|
||||
|
||||
# Create the build matrix arrays
|
||||
$FRAMEWORKS = @('net9.0')
|
||||
$RUNTIMES = @('win-x86', 'win-x64', 'win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64')
|
||||
|
||||
# Use expanded lists, if requested
|
||||
if ($USE_ALL.IsPresent) {
|
||||
$FRAMEWORKS = @('net40', 'net452', 'net462', 'net472', 'net48', 'netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0', 'net9.0')
|
||||
}
|
||||
|
||||
# Create the filter arrays
|
||||
$SINGLE_FILE_CAPABLE = @('net5.0', 'net6.0', 'net7.0', 'net8.0', 'net9.0')
|
||||
$VALID_APPLE_FRAMEWORKS = @('net6.0', 'net7.0', 'net8.0', 'net9.0')
|
||||
$VALID_CROSS_PLATFORM_FRAMEWORKS = @('netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0', 'net9.0')
|
||||
$VALID_CROSS_PLATFORM_RUNTIMES = @('win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64')
|
||||
|
||||
# Only build if requested
|
||||
if (!$NO_BUILD.IsPresent) {
|
||||
# Restore Nuget packages for all builds
|
||||
Write-Host "Restoring Nuget packages"
|
||||
dotnet restore
|
||||
|
||||
# Build Program
|
||||
foreach ($FRAMEWORK in $FRAMEWORKS) {
|
||||
foreach ($RUNTIME in $RUNTIMES) {
|
||||
# Output the current build
|
||||
Write-Host "===== Build Program - $FRAMEWORK, $RUNTIME ====="
|
||||
|
||||
# If we have an invalid combination of framework and runtime
|
||||
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
|
||||
Write-Host "Skipped due to invalid combination"
|
||||
continue
|
||||
}
|
||||
|
||||
# If we have Apple silicon but an unsupported framework
|
||||
if ($VALID_APPLE_FRAMEWORKS -notcontains $FRAMEWORK -and $RUNTIME -eq 'osx-arm64') {
|
||||
Write-Host "Skipped due to no Apple Silicon support"
|
||||
continue
|
||||
}
|
||||
|
||||
# Only .NET 5 and above can publish to a single file
|
||||
if ($SINGLE_FILE_CAPABLE -contains $FRAMEWORK) {
|
||||
# Only include Debug if building all
|
||||
if ($USE_ALL.IsPresent) {
|
||||
dotnet publish NDecrypt\NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
|
||||
}
|
||||
dotnet publish NDecrypt\NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
|
||||
}
|
||||
else {
|
||||
# Only include Debug if building all
|
||||
if ($USE_ALL.IsPresent) {
|
||||
dotnet publish NDecrypt\NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
|
||||
}
|
||||
dotnet publish NDecrypt\NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Only create archives if requested
|
||||
if (!$NO_ARCHIVE.IsPresent) {
|
||||
# Create Program archives
|
||||
foreach ($FRAMEWORK in $FRAMEWORKS) {
|
||||
foreach ($RUNTIME in $RUNTIMES) {
|
||||
# Output the current build
|
||||
Write-Host "===== Archive Program - $FRAMEWORK, $RUNTIME ====="
|
||||
|
||||
# If we have an invalid combination of framework and runtime
|
||||
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
|
||||
Write-Host "Skipped due to invalid combination"
|
||||
continue
|
||||
}
|
||||
|
||||
# If we have Apple silicon but an unsupported framework
|
||||
if ($VALID_APPLE_FRAMEWORKS -notcontains $FRAMEWORK -and $RUNTIME -eq 'osx-arm64') {
|
||||
Write-Host "Skipped due to no Apple Silicon support"
|
||||
continue
|
||||
}
|
||||
|
||||
# Only include Debug if building all
|
||||
if ($USE_ALL.IsPresent) {
|
||||
Set-Location -Path $BUILD_FOLDER\NDecrypt\bin\Debug\${FRAMEWORK}\${RUNTIME}\publish\
|
||||
7z a -tzip $BUILD_FOLDER\NDecrypt_${FRAMEWORK}_${RUNTIME}_debug.zip *
|
||||
}
|
||||
|
||||
Set-Location -Path $BUILD_FOLDER\NDecrypt\bin\Release\${FRAMEWORK}\${RUNTIME}\publish\
|
||||
7z a -tzip $BUILD_FOLDER\NDecrypt_${FRAMEWORK}_${RUNTIME}_release.zip *
|
||||
}
|
||||
}
|
||||
|
||||
# Reset the directory
|
||||
Set-Location -Path $PSScriptRoot
|
||||
}
|
||||
Reference in New Issue
Block a user