mirror of
https://github.com/SabreTools/NDecrypt.git
synced 2026-02-04 13:45:30 +00:00
Compare commits
197 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ec0517866 | ||
|
|
d94b83aca6 | ||
|
|
e63792070a | ||
|
|
2bc04c9e15 | ||
|
|
632b8d8c53 | ||
|
|
eada29c89b | ||
|
|
818ca1ea03 | ||
|
|
eceb4ba22e | ||
|
|
8627455ed7 | ||
|
|
a7cc4ba4ed | ||
|
|
07676f4dcc | ||
|
|
1a69113af7 | ||
|
|
92adfa17df | ||
|
|
c48ac6e4cc | ||
|
|
4e778bc837 | ||
|
|
ccd04cb15e | ||
|
|
97c17bb9dc | ||
|
|
2447007900 | ||
|
|
f79a5fa246 | ||
|
|
16aeb6cafe | ||
|
|
c4d812c426 | ||
|
|
383572d5b5 | ||
|
|
415b3f17c1 | ||
|
|
06d52e04c5 | ||
|
|
a26e8b260c | ||
|
|
6c2edd225d | ||
|
|
1fa9345f06 | ||
|
|
e342448599 | ||
|
|
27431e05de | ||
|
|
56f8869d1d | ||
|
|
9e9efaf491 | ||
|
|
e8a0d706de | ||
|
|
9eb49d170f | ||
|
|
c31f95b85d | ||
|
|
dc1952d6f9 | ||
|
|
6ae797a6eb | ||
|
|
ed8e97aa0c | ||
|
|
cc16770126 | ||
|
|
8c186f969c | ||
|
|
ac542076aa | ||
|
|
94bebddda2 | ||
|
|
8c66c40b48 | ||
|
|
1fbacaffb0 | ||
|
|
0448682934 | ||
|
|
9e563785c9 | ||
|
|
7469c97c6b | ||
|
|
7c909af154 | ||
|
|
31b40e6f10 | ||
|
|
69a07cde85 | ||
|
|
b9b4d6876c | ||
|
|
bed965c6dd | ||
|
|
f01c04d796 | ||
|
|
5de970965e | ||
|
|
f3e4aa0b7e | ||
|
|
e2110e80c0 | ||
|
|
b0f2c6658a | ||
|
|
baf0b9045e | ||
|
|
b82eb54aab | ||
|
|
7d541b6c4b | ||
|
|
9d72a25cc2 | ||
|
|
79e8c1b6bf | ||
|
|
8f6df04e2c | ||
|
|
466144f8c9 | ||
|
|
629f2cc11e | ||
|
|
1ffd1a3bff | ||
|
|
d4bcd62941 | ||
|
|
702c31413b | ||
|
|
d4a6e902cf | ||
|
|
c00e72506e | ||
|
|
7ccc160e90 | ||
|
|
068b83da8e | ||
|
|
d429ea3e64 | ||
|
|
5bf1d85e05 | ||
|
|
32b655b1f9 | ||
|
|
e3a64ece96 | ||
|
|
74986afc38 | ||
|
|
3cc2bcf022 | ||
|
|
ebd13e3728 | ||
|
|
2d7a54ddb7 | ||
|
|
60d989d899 | ||
|
|
4de3cfe6fa | ||
|
|
50034975c3 | ||
|
|
ab4f076846 | ||
|
|
08840055a4 | ||
|
|
02eafc688a | ||
|
|
db7c7ee273 | ||
|
|
383e6bdfce | ||
|
|
169bd48762 | ||
|
|
a2ab3c25c9 | ||
|
|
abf0843d22 | ||
|
|
ca64a2575e | ||
|
|
035760b5f5 | ||
|
|
66e6a8cd4a | ||
|
|
d85e7656c4 | ||
|
|
942151b6d7 | ||
|
|
d7a51c7798 | ||
|
|
bd86b95494 | ||
|
|
5562b403a4 | ||
|
|
ead70df624 | ||
|
|
33e62e046b | ||
|
|
bb0b5a3d05 | ||
|
|
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 |
167
.editorconfig
Normal file
167
.editorconfig
Normal file
@@ -0,0 +1,167 @@
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# C# files
|
||||
[*.cs]
|
||||
|
||||
# Indentation and spacing
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
tab_width = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# New line preferences
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
max_line_length = unset
|
||||
|
||||
# using directive preferences
|
||||
csharp_using_directive_placement = outside_namespace
|
||||
dotnet_diagnostic.IDE0005.severity = error
|
||||
|
||||
# Code-block preferences
|
||||
csharp_style_namespace_declarations = block_scoped
|
||||
csharp_style_prefer_method_group_conversion = true
|
||||
csharp_style_prefer_top_level_statements = false
|
||||
|
||||
# Expression-level preferences
|
||||
csharp_prefer_simple_default_expression = true
|
||||
csharp_style_inlined_variable_declaration = true
|
||||
csharp_style_unused_value_assignment_preference = discard_variable
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable
|
||||
dotnet_diagnostic.IDE0001.severity = warning
|
||||
dotnet_diagnostic.IDE0002.severity = warning
|
||||
dotnet_diagnostic.IDE0004.severity = warning
|
||||
dotnet_diagnostic.IDE0010.severity = error
|
||||
dotnet_diagnostic.IDE0051.severity = warning
|
||||
dotnet_diagnostic.IDE0052.severity = warning
|
||||
dotnet_diagnostic.IDE0072.severity = warning
|
||||
dotnet_diagnostic.IDE0080.severity = warning
|
||||
dotnet_diagnostic.IDE0100.severity = error
|
||||
dotnet_diagnostic.IDE0110.severity = error
|
||||
dotnet_diagnostic.IDE0120.severity = warning
|
||||
dotnet_diagnostic.IDE0121.severity = warning
|
||||
dotnet_diagnostic.IDE0240.severity = error
|
||||
dotnet_diagnostic.IDE0241.severity = error
|
||||
dotnet_style_coalesce_expression = true
|
||||
dotnet_style_namespace_match_folder = false
|
||||
dotnet_style_null_propagation = true
|
||||
dotnet_style_prefer_auto_properties = true
|
||||
dotnet_style_prefer_collection_expression = when_types_loosely_match
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
|
||||
dotnet_style_prefer_compound_assignment = true
|
||||
csharp_style_prefer_simple_property_accessors = true
|
||||
dotnet_style_prefer_simplified_interpolation = true
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true
|
||||
csharp_style_prefer_unbound_generic_type_in_nameof = true
|
||||
|
||||
# Field preferences
|
||||
dotnet_diagnostic.IDE0044.severity = warning
|
||||
dotnet_style_readonly_field = true
|
||||
|
||||
# Language keyword vs. framework types preferences
|
||||
dotnet_diagnostic.IDE0049.severity = error
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true
|
||||
dotnet_style_predefined_type_for_member_access = true
|
||||
|
||||
# Modifier preferences
|
||||
csharp_prefer_static_local_function = true
|
||||
csharp_style_prefer_readonly_struct = true
|
||||
dotnet_diagnostic.IDE0036.severity = warning
|
||||
dotnet_diagnostic.IDE0040.severity = error
|
||||
dotnet_diagnostic.IDE0380.severity = error
|
||||
dotnet_style_require_accessibility_modifiers = always
|
||||
|
||||
# New-line preferences
|
||||
dotnet_diagnostic.IDE2000.severity = warning
|
||||
dotnet_diagnostic.IDE2002.severity = warning
|
||||
dotnet_diagnostic.IDE2003.severity = warning
|
||||
dotnet_diagnostic.IDE2004.severity = warning
|
||||
dotnet_diagnostic.IDE2005.severity = warning
|
||||
dotnet_diagnostic.IDE2006.severity = warning
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false
|
||||
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false
|
||||
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false
|
||||
dotnet_style_allow_multiple_blank_lines_experimental = false
|
||||
dotnet_style_allow_statement_immediately_after_block_experimental = false
|
||||
|
||||
# Null-checking preferences
|
||||
csharp_style_conditional_delegate_call = true
|
||||
|
||||
# Parameter preferences
|
||||
dotnet_code_quality_unused_parameters = all
|
||||
dotnet_diagnostic.IDE0280.severity = error
|
||||
|
||||
# Parentheses preferences
|
||||
dotnet_diagnostic.IDE0047.severity = warning
|
||||
dotnet_diagnostic.IDE0048.severity = warning
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
|
||||
dotnet_style_parentheses_in_other_operators = always_for_clarity
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
|
||||
|
||||
# Pattern-matching preferences
|
||||
dotnet_diagnostic.IDE0019.severity = warning
|
||||
dotnet_diagnostic.IDE0020.severity = warning
|
||||
dotnet_diagnostic.IDE0038.severity = warning
|
||||
dotnet_diagnostic.IDE0066.severity = none
|
||||
dotnet_diagnostic.IDE0083.severity = warning
|
||||
dotnet_diagnostic.IDE0260.severity = warning
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true
|
||||
csharp_style_prefer_not_pattern = true
|
||||
csharp_style_prefer_pattern_matching = true
|
||||
|
||||
# this. and Me. preferences
|
||||
dotnet_style_qualification_for_event = false
|
||||
dotnet_style_qualification_for_field = false
|
||||
dotnet_style_qualification_for_method = false
|
||||
dotnet_style_qualification_for_property = false
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_for_built_in_types = false
|
||||
csharp_style_var_when_type_is_apparent = true
|
||||
|
||||
# .NET formatting options
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = true
|
||||
|
||||
# C# formatting options
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = false
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_indent_switch_labels = true
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = true
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
48
.github/workflows/build_and_test.yml
vendored
Normal file
48
.github/workflows/build_and_test.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: Build and Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: |
|
||||
8.0.x
|
||||
9.0.x
|
||||
10.0.x
|
||||
|
||||
- name: Run tests
|
||||
run: dotnet test
|
||||
|
||||
- name: Run publish script
|
||||
run: ./publish-nix.sh -d
|
||||
|
||||
- name: Update rolling tag
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag -f rolling
|
||||
git push origin :refs/tags/rolling || true
|
||||
git push origin rolling --force
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.20.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: "*.nupkg,*.snupkg,*.zip"
|
||||
body: "Last built commit: ${{ github.sha }}"
|
||||
name: "Rolling Release"
|
||||
prerelease: True
|
||||
replacesArtifacts: True
|
||||
tag: "rolling"
|
||||
updateOnlyUnreleased: True
|
||||
20
.github/workflows/check_pr.yml
vendored
Normal file
20
.github/workflows/check_pr.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Build PR
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: |
|
||||
8.0.x
|
||||
9.0.x
|
||||
10.0.x
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -10,7 +10,7 @@
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/NDecrypt/bin/Debug/net6.0/NDecrypt.dll",
|
||||
"program": "${workspaceFolder}/NDecrypt/bin/Debug/net10.0/NDecrypt.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/NDecrypt",
|
||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018-2023 Matt Nadareski
|
||||
Copyright (c) 2018-2025 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:
|
||||
|
||||
|
||||
89
NDecrypt.Core/Configuration.cs
Normal file
89
NDecrypt.Core/Configuration.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
internal class Configuration
|
||||
{
|
||||
#region DS-Specific Fields
|
||||
|
||||
/// <summary>
|
||||
/// Encryption data taken from woodsec
|
||||
/// </summary>
|
||||
public string? NitroEncryptionData { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3DS-Specific Fields
|
||||
|
||||
/// <summary>
|
||||
/// AES Hardware Constant
|
||||
/// </summary>
|
||||
/// <remarks>generator</remarks>
|
||||
public string? AESHardwareConstant { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x18 (New 3DS 9.3)
|
||||
/// </summary>
|
||||
/// <remarks>slot0x18KeyX</remarks>
|
||||
public string? KeyX0x18 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x18 (New 3DS 9.3)
|
||||
/// </summary>
|
||||
public string? DevKeyX0x18 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x1B (New 3DS 9.6)
|
||||
/// </summary>
|
||||
/// <remarks>slot0x1BKeyX</remarks>
|
||||
public string? KeyX0x1B { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x1B New 3DS 9.6)
|
||||
/// </summary>
|
||||
public string? DevKeyX0x1B { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x25 (> 7.x)
|
||||
/// </summary>
|
||||
/// <remarks>slot0x25KeyX</remarks>
|
||||
public string? KeyX0x25 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x25 (> 7.x)
|
||||
/// </summary>
|
||||
public string? DevKeyX0x25 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x2C (< 6.x)
|
||||
/// </summary>
|
||||
/// <remarks>slot0x2CKeyX</remarks>
|
||||
public string? KeyX0x2C { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x2C (< 6.x)
|
||||
/// </summary>
|
||||
public string? DevKeyX0x2C { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
public static Configuration? Create(string path)
|
||||
{
|
||||
// Ensure the file exists
|
||||
if (!File.Exists(path))
|
||||
return null;
|
||||
|
||||
// Parse the configuration directly
|
||||
try
|
||||
{
|
||||
string contents = File.ReadAllText(path);
|
||||
return JsonConvert.DeserializeObject<Configuration?>(contents);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
168
NDecrypt.Core/DSTool.cs
Normal file
168
NDecrypt.Core/DSTool.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
#if NETFRAMEWORK || NETSTANDARD2_0_OR_GREATER
|
||||
using SabreTools.IO.Extensions;
|
||||
#endif
|
||||
using SabreTools.Serialization.Wrappers;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
public class DSTool : ITool
|
||||
{
|
||||
/// <summary>
|
||||
/// Decryption args to use while processing
|
||||
/// </summary>
|
||||
private readonly DecryptArgs _decryptArgs;
|
||||
|
||||
public DSTool(DecryptArgs decryptArgs)
|
||||
{
|
||||
_decryptArgs = decryptArgs;
|
||||
}
|
||||
|
||||
#region Encrypt
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool EncryptFile(string input, string? output, bool force)
|
||||
{
|
||||
try
|
||||
{
|
||||
// If the output is provided, copy the input file
|
||||
if (output is not null)
|
||||
File.Copy(input, output, overwrite: true);
|
||||
else
|
||||
output = input;
|
||||
|
||||
// Open the output file for processing
|
||||
using var reader = File.Open(output, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var nitro = Nitro.Create(reader);
|
||||
if (nitro is null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a DS or DSi Rom!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure the secure area was read
|
||||
if (nitro.SecureArea is null)
|
||||
{
|
||||
Console.WriteLine("Error: Invalid secure area!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Encrypt the secure area
|
||||
nitro.EncryptSecureArea(_decryptArgs.NitroEncryptionData, force);
|
||||
|
||||
// Write the encrypted secure area
|
||||
using var writer = File.Open(output, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
writer.Seek(0x4000, SeekOrigin.Begin);
|
||||
writer.Write(nitro.SecureArea);
|
||||
writer.Flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine($"An error has occurred. {output} 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;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Decrypt
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool DecryptFile(string input, string? output, bool force)
|
||||
{
|
||||
try
|
||||
{
|
||||
// If the output is provided, copy the input file
|
||||
if (output is not null)
|
||||
File.Copy(input, output, overwrite: true);
|
||||
else
|
||||
output = input;
|
||||
|
||||
// Open the read and write on the same file for inplace processing
|
||||
using var reader = File.Open(output, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var nitro = Nitro.Create(reader);
|
||||
if (nitro is null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a DS or DSi Rom!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure the secure area was read
|
||||
if (nitro.SecureArea is null)
|
||||
{
|
||||
Console.WriteLine("Error: Invalid secure area!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decrypt the secure area
|
||||
nitro.DecryptSecureArea(_decryptArgs.NitroEncryptionData, force);
|
||||
|
||||
// Write the decrypted secure area
|
||||
using var writer = File.Open(output, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
writer.Seek(0x4000, SeekOrigin.Begin);
|
||||
writer.Write(nitro.SecureArea);
|
||||
writer.Flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine($"An error has occurred. {output} 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;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Info
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? GetInformation(string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Open the file for reading
|
||||
using var input = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = Nitro.Create(input);
|
||||
if (cart?.Model is null)
|
||||
return "Error: Not a DS/DSi cart image!";
|
||||
|
||||
// Get a string builder for the status
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("\tSecure Area: ");
|
||||
|
||||
// Get the encryption status
|
||||
bool? decrypted = cart.CheckIfDecrypted(out _);
|
||||
if (decrypted is null)
|
||||
sb.Append("Empty");
|
||||
else if (decrypted == true)
|
||||
sb.Append("Decrypted");
|
||||
else
|
||||
sb.Append("Encrypted");
|
||||
|
||||
// Return the status for the secure area
|
||||
sb.Append(Environment.NewLine);
|
||||
return sb.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using NDecrypt.Core.Tools;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.IO.Encryption;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
@@ -10,16 +10,6 @@ namespace NDecrypt.Core
|
||||
{
|
||||
#region Common Fields
|
||||
|
||||
/// <summary>
|
||||
/// Flag to indicate operation
|
||||
/// </summary>
|
||||
public bool Encrypt { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Flag to indicate forcing the operation
|
||||
/// </summary>
|
||||
public bool Force { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Represents if all of the keys have been initialized properly
|
||||
/// </summary>
|
||||
@@ -27,267 +17,375 @@ namespace NDecrypt.Core
|
||||
|
||||
#endregion
|
||||
|
||||
#region DS-Specific Fields
|
||||
|
||||
/// <summary>
|
||||
/// Blowfish Table
|
||||
/// </summary>
|
||||
public byte[] NitroEncryptionData { get; private set; } = [];
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3DS-Specific Fields
|
||||
|
||||
/// <summary>
|
||||
/// Flag to indicate key types to use [3DS only]
|
||||
/// </summary>
|
||||
public bool Development { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Path to the keyfile [3DS only]
|
||||
/// </summary>
|
||||
public string KeyFile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag to indicate keyfile format to use [3DS only]
|
||||
/// </summary>
|
||||
public bool UseCitraKeyFile { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// AES Hardware Constant
|
||||
/// </summary>
|
||||
public BigInteger AESHardwareConstant { get; private set; }
|
||||
|
||||
#region Retail Keys
|
||||
public byte[] AESHardwareConstant { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x18 (New 3DS 9.3)
|
||||
/// </summary>
|
||||
public BigInteger KeyX0x18 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x1B (New 3DS 9.6)
|
||||
/// </summary>
|
||||
public BigInteger KeyX0x1B { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x25 (> 7.x)
|
||||
/// </summary>
|
||||
public BigInteger KeyX0x25 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x2C (< 6.x)
|
||||
/// </summary>
|
||||
public BigInteger KeyX0x2C { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Development Keys
|
||||
public byte[] KeyX0x18 { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x18 (New 3DS 9.3)
|
||||
/// </summary>
|
||||
public BigInteger DevKeyX0x18 { get; private set; }
|
||||
public byte[] DevKeyX0x18 { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x1B (New 3DS 9.6)
|
||||
/// </summary>
|
||||
public byte[] KeyX0x1B { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x1B New 3DS 9.6)
|
||||
/// </summary>
|
||||
public BigInteger DevKeyX0x1B { get; private set; }
|
||||
public byte[] DevKeyX0x1B { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x25 (> 7.x)
|
||||
/// </summary>
|
||||
public byte[] KeyX0x25 { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x25 (> 7.x)
|
||||
/// </summary>
|
||||
public BigInteger DevKeyX0x25 { get; private set; }
|
||||
public byte[] DevKeyX0x25 { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// KeyX 0x2C (< 6.x)
|
||||
/// </summary>
|
||||
public byte[] KeyX0x2C { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Dev KeyX 0x2C (< 6.x)
|
||||
/// </summary>
|
||||
public BigInteger DevKeyX0x2C { get; private set; }
|
||||
public byte[] DevKeyX0x2C { get; private set; } = [];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Test Values
|
||||
|
||||
/// <summary>
|
||||
/// Expected hash for NitroEncryptionData
|
||||
/// </summary>
|
||||
private static readonly byte[] ExpectedNitroSha512Hash =
|
||||
[
|
||||
0x1A, 0xD6, 0x40, 0x21, 0xFC, 0x3D, 0x1A, 0x9A,
|
||||
0x9B, 0xC0, 0x88, 0x8E, 0x2E, 0x68, 0xDE, 0x4E,
|
||||
0x8A, 0x60, 0x6B, 0x86, 0x63, 0x22, 0xD2, 0xC7,
|
||||
0xC6, 0xD7, 0xD6, 0xCE, 0x65, 0xF5, 0xBA, 0xA7,
|
||||
0xEA, 0x69, 0x63, 0x7E, 0xC9, 0xE4, 0x57, 0x7B,
|
||||
0x01, 0xFD, 0xCE, 0xC2, 0x26, 0x3B, 0xD9, 0x0D,
|
||||
0x84, 0x57, 0xC2, 0x00, 0xB8, 0x56, 0x9F, 0xE5,
|
||||
0x56, 0xDA, 0x8D, 0xDE, 0x84, 0xB8, 0x8E, 0xE4,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Initial value for key validation tests
|
||||
/// </summary>
|
||||
private static readonly byte[] TestIV =
|
||||
[
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Pattern to use for key validation tests
|
||||
/// </summary>
|
||||
private static readonly byte[] TestPattern =
|
||||
[
|
||||
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08,
|
||||
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
|
||||
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08,
|
||||
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
|
||||
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08,
|
||||
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
|
||||
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08,
|
||||
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Expected output value for KeyX0x18
|
||||
/// </summary>
|
||||
private static readonly byte[] ExpectedKeyX0x18 =
|
||||
[
|
||||
0x06, 0xF1, 0xB2, 0x3B, 0x12, 0xAD, 0x80, 0xC1,
|
||||
0x13, 0xC6, 0x18, 0x3D, 0x27, 0xB8, 0xB9, 0x95,
|
||||
0x49, 0x73, 0x59, 0x82, 0xEF, 0xFE, 0x16, 0x48,
|
||||
0x91, 0x2A, 0x89, 0x55, 0x9A, 0xDC, 0x3C, 0xA0,
|
||||
0x84, 0x46, 0x14, 0xE0, 0x16, 0x59, 0x8E, 0x4F,
|
||||
0xC2, 0x6C, 0x52, 0xA4, 0x7D, 0xAD, 0x4F, 0x23,
|
||||
0xF1, 0xC6, 0x99, 0x44, 0x39, 0xB7, 0x42, 0xF0,
|
||||
0x1F, 0xBB, 0x02, 0xF6, 0x0A, 0x8A, 0xC2, 0x9A,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Expected output value for DevKeyX0x18
|
||||
/// </summary>
|
||||
private static readonly byte[] ExpectedDevKeyX0x18 =
|
||||
[
|
||||
0x99, 0x6E, 0x3C, 0x54, 0x97, 0x3C, 0xEA, 0xE8,
|
||||
0xBA, 0xAE, 0x18, 0x5C, 0x93, 0x27, 0x65, 0x50,
|
||||
0xF6, 0x6D, 0x67, 0xD7, 0xEF, 0xBD, 0x7C, 0xCB,
|
||||
0x8A, 0xC1, 0x1A, 0x54, 0xFC, 0x3B, 0x8B, 0x3A,
|
||||
0x0E, 0xE5, 0xEF, 0x27, 0x4A, 0x73, 0x7E, 0x0A,
|
||||
0x2E, 0x2E, 0x9D, 0xAF, 0x6C, 0x03, 0xF2, 0x91,
|
||||
0xC4, 0xFA, 0x73, 0xFD, 0x6B, 0xA0, 0x07, 0xD4,
|
||||
0x75, 0x5B, 0x6F, 0x2E, 0x8B, 0x68, 0x4C, 0xD1,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Expected output value for KeyX0x1B
|
||||
/// </summary>
|
||||
private static readonly byte[] ExpectedKeyX0x1B =
|
||||
[
|
||||
0x0A, 0xE4, 0x79, 0x02, 0x1B, 0xFA, 0x25, 0x4B,
|
||||
0x2D, 0x92, 0x4F, 0xA8, 0x41, 0x59, 0xCE, 0x10,
|
||||
0x09, 0xE6, 0x08, 0x61, 0x23, 0xC7, 0xD2, 0x30,
|
||||
0x84, 0x37, 0xD5, 0x49, 0x42, 0x94, 0xB2, 0x70,
|
||||
0x6A, 0xF3, 0x75, 0xB0, 0x1F, 0x4F, 0xA1, 0xCE,
|
||||
0x03, 0xA2, 0x6A, 0x19, 0x5D, 0x32, 0x0D, 0xB5,
|
||||
0x79, 0xCD, 0xFD, 0xF0, 0xDE, 0x49, 0x26, 0x2D,
|
||||
0x29, 0x36, 0x30, 0x69, 0x8B, 0x45, 0xE1, 0xFC,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Expected output value for DevKeyX0x1B
|
||||
/// </summary>
|
||||
private static readonly byte[] ExpectedDevKeyX0x1B =
|
||||
[
|
||||
0x16, 0x4F, 0xD9, 0x58, 0xC9, 0x20, 0xB3, 0xED,
|
||||
0xC4, 0xEB, 0x57, 0x39, 0x10, 0xEF, 0xA8, 0xCC,
|
||||
0xE5, 0x49, 0xBF, 0x52, 0x10, 0xA9, 0xCC, 0xE1,
|
||||
0x65, 0x3B, 0x2D, 0x51, 0x45, 0xFB, 0x60, 0x52,
|
||||
0x3E, 0x29, 0xEB, 0xEB, 0x3F, 0xF2, 0x76, 0x08,
|
||||
0x00, 0x05, 0x7F, 0x64, 0x29, 0x4A, 0x17, 0x22,
|
||||
0x56, 0x7F, 0x49, 0x94, 0x1A, 0x8C, 0x56, 0x35,
|
||||
0x38, 0xBE, 0xA4, 0x2E, 0x58, 0xD3, 0x81, 0x8C,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Expected output value for KeyX0x25
|
||||
/// </summary>
|
||||
private static readonly byte[] ExpectedKeyX0x25 =
|
||||
[
|
||||
0x37, 0xBC, 0x73, 0xD6, 0xEE, 0x73, 0xE0, 0x94,
|
||||
0x42, 0x84, 0x74, 0xE5, 0xD8, 0xFB, 0x5F, 0x65,
|
||||
0xF4, 0xCF, 0x2E, 0xC1, 0x43, 0x48, 0x6C, 0xAA,
|
||||
0xC8, 0xF9, 0x96, 0xE6, 0x33, 0xDD, 0xE7, 0xBF,
|
||||
0xD2, 0x21, 0x89, 0x39, 0x13, 0xD1, 0xEC, 0xCA,
|
||||
0x1D, 0x5D, 0x1F, 0x77, 0x95, 0xD2, 0x8B, 0x27,
|
||||
0x92, 0x79, 0xC5, 0x1D, 0x72, 0xA7, 0x28, 0x57,
|
||||
0x41, 0x0E, 0x46, 0xB8, 0x80, 0x7B, 0x7C, 0x0D,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Expected output value for DevKeyX0x25
|
||||
/// </summary>
|
||||
private static readonly byte[] ExpectedDevKeyX0x25 =
|
||||
[
|
||||
0x71, 0x65, 0x30, 0xF2, 0x68, 0xEC, 0x65, 0x0A,
|
||||
0x8C, 0x9E, 0xC5, 0x5A, 0xFA, 0x37, 0x8E, 0xDA,
|
||||
0x7B, 0x58, 0x3B, 0x66, 0x7C, 0x9D, 0x16, 0xD9,
|
||||
0x2D, 0x8F, 0xCF, 0x04, 0x66, 0x7F, 0x27, 0x41,
|
||||
0xBF, 0x5F, 0x1E, 0x11, 0x4C, 0xD6, 0xB9, 0x0A,
|
||||
0xC5, 0x42, 0xCF, 0x2B, 0x87, 0x6B, 0xD4, 0x72,
|
||||
0x4D, 0x9C, 0x29, 0x2E, 0xF8, 0xB0, 0x6F, 0x22,
|
||||
0x35, 0x5B, 0x96, 0x83, 0xD1, 0xE4, 0x5E, 0xDB,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Expected output value for KeyX0x2C
|
||||
/// </summary>
|
||||
private static readonly byte[] ExpectedKeyX0x2C =
|
||||
[
|
||||
0xAE, 0x44, 0x20, 0xDB, 0xA5, 0x96, 0xDC, 0xF3,
|
||||
0xD8, 0x23, 0x9E, 0x3C, 0x44, 0x73, 0x3D, 0xCD,
|
||||
0x07, 0xD5, 0xF8, 0xD0, 0xC6, 0xB3, 0x5A, 0x80,
|
||||
0xB5, 0x5A, 0x55, 0x30, 0x5D, 0x4A, 0xBE, 0x61,
|
||||
0xBF, 0xEF, 0x64, 0x17, 0x28, 0xD6, 0x26, 0x52,
|
||||
0x42, 0x4D, 0x8F, 0x1C, 0xBC, 0x63, 0xD3, 0x91,
|
||||
0x7D, 0xA6, 0x4F, 0xAF, 0x26, 0x38, 0x60, 0xEE,
|
||||
0x79, 0x92, 0x2F, 0xD8, 0xCA, 0x4E, 0xE7, 0xEC,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Expected output value for DevKeyX0x2C
|
||||
/// </summary>
|
||||
private static readonly byte[] ExpectedDevKeyX0x2C =
|
||||
[
|
||||
0x5F, 0x73, 0xD5, 0x9A, 0x67, 0xFF, 0x8C, 0x12,
|
||||
0x31, 0x58, 0x0B, 0x58, 0x46, 0xFE, 0x05, 0x16,
|
||||
0x92, 0xE4, 0x84, 0x06, 0x18, 0x9B, 0x58, 0x91,
|
||||
0xE7, 0xF8, 0xCD, 0xA9, 0x95, 0xAC, 0x07, 0xCD,
|
||||
0x43, 0x20, 0x7A, 0x8C, 0xCC, 0xAB, 0x48, 0x50,
|
||||
0x29, 0x2F, 0x96, 0x73, 0xB0, 0xD9, 0xE5, 0xCB,
|
||||
0xE6, 0x9A, 0x0D, 0xF7, 0xD0, 0x1E, 0xC2, 0xEC,
|
||||
0xC1, 0xE2, 0x8E, 0xEE, 0x89, 0xB9, 0xB1, 0x97,
|
||||
];
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Setup all of the necessary constants
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
/// <param name="keyfile">Path to the keyfile</param>
|
||||
public DecryptArgs(string? config)
|
||||
{
|
||||
// If we're already attempted to set the constants, don't try to again
|
||||
if (IsReady != null)
|
||||
return;
|
||||
|
||||
// Read the proper keyfile format
|
||||
if (UseCitraKeyFile)
|
||||
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 (!File.Exists(keyfile))
|
||||
if (config is null || !File.Exists(config))
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (IniReader reader = new IniReader(keyfile))
|
||||
{
|
||||
// This is required to preserve sign for BigInteger
|
||||
byte[] signByte = new byte[] { 0x00 };
|
||||
|
||||
while (reader.ReadNextLine())
|
||||
{
|
||||
if (reader.KeyValuePair == null || string.IsNullOrWhiteSpace(reader.KeyValuePair?.Key))
|
||||
break;
|
||||
|
||||
var kvp = reader.KeyValuePair.Value;
|
||||
byte[] value = StringToByteArray(kvp.Value).Reverse().ToArray();
|
||||
byte[] valueWithSign = value.Concat(signByte).ToArray();
|
||||
|
||||
switch (kvp.Key)
|
||||
{
|
||||
// Hardware constant
|
||||
case "generator":
|
||||
AESHardwareConstant = new BigInteger(value);
|
||||
break;
|
||||
|
||||
// Retail Keys
|
||||
case "slot0x18KeyX":
|
||||
KeyX0x18 = new BigInteger(valueWithSign);
|
||||
break;
|
||||
case "slot0x1BKeyX":
|
||||
KeyX0x1B = new BigInteger(valueWithSign);
|
||||
break;
|
||||
case "slot0x25KeyX":
|
||||
KeyX0x25 = new BigInteger(valueWithSign);
|
||||
break;
|
||||
case "slot0x2CKeyX":
|
||||
KeyX0x2C = new BigInteger(valueWithSign);
|
||||
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
|
||||
// Try to read the configuration file
|
||||
var configObj = Configuration.Create(config);
|
||||
if (configObj is null)
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the fields from the configuration
|
||||
NitroEncryptionData = configObj.NitroEncryptionData.FromHexString() ?? [];
|
||||
AESHardwareConstant = configObj.AESHardwareConstant.FromHexString() ?? [];
|
||||
KeyX0x18 = configObj.KeyX0x18.FromHexString() ?? [];
|
||||
KeyX0x1B = configObj.KeyX0x1B.FromHexString() ?? [];
|
||||
KeyX0x25 = configObj.KeyX0x25.FromHexString() ?? [];
|
||||
KeyX0x2C = configObj.KeyX0x2C.FromHexString() ?? [];
|
||||
DevKeyX0x18 = configObj.DevKeyX0x18.FromHexString() ?? [];
|
||||
DevKeyX0x1B = configObj.DevKeyX0x1B.FromHexString() ?? [];
|
||||
DevKeyX0x25 = configObj.DevKeyX0x25.FromHexString() ?? [];
|
||||
DevKeyX0x2C = configObj.DevKeyX0x2C.FromHexString() ?? [];
|
||||
|
||||
IsReady = true;
|
||||
ValidateKeys();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Setup all of the necessary constants from keys.bin
|
||||
/// Validate that all keys provided are going to be valid
|
||||
/// </summary>
|
||||
/// <param name="keyfile">Path to keys.bin</param>
|
||||
/// <remarks>keys.bin should be in little endian format</remarks>
|
||||
private void InitKeysBin(string keyfile)
|
||||
/// <remarks>Does not know what the keys are, just the result</remarks>
|
||||
private void ValidateKeys()
|
||||
{
|
||||
if (!File.Exists(keyfile))
|
||||
// NitroEncryptionData
|
||||
if (NitroEncryptionData.Length > 0)
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (BinaryReader reader = new BinaryReader(File.Open(keyfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
|
||||
byte[]? actual = HashTool.GetByteArrayHashArray(NitroEncryptionData, HashType.SHA512);
|
||||
if (actual is null || !actual.EqualsExactly(ExpectedNitroSha512Hash))
|
||||
{
|
||||
// This is required to preserve sign for BigInteger
|
||||
byte[] signByte = new byte[] { 0x00 };
|
||||
|
||||
// Hardware constant
|
||||
AESHardwareConstant = new BigInteger(reader.ReadBytes(16));
|
||||
|
||||
// Retail keys
|
||||
KeyX0x18 = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
KeyX0x1B = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
KeyX0x25 = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
KeyX0x2C = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
|
||||
// Development keys
|
||||
DevKeyX0x18 = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
DevKeyX0x1B = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
DevKeyX0x25 = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
DevKeyX0x2C = new BigInteger(reader.ReadBytes(16).Concat(signByte).ToArray());
|
||||
Console.WriteLine($"NitroEncryptionData invalid value, disabling...");
|
||||
NitroEncryptionData = [];
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
||||
// KeyX0x18
|
||||
if (KeyX0x18.Length > 0)
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
var cipher = AESCTR.CreateEncryptionCipher(KeyX0x18, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!actual.EqualsExactly(ExpectedKeyX0x18))
|
||||
{
|
||||
Console.WriteLine($"KeyX0x18 invalid value, disabling...");
|
||||
KeyX0x18 = [];
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
// DevKeyX0x18
|
||||
if (DevKeyX0x18.Length > 0)
|
||||
{
|
||||
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
||||
var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x18, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!actual.EqualsExactly(ExpectedDevKeyX0x18))
|
||||
{
|
||||
Console.WriteLine($"DevKeyX0x18 invalid value, disabling...");
|
||||
DevKeyX0x18 = [];
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
// KeyX0x1B
|
||||
if (KeyX0x1B.Length > 0)
|
||||
{
|
||||
var cipher = AESCTR.CreateEncryptionCipher(KeyX0x1B, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!actual.EqualsExactly(ExpectedKeyX0x1B))
|
||||
{
|
||||
Console.WriteLine($"KeyX0x1B invalid value, disabling...");
|
||||
KeyX0x1B = [];
|
||||
}
|
||||
}
|
||||
|
||||
// DevKeyX0x1B
|
||||
if (DevKeyX0x1B.Length > 0)
|
||||
{
|
||||
var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x1B, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!actual.EqualsExactly(ExpectedDevKeyX0x1B))
|
||||
{
|
||||
Console.WriteLine($"DevKeyX0x1B invalid value, disabling...");
|
||||
DevKeyX0x1B = [];
|
||||
}
|
||||
}
|
||||
|
||||
// KeyX0x25
|
||||
if (KeyX0x25.Length > 0)
|
||||
{
|
||||
var cipher = AESCTR.CreateEncryptionCipher(KeyX0x25, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!actual.EqualsExactly(ExpectedKeyX0x25))
|
||||
{
|
||||
Console.WriteLine($"KeyX0x25 invalid value, disabling...");
|
||||
KeyX0x25 = [];
|
||||
}
|
||||
}
|
||||
|
||||
// DevKeyX0x25
|
||||
if (DevKeyX0x25.Length > 0)
|
||||
{
|
||||
var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x25, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!actual.EqualsExactly(ExpectedDevKeyX0x25))
|
||||
{
|
||||
Console.WriteLine($"DevKeyX0x25 invalid value, disabling...");
|
||||
DevKeyX0x25 = [];
|
||||
}
|
||||
}
|
||||
|
||||
// KeyX0x2C
|
||||
if (KeyX0x2C.Length > 0)
|
||||
{
|
||||
var cipher = AESCTR.CreateEncryptionCipher(KeyX0x2C, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!actual.EqualsExactly(ExpectedKeyX0x2C))
|
||||
{
|
||||
Console.WriteLine($"KeyX0x2C invalid value, disabling...");
|
||||
KeyX0x2C = [];
|
||||
}
|
||||
}
|
||||
|
||||
// DevKeyX0x2C
|
||||
if (DevKeyX0x2C.Length > 0)
|
||||
{
|
||||
var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x2C, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!actual.EqualsExactly(ExpectedDevKeyX0x2C))
|
||||
{
|
||||
Console.WriteLine($"DevKeyX0x2C invalid value, disabling...");
|
||||
DevKeyX0x2C = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
public static class Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Add an integer value to a number represented by a byte array
|
||||
/// </summary>
|
||||
/// <param name="input">Byte array to add to</param>
|
||||
/// <param name="add">Amount to add</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
public static byte[] AddToByteArray(byte[] input, int add)
|
||||
{
|
||||
int len = input.Length;
|
||||
var bigint = new BigInteger(input.Reverse().ToArray());
|
||||
bigint += add;
|
||||
var arr = bigint.ToByteArray().Reverse().ToArray();
|
||||
|
||||
if (arr.Length < len)
|
||||
{
|
||||
byte[] temp = new byte[len];
|
||||
for (int i = 0; i < (len - arr.Length); i++)
|
||||
temp[i] = 0x00;
|
||||
|
||||
Array.Copy(arr, 0, temp, len - arr.Length, arr.Length);
|
||||
arr = temp;
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create AES cipher and intialize
|
||||
/// </summary>
|
||||
/// <param name="key">BigInteger representation of 128-bit encryption key</param>
|
||||
/// <param name="iv">AES initial value for counter</param>
|
||||
/// <param name="encrypt">True if cipher is created for encryption, false otherwise</param>
|
||||
/// <returns>Initialized AES cipher</returns>
|
||||
public static IBufferedCipher CreateAESCipher(BigInteger key, byte[] iv, bool encrypt)
|
||||
{
|
||||
var cipher = CipherUtilities.GetCipher("AES/CTR");
|
||||
cipher.Init(encrypt, new ParametersWithIV(new KeyParameter(TakeSixteen(key)), iv));
|
||||
return cipher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a rotate left on a BigInteger
|
||||
/// </summary>
|
||||
/// <param name="val">BigInteger value to rotate</param>
|
||||
/// <param name="r_bits">Number of bits to rotate</param>
|
||||
/// <param name="max_bits">Maximum number of bits to rotate on</param>
|
||||
/// <returns>Rotated BigInteger value</returns>
|
||||
public static BigInteger RotateLeft(BigInteger val, int r_bits, int max_bits)
|
||||
{
|
||||
return (val << r_bits % max_bits) & (BigInteger.Pow(2, max_bits) - 1) | ((val & (BigInteger.Pow(2, max_bits) - 1)) >> (max_bits - (r_bits % max_bits)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a 16-byte array representation of a BigInteger
|
||||
/// </summary>
|
||||
/// <param name="input">BigInteger value to convert</param>
|
||||
/// <returns>16-byte array representing the BigInteger</returns>
|
||||
private static byte[] TakeSixteen(BigInteger input)
|
||||
{
|
||||
var arr = input.ToByteArray().Take(16).Reverse().ToArray();
|
||||
|
||||
if (arr.Length < 16)
|
||||
{
|
||||
byte[] temp = new byte[16];
|
||||
for (int i = 0; i < (16 - arr.Length); i++)
|
||||
temp[i] = 0x00;
|
||||
|
||||
Array.Copy(arr, 0, temp, 16 - arr.Length, arr.Length);
|
||||
arr = temp;
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,31 @@
|
||||
{
|
||||
public interface ITool
|
||||
{
|
||||
bool ProcessFile();
|
||||
/// <summary>
|
||||
/// Attempts to encrypt an input file
|
||||
/// </summary>
|
||||
/// <param name="input">Name of the file to encrypt</param>
|
||||
/// <param name="output">Optional name of the file to write to</param>
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <returns>True if the file could be encrypted, false otherwise</returns>
|
||||
/// <remarks>If an output filename is not provided, the input file will be overwritten</remarks>
|
||||
public bool EncryptFile(string input, string? output, bool force);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to decrypt an input file
|
||||
/// </summary>
|
||||
/// <param name="input">Name of the file to decrypt</param>
|
||||
/// <param name="output">Optional name of the file to write to</param>
|
||||
/// <param name="force">Indicates if the operation should be forced</param>
|
||||
/// <returns>True if the file could be decrypted, false otherwise</returns>
|
||||
/// <remarks>If an output filename is not provided, the input file will be overwritten</remarks>
|
||||
public bool DecryptFile(string input, string? output, bool force);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get information on an input file
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of the file get information on</param>
|
||||
/// <returns>String representing the info, null on error</returns>
|
||||
public string? GetInformation(string filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net48;net6.0</TargetFrameworks>
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Copyright>Copyright (c)2018-2023 Matt Nadareski</Copyright>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<RepositoryUrl>https://github.com/SabreTools/NDecrypt</RepositoryUrl>
|
||||
<Version>0.2.5</Version>
|
||||
<AssemblyVersion>$(Version)</AssemblyVersion>
|
||||
<FileVersion>$(Version)</FileVersion>
|
||||
<IncludeSource>true</IncludeSource>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>0.5.0</VersionPrefix>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.NetCore" Version="1.9.0" />
|
||||
</ItemGroup>
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Description>Common code for all NDecrypt processors</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2019-2025</Copyright>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/SabreTools/NDecrypt</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.Hashing" Version="[1.6.0]" />
|
||||
<PackageReference Include="SabreTools.IO" Version="[1.9.0]" />
|
||||
<PackageReference Include="SabreTools.Serialization" Version="[2.2.1]" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
138
NDecrypt.Core/PartitionKeys.cs
Normal file
138
NDecrypt.Core/PartitionKeys.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using SabreTools.Data.Models.N3DS;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
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 is not null && signature.Length < 16)
|
||||
throw new ArgumentOutOfRangeException(nameof(signature), $"{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 is not null)
|
||||
Array.Copy(signature, KeyY, 16);
|
||||
|
||||
// Set the standard normal key values
|
||||
NormalKey = new byte[16];
|
||||
|
||||
NormalKey2C = KeyX2C.RotateLeft(2);
|
||||
NormalKey2C = NormalKey2C.Xor(KeyY);
|
||||
NormalKey2C = NormalKey2C.Add(add: args.AESHardwareConstant);
|
||||
NormalKey2C = NormalKey2C.RotateLeft(87);
|
||||
|
||||
// Special case for zero-key
|
||||
#if NET20 || NET35
|
||||
if ((masks & BitMasks.FixedCryptoKey) > 0)
|
||||
#else
|
||||
if (masks.HasFlag(BitMasks.FixedCryptoKey))
|
||||
#endif
|
||||
{
|
||||
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 = KeyX.RotateLeft(2);
|
||||
NormalKey = NormalKey.Xor(KeyY);
|
||||
NormalKey = NormalKey.Add(args.AESHardwareConstant);
|
||||
NormalKey = NormalKey.RotateLeft(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 NET20 || NET35
|
||||
if ((masks & BitMasks.FixedCryptoKey) > 0)
|
||||
#else
|
||||
if (masks.HasFlag(BitMasks.FixedCryptoKey))
|
||||
#endif
|
||||
{
|
||||
NormalKey = new byte[16];
|
||||
return;
|
||||
}
|
||||
|
||||
// Encrypting RomFS for partitions 1 and up always use Key0x2C
|
||||
KeyX = _development ? _decryptArgs.DevKeyX0x2C : _decryptArgs.KeyX0x2C;
|
||||
|
||||
NormalKey = KeyX.RotateLeft(2);
|
||||
NormalKey = NormalKey.Xor(KeyY);
|
||||
NormalKey = NormalKey.Add(_decryptArgs.AESHardwareConstant);
|
||||
NormalKey = NormalKey.RotateLeft(87);
|
||||
}
|
||||
}
|
||||
}
|
||||
929
NDecrypt.Core/ThreeDSTool.cs
Normal file
929
NDecrypt.Core/ThreeDSTool.cs
Normal file
@@ -0,0 +1,929 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.Data.Models.N3DS;
|
||||
using SabreTools.IO.Encryption;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Serialization.Wrappers;
|
||||
using static SabreTools.Data.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;
|
||||
}
|
||||
|
||||
#region Decrypt
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool DecryptFile(string input, string? output, 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
|
||||
{
|
||||
// If the output is provided, copy the input file
|
||||
if (output is not null)
|
||||
File.Copy(input, output, overwrite: true);
|
||||
else
|
||||
output = input;
|
||||
|
||||
// Open the output file for processing
|
||||
using var reader = File.Open(output, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var writer = File.Open(output, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = N3DS.Create(reader);
|
||||
if (cart?.Model is null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS cart image!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decrypt all 8 NCCH partitions
|
||||
DecryptAllPartitions(cart, force, reader, writer);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine($"An error has occurred. {output} 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void DecryptAllPartitions(N3DS cart, bool force, Stream reader, Stream writer)
|
||||
{
|
||||
// Check the partitions table
|
||||
if (cart.PartitionsTable is null || cart.Partitions is 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 is null || partition.MagicID != NCCHMagicNumber)
|
||||
{
|
||||
Console.WriteLine($"Partition {p} Not found... Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check the partition has data
|
||||
var partitionEntry = cart.PartitionsTable[p];
|
||||
if (partitionEntry is 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, reader, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void DecryptPartition(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Determine the keys needed for this partition
|
||||
SetDecryptionKeys(cart, index);
|
||||
|
||||
// Decrypt the parts of the partition
|
||||
DecryptExtendedHeader(cart, index, reader, writer);
|
||||
DecryptExeFS(cart, index, reader, writer);
|
||||
DecryptRomFS(cart, index, reader, writer);
|
||||
|
||||
// Update the flags
|
||||
UpdateDecryptCryptoAndMasks(cart, index, writer);
|
||||
}
|
||||
|
||||
/// <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 is 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="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private bool DecryptExtendedHeader(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
if (partitionOffset == 0 || partitionOffset > reader.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
|
||||
reader.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
writer.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {index}: Decrypting - ExHeader");
|
||||
|
||||
// Create the Plain AES cipher for this partition
|
||||
var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, cart.PlainIV(index));
|
||||
|
||||
// Process the extended header
|
||||
AESCTR.PerformOperation(Constants.CXTExtendedDataHeaderLength, cipher, reader, writer, 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
|
||||
reader.Seek(0, SeekOrigin.Begin);
|
||||
#endif
|
||||
writer.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="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private bool DecryptExeFS(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Validate the ExeFS
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsHeaderOffset == 0 || exeFsHeaderOffset > reader.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, reader, writer);
|
||||
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
if (cart.GetCryptoMethod(index) != CryptoMethod.Original)
|
||||
DecryptExeFSFileEntries(cart, index, reader, writer);
|
||||
|
||||
// Get the ExeFS files offset
|
||||
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
|
||||
|
||||
// Seek to the ExeFS
|
||||
reader.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
writer.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
uint ctroffsetE = cart.MediaUnitSize / 0x10;
|
||||
byte[] exefsIVWithOffset = cart.ExeFSIV(index).Add(ctroffsetE);
|
||||
var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffset);
|
||||
|
||||
// Setup and perform the decryption
|
||||
exeFsSize -= cart.MediaUnitSize;
|
||||
AESCTR.PerformOperation(exeFsSize,
|
||||
cipher,
|
||||
reader,
|
||||
writer,
|
||||
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="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void DecryptExeFSFilenameTable(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Get ExeFS offset
|
||||
uint exeFsOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsOffset == 0 || exeFsOffset > reader.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Seek to the ExeFS header
|
||||
reader.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
writer.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {index} ExeFS: Decrypting - ExeFS Filename Table");
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, cart.ExeFSIV(index));
|
||||
|
||||
// Process the filename table
|
||||
byte[] readBytes = reader.ReadBytes((int)cart.MediaUnitSize);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
writer.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
|
||||
reader.Seek(0, SeekOrigin.Begin);
|
||||
#endif
|
||||
writer.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="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void DecryptExeFSFileEntries(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
if (cart.ExeFSHeaders is null || index < 0 || index > cart.ExeFSHeaders.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reread the decrypted ExeFS header
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
reader.Seek(exeFsHeaderOffset, SeekOrigin.Begin);
|
||||
cart.ExeFSHeaders[index] = SabreTools.Serialization.Readers.N3DS.ParseExeFSHeader(reader);
|
||||
|
||||
// Get the ExeFS header
|
||||
var exeFsHeader = cart.ExeFSHeaders[index];
|
||||
if (exeFsHeader?.FileHeaders is null)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS header does not exist. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the ExeFS files offset
|
||||
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 is null)
|
||||
continue;
|
||||
|
||||
// Create the ExeFS AES ciphers for this partition
|
||||
uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize) / 0x10;
|
||||
byte[] exefsIVWithOffsetForHeader = cart.ExeFSIV(index).Add(ctroffset);
|
||||
var firstCipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey, exefsIVWithOffsetForHeader);
|
||||
var secondCipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffsetForHeader);
|
||||
|
||||
// Seek to the file entry
|
||||
reader.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
writer.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
// Setup and perform the encryption
|
||||
AESCTR.PerformOperation(fileHeader.FileSize,
|
||||
firstCipher,
|
||||
secondCipher,
|
||||
reader,
|
||||
writer,
|
||||
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="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private bool DecryptRomFS(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Validate the RomFS
|
||||
uint romFsOffset = cart.GetRomFSOffset(index);
|
||||
if (romFsOffset == 0 || romFsOffset > reader.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
|
||||
reader.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
writer.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the RomFS AES cipher for this partition
|
||||
var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey, cart.RomFSIV(index));
|
||||
|
||||
// Setup and perform the decryption
|
||||
AESCTR.PerformOperation(romFsSize,
|
||||
cipher,
|
||||
reader,
|
||||
writer,
|
||||
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="writer">Stream representing the output</param>
|
||||
private static void UpdateDecryptCryptoAndMasks(N3DS cart, int index, Stream writer)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
|
||||
// Seek to the CryptoMethod location
|
||||
writer.Seek(partitionOffset + 0x18B, SeekOrigin.Begin);
|
||||
|
||||
// Write the new CryptoMethod
|
||||
writer.Write((byte)CryptoMethod.Original);
|
||||
writer.Flush();
|
||||
|
||||
// Seek to the BitMasks location
|
||||
writer.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;
|
||||
writer.Write((byte)flag);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Encrypt
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool EncryptFile(string input, string? output, 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
|
||||
{
|
||||
// If the output is provided, copy the input file
|
||||
if (output is not null)
|
||||
File.Copy(input, output, overwrite: true);
|
||||
else
|
||||
output = input;
|
||||
|
||||
// Open the output file for processing
|
||||
using var reader = File.Open(output, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var writer = File.Open(output, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = N3DS.Create(reader);
|
||||
if (cart?.Model is null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS cart image!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Encrypt all 8 NCCH partitions
|
||||
EncryptAllPartitions(cart, force, reader, writer);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine($"An error has occurred. {output} 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void EncryptAllPartitions(N3DS cart, bool force, Stream reader, Stream writer)
|
||||
{
|
||||
// Check the partitions table
|
||||
if (cart.PartitionsTable is null || cart.Partitions is 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 is null || partition.MagicID != NCCHMagicNumber)
|
||||
{
|
||||
Console.WriteLine($"Partition {p} Not found... Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check the partition has data
|
||||
var partitionEntry = cart.PartitionsTable[p];
|
||||
if (partitionEntry is 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, reader, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void EncryptPartition(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Determine the keys needed for this partition
|
||||
SetEncryptionKeys(cart, index);
|
||||
|
||||
// Encrypt the parts of the partition
|
||||
EncryptExtendedHeader(cart, index, reader, writer);
|
||||
EncryptExeFS(cart, index, reader, writer);
|
||||
EncryptRomFS(cart, index, reader, writer);
|
||||
|
||||
// Update the flags
|
||||
UpdateEncryptCryptoAndMasks(cart, index, writer);
|
||||
}
|
||||
|
||||
/// <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 is null)
|
||||
return;
|
||||
|
||||
// Get the backup header
|
||||
var backupHeader = cart.BackupHeader;
|
||||
if (backupHeader?.Flags is 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="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private bool EncryptExtendedHeader(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
if (partitionOffset == 0 || partitionOffset > reader.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
|
||||
reader.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
writer.Seek(partitionOffset + 0x200, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {index}: Encrypting - ExHeader");
|
||||
|
||||
// Create the Plain AES cipher for this partition
|
||||
var cipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey2C, cart.PlainIV(index));
|
||||
|
||||
// Process the extended header
|
||||
AESCTR.PerformOperation(Constants.CXTExtendedDataHeaderLength, cipher, reader, writer, 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
|
||||
reader.Seek(0, SeekOrigin.Begin);
|
||||
#endif
|
||||
writer.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="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private bool EncryptExeFS(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
if (cart.ExeFSHeaders is 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 is 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, reader, writer);
|
||||
|
||||
// Encrypt the filename table
|
||||
EncryptExeFSFilenameTable(cart, index, reader, writer);
|
||||
|
||||
// Get the ExeFS files offset
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
uint exeFsFilesOffset = exeFsHeaderOffset + cart.MediaUnitSize;
|
||||
|
||||
// Seek to the ExeFS
|
||||
reader.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
writer.Seek(exeFsFilesOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
uint ctroffsetE = cart.MediaUnitSize / 0x10;
|
||||
byte[] exefsIVWithOffset = cart.ExeFSIV(index).Add(ctroffsetE);
|
||||
var cipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffset);
|
||||
|
||||
// Setup and perform the encryption
|
||||
uint exeFsSize = cart.GetExeFSSize(index) - cart.MediaUnitSize;
|
||||
AESCTR.PerformOperation(exeFsSize,
|
||||
cipher,
|
||||
reader,
|
||||
writer,
|
||||
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="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void EncryptExeFSFilenameTable(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Get ExeFS offset
|
||||
uint exeFsOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsOffset == 0 || exeFsOffset > reader.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Seek to the ExeFS header
|
||||
reader.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
writer.Seek(exeFsOffset, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {index} ExeFS: Encrypting - ExeFS Filename Table");
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
var cipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey2C, cart.ExeFSIV(index));
|
||||
|
||||
// Process the filename table
|
||||
byte[] readBytes = reader.ReadBytes((int)cart.MediaUnitSize);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
writer.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
|
||||
reader.Seek(0, SeekOrigin.Begin);
|
||||
#endif
|
||||
writer.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="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void EncryptExeFSFileEntries(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Get ExeFS offset
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
if (exeFsHeaderOffset == 0 || exeFsHeaderOffset > reader.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 is 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 is null)
|
||||
continue;
|
||||
|
||||
// Create the ExeFS AES ciphers for this partition
|
||||
uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize) / 0x10;
|
||||
byte[] exefsIVWithOffsetForHeader = cart.ExeFSIV(index).Add(ctroffset);
|
||||
var firstCipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey, exefsIVWithOffsetForHeader);
|
||||
var secondCipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffsetForHeader);
|
||||
|
||||
// Seek to the file entry
|
||||
reader.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
writer.Seek(exeFsFilesOffset + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
// Setup and perform the encryption
|
||||
AESCTR.PerformOperation(fileHeader.FileSize,
|
||||
firstCipher,
|
||||
secondCipher,
|
||||
reader,
|
||||
writer,
|
||||
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="reader">Stream representing the input</param>
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private bool EncryptRomFS(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
// Validate the RomFS
|
||||
uint romFsOffset = cart.GetRomFSOffset(index);
|
||||
if (romFsOffset == 0 || romFsOffset > reader.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
|
||||
reader.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
writer.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 = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey, cart.RomFSIV(index));
|
||||
|
||||
// Setup and perform the decryption
|
||||
AESCTR.PerformOperation(romFsSize,
|
||||
cipher,
|
||||
reader,
|
||||
writer,
|
||||
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="writer">Stream representing the output</param>
|
||||
private static void UpdateEncryptCryptoAndMasks(N3DS cart, int index, Stream writer)
|
||||
{
|
||||
// Get required offsets
|
||||
uint partitionOffset = cart.GetPartitionOffset(index);
|
||||
|
||||
// Get the backup header
|
||||
var backupHeader = cart.BackupHeader;
|
||||
if (backupHeader?.Flags is null)
|
||||
return;
|
||||
|
||||
// Seek to the CryptoMethod location
|
||||
writer.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;
|
||||
writer.Write(cryptoMethod);
|
||||
writer.Flush();
|
||||
|
||||
// Seek to the BitMasks location
|
||||
writer.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;
|
||||
writer.Write((byte)flag);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Info
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? GetInformation(string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Open the file for reading
|
||||
using var input = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = N3DS.Create(input);
|
||||
if (cart?.Model is null)
|
||||
return "Error: Not a 3DS cart image!";
|
||||
|
||||
// Get a string builder for the status
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// Iterate over all 8 NCCH partitions
|
||||
for (int p = 0; p < 8; p++)
|
||||
{
|
||||
bool decrypted = cart.PossiblyDecrypted(p);
|
||||
sb.AppendLine($"\tPartition {p}: {(decrypted ? "Decrypted" : "Encrypted")}");
|
||||
}
|
||||
|
||||
// Return the status for all partitions
|
||||
return sb.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace NDecrypt.Core.Tools
|
||||
{
|
||||
/// <summary>
|
||||
/// Available hashing types
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum Hash
|
||||
{
|
||||
CRC = 1 << 0,
|
||||
MD5 = 1 << 1,
|
||||
SHA1 = 1 << 2,
|
||||
SHA256 = 1 << 3,
|
||||
SHA384 = 1 << 4,
|
||||
SHA512 = 1 << 5,
|
||||
|
||||
// Special combinations
|
||||
Standard = CRC | MD5 | SHA1,
|
||||
DeepHashes = SHA256 | SHA384 | SHA512,
|
||||
SecureHashes = MD5 | SHA1 | SHA256 | SHA384 | SHA512,
|
||||
All = CRC | MD5 | SHA1 | SHA256 | SHA384 | SHA512,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Different types of INI rows being parsed
|
||||
/// </summary>
|
||||
public enum IniRowType
|
||||
{
|
||||
None,
|
||||
SectionHeader,
|
||||
KeyValue,
|
||||
Comment,
|
||||
Invalid,
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace NDecrypt.Core.Tools
|
||||
{
|
||||
/// <summary>
|
||||
/// Async hashing class wraper
|
||||
/// </summary>
|
||||
public class Hasher
|
||||
{
|
||||
public Hash HashType { get; private set; }
|
||||
private IDisposable _hasher;
|
||||
|
||||
public Hasher(Hash hashType)
|
||||
{
|
||||
this.HashType = hashType;
|
||||
GetHasher();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate the correct hashing class based on the hash type
|
||||
/// </summary>
|
||||
private void GetHasher()
|
||||
{
|
||||
switch (HashType)
|
||||
{
|
||||
case Hash.CRC:
|
||||
_hasher = new OptimizedCRC.OptimizedCRC();
|
||||
break;
|
||||
|
||||
case Hash.MD5:
|
||||
_hasher = MD5.Create();
|
||||
break;
|
||||
|
||||
case Hash.SHA1:
|
||||
_hasher = SHA1.Create();
|
||||
break;
|
||||
|
||||
case Hash.SHA256:
|
||||
_hasher = SHA256.Create();
|
||||
break;
|
||||
|
||||
case Hash.SHA384:
|
||||
_hasher = SHA384.Create();
|
||||
break;
|
||||
|
||||
case Hash.SHA512:
|
||||
_hasher = SHA512.Create();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_hasher.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a buffer of some length with the internal hash algorithm
|
||||
/// </summary>
|
||||
public void Process(byte[] buffer, int size)
|
||||
{
|
||||
switch (HashType)
|
||||
{
|
||||
case Hash.CRC:
|
||||
(_hasher as OptimizedCRC.OptimizedCRC).Update(buffer, 0, size);
|
||||
break;
|
||||
|
||||
case Hash.MD5:
|
||||
case Hash.SHA1:
|
||||
case Hash.SHA256:
|
||||
case Hash.SHA384:
|
||||
case Hash.SHA512:
|
||||
(_hasher as HashAlgorithm).TransformBlock(buffer, 0, size, null, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Terminate the internal hash algorigthm
|
||||
/// </summary>
|
||||
public void Terminate()
|
||||
{
|
||||
byte[] emptyBuffer = new byte[0];
|
||||
switch (HashType)
|
||||
{
|
||||
case Hash.CRC:
|
||||
(_hasher as OptimizedCRC.OptimizedCRC).Update(emptyBuffer, 0, 0);
|
||||
break;
|
||||
|
||||
case Hash.MD5:
|
||||
case Hash.SHA1:
|
||||
case Hash.SHA256:
|
||||
case Hash.SHA384:
|
||||
case Hash.SHA512:
|
||||
(_hasher as HashAlgorithm).TransformFinalBlock(emptyBuffer, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get internal hash as a byte array
|
||||
/// </summary>
|
||||
public byte[] GetHash()
|
||||
{
|
||||
switch (HashType)
|
||||
{
|
||||
case Hash.CRC:
|
||||
return BitConverter.GetBytes((_hasher as OptimizedCRC.OptimizedCRC).Value).Reverse().ToArray();
|
||||
|
||||
case Hash.MD5:
|
||||
case Hash.SHA1:
|
||||
case Hash.SHA256:
|
||||
case Hash.SHA384:
|
||||
case Hash.SHA512:
|
||||
return (_hasher as HashAlgorithm).Hash;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NDecrypt.Core.Tools
|
||||
{
|
||||
public class IniReader : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal stream reader for inputting
|
||||
/// </summary>
|
||||
private readonly StreamReader sr;
|
||||
|
||||
/// <summary>
|
||||
/// Get if at end of stream
|
||||
/// </summary>
|
||||
public bool EndOfStream
|
||||
{
|
||||
get
|
||||
{
|
||||
return sr?.EndOfStream ?? true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contents of the currently read line as a key value pair
|
||||
/// </summary>
|
||||
public KeyValuePair<string, string>? KeyValuePair { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Contents of the current line, unprocessed
|
||||
/// </summary>
|
||||
public string CurrentLine { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Get the current line number
|
||||
/// </summary>
|
||||
public long LineNumber { get; private set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Current row type
|
||||
/// </summary>
|
||||
public IniRowType RowType { get; private set; } = IniRowType.None;
|
||||
|
||||
/// <summary>
|
||||
/// Current section being read
|
||||
/// </summary>
|
||||
public string Section { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Validate that rows are in key=value format
|
||||
/// </summary>
|
||||
public bool ValidateRows { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for reading from a file
|
||||
/// </summary>
|
||||
public IniReader(string filename)
|
||||
{
|
||||
sr = new StreamReader(filename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for reading from a stream
|
||||
/// </summary>
|
||||
public IniReader(Stream stream, Encoding encoding)
|
||||
{
|
||||
sr = new StreamReader(stream, encoding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the next line in the INI file
|
||||
/// </summary>
|
||||
public bool ReadNextLine()
|
||||
{
|
||||
if (!(sr.BaseStream?.CanRead ?? false) || sr.EndOfStream)
|
||||
return false;
|
||||
|
||||
CurrentLine = sr.ReadLine().Trim();
|
||||
LineNumber++;
|
||||
ProcessLine();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the current line and extract out values
|
||||
/// </summary>
|
||||
private void ProcessLine()
|
||||
{
|
||||
// Comment
|
||||
if (CurrentLine.StartsWith(";"))
|
||||
{
|
||||
KeyValuePair = null;
|
||||
RowType = IniRowType.Comment;
|
||||
}
|
||||
|
||||
// Section
|
||||
else if (CurrentLine.StartsWith("[") && CurrentLine.EndsWith("]"))
|
||||
{
|
||||
KeyValuePair = null;
|
||||
RowType = IniRowType.SectionHeader;
|
||||
Section = CurrentLine.TrimStart('[').TrimEnd(']');
|
||||
}
|
||||
|
||||
// KeyValuePair
|
||||
else if (CurrentLine.Contains("="))
|
||||
{
|
||||
// Split the line by '=' for key-value pairs
|
||||
string[] data = CurrentLine.Split('=');
|
||||
|
||||
// If the value field contains an '=', we need to put them back in
|
||||
string key = data[0].Trim();
|
||||
string value = string.Join("=", data.Skip(1)).Trim();
|
||||
|
||||
KeyValuePair = new KeyValuePair<string, string>(key, value);
|
||||
RowType = IniRowType.KeyValue;
|
||||
}
|
||||
|
||||
// Empty
|
||||
else if (string.IsNullOrEmpty(CurrentLine))
|
||||
{
|
||||
KeyValuePair = null;
|
||||
CurrentLine = string.Empty;
|
||||
RowType = IniRowType.None;
|
||||
}
|
||||
|
||||
// Invalid
|
||||
else
|
||||
{
|
||||
KeyValuePair = null;
|
||||
RowType = IniRowType.Invalid;
|
||||
|
||||
if (ValidateRows)
|
||||
throw new InvalidDataException($"Invalid INI row found, cannot continue: {CurrentLine}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of the underlying reader
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
sr.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2012-2015 Eugene Larchenko (spct@mail.ru)
|
||||
|
||||
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 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.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace OptimizedCRC
|
||||
{
|
||||
internal class OptimizedCRC : IDisposable
|
||||
{
|
||||
private const uint kCrcPoly = 0xEDB88320;
|
||||
private const uint kInitial = 0xFFFFFFFF;
|
||||
private const int CRC_NUM_TABLES = 8;
|
||||
private static readonly uint[] Table;
|
||||
|
||||
static OptimizedCRC()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
Table = new uint[256 * CRC_NUM_TABLES];
|
||||
int i;
|
||||
for (i = 0; i < 256; i++)
|
||||
{
|
||||
uint r = (uint)i;
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1));
|
||||
}
|
||||
Table[i] = r;
|
||||
}
|
||||
for (; i < 256 * CRC_NUM_TABLES; i++)
|
||||
{
|
||||
uint r = Table[i - 256];
|
||||
Table[i] = Table[r & 0xFF] ^ (r >> 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint UnsignedValue;
|
||||
|
||||
public OptimizedCRC()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset CRC
|
||||
/// </summary>
|
||||
public void Init()
|
||||
{
|
||||
UnsignedValue = kInitial;
|
||||
}
|
||||
|
||||
public int Value
|
||||
{
|
||||
get { return (int)~UnsignedValue; }
|
||||
}
|
||||
|
||||
public void Update(byte[] data, int offset, int count)
|
||||
{
|
||||
new ArraySegment<byte>(data, offset, count); // check arguments
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var table = OptimizedCRC.Table;
|
||||
|
||||
uint crc = UnsignedValue;
|
||||
|
||||
for (; (offset & 7) != 0 && count != 0; count--)
|
||||
{
|
||||
crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]];
|
||||
}
|
||||
|
||||
if (count >= 8)
|
||||
{
|
||||
/*
|
||||
* Idea from 7-zip project sources (http://7-zip.org/sdk.html)
|
||||
*/
|
||||
|
||||
int end = (count - 8) & ~7;
|
||||
count -= end;
|
||||
end += offset;
|
||||
|
||||
while (offset != end)
|
||||
{
|
||||
crc ^= (uint)(data[offset] + (data[offset + 1] << 8) + (data[offset + 2] << 16) + (data[offset + 3] << 24));
|
||||
uint high = (uint)(data[offset + 4] + (data[offset + 5] << 8) + (data[offset + 6] << 16) + (data[offset + 7] << 24));
|
||||
offset += 8;
|
||||
|
||||
crc = table[(byte)crc + 0x700]
|
||||
^ table[(byte)(crc >>= 8) + 0x600]
|
||||
^ table[(byte)(crc >>= 8) + 0x500]
|
||||
^ table[/*(byte)*/(crc >> 8) + 0x400]
|
||||
^ table[(byte)(high) + 0x300]
|
||||
^ table[(byte)(high >>= 8) + 0x200]
|
||||
^ table[(byte)(high >>= 8) + 0x100]
|
||||
^ table[/*(byte)*/(high >> 8) + 0x000];
|
||||
}
|
||||
}
|
||||
|
||||
while (count-- != 0)
|
||||
{
|
||||
crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]];
|
||||
}
|
||||
|
||||
UnsignedValue = crc;
|
||||
}
|
||||
|
||||
static public int Compute(byte[] data, int offset, int count)
|
||||
{
|
||||
var crc = new OptimizedCRC();
|
||||
crc.Update(data, offset, count);
|
||||
return crc.Value;
|
||||
}
|
||||
|
||||
static public int Compute(byte[] data)
|
||||
{
|
||||
return Compute(data, 0, data.Length);
|
||||
}
|
||||
|
||||
static public int Compute(ArraySegment<byte> block)
|
||||
{
|
||||
return Compute(block.Array, block.Offset, block.Count);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
UnsignedValue = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,576 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using NDecrypt.Core;
|
||||
using NDecrypt.N3DS.Headers;
|
||||
using static NDecrypt.Core.Helper;
|
||||
|
||||
namespace NDecrypt.N3DS
|
||||
{
|
||||
// https://www.3dbrew.org/wiki/CIA
|
||||
public class CIATool : ITool
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the input CIA file
|
||||
/// </summary>
|
||||
private readonly string filename;
|
||||
|
||||
/// <summary>
|
||||
/// Decryption args to use while processing
|
||||
/// </summary>
|
||||
private readonly DecryptArgs decryptArgs;
|
||||
|
||||
public CIATool(string filename, DecryptArgs decryptArgs)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.decryptArgs = decryptArgs;
|
||||
}
|
||||
|
||||
#region Common Methods
|
||||
|
||||
/// <summary>
|
||||
/// Process an input file given the input values
|
||||
/// </summary>
|
||||
public bool ProcessFile()
|
||||
{
|
||||
// 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 (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
|
||||
using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)))
|
||||
{
|
||||
CIAHeader header = CIAHeader.Read(reader);
|
||||
if (header == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS CIA!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process all NCCH partitions
|
||||
ProcessAllPartitions(header, reader, writer);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process all partitions in the content file data of a CIA header
|
||||
/// </summary>
|
||||
/// <param name="ciaHeader">CIA header representing the 3DS CIA file</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessAllPartitions(CIAHeader ciaHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// Iterate over all NCCH partitions
|
||||
for (int p = 0; p < ciaHeader.Partitions.Length; p++)
|
||||
{
|
||||
NCCHHeader ncchHeader = ciaHeader.Partitions[0];
|
||||
ProcessPartition(ciaHeader, ncchHeader, reader, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a single partition
|
||||
/// </summary>
|
||||
/// <param name="ciaHeader">CIA header representing the 3DS CIA file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessPartition(CIAHeader ciaHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If we're forcing the operation, tell the user
|
||||
if (decryptArgs.Force)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} is not verified due to force flag being set.");
|
||||
}
|
||||
// If we're not forcing the operation, check if the 'NoCrypto' bit is set
|
||||
else if (ncchHeader.Flags.PossblyDecrypted ^ decryptArgs.Encrypt)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber}: Already " + (decryptArgs.Encrypt ? "Encrypted" : "Decrypted") + "?...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the Keys to be used
|
||||
SetEncryptionKeys(ciaHeader, ncchHeader);
|
||||
|
||||
// Process the extended header
|
||||
ProcessExtendedHeader(ncchHeader, reader, writer);
|
||||
|
||||
// If we're encrypting, encrypt the filesystems and update the flags
|
||||
if (decryptArgs.Encrypt)
|
||||
{
|
||||
EncryptExeFS(ncchHeader, reader, writer);
|
||||
EncryptRomFS(ncchHeader, reader, writer);
|
||||
UpdateEncryptCryptoAndMasks(ciaHeader, ncchHeader, writer);
|
||||
}
|
||||
|
||||
// If we're decrypting, decrypt the filesystems and update the flags
|
||||
else
|
||||
{
|
||||
DecryptExeFS(ncchHeader, reader, writer);
|
||||
DecryptRomFS(ncchHeader, reader, writer);
|
||||
UpdateDecryptCryptoAndMasks(ncchHeader, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine the set of keys to be used for encryption or decryption
|
||||
/// </summary>
|
||||
/// <param name="ciaHeader">NCSD header representing the 3DS CIA file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
private void SetEncryptionKeys(CIAHeader ciaHeader, NCCHHeader ncchHeader)
|
||||
{
|
||||
ncchHeader.KeyX = 0;
|
||||
ncchHeader.KeyX2C = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C;
|
||||
|
||||
// Backup headers can't have a KeyY value set
|
||||
if (ncchHeader.RSA2048Signature != null)
|
||||
ncchHeader.KeyY = new BigInteger(ncchHeader.RSA2048Signature.Take(16).Reverse().ToArray());
|
||||
else
|
||||
ncchHeader.KeyY = new BigInteger(0);
|
||||
|
||||
ncchHeader.NormalKey = 0;
|
||||
ncchHeader.NormalKey2C = RotateLeft((RotateLeft(ncchHeader.KeyX2C, 2, 128) ^ ncchHeader.KeyY) + decryptArgs.AESHardwareConstant, 87, 128);
|
||||
|
||||
// TODO: Figure out what sane defaults for these values are
|
||||
// Set the header to use based on mode
|
||||
BitMasks masks = BitMasks.NoCrypto;
|
||||
CryptoMethod method = CryptoMethod.Original;
|
||||
if (decryptArgs.Encrypt)
|
||||
{
|
||||
// TODO: Can we actually re-encrypt a CIA?
|
||||
//masks = ciaHeader.BackupHeader.Flags.BitMasks;
|
||||
//method = ciaHeader.BackupHeader.Flags.CryptoMethod;
|
||||
}
|
||||
else
|
||||
{
|
||||
masks = ncchHeader.Flags.BitMasks;
|
||||
method = ncchHeader.Flags.CryptoMethod;
|
||||
}
|
||||
|
||||
if (masks.HasFlag(BitMasks.FixedCryptoKey))
|
||||
{
|
||||
ncchHeader.NormalKey = 0x00;
|
||||
ncchHeader.NormalKey2C = 0x00;
|
||||
Console.WriteLine("Encryption Method: Zero Key");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (method == CryptoMethod.Original)
|
||||
{
|
||||
ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C;
|
||||
Console.WriteLine("Encryption Method: Key 0x2C");
|
||||
}
|
||||
else if (method == CryptoMethod.Seven)
|
||||
{
|
||||
ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x25 : decryptArgs.KeyX0x25;
|
||||
Console.WriteLine("Encryption Method: Key 0x25");
|
||||
}
|
||||
else if (method == CryptoMethod.NineThree)
|
||||
{
|
||||
ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x18 : decryptArgs.KeyX0x18;
|
||||
Console.WriteLine("Encryption Method: Key 0x18");
|
||||
}
|
||||
else if (method == CryptoMethod.NineSix)
|
||||
{
|
||||
ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x1B : decryptArgs.KeyX0x1B;
|
||||
Console.WriteLine("Encryption Method: Key 0x1B");
|
||||
}
|
||||
|
||||
ncchHeader.NormalKey = RotateLeft((RotateLeft(ncchHeader.KeyX, 2, 128) ^ ncchHeader.KeyY) + decryptArgs.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the extended header, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private bool ProcessExtendedHeader(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value?
|
||||
uint mediaUnitSize = 0x200; // mediaUnitSize;
|
||||
|
||||
if (ncchHeader.ExtendedHeaderSizeInBytes > 0)
|
||||
{
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + ": ExHeader");
|
||||
|
||||
var cipher = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.PlainIV, decryptArgs.Encrypt);
|
||||
writer.Write(cipher.ProcessBytes(reader.ReadBytes(Constants.CXTExtendedDataHeaderLength)));
|
||||
writer.Flush();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Extended Header... Skipping...");
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the extended header, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessExeFSFileEntries(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value?
|
||||
uint mediaUnitSize = 0x200; // mediaUnitSize;
|
||||
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
|
||||
ExeFSHeader exefsHeader = ExeFSHeader.Read(reader);
|
||||
|
||||
// If the header failed to read, log and return
|
||||
if (exefsHeader == null)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS header could not be read. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders)
|
||||
{
|
||||
// Only decrypt a file if it's a code binary
|
||||
if (!fileHeader.IsCodeBinary)
|
||||
continue;
|
||||
|
||||
uint datalenM = ((fileHeader.FileSize) / (1024 * 1024));
|
||||
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
|
||||
uint ctroffset = ((fileHeader.FileOffset + mediaUnitSize) / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffsetForHeader = AddToByteArray(ncchHeader.ExeFSIV, (int)ctroffset);
|
||||
|
||||
var firstCipher = CreateAESCipher(ncchHeader.NormalKey, exefsIVWithOffsetForHeader, decryptArgs.Encrypt);
|
||||
var secondCipher = CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffsetForHeader, !decryptArgs.Encrypt);
|
||||
|
||||
reader.BaseStream.Seek((((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * mediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * mediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
if (datalenM > 0)
|
||||
{
|
||||
for (int i = 0; i < datalenM; i++)
|
||||
{
|
||||
writer.Write(secondCipher.ProcessBytes(firstCipher.ProcessBytes(reader.ReadBytes(1024 * 1024))));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {i} / {datalenM + 1} mb...");
|
||||
}
|
||||
}
|
||||
|
||||
if (datalenB > 0)
|
||||
{
|
||||
writer.Write(secondCipher.DoFinal(firstCipher.DoFinal(reader.ReadBytes((int)datalenB))));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the ExeFS Filename Table
|
||||
/// </summary>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessExeFSFilenameTable(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value?
|
||||
uint mediaUnitSize = 0x200; // mediaUnitSize;
|
||||
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table");
|
||||
|
||||
var exeFSFilenameTable = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.ExeFSIV, decryptArgs.Encrypt);
|
||||
writer.Write(exeFSFilenameTable.ProcessBytes(reader.ReadBytes((int)mediaUnitSize)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessExeFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value?
|
||||
uint mediaUnitSize = 0x200; // mediaUnitSize;
|
||||
|
||||
int exefsSizeM = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * mediaUnitSize) / (1024 * 1024));
|
||||
int exefsSizeB = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * mediaUnitSize) % (1024 * 1024));
|
||||
int ctroffsetE = (int)(mediaUnitSize / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffset = AddToByteArray(ncchHeader.ExeFSIV, ctroffsetE);
|
||||
|
||||
var exeFS = CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffset, decryptArgs.Encrypt);
|
||||
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * mediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * mediaUnitSize, SeekOrigin.Begin);
|
||||
if (exefsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < exefsSizeM; i++)
|
||||
{
|
||||
writer.Write(exeFS.ProcessBytes(reader.ReadBytes(1024 * 1024)));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (exefsSizeB > 0)
|
||||
{
|
||||
writer.Write(exeFS.DoFinal(reader.ReadBytes(exefsSizeB)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Decrypt
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void DecryptExeFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If the ExeFS size is 0, we log and return
|
||||
if (ncchHeader.ExeFSSizeInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrypt the filename table
|
||||
ProcessExeFSFilenameTable(ncchHeader, reader, writer);
|
||||
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
if (ncchHeader.Flags.CryptoMethod != CryptoMethod.Original)
|
||||
ProcessExeFSFileEntries(ncchHeader, reader, writer);
|
||||
|
||||
// Decrypt the rest of the ExeFS
|
||||
ProcessExeFS(ncchHeader, reader, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the RomFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// TODO: See how much can be extracted into a common method with Encrypt
|
||||
private void DecryptRomFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value?
|
||||
uint mediaUnitSize = 0x200; // ncsdHeader.MediaUnitSize;
|
||||
|
||||
// If the RomFS offset is 0, we log and return
|
||||
if (ncchHeader.RomFSOffsetInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} RomFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * mediaUnitSize) / (1024 * 1024));
|
||||
int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * mediaUnitSize) % (1024 * 1024));
|
||||
|
||||
var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV, decryptArgs.Encrypt);
|
||||
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
|
||||
if (romfsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < romfsSizeM; i++)
|
||||
{
|
||||
writer.Write(cipher.ProcessBytes(reader.ReadBytes(1024 * 1024)));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Decrypting: {i} / {romfsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
writer.Write(cipher.DoFinal(reader.ReadBytes(romfsSizeB)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Decrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the CryptoMethod and BitMasks for the decrypted partition
|
||||
/// </summary>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void UpdateDecryptCryptoAndMasks(NCCHHeader ncchHeader, BinaryWriter writer)
|
||||
{
|
||||
// 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
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x18B, SeekOrigin.Begin);
|
||||
writer.Write((byte)CryptoMethod.Original);
|
||||
writer.Flush();
|
||||
|
||||
// Write the new BitMasks flag
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x18F, SeekOrigin.Begin);
|
||||
BitMasks flag = ncchHeader.Flags.BitMasks;
|
||||
flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
|
||||
flag |= BitMasks.NoCrypto;
|
||||
writer.Write((byte)flag);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Encrypt
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void EncryptExeFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If the ExeFS size is 0, we log and return
|
||||
if (ncchHeader.ExeFSSizeInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// 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)
|
||||
// ProcessExeFSFileEntries(ncchHeader, reader, writer);
|
||||
|
||||
// Encrypt the filename table
|
||||
ProcessExeFSFilenameTable(ncchHeader, reader, writer);
|
||||
|
||||
// Encrypt the rest of the ExeFS
|
||||
ProcessExeFS(ncchHeader, reader, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the RomFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// TODO: See how much can be extracted into a common method with Decrypt
|
||||
private void EncryptRomFS(NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value?
|
||||
uint mediaUnitSize = 0x200; // ncsdHeader.MediaUnitSize;
|
||||
|
||||
// If the RomFS offset is 0, we log and return
|
||||
if (ncchHeader.RomFSOffsetInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} RomFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * mediaUnitSize) / (1024 * 1024));
|
||||
int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * mediaUnitSize) % (1024 * 1024));
|
||||
|
||||
// Encrypting RomFS for partitions 1 and up always use Key0x2C
|
||||
if (ncchHeader.PartitionNumber > 0)
|
||||
{
|
||||
// TODO: Determine how to figure out the original crypto method, if possible
|
||||
//if (ciaHeader.BackupHeader.Flags?.BitMasks.HasFlag(BitMasks.FixedCryptoKey) == true) // except if using zero-key
|
||||
//{
|
||||
// ncchHeader.NormalKey = 0x00;
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
ncchHeader.KeyX = (decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C);
|
||||
ncchHeader.NormalKey = RotateLeft((RotateLeft(ncchHeader.KeyX, 2, 128) ^ ncchHeader.KeyY) + decryptArgs.AESHardwareConstant, 87, 128);
|
||||
//}
|
||||
}
|
||||
|
||||
var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV, decryptArgs.Encrypt);
|
||||
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
|
||||
if (romfsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < romfsSizeM; i++)
|
||||
{
|
||||
writer.Write(cipher.ProcessBytes(reader.ReadBytes(1024 * 1024)));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Encrypting: {i} / {romfsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
writer.Write(cipher.DoFinal(reader.ReadBytes(romfsSizeB)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Encrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the CryptoMethod and BitMasks for the encrypted partition
|
||||
/// </summary>
|
||||
/// <param name="ciaHeader">CIA header representing the 3DS CIA file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void UpdateEncryptCryptoAndMasks(CIAHeader ciaHeader, NCCHHeader ncchHeader, BinaryWriter writer)
|
||||
{
|
||||
// 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
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x18B, SeekOrigin.Begin);
|
||||
|
||||
// For partitions 1 and up, set crypto-method to 0x00
|
||||
if (ncchHeader.PartitionNumber > 0)
|
||||
writer.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);
|
||||
|
||||
writer.Flush();
|
||||
|
||||
// Write the new BitMasks flag
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x18F, SeekOrigin.Begin);
|
||||
BitMasks flag = ncchHeader.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;
|
||||
writer.Write((byte)flag);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace NDecrypt.N3DS
|
||||
{
|
||||
internal class Constants
|
||||
{
|
||||
// Setup Keys and IVs
|
||||
public static byte[] PlainCounter = new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
public static byte[] ExefsCounter = new byte[] { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
public static byte[] RomfsCounter = new byte[] { 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
public const int CXTExtendedDataHeaderLength = 0x800;
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace NDecrypt.N3DS
|
||||
{
|
||||
[Flags]
|
||||
internal enum ARM9AccessControlDescriptors : byte
|
||||
{
|
||||
MountNandRoot = 0x01,
|
||||
MountNandroWriteAccess = 0x02,
|
||||
MountTwlnRoot = 0x04,
|
||||
MountWnandRoot = 0x08,
|
||||
MountCardSPI = 0x0F,
|
||||
UseSDIF3 = 0x10,
|
||||
CreateSeed = 0x20,
|
||||
UseCardSPI = 0x40,
|
||||
SDApplication = 0x80,
|
||||
MoundSdmcWriteAccess = 0xF0,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum ARM11LSCFlag0 : byte
|
||||
{
|
||||
IdealProcessor = 0x01 | 0x02,
|
||||
AffinityMask = 0x04 | 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// Value Description
|
||||
/// 0 Prod (64MB of usable application memory)
|
||||
/// 1 Undefined (unusable)
|
||||
/// 2 Dev1 (96MB of usable application memory)
|
||||
/// 3 Dev2 (80MB of usable application memory)
|
||||
/// 4 Dev3 (72MB of usable application memory)
|
||||
/// 5 Dev4 (32MB of usable application memory)
|
||||
/// 6-7 Undefined Same as Prod?
|
||||
/// </summary>
|
||||
Old3DSSystemMode = 0x0F | 0x10 | 0x20 | 0x40,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum ARM11LSCFlag1 : byte
|
||||
{
|
||||
EnableL2Cache = 0x01,
|
||||
Cpuspeed_804MHz = 0x02,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum ARM11LSCFlag2 : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Value Description
|
||||
/// 0 Legacy (use Old3DS system mode)
|
||||
/// 1 Prod (124MB of usable application memory)
|
||||
/// 2 Dev1 (178MB of usable application memory)
|
||||
/// 3 Dev2 (124MB of usable application memory)
|
||||
/// 4-7 Undefined Same as Prod?
|
||||
/// </summary>
|
||||
New3DSSystemMode = 0x01 | 0x02 | 0x04 | 0x08,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum BitMasks : byte
|
||||
{
|
||||
FixedCryptoKey = 0x01,
|
||||
NoMountRomFs = 0x02,
|
||||
NoCrypto = 0x04,
|
||||
NewKeyYGenerator = 0x20,
|
||||
}
|
||||
|
||||
internal enum ContentIndex : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// Main Content (.CXI for 3DS executable content/.CFA for 3DS Data Archives/.SRL for TWL content)
|
||||
/// </summary>
|
||||
MainContent = 0x0000,
|
||||
|
||||
/// <summary>
|
||||
/// Home Menu Manual (.CFA)
|
||||
/// </summary>
|
||||
HomeMenuManual = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// DLP Child Container (.CFA)
|
||||
/// </summary>
|
||||
DLPChildContainer = 0x0002,
|
||||
}
|
||||
|
||||
internal enum ContentPlatform : byte
|
||||
{
|
||||
CTR = 0x01,
|
||||
Snake = 0x02, // New3DS
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum ContentType : byte
|
||||
{
|
||||
Data = 0x01,
|
||||
Executable = 0x02,
|
||||
SystemUpdate = 0x04,
|
||||
Manual = 0x08,
|
||||
Child = 0x04 | 0x08,
|
||||
Trial = 0x10,
|
||||
}
|
||||
|
||||
internal enum CryptoMethod : byte
|
||||
{
|
||||
Original = 0x00,
|
||||
Seven = 0x01,
|
||||
NineThree = 0x0A,
|
||||
NineSix = 0x0B,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum FilesystemAccessInfo : ulong
|
||||
{
|
||||
CategorySystemApplication = 0x1,
|
||||
CategoryHardwareCheck = 0x2,
|
||||
CategoryFilesystemTool = 0x4,
|
||||
Debug = 0x8,
|
||||
TWLCardBackup = 0x10,
|
||||
TWLNANDData = 0x20,
|
||||
BOSS = 0x40,
|
||||
sdmcRoot = 0x80,
|
||||
Core = 0x100,
|
||||
nandRootroReadOnly = 0x200,
|
||||
nandRootrw = 0x400,
|
||||
nandrootroWriteAccess = 0x800,
|
||||
CategorySystemSettings = 0x1000,
|
||||
Cardboard = 0x2000,
|
||||
ExportImportIVS = 0x4000,
|
||||
sdmcRootWriteOnly = 0x8000,
|
||||
SwitchCleanup = 0x10000, // Introduced in 3.0.0?
|
||||
SavedataMove = 0x20000, // Introduced in 5.0.0
|
||||
Shop = 0x40000, // Introduced in 5.0.0
|
||||
Shell = 0x80000, // Introduced in 5.0.0
|
||||
CategoryHomeMenu = 0x100000, // Introduced in 6.0.0
|
||||
SeedDB = 0x200000, // Introduced in 9.6.0-X FIRM. Home Menu has this bit set starting with 9.6.0-X.
|
||||
}
|
||||
|
||||
internal enum FilesystemType : ulong
|
||||
{
|
||||
None = 0,
|
||||
Normal = 1,
|
||||
FIRM = 3,
|
||||
AGB_FIRMSave = 4,
|
||||
}
|
||||
|
||||
internal enum MediaCardDeviceType : byte
|
||||
{
|
||||
NORFlash = 0x01,
|
||||
None = 0x02,
|
||||
BT = 0x03,
|
||||
}
|
||||
|
||||
internal enum MediaPlatformIndex : byte
|
||||
{
|
||||
CTR = 0x01,
|
||||
}
|
||||
|
||||
internal enum MediaTypeIndex : byte
|
||||
{
|
||||
InnerDevice = 0x00,
|
||||
Card1 = 0x01,
|
||||
Card2 = 0x02,
|
||||
ExtendedDevice = 0x03,
|
||||
}
|
||||
|
||||
internal enum NCCHFlags
|
||||
{
|
||||
CryptoMethod = 0x03,
|
||||
ContentPlatform = 0x04,
|
||||
ContentTypeBitMask = 0x05,
|
||||
ContentUnitSize = 0x06,
|
||||
BitMasks = 0x07,
|
||||
}
|
||||
|
||||
internal enum NCSDFlags
|
||||
{
|
||||
BackupWriteWaitTime = 0x00,
|
||||
MediaCardDevice3X = 0x03,
|
||||
MediaPlatformIndex = 0x04,
|
||||
MediaTypeIndex = 0x05,
|
||||
MediaUnitSize = 0x06,
|
||||
MediaCardDevice2X = 0x07,
|
||||
}
|
||||
|
||||
internal enum PublicKeyType : uint
|
||||
{
|
||||
RSA_4096 = 0x00000000,
|
||||
RSA_2048 = 0x01000000,
|
||||
ECDSA = 0x02000000,
|
||||
}
|
||||
|
||||
internal enum ResourceLimitCategory
|
||||
{
|
||||
APPLICATION = 0,
|
||||
SYS_APPLET = 1,
|
||||
LIB_APPLET = 2,
|
||||
OTHER = 3,
|
||||
}
|
||||
|
||||
// Note: These are reversed because of how C# reads values
|
||||
internal enum SignatureType : uint
|
||||
{
|
||||
RSA_4096_SHA1 = 0x00000100,
|
||||
RSA_2048_SHA1 = 0x01000100,
|
||||
ECDSA_SHA1 = 0x02000100,
|
||||
RSA_4096_SHA256 = 0x03000100,
|
||||
RSA_2048_SHA256 = 0x04000100,
|
||||
ECDSA_SHA256 = 0x05000100,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum StorageInfoOtherAttributes : byte
|
||||
{
|
||||
NotUseROMFS = 0x01,
|
||||
UseExtendedSavedataAccess = 0x02,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum TMDContentType : ushort
|
||||
{
|
||||
Encrypted = 0x0001,
|
||||
Disc = 0x0002,
|
||||
CFM = 0x0004,
|
||||
Optional = 0x4000,
|
||||
Shared = 0x8000,
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class ARM11KernelCapabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Descriptors
|
||||
/// -------------------
|
||||
/// Pattern of bits 20-31 Type Fields
|
||||
/// 0b1110xxxxxxxx Interrupt info
|
||||
/// 0b11110xxxxxxx System call mask Bits 24-26: System call mask table index; Bits 0-23: mask
|
||||
/// 0b1111110xxxxx Kernel release version Bits 8-15: Major version; Bits 0-7: Minor version
|
||||
/// 0b11111110xxxx Handle table size Bits 0-18: size
|
||||
/// 0b111111110xxx Kernel flags
|
||||
/// 0b11111111100x Map address range Describes a memory mapping like the 0b111111111110 descriptor, but an entire range rather than a single page is mapped.Another 0b11111111100x descriptor must follow this one to denote the(exclusive) end of the address range to map.
|
||||
/// 0b111111111110 Map memory page Bits 0-19: page index to map(virtual address >> 12; the physical address is determined per-page according to Memory layout); Bit 20: Map read-only(otherwise read-write)
|
||||
///
|
||||
/// ARM11 Kernel Flags
|
||||
/// -------------------
|
||||
/// Bit Description
|
||||
/// 0 Allow debug
|
||||
/// 1 Force debug
|
||||
/// 2 Allow non-alphanum
|
||||
/// 3 Shared page writing
|
||||
/// 4 Privilege priority
|
||||
/// 5 Allow main() args
|
||||
/// 6 Shared device memory
|
||||
/// 7 Runnable on sleep
|
||||
/// 8-11 Memory type(1: application, 2: system, 3: base)
|
||||
/// 12 Special memory
|
||||
/// 13 Process has access to CPU core 2 (New3DS only)
|
||||
/// </summary>
|
||||
public byte[][] Descriptors { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get ARM11 kernel capabilities, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ARM11 kernel capabilities object, null on error</returns>
|
||||
public static ARM11KernelCapabilities Read(BinaryReader reader)
|
||||
{
|
||||
ARM11KernelCapabilities kc = new ARM11KernelCapabilities();
|
||||
|
||||
try
|
||||
{
|
||||
kc.Descriptors = new byte[28][];
|
||||
for (int i = 0; i < 28; i++)
|
||||
kc.Descriptors[i] = reader.ReadBytes(4);
|
||||
|
||||
kc.Reserved = reader.ReadBytes(0x10);
|
||||
return kc;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class ARM11LocalSystemCapabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Program ID
|
||||
/// </summary>
|
||||
public byte[] ProgramID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Core version (The Title ID low of the required FIRM)
|
||||
/// </summary>
|
||||
public uint CoreVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag1 (implemented starting from 8.0.0-18).
|
||||
/// </summary>
|
||||
public ARM11LSCFlag1 Flag1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag2 (implemented starting from 8.0.0-18).
|
||||
/// </summary>
|
||||
public ARM11LSCFlag2 Flag2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag0
|
||||
/// </summary>
|
||||
public ARM11LSCFlag0 Flag0 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Priority
|
||||
/// </summary>
|
||||
public byte Priority { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Resource limit descriptors. The first byte here controls the maximum allowed CpuTime.
|
||||
/// </summary>
|
||||
public byte[][] ResourceLimitDescriptors { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Storage info
|
||||
/// </summary>
|
||||
public StorageInfo StorageInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Service access control
|
||||
/// </summary>
|
||||
public byte[][] ServiceAccessControl { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Extended service access control, support for this was implemented with 9.3.0-X.
|
||||
/// </summary>
|
||||
public byte[][] ExtendedServiceAccessControl { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Resource limit category. (0 = APPLICATION, 1 = SYS_APPLET, 2 = LIB_APPLET, 3 = OTHER (sysmodules running under the BASE memregion))
|
||||
/// </summary>
|
||||
public ResourceLimitCategory ResourceLimitCategory { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get ARM11 local system capabilities, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ARM11 local system capabilities object, null on error</returns>
|
||||
public static ARM11LocalSystemCapabilities Read(BinaryReader reader)
|
||||
{
|
||||
ARM11LocalSystemCapabilities lsc = new ARM11LocalSystemCapabilities();
|
||||
|
||||
try
|
||||
{
|
||||
lsc.ProgramID = reader.ReadBytes(8);
|
||||
lsc.CoreVersion = reader.ReadUInt32();
|
||||
lsc.Flag1 = (ARM11LSCFlag1)reader.ReadByte();
|
||||
lsc.Flag2 = (ARM11LSCFlag2)reader.ReadByte();
|
||||
lsc.Flag0 = (ARM11LSCFlag0)reader.ReadByte();
|
||||
lsc.Priority = reader.ReadByte();
|
||||
|
||||
lsc.ResourceLimitDescriptors = new byte[16][];
|
||||
for (int i = 0; i < 16; i++)
|
||||
lsc.ResourceLimitDescriptors[i] = reader.ReadBytes(2);
|
||||
|
||||
lsc.StorageInfo = StorageInfo.Read(reader);
|
||||
|
||||
lsc.ServiceAccessControl = new byte[32][];
|
||||
for (int i = 0; i < 32; i++)
|
||||
lsc.ServiceAccessControl[i] = reader.ReadBytes(8);
|
||||
|
||||
lsc.ExtendedServiceAccessControl = new byte[2][];
|
||||
for (int i = 0; i < 2; i++)
|
||||
lsc.ExtendedServiceAccessControl[i] = reader.ReadBytes(8);
|
||||
|
||||
lsc.Reserved = reader.ReadBytes(0xF);
|
||||
lsc.ResourceLimitCategory = (ResourceLimitCategory)reader.ReadByte();
|
||||
return lsc;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class ARM9AccessControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Descriptors
|
||||
/// </summary>
|
||||
public ARM9AccessControlDescriptors[] Descriptors { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 Descriptor Version. Originally this value had to be ≥ 2. Starting with 9.3.0-X this value has to be either value 2 or value 3.
|
||||
/// </summary>
|
||||
public byte DescriptorVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get ARM9 access control, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ARM9 access control object, null on error</returns>
|
||||
public static ARM9AccessControl Read(BinaryReader reader)
|
||||
{
|
||||
ARM9AccessControl ac = new ARM9AccessControl();
|
||||
|
||||
try
|
||||
{
|
||||
ac.Descriptors = new ARM9AccessControlDescriptors[15];
|
||||
for (int i = 0; i < 15; i++)
|
||||
ac.Descriptors[i] = (ARM9AccessControlDescriptors)reader.ReadByte();
|
||||
ac.DescriptorVersion = reader.ReadByte();
|
||||
return ac;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class AccessControlInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// ARM11 local system capabilities
|
||||
/// </summary>
|
||||
public ARM11LocalSystemCapabilities ARM11LocalSystemCapabilities { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM11 kernel capabilities
|
||||
/// </summary>
|
||||
public ARM11KernelCapabilities ARM11KernelCapabilities { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 access control
|
||||
/// </summary>
|
||||
public ARM9AccessControl ARM9AccessControl { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get access control info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Access control info object, null on error</returns>
|
||||
public static AccessControlInfo Read(BinaryReader reader)
|
||||
{
|
||||
AccessControlInfo aci = new AccessControlInfo();
|
||||
|
||||
try
|
||||
{
|
||||
aci.ARM11LocalSystemCapabilities = ARM11LocalSystemCapabilities.Read(reader);
|
||||
aci.ARM11KernelCapabilities = ARM11KernelCapabilities.Read(reader);
|
||||
aci.ARM9AccessControl = ARM9AccessControl.Read(reader);
|
||||
return aci;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
// https://www.3dbrew.org/wiki/CIA
|
||||
internal class CIAHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// Archive header size, usually 0x2020 bytes
|
||||
/// </summary>
|
||||
public int HeaderSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type
|
||||
/// </summary>
|
||||
public ushort Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Version
|
||||
/// </summary>
|
||||
public ushort Version { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Certificate chain size
|
||||
/// </summary>
|
||||
public int CertificateChainSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ticket size
|
||||
/// </summary>
|
||||
public int TicketSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// TMD file size
|
||||
/// </summary>
|
||||
public int TMDFileSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Meta size (0 if no Meta data is present)
|
||||
/// </summary>
|
||||
public int MetaSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content size
|
||||
/// </summary>
|
||||
public long ContentSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content Index
|
||||
/// </summary>
|
||||
public byte[] ContentIndex { get; private set; }
|
||||
|
||||
#region Content Index
|
||||
|
||||
/// <summary>
|
||||
/// Certificate chain
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// https://www.3dbrew.org/wiki/CIA#Certificate_Chain
|
||||
/// </remarks>
|
||||
public Certificate[] CertificateChain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ticket
|
||||
/// </summary>
|
||||
public Ticket Ticket { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// TMD file data
|
||||
/// </summary>
|
||||
public TitleMetadata TMDFileData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content file data
|
||||
/// </summary>
|
||||
public NCCHHeader[] Partitions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Meta file data (Not a necessary component)
|
||||
/// </summary>
|
||||
public MetaFile MetaFileData { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get a CIA header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>CIA header object, null on error</returns>
|
||||
public static CIAHeader Read(BinaryReader reader)
|
||||
{
|
||||
CIAHeader header = new CIAHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.HeaderSize = reader.ReadInt32();
|
||||
header.Type = reader.ReadUInt16();
|
||||
header.Version = reader.ReadUInt16();
|
||||
header.CertificateChainSize = reader.ReadInt32();
|
||||
header.TicketSize = reader.ReadInt32();
|
||||
header.TMDFileSize = reader.ReadInt32();
|
||||
header.MetaSize = reader.ReadInt32();
|
||||
header.ContentSize = reader.ReadInt64();
|
||||
header.ContentIndex = reader.ReadBytes(0x2000);
|
||||
if (reader.BaseStream.Position % 64 != 0)
|
||||
reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current);
|
||||
|
||||
header.CertificateChain = new Certificate[3];
|
||||
header.CertificateChain[0] = Certificate.Read(reader); // CA
|
||||
header.CertificateChain[1] = Certificate.Read(reader); // Ticket
|
||||
header.CertificateChain[2] = Certificate.Read(reader); // TMD
|
||||
if (reader.BaseStream.Position % 64 != 0)
|
||||
reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current);
|
||||
|
||||
header.Ticket = Ticket.Read(reader, header.TicketSize);
|
||||
if (reader.BaseStream.Position % 64 != 0)
|
||||
reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current);
|
||||
|
||||
header.TMDFileData = TitleMetadata.Read(reader, header.TMDFileSize);
|
||||
if (reader.BaseStream.Position % 64 != 0)
|
||||
reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current);
|
||||
|
||||
long startingPosition = reader.BaseStream.Position;
|
||||
List<NCCHHeader> headers = new List<NCCHHeader>();
|
||||
while (reader.BaseStream.Position < startingPosition + header.ContentSize)
|
||||
{
|
||||
long initPosition = reader.BaseStream.Position;
|
||||
NCCHHeader ncchHeader = NCCHHeader.Read(reader, readSignature: true);
|
||||
if (ncchHeader == null)
|
||||
break;
|
||||
|
||||
headers.Add(ncchHeader);
|
||||
reader.BaseStream.Seek(initPosition + ncchHeader.ContentSizeInMediaUnits * 0x200, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
header.Partitions = headers.ToArray();
|
||||
if (header.MetaSize > 0)
|
||||
header.MetaFileData = MetaFile.Read(reader);
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class CXIExtendedHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// SCI
|
||||
/// </summary>
|
||||
public SystemControlInfo SCI { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ACI
|
||||
/// </summary>
|
||||
public AccessControlInfo ACI { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// AccessDesc signature (RSA-2048-SHA256)
|
||||
/// </summary>
|
||||
public byte[] AccessDescSignature { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// NCCH HDR RSA-2048 public key
|
||||
/// </summary>
|
||||
public byte[] NCCHHDRPublicKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ACI (for limitation of first ACI)
|
||||
/// </summary>
|
||||
public AccessControlInfo ACIForLimitations { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get a CXI extended header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>CXI extended header object, null on error</returns>
|
||||
public static CXIExtendedHeader Read(BinaryReader reader)
|
||||
{
|
||||
CXIExtendedHeader header = new CXIExtendedHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.SCI = SystemControlInfo.Read(reader);
|
||||
header.ACI = AccessControlInfo.Read(reader);
|
||||
header.AccessDescSignature = reader.ReadBytes(0x100);
|
||||
header.NCCHHDRPublicKey = reader.ReadBytes(0x100);
|
||||
header.ACIForLimitations = AccessControlInfo.Read(reader);
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
// https://www.3dbrew.org/wiki/Certificates
|
||||
internal class Certificate
|
||||
{
|
||||
/// <summary>
|
||||
/// Signature Type
|
||||
/// </summary>
|
||||
public SignatureType SignatureType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signature size
|
||||
/// </summary>
|
||||
public ushort SignatureSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Padding size
|
||||
/// </summary>
|
||||
public byte PaddingSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signature
|
||||
/// </summary>
|
||||
public byte[] Signature { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Issuer
|
||||
/// </summary>
|
||||
public byte[] Issuer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Issuer as a trimmed string
|
||||
/// </summary>
|
||||
public string IssuerString => Issuer != null && Issuer.Length > 0
|
||||
? Encoding.ASCII.GetString(Issuer)?.TrimEnd('\0')
|
||||
: null;
|
||||
|
||||
/// <summary>
|
||||
/// Key Type
|
||||
/// </summary>
|
||||
public PublicKeyType KeyType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name
|
||||
/// </summary>
|
||||
public byte[] Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name as a trimmed string
|
||||
/// </summary>
|
||||
public string NameString => Name != null && Name.Length > 0
|
||||
? Encoding.ASCII.GetString(Name)?.TrimEnd('\0')
|
||||
: null;
|
||||
|
||||
/// <summary>
|
||||
/// Expiration time as UNIX Timestamp, used at least for CTCert
|
||||
/// </summary>
|
||||
public uint ExpirationTime { get; private set; }
|
||||
|
||||
// This contains the Public Key(i.e. Modulus & Public Exponent)
|
||||
#region RSA
|
||||
|
||||
/// <summary>
|
||||
/// Modulus
|
||||
/// </summary>
|
||||
public byte[] Modulus { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Public Exponent
|
||||
/// </summary>
|
||||
public uint PublicExponent { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
// This contains the ECC public key, and is as follows:
|
||||
#region ECC
|
||||
|
||||
/// <summary>
|
||||
/// Public Key
|
||||
/// </summary>
|
||||
public byte[] PublicKey { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get certificate, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Certificate object, null on error</returns>
|
||||
public static Certificate Read(BinaryReader reader)
|
||||
{
|
||||
Certificate ct = new Certificate();
|
||||
|
||||
try
|
||||
{
|
||||
ct.SignatureType = (SignatureType)reader.ReadUInt32();
|
||||
switch (ct.SignatureType)
|
||||
{
|
||||
case SignatureType.RSA_4096_SHA1:
|
||||
case SignatureType.RSA_4096_SHA256:
|
||||
ct.SignatureSize = 0x200;
|
||||
ct.PaddingSize = 0x3C;
|
||||
break;
|
||||
case SignatureType.RSA_2048_SHA1:
|
||||
case SignatureType.RSA_2048_SHA256:
|
||||
ct.SignatureSize = 0x100;
|
||||
ct.PaddingSize = 0x3C;
|
||||
break;
|
||||
case SignatureType.ECDSA_SHA1:
|
||||
case SignatureType.ECDSA_SHA256:
|
||||
ct.SignatureSize = 0x03C;
|
||||
ct.PaddingSize = 0x40;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
ct.Signature = reader.ReadBytes(ct.SignatureSize);
|
||||
reader.ReadBytes(ct.PaddingSize); // Padding
|
||||
ct.Issuer = reader.ReadBytes(0x40);
|
||||
ct.KeyType = (PublicKeyType)reader.ReadUInt32();
|
||||
ct.Name = reader.ReadBytes(0x40);
|
||||
ct.ExpirationTime = reader.ReadUInt32();
|
||||
|
||||
switch (ct.KeyType)
|
||||
{
|
||||
case PublicKeyType.RSA_4096:
|
||||
ct.Modulus = reader.ReadBytes(0x200);
|
||||
ct.PublicExponent = reader.ReadUInt32();
|
||||
reader.ReadBytes(0x34); // Padding
|
||||
break;
|
||||
case PublicKeyType.RSA_2048:
|
||||
ct.Modulus = reader.ReadBytes(0x100);
|
||||
ct.PublicExponent = reader.ReadUInt32();
|
||||
reader.ReadBytes(0x34); // Padding
|
||||
break;
|
||||
case PublicKeyType.ECDSA:
|
||||
ct.PublicKey = reader.ReadBytes(0x3C);
|
||||
reader.ReadBytes(0x3C); // Padding
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return ct;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class CodeSetInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Address
|
||||
/// </summary>
|
||||
public byte[] Address { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Physical region size (in page-multiples)
|
||||
/// </summary>
|
||||
public uint PhysicalRegionSizeInPages { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size (in bytes)
|
||||
/// </summary>
|
||||
public uint SizeInBytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get code set info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Code set info object, null on error</returns>
|
||||
public static CodeSetInfo Read(BinaryReader reader)
|
||||
{
|
||||
CodeSetInfo csi = new CodeSetInfo();
|
||||
|
||||
try
|
||||
{
|
||||
csi.Address = reader.ReadBytes(4);
|
||||
csi.PhysicalRegionSizeInPages = reader.ReadUInt32();
|
||||
csi.SizeInBytes = reader.ReadUInt32();
|
||||
return csi;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class ContentChunkRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Content id
|
||||
/// </summary>
|
||||
public uint ContentId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content index
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does not apply to DLC.
|
||||
/// </remarks>
|
||||
public ContentIndex ContentIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content type
|
||||
/// </summary>
|
||||
public TMDContentType ContentType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content size
|
||||
/// </summary>
|
||||
public ulong ContentSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SHA-256 hash
|
||||
/// </summary>
|
||||
public byte[] SHA256Hash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get content chunk record, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Content chunk record object, null on error</returns>
|
||||
public static ContentChunkRecord Read(BinaryReader reader)
|
||||
{
|
||||
ContentChunkRecord ccr = new ContentChunkRecord();
|
||||
|
||||
try
|
||||
{
|
||||
ccr.ContentId = reader.ReadUInt32();
|
||||
ccr.ContentIndex = (ContentIndex)reader.ReadUInt16();
|
||||
ccr.ContentType = (TMDContentType)reader.ReadUInt16();
|
||||
ccr.ContentSize = reader.ReadUInt64();
|
||||
ccr.SHA256Hash = reader.ReadBytes(0x20);
|
||||
return ccr;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class ContentInfoRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Content index offset
|
||||
/// </summary>
|
||||
public ushort ContentIndexOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content command count [k]
|
||||
/// </summary>
|
||||
public ushort ContentCommandCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SHA-256 hash of the next k content records that have not been hashed yet
|
||||
/// </summary>
|
||||
public byte[] UnhashedContentRecordsSHA256Hash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get content info record, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Content info record object, null on error</returns>
|
||||
public static ContentInfoRecord Read(BinaryReader reader)
|
||||
{
|
||||
ContentInfoRecord cir = new ContentInfoRecord();
|
||||
|
||||
try
|
||||
{
|
||||
cir.ContentIndexOffset = reader.ReadUInt16();
|
||||
cir.ContentCommandCount = reader.ReadUInt16();
|
||||
cir.UnhashedContentRecordsSHA256Hash = reader.ReadBytes(0x20);
|
||||
return cir;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class ExeFSFileHeader
|
||||
{
|
||||
// .code\0\0\0
|
||||
private readonly byte[] codeSegmentBytes = new byte[] { 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x00, 0x00, 0x00 };
|
||||
|
||||
/// <summary>
|
||||
/// File name
|
||||
/// </summary>
|
||||
public byte[] FileName { get; private set; }
|
||||
public string ReadableFileName { get { return Encoding.ASCII.GetString(FileName); } }
|
||||
public bool IsCodeBinary { get { return Enumerable.SequenceEqual(FileName, codeSegmentBytes); } }
|
||||
|
||||
/// <summary>
|
||||
/// File offset
|
||||
/// </summary>
|
||||
public uint FileOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File size
|
||||
/// </summary>
|
||||
public uint FileSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SHA256 hash calculated over the entire file contents
|
||||
/// </summary>
|
||||
public byte[] FileHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an ExeFS file header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ExeFS file header object, null on error</returns>
|
||||
public static ExeFSFileHeader Read(BinaryReader reader)
|
||||
{
|
||||
ExeFSFileHeader header = new ExeFSFileHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.FileName = reader.ReadBytes(8);
|
||||
header.FileOffset = reader.ReadUInt32();
|
||||
header.FileSize = reader.ReadUInt32();
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class ExeFSHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// File headers (10 headers maximum, 16 bytes each)
|
||||
/// </summary>
|
||||
public ExeFSFileHeader[] FileHeaders { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an ExeFS header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ExeFS header object, null on error</returns>
|
||||
public static ExeFSHeader Read(BinaryReader reader)
|
||||
{
|
||||
ExeFSHeader header = new ExeFSHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.FileHeaders = new ExeFSFileHeader[10];
|
||||
for (int i = 0; i < 10; i++)
|
||||
header.FileHeaders[i] = ExeFSFileHeader.Read(reader);
|
||||
|
||||
header.Reserved = reader.ReadBytes(0x20);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
header.FileHeaders[9 - i].FileHash = reader.ReadBytes(0x20);
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class MetaFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Title ID dependency list - Taken from the application's ExHeader
|
||||
/// </summary>
|
||||
public byte[] TitleIDDependencyList { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Core Version
|
||||
/// </summary>
|
||||
public uint CoreVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Icon Data(.ICN) - Taken from the application's ExeFS
|
||||
/// </summary>
|
||||
public byte[] IconData { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get the Metafile data, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Metafile data object, null on error</returns>
|
||||
public static MetaFile Read(BinaryReader reader)
|
||||
{
|
||||
MetaFile metaFile = new MetaFile();
|
||||
|
||||
try
|
||||
{
|
||||
metaFile.TitleIDDependencyList = reader.ReadBytes(0x180);
|
||||
metaFile.Reserved1 = reader.ReadBytes(0x180);
|
||||
metaFile.CoreVersion = reader.ReadUInt32();
|
||||
metaFile.Reserved2 = reader.ReadBytes(0xFC);
|
||||
metaFile.IconData = reader.ReadBytes(0x36C0);
|
||||
|
||||
return metaFile;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class NCCHHeader
|
||||
{
|
||||
private const string NCCHMagicNumber = "NCCH";
|
||||
|
||||
/// <summary>
|
||||
/// Partition number for the current partition
|
||||
/// </summary>
|
||||
public int PartitionNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partition table entry for the current partition
|
||||
/// </summary>
|
||||
public PartitionTableEntry Entry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// RSA-2048 signature of the NCCH header, using SHA-256.
|
||||
/// </summary>
|
||||
public byte[] RSA2048Signature { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content size, in media units (1 media unit = 0x200 bytes)
|
||||
/// </summary>
|
||||
public uint ContentSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partition ID
|
||||
/// </summary>
|
||||
public byte[] PartitionId { get; private set; }
|
||||
public byte[] PlainIV { get { return PartitionId.Concat(Constants.PlainCounter).ToArray(); } }
|
||||
public byte[] ExeFSIV { get { return PartitionId.Concat(Constants.ExefsCounter).ToArray(); } }
|
||||
public byte[] RomFSIV { get { return PartitionId.Concat(Constants.RomfsCounter).ToArray(); } }
|
||||
|
||||
/// <summary>
|
||||
/// Boot rom key
|
||||
/// </summary>
|
||||
public BigInteger KeyX { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// NCCH boot rom key
|
||||
/// </summary>
|
||||
public BigInteger KeyX2C { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Kernel9/Process9 key
|
||||
/// </summary>
|
||||
public BigInteger KeyY { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Normal AES key
|
||||
/// </summary>
|
||||
public BigInteger NormalKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// NCCH AES key
|
||||
/// </summary>
|
||||
public BigInteger NormalKey2C { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maker code
|
||||
/// </summary>
|
||||
public ushort MakerCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Version
|
||||
/// </summary>
|
||||
public ushort Version { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// When ncchflag[7] = 0x20 starting with FIRM 9.6.0-X, this is compared with the first output u32 from a
|
||||
/// SHA256 hash. The data used for that hash is 0x18-bytes: [0x10-long title-unique content lock seed]
|
||||
/// [programID from NCCH + 0x118]. This hash is only used for verification of the content lock seed, and
|
||||
/// is not the actual keyY.
|
||||
/// </summary>
|
||||
public uint VerificationHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Program ID
|
||||
/// </summary>
|
||||
public byte[] ProgramId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logo Region SHA-256 hash. (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11)
|
||||
/// </summary>
|
||||
public byte[] LogoRegionHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Product code
|
||||
/// </summary>
|
||||
public byte[] ProductCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Extended header SHA-256 hash (SHA256 of 2x Alignment Size, beginning at 0x0 of ExHeader)
|
||||
/// </summary>
|
||||
public byte[] ExtendedHeaderHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Extended header size, in bytes
|
||||
/// </summary>
|
||||
public uint ExtendedHeaderSizeInBytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flags
|
||||
/// </summary>
|
||||
public NCCHHeaderFlags Flags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plain region offset, in media units
|
||||
/// </summary>
|
||||
public uint PlainRegionOffsetInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plain region size, in media units
|
||||
/// </summary>
|
||||
public uint PlainRegionSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logo Region offset, in media units (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11)
|
||||
/// </summary>
|
||||
public uint LogoRegionOffsetInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logo Region size, in media units (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11)
|
||||
/// </summary>
|
||||
public uint LogoRegionSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS offset, in media units
|
||||
/// </summary>
|
||||
public uint ExeFSOffsetInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS size, in media units
|
||||
/// </summary>
|
||||
public uint ExeFSSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS hash region size, in media units
|
||||
/// </summary>
|
||||
public uint ExeFSHashRegionSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved3 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RomFS offset, in media units
|
||||
/// </summary>
|
||||
public uint RomFSOffsetInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RomFS size, in media units
|
||||
/// </summary>
|
||||
public uint RomFSSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RomFS hash region size, in media units
|
||||
/// </summary>
|
||||
public uint RomFSHashRegionSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved4 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS superblock SHA-256 hash - (SHA-256 hash, starting at 0x0 of the ExeFS over the number of
|
||||
/// media units specified in the ExeFS hash region size)
|
||||
/// </summary>
|
||||
public byte[] ExeFSSuperblockHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RomFS superblock SHA-256 hash - (SHA-256 hash, starting at 0x0 of the RomFS over the number
|
||||
/// of media units specified in the RomFS hash region size)
|
||||
/// </summary>
|
||||
public byte[] RomFSSuperblockHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an NCCH header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="readSignature">True if the RSA signature is read, false otherwise</param>
|
||||
/// <returns>NCCH header object, null on error</returns>
|
||||
public static NCCHHeader Read(BinaryReader reader, bool readSignature)
|
||||
{
|
||||
NCCHHeader header = new NCCHHeader();
|
||||
|
||||
try
|
||||
{
|
||||
if (readSignature)
|
||||
header.RSA2048Signature = reader.ReadBytes(0x100);
|
||||
|
||||
if (new string(reader.ReadChars(4)) != NCCHMagicNumber)
|
||||
return null;
|
||||
|
||||
header.ContentSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.PartitionId = reader.ReadBytes(8).Reverse().ToArray();
|
||||
header.MakerCode = reader.ReadUInt16();
|
||||
header.Version = reader.ReadUInt16();
|
||||
header.VerificationHash = reader.ReadUInt32();
|
||||
header.ProgramId = reader.ReadBytes(8);
|
||||
header.Reserved1 = reader.ReadBytes(0x10);
|
||||
header.LogoRegionHash = reader.ReadBytes(0x20);
|
||||
header.ProductCode = reader.ReadBytes(0x10);
|
||||
header.ExtendedHeaderHash = reader.ReadBytes(0x20);
|
||||
header.ExtendedHeaderSizeInBytes = reader.ReadUInt32();
|
||||
header.Reserved2 = reader.ReadBytes(4);
|
||||
header.Flags = NCCHHeaderFlags.Read(reader);
|
||||
header.PlainRegionOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.PlainRegionSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.LogoRegionOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.LogoRegionSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.ExeFSOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.ExeFSSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.ExeFSHashRegionSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.Reserved3 = reader.ReadBytes(4);
|
||||
header.RomFSOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.RomFSSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.RomFSHashRegionSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.Reserved4 = reader.ReadBytes(4);
|
||||
header.ExeFSSuperblockHash = reader.ReadBytes(0x20);
|
||||
header.RomFSSuperblockHash = reader.ReadBytes(0x20);
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class NCCHHeaderFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved0 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Crypto Method: When this is non-zero, a NCCH crypto method using two keyslots is used.
|
||||
/// </summary>
|
||||
public CryptoMethod CryptoMethod { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content Platform: 1 = CTR, 2 = snake (New 3DS).
|
||||
/// </summary>
|
||||
public ContentPlatform ContentPlatform { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content Type Bit-masks: Data = 0x1, Executable = 0x2, SystemUpdate = 0x4, Manual = 0x8,
|
||||
/// Child = (0x4|0x8), Trial = 0x10. When 'Data' is set, but not 'Executable', NCCH is a CFA.
|
||||
/// Otherwise when 'Executable' is set, NCCH is a CXI.
|
||||
/// </summary>
|
||||
public ContentType MediaPlatformIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content Unit Size i.e. u32 ContentUnitSize = 0x200*2^flags[6];
|
||||
/// </summary>
|
||||
public byte ContentUnitSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bit-masks: FixedCryptoKey = 0x1, NoMountRomFs = 0x2, NoCrypto = 0x4, using a new keyY
|
||||
/// generator = 0x20(starting with FIRM 9.6.0-X).
|
||||
/// </summary>
|
||||
public BitMasks BitMasks { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get if the NoCrypto bit is set
|
||||
/// </summary>
|
||||
public bool PossblyDecrypted { get { return BitMasks.HasFlag(BitMasks.NoCrypto); } }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an NCCH header flags, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>NCCH header flags object, null on error</returns>
|
||||
public static NCCHHeaderFlags Read(BinaryReader reader)
|
||||
{
|
||||
NCCHHeaderFlags flags = new NCCHHeaderFlags();
|
||||
|
||||
try
|
||||
{
|
||||
flags.Reserved0 = reader.ReadByte();
|
||||
flags.Reserved1 = reader.ReadByte();
|
||||
flags.Reserved2 = reader.ReadByte();
|
||||
flags.CryptoMethod = (CryptoMethod)reader.ReadByte();
|
||||
flags.ContentPlatform = (ContentPlatform)reader.ReadByte();
|
||||
flags.MediaPlatformIndex = (ContentType)reader.ReadByte();
|
||||
flags.ContentUnitSize = reader.ReadByte();
|
||||
flags.BitMasks = (BitMasks)reader.ReadByte();
|
||||
return flags;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write NCCH header flags to stream, if possible
|
||||
/// </summary>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
try
|
||||
{
|
||||
writer.Write(this.Reserved0);
|
||||
writer.Write(this.Reserved1);
|
||||
writer.Write(this.Reserved2);
|
||||
writer.Write((byte)this.CryptoMethod);
|
||||
writer.Write((byte)this.ContentPlatform);
|
||||
writer.Write((byte)this.MediaPlatformIndex);
|
||||
writer.Write((byte)this.ContentUnitSize);
|
||||
writer.Write((byte)this.BitMasks);
|
||||
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,325 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class NCSDHeader
|
||||
{
|
||||
private const string NCSDMagicNumber = "NCSD";
|
||||
|
||||
#region Common to all NCSD files
|
||||
|
||||
/// <summary>
|
||||
/// RSA-2048 SHA-256 signature of the NCSD header
|
||||
/// </summary>
|
||||
public byte[] RSA2048Signature { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the NCSD image, in media units (1 media unit = 0x200 bytes)
|
||||
/// </summary>
|
||||
public uint ImageSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Media ID
|
||||
/// </summary>
|
||||
public byte[] MediaId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partitions FS type (0=None, 1=Normal, 3=FIRM, 4=AGB_FIRM save)
|
||||
/// </summary>
|
||||
public FilesystemType PartitionsFSType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partitions crypt type (each byte corresponds to a partition in the partition table)
|
||||
/// </summary>
|
||||
public byte[] PartitionsCryptType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset & Length partition table, in media units
|
||||
/// </summary>
|
||||
public PartitionTableEntry[] PartitionsTable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partition table entry for Executable Content (CXI)
|
||||
/// </summary>
|
||||
public PartitionTableEntry ExecutableContent { get { return PartitionsTable[0]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Partition table entry for E-Manual (CFA)
|
||||
/// </summary>
|
||||
public PartitionTableEntry EManual { get { return PartitionsTable[1]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Partition table entry for Download Play Child container (CFA)
|
||||
/// </summary>
|
||||
public PartitionTableEntry DownloadPlayChildContainer { get { return PartitionsTable[2]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Partition table entry for New3DS Update Data (CFA)
|
||||
/// </summary>
|
||||
public PartitionTableEntry New3DSUpdateData { get { return PartitionsTable[6]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Partition table entry for Update Data (CFA)
|
||||
/// </summary>
|
||||
public PartitionTableEntry UpdateData { get { return PartitionsTable[7]; } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region CTR Cart Image (CCI) Specific
|
||||
|
||||
/// <summary>
|
||||
/// Exheader SHA-256 hash
|
||||
/// </summary>
|
||||
public byte[] ExheaderHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional header size
|
||||
/// </summary>
|
||||
public uint AdditionalHeaderSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sector zero offset
|
||||
/// </summary>
|
||||
public uint SectorZeroOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partition Flags
|
||||
/// </summary>
|
||||
public byte[] PartitionFlags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Backup Write Wait Time (The time to wait to write save to backup after the card is recognized (0-255
|
||||
/// seconds)).NATIVE_FIRM loads this flag from the gamecard NCSD header starting with 6.0.0-11.
|
||||
/// </summary>
|
||||
public byte BackupWriteWaitTime { get { return PartitionFlags[(int)NCSDFlags.BackupWriteWaitTime]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (SDK 3.X+)
|
||||
/// </summary>
|
||||
public MediaCardDeviceType MediaCardDevice3X { get { return (MediaCardDeviceType)PartitionFlags[(int)NCSDFlags.MediaCardDevice3X]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Media Platform Index (1 = CTR)
|
||||
/// </summary>
|
||||
public MediaPlatformIndex MediaPlatformIndex { get { return (MediaPlatformIndex)PartitionFlags[(int)NCSDFlags.MediaPlatformIndex]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Media Type Index (0 = Inner Device, 1 = Card1, 2 = Card2, 3 = Extended Device)
|
||||
/// </summary>
|
||||
public MediaTypeIndex MediaTypeIndex { get { return (MediaTypeIndex)PartitionFlags[(int)NCSDFlags.MediaTypeIndex]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Media Unit Size i.e. u32 MediaUnitSize = 0x200*2^flags[6];
|
||||
/// </summary>
|
||||
public uint MediaUnitSize { get { return (uint)(0x200 * Math.Pow(2, PartitionFlags[(int)NCSDFlags.MediaUnitSize])); } }
|
||||
|
||||
/// <summary>
|
||||
/// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (Only SDK 2.X)
|
||||
/// </summary>
|
||||
public MediaCardDeviceType MediaCardDevice2X { get { return (MediaCardDeviceType)PartitionFlags[(int)NCSDFlags.MediaCardDevice2X]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Partition ID table
|
||||
/// </summary>
|
||||
public byte[][] PartitionIdTable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved?
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Support for this was implemented with 9.6.0-X FIRM. Bit0=1 enables using bits 1-2, it's unknown
|
||||
/// what these two bits are actually used for(the value of these two bits get compared with some other
|
||||
/// value during NCSD verification/loading). This appears to enable a new, likely hardware-based,
|
||||
/// antipiracy check on cartridges.
|
||||
/// </summary>
|
||||
public byte FirmUpdateByte1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Support for this was implemented with 9.6.0-X FIRM, see below regarding save crypto.
|
||||
/// </summary>
|
||||
public byte FirmUpdateByte2 { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Raw NAND Format Specific
|
||||
|
||||
/// <summary>
|
||||
/// Unknown
|
||||
/// </summary>
|
||||
public byte[] Unknown { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Encrypted MBR partition-table, for the TWL partitions(key-data used for this keyslot is console-unique).
|
||||
/// </summary>
|
||||
public byte[] EncryptedMBR { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Card Info Header
|
||||
|
||||
/// <summary>
|
||||
/// CARD2: Writable Address In Media Units (For 'On-Chip' Savedata). CARD1: Always 0xFFFFFFFF.
|
||||
/// </summary>
|
||||
public byte[] CARD2WritableAddressMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Card Info Bitmask
|
||||
/// </summary>
|
||||
public byte[] CardInfoBytemask { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved1
|
||||
/// </summary>
|
||||
public byte[] Reserved3 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Title version
|
||||
/// </summary>
|
||||
public ushort TitleVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Card revision
|
||||
/// </summary>
|
||||
public ushort CardRevision { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved2
|
||||
/// </summary>
|
||||
public byte[] Reserved4 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Card seed keyY (first u64 is Media ID (same as first NCCH partitionId))
|
||||
/// </summary>
|
||||
public byte[] CardSeedKeyY { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Encrypted card seed (AES-CCM, keyslot 0x3B for retail cards, see CTRCARD_SECSEED) /// </summary>
|
||||
public byte[] EncryptedCardSeed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Card seed AES-MAC
|
||||
/// </summary>
|
||||
public byte[] CardSeedAESMAC { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Card seed nonce
|
||||
/// </summary>
|
||||
public byte[] CardSeedNonce { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved3
|
||||
/// </summary>
|
||||
public byte[] Reserved5 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Copy of first NCCH header (excluding RSA signature)
|
||||
/// </summary>
|
||||
public NCCHHeader BackupHeader { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Development Card Info Header Extension
|
||||
|
||||
/// <summary>
|
||||
/// CardDeviceReserved1
|
||||
/// </summary>
|
||||
public byte[] CardDeviceReserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// TitleKey
|
||||
/// </summary>
|
||||
public byte[] TitleKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// CardDeviceReserved2
|
||||
/// </summary>
|
||||
public byte[] CardDeviceReserved2 { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an NCSD header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="development">True if development cart, false otherwise</param>
|
||||
/// <returns>NCSD header object, null on error</returns>
|
||||
public static NCSDHeader Read(BinaryReader reader, bool development)
|
||||
{
|
||||
NCSDHeader header = new NCSDHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.RSA2048Signature = reader.ReadBytes(0x100);
|
||||
|
||||
if (new string(reader.ReadChars(4)) != NCSDMagicNumber)
|
||||
return null;
|
||||
|
||||
header.ImageSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.MediaId = reader.ReadBytes(8);
|
||||
header.PartitionsFSType = (FilesystemType)reader.ReadUInt64();
|
||||
header.PartitionsCryptType = reader.ReadBytes(8);
|
||||
|
||||
header.PartitionsTable = new PartitionTableEntry[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
header.PartitionsTable[i] = PartitionTableEntry.Read(reader);
|
||||
|
||||
if (header.PartitionsFSType == FilesystemType.Normal
|
||||
|| header.PartitionsFSType == FilesystemType.None)
|
||||
{
|
||||
header.ExheaderHash = reader.ReadBytes(0x20);
|
||||
header.AdditionalHeaderSize = reader.ReadUInt32();
|
||||
header.SectorZeroOffset = reader.ReadUInt32();
|
||||
header.PartitionFlags = reader.ReadBytes(8);
|
||||
|
||||
header.PartitionIdTable = new byte[8][];
|
||||
for (int i = 0; i < 8; i++)
|
||||
header.PartitionIdTable[i] = reader.ReadBytes(8);
|
||||
|
||||
header.Reserved1 = reader.ReadBytes(0x20);
|
||||
header.Reserved2 = reader.ReadBytes(0xE);
|
||||
header.FirmUpdateByte1 = reader.ReadByte();
|
||||
header.FirmUpdateByte2 = reader.ReadByte();
|
||||
|
||||
header.CARD2WritableAddressMediaUnits = reader.ReadBytes(4);
|
||||
header.CardInfoBytemask = reader.ReadBytes(4);
|
||||
header.Reserved3 = reader.ReadBytes(0x108);
|
||||
header.TitleVersion = reader.ReadUInt16();
|
||||
header.CardRevision = reader.ReadUInt16();
|
||||
header.Reserved4 = reader.ReadBytes(0xCEC); // Incorrectly documented as 0xCEE
|
||||
header.CardSeedKeyY = reader.ReadBytes(0x10);
|
||||
header.EncryptedCardSeed = reader.ReadBytes(0x10);
|
||||
header.CardSeedAESMAC = reader.ReadBytes(0x10);
|
||||
header.CardSeedNonce = reader.ReadBytes(0xC);
|
||||
header.Reserved5 = reader.ReadBytes(0xC4);
|
||||
header.BackupHeader = NCCHHeader.Read(reader, readSignature: false);
|
||||
|
||||
if (development)
|
||||
{
|
||||
header.CardDeviceReserved1 = reader.ReadBytes(0x200);
|
||||
header.TitleKey = reader.ReadBytes(0x10);
|
||||
header.CardDeviceReserved2 = reader.ReadBytes(0xF0);
|
||||
}
|
||||
}
|
||||
else if (header.PartitionsFSType == FilesystemType.FIRM)
|
||||
{
|
||||
header.Unknown = reader.ReadBytes(0x5E);
|
||||
header.EncryptedMBR = reader.ReadBytes(0x42);
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class PartitionTableEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset
|
||||
/// </summary>
|
||||
public uint Offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Length
|
||||
/// </summary>
|
||||
public uint Length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get partition table entry, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Partition table entry object, null on error</returns>
|
||||
public static PartitionTableEntry Read(BinaryReader reader)
|
||||
{
|
||||
PartitionTableEntry entry = new PartitionTableEntry();
|
||||
|
||||
try
|
||||
{
|
||||
entry.Offset = reader.ReadUInt32();
|
||||
entry.Length = reader.ReadUInt32();
|
||||
return entry;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for a valid partition
|
||||
/// </summary>
|
||||
/// <returns>True if the offset and length are not 0, false otherwise</returns>
|
||||
public bool IsValid()
|
||||
{
|
||||
return Offset != 0 && Length != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
// https://www.3dbrew.org/wiki/RomFS
|
||||
internal class RomFSHeader
|
||||
{
|
||||
private const string RomFSMagicNumber = "IVFC";
|
||||
private const uint RomFSSecondMagicNumber = 0x10000;
|
||||
|
||||
/// <summary>
|
||||
/// Master hash size
|
||||
/// </summary>
|
||||
public uint MasterHashSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 1 logical offset
|
||||
/// </summary>
|
||||
public ulong Level1LogicalOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 1 hashdata size
|
||||
/// </summary>
|
||||
public ulong Level1HashdataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 1 block size, in log2
|
||||
/// </summary>
|
||||
public uint Level1BlockSizeLog2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 2 logical offset
|
||||
/// </summary>
|
||||
public ulong Level2LogicalOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 2 hashdata size
|
||||
/// </summary>
|
||||
public ulong Level2HashdataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 2 block size, in log2
|
||||
/// </summary>
|
||||
public uint Level2BlockSizeLog2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 3 logical offset
|
||||
/// </summary>
|
||||
public ulong Level3LogicalOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 3 hashdata size
|
||||
/// </summary>
|
||||
public ulong Level3HashdataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 3 block size, in log2
|
||||
/// </summary>
|
||||
public uint Level3BlockSizeLog2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved3 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved4 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional info size.
|
||||
/// </summary>
|
||||
public uint OptionalInfoSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get a RomFS header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>RomFS header object, null on error</returns>
|
||||
public static RomFSHeader Read(BinaryReader reader)
|
||||
{
|
||||
RomFSHeader header = new RomFSHeader();
|
||||
|
||||
try
|
||||
{
|
||||
if (new string(reader.ReadChars(4)) != RomFSMagicNumber)
|
||||
return null;
|
||||
|
||||
if (reader.ReadUInt32() != RomFSSecondMagicNumber)
|
||||
return null;
|
||||
|
||||
header.MasterHashSize = reader.ReadUInt32();
|
||||
header.Level1LogicalOffset = reader.ReadUInt64();
|
||||
header.Level1HashdataSize = reader.ReadUInt64();
|
||||
header.Level1BlockSizeLog2 = reader.ReadUInt32();
|
||||
header.Reserved1 = reader.ReadBytes(4);
|
||||
header.Level2LogicalOffset = reader.ReadUInt64();
|
||||
header.Level2HashdataSize = reader.ReadUInt64();
|
||||
header.Level2BlockSizeLog2 = reader.ReadUInt32();
|
||||
header.Reserved2 = reader.ReadBytes(4);
|
||||
header.Level3LogicalOffset = reader.ReadUInt64();
|
||||
header.Level3HashdataSize = reader.ReadUInt64();
|
||||
header.Level3BlockSizeLog2 = reader.ReadUInt32();
|
||||
header.Reserved3 = reader.ReadBytes(4);
|
||||
header.Reserved4 = reader.ReadBytes(4);
|
||||
header.OptionalInfoSize = reader.ReadUInt32();
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class StorageInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Extdata ID
|
||||
/// </summary>
|
||||
public byte[] ExtdataID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// System savedata IDs
|
||||
/// </summary>
|
||||
public byte[] SystemSavedataIDs { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Storage accessible unique IDs
|
||||
/// </summary>
|
||||
public byte[] StorageAccessibleUniqueIDs { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Filesystem access info
|
||||
/// </summary>
|
||||
public byte[] FilesystemAccessInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Other attributes
|
||||
/// </summary>
|
||||
public StorageInfoOtherAttributes OtherAttributes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get storage info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Storage info object, null on error</returns>
|
||||
public static StorageInfo Read(BinaryReader reader)
|
||||
{
|
||||
StorageInfo si = new StorageInfo();
|
||||
|
||||
try
|
||||
{
|
||||
si.ExtdataID = reader.ReadBytes(8);
|
||||
si.SystemSavedataIDs = reader.ReadBytes(8);
|
||||
si.StorageAccessibleUniqueIDs = reader.ReadBytes(8);
|
||||
si.FilesystemAccessInfo = reader.ReadBytes(7);
|
||||
si.OtherAttributes = (StorageInfoOtherAttributes)reader.ReadByte();
|
||||
return si;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class SystemControlInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Application title (default is "CtrApp")
|
||||
/// </summary>
|
||||
public char[] ApplicationTitle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag (bit 0: CompressExefsCode, bit 1: SDApplication)
|
||||
/// </summary>
|
||||
public byte Flag { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Remaster version
|
||||
/// </summary>
|
||||
public byte[] RemasterVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Text code set info
|
||||
/// </summary>
|
||||
public CodeSetInfo TextCodesetInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stack size
|
||||
/// </summary>
|
||||
public uint StackSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read-only code set info
|
||||
/// </summary>
|
||||
public CodeSetInfo ReadOnlyCodeSetInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Data code set info
|
||||
/// </summary>
|
||||
public CodeSetInfo DataCodeSetInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// BSS size
|
||||
/// </summary>
|
||||
public uint BSSSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dependency module (program ID) list
|
||||
/// </summary>
|
||||
public byte[][] DependencyModuleList { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SystemInfo
|
||||
/// </summary>
|
||||
public SystemInfo SystemInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get system control info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>System control info object, null on error</returns>
|
||||
public static SystemControlInfo Read(BinaryReader reader)
|
||||
{
|
||||
SystemControlInfo sci = new SystemControlInfo();
|
||||
|
||||
try
|
||||
{
|
||||
sci.ApplicationTitle = reader.ReadChars(8);
|
||||
sci.Reserved1 = reader.ReadBytes(5);
|
||||
sci.Flag = reader.ReadByte();
|
||||
sci.RemasterVersion = reader.ReadBytes(2);
|
||||
sci.TextCodesetInfo = CodeSetInfo.Read(reader);
|
||||
sci.StackSize = reader.ReadUInt32();
|
||||
sci.ReadOnlyCodeSetInfo = CodeSetInfo.Read(reader);
|
||||
sci.Reserved2 = reader.ReadBytes(4);
|
||||
sci.DataCodeSetInfo = CodeSetInfo.Read(reader);
|
||||
sci.BSSSize = reader.ReadUInt32();
|
||||
|
||||
sci.DependencyModuleList = new byte[48][];
|
||||
for (int i = 0; i < 48; i++)
|
||||
sci.DependencyModuleList[i] = reader.ReadBytes(8);
|
||||
|
||||
sci.SystemInfo = SystemInfo.Read(reader);
|
||||
return sci;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
internal class SystemInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// SaveData Size
|
||||
/// </summary>
|
||||
public ulong SaveDataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Jump ID
|
||||
/// </summary>
|
||||
public byte[] JumpID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get system info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>System info object, null on error</returns>
|
||||
public static SystemInfo Read(BinaryReader reader)
|
||||
{
|
||||
SystemInfo si = new SystemInfo();
|
||||
|
||||
try
|
||||
{
|
||||
si.SaveDataSize = reader.ReadUInt64();
|
||||
si.JumpID = reader.ReadBytes(8);
|
||||
si.Reserved = reader.ReadBytes(0x30);
|
||||
return si;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,279 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
// https://www.3dbrew.org/wiki/Ticket
|
||||
internal class Ticket
|
||||
{
|
||||
/// <summary>
|
||||
/// Signature Type
|
||||
/// </summary>
|
||||
public SignatureType SignatureType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signature size
|
||||
/// </summary>
|
||||
public ushort SignatureSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Padding size
|
||||
/// </summary>
|
||||
public byte PaddingSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signature
|
||||
/// </summary>
|
||||
public byte[] Signature { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Issuer
|
||||
/// </summary>
|
||||
public byte[] Issuer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Issuer as a trimmed string
|
||||
/// </summary>
|
||||
public string IssuerString => Issuer != null && Issuer.Length > 0
|
||||
? Encoding.ASCII.GetString(Issuer)?.TrimEnd('\0')
|
||||
: null;
|
||||
|
||||
/// <summary>
|
||||
/// ECC PublicKey
|
||||
/// </summary>
|
||||
public byte[] ECCPublicKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Version (For 3DS this is always 1)
|
||||
/// </summary>
|
||||
public byte Version { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// CaCrlVersion
|
||||
/// </summary>
|
||||
public byte CaCrlVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SignerCrlVersion
|
||||
/// </summary>
|
||||
public byte SignerCrlVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// TitleKey (normal-key encrypted using one of the common keyYs; see below)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The titlekey is decrypted by using the AES engine with the ticket common-key keyslot.
|
||||
/// The keyY is selected through an index (ticket offset 0xB1) into a plaintext array
|
||||
/// of 6 keys ("common keyYs") stored in the data section of Process9. AES-CBC mode is used
|
||||
/// where the IV is the big-endian titleID. Note that on a retail unit index0 is a retail keyY,
|
||||
/// while on a dev-unit index0 is the dev common-key which is a normal-key.
|
||||
/// (On retail for these keyYs, the hardware key-scrambler is used)
|
||||
///
|
||||
/// The titlekey is used to decrypt content downloaded from the CDN using 128-bit AES-CBC with
|
||||
/// the content index (as big endian u16, padded with trailing zeroes) as the IV.
|
||||
/// </remarks>
|
||||
public byte[] TitleKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// TicketID
|
||||
/// </summary>
|
||||
public ulong TicketID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ConsoleID
|
||||
/// </summary>
|
||||
public uint ConsoleID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// TitleID
|
||||
/// </summary>
|
||||
public ulong TitleID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public ushort Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ticket title version
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Ticket Title Version is generally the same as the title version stored in the
|
||||
/// Title Metadata. Although it doesn't have to match the TMD version to be valid.
|
||||
/// </remarks>
|
||||
public ushort TicketTitleVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public ulong Reserved3 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// License Type
|
||||
/// </summary>
|
||||
public byte LicenseType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Index to the common keyY used for this ticket, usually 0x1 for retail system titles;
|
||||
/// see below.
|
||||
/// </summary>
|
||||
public byte CommonKeyYIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved4 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// eShop Account ID?
|
||||
/// </summary>
|
||||
public uint eShopAccountID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved5 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Audit
|
||||
/// </summary>
|
||||
public byte Audit { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved6 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Limits
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In demos, the first u32 in the "Limits" section is 0x4, then the second u32 is the max-playcount.
|
||||
/// </remarks>
|
||||
public int[] Limits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Denotes if the ticket denotes a demo or not
|
||||
/// </summary>
|
||||
public bool IsDemo => Limits != null && Limits.Length > 0 ? Limits[0] == 0x0004 : false;
|
||||
|
||||
/// <summary>
|
||||
/// Denotes if the max playcount for a demo
|
||||
/// </summary>
|
||||
public int PlayCount => Limits != null && Limits.Length > 1 ? Limits[1] : 0;
|
||||
|
||||
/// <summary>
|
||||
/// The Content Index of a ticket has its own size defined within itself,
|
||||
/// with seemingly a minimal of 20 bytes, the second u32 in big endian defines
|
||||
/// the full value of X.
|
||||
/// </summary>
|
||||
public int ContentIndexSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content Index
|
||||
/// </summary>
|
||||
public byte[] ContentIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Certificate chain
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// https://www.3dbrew.org/wiki/Ticket#Certificate_Chain
|
||||
/// </remarks>
|
||||
public Certificate[] CertificateChain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get ticket, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="ticketSize">Ticket size from the header</param>
|
||||
/// <returns>Ticket object, null on error</returns>
|
||||
public static Ticket Read(BinaryReader reader, int ticketSize)
|
||||
{
|
||||
Ticket tk = new Ticket();
|
||||
|
||||
try
|
||||
{
|
||||
long startingPosition = reader.BaseStream.Position;
|
||||
|
||||
tk.SignatureType = (SignatureType)reader.ReadUInt32();
|
||||
switch (tk.SignatureType)
|
||||
{
|
||||
case SignatureType.RSA_4096_SHA1:
|
||||
case SignatureType.RSA_4096_SHA256:
|
||||
tk.SignatureSize = 0x200;
|
||||
tk.PaddingSize = 0x3C;
|
||||
break;
|
||||
case SignatureType.RSA_2048_SHA1:
|
||||
case SignatureType.RSA_2048_SHA256:
|
||||
tk.SignatureSize = 0x100;
|
||||
tk.PaddingSize = 0x3C;
|
||||
break;
|
||||
case SignatureType.ECDSA_SHA1:
|
||||
case SignatureType.ECDSA_SHA256:
|
||||
tk.SignatureSize = 0x03C;
|
||||
tk.PaddingSize = 0x40;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
tk.Signature = reader.ReadBytes(tk.SignatureSize);
|
||||
reader.ReadBytes(tk.PaddingSize); // Padding
|
||||
tk.Issuer = reader.ReadBytes(0x40);
|
||||
tk.ECCPublicKey = reader.ReadBytes(0x3C);
|
||||
tk.Version = reader.ReadByte();
|
||||
tk.CaCrlVersion = reader.ReadByte();
|
||||
tk.SignerCrlVersion = reader.ReadByte();
|
||||
tk.TitleKey = reader.ReadBytes(0x10);
|
||||
tk.Reserved1 = reader.ReadByte();
|
||||
tk.TicketID = reader.ReadUInt64();
|
||||
tk.ConsoleID = reader.ReadUInt32();
|
||||
tk.TitleID = reader.ReadUInt64();
|
||||
tk.Reserved2 = reader.ReadUInt16();
|
||||
tk.TicketTitleVersion = reader.ReadUInt16();
|
||||
tk.Reserved3 = reader.ReadUInt64();
|
||||
tk.LicenseType = reader.ReadByte();
|
||||
tk.CommonKeyYIndex = reader.ReadByte();
|
||||
tk.Reserved4 = reader.ReadBytes(0x2A);
|
||||
tk.eShopAccountID = reader.ReadUInt32();
|
||||
tk.Reserved5 = reader.ReadByte();
|
||||
tk.Audit = reader.ReadByte();
|
||||
tk.Reserved6 = reader.ReadBytes(0x42);
|
||||
|
||||
tk.Limits = new int[0x10];
|
||||
for (int i = 0; i < 0x10; i++)
|
||||
{
|
||||
tk.Limits[i] = reader.ReadInt32();
|
||||
}
|
||||
|
||||
reader.ReadBytes(4); // Seek to size in Content Index
|
||||
tk.ContentIndexSize = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0);
|
||||
reader.BaseStream.Seek(-8, SeekOrigin.Current);
|
||||
tk.ContentIndex = reader.ReadBytes(tk.ContentIndexSize);
|
||||
if (reader.BaseStream.Position % 64 != 0)
|
||||
reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current);
|
||||
|
||||
if (ticketSize > (reader.BaseStream.Position - startingPosition) + (2 * 0x200))
|
||||
{
|
||||
tk.CertificateChain = new Certificate[2];
|
||||
tk.CertificateChain[0] = Certificate.Read(reader); // Ticket
|
||||
tk.CertificateChain[1] = Certificate.Read(reader); // CA
|
||||
}
|
||||
|
||||
return tk;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NDecrypt.N3DS.Headers
|
||||
{
|
||||
// https://www.3dbrew.org/wiki/Title_metadata
|
||||
internal class TitleMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Signature Type
|
||||
/// </summary>
|
||||
public SignatureType SignatureType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signature size
|
||||
/// </summary>
|
||||
public ushort SignatureSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Padding size
|
||||
/// </summary>
|
||||
public byte PaddingSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signature
|
||||
/// </summary>
|
||||
public byte[] Signature { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signature Issuer
|
||||
/// </summary>
|
||||
public byte[] SignatureIssuer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signature Issuer as a trimmed string
|
||||
/// </summary>
|
||||
public string SignatureIssuerString => SignatureIssuer != null && SignatureIssuer.Length > 0
|
||||
? Encoding.ASCII.GetString(SignatureIssuer)?.TrimEnd('\0')
|
||||
: null;
|
||||
|
||||
/// <summary>
|
||||
/// Version
|
||||
/// </summary>
|
||||
public byte Version { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// CaCrlVersion
|
||||
/// </summary>
|
||||
public byte CaCrlVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SignerCrlVersion
|
||||
/// </summary>
|
||||
public byte SignerCrlVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// System Version
|
||||
/// </summary>
|
||||
public ulong SystemVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// TitleID
|
||||
/// </summary>
|
||||
public ulong TitleID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Title Type
|
||||
/// </summary>
|
||||
public uint TitleType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Group ID
|
||||
/// </summary>
|
||||
public ushort GroupID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Save Data Size in Little Endian (Bytes) (Also SRL Public Save Data Size)
|
||||
/// </summary>
|
||||
public uint SaveDataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SRL Private Save Data Size in Little Endian (Bytes)
|
||||
/// </summary>
|
||||
public uint SRLPrivateSaveDataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public uint Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SRL Flag
|
||||
/// </summary>
|
||||
public byte SRLFlag { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved3 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Access Rights
|
||||
/// </summary>
|
||||
public uint AccessRights { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Title Version
|
||||
/// </summary>
|
||||
public ushort TitleVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content Count
|
||||
/// </summary>
|
||||
public ushort ContentCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Boot Content
|
||||
/// </summary>
|
||||
public ushort BootContent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Padding
|
||||
/// </summary>
|
||||
public ushort Padding { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SHA-256 Hash of the Content Info Records
|
||||
/// </summary>
|
||||
public byte[] SHA256HashContentInfoRecords { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// There are 64 of these records, usually only the first is used.
|
||||
/// </summary>
|
||||
public ContentInfoRecord[] ContentInfoRecords { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// There is one of these for each content contained in this title.
|
||||
/// (Determined by "Content Count" in the TMD Header).
|
||||
/// </summary>
|
||||
public ContentChunkRecord[] ContentChunkRecords { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Certificate chain
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// https://www.3dbrew.org/wiki/Title_metadata#Certificate_Chain
|
||||
/// </remarks>
|
||||
public Certificate[] CertificateChain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get ticket metadata, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="metadataSize">Metadata size from the header</param>
|
||||
/// <returns>Title metadata object, null on error</returns>
|
||||
public static TitleMetadata Read(BinaryReader reader, int metadataSize)
|
||||
{
|
||||
TitleMetadata tm = new TitleMetadata();
|
||||
|
||||
try
|
||||
{
|
||||
long startingPosition = reader.BaseStream.Position;
|
||||
|
||||
tm.SignatureType = (SignatureType)reader.ReadUInt32();
|
||||
switch (tm.SignatureType)
|
||||
{
|
||||
case SignatureType.RSA_4096_SHA1:
|
||||
case SignatureType.RSA_4096_SHA256:
|
||||
tm.SignatureSize = 0x200;
|
||||
tm.PaddingSize = 0x3C;
|
||||
break;
|
||||
case SignatureType.RSA_2048_SHA1:
|
||||
case SignatureType.RSA_2048_SHA256:
|
||||
tm.SignatureSize = 0x100;
|
||||
tm.PaddingSize = 0x3C;
|
||||
break;
|
||||
case SignatureType.ECDSA_SHA1:
|
||||
case SignatureType.ECDSA_SHA256:
|
||||
tm.SignatureSize = 0x03C;
|
||||
tm.PaddingSize = 0x40;
|
||||
break;
|
||||
}
|
||||
|
||||
tm.Signature = reader.ReadBytes(tm.SignatureSize);
|
||||
reader.ReadBytes(tm.PaddingSize); // Padding
|
||||
tm.SignatureIssuer = reader.ReadBytes(0x40);
|
||||
tm.Version = reader.ReadByte();
|
||||
tm.CaCrlVersion = reader.ReadByte();
|
||||
tm.SignerCrlVersion = reader.ReadByte();
|
||||
tm.Reserved1 = reader.ReadByte();
|
||||
tm.SystemVersion = reader.ReadUInt64();
|
||||
tm.TitleID = reader.ReadUInt64();
|
||||
tm.TitleType = reader.ReadUInt32();
|
||||
tm.GroupID = reader.ReadUInt16();
|
||||
tm.SaveDataSize = reader.ReadUInt32();
|
||||
tm.SRLPrivateSaveDataSize = reader.ReadUInt32();
|
||||
tm.Reserved2 = reader.ReadUInt32();
|
||||
tm.SRLFlag = reader.ReadByte();
|
||||
tm.Reserved3 = reader.ReadBytes(0x31);
|
||||
tm.AccessRights = reader.ReadUInt32();
|
||||
tm.TitleVersion = reader.ReadUInt16();
|
||||
tm.ContentCount = BitConverter.ToUInt16(reader.ReadBytes(2).Reverse().ToArray(), 0);
|
||||
tm.BootContent = reader.ReadUInt16();
|
||||
tm.Padding = reader.ReadUInt16();
|
||||
tm.SHA256HashContentInfoRecords = reader.ReadBytes(0x20);
|
||||
|
||||
tm.ContentInfoRecords = new ContentInfoRecord[64];
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
tm.ContentInfoRecords[i] = ContentInfoRecord.Read(reader);
|
||||
}
|
||||
|
||||
tm.ContentChunkRecords = new ContentChunkRecord[tm.ContentCount];
|
||||
for (int i = 0; i < tm.ContentCount; i++)
|
||||
{
|
||||
tm.ContentChunkRecords[i] = ContentChunkRecord.Read(reader);
|
||||
}
|
||||
|
||||
if (metadataSize > (reader.BaseStream.Position - startingPosition) + (2 * 0x200))
|
||||
{
|
||||
tm.CertificateChain = new Certificate[2];
|
||||
tm.CertificateChain[0] = Certificate.Read(reader); // TMD
|
||||
tm.CertificateChain[1] = Certificate.Read(reader); // CA
|
||||
}
|
||||
|
||||
return tm;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net48;net6.0</TargetFrameworks>
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Copyright>Copyright (c)2018-2023 Matt Nadareski</Copyright>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<RepositoryUrl>https://github.com/SabreTools/NDecrypt</RepositoryUrl>
|
||||
<Version>0.2.5</Version>
|
||||
<AssemblyVersion>$(Version)</AssemblyVersion>
|
||||
<FileVersion>$(Version)</FileVersion>
|
||||
<IncludeSource>true</IncludeSource>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NDecrypt.Core\NDecrypt.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,611 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using NDecrypt.Core;
|
||||
using NDecrypt.N3DS.Headers;
|
||||
using static NDecrypt.Core.Helper;
|
||||
|
||||
namespace NDecrypt.N3DS
|
||||
{
|
||||
public class ThreeDSTool : ITool
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the input 3DS file
|
||||
/// </summary>
|
||||
private readonly string filename;
|
||||
|
||||
/// <summary>
|
||||
/// Decryption args to use while processing
|
||||
/// </summary>
|
||||
private readonly DecryptArgs decryptArgs;
|
||||
|
||||
public ThreeDSTool(string filename, DecryptArgs decryptArgs)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.decryptArgs = decryptArgs;
|
||||
}
|
||||
|
||||
#region Common Methods
|
||||
|
||||
/// <summary>
|
||||
/// Process an input file given the input values
|
||||
/// </summary>
|
||||
public bool ProcessFile()
|
||||
{
|
||||
// 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 (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
|
||||
using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)))
|
||||
{
|
||||
NCSDHeader header = NCSDHeader.Read(reader, decryptArgs.Development);
|
||||
if (header == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS cart image!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process all 8 NCCH partitions
|
||||
ProcessAllPartitions(header, 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 3DS or New 3DS cart image and try again.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process all partitions in the partition table of an NCSD header
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessAllPartitions(NCSDHeader ncsdHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// Iterate over all 8 NCCH partitions
|
||||
for (int p = 0; p < 8; p++)
|
||||
{
|
||||
NCCHHeader ncchHeader = GetPartitionHeader(ncsdHeader, reader, p);
|
||||
if (ncchHeader == null)
|
||||
continue;
|
||||
|
||||
ProcessPartition(ncsdHeader, ncchHeader, reader, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific partition header from the partition table
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="partitionNumber">Partition number to attempt to retrieve</param>
|
||||
/// <returns>NCCH header for the partition requested, null on error</returns>
|
||||
private NCCHHeader GetPartitionHeader(NCSDHeader ncsdHeader, BinaryReader reader, int partitionNumber)
|
||||
{
|
||||
if (!ncsdHeader.PartitionsTable[partitionNumber].IsValid())
|
||||
{
|
||||
Console.WriteLine($"Partition {partitionNumber} Not found... Skipping...");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Seek to the beginning of the NCCH partition
|
||||
reader.BaseStream.Seek((ncsdHeader.PartitionsTable[partitionNumber].Offset * ncsdHeader.MediaUnitSize), SeekOrigin.Begin);
|
||||
|
||||
NCCHHeader partitionHeader = NCCHHeader.Read(reader, readSignature: true);
|
||||
if (partitionHeader == null)
|
||||
{
|
||||
Console.WriteLine($"Partition {partitionNumber} Unable to read NCCH header");
|
||||
return null;
|
||||
}
|
||||
|
||||
partitionHeader.PartitionNumber = partitionNumber;
|
||||
partitionHeader.Entry = ncsdHeader.PartitionsTable[partitionNumber];
|
||||
return partitionHeader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a single partition
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessPartition(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If we're forcing the operation, tell the user
|
||||
if (decryptArgs.Force)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} is not verified due to force flag being set.");
|
||||
}
|
||||
// If we're not forcing the operation, check if the 'NoCrypto' bit is set
|
||||
else if (ncchHeader.Flags.PossblyDecrypted ^ decryptArgs.Encrypt)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber}: Already " + (decryptArgs.Encrypt ? "Encrypted" : "Decrypted") + "?...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the Keys to be used
|
||||
SetEncryptionKeys(ncsdHeader, ncchHeader);
|
||||
|
||||
// Process the extended header
|
||||
ProcessExtendedHeader(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// If we're encrypting, encrypt the filesystems and update the flags
|
||||
if (decryptArgs.Encrypt)
|
||||
{
|
||||
EncryptExeFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
EncryptRomFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
UpdateEncryptCryptoAndMasks(ncsdHeader, ncchHeader, writer);
|
||||
}
|
||||
|
||||
// If we're decrypting, decrypt the filesystems and update the flags
|
||||
else
|
||||
{
|
||||
DecryptExeFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
DecryptRomFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
UpdateDecryptCryptoAndMasks(ncsdHeader, ncchHeader, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine the set of keys to be used for encryption or decryption
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
private void SetEncryptionKeys(NCSDHeader ncsdHeader, NCCHHeader ncchHeader)
|
||||
{
|
||||
ncchHeader.KeyX = 0;
|
||||
ncchHeader.KeyX2C = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C;
|
||||
|
||||
// Backup headers can't have a KeyY value set
|
||||
if (ncchHeader.RSA2048Signature != null)
|
||||
ncchHeader.KeyY = new BigInteger(ncchHeader.RSA2048Signature.Take(16).Reverse().ToArray());
|
||||
else
|
||||
ncchHeader.KeyY = new BigInteger(0);
|
||||
|
||||
ncchHeader.NormalKey = 0;
|
||||
ncchHeader.NormalKey2C = RotateLeft((RotateLeft(ncchHeader.KeyX2C, 2, 128) ^ ncchHeader.KeyY) + decryptArgs.AESHardwareConstant, 87, 128);
|
||||
|
||||
// Set the header to use based on mode
|
||||
BitMasks masks;
|
||||
CryptoMethod method;
|
||||
if (decryptArgs.Encrypt)
|
||||
{
|
||||
masks = ncsdHeader.BackupHeader.Flags.BitMasks;
|
||||
method = ncsdHeader.BackupHeader.Flags.CryptoMethod;
|
||||
}
|
||||
else
|
||||
{
|
||||
masks = ncchHeader.Flags.BitMasks;
|
||||
method = ncchHeader.Flags.CryptoMethod;
|
||||
}
|
||||
|
||||
if (masks.HasFlag(BitMasks.FixedCryptoKey))
|
||||
{
|
||||
ncchHeader.NormalKey = 0x00;
|
||||
ncchHeader.NormalKey2C = 0x00;
|
||||
Console.WriteLine("Encryption Method: Zero Key");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (method == CryptoMethod.Original)
|
||||
{
|
||||
ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C;
|
||||
Console.WriteLine("Encryption Method: Key 0x2C");
|
||||
}
|
||||
else if (method == CryptoMethod.Seven)
|
||||
{
|
||||
ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x25 : decryptArgs.KeyX0x25;
|
||||
Console.WriteLine("Encryption Method: Key 0x25");
|
||||
}
|
||||
else if (method == CryptoMethod.NineThree)
|
||||
{
|
||||
ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x18 : decryptArgs.KeyX0x18;
|
||||
Console.WriteLine("Encryption Method: Key 0x18");
|
||||
}
|
||||
else if (method == CryptoMethod.NineSix)
|
||||
{
|
||||
ncchHeader.KeyX = decryptArgs.Development ? decryptArgs.DevKeyX0x1B : decryptArgs.KeyX0x1B;
|
||||
Console.WriteLine("Encryption Method: Key 0x1B");
|
||||
}
|
||||
|
||||
ncchHeader.NormalKey = RotateLeft((RotateLeft(ncchHeader.KeyX, 2, 128) ^ ncchHeader.KeyY) + decryptArgs.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the extended header, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private bool ProcessExtendedHeader(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
if (ncchHeader.ExtendedHeaderSizeInBytes > 0)
|
||||
{
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x200, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x200, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + ": ExHeader");
|
||||
|
||||
var cipher = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.PlainIV, decryptArgs.Encrypt);
|
||||
byte[] readBytes = reader.ReadBytes(Constants.CXTExtendedDataHeaderLength);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
writer.Write(processedBytes);
|
||||
writer.Flush();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Extended Header... Skipping...");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the extended header, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessExeFSFileEntries(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
ExeFSHeader exefsHeader = ExeFSHeader.Read(reader);
|
||||
|
||||
// If the header failed to read, log and return
|
||||
if (exefsHeader == null)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS header could not be read. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders)
|
||||
{
|
||||
// Only decrypt a file if it's a code binary
|
||||
if (!fileHeader.IsCodeBinary)
|
||||
continue;
|
||||
|
||||
uint datalenM = ((fileHeader.FileSize) / (1024 * 1024));
|
||||
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
|
||||
uint ctroffset = ((fileHeader.FileOffset + ncsdHeader.MediaUnitSize) / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffsetForHeader = AddToByteArray(ncchHeader.ExeFSIV, (int)ctroffset);
|
||||
|
||||
var firstCipher = CreateAESCipher(ncchHeader.NormalKey, exefsIVWithOffsetForHeader, decryptArgs.Encrypt);
|
||||
var secondCipher = CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffsetForHeader, !decryptArgs.Encrypt);
|
||||
|
||||
reader.BaseStream.Seek((((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * ncsdHeader.MediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) + 1) * ncsdHeader.MediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
if (datalenM > 0)
|
||||
{
|
||||
for (int i = 0; i < datalenM; i++)
|
||||
{
|
||||
byte[] readBytes = reader.ReadBytes(1024 * 1024);
|
||||
byte[] firstProcessedBytes = firstCipher.ProcessBytes(readBytes);
|
||||
byte[] secondProcessedBytes = secondCipher.ProcessBytes(firstProcessedBytes);
|
||||
writer.Write(secondProcessedBytes);
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {i} / {datalenM + 1} mb...");
|
||||
}
|
||||
}
|
||||
|
||||
if (datalenB > 0)
|
||||
{
|
||||
byte[] readBytes = reader.ReadBytes((int)datalenB);
|
||||
byte[] firstFinalBytes = firstCipher.DoFinal(readBytes);
|
||||
byte[] secondFinalBytes = secondCipher.DoFinal(firstFinalBytes);
|
||||
writer.Write(secondFinalBytes);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the ExeFS Filename Table
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessExeFSFilenameTable(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table");
|
||||
|
||||
var cipher = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.ExeFSIV, decryptArgs.Encrypt);
|
||||
byte[] readBytes = reader.ReadBytes((int)ncsdHeader.MediaUnitSize);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
writer.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
|
||||
reader.BaseStream.Seek(0, SeekOrigin.Begin);
|
||||
#endif
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
int exefsSizeM = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * ncsdHeader.MediaUnitSize) / (1024 * 1024));
|
||||
int exefsSizeB = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * ncsdHeader.MediaUnitSize) % (1024 * 1024));
|
||||
int ctroffsetE = (int)(ncsdHeader.MediaUnitSize / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffset = AddToByteArray(ncchHeader.ExeFSIV, ctroffsetE);
|
||||
|
||||
var cipher = CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffset, decryptArgs.Encrypt);
|
||||
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
if (exefsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < exefsSizeM; i++)
|
||||
{
|
||||
byte[] readBytes = reader.ReadBytes(1024 * 1024);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
writer.Write(processedBytes);
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (exefsSizeB > 0)
|
||||
{
|
||||
byte[] readBytes = reader.ReadBytes(exefsSizeB);
|
||||
byte[] finalBytes = cipher.DoFinal(readBytes);
|
||||
writer.Write(finalBytes);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Decrypt
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void DecryptExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If the ExeFS size is 0, we log and return
|
||||
if (ncchHeader.ExeFSSizeInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrypt the filename table
|
||||
ProcessExeFSFilenameTable(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
if (ncchHeader.Flags.CryptoMethod != CryptoMethod.Original)
|
||||
ProcessExeFSFileEntries(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// Decrypt the rest of the ExeFS
|
||||
ProcessExeFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the RomFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// TODO: See how much can be extracted into a common method with Encrypt
|
||||
private void DecryptRomFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If the RomFS offset is 0, we log and return
|
||||
if (ncchHeader.RomFSOffsetInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} RomFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) / (1024 * 1024));
|
||||
int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) % (1024 * 1024));
|
||||
|
||||
var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV, decryptArgs.Encrypt);
|
||||
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
if (romfsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < romfsSizeM; i++)
|
||||
{
|
||||
byte[] readBytes = reader.ReadBytes(1024 * 1024);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
writer.Write(processedBytes);
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Decrypting: {i} / {romfsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
byte[] readBytes = reader.ReadBytes(romfsSizeB);
|
||||
byte[] finalBytes = cipher.DoFinal(readBytes);
|
||||
writer.Write(finalBytes);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Decrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the CryptoMethod and BitMasks for the decrypted partition
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void UpdateDecryptCryptoAndMasks(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryWriter writer)
|
||||
{
|
||||
// Write the new CryptoMethod
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18B, SeekOrigin.Begin);
|
||||
writer.Write((byte)CryptoMethod.Original);
|
||||
writer.Flush();
|
||||
|
||||
// Write the new BitMasks flag
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18F, SeekOrigin.Begin);
|
||||
BitMasks flag = ncchHeader.Flags.BitMasks;
|
||||
flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
|
||||
flag |= BitMasks.NoCrypto;
|
||||
writer.Write((byte)flag);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Encrypt
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void EncryptExeFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If the ExeFS size is 0, we log and return
|
||||
if (ncchHeader.ExeFSSizeInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// For all but the original crypto method, process each of the files in the table
|
||||
if (ncsdHeader.BackupHeader.Flags.CryptoMethod != CryptoMethod.Original)
|
||||
ProcessExeFSFileEntries(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// Encrypt the filename table
|
||||
ProcessExeFSFilenameTable(ncsdHeader, ncchHeader, reader, writer);
|
||||
|
||||
// Encrypt the rest of the ExeFS
|
||||
ProcessExeFS(ncsdHeader, ncchHeader, reader, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the RomFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// TODO: See how much can be extracted into a common method with Decrypt
|
||||
private void EncryptRomFS(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If the RomFS offset is 0, we log and return
|
||||
if (ncchHeader.RomFSOffsetInMediaUnits == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} RomFS: No Data... Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) / (1024 * 1024));
|
||||
int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) % (1024 * 1024));
|
||||
|
||||
// Encrypting RomFS for partitions 1 and up always use Key0x2C
|
||||
if (ncchHeader.PartitionNumber > 0)
|
||||
{
|
||||
if (ncsdHeader.BackupHeader.Flags?.BitMasks.HasFlag(BitMasks.FixedCryptoKey) == true) // except if using zero-key
|
||||
{
|
||||
ncchHeader.NormalKey = 0x00;
|
||||
}
|
||||
else
|
||||
{
|
||||
ncchHeader.KeyX = (decryptArgs.Development ? decryptArgs.DevKeyX0x2C : decryptArgs.KeyX0x2C);
|
||||
ncchHeader.NormalKey = RotateLeft((RotateLeft(ncchHeader.KeyX, 2, 128) ^ ncchHeader.KeyY) + decryptArgs.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
}
|
||||
|
||||
var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV, decryptArgs.Encrypt);
|
||||
|
||||
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
|
||||
if (romfsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < romfsSizeM; i++)
|
||||
{
|
||||
byte[] readBytes = reader.ReadBytes(1024 * 1024);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
writer.Write(processedBytes);
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Encrypting: {i} / {romfsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
byte[] readBytes = reader.ReadBytes(romfsSizeB);
|
||||
byte[] finalBytes = cipher.DoFinal(readBytes);
|
||||
writer.Write(finalBytes);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {ncchHeader.PartitionNumber} RomFS: Encrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the CryptoMethod and BitMasks for the encrypted partition
|
||||
/// </summary>
|
||||
/// <param name="ncsdHeader">NCSD header representing the 3DS file</param>
|
||||
/// <param name="ncchHeader">NCCH header representing the partition</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void UpdateEncryptCryptoAndMasks(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryWriter writer)
|
||||
{
|
||||
// Write the new CryptoMethod
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18B, SeekOrigin.Begin);
|
||||
|
||||
// For partitions 1 and up, set crypto-method to 0x00
|
||||
if (ncchHeader.PartitionNumber > 0)
|
||||
writer.Write((byte)CryptoMethod.Original);
|
||||
|
||||
// If partition 0, restore crypto-method from backup flags
|
||||
else
|
||||
writer.Write((byte)ncsdHeader.BackupHeader.Flags.CryptoMethod);
|
||||
|
||||
writer.Flush();
|
||||
|
||||
// Write the new BitMasks flag
|
||||
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18F, SeekOrigin.Begin);
|
||||
BitMasks flag = ncchHeader.Flags.BitMasks;
|
||||
flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF;
|
||||
flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & ncsdHeader.BackupHeader.Flags.BitMasks;
|
||||
writer.Write((byte)flag);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
namespace NDecrypt.Nitro
|
||||
{
|
||||
internal class Constants
|
||||
{
|
||||
public static byte[] NDSEncryptionData = new byte[]
|
||||
{
|
||||
0x99,0xD5,0x20,0x5F,0x57,0x44,0xF5,0xB9,0x6E,0x19,0xA4,0xD9,0x9E,0x6A,0x5A,0x94,
|
||||
0xD8,0xAE,0xF1,0xEB,0x41,0x75,0xE2,0x3A,0x93,0x82,0xD0,0x32,0x33,0xEE,0x31,0xD5,
|
||||
0xCC,0x57,0x61,0x9A,0x37,0x06,0xA2,0x1B,0x79,0x39,0x72,0xF5,0x55,0xAE,0xF6,0xBE,
|
||||
0x5F,0x1B,0x69,0xFB,0xE5,0x9D,0xF1,0xE9,0xCE,0x2C,0xD9,0xA1,0x5E,0x32,0x05,0xE6,
|
||||
0xFE,0xD3,0xFE,0xCF,0xD4,0x62,0x04,0x0D,0x8B,0xF5,0xEC,0xB7,0x2B,0x60,0x79,0xBB,
|
||||
0x12,0x95,0x31,0x0D,0x6E,0x3F,0xDA,0x2B,0x88,0x84,0xF0,0xF1,0x3D,0x12,0x7E,0x25,
|
||||
0x45,0x22,0xF1,0xBB,0x24,0x06,0x1A,0x06,0x11,0xAD,0xDF,0x28,0x8B,0x64,0x81,0x34,
|
||||
0x2B,0xEB,0x33,0x29,0x99,0xAA,0xF2,0xBD,0x9C,0x14,0x95,0x9D,0x9F,0xF7,0xF5,0x8C,
|
||||
0x72,0x97,0xA1,0x29,0x9D,0xD1,0x5F,0xCF,0x66,0x4D,0x07,0x1A,0xDE,0xD3,0x4A,0x4B,
|
||||
0x85,0xC9,0xA7,0xA3,0x17,0x95,0x05,0x3A,0x3D,0x49,0x0A,0xBF,0x0A,0x89,0x8B,0xA2,
|
||||
0x4A,0x82,0x49,0xDD,0x27,0x90,0xF1,0x0B,0xE9,0xEB,0x1C,0x6A,0x83,0x76,0x45,0x05,
|
||||
0xBA,0x81,0x70,0x61,0x17,0x3F,0x4B,0xDE,0xAE,0xCF,0xAB,0x39,0x57,0xF2,0x3A,0x56,
|
||||
0x48,0x11,0xAD,0x8A,0x40,0xE1,0x45,0x3F,0xFA,0x9B,0x02,0x54,0xCA,0xA6,0x93,0xFB,
|
||||
0xEF,0x4D,0xFE,0x6F,0xA3,0xD8,0x87,0x9C,0x08,0xBA,0xD5,0x48,0x6A,0x8D,0x2D,0xFD,
|
||||
0x6E,0x15,0xF8,0x74,0xBD,0xBE,0x52,0x8B,0x18,0x22,0x8A,0x9E,0xFB,0x74,0x37,0x07,
|
||||
0x1B,0x36,0x6C,0x4A,0x19,0xBA,0x42,0x62,0xB9,0x79,0x91,0x10,0x7B,0x67,0x65,0x96,
|
||||
0xFE,0x02,0x23,0xE8,0xEE,0x99,0x8C,0x77,0x3E,0x5C,0x86,0x64,0x4D,0x6D,0x78,0x86,
|
||||
0xA5,0x4F,0x65,0xE2,0x1E,0xB2,0xDF,0x5A,0x0A,0xD0,0x7E,0x08,0x14,0xB0,0x71,0xAC,
|
||||
0xBD,0xDB,0x83,0x1C,0xB9,0xD7,0xA1,0x62,0xCD,0xC6,0x63,0x7C,0x52,0x69,0xC3,0xE6,
|
||||
0xBF,0x75,0xCE,0x12,0x44,0x5D,0x21,0x04,0xFA,0xFB,0xD3,0x3C,0x38,0x11,0x63,0xD4,
|
||||
0x95,0x85,0x41,0x49,0x46,0x09,0xF2,0x08,0x43,0x11,0xDC,0x1F,0x76,0xC0,0x15,0x6D,
|
||||
0x1F,0x3C,0x63,0x70,0xEA,0x87,0x80,0x6C,0xC3,0xBD,0x63,0x8B,0xC2,0x37,0x21,0x37,
|
||||
0xDC,0xEE,0x09,0x23,0x2E,0x37,0x6A,0x4D,0x73,0x90,0xF7,0x50,0x30,0xAC,0x1C,0x92,
|
||||
0x04,0x10,0x23,0x91,0x4F,0xD2,0x07,0xAA,0x68,0x3E,0x4F,0x9A,0xC9,0x64,0x60,0x6A,
|
||||
0xC8,0x14,0x21,0xF3,0xD6,0x22,0x41,0x12,0x44,0x24,0xCF,0xE6,0x8A,0x56,0xDD,0x0D,
|
||||
0x53,0x4D,0xE1,0x85,0x1E,0x8C,0x52,0x5A,0x9C,0x19,0x84,0xC2,0x03,0x57,0xF1,0x6F,
|
||||
0xE3,0x00,0xBE,0x58,0xF6,0x4C,0xED,0xD5,0x21,0x64,0x9C,0x1F,0xBE,0x55,0x03,0x3C,
|
||||
0x4A,0xDC,0xFF,0xAA,0xC9,0xDA,0xE0,0x5D,0x5E,0xBF,0xE6,0xDE,0xF5,0xD8,0xB1,0xF8,
|
||||
0xFF,0x36,0xB3,0xB9,0x62,0x67,0x95,0xDB,0x31,0x5F,0x37,0xED,0x4C,0x70,0x67,0x99,
|
||||
0x90,0xB5,0x18,0x31,0x6C,0x3D,0x99,0x99,0xE4,0x42,0xDA,0xD3,0x25,0x42,0x13,0xA0,
|
||||
0xAE,0xD7,0x70,0x6C,0xB1,0x55,0xCF,0xC7,0xD7,0x46,0xD5,0x43,0x61,0x17,0x3D,0x44,
|
||||
0x28,0xE9,0x33,0x85,0xD5,0xD0,0xA2,0x93,0xAA,0x25,0x12,0x1F,0xFB,0xC5,0x0B,0x46,
|
||||
0xF5,0x97,0x76,0x56,0x45,0xA6,0xBE,0x87,0xB1,0x94,0x6B,0xE8,0xB1,0xFE,0x33,0x99,
|
||||
0xAE,0x1F,0x3E,0x6C,0x39,0x71,0x1D,0x09,0x00,0x90,0x37,0xE4,0x10,0x3E,0x75,0x74,
|
||||
0xFF,0x8C,0x83,0x3B,0xB0,0xF1,0xB0,0xF9,0x01,0x05,0x47,0x42,0x95,0xF1,0xD6,0xAC,
|
||||
0x7E,0x38,0xE6,0x9E,0x95,0x74,0x26,0x3F,0xB4,0x68,0x50,0x18,0xD0,0x43,0x30,0xB4,
|
||||
0x4C,0x4B,0xE3,0x68,0xBF,0xE5,0x4D,0xB6,0x95,0x8B,0x0A,0xA0,0x74,0x25,0x32,0x77,
|
||||
0xCF,0xA1,0xF7,0x2C,0xD8,0x71,0x13,0x5A,0xAB,0xEA,0xC9,0x51,0xE8,0x0D,0xEE,0xEF,
|
||||
0xE9,0x93,0x7E,0x19,0xA7,0x1E,0x43,0x38,0x81,0x16,0x2C,0xA1,0x48,0xE3,0x73,0xCC,
|
||||
0x29,0x21,0x6C,0xD3,0x5D,0xCE,0xA0,0xD9,0x61,0x71,0x43,0xA0,0x15,0x13,0xB5,0x64,
|
||||
0x92,0xCF,0x2A,0x19,0xDC,0xAD,0xB7,0xA5,0x9F,0x86,0x65,0xF8,0x1A,0x9F,0xE7,0xFB,
|
||||
0xF7,0xFD,0xB8,0x13,0x6C,0x27,0xDB,0x6F,0xDF,0x35,0x1C,0xF7,0x8D,0x2C,0x5B,0x9B,
|
||||
0x12,0xAB,0x38,0x64,0x06,0xCC,0xDE,0x31,0xE8,0x4E,0x75,0x11,0x64,0xE3,0xFA,0xEA,
|
||||
0xEB,0x34,0x54,0xC2,0xAD,0x3F,0x34,0xEB,0x93,0x2C,0x7D,0x26,0x36,0x9D,0x56,0xF3,
|
||||
0x5A,0xE1,0xF6,0xB3,0x98,0x63,0x4A,0x9E,0x32,0x83,0xE4,0x9A,0x84,0x60,0x7D,0x90,
|
||||
0x2E,0x13,0x0E,0xEE,0x93,0x4B,0x36,0xA2,0x85,0xEC,0x16,0x38,0xE8,0x88,0x06,0x02,
|
||||
0xBF,0xF0,0xA0,0x3A,0xED,0xD7,0x6A,0x9A,0x73,0xE1,0x57,0xCF,0xF8,0x44,0xB8,0xDC,
|
||||
0x2E,0x23,0x59,0xD1,0xDF,0x95,0x52,0x71,0x99,0x61,0xA0,0x4B,0xD5,0x7F,0x6E,0x78,
|
||||
0xBA,0xA9,0xC5,0x30,0xD3,0x40,0x86,0x32,0x9D,0x32,0x0C,0x9C,0x37,0xB7,0x02,0x2F,
|
||||
0xBA,0x54,0x98,0xA9,0xC4,0x13,0x04,0xC9,0x8D,0xBE,0xC8,0xE7,0x5D,0x97,0x50,0x2E,
|
||||
0x93,0xD6,0x22,0x59,0x0C,0x27,0xBC,0x22,0x92,0xE0,0xA7,0x20,0x0F,0x93,0x6F,0x7F,
|
||||
0x4C,0x9F,0xD3,0xB5,0xA6,0x2A,0x0B,0x74,0x67,0x49,0x7D,0x10,0x26,0xCB,0xD1,0xC5,
|
||||
0x86,0x71,0xE7,0x8C,0xA0,0x9C,0xE9,0x5B,0xB2,0x1A,0xF6,0x01,0xEE,0x8C,0x9E,0x5E,
|
||||
0x83,0xF2,0x1A,0xDB,0xE6,0xE5,0xEA,0x84,0x59,0x76,0xD2,0x7C,0xF6,0x8D,0xA5,0x49,
|
||||
0x36,0x48,0xC2,0x16,0x52,0xBB,0x83,0xA3,0x74,0xB9,0x07,0x0C,0x3B,0xFF,0x61,0x28,
|
||||
0xE1,0x61,0xE9,0xE4,0xEF,0x6E,0x15,0xAA,0x4E,0xBA,0xE8,0x5D,0x05,0x96,0xBB,0x32,
|
||||
0x56,0xB0,0xFB,0x72,0x52,0x0F,0x0E,0xC8,0x42,0x25,0x65,0x76,0x89,0xAF,0xF2,0xDE,
|
||||
0x10,0x27,0xF0,0x01,0x4B,0x74,0xA7,0x97,0x07,0xD5,0x26,0x54,0x54,0x09,0x1F,0x82,
|
||||
0x0A,0x86,0x7D,0x30,0x39,0x0E,0xB3,0x26,0x9B,0x0B,0x57,0xBB,0x36,0x06,0x31,0xAF,
|
||||
0xFD,0x79,0xFC,0xD9,0x30,0x10,0x2B,0x0C,0xB3,0xE1,0x9B,0xD7,0x7B,0xDC,0x5F,0xEF,
|
||||
0xD2,0xF8,0x13,0x45,0x4D,0x47,0x75,0xBD,0x46,0x96,0x3C,0x7E,0x75,0xF3,0x3E,0xB5,
|
||||
0x67,0xC5,0x9A,0x3B,0xB0,0x5B,0x29,0x6B,0xDE,0x80,0x5B,0xC8,0x15,0x05,0xB1,0x31,
|
||||
0xB6,0xCE,0x49,0xDD,0xAD,0x84,0xB5,0xAE,0x60,0xDC,0x67,0x31,0x34,0x30,0xFE,0x4E,
|
||||
0xBD,0x80,0x2F,0xA6,0xBF,0x63,0x39,0x21,0x86,0xD9,0x35,0x7F,0x16,0x68,0x22,0x05,
|
||||
0x54,0xE9,0x90,0x26,0x8C,0x07,0x6C,0x51,0xA4,0x31,0x55,0xD7,0x09,0x07,0xA8,0x3E,
|
||||
0x2E,0x53,0x66,0xC1,0xF8,0xF2,0x7B,0xC4,0xF2,0x58,0xCF,0xF1,0x87,0xC5,0xA2,0xE7,
|
||||
0x27,0x8F,0x30,0x87,0x58,0xA0,0x64,0x62,0x23,0x18,0xB9,0x88,0x7C,0xFA,0xCE,0xC4,
|
||||
0x98,0xAE,0xAD,0x17,0xCC,0x4A,0x5B,0xF3,0xE9,0x48,0xD5,0x56,0xD3,0x0D,0xF2,0xC8,
|
||||
0x92,0x73,0x8C,0xDB,0xD7,0x2F,0x56,0xAC,0x81,0xF9,0x92,0x69,0x4D,0xC6,0x32,0xF6,
|
||||
0xE6,0xC0,0x8D,0x21,0xE2,0x76,0x80,0x61,0x11,0xBC,0xDC,0x6C,0x93,0xAF,0x19,0x69,
|
||||
0x9B,0xD0,0xBF,0xB9,0x31,0x9F,0x02,0x67,0xA3,0x51,0xEE,0x83,0x06,0x22,0x7B,0x0C,
|
||||
0xAB,0x49,0x42,0x40,0xB8,0xD5,0x01,0x7D,0xCE,0x5E,0xF7,0x55,0x53,0x39,0xC5,0x99,
|
||||
0x46,0xD8,0x87,0x9F,0xBA,0xF7,0x64,0xB4,0xE3,0x9A,0xFA,0xA1,0x6D,0x90,0x68,0x10,
|
||||
0x30,0xCA,0x8A,0x54,0xA7,0x9F,0x60,0xC3,0x19,0xF5,0x6B,0x0D,0x7A,0x51,0x98,0xE6,
|
||||
0x98,0x43,0x51,0xB4,0xD6,0x35,0xE9,0x4F,0xC3,0xDF,0x0F,0x7B,0xD6,0x2F,0x5C,0xBD,
|
||||
0x3A,0x15,0x61,0x19,0xF1,0x4B,0xCB,0xAA,0xDC,0x6D,0x64,0xC9,0xD3,0xC6,0x1E,0x56,
|
||||
0xEF,0x38,0x4C,0x50,0x71,0x86,0x75,0xCC,0x0D,0x0D,0x4E,0xE9,0x28,0xF6,0x06,0x5D,
|
||||
0x70,0x1B,0xAA,0xD3,0x45,0xCF,0xA8,0x39,0xAC,0x95,0xA6,0x2E,0xB4,0xE4,0x22,0xD4,
|
||||
0x74,0xA8,0x37,0x5F,0x48,0x7A,0x04,0xCC,0xA5,0x4C,0x40,0xD8,0x28,0xB4,0x28,0x08,
|
||||
0x0D,0x1C,0x72,0x52,0x41,0xF0,0x7D,0x47,0x19,0x3A,0x53,0x4E,0x58,0x84,0x62,0x6B,
|
||||
0x93,0xB5,0x8A,0x81,0x21,0x4E,0x0D,0xDC,0xB4,0x3F,0xA2,0xC6,0xFC,0xC9,0x2B,0x40,
|
||||
0xDA,0x38,0x04,0xE9,0x5E,0x5A,0x86,0x6B,0x0C,0x22,0x25,0x85,0x68,0x11,0x8D,0x7C,
|
||||
0x92,0x1D,0x95,0x55,0x4D,0xAB,0x8E,0xBB,0xDA,0xA6,0xE6,0xB7,0x51,0xB6,0x32,0x5A,
|
||||
0x05,0x41,0xDD,0x05,0x2A,0x0A,0x56,0x50,0x91,0x17,0x47,0xCC,0xC9,0xE6,0x7E,0xB5,
|
||||
0x61,0x4A,0xDB,0x73,0x67,0x51,0xC8,0x33,0xF5,0xDA,0x6E,0x74,0x2E,0x54,0xC3,0x37,
|
||||
0x0D,0x6D,0xAF,0x08,0xE8,0x15,0x8A,0x5F,0xE2,0x59,0x21,0xCD,0xA8,0xDE,0x0C,0x06,
|
||||
0x5A,0x77,0x6B,0x5F,0xDB,0x18,0x65,0x3E,0xC8,0x50,0xDE,0x78,0xE0,0xB8,0x82,0xB3,
|
||||
0x5D,0x4E,0x72,0x32,0x07,0x4F,0xC1,0x34,0x23,0xBA,0x96,0xB7,0x67,0x4E,0xA4,0x28,
|
||||
0x1E,0x34,0x62,0xEB,0x2D,0x6A,0x70,0xE9,0x2F,0x42,0xC4,0x70,0x4E,0x5A,0x31,0x9C,
|
||||
0xF9,0x5B,0x47,0x28,0xAA,0xDA,0x71,0x6F,0x38,0x1F,0xB3,0x78,0xC4,0x92,0x6B,0x1C,
|
||||
0x9E,0xF6,0x35,0x9A,0xB7,0x4D,0x0E,0xBF,0xCC,0x18,0x29,0x41,0x03,0x48,0x35,0x5D,
|
||||
0x55,0xD0,0x2B,0xC6,0x29,0xAF,0x5C,0x60,0x74,0x69,0x8E,0x5E,0x9B,0x7C,0xD4,0xBD,
|
||||
0x7B,0x44,0x64,0x7D,0x3F,0x92,0x5D,0x69,0xB6,0x1F,0x00,0x4B,0xD4,0x83,0x35,0xCF,
|
||||
0x7E,0x64,0x4E,0x17,0xAE,0x8D,0xD5,0x2E,0x9A,0x28,0x12,0x4E,0x2E,0x2B,0x49,0x08,
|
||||
0x5C,0xAE,0xC6,0x46,0x85,0xAE,0x41,0x61,0x1E,0x6F,0x82,0xD2,0x51,0x37,0x16,0x1F,
|
||||
0x0B,0xF6,0x59,0xA4,0x9A,0xCA,0x5A,0xAF,0x0D,0xD4,0x33,0x8B,0x20,0x63,0xF1,0x84,
|
||||
0x80,0x5C,0xCB,0xCF,0x08,0xB4,0xB9,0xD3,0x16,0x05,0xBD,0x62,0x83,0x31,0x9B,0x56,
|
||||
0x51,0x98,0x9F,0xBA,0xB2,0x5B,0xAA,0xB2,0x22,0x6B,0x2C,0xB5,0xD4,0x48,0xFA,0x63,
|
||||
0x2B,0x5F,0x58,0xFA,0x61,0xFA,0x64,0x09,0xBB,0x38,0xE0,0xB8,0x9D,0x92,0x60,0xA8,
|
||||
0x0D,0x67,0x6F,0x0E,0x37,0xF5,0x0D,0x01,0x9F,0xC2,0x77,0xD4,0xFE,0xEC,0xF1,0x73,
|
||||
0x30,0x39,0xE0,0x7D,0xF5,0x61,0x98,0xE4,0x2C,0x28,0x55,0x04,0x56,0x55,0xDB,0x2F,
|
||||
0x6B,0xEC,0xE5,0x58,0x06,0xB6,0x64,0x80,0x6A,0x2A,0x1A,0x4E,0x5B,0x0F,0xD8,0xC4,
|
||||
0x0A,0x2E,0x52,0x19,0xD9,0x62,0xF5,0x30,0x48,0xBE,0x8C,0x7B,0x4F,0x38,0x9B,0xA2,
|
||||
0xC3,0xAF,0xC9,0xD3,0xC7,0xC1,0x62,0x41,0x86,0xB9,0x61,0x21,0x57,0x6F,0x99,0x4F,
|
||||
0xC1,0xBA,0xCE,0x7B,0xB5,0x3B,0x4D,0x5E,0x8A,0x8B,0x44,0x57,0x5F,0x13,0x5F,0x70,
|
||||
0x6D,0x5B,0x29,0x47,0xDC,0x38,0xE2,0xEC,0x04,0x55,0x65,0x12,0x2A,0xE8,0x17,0x43,
|
||||
0xE1,0x8E,0xDD,0x2A,0xB3,0xE2,0x94,0xF7,0x09,0x6E,0x5C,0xE6,0xEB,0x8A,0xF8,0x6D,
|
||||
0x89,0x49,0x54,0x48,0xF5,0x2F,0xAD,0xBF,0xEA,0x94,0x4B,0xCA,0xFC,0x39,0x87,0x82,
|
||||
0x5F,0x8A,0x01,0xF2,0x75,0xF2,0xE6,0x71,0xD6,0xD8,0x42,0xDE,0xF1,0x2D,0x1D,0x28,
|
||||
0xA6,0x88,0x7E,0xA3,0xA0,0x47,0x1D,0x30,0xD9,0xA3,0x71,0xDF,0x49,0x1C,0xCB,0x01,
|
||||
0xF8,0x36,0xB1,0xF2,0xF0,0x22,0x58,0x5D,0x45,0x6B,0xBD,0xA0,0xBB,0xB2,0x88,0x42,
|
||||
0xC7,0x8C,0x28,0xCE,0x93,0xE8,0x90,0x63,0x08,0x90,0x7C,0x89,0x3C,0xF5,0x7D,0xB7,
|
||||
0x04,0x2D,0x4F,0x55,0x51,0x16,0xFD,0x7E,0x79,0xE8,0xBE,0xC1,0xF2,0x12,0xD4,0xF8,
|
||||
0xB4,0x84,0x05,0x23,0xA0,0xCC,0xD2,0x2B,0xFD,0xE1,0xAB,0xAD,0x0D,0xD1,0x55,0x6C,
|
||||
0x23,0x41,0x94,0x4D,0x77,0x37,0x4F,0x05,0x28,0x0C,0xBF,0x17,0xB3,0x12,0x67,0x6C,
|
||||
0x8C,0xC3,0x5A,0xF7,0x41,0x84,0x2A,0x6D,0xD0,0x94,0x12,0x27,0x2C,0xB4,0xED,0x9C,
|
||||
0x4D,0xEC,0x47,0x82,0x97,0xD5,0x67,0xB9,0x1B,0x9D,0xC0,0x55,0x07,0x7E,0xE5,0x8E,
|
||||
0xE2,0xA8,0xE7,0x3E,0x12,0xE4,0x0E,0x3A,0x2A,0x45,0x55,0x34,0xA2,0xF9,0x2D,0x5A,
|
||||
0x1B,0xAB,0x52,0x7C,0x83,0x10,0x5F,0x55,0xD2,0xF1,0x5A,0x43,0x2B,0xC6,0xA7,0xA4,
|
||||
0x89,0x15,0x95,0xE8,0xB4,0x4B,0x9D,0xF8,0x75,0xE3,0x9F,0x60,0x78,0x5B,0xD6,0xE6,
|
||||
0x0D,0x44,0xE6,0x21,0x06,0xBD,0x47,0x22,0x53,0xA4,0x00,0xAD,0x8D,0x43,0x13,0x85,
|
||||
0x39,0xF7,0xAA,0xFC,0x38,0xAF,0x7B,0xED,0xFC,0xE4,0x2B,0x54,0x50,0x98,0x4C,0xFC,
|
||||
0x85,0x80,0xF7,0xDF,0x3C,0x80,0x22,0xE1,0x94,0xDA,0xDE,0x24,0xC6,0xB0,0x7A,0x39,
|
||||
0x38,0xDC,0x0F,0xA1,0xA7,0xF4,0xF9,0x6F,0x63,0x18,0x57,0x8B,0x84,0x41,0x2A,0x2E,
|
||||
0xD4,0x53,0xF2,0xD9,0x00,0x0F,0xD0,0xDD,0x99,0x6E,0x19,0xA6,0x0A,0xD0,0xEC,0x5B,
|
||||
0x58,0x24,0xAB,0xC0,0xCB,0x06,0x65,0xEC,0x1A,0x13,0x38,0x94,0x0A,0x67,0x03,0x2F,
|
||||
0x3F,0xF7,0xE3,0x77,0x44,0x77,0x33,0xC6,0x14,0x39,0xD0,0xE3,0xC0,0xA2,0x08,0x79,
|
||||
0xBB,0x40,0x99,0x57,0x41,0x0B,0x01,0x90,0xCD,0xE1,0xCC,0x48,0x67,0xDB,0xB3,0xAF,
|
||||
0x88,0x74,0xF3,0x4C,0x82,0x8F,0x72,0xB1,0xB5,0x23,0x29,0xC4,0x12,0x6C,0x19,0xFC,
|
||||
0x8E,0x46,0xA4,0x9C,0xC4,0x25,0x65,0x87,0xD3,0x6D,0xBE,0x8A,0x93,0x11,0x03,0x38,
|
||||
0xED,0x83,0x2B,0xF3,0x46,0xA4,0x93,0xEA,0x3B,0x53,0x85,0x1D,0xCE,0xD4,0xF1,0x08,
|
||||
0x83,0x27,0xED,0xFC,0x9B,0x1A,0x18,0xBC,0xF9,0x8B,0xAE,0xDC,0x24,0xAB,0x50,0x38,
|
||||
0xE9,0x72,0x4B,0x10,0x22,0x17,0x7B,0x46,0x5D,0xAB,0x59,0x64,0xF3,0x40,0xAE,0xF8,
|
||||
0xBB,0xE5,0xC8,0xF9,0x26,0x03,0x4E,0x55,0x7D,0xEB,0xEB,0xFE,0xF7,0x39,0xE6,0xE0,
|
||||
0x0A,0x11,0xBE,0x2E,0x28,0xFF,0x98,0xED,0xC0,0xC9,0x42,0x56,0x42,0xC3,0xFD,0x00,
|
||||
0xF6,0xAF,0x87,0xA2,0x5B,0x01,0x3F,0x32,0x92,0x47,0x95,0x9A,0x72,0xA5,0x32,0x3D,
|
||||
0xAE,0x6B,0xD0,0x9B,0x07,0xD2,0x49,0x92,0xE3,0x78,0x4A,0xFA,0xA1,0x06,0x7D,0xF2,
|
||||
0x41,0xCF,0x77,0x74,0x04,0x14,0xB2,0x0C,0x86,0x84,0x64,0x16,0xD5,0xBB,0x51,0xA1,
|
||||
0xE5,0x6F,0xF1,0xD1,0xF2,0xE2,0xF7,0x5F,0x58,0x20,0x4D,0xB8,0x57,0xC7,0xCF,0xDD,
|
||||
0xC5,0xD8,0xBE,0x76,0x3D,0xF6,0x5F,0x7E,0xE7,0x2A,0x8B,0x88,0x24,0x1B,0x38,0x3F,
|
||||
0x0E,0x41,0x23,0x77,0xF5,0xF0,0x4B,0xD4,0x0C,0x1F,0xFA,0xA4,0x0B,0x80,0x5F,0xCF,
|
||||
0x45,0xF6,0xE0,0xDA,0x2F,0x34,0x59,0x53,0xFB,0x20,0x3C,0x52,0x62,0x5E,0x35,0xB5,
|
||||
0x62,0xFE,0x8B,0x60,0x63,0xE3,0x86,0x5A,0x15,0x1A,0x6E,0xD1,0x47,0x45,0xBC,0x32,
|
||||
0xB4,0xEB,0x67,0x38,0xAB,0xE4,0x6E,0x33,0x3A,0xB5,0xED,0xA3,0xAD,0x67,0xE0,0x4E,
|
||||
0x41,0x95,0xEE,0x62,0x62,0x71,0x26,0x1D,0x31,0xEF,0x62,0x30,0xAF,0xD7,0x82,0xAC,
|
||||
0xC2,0xDC,0x05,0x04,0xF5,0x97,0x07,0xBF,0x11,0x59,0x23,0x07,0xC0,0x64,0x02,0xE8,
|
||||
0x97,0xE5,0x3E,0xAF,0x18,0xAC,0x59,0xA6,0x8B,0x4A,0x33,0x90,0x1C,0x6E,0x7C,0x9C,
|
||||
0x20,0x7E,0x4C,0x3C,0x3E,0x61,0x64,0xBB,0xC5,0x6B,0x7C,0x7E,0x3E,0x9F,0xC5,0x4C,
|
||||
0x9F,0xEA,0x73,0xF5,0xD7,0x89,0xC0,0x4C,0xF4,0xFB,0xF4,0x2D,0xEC,0x14,0x1B,0x51,
|
||||
0xD5,0xC1,0x12,0xC8,0x10,0xDF,0x0B,0x4A,0x8B,0x9C,0xBC,0x93,0x45,0x6A,0x3E,0x3E,
|
||||
0x7D,0xC1,0xA9,0xBA,0xCD,0xC1,0xB4,0x07,0xE4,0xE1,0x68,0x86,0x43,0xB2,0x6D,0x38,
|
||||
0xF3,0xFB,0x0C,0x5C,0x66,0x37,0x71,0xDE,0x56,0xEF,0x6E,0xA0,0x10,0x40,0x65,0xA7,
|
||||
0x98,0xF7,0xD0,0xBE,0x0E,0xC8,0x37,0x36,0xEC,0x10,0xCA,0x7C,0x9C,0xAB,0x84,0x1E,
|
||||
0x05,0x17,0x76,0x02,0x1C,0x4F,0x52,0xAA,0x5F,0xC1,0xC6,0xA0,0x56,0xB9,0xD8,0x04,
|
||||
0x84,0x44,0x4D,0xA7,0x59,0xD8,0xDE,0x60,0xE6,0x38,0x0E,0x05,0x8F,0x03,0xE1,0x3B,
|
||||
0x6D,0x81,0x04,0x33,0x6F,0x30,0x0B,0xCE,0x69,0x05,0x21,0x33,0xFB,0x26,0xBB,0x89,
|
||||
0x7D,0xB6,0xAE,0x87,0x7E,0x51,0x07,0xE0,0xAC,0xF7,0x96,0x0A,0x6B,0xF9,0xC4,0x5C,
|
||||
0x1D,0xE4,0x44,0x47,0xB8,0x5E,0xFA,0xE3,0x78,0x84,0x55,0x42,0x4B,0x48,0x5E,0xF7,
|
||||
0x7D,0x47,0x35,0x86,0x1D,0x2B,0x43,0x05,0x03,0xEC,0x8A,0xB8,0x1E,0x06,0x3C,0x76,
|
||||
0x0C,0x48,0x1A,0x43,0xA7,0xB7,0x8A,0xED,0x1E,0x13,0xC6,0x43,0xEE,0x10,0xEF,0xDB,
|
||||
0xEC,0xFB,0x3C,0x83,0xB2,0x95,0x44,0xEF,0xD8,0x54,0x51,0x4E,0x2D,0x11,0x44,0x1D,
|
||||
0xFB,0x36,0x59,0x1E,0x7A,0x34,0xC1,0xC3,0xCA,0x57,0x00,0x61,0xEA,0x67,0xA5,0x16,
|
||||
0x9B,0x55,0xD0,0x55,0xE1,0x7F,0xD9,0x36,0xD2,0x40,0x76,0xAE,0xDC,0x01,0xCE,0xB0,
|
||||
0x7A,0x83,0xD5,0xCB,0x20,0x98,0xEC,0x6B,0xC1,0x72,0x92,0x34,0xF3,0x82,0x57,0x37,
|
||||
0x62,0x8A,0x32,0x36,0x0C,0x90,0x43,0xAE,0xAE,0x5C,0x9B,0x78,0x8E,0x13,0x65,0x02,
|
||||
0xFD,0x68,0x71,0xC1,0xFE,0xB0,0x31,0xA0,0x24,0x82,0xB0,0xC3,0xB1,0x79,0x69,0xA7,
|
||||
0xF5,0xD2,0xEB,0xD0,0x82,0xC0,0x32,0xDC,0x9E,0xC7,0x26,0x3C,0x6D,0x8D,0x98,0xC1,
|
||||
0xBB,0x22,0xD4,0xD0,0x0F,0x33,0xEC,0x3E,0xB9,0xCC,0xE1,0xDC,0x6A,0x4C,0x77,0x36,
|
||||
0x14,0x1C,0xF9,0xBF,0x81,0x9F,0x28,0x5F,0x71,0x85,0x32,0x29,0x90,0x75,0x48,0xC4,
|
||||
0xB3,0x4A,0xCE,0xD8,0x44,0x8F,0x14,0x2F,0xFD,0x40,0x57,0xEF,0xAA,0x08,0x75,0xD9,
|
||||
0x46,0xD1,0xD6,0x6E,0x32,0x55,0x1F,0xC3,0x18,0xFE,0x84,0x1F,0xFC,0x84,0xD5,0xFF,
|
||||
0x71,0x5E,0x1B,0x48,0xC3,0x86,0x95,0x0E,0x28,0x08,0x27,0xD3,0x38,0x83,0x71,0x7B,
|
||||
0x4C,0x80,0x63,0x54,0x9A,0x56,0xB0,0xAC,0xCF,0x80,0xCA,0x31,0x09,0xEF,0xFE,0xF3,
|
||||
0xBE,0xAF,0x24,0x7E,0xA6,0xFE,0x53,0x3F,0xC2,0x8D,0x4A,0x33,0x68,0xD1,0x22,0xA6,
|
||||
0x66,0xAD,0x7B,0xEA,0xDE,0xB6,0x43,0xB0,0xA1,0x25,0x95,0x00,0xA3,0x3F,0x75,0x46,
|
||||
0x14,0x11,0x44,0xEC,0xD7,0x95,0xBC,0x92,0xF0,0x4F,0xA9,0x16,0x53,0x62,0x97,0x60,
|
||||
0x2A,0x0F,0x41,0xF1,0x71,0x24,0xBE,0xEE,0x94,0x7F,0x08,0xCD,0x60,0x93,0xB3,0x85,
|
||||
0x5B,0x07,0x00,0x3F,0xD8,0x0F,0x28,0x83,0x9A,0xD1,0x69,0x9F,0xD1,0xDA,0x2E,0xC3,
|
||||
0x90,0x01,0xA2,0xB9,0x6B,0x4E,0x2A,0x66,0x9D,0xDA,0xAE,0xA6,0xEA,0x2A,0xD3,0x68,
|
||||
0x2F,0x0C,0x0C,0x9C,0xD2,0x8C,0x4A,0xED,0xE2,0x9E,0x57,0x65,0x9D,0x09,0x87,0xA3,
|
||||
0xB4,0xC4,0x32,0x5D,0xC9,0xD4,0x32,0x2B,0xB1,0xE0,0x71,0x1E,0x64,0x4D,0xE6,0x90,
|
||||
0x71,0xE3,0x1E,0x40,0xED,0x7D,0xF3,0x84,0x0E,0xED,0xC8,0x78,0x76,0xAE,0xC0,0x71,
|
||||
0x27,0x72,0xBB,0x05,0xEA,0x02,0x64,0xFB,0xF3,0x48,0x6B,0xB5,0x42,0x93,0x3F,0xED,
|
||||
0x9F,0x13,0x53,0xD2,0xF7,0xFE,0x2A,0xEC,0x1D,0x47,0x25,0xDB,0x3C,0x91,0x86,0xC6,
|
||||
0x8E,0xF0,0x11,0xFD,0x23,0x74,0x36,0xF7,0xA4,0xF5,0x9E,0x7A,0x7E,0x53,0x50,0x44,
|
||||
0xD4,0x47,0xCA,0xD3,0xEB,0x38,0x6D,0xE6,0xD9,0x71,0x94,0x7F,0x4A,0xC6,0x69,0x4B,
|
||||
0x11,0xF4,0x52,0xEA,0x22,0xFE,0x8A,0xB0,0x36,0x67,0x8B,0x59,0xE8,0xE6,0x80,0x2A,
|
||||
0xEB,0x65,0x04,0x13,0xEE,0xEC,0xDC,0x9E,0x5F,0xB1,0xEC,0x05,0x6A,0x59,0xE6,0x9F,
|
||||
0x5E,0x59,0x6B,0x89,0xBF,0xF7,0x1A,0xCA,0x44,0xF9,0x5B,0x6A,0x71,0x85,0x03,0xE4,
|
||||
0x29,0x62,0xE0,0x70,0x6F,0x41,0xC4,0xCF,0xB2,0xB1,0xCC,0xE3,0x7E,0xA6,0x07,0xA8,
|
||||
0x87,0xE7,0x7F,0x84,0x93,0xDB,0x52,0x4B,0x6C,0xEC,0x7E,0xDD,0xD4,0x24,0x48,0x10,
|
||||
0x69,0x9F,0x04,0x60,0x74,0xE6,0x48,0x18,0xF3,0xE4,0x2C,0xB9,0x4F,0x2E,0x50,0x7A,
|
||||
0xDF,0xD4,0x54,0x69,0x2B,0x8B,0xA7,0xF3,0xCE,0xFF,0x1F,0xF3,0x3E,0x26,0x01,0x39,
|
||||
0x17,0x95,0x84,0x89,0xB0,0xF0,0x4C,0x4B,0x82,0x91,0x9F,0xC4,0x4B,0xAC,0x9D,0xA5,
|
||||
0x74,0xAF,0x17,0x25,0xC9,0xCA,0x32,0xD3,0xBC,0x89,0x8A,0x84,0x89,0xCC,0x0D,0xAE,
|
||||
0x7C,0xA2,0xDB,0x9C,0x6A,0x78,0x91,0xEE,0xEA,0x76,0x5D,0x4E,0x87,0x60,0xF5,0x69,
|
||||
0x15,0x67,0xD4,0x02,0xCF,0xAF,0x48,0x36,0x07,0xEA,0xBF,0x6F,0x66,0x2D,0x06,0x8F,
|
||||
0xC4,0x9A,0xFE,0xF9,0xF6,0x90,0x87,0x75,0xB8,0xF7,0xAD,0x0F,0x76,0x10,0x5A,0x3D,
|
||||
0x59,0xB0,0x2E,0xB3,0xC7,0x35,0x2C,0xCC,0x70,0x56,0x2B,0xCB,0xE3,0x37,0x96,0xC5,
|
||||
0x2F,0x46,0x1B,0x8A,0x22,0x46,0xC7,0x88,0xA7,0x26,0x32,0x98,0x61,0xDF,0x86,0x22,
|
||||
0x8A,0xF4,0x1C,0x2F,0x87,0xA1,0x09,0xAA,0xCC,0xA9,0xAE,0xD3,0xBD,0x00,0x45,0x1C,
|
||||
0x9A,0x54,0x87,0x86,0x52,0x87,0xEF,0xFF,0x1E,0x8F,0xA1,0x8F,0xC1,0x89,0x5C,0x35,
|
||||
0x1B,0xDA,0x2D,0x3A,0x2C,0x16,0xB2,0xC2,0xF1,0x56,0xE2,0x78,0xC1,0x6B,0x63,0x97,
|
||||
0xC5,0x56,0x8F,0xC9,0x32,0x7F,0x2C,0xAA,0xAF,0xA6,0xA8,0xAC,0x20,0x91,0x22,0x88,
|
||||
0xDE,0xE4,0x60,0x8B,0xF9,0x4B,0x42,0x25,0x1A,0xE3,0x7F,0x9C,0x2C,0x19,0x89,0x3A,
|
||||
0x7E,0x05,0xD4,0x36,0xCC,0x69,0x58,0xC2,0xC1,0x32,0x8B,0x2F,0x90,0x85,0xEB,0x7A,
|
||||
0x39,0x50,0xA5,0xA1,0x27,0x92,0xC5,0x66,0xB0,0x20,0x4F,0x58,0x7E,0x55,0x83,0x43,
|
||||
0x2B,0x45,0xE2,0x9C,0xE4,0xD8,0x12,0x90,0x2C,0x16,0x83,0x56,0x16,0x79,0x03,0xB3,
|
||||
0xAD,0x2D,0x61,0x18,0x1A,0x13,0x1F,0x37,0xE2,0xE1,0x9C,0x73,0x7B,0x80,0xD5,0xFD,
|
||||
0x2D,0x51,0x87,0xFC,0x7B,0xAA,0xD7,0x1F,0x2C,0x7A,0x8E,0xAF,0xF4,0x8D,0xBB,0xCD,
|
||||
0x95,0x11,0x7C,0x72,0x0B,0xEE,0x6F,0xE2,0xB9,0xAF,0xDE,0x37,0x83,0xDE,0x8C,0x8D,
|
||||
0x62,0x05,0x67,0xB7,0x96,0xC6,0x8D,0x56,0xB6,0x0D,0xD7,0x62,0xBA,0xD6,0x46,0x36,
|
||||
0xBD,0x8E,0xC8,0xE6,0xEA,0x2A,0x6C,0x10,0x14,0xFF,0x6B,0x5B,0xFA,0x82,0x3C,0x46,
|
||||
0xB1,0x30,0x43,0x46,0x51,0x8A,0x7D,0x9B,0x92,0x3E,0x83,0x79,0x5B,0x55,0x5D,0xB2,
|
||||
0x6C,0x5E,0xCE,0x90,0x62,0x8E,0x53,0x98,0xC9,0x0D,0x6D,0xE5,0x2D,0x57,0xCD,0xC5,
|
||||
0x81,0x57,0xBA,0xE1,0xE8,0xB8,0x8F,0x72,0xE5,0x4F,0x13,0xDC,0xEA,0x9D,0x71,0x15,
|
||||
0x10,0xB2,0x11,0x88,0xD5,0x09,0xD4,0x7F,0x5B,0x65,0x7F,0x2C,0x3B,0x38,0x4C,0x11,
|
||||
0x68,0x50,0x8D,0xFB,0x9E,0xB0,0x59,0xBF,0x94,0x80,0x89,0x4A,0xC5,0x1A,0x18,0x12,
|
||||
0x89,0x53,0xD1,0x4A,0x10,0x29,0xE8,0x8C,0x1C,0xEC,0xB6,0xEA,0x46,0xC7,0x17,0x8B,
|
||||
0x25,0x15,0x31,0xA8,0xA2,0x6B,0x43,0xB1,0x9D,0xE2,0xDB,0x0B,0x87,0x9B,0xB0,0x11,
|
||||
0x04,0x0E,0x71,0xD2,0x29,0x77,0x89,0x82,0x0A,0x66,0x41,0x7F,0x1D,0x0B,0x48,0xFF,
|
||||
0x72,0xBB,0x24,0xFD,0xC2,0x48,0xA1,0x9B,0xFE,0x7B,0x7F,0xCE,0x88,0xDB,0x86,0xD9,
|
||||
0x85,0x3B,0x1C,0xB0,0xDC,0xA8,0x33,0x07,0xBF,0x51,0x2E,0xE3,0x0E,0x9A,0x00,0x97,
|
||||
0x1E,0x06,0xC0,0x97,0x43,0x9D,0xD8,0xB6,0x45,0xC4,0x86,0x67,0x5F,0x00,0xF8,0x88,
|
||||
0x9A,0xA4,0x52,0x9E,0xC7,0xAA,0x8A,0x83,0x75,0xEC,0xC5,0x18,0xAE,0xCE,0xC3,0x2F,
|
||||
0x1A,0x2B,0xF9,0x18,0xFF,0xAE,0x1A,0xF5,0x53,0x0B,0xB5,0x33,0x51,0xA7,0xFD,0xE8,
|
||||
0xA8,0xE1,0xA2,0x64,0xB6,0x22,0x17,0x43,0x80,0xCC,0x0A,0xD8,0xAE,0x3B,0xBA,0x40,
|
||||
0xD7,0xD9,0x92,0x4A,0x89,0xDF,0x04,0x10,0xEE,0x9B,0x18,0x2B,0x6A,0x77,0x69,0x8A,
|
||||
0x68,0xF4,0xF9,0xB9,0xA2,0x21,0x15,0x6E,0xE6,0x1E,0x3B,0x03,0x62,0x30,0x9B,0x60,
|
||||
0x41,0x7E,0x25,0x9B,0x9E,0x8F,0xC5,0x52,0x10,0x08,0xF8,0xC2,0x69,0xA1,0x21,0x11,
|
||||
0x88,0x37,0x5E,0x79,0x35,0x66,0xFF,0x10,0x42,0x18,0x6E,0xED,0x97,0xB6,0x6B,0x1C,
|
||||
0x4E,0x36,0xE5,0x6D,0x7D,0xB4,0xE4,0xBF,0x20,0xB9,0xE0,0x05,0x3A,0x69,0xD5,0xB8,
|
||||
0xE3,0xD5,0xDC,0xE0,0xB9,0xAC,0x53,0x3E,0x07,0xA4,0x57,0xAD,0x77,0xFF,0x48,0x18,
|
||||
0x76,0x2A,0xAC,0x49,0x2A,0x8E,0x47,0x75,0x6D,0x9F,0x67,0x63,0x30,0x35,0x8C,0x39,
|
||||
0x05,0x39,0xD5,0x6F,0x64,0x3A,0x5B,0xAD,0xCA,0x0B,0xBB,0x82,0x52,0x99,0x45,0xB1,
|
||||
0x93,0x36,0x36,0x99,0xAF,0x13,0x20,0x44,0x36,0xD8,0x02,0x44,0x09,0x39,0x92,0x85,
|
||||
0xFF,0x4A,0x4A,0x97,0x87,0xA6,0x63,0xD7,0xC7,0xB5,0xB5,0x24,0xED,0x0F,0xB4,0x6F,
|
||||
0x0C,0x58,0x52,0x14,0xD9,0xA6,0x7B,0xD3,0x79,0xBC,0x38,0x58,0xA1,0xBD,0x3B,0x84,
|
||||
0x06,0xD8,0x1A,0x06,0xFD,0x6B,0xA8,0xEA,0x4B,0x69,0x28,0x04,0x37,0xAD,0x82,0x99,
|
||||
0xFB,0x0E,0x1B,0x85,0xBD,0xA8,0x5D,0x73,0xCD,0xDC,0x58,0x75,0x0A,0xBE,0x63,0x6C,
|
||||
0x48,0xE7,0x4C,0xE4,0x30,0x2B,0x04,0x60,0xB9,0x15,0xD8,0xDA,0x86,0x81,0x75,0x8F,
|
||||
0x96,0xD4,0x8D,0x1C,0x5D,0x70,0x85,0x7C,0x1C,0x67,0x7B,0xD5,0x08,0x67,0xA6,0xCE,
|
||||
0x4B,0x0A,0x66,0x70,0xB7,0xE5,0x63,0xD4,0x5B,0x8A,0x82,0xEA,0x10,0x67,0xCA,0xE2,
|
||||
0xF4,0xEF,0x17,0x85,0x2F,0x2A,0x5F,0x8A,0x97,0x82,0xF8,0x6A,0xD6,0x34,0x10,0xEA,
|
||||
0xEB,0xC9,0x5C,0x3C,0xE1,0x49,0xF8,0x46,0xEB,0xDE,0xBD,0xF6,0xA9,0x92,0xF1,0xAA,
|
||||
0xA6,0xA0,0x18,0xB0,0x3A,0xD3,0x0F,0x1F,0xF3,0x6F,0xFF,0x31,0x45,0x43,0x44,0xD3,
|
||||
0x50,0x9A,0xF7,0x88,0x09,0x96,0xC1,0xCE,0x76,0xCC,0xF2,0x2C,0x2C,0xBA,0xAD,0x82,
|
||||
0x77,0x8F,0x18,0x84,0xC0,0xD2,0x07,0x9C,0x36,0x90,0x83,0x4E,0x0B,0xA5,0x4F,0x43,
|
||||
0x3E,0x04,0xAB,0x78,0x4F,0xD6,0xFB,0x09,0x01,0x24,0x90,0xDA,0x6F,0x3C,0x3A,0x61,
|
||||
0x0D,0x7F,0x69,0x4A,0xEB,0x2B,0x30,0x02,0xB4,0xDB,0xE0,0x84,0xA9,0xEC,0xD7,0x35,
|
||||
0xBF,0x37,0x7D,0x85,0x58,0xCE,0xA9,0x4E,0xE4,0x80,0xC7,0xA8,0xD3,0x30,0x67,0x48,
|
||||
0xEB,0x29,0xAF,0x2F,0x74,0x6A,0xB4,0xA7,0x3F,0x0F,0x3F,0x92,0xAF,0xF3,0xCA,0xAC,
|
||||
0xAF,0x4B,0xD9,0x94,0xC0,0x43,0xCA,0x81,0x0D,0x2F,0x48,0xA1,0xB0,0x27,0xD5,0xD2,
|
||||
0xEF,0x4B,0x05,0x85,0xA3,0xDE,0x4D,0x93,0x30,0x3C,0xF0,0xBB,0x4A,0x8F,0x30,0x27,
|
||||
0x4C,0xEB,0xE3,0x3E,0x64,0xED,0x9A,0x2F,0x3B,0xF1,0x82,0xF0,0xBA,0xF4,0xCF,0x7F,
|
||||
0x40,0xCB,0xB0,0xE1,0x7F,0xBC,0xAA,0x57,0xD3,0xC9,0x74,0xF2,0xFA,0x43,0x0D,0x22,
|
||||
0xD0,0xF4,0x77,0x4E,0x93,0xD7,0x85,0x70,0x1F,0x99,0xBF,0xB6,0xDE,0x35,0xF1,0x30,
|
||||
0xA7,0x5E,0x71,0xF0,0x6B,0x01,0x2D,0x7B,0x64,0xF0,0x33,0x53,0x0A,0x39,0x88,0xF3,
|
||||
0x6B,0x3A,0xA6,0x6B,0x35,0xD2,0x2F,0x43,0xCD,0x02,0xFD,0xB5,0xE9,0xBC,0x5B,0xAA,
|
||||
0xD8,0xA4,0x19,0x7E,0x0E,0x5D,0x94,0x81,0x9E,0x6F,0x77,0xAD,0xD6,0x0E,0x74,0x93,
|
||||
0x96,0xE7,0xC4,0x18,0x5F,0xAD,0xF5,0x19,
|
||||
};
|
||||
|
||||
#region ARM9 decryption check values
|
||||
|
||||
public const uint MAGIC30 = 0x72636E65;
|
||||
public const uint MAGIC34 = 0x6A624F79;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,364 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NDecrypt.Core;
|
||||
using NDecrypt.Nitro.Headers;
|
||||
|
||||
namespace NDecrypt.Nitro
|
||||
{
|
||||
public class DSTool : ITool
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the input DS/DSi file
|
||||
/// </summary>
|
||||
private readonly string filename;
|
||||
|
||||
/// <summary>
|
||||
/// Decryption args to use while processing
|
||||
/// </summary>
|
||||
private readonly DecryptArgs decryptArgs;
|
||||
|
||||
#region Encryption process variables
|
||||
|
||||
private uint[] cardHash = new uint[0x412];
|
||||
private uint[] arg2 = new uint[3];
|
||||
|
||||
#endregion
|
||||
|
||||
public DSTool(string filename, DecryptArgs decryptArgs)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.decryptArgs = decryptArgs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process an input file given the input values
|
||||
/// </summary>
|
||||
public bool ProcessFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Open the read and write on the same file for inplace processing
|
||||
using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
|
||||
using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)))
|
||||
{
|
||||
NDSHeader header = NDSHeader.Read(reader);
|
||||
if (header == null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a DS or DSi Rom!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process the secure area
|
||||
ProcessSecureArea(header, reader, writer);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine($"An error has occurred. {filename} may be corrupted if it was partially processed.");
|
||||
Console.WriteLine("Please check that the file was a valid DS or DSi file and try again.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process secure area in the DS/DSi file
|
||||
/// </summary>
|
||||
/// <param name="ndsHeader">NDS header representing the DS file</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessSecureArea(NDSHeader ndsHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// If we're forcing the operation, tell the user
|
||||
if (decryptArgs.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 (decryptArgs.Encrypt ^ isDecrypted.Value)
|
||||
{
|
||||
Console.WriteLine("File is already " + (decryptArgs.Encrypt ? "encrypted" : "decrypted"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessARM9(ndsHeader, reader, writer);
|
||||
|
||||
Console.WriteLine("File has been " + (decryptArgs.Encrypt ? "encrypted" : "decrypted"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the current file is already decrypted or not (or has an empty secure area)
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>True if the file has known values for a decrypted file, null if it's empty, false otherwise</returns>
|
||||
private bool? CheckIfDecrypted(BinaryReader reader)
|
||||
{
|
||||
reader.BaseStream.Seek(0x4000, SeekOrigin.Begin);
|
||||
uint firstValue = reader.ReadUInt32();
|
||||
uint secondValue = reader.ReadUInt32();
|
||||
|
||||
// Empty secure area standard
|
||||
if (firstValue == 0x00000000 && secondValue == 0x00000000)
|
||||
{
|
||||
Console.WriteLine("Empty secure area found. Cannot be encrypted or decrypted.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Improperly decrypted empty secure area (decrypt empty with woodsec)
|
||||
else if ((firstValue == 0xE386C397 && secondValue == 0x82775B7E)
|
||||
|| (firstValue == 0xF98415B8 && secondValue == 0x698068FC)
|
||||
|| (firstValue == 0xA71329EE && secondValue == 0x2A1D4C38)
|
||||
|| (firstValue == 0xC44DCC48 && secondValue == 0x38B6F8CB)
|
||||
|| (firstValue == 0x3A9323B5 && secondValue == 0xC0387241))
|
||||
{
|
||||
Console.WriteLine("Improperly decrypted empty secure area found. Should be encrypted to get proper value.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Improperly encrypted empty secure area (encrypt empty with woodsec)
|
||||
else if ((firstValue == 0x4BCE88BE && secondValue == 0xD3662DD1)
|
||||
|| (firstValue == 0x2543C534 && secondValue == 0xCC4BE38E))
|
||||
{
|
||||
Console.WriteLine("Improperly encrypted empty secure area found. Should be decrypted to get proper value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Properly decrypted nonstandard value (mastering issue)
|
||||
else if ((firstValue == 0xD0D48B67 && secondValue == 0x39392F23) // Dragon Quest 5 (EU)
|
||||
|| (firstValue == 0x014A191A && secondValue == 0xA5C470B9) // Dragon Quest 5 (USA)
|
||||
|| (firstValue == 0x7829BC8D && secondValue == 0x9968EF44) // Dragon Quest 5 (JP)
|
||||
|| (firstValue == 0xC4A15AB8 && secondValue == 0xD2E667C8) // Prince of Persia (EU)
|
||||
|| (firstValue == 0xD5E97D20 && secondValue == 0x21B2A159)) // Prince of Persia (USA)
|
||||
{
|
||||
Console.WriteLine("Decrypted secure area for known, nonstandard value found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Properly decrypted prototype value
|
||||
else if (firstValue == 0xBA35F813 && secondValue == 0xB691AAE8)
|
||||
{
|
||||
Console.WriteLine("Decrypted secure area for prototype found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Standard decryption values
|
||||
return firstValue == 0xE7FFDEFF && secondValue == 0xE7FFDEFF;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the secure ARM9 region of the file, if possible
|
||||
/// </summary>
|
||||
/// <param name="ndsHeader">NDS header representing the DS file</param>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
private void ProcessARM9(NDSHeader ndsHeader, BinaryReader reader, BinaryWriter writer)
|
||||
{
|
||||
// Seek to the beginning of the secure area
|
||||
reader.BaseStream.Seek(0x4000, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek(0x4000, SeekOrigin.Begin);
|
||||
|
||||
// Grab the first two blocks
|
||||
uint p0 = reader.ReadUInt32();
|
||||
uint p1 = reader.ReadUInt32();
|
||||
|
||||
// Perform the initialization steps
|
||||
Init1(ndsHeader);
|
||||
if (!decryptArgs.Encrypt) Decrypt(ref p1, ref p0);
|
||||
arg2[1] <<= 1;
|
||||
arg2[2] >>= 1;
|
||||
Init2();
|
||||
|
||||
// If we're decrypting, set the proper flags
|
||||
if (!decryptArgs.Encrypt)
|
||||
{
|
||||
Decrypt(ref p1, ref p0);
|
||||
if (p0 == Constants.MAGIC30 && p1 == Constants.MAGIC34)
|
||||
{
|
||||
p0 = 0xE7FFDEFF;
|
||||
p1 = 0xE7FFDEFF;
|
||||
}
|
||||
|
||||
writer.Write(p0);
|
||||
writer.Write(p1);
|
||||
}
|
||||
|
||||
// Ensure alignment
|
||||
reader.BaseStream.Seek(0x4008, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek(0x4008, SeekOrigin.Begin);
|
||||
|
||||
// Loop throgh the main encryption step
|
||||
uint size = 0x800 - 8;
|
||||
while (size > 0)
|
||||
{
|
||||
p0 = reader.ReadUInt32();
|
||||
p1 = reader.ReadUInt32();
|
||||
|
||||
if (decryptArgs.Encrypt)
|
||||
Encrypt(ref p1, ref p0);
|
||||
else
|
||||
Decrypt(ref p1, ref p0);
|
||||
|
||||
writer.Write(p0);
|
||||
writer.Write(p1);
|
||||
|
||||
size -= 8;
|
||||
}
|
||||
|
||||
// Replace the header explicitly if we're encrypting
|
||||
if (decryptArgs.Encrypt)
|
||||
{
|
||||
reader.BaseStream.Seek(0x4000, SeekOrigin.Begin);
|
||||
writer.BaseStream.Seek(0x4000, SeekOrigin.Begin);
|
||||
|
||||
p0 = reader.ReadUInt32();
|
||||
p1 = reader.ReadUInt32();
|
||||
|
||||
if (p0 == 0xE7FFDEFF && p1 == 0xE7FFDEFF)
|
||||
{
|
||||
p0 = Constants.MAGIC30;
|
||||
p1 = Constants.MAGIC34;
|
||||
}
|
||||
|
||||
Encrypt(ref p1, ref p0);
|
||||
Init1(ndsHeader);
|
||||
Encrypt(ref p1, ref p0);
|
||||
|
||||
writer.Write(p0);
|
||||
writer.Write(p1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// First common initialization step
|
||||
/// </summary>
|
||||
/// <param name="ndsHeader">NDS header representing the DS file</param>
|
||||
private void Init1(NDSHeader ndsHeader)
|
||||
{
|
||||
Buffer.BlockCopy(Constants.NDSEncryptionData, 0, cardHash, 0, 4 * (1024 + 18));
|
||||
arg2 = new uint[] { ndsHeader.Gamecode, ndsHeader.Gamecode >> 1, ndsHeader.Gamecode << 1 };
|
||||
Init2();
|
||||
Init2();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Second common initialization step
|
||||
/// </summary>
|
||||
private void Init2()
|
||||
{
|
||||
Encrypt(ref arg2[2], ref arg2[1]);
|
||||
Encrypt(ref arg2[1], ref arg2[0]);
|
||||
|
||||
byte[] allBytes = BitConverter.GetBytes(arg2[0])
|
||||
.Concat(BitConverter.GetBytes(arg2[1]))
|
||||
.Concat(BitConverter.GetBytes(arg2[2]))
|
||||
.ToArray();
|
||||
|
||||
UpdateHashtable(allBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a decryption step
|
||||
/// </summary>
|
||||
/// <param name="arg1">First unsigned value to use in decryption</param>
|
||||
/// <param name="arg2">Second unsigned value to use in decryption</param>
|
||||
private void Decrypt(ref uint arg1, ref uint arg2)
|
||||
{
|
||||
uint a = arg1;
|
||||
uint b = arg2;
|
||||
for (int i = 17; i > 1; i--)
|
||||
{
|
||||
uint c = cardHash[i] ^ a;
|
||||
a = b ^ Lookup(c);
|
||||
b = c;
|
||||
}
|
||||
|
||||
arg1 = b ^ cardHash[0];
|
||||
arg2 = a ^ cardHash[1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform an encryption step
|
||||
/// </summary>
|
||||
/// <param name="arg1">First unsigned value to use in encryption</param>
|
||||
/// <param name="arg2">Second unsigned value to use in encryption</param>
|
||||
private void Encrypt(ref uint arg1, ref uint arg2)
|
||||
{
|
||||
uint a = arg1;
|
||||
uint b = arg2;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
uint c = cardHash[i] ^ a;
|
||||
a = b ^ Lookup(c);
|
||||
b = c;
|
||||
}
|
||||
|
||||
arg2 = a ^ cardHash[16];
|
||||
arg1 = b ^ cardHash[17];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lookup the value from the hashtable
|
||||
/// </summary>
|
||||
/// <param name="v">Value to lookup in the hashtable</param>
|
||||
/// <returns>Processed value through the hashtable</returns>
|
||||
private uint Lookup(uint v)
|
||||
{
|
||||
uint a = (v >> 24) & 0xFF;
|
||||
uint b = (v >> 16) & 0xFF;
|
||||
uint c = (v >> 8) & 0xFF;
|
||||
uint d = (v >> 0) & 0xFF;
|
||||
|
||||
a = cardHash[a + 18 + 0];
|
||||
b = cardHash[b + 18 + 256];
|
||||
c = cardHash[c + 18 + 512];
|
||||
d = cardHash[d + 18 + 768];
|
||||
|
||||
return d + (c ^ (b + a));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the hashtable
|
||||
/// </summary>
|
||||
/// <param name="arg1">Value to update the hashtable with</param>
|
||||
private void UpdateHashtable(byte[] arg1)
|
||||
{
|
||||
for (int j = 0; j < 18; j++)
|
||||
{
|
||||
uint r3 = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
r3 <<= 8;
|
||||
r3 |= arg1[(j * 4 + i) & 7];
|
||||
}
|
||||
|
||||
cardHash[j] ^= r3;
|
||||
}
|
||||
|
||||
uint tmp1 = 0;
|
||||
uint tmp2 = 0;
|
||||
for (int i = 0; i < 18; i += 2)
|
||||
{
|
||||
Encrypt(ref tmp1, ref tmp2);
|
||||
cardHash[i + 0] = tmp1;
|
||||
cardHash[i + 1] = tmp2;
|
||||
}
|
||||
for (int i = 0; i < 0x400; i += 2)
|
||||
{
|
||||
Encrypt(ref tmp1, ref tmp2);
|
||||
cardHash[i + 18 + 0] = tmp1;
|
||||
cardHash[i + 18 + 1] = tmp2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace NDecrypt.Nitro
|
||||
{
|
||||
internal enum NDSUnitcode : byte
|
||||
{
|
||||
NDS = 0x00,
|
||||
NDSPlusDSi = 0x02,
|
||||
DSi = 0x03,
|
||||
}
|
||||
}
|
||||
@@ -1,578 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace NDecrypt.Nitro.Headers
|
||||
{
|
||||
internal class NDSHeader
|
||||
{
|
||||
#region Common
|
||||
|
||||
/// <summary>
|
||||
/// Game Title
|
||||
/// </summary>
|
||||
public char[] GameTitle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gamecode
|
||||
/// </summary>
|
||||
public uint Gamecode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Makercode
|
||||
/// </summary>
|
||||
public char[] Makercode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unitcode
|
||||
/// </summary>
|
||||
public NDSUnitcode Unitcode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Encryption seed select (device code. 0 = normal)
|
||||
/// </summary>
|
||||
public byte EncryptionSeedSelect { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Devicecapacity
|
||||
/// </summary>
|
||||
public byte Devicecapacity { get; private set; }
|
||||
public int DeviceCapacityInBytes { get { return (1 << Devicecapacity) * (1024 * 1024); } }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Game Revision (used by DSi titles)
|
||||
/// </summary>
|
||||
public ushort GameRevision { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ROM Version
|
||||
/// </summary>
|
||||
public byte RomVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Internal flags, (Bit2: Autostart)
|
||||
/// </summary>
|
||||
public byte InternalFlags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 rom offset
|
||||
/// </summary>
|
||||
public uint ARM9RomOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 entry address
|
||||
/// </summary>
|
||||
public uint ARM9EntryAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 load address
|
||||
/// </summary>
|
||||
public uint ARM9LoadAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 size
|
||||
/// </summary>
|
||||
public uint ARM9Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7 rom offset
|
||||
/// </summary>
|
||||
public uint ARM7RomOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7 entry address
|
||||
/// </summary>
|
||||
public uint ARM7EntryAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7 load address
|
||||
/// </summary>
|
||||
public uint ARM7LoadAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7 size
|
||||
/// </summary>
|
||||
public uint ARM7Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Name Table (FNT) offset
|
||||
/// </summary>
|
||||
public uint FileNameTableOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Name Table (FNT) length
|
||||
/// </summary>
|
||||
public uint FileNameTableLength { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Allocation Table (FNT) offset
|
||||
/// </summary>
|
||||
public uint FileAllocationTableOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Allocation Table (FNT) length
|
||||
/// </summary>
|
||||
public uint FileAllocationTableLength { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Name Table (FNT) offset
|
||||
/// </summary>
|
||||
public uint ARM9OverlayOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Name Table (FNT) length
|
||||
/// </summary>
|
||||
public uint ARM9OverlayLength { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Name Table (FNT) offset
|
||||
/// </summary>
|
||||
public uint ARM7OverlayOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Name Table (FNT) length
|
||||
/// </summary>
|
||||
public uint ARM7OverlayLength { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Normal card control register settings (0x00416657 for OneTimePROM)
|
||||
/// </summary>
|
||||
public byte[] NormalCardControlRegisterSettings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Secure card control register settings (0x081808F8 for OneTimePROM)
|
||||
/// </summary>
|
||||
public byte[] SecureCardControlRegisterSettings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Icon Banner offset (NDSi same as NDS, but with new extra entries)
|
||||
/// </summary>
|
||||
public uint IconBannerOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Secure area (2K) CRC
|
||||
/// </summary>
|
||||
public ushort SecureAreaCRC { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Secure transfer timeout (0x0D7E for OneTimePROM)
|
||||
/// </summary>
|
||||
public ushort SecureTransferTimeout { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 autoload
|
||||
/// </summary>
|
||||
public byte[] ARM9Autoload { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7 autoload
|
||||
/// </summary>
|
||||
public byte[] ARM7Autoload { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Secure disable
|
||||
/// </summary>
|
||||
public byte[] SecureDisable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// NTR region ROM size (excluding DSi area)
|
||||
/// </summary>
|
||||
public uint NTRRegionRomSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Header size
|
||||
/// </summary>
|
||||
public uint HeaderSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///Reserved (0x88, 0x8C, 0x90 = Unknown, used by DSi)
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Nintendo Logo
|
||||
/// </summary>
|
||||
public byte[] NintendoLogo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Nintendo Logo CRC
|
||||
/// </summary>
|
||||
public ushort NintendoLogoCRC { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Header CRC
|
||||
/// </summary>
|
||||
public ushort HeaderCRC { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Debugger reserved
|
||||
/// </summary>
|
||||
public byte[] DebuggerReserved { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extended DSi
|
||||
|
||||
/// <summary>
|
||||
/// Global MBK1..MBK5 Settings
|
||||
/// </summary>
|
||||
public byte[] GlobalMBK15Settings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Local MBK6..MBK8 Settings for ARM9
|
||||
/// </summary>
|
||||
public byte[] LocalMBK68SettingsARM9 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Local MBK6..MBK8 Settings for ARM7
|
||||
/// </summary>
|
||||
public byte[] LocalMBK68SettingsARM7 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Global MBK9 Setting
|
||||
/// </summary>
|
||||
public byte[] GlobalMBK9Setting { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public byte[] RegionFlags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Access control
|
||||
/// </summary>
|
||||
public byte[] AccessControl { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7 SCFG EXT mask (controls which devices to enable)
|
||||
/// </summary>
|
||||
public byte[] ARM7SCFGEXTMask { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved/flags? When bit2 of byte 0x1bf is set, usage of banner.sav from the title data dir is enabled.(additional banner data)
|
||||
/// </summary>
|
||||
public byte[] ReservedFlags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9i rom offset
|
||||
/// </summary>
|
||||
public uint ARM9iRomOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved3 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9i load address
|
||||
/// </summary>
|
||||
public uint ARM9iLoadAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9i size;
|
||||
/// </summary>
|
||||
public uint ARM9iSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7i rom offset
|
||||
/// </summary>
|
||||
public uint ARM7iRomOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to base address where various structures and parameters are passed to the title - what is that???
|
||||
/// </summary>
|
||||
public byte[] Reserved4 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7i load address
|
||||
/// </summary>
|
||||
public uint ARM7iLoadAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7i size;
|
||||
/// </summary>
|
||||
public uint ARM7iSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest NTR region offset
|
||||
/// </summary>
|
||||
public uint DigestNTRRegionOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest NTR region length
|
||||
/// </summary>
|
||||
public uint DigestNTRRegionLength { get; private set; }
|
||||
|
||||
// <summary>
|
||||
/// Digest TWL region offset
|
||||
/// </summary>
|
||||
public uint DigestTWLRegionOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest TWL region length
|
||||
/// </summary>
|
||||
public uint DigestTWLRegionLength { get; private set; }
|
||||
|
||||
// <summary>
|
||||
/// Digest Sector Hashtable region offset
|
||||
/// </summary>
|
||||
public uint DigestSectorHashtableRegionOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest Sector Hashtable region length
|
||||
/// </summary>
|
||||
public uint DigestSectorHashtableRegionLength { get; private set; }
|
||||
|
||||
// <summary>
|
||||
/// Digest Block Hashtable region offset
|
||||
/// </summary>
|
||||
public uint DigestBlockHashtableRegionOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest Block Hashtable region length
|
||||
/// </summary>
|
||||
public uint DigestBlockHashtableRegionLength { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest Sector size
|
||||
/// </summary>
|
||||
public uint DigestSectorSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digeset Block Sectorount
|
||||
/// </summary>
|
||||
public uint DigestBlockSectorCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Icon Banner Size (usually 0x23C0)
|
||||
/// </summary>
|
||||
public uint IconBannerSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unknown (used by DSi)
|
||||
/// </summary>
|
||||
public byte[] Unknown1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// NTR+TWL region ROM size (total size including DSi area)
|
||||
/// </summary>
|
||||
public uint NTRTWLRegionRomSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unknown (used by DSi)
|
||||
/// </summary>
|
||||
public byte[] Unknown2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modcrypt area 1 offset
|
||||
/// </summary>
|
||||
public uint ModcryptArea1Offset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modcrypt area 1 size
|
||||
/// </summary>
|
||||
public uint ModcryptArea1Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modcrypt area 2 offset
|
||||
/// </summary>
|
||||
public uint ModcryptArea2Offset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modcrypt area 2 size
|
||||
/// </summary>
|
||||
public uint ModcryptArea2Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Title ID
|
||||
/// </summary>
|
||||
public byte[] TitleID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// DSiWare: "public.sav" size
|
||||
/// </summary>
|
||||
public uint DSiWarePublicSavSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// DSiWare: "private.sav" size
|
||||
/// </summary>
|
||||
public uint DSiWarePrivateSavSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved (zero)
|
||||
/// </summary>
|
||||
public byte[] ReservedZero { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unknown (used by DSi)
|
||||
/// </summary>
|
||||
public byte[] Unknown3 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 (with encrypted secure area) SHA1 HMAC hash
|
||||
/// </summary>
|
||||
public byte[] ARM9WithSecureAreaSHA1HMACHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7 SHA1 HMAC hash
|
||||
/// </summary>
|
||||
public byte[] ARM7SHA1HMACHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest master SHA1 HMAC hash
|
||||
/// </summary>
|
||||
public byte[] DigestMasterSHA1HMACHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Banner SHA1 HMAC hash
|
||||
/// </summary>
|
||||
public byte[] BannerSHA1HMACHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9i (decrypted) SHA1 HMAC hash
|
||||
/// </summary>
|
||||
public byte[] ARM9iDecryptedSHA1HMACHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM7i (decrypted) SHA1 HMAC hash
|
||||
/// </summary>
|
||||
public byte[] ARM7iDecryptedSHA1HMACHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved5 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 (without secure area) SHA1 HMAC hash
|
||||
/// </summary>
|
||||
public byte[] ARM9NoSecureAreaSHA1HMACHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved6 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved and unchecked region, always zero. Used for passing arguments in debug environment.
|
||||
/// </summary>
|
||||
public byte[] ReservedAndUnchecked { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RSA signature (the first 0xE00 bytes of the header are signed with an 1024-bit RSA signature).
|
||||
/// </summary>
|
||||
public byte[] RSASignature { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an NDS/NDSi header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>NDS/NDSi header object, null on error</returns>
|
||||
public static NDSHeader Read(BinaryReader reader)
|
||||
{
|
||||
NDSHeader header = new NDSHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.GameTitle = reader.ReadChars(0xC);
|
||||
header.Gamecode = reader.ReadUInt32();
|
||||
header.Makercode = reader.ReadChars(2);
|
||||
header.Unitcode = (NDSUnitcode)reader.ReadByte();
|
||||
header.EncryptionSeedSelect = reader.ReadByte();
|
||||
header.Devicecapacity = reader.ReadByte();
|
||||
header.Reserved1 = reader.ReadBytes(7);
|
||||
header.GameRevision = reader.ReadUInt16();
|
||||
header.RomVersion = reader.ReadByte();
|
||||
header.InternalFlags = reader.ReadByte();
|
||||
header.ARM9RomOffset = reader.ReadUInt32();
|
||||
header.ARM9EntryAddress = reader.ReadUInt32();
|
||||
header.ARM9LoadAddress = reader.ReadUInt32();
|
||||
header.ARM9Size = reader.ReadUInt32();
|
||||
header.ARM7RomOffset = reader.ReadUInt32();
|
||||
header.ARM7EntryAddress = reader.ReadUInt32();
|
||||
header.ARM7LoadAddress = reader.ReadUInt32();
|
||||
header.ARM7Size = reader.ReadUInt32();
|
||||
header.FileNameTableOffset = reader.ReadUInt32();
|
||||
header.FileNameTableLength = reader.ReadUInt32();
|
||||
header.FileAllocationTableOffset = reader.ReadUInt32();
|
||||
header.FileAllocationTableLength = reader.ReadUInt32();
|
||||
header.ARM9OverlayOffset = reader.ReadUInt32();
|
||||
header.ARM9OverlayLength = reader.ReadUInt32();
|
||||
header.ARM7OverlayOffset = reader.ReadUInt32();
|
||||
header.ARM7OverlayLength = reader.ReadUInt32();
|
||||
header.SecureDisable = reader.ReadBytes(8);
|
||||
header.NTRRegionRomSize = reader.ReadUInt32();
|
||||
header.HeaderSize = reader.ReadUInt32();
|
||||
header.Reserved2 = reader.ReadBytes(56);
|
||||
header.NintendoLogo = reader.ReadBytes(156);
|
||||
header.NintendoLogoCRC = reader.ReadUInt16();
|
||||
header.DebuggerReserved = reader.ReadBytes(0x20);
|
||||
|
||||
// If we have a DSi compatible title
|
||||
if (header.Unitcode == NDSUnitcode.NDSPlusDSi
|
||||
|| header.Unitcode == NDSUnitcode.DSi)
|
||||
{
|
||||
header.GlobalMBK15Settings = reader.ReadBytes(20);
|
||||
header.LocalMBK68SettingsARM9 = reader.ReadBytes(12);
|
||||
header.LocalMBK68SettingsARM7 = reader.ReadBytes(12);
|
||||
header.GlobalMBK9Setting = reader.ReadBytes(4);
|
||||
header.RegionFlags = reader.ReadBytes(4);
|
||||
header.AccessControl = reader.ReadBytes(4);
|
||||
header.ARM7SCFGEXTMask = reader.ReadBytes(4);
|
||||
header.ReservedFlags = reader.ReadBytes(4);
|
||||
header.ARM9iRomOffset = reader.ReadUInt32();
|
||||
header.Reserved3 = reader.ReadBytes(4);
|
||||
header.ARM9iLoadAddress = reader.ReadUInt32();
|
||||
header.ARM9iSize = reader.ReadUInt32();
|
||||
header.ARM7iRomOffset = reader.ReadUInt32();
|
||||
header.Reserved4 = reader.ReadBytes(4);
|
||||
header.ARM7iLoadAddress = reader.ReadUInt32();
|
||||
header.ARM7iSize = reader.ReadUInt32();
|
||||
header.DigestNTRRegionOffset = reader.ReadUInt32();
|
||||
header.DigestNTRRegionLength = reader.ReadUInt32();
|
||||
header.DigestTWLRegionOffset = reader.ReadUInt32();
|
||||
header.DigestTWLRegionLength = reader.ReadUInt32();
|
||||
header.DigestSectorHashtableRegionOffset = reader.ReadUInt32();
|
||||
header.DigestSectorHashtableRegionLength = reader.ReadUInt32();
|
||||
header.DigestBlockHashtableRegionOffset = reader.ReadUInt32();
|
||||
header.DigestBlockHashtableRegionLength = reader.ReadUInt32();
|
||||
header.DigestSectorSize = reader.ReadUInt32();
|
||||
header.DigestBlockSectorCount = reader.ReadUInt32();
|
||||
header.IconBannerSize = reader.ReadUInt32();
|
||||
header.Unknown1 = reader.ReadBytes(4);
|
||||
header.ModcryptArea1Offset = reader.ReadUInt32();
|
||||
header.ModcryptArea1Size = reader.ReadUInt32();
|
||||
header.ModcryptArea2Offset = reader.ReadUInt32();
|
||||
header.ModcryptArea2Size = reader.ReadUInt32();
|
||||
header.TitleID = reader.ReadBytes(8);
|
||||
header.DSiWarePublicSavSize = reader.ReadUInt32();
|
||||
header.DSiWarePrivateSavSize = reader.ReadUInt32();
|
||||
header.ReservedZero = reader.ReadBytes(176);
|
||||
header.Unknown2 = reader.ReadBytes(0x10);
|
||||
header.ARM9WithSecureAreaSHA1HMACHash = reader.ReadBytes(20);
|
||||
header.ARM7SHA1HMACHash = reader.ReadBytes(20);
|
||||
header.DigestMasterSHA1HMACHash = reader.ReadBytes(20);
|
||||
header.BannerSHA1HMACHash = reader.ReadBytes(20);
|
||||
header.ARM9iDecryptedSHA1HMACHash = reader.ReadBytes(20);
|
||||
header.ARM7iDecryptedSHA1HMACHash = reader.ReadBytes(20);
|
||||
header.Reserved5 = reader.ReadBytes(40);
|
||||
header.ARM9NoSecureAreaSHA1HMACHash = reader.ReadBytes(20);
|
||||
header.Reserved6 = reader.ReadBytes(2636);
|
||||
header.ReservedAndUnchecked = reader.ReadBytes(0x180);
|
||||
header.RSASignature = reader.ReadBytes(0x80);
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net48;net6.0</TargetFrameworks>
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Copyright>Copyright (c)2018-2023 Matt Nadareski</Copyright>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<RepositoryUrl>https://github.com/SabreTools/NDecrypt</RepositoryUrl>
|
||||
<Version>0.2.5</Version>
|
||||
<AssemblyVersion>$(Version)</AssemblyVersion>
|
||||
<FileVersion>$(Version)</FileVersion>
|
||||
<IncludeSource>true</IncludeSource>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NDecrypt.Core\NDecrypt.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
12
NDecrypt.sln
12
NDecrypt.sln
@@ -12,10 +12,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NDecrypt", "NDecrypt\NDecry
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NDecrypt.Core", "NDecrypt.Core\NDecrypt.Core.csproj", "{91C54370-5741-4742-B2E9-EC498551AD1C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NDecrypt.N3DS", "NDecrypt.N3DS\NDecrypt.N3DS.csproj", "{F0A33533-B248-4D62-95CC-47DFC9721A11}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NDecrypt.Nitro", "NDecrypt.Nitro\NDecrypt.Nitro.csproj", "{1D2AE261-86FE-44E4-A7D3-C43AA5FB8523}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -30,14 +26,6 @@ Global
|
||||
{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
|
||||
{F0A33533-B248-4D62-95CC-47DFC9721A11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F0A33533-B248-4D62-95CC-47DFC9721A11}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F0A33533-B248-4D62-95CC-47DFC9721A11}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F0A33533-B248-4D62-95CC-47DFC9721A11}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1D2AE261-86FE-44E4-A7D3-C43AA5FB8523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1D2AE261-86FE-44E4-A7D3-C43AA5FB8523}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1D2AE261-86FE-44E4-A7D3-C43AA5FB8523}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1D2AE261-86FE-44E4-A7D3-C43AA5FB8523}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
15
NDecrypt/Enumerations.cs
Normal file
15
NDecrypt/Enumerations.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace NDecrypt
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of the detected file
|
||||
/// </summary>
|
||||
internal enum FileType
|
||||
{
|
||||
NULL,
|
||||
NDS,
|
||||
NDSi,
|
||||
iQueDS,
|
||||
N3DS,
|
||||
iQue3DS,
|
||||
}
|
||||
}
|
||||
198
NDecrypt/Features/BaseFeature.cs
Normal file
198
NDecrypt/Features/BaseFeature.cs
Normal file
@@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NDecrypt.Core;
|
||||
using SabreTools.CommandLine;
|
||||
using SabreTools.CommandLine.Inputs;
|
||||
|
||||
namespace NDecrypt.Features
|
||||
{
|
||||
internal abstract class BaseFeature : Feature
|
||||
{
|
||||
#region Common Inputs
|
||||
|
||||
protected const string ConfigName = "config";
|
||||
protected readonly StringInput ConfigString = new(ConfigName, ["-c", "--config"], "Path to config.json");
|
||||
|
||||
protected const string DevelopmentName = "development";
|
||||
protected readonly FlagInput DevelopmentFlag = new(DevelopmentName, ["-d", "--development"], "Enable using development keys, if available");
|
||||
|
||||
protected const string ForceName = "force";
|
||||
protected readonly FlagInput ForceFlag = new(ForceName, ["-f", "--force"], "Force operation by avoiding sanity checks");
|
||||
|
||||
protected const string HashName = "hash";
|
||||
protected readonly FlagInput HashFlag = new(HashName, "--hash", "Output size and hashes to a companion file");
|
||||
|
||||
protected const string OverwriteName = "overwrite";
|
||||
protected readonly FlagInput OverwriteFlag = new(OverwriteName, ["-o", "--overwrite"], "Overwrite input files instead of creating new ones");
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Mapping of reusable tools
|
||||
/// </summary>
|
||||
private readonly Dictionary<FileType, ITool> _tools = [];
|
||||
|
||||
protected BaseFeature(string name, string[] flags, string description, string? detailed = null)
|
||||
: base(name, flags, description, detailed)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Execute()
|
||||
{
|
||||
// Initialize required pieces
|
||||
InitializeTools();
|
||||
|
||||
for (int i = 0; i < Inputs.Count; i++)
|
||||
{
|
||||
if (File.Exists(Inputs[i]))
|
||||
{
|
||||
ProcessFile(Inputs[i]);
|
||||
}
|
||||
else if (Directory.Exists(Inputs[i]))
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(Inputs[i], "*", SearchOption.AllDirectories))
|
||||
{
|
||||
ProcessFile(file);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"{Inputs[i]} is not a file or folder. Please check your spelling and formatting and try again.");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool VerifyInputs() => Inputs.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Process a single file path
|
||||
/// </summary>
|
||||
/// <param name="input">File path to process</param>
|
||||
protected abstract void ProcessFile(string input);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the tools to be used by the feature
|
||||
/// </summary>
|
||||
private void InitializeTools()
|
||||
{
|
||||
|
||||
var decryptArgs = new DecryptArgs(GetString(ConfigName, "config.json"));
|
||||
_tools[FileType.NDS] = new DSTool(decryptArgs);
|
||||
_tools[FileType.N3DS] = new ThreeDSTool(GetBoolean(DevelopmentName), decryptArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Derive the encryption tool to be used for the given file
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to derive the tool from</param>
|
||||
protected 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],
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Derive an output filename from the input, if possible
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of the input file to derive from</param>
|
||||
/// <param name="extension">Preferred extension set by the feature implementation</param>
|
||||
/// <returns>Output filename based on the input</returns>
|
||||
protected static string GetOutputFile(string filename, string extension)
|
||||
{
|
||||
// Empty filenames are passed back
|
||||
if (filename.Length == 0)
|
||||
return filename;
|
||||
|
||||
// TODO: Replace the suffix instead of just appending
|
||||
// TODO: Ensure that the input and output aren't the same
|
||||
|
||||
// If the extension does not include a leading period
|
||||
#if NETCOREAPP || NETSTANDARD2_0_OR_GREATER
|
||||
if (!extension.StartsWith('.'))
|
||||
#else
|
||||
if (!extension.StartsWith("."))
|
||||
#endif
|
||||
extension = $".{extension}";
|
||||
|
||||
// Append the extension and return
|
||||
return $"{filename}{extension}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write out the hashes of a file to a named file
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to get hashes for/param>
|
||||
protected 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 is null)
|
||||
return;
|
||||
|
||||
// Open the output file and write the hashes
|
||||
using var fs = File.Open(Path.GetFullPath(filename) + ".hash", FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
using var sw = new StreamWriter(fs);
|
||||
sw.Write(hashString);
|
||||
}
|
||||
|
||||
/// <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
|
||||
|| filename.EndsWith(".cci", StringComparison.OrdinalIgnoreCase)) // Development carts/images
|
||||
{
|
||||
Console.WriteLine("File recognized as Nintendo 3DS");
|
||||
return FileType.N3DS;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Unrecognized file format for {filename}. Expected *.nds, *.srl, *.dsi, *.3ds, *.cci");
|
||||
return FileType.NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
NDecrypt/Features/DecryptFeature.cs
Normal file
57
NDecrypt/Features/DecryptFeature.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
|
||||
namespace NDecrypt.Features
|
||||
{
|
||||
internal sealed class DecryptFeature : BaseFeature
|
||||
{
|
||||
#region Feature Definition
|
||||
|
||||
public const string DisplayName = "decrypt";
|
||||
|
||||
private static readonly string[] _flags = ["d", "decrypt"];
|
||||
|
||||
private const string _description = "Decrypt the input files";
|
||||
|
||||
#endregion
|
||||
|
||||
public DecryptFeature()
|
||||
: base(DisplayName, _flags, _description)
|
||||
{
|
||||
RequiresInputs = true;
|
||||
|
||||
Add(ConfigString);
|
||||
Add(DevelopmentFlag);
|
||||
Add(ForceFlag);
|
||||
Add(HashFlag);
|
||||
|
||||
// TODO: Include this when enabled
|
||||
// Add(OverwriteFlag);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ProcessFile(string input)
|
||||
{
|
||||
// Attempt to derive the tool for the path
|
||||
var tool = DeriveTool(input);
|
||||
if (tool is null)
|
||||
return;
|
||||
|
||||
// Derive the output filename, if required
|
||||
string? output = null;
|
||||
if (!GetBoolean(OverwriteName))
|
||||
output = GetOutputFile(input, ".dec");
|
||||
|
||||
Console.WriteLine($"Processing {input}");
|
||||
|
||||
if (!tool.DecryptFile(input, output, GetBoolean(ForceName)))
|
||||
{
|
||||
Console.WriteLine("Decryption failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Output the file hashes, if expected
|
||||
if (GetBoolean(HashName))
|
||||
WriteHashes(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
NDecrypt/Features/EncryptFeature.cs
Normal file
57
NDecrypt/Features/EncryptFeature.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
|
||||
namespace NDecrypt.Features
|
||||
{
|
||||
internal sealed class EncryptFeature : BaseFeature
|
||||
{
|
||||
#region Feature Definition
|
||||
|
||||
public const string DisplayName = "encrypt";
|
||||
|
||||
private static readonly string[] _flags = ["e", "encrypt"];
|
||||
|
||||
private const string _description = "Encrypt the input files";
|
||||
|
||||
#endregion
|
||||
|
||||
public EncryptFeature()
|
||||
: base(DisplayName, _flags, _description)
|
||||
{
|
||||
RequiresInputs = true;
|
||||
|
||||
Add(ConfigString);
|
||||
Add(DevelopmentFlag);
|
||||
Add(ForceFlag);
|
||||
Add(HashFlag);
|
||||
|
||||
// TODO: Include this when enabled
|
||||
// Add(OverwriteFlag);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ProcessFile(string input)
|
||||
{
|
||||
// Attempt to derive the tool for the path
|
||||
var tool = DeriveTool(input);
|
||||
if (tool is null)
|
||||
return;
|
||||
|
||||
// Derive the output filename, if required
|
||||
string? output = null;
|
||||
if (!GetBoolean(OverwriteName))
|
||||
output = GetOutputFile(input, ".enc");
|
||||
|
||||
Console.WriteLine($"Processing {input}");
|
||||
|
||||
if (!tool.EncryptFile(input, output, GetBoolean(ForceName)))
|
||||
{
|
||||
Console.WriteLine("Encryption failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Output the file hashes, if expected
|
||||
if (GetBoolean(HashName))
|
||||
WriteHashes(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
NDecrypt/Features/InfoFeature.cs
Normal file
45
NDecrypt/Features/InfoFeature.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
|
||||
namespace NDecrypt.Features
|
||||
{
|
||||
internal sealed class InfoFeature : BaseFeature
|
||||
{
|
||||
#region Feature Definition
|
||||
|
||||
public const string DisplayName = "info";
|
||||
|
||||
private static readonly string[] _flags = ["i", "info"];
|
||||
|
||||
private const string _description = "Output file information";
|
||||
|
||||
#endregion
|
||||
|
||||
public InfoFeature()
|
||||
: base(DisplayName, _flags, _description)
|
||||
{
|
||||
RequiresInputs = true;
|
||||
|
||||
Add(HashFlag);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ProcessFile(string input)
|
||||
{
|
||||
// Attempt to derive the tool for the path
|
||||
var tool = DeriveTool(input);
|
||||
if (tool is null)
|
||||
return;
|
||||
|
||||
Console.WriteLine($"Processing {input}");
|
||||
|
||||
string? infoString = tool.GetInformation(input);
|
||||
infoString ??= "There was a problem getting file information!";
|
||||
|
||||
Console.WriteLine(infoString);
|
||||
|
||||
// Output the file hashes, if expected
|
||||
if (GetBoolean(HashName))
|
||||
WriteHashes(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NDecrypt.Core.Tools;
|
||||
using SabreTools.Hashing;
|
||||
|
||||
namespace NDecrypt
|
||||
{
|
||||
@@ -14,106 +10,24 @@ namespace NDecrypt
|
||||
/// </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)
|
||||
public static string? GetInfo(string input)
|
||||
{
|
||||
// If the file doesn't exist, return null
|
||||
if (!File.Exists(input))
|
||||
return null;
|
||||
|
||||
// Get the file length
|
||||
long size = new FileInfo(input).Length;
|
||||
|
||||
// Open the file
|
||||
Stream inputStream = File.OpenRead(input);
|
||||
|
||||
try
|
||||
{
|
||||
// Get a list of hashers to run over the buffer
|
||||
List<Hasher> hashers = new List<Hasher>
|
||||
{
|
||||
new Hasher(Hash.CRC),
|
||||
new Hasher(Hash.MD5),
|
||||
new Hasher(Hash.SHA1),
|
||||
new Hasher(Hash.SHA256),
|
||||
};
|
||||
|
||||
// Initialize the hashing helpers
|
||||
int buffersize = 3 * 1024 * 1024;
|
||||
byte[] buffer = new byte[buffersize];
|
||||
|
||||
/*
|
||||
Please note that some of the following code is adapted from
|
||||
RomVault. This is a modified version of how RomVault does
|
||||
threaded hashing. As such, some of the terminology and code
|
||||
is the same, though variable names and comments may have
|
||||
been tweaked to better fit this code base.
|
||||
*/
|
||||
|
||||
// Pre load the buffer
|
||||
int next = buffersize > size ? (int)size : buffersize;
|
||||
int current = inputStream.Read(buffer, 0, next);
|
||||
long refsize = size;
|
||||
|
||||
while (refsize > 0)
|
||||
{
|
||||
// Run hashes in parallel
|
||||
if (current > 0)
|
||||
Parallel.ForEach(hashers, h => h.Process(buffer, current));
|
||||
|
||||
// Load the next buffer
|
||||
refsize -= current;
|
||||
next = buffersize > refsize ? (int)refsize : buffersize;
|
||||
|
||||
if (next > 0)
|
||||
current = inputStream.Read(buffer, 0, next);
|
||||
}
|
||||
|
||||
// Finalize all hashing helpers
|
||||
Parallel.ForEach(hashers, h => h.Terminate());
|
||||
|
||||
// Get the results
|
||||
string result = $"Size: {size}\n"
|
||||
+ $"CRC32: {ByteArrayToString(hashers.First(h => h.HashType == Hash.CRC).GetHash()) ?? ""}\n"
|
||||
+ $"MD5: {ByteArrayToString(hashers.First(h => h.HashType == Hash.MD5).GetHash()) ?? ""}\n"
|
||||
+ $"SHA1: {ByteArrayToString(hashers.First(h => h.HashType == Hash.SHA1).GetHash()) ?? ""}\n"
|
||||
+ $"SHA256: {ByteArrayToString(hashers.First(h => h.HashType == Hash.SHA256).GetHash()) ?? ""}\n";
|
||||
|
||||
// Dispose of the hashers
|
||||
hashers.ForEach(h => h.Dispose());
|
||||
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
inputStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a byte array to a hex string
|
||||
/// </summary>
|
||||
/// <param name="bytes">Byte array to convert</param>
|
||||
/// <returns>Hex string representing the byte array</returns>
|
||||
/// <link>http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa</link>
|
||||
private static string ByteArrayToString(byte[] bytes)
|
||||
{
|
||||
// If we get null in, we send null out
|
||||
if (bytes == 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 is null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
string hex = BitConverter.ToString(bytes);
|
||||
return hex.Replace("-", string.Empty).ToLowerInvariant();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// Get the results
|
||||
return $"Size: {size}\n"
|
||||
+ $"CRC-32: {(hashDict.TryGetValue(HashType.CRC32, out string? value) ? value : string.Empty)}\n"
|
||||
+ $"MD5: {(hashDict.TryGetValue(HashType.MD5, out string? value1) ? value1 : string.Empty)}\n"
|
||||
+ $"SHA-1: {(hashDict.TryGetValue(HashType.SHA1, out string? value2) ? value2 : string.Empty)}\n"
|
||||
+ $"SHA-256: {(hashDict.TryGetValue(HashType.SHA256, out string? value3) ? value3 : string.Empty)}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,50 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net48;net6.0</TargetFrameworks>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Title>NDecrypt</Title>
|
||||
<AssemblyName>NDecrypt</AssemblyName>
|
||||
<Description>DS/3DS Encryption Tool</Description>
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Product>NDecrypt</Product>
|
||||
<Copyright>Copyright (c)2018-2023 Matt Nadareski</Copyright>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<RepositoryUrl>https://github.com/SabreTools/NDecrypt</RepositoryUrl>
|
||||
<Version>0.2.5</Version>
|
||||
<AssemblyVersion>$(Version)</AssemblyVersion>
|
||||
<FileVersion>$(Version)</FileVersion>
|
||||
<IncludeSource>true</IncludeSource>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>0.5.0</VersionPrefix>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NDecrypt.Core\NDecrypt.Core.csproj" />
|
||||
<ProjectReference Include="..\NDecrypt.N3DS\NDecrypt.N3DS.csproj" />
|
||||
<ProjectReference Include="..\NDecrypt.Nitro\NDecrypt.Nitro.csproj" />
|
||||
</ItemGroup>
|
||||
<!-- Package Properties -->
|
||||
<Title>NDecrypt</Title>
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Description>DS/3DS Encryption Tool</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2018-2025</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`)) OR $(TargetFramework.StartsWith(`net10`))">
|
||||
<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;net10.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NDecrypt.Core\NDecrypt.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="SabreTools.CommandLine" Version="[1.4.0]" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,254 +1,89 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using NDecrypt.Core;
|
||||
using NDecrypt.N3DS;
|
||||
using NDecrypt.Nitro;
|
||||
using System.Collections.Generic;
|
||||
using NDecrypt.Features;
|
||||
using SabreTools.CommandLine;
|
||||
using SabreTools.CommandLine.Features;
|
||||
|
||||
namespace NDecrypt
|
||||
{
|
||||
class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of the detected file
|
||||
/// </summary>
|
||||
private enum FileType
|
||||
{
|
||||
NULL,
|
||||
NDS,
|
||||
NDSi,
|
||||
iQueDS,
|
||||
N3DS,
|
||||
iQue3DS,
|
||||
N3DSCIA,
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
// Create the command set
|
||||
var commandSet = CreateCommands();
|
||||
|
||||
// If we have no args, show the help and quit
|
||||
if (args is null || args.Length == 0)
|
||||
{
|
||||
DisplayHelp("Not enough arguments");
|
||||
commandSet.OutputAllHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
var decryptArgs = new DecryptArgs();
|
||||
if (args[0] == "decrypt" || args[0] == "d")
|
||||
// Get the first argument as a feature flag
|
||||
string featureName = args[0];
|
||||
|
||||
// Get the associated feature
|
||||
var topLevel = commandSet.GetTopLevel(featureName);
|
||||
if (topLevel is null || topLevel is not Feature feature)
|
||||
{
|
||||
decryptArgs.Encrypt = false;
|
||||
}
|
||||
else if (args[0] == "encrypt" || args[0] == "e")
|
||||
{
|
||||
decryptArgs.Encrypt = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayHelp($"Invalid operation: {args[0]}");
|
||||
Console.WriteLine($"'{featureName}' is not valid feature flag");
|
||||
commandSet.OutputFeatureHelp(featureName);
|
||||
return;
|
||||
}
|
||||
|
||||
bool outputHashes = false;
|
||||
int start = 1;
|
||||
for ( ; start < args.Length; start++)
|
||||
// Handle default help functionality
|
||||
if (topLevel is Help helpFeature)
|
||||
{
|
||||
if (args[start] == "-c" || args[start] == "--citra")
|
||||
{
|
||||
decryptArgs.UseCitraKeyFile = true;
|
||||
}
|
||||
else if (args[start] == "-dev" || args[start] == "--development")
|
||||
{
|
||||
decryptArgs.Development = true;
|
||||
}
|
||||
else if (args[start] == "-f" || args[start] == "--force")
|
||||
{
|
||||
decryptArgs.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
|
||||
decryptArgs.KeyFile = tempPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
helpFeature.ProcessArgs(args, 0, commandSet);
|
||||
return;
|
||||
}
|
||||
|
||||
// Derive the keyfile path based on the runtime folder if not already set
|
||||
if (string.IsNullOrWhiteSpace(decryptArgs.KeyFile))
|
||||
{
|
||||
if (decryptArgs.UseCitraKeyFile)
|
||||
decryptArgs.KeyFile = Path.Combine(Path.GetDirectoryName(AppContext.BaseDirectory), "aes_keys.txt");
|
||||
else
|
||||
decryptArgs.KeyFile = Path.Combine(Path.GetDirectoryName(AppContext.BaseDirectory), "keys.bin");
|
||||
}
|
||||
|
||||
// If we are using a Citra keyfile, there are no development keys
|
||||
if (decryptArgs.Development && decryptArgs.UseCitraKeyFile)
|
||||
{
|
||||
Console.WriteLine("Citra keyfiles don't contain development keys; disabling the option...");
|
||||
decryptArgs.Development = false;
|
||||
}
|
||||
|
||||
// Initialize the constants, if possible
|
||||
decryptArgs.Initialize();
|
||||
|
||||
for (int i = start; i < args.Length; i++)
|
||||
{
|
||||
if (File.Exists(args[i]))
|
||||
{
|
||||
ProcessPath(args[i], decryptArgs, outputHashes);
|
||||
}
|
||||
else if (Directory.Exists(args[i]))
|
||||
{
|
||||
foreach (string file in Directory.EnumerateFiles(args[i], "*", SearchOption.AllDirectories))
|
||||
{
|
||||
ProcessPath(file, decryptArgs, outputHashes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"{args[i]} is not a file or folder. Please check your spelling and formatting and try again.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display a basic help text
|
||||
/// </summary>
|
||||
/// <param name="path">Path to the file to process</param>
|
||||
/// <param name="decryptArgs">DecryptArgs to use during processing</param>
|
||||
/// <param name="outputHashes">True to write out a hashfile, false otherwise</param>
|
||||
private static void ProcessPath(string path, DecryptArgs decryptArgs, bool outputHashes)
|
||||
{
|
||||
Console.WriteLine(path);
|
||||
ITool tool = DeriveTool(path, decryptArgs);
|
||||
if (tool?.ProcessFile() != true)
|
||||
Console.WriteLine("Processing failed!");
|
||||
else 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.exe <opeation> [flags] <path> ...
|
||||
|
||||
Possible values for <operation>:
|
||||
e, encrypt - Encrypt the input files
|
||||
d, decrypt - Decrypt the input files
|
||||
|
||||
Possible values for [flags] (one or more can be used):
|
||||
-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.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Derive the encryption tool to be used for the given file
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to derive the tool from</param>
|
||||
/// <param name="decryptArgs">Arguments to pass to the tools on creation</param>
|
||||
/// <returns></returns>
|
||||
private static ITool DeriveTool(string filename, DecryptArgs decryptArgs)
|
||||
{
|
||||
FileType type = DetermineFileType(filename);
|
||||
switch(type)
|
||||
{
|
||||
case FileType.NDS:
|
||||
Console.WriteLine("File recognized as Nintendo DS");
|
||||
return new DSTool(filename, decryptArgs);
|
||||
case FileType.NDSi:
|
||||
Console.WriteLine("File recognized as Nintendo DS");
|
||||
return new DSTool(filename, decryptArgs);
|
||||
case FileType.iQueDS:
|
||||
Console.WriteLine("File recognized as iQue DS");
|
||||
return new DSTool(filename, decryptArgs);
|
||||
case FileType.N3DS:
|
||||
Console.WriteLine("File recognized as Nintendo 3DS");
|
||||
return new ThreeDSTool(filename, decryptArgs);
|
||||
case FileType.N3DSCIA:
|
||||
Console.WriteLine("File recognized as Nintendo 3DS CIA [CAUTION: NOT WORKING CURRENTLY]");
|
||||
return new CIATool(filename, decryptArgs);
|
||||
case FileType.NULL:
|
||||
default:
|
||||
Console.WriteLine($"Unrecognized file format for {filename}. Expected *.nds, *.srl, *.dsi, *.3ds");
|
||||
return 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(".srl", StringComparison.OrdinalIgnoreCase)) // Development carts/images
|
||||
return FileType.NDS;
|
||||
|
||||
else if (filename.EndsWith(".dsi", StringComparison.OrdinalIgnoreCase))
|
||||
return FileType.NDSi;
|
||||
|
||||
else if (filename.EndsWith(".ids", StringComparison.OrdinalIgnoreCase))
|
||||
return FileType.iQueDS;
|
||||
|
||||
else if (filename.EndsWith(".3ds", StringComparison.OrdinalIgnoreCase))
|
||||
return FileType.N3DS;
|
||||
|
||||
else if (filename.EndsWith(".cia", StringComparison.OrdinalIgnoreCase))
|
||||
return FileType.N3DSCIA;
|
||||
|
||||
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))
|
||||
// Now verify that all other flags are valid
|
||||
if (!feature.ProcessArgs(args, 1))
|
||||
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))
|
||||
// If inputs are required
|
||||
if (feature.RequiresInputs && !feature.VerifyInputs())
|
||||
{
|
||||
sw.WriteLine(hashString);
|
||||
commandSet.OutputFeatureHelp(topLevel.Name);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
// Now execute the current feature
|
||||
if (!feature.Execute())
|
||||
{
|
||||
Console.Error.WriteLine("An error occurred during processing!");
|
||||
commandSet.OutputFeatureHelp(topLevel.Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the command set for the program
|
||||
/// </summary>
|
||||
private static CommandSet CreateCommands()
|
||||
{
|
||||
List<string> header = [
|
||||
"Cart Image Encrypt/Decrypt Tool",
|
||||
string.Empty,
|
||||
"NDecrypt <operation> [options] <path> ...",
|
||||
string.Empty,
|
||||
];
|
||||
|
||||
List<string> footer = [
|
||||
string.Empty,
|
||||
"<path> can be any file or folder that contains uncompressed items.",
|
||||
"More than one path can be specified at a time.",
|
||||
];
|
||||
|
||||
var commandSet = new CommandSet(header, footer);
|
||||
|
||||
commandSet.Add(new Help());
|
||||
commandSet.Add(new EncryptFeature());
|
||||
commandSet.Add(new DecryptFeature());
|
||||
commandSet.Add(new InfoFeature());
|
||||
|
||||
return commandSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
91
README.md
91
README.md
@@ -1,6 +1,6 @@
|
||||
# NDecrypt
|
||||
|
||||
[](https://ci.appveyor.com/project/mnadareski/ndecrypt)
|
||||
[](https://github.com/SabreTools/NDecrypt/actions/workflows/build_and_test.yml)
|
||||
|
||||
A simple tool for simple people.
|
||||
|
||||
@@ -14,48 +14,79 @@ This is a code port of 3 different programs:
|
||||
|
||||
## No really, what is this?
|
||||
|
||||
This tool allows you to encrypt and decrypt your personally dumped NDS and N3DS roms with minimal hassle. The only caveat right now is that you need a `keys.bin` file for your personally obtained encryption keys.
|
||||
This tool allows you to encrypt and decrypt your personally dumped Nintendo DS, 3DS, and New 3DS cart images with minimal hassle.
|
||||
|
||||
## 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> ...
|
||||
Usage: NDecrypt <operation> [flags] <path> ...
|
||||
|
||||
Possible values for <operation>:
|
||||
e, encrypt - Encrypt the input files
|
||||
d, decrypt - Decrypt the input files
|
||||
Possible values for <operation>:
|
||||
e, encrypt - Encrypt the input files
|
||||
d, decrypt - Decrypt the input files
|
||||
i, info - Output file information
|
||||
|
||||
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
|
||||
Possible values for [flags] (one or more can be used):
|
||||
-?, -h, --help Display this help text and quit
|
||||
-c, --config <path> Path to config.json
|
||||
-d, --development Enable using development keys, if available
|
||||
-f, --force Force operation by avoiding sanity checks
|
||||
--hash Output size and hashes to a companion file
|
||||
|
||||
<path> can be any file or folder that contains uncompressed items.
|
||||
More than one path can be specified at a time.
|
||||
<path> can be any file or folder that contains uncompressed items.
|
||||
More than one path can be specified at a time.
|
||||
|
||||
### Additional Notes
|
||||
|
||||
**Note:** This overwrites the input files, so make backups if you're working on your original, personal dumps.
|
||||
- Input files are overwritten, even if they are only partially processed. You should make backups of the files you're working on if you're worried about this.
|
||||
- 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.
|
||||
- Required files will automatically be searched for in the application runtime directory as well as `%HOME%/.config/ndecrypt`, also known as `%USERPROFILE%\.config\ndecrypt` on Windows.
|
||||
|
||||
**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
|
||||
|
||||
## I feel like something is missing...
|
||||
There is a major file that you can use to give NDecrypt that extra _oomph_ of functionality that it really needs. That is, you can't do any encryption or decryption without it present. I can't give you the files and I can't generate them for you on the fly with the correct values. Keys are a thorny thing and I just do not want to deal with them. Values are validated, at least, but you'll only get yelled at on run if one of them is wrong. Don't worry, they're just disabled, not removed.
|
||||
|
||||
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:
|
||||
This convenient table gives an overview of mappings between the current `config.json` type along with the 2 formerly-supported types and a completely unsupported but common type.
|
||||
|
||||
- Hardware constant
|
||||
- KeyX0x18 / slot0x18KeyX
|
||||
- KeyX0x1B / slot0x1BKeyX
|
||||
- KeyX0x25 / slot0x25KeyX
|
||||
- KeyX0x2C / slot0x2CKeyX
|
||||
- DevKeyX0x18
|
||||
- DevKeyX0x1B
|
||||
- DevKeyX0x25
|
||||
- DevKeyX0x2C
|
||||
| `config.json` | `keys.bin` order | `aes_keys.txt` | rom-properties `keys.conf` |
|
||||
| --- | --- | --- | --- |
|
||||
| `NitroEncryptionData` | **N/A** | **UNMAPPED** | **UNMAPPED** |
|
||||
| `AESHardwareConstant` | 1 | `generator` | `ctr-scrambler` |
|
||||
| `KeyX0x18` | 2 | `slot0x18KeyX` | `ctr-Slot0x18KeyX` |
|
||||
| `KeyX0x1B` | 3 | `slot0x1BKeyX` | `ctr-Slot0x1BKeyX` |
|
||||
| `KeyX0x25` | 4 | `slot0x25KeyX` | `ctr-Slot0x25KeyX` |
|
||||
| `KeyX0x2C` | 5 | `slot0x2CKeyX` | `ctr-Slot0x2CKeyX` |
|
||||
| `DevKeyX0x18` | 6 | **UNMAPPED** | `ctr-dev-Slot0x18KeyX` |
|
||||
| `DevKeyX0x1B` | 7 | **UNMAPPED** | `ctr-dev-Slot0x1BKeyX` |
|
||||
| `DevKeyX0x25` | 8 | **UNMAPPED** | `ctr-dev-Slot0x25KeyX` |
|
||||
| `DevKeyX0x2C` | 9 | **UNMAPPED** | `ctr-dev-Slot0x2CKeyX` |
|
||||
|
||||
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.
|
||||
**Note:** `Dev*` keys are not required for the vast majority of normal operations. They're only used if the `-d` option is included. Working with your own retail carts will pretty much never require these, so don't drive yourself silly dealing with them.
|
||||
|
||||
*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.
|
||||
**Note:** The `NitroEncryptionData` field is also known as the "Blowfish table" for Nintendo DS carts. It's stored in the same hex string format as the other keys. There's some complicated stuff about how it's used and where it's stored, but all you need to know is that it is required.
|
||||
|
||||
**Community Note:** If you have used previous versions of NDecrypt and already have either `keys.bin` or `aes_keys.txt`, consider using [this helpful community-made script](https://gist.github.com/Dimensional/82f212a0b35bcf9caaa2bc9a70b3a92a) to make your life a bit easier. It will convert them into the new `config.json` format that will be supported from here on out.
|
||||
|
||||
### `config.json`
|
||||
|
||||
The up-and-coming, shiny, new, exciting, JSON-based format for storing the encryption keys that you need for Nintendo DS, 3DS, and New 3DS. This JSON file is not generated by anything, but maps pretty much one-to-one with the code inside of NDecrypt, making it super convenient to use. Keys provided need to be hex strings (e.g. `"AABBCCDD"`). Any keys that are left with `null` or `""` as the value will be ignored. See [the sample config](https://github.com/SabreTools/NDecrypt/blob/master/config-default.json) that I've nicely generated for you. You're welcome.
|
||||
|
||||
In the future, this file will be automatically generated on first run along with some cutesy little message telling you to fill it out when you get a chance. It's not doing it right now because I don't want to confuse users. Including those reading this. How meta.
|
||||
|
||||
### `keys.bin` (Deprecated)
|
||||
|
||||
This is the OG of NDecrypt key file formats. It's a weird, binary blob of a format that is composed of little-endian values (most common extraction methods produce big endian, so keep that in mind). It's only compatible wtih Nintendo 3DS and New 3DS keys and is incredibly inflexible in its layout. The little-endianness of it is a relic of how keys were handled in-code previously and I really can't fix it now. If you don't have a key, it needs to be filled with `0x00` bytes so it doesn't mess up the read. Yeah.
|
||||
|
||||
Oddly, this gets confused with some similar format that GodMode9 works with, but it has nothing to do with it. If you try to use one of those files in place of this one, something will probably break. It wasn't intentional, I just didn't look ahead of time. See the table in the main part of this section for the order the keys need to be stored in.
|
||||
|
||||
### `aes_keys.txt` (Deprecated)
|
||||
|
||||
This is an INI-based format that was super popular among 3DS emulators and probably still is. Weird thing, I know, but just roll with it please.
|
||||
|
||||
## But does it work?
|
||||
|
||||
@@ -74,4 +105,4 @@ I'd like to thank the developers of the original programs for doing the actual h
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
61
appveyor.yml
61
appveyor.yml
@@ -1,61 +0,0 @@
|
||||
# version format
|
||||
version: 0.2.5-{build}
|
||||
|
||||
# pull request template
|
||||
pull_requests:
|
||||
do_not_increment_build_number: true
|
||||
|
||||
# vm template
|
||||
image: Visual Studio 2022
|
||||
|
||||
# msbuild configuration
|
||||
configuration:
|
||||
- Debug
|
||||
|
||||
# install dependencies
|
||||
install:
|
||||
- cd %APPVEYOR_BUILD_FOLDER%
|
||||
- git submodule update --init --recursive
|
||||
|
||||
# build step
|
||||
build_script:
|
||||
- cmd: dotnet restore
|
||||
- cmd: dotnet build NDecrypt\NDecrypt.csproj --framework net48
|
||||
- cmd: dotnet publish NDecrypt\NDecrypt.csproj --framework net6.0 --runtime win-x86 --self-contained true -p:PublishSingleFile=true
|
||||
- cmd: dotnet publish NDecrypt\NDecrypt.csproj --framework net6.0 --runtime win-x64 --self-contained true -p:PublishSingleFile=true
|
||||
- cmd: dotnet publish NDecrypt\NDecrypt.csproj --framework net6.0 --runtime linux-x64 --self-contained true -p:PublishSingleFile=true
|
||||
- cmd: dotnet publish NDecrypt\NDecrypt.csproj --framework net6.0 --runtime osx-x64 --self-contained true -p:PublishSingleFile=true
|
||||
|
||||
# post-build script
|
||||
after_build:
|
||||
- cmd: cd %APPVEYOR_BUILD_FOLDER%\NDecrypt\bin\Debug\net48
|
||||
- cmd: 7z a -tzip %APPVEYOR_BUILD_FOLDER%\NDecrypt-%APPVEYOR_REPO_COMMIT%_net48.zip *
|
||||
- cmd: cd %APPVEYOR_BUILD_FOLDER%\NDecrypt\bin\Debug\net6.0\win-x86\publish\
|
||||
- cmd: 7z a -tzip %APPVEYOR_BUILD_FOLDER%\NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_win-x86.zip *
|
||||
- cmd: cd %APPVEYOR_BUILD_FOLDER%\NDecrypt\bin\Debug\net6.0\win-x64\publish\
|
||||
- cmd: 7z a -tzip %APPVEYOR_BUILD_FOLDER%\NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_win-x64.zip *
|
||||
- cmd: cd %APPVEYOR_BUILD_FOLDER%\NDecrypt\bin\Debug\net6.0\linux-x64\publish\
|
||||
- cmd: 7z a -tzip %APPVEYOR_BUILD_FOLDER%\NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_linux-x64.zip *
|
||||
- cmd: cd %APPVEYOR_BUILD_FOLDER%\NDecrypt\bin\Debug\net6.0\osx-x64\publish\
|
||||
- cmd: 7z a -tzip %APPVEYOR_BUILD_FOLDER%\NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_osx-x64.zip *
|
||||
|
||||
# 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
|
||||
|
||||
# artifact linking
|
||||
artifacts:
|
||||
- path: NDecrypt-%APPVEYOR_REPO_COMMIT%_net48.zip
|
||||
name: NDecrypt (.NET Framework 4.8)
|
||||
- path: NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_win-x86.zip
|
||||
name: NDecrypt (.NET 6.0, Windows x86)
|
||||
- path: NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_win-x64.zip
|
||||
name: NDecrypt (.NET 6.0, Windows x64)
|
||||
- path: NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_linux-x64.zip
|
||||
name: NDecrypt (.NET 6.0, Linux x64)
|
||||
- path: NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_osx-x64.zip
|
||||
name: NDecrypt (.NET 6.0, OSX x64)
|
||||
12
config-default.json
Normal file
12
config-default.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"NitroEncryptionData": null,
|
||||
"AESHardwareConstant": null,
|
||||
"KeyX0x18": null,
|
||||
"KeyX0x1B": null,
|
||||
"KeyX0x25": null,
|
||||
"KeyX0x2C": null,
|
||||
"DevKeyX0x18": null,
|
||||
"DevKeyX0x1B": null,
|
||||
"DevKeyX0x25": null,
|
||||
"DevKeyX0x2C": null
|
||||
}
|
||||
151
publish-nix.sh
Executable file
151
publish-nix.sh
Executable file
@@ -0,0 +1,151 @@
|
||||
#!/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
|
||||
INCLUDE_DEBUG=false
|
||||
NO_BUILD=false
|
||||
NO_ARCHIVE=false
|
||||
while getopts "udba" OPTION; do
|
||||
case $OPTION in
|
||||
u)
|
||||
USE_ALL=true
|
||||
;;
|
||||
d)
|
||||
INCLUDE_DEBUG=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 " Include debug builds (-d) $INCLUDE_DEBUG"
|
||||
echo " No build (-b) $NO_BUILD"
|
||||
echo " No archive (-a) $NO_ARCHIVE"
|
||||
echo " "
|
||||
|
||||
# Create the build matrix arrays
|
||||
FRAMEWORKS=("net10.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=("net20" "net35" "net40" "net452" "net462" "net472" "net48" "netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0" "net9.0" "net10.0")
|
||||
fi
|
||||
|
||||
# Create the filter arrays
|
||||
SINGLE_FILE_CAPABLE=("net5.0" "net6.0" "net7.0" "net8.0" "net9.0" "net10.0")
|
||||
VALID_APPLE_FRAMEWORKS=("net6.0" "net7.0" "net8.0" "net9.0" "net10.0")
|
||||
VALID_CROSS_PLATFORM_FRAMEWORKS=("netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0" "net9.0" "net10.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
|
||||
|
||||
# Create Nuget Packages
|
||||
dotnet pack NDecrypt.Core/NDecrypt.Core.csproj --output $BUILD_FOLDER
|
||||
|
||||
# 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 set
|
||||
if [ $INCLUDE_DEBUG = 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 set
|
||||
if [ $INCLUDE_DEBUG = 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 set
|
||||
if [ $INCLUDE_DEBUG = 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
|
||||
136
publish-win.ps1
Normal file
136
publish-win.ps1
Normal file
@@ -0,0 +1,136 @@
|
||||
# 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("IncludeDebug")]
|
||||
[switch]$INCLUDE_DEBUG,
|
||||
|
||||
[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 " Include debug builds (-IncludeDebug) $INCLUDE_DEBUG"
|
||||
Write-Host " No build (-NoBuild) $NO_BUILD"
|
||||
Write-Host " No archive (-NoArchive) $NO_ARCHIVE"
|
||||
Write-Host " "
|
||||
|
||||
# Create the build matrix arrays
|
||||
$FRAMEWORKS = @('net10.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 = @('net20', 'net35', 'net40', 'net452', 'net462', 'net472', 'net48', 'netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0', 'net9.0', 'net10.0')
|
||||
}
|
||||
|
||||
# Create the filter arrays
|
||||
$SINGLE_FILE_CAPABLE = @('net5.0', 'net6.0', 'net7.0', 'net8.0', 'net9.0', 'net10.0')
|
||||
$VALID_APPLE_FRAMEWORKS = @('net6.0', 'net7.0', 'net8.0', 'net9.0', 'net10.0')
|
||||
$VALID_CROSS_PLATFORM_FRAMEWORKS = @('netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0', 'net9.0', 'net10.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
|
||||
|
||||
# Create Nuget Package
|
||||
dotnet pack NDecrypt.Core\NDecrypt.Core.csproj --output $BUILD_FOLDER
|
||||
|
||||
# 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 set
|
||||
if ($INCLUDE_DEBUG.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 set
|
||||
if ($INCLUDE_DEBUG.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 set
|
||||
if ($INCLUDE_DEBUG.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