mirror of
https://github.com/SabreTools/NDecrypt.git
synced 2026-02-05 13:49:41 +00:00
Compare commits
22 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 |
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
|
||||
74
.github/workflows/build_and_test.yml
vendored
74
.github/workflows/build_and_test.yml
vendored
@@ -1,40 +1,48 @@
|
||||
name: Build and Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
push:
|
||||
branches: ["master"]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: |
|
||||
6.0.x
|
||||
8.0.x
|
||||
9.0.x
|
||||
|
||||
- name: Run tests
|
||||
run: dotnet test
|
||||
|
||||
- name: Run publish script
|
||||
run: ./publish-nix.sh -d
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.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
|
||||
- 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
|
||||
|
||||
30
.github/workflows/check_pr.yml
vendored
30
.github/workflows/check_pr.yml
vendored
@@ -3,18 +3,18 @@ name: Build PR
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: |
|
||||
6.0.x
|
||||
8.0.x
|
||||
9.0.x
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
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/net9.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
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
public static class CommonOperations
|
||||
{
|
||||
#region AES
|
||||
|
||||
/// <summary>
|
||||
/// Create AES decryption cipher and intialize
|
||||
/// </summary>
|
||||
/// <param name="key">Byte array representation of 128-bit encryption key</param>
|
||||
/// <param name="iv">AES initial value for counter</param>
|
||||
/// <returns>Initialized AES cipher</returns>
|
||||
public static IBufferedCipher CreateAESDecryptionCipher(byte[] key, byte[] iv)
|
||||
{
|
||||
if (key.Length != 16)
|
||||
throw new ArgumentOutOfRangeException(nameof(key));
|
||||
|
||||
var keyParam = new KeyParameter(key);
|
||||
var cipher = CipherUtilities.GetCipher("AES/CTR");
|
||||
cipher.Init(forEncryption: false, new ParametersWithIV(keyParam, iv));
|
||||
return cipher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create AES encryption cipher and intialize
|
||||
/// </summary>
|
||||
/// <param name="key">Byte array representation of 128-bit encryption key</param>
|
||||
/// <param name="iv">AES initial value for counter</param>
|
||||
/// <returns>Initialized AES cipher</returns>
|
||||
public static IBufferedCipher CreateAESEncryptionCipher(byte[] key, byte[] iv)
|
||||
{
|
||||
if (key.Length != 16)
|
||||
throw new ArgumentOutOfRangeException(nameof(key));
|
||||
|
||||
var keyParam = new KeyParameter(key);
|
||||
var cipher = CipherUtilities.GetCipher("AES/CTR");
|
||||
cipher.Init(forEncryption: true, new ParametersWithIV(keyParam, iv));
|
||||
return cipher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform an AES operation using an existing cipher
|
||||
/// </summary>
|
||||
public static void PerformAESOperation(uint size,
|
||||
IBufferedCipher cipher,
|
||||
Stream input,
|
||||
Stream output,
|
||||
Action<string>? progress)
|
||||
{
|
||||
// Get MiB-aligned block count and extra byte count
|
||||
int blockCount = (int)((long)size / (1024 * 1024));
|
||||
int extraBytes = (int)((long)size % (1024 * 1024));
|
||||
|
||||
// Process MiB-aligned data
|
||||
if (blockCount > 0)
|
||||
{
|
||||
for (int i = 0; i < blockCount; i++)
|
||||
{
|
||||
byte[] readBytes = input.ReadBytes(1024 * 1024);
|
||||
byte[] processedBytes = cipher.ProcessBytes(readBytes);
|
||||
output.Write(processedBytes);
|
||||
output.Flush();
|
||||
progress?.Invoke($"{i} / {blockCount + 1} MB");
|
||||
}
|
||||
}
|
||||
|
||||
// Process additional data
|
||||
if (extraBytes > 0)
|
||||
{
|
||||
byte[] readBytes = input.ReadBytes(extraBytes);
|
||||
byte[] finalBytes = cipher.DoFinal(readBytes);
|
||||
output.Write(finalBytes);
|
||||
output.Flush();
|
||||
}
|
||||
|
||||
progress?.Invoke($"{blockCount + 1} / {blockCount + 1} MB... Done!\r\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform an AES operation using two existing ciphers
|
||||
/// </summary>
|
||||
public static void PerformAESOperation(uint size,
|
||||
IBufferedCipher firstCipher,
|
||||
IBufferedCipher secondCipher,
|
||||
Stream input,
|
||||
Stream output,
|
||||
Action<string> progress)
|
||||
{
|
||||
// Get MiB-aligned block count and extra byte count
|
||||
int blockCount = (int)((long)size / (1024 * 1024));
|
||||
int extraBytes = (int)((long)size % (1024 * 1024));
|
||||
|
||||
// Process MiB-aligned data
|
||||
if (blockCount > 0)
|
||||
{
|
||||
for (int i = 0; i < blockCount; i++)
|
||||
{
|
||||
byte[] readBytes = input.ReadBytes(1024 * 1024);
|
||||
byte[] firstProcessedBytes = firstCipher.ProcessBytes(readBytes);
|
||||
byte[] secondProcessedBytes = secondCipher.ProcessBytes(firstProcessedBytes);
|
||||
output.Write(secondProcessedBytes);
|
||||
output.Flush();
|
||||
progress($"{i} / {blockCount + 1} MB");
|
||||
}
|
||||
}
|
||||
|
||||
// Process additional data
|
||||
if (extraBytes > 0)
|
||||
{
|
||||
byte[] readBytes = input.ReadBytes(extraBytes);
|
||||
byte[] firstFinalBytes = firstCipher.DoFinal(readBytes);
|
||||
byte[] secondFinalBytes = secondCipher.DoFinal(firstFinalBytes);
|
||||
output.Write(secondFinalBytes);
|
||||
output.Flush();
|
||||
}
|
||||
|
||||
progress($"{blockCount + 1} / {blockCount + 1} MB... Done!\r\n");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Byte Arrays
|
||||
|
||||
/// <summary>
|
||||
/// Add an integer value to a number represented by a byte array
|
||||
/// </summary>
|
||||
/// <param name="input">Byte array to add to</param>
|
||||
/// <param name="add">Amount to add</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
public static byte[] Add(byte[] input, uint add)
|
||||
{
|
||||
byte[] addBytes = BitConverter.GetBytes(add);
|
||||
Array.Reverse(addBytes);
|
||||
byte[] paddedBytes = new byte[16];
|
||||
Array.Copy(addBytes, 0, paddedBytes, 12, 4);
|
||||
return Add(input, paddedBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add two numbers represented by byte arrays
|
||||
/// </summary>
|
||||
/// <param name="left">Byte array to add to</param>
|
||||
/// <param name="right">Amount to add</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
public static byte[] Add(byte[] left, byte[] right)
|
||||
{
|
||||
int addBytes = Math.Min(left.Length, right.Length);
|
||||
int outLength = Math.Max(left.Length, right.Length);
|
||||
|
||||
byte[] output = new byte[outLength];
|
||||
|
||||
uint carry = 0;
|
||||
for (int i = addBytes - 1; i >= 0; i--)
|
||||
{
|
||||
uint addValue = (uint)(left[i] + right[i]) + carry;
|
||||
output[i] = (byte)addValue;
|
||||
carry = addValue >> 8;
|
||||
}
|
||||
|
||||
if (outLength != addBytes && left.Length == outLength)
|
||||
Array.Copy(left, addBytes, output, addBytes, outLength - addBytes);
|
||||
else if (outLength != addBytes && right.Length == outLength)
|
||||
Array.Copy(right, addBytes, output, addBytes, outLength - addBytes);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a rotate left on a byte array
|
||||
/// </summary>
|
||||
/// <param name="val">Byte array value to rotate</param>
|
||||
/// <param name="r_bits">Number of bits to rotate</param>
|
||||
/// <returns>Rotated byte array value</returns>
|
||||
public static byte[] RotateLeft(byte[] val, int r_bits)
|
||||
{
|
||||
byte[] output = new byte[val.Length];
|
||||
Array.Copy(val, output, output.Length);
|
||||
|
||||
// Shift by bytes
|
||||
while (r_bits >= 8)
|
||||
{
|
||||
byte temp = output[0];
|
||||
for (int i = 0; i < output.Length - 1; i++)
|
||||
{
|
||||
output[i] = output[i + 1];
|
||||
}
|
||||
|
||||
output[output.Length - 1] = temp;
|
||||
r_bits -= 8;
|
||||
}
|
||||
|
||||
// Shift by bits
|
||||
if (r_bits > 0)
|
||||
{
|
||||
byte bitMask = (byte)(8 - r_bits), carry, wrap = 0;
|
||||
for (int i = 0; i < output.Length; i++)
|
||||
{
|
||||
carry = (byte)((255 << bitMask & output[i]) >> bitMask);
|
||||
|
||||
// Make sure the first byte carries to the end
|
||||
if (i == 0)
|
||||
wrap = carry;
|
||||
|
||||
// Otherwise, move to the last byte
|
||||
else
|
||||
output[i - 1] |= carry;
|
||||
|
||||
// Shift the current bits
|
||||
output[i] <<= r_bits;
|
||||
}
|
||||
|
||||
// Make sure the wrap happens
|
||||
output[output.Length - 1] |= wrap;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// XOR two numbers represented by byte arrays
|
||||
/// </summary>
|
||||
/// <param name="left">Byte array to XOR to</param>
|
||||
/// <param name="right">Amount to XOR</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
public static byte[] Xor(byte[] left, byte[] right)
|
||||
{
|
||||
int xorBytes = Math.Min(left.Length, right.Length);
|
||||
int outLength = Math.Max(left.Length, right.Length);
|
||||
|
||||
byte[] output = new byte[outLength];
|
||||
for (int i = 0; i < xorBytes; i++)
|
||||
{
|
||||
output[i] = (byte)(left[i] ^ right[i]);
|
||||
}
|
||||
|
||||
if (outLength != xorBytes && left.Length == outLength)
|
||||
Array.Copy(left, xorBytes, output, xorBytes, outLength - xorBytes);
|
||||
else if (outLength != xorBytes && right.Length == outLength)
|
||||
Array.Copy(right, xorBytes, output, xorBytes, outLength - xorBytes);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -86,4 +86,4 @@ namespace NDecrypt.Core
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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
|
||||
@@ -26,7 +28,7 @@ namespace NDecrypt.Core
|
||||
try
|
||||
{
|
||||
// If the output is provided, copy the input file
|
||||
if (output != null)
|
||||
if (output is not null)
|
||||
File.Copy(input, output, overwrite: true);
|
||||
else
|
||||
output = input;
|
||||
@@ -36,14 +38,14 @@ namespace NDecrypt.Core
|
||||
|
||||
// Deserialize the cart information
|
||||
var nitro = Nitro.Create(reader);
|
||||
if (nitro == null)
|
||||
if (nitro is null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a DS or DSi Rom!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure the secure area was read
|
||||
if (nitro.SecureArea == null)
|
||||
if (nitro.SecureArea is null)
|
||||
{
|
||||
Console.WriteLine("Error: Invalid secure area!");
|
||||
return false;
|
||||
@@ -78,7 +80,7 @@ namespace NDecrypt.Core
|
||||
try
|
||||
{
|
||||
// If the output is provided, copy the input file
|
||||
if (output != null)
|
||||
if (output is not null)
|
||||
File.Copy(input, output, overwrite: true);
|
||||
else
|
||||
output = input;
|
||||
@@ -88,14 +90,14 @@ namespace NDecrypt.Core
|
||||
|
||||
// Deserialize the cart information
|
||||
var nitro = Nitro.Create(reader);
|
||||
if (nitro == null)
|
||||
if (nitro is null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a DS or DSi Rom!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure the secure area was read
|
||||
if (nitro.SecureArea == null)
|
||||
if (nitro.SecureArea is null)
|
||||
{
|
||||
Console.WriteLine("Error: Invalid secure area!");
|
||||
return false;
|
||||
@@ -134,7 +136,7 @@ namespace NDecrypt.Core
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = Nitro.Create(input);
|
||||
if (cart?.Model == null)
|
||||
if (cart?.Model is null)
|
||||
return "Error: Not a DS/DSi cart image!";
|
||||
|
||||
// Get a string builder for the status
|
||||
@@ -143,7 +145,7 @@ namespace NDecrypt.Core
|
||||
|
||||
// Get the encryption status
|
||||
bool? decrypted = cart.CheckIfDecrypted(out _);
|
||||
if (decrypted == null)
|
||||
if (decrypted is null)
|
||||
sb.Append("Empty");
|
||||
else if (decrypted == true)
|
||||
sb.Append("Decrypted");
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.IO.Encryption;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Matching;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
@@ -243,7 +244,7 @@ namespace NDecrypt.Core
|
||||
/// <param name="keyfile">Path to the keyfile</param>
|
||||
public DecryptArgs(string? config)
|
||||
{
|
||||
if (config == null || !File.Exists(config))
|
||||
if (config is null || !File.Exists(config))
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
@@ -251,7 +252,7 @@ namespace NDecrypt.Core
|
||||
|
||||
// Try to read the configuration file
|
||||
var configObj = Configuration.Create(config);
|
||||
if (configObj == null)
|
||||
if (configObj is null)
|
||||
{
|
||||
IsReady = false;
|
||||
return;
|
||||
@@ -282,9 +283,8 @@ namespace NDecrypt.Core
|
||||
// NitroEncryptionData
|
||||
if (NitroEncryptionData.Length > 0)
|
||||
{
|
||||
using var hasher = System.Security.Cryptography.SHA512.Create();
|
||||
byte[] actual = hasher.ComputeHash(NitroEncryptionData);
|
||||
if (!Extensions.EqualsExactly(ExpectedNitroSha512Hash, actual))
|
||||
byte[]? actual = HashTool.GetByteArrayHashArray(NitroEncryptionData, HashType.SHA512);
|
||||
if (actual is null || !actual.EqualsExactly(ExpectedNitroSha512Hash))
|
||||
{
|
||||
Console.WriteLine($"NitroEncryptionData invalid value, disabling...");
|
||||
NitroEncryptionData = [];
|
||||
@@ -294,9 +294,9 @@ namespace NDecrypt.Core
|
||||
// KeyX0x18
|
||||
if (KeyX0x18.Length > 0)
|
||||
{
|
||||
var cipher = CommonOperations.CreateAESEncryptionCipher(KeyX0x18, TestIV);
|
||||
var cipher = AESCTR.CreateEncryptionCipher(KeyX0x18, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!Extensions.EqualsExactly(ExpectedKeyX0x18, actual))
|
||||
if (!actual.EqualsExactly(ExpectedKeyX0x18))
|
||||
{
|
||||
Console.WriteLine($"KeyX0x18 invalid value, disabling...");
|
||||
KeyX0x18 = [];
|
||||
@@ -306,9 +306,9 @@ namespace NDecrypt.Core
|
||||
// DevKeyX0x18
|
||||
if (DevKeyX0x18.Length > 0)
|
||||
{
|
||||
var cipher = CommonOperations.CreateAESEncryptionCipher(DevKeyX0x18, TestIV);
|
||||
var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x18, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!Extensions.EqualsExactly(ExpectedDevKeyX0x18, actual))
|
||||
if (!actual.EqualsExactly(ExpectedDevKeyX0x18))
|
||||
{
|
||||
Console.WriteLine($"DevKeyX0x18 invalid value, disabling...");
|
||||
DevKeyX0x18 = [];
|
||||
@@ -318,9 +318,9 @@ namespace NDecrypt.Core
|
||||
// KeyX0x1B
|
||||
if (KeyX0x1B.Length > 0)
|
||||
{
|
||||
var cipher = CommonOperations.CreateAESEncryptionCipher(KeyX0x1B, TestIV);
|
||||
var cipher = AESCTR.CreateEncryptionCipher(KeyX0x1B, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!Extensions.EqualsExactly(ExpectedKeyX0x1B, actual))
|
||||
if (!actual.EqualsExactly(ExpectedKeyX0x1B))
|
||||
{
|
||||
Console.WriteLine($"KeyX0x1B invalid value, disabling...");
|
||||
KeyX0x1B = [];
|
||||
@@ -330,9 +330,9 @@ namespace NDecrypt.Core
|
||||
// DevKeyX0x1B
|
||||
if (DevKeyX0x1B.Length > 0)
|
||||
{
|
||||
var cipher = CommonOperations.CreateAESEncryptionCipher(DevKeyX0x1B, TestIV);
|
||||
var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x1B, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!Extensions.EqualsExactly(ExpectedDevKeyX0x1B, actual))
|
||||
if (!actual.EqualsExactly(ExpectedDevKeyX0x1B))
|
||||
{
|
||||
Console.WriteLine($"DevKeyX0x1B invalid value, disabling...");
|
||||
DevKeyX0x1B = [];
|
||||
@@ -342,9 +342,9 @@ namespace NDecrypt.Core
|
||||
// KeyX0x25
|
||||
if (KeyX0x25.Length > 0)
|
||||
{
|
||||
var cipher = CommonOperations.CreateAESEncryptionCipher(KeyX0x25, TestIV);
|
||||
var cipher = AESCTR.CreateEncryptionCipher(KeyX0x25, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!Extensions.EqualsExactly(ExpectedKeyX0x25, actual))
|
||||
if (!actual.EqualsExactly(ExpectedKeyX0x25))
|
||||
{
|
||||
Console.WriteLine($"KeyX0x25 invalid value, disabling...");
|
||||
KeyX0x25 = [];
|
||||
@@ -354,9 +354,9 @@ namespace NDecrypt.Core
|
||||
// DevKeyX0x25
|
||||
if (DevKeyX0x25.Length > 0)
|
||||
{
|
||||
var cipher = CommonOperations.CreateAESEncryptionCipher(DevKeyX0x25, TestIV);
|
||||
var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x25, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!Extensions.EqualsExactly(ExpectedDevKeyX0x25, actual))
|
||||
if (!actual.EqualsExactly(ExpectedDevKeyX0x25))
|
||||
{
|
||||
Console.WriteLine($"DevKeyX0x25 invalid value, disabling...");
|
||||
DevKeyX0x25 = [];
|
||||
@@ -366,9 +366,9 @@ namespace NDecrypt.Core
|
||||
// KeyX0x2C
|
||||
if (KeyX0x2C.Length > 0)
|
||||
{
|
||||
var cipher = CommonOperations.CreateAESEncryptionCipher(KeyX0x2C, TestIV);
|
||||
var cipher = AESCTR.CreateEncryptionCipher(KeyX0x2C, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!Extensions.EqualsExactly(ExpectedKeyX0x2C, actual))
|
||||
if (!actual.EqualsExactly(ExpectedKeyX0x2C))
|
||||
{
|
||||
Console.WriteLine($"KeyX0x2C invalid value, disabling...");
|
||||
KeyX0x2C = [];
|
||||
@@ -378,9 +378,9 @@ namespace NDecrypt.Core
|
||||
// DevKeyX0x2C
|
||||
if (DevKeyX0x2C.Length > 0)
|
||||
{
|
||||
var cipher = CommonOperations.CreateAESEncryptionCipher(DevKeyX0x2C, TestIV);
|
||||
var cipher = AESCTR.CreateEncryptionCipher(DevKeyX0x2C, TestIV);
|
||||
byte[] actual = cipher.ProcessBytes(TestPattern);
|
||||
if (!Extensions.EqualsExactly(ExpectedDevKeyX0x2C, actual))
|
||||
if (!actual.EqualsExactly(ExpectedDevKeyX0x2C))
|
||||
{
|
||||
Console.WriteLine($"DevKeyX0x2C invalid value, disabling...");
|
||||
DevKeyX0x2C = [];
|
||||
@@ -388,4 +388,4 @@ namespace NDecrypt.Core
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/// <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>
|
||||
bool EncryptFile(string input, string? output, bool force);
|
||||
public bool EncryptFile(string input, string? output, bool force);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to decrypt an input file
|
||||
@@ -20,13 +20,13 @@
|
||||
/// <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>
|
||||
bool DecryptFile(string input, string? output, bool force);
|
||||
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>
|
||||
string? GetInformation(string filename);
|
||||
public string? GetInformation(string filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<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>
|
||||
@@ -11,7 +11,7 @@
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>0.4.2</VersionPrefix>
|
||||
<VersionPrefix>0.5.0</VersionPrefix>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
@@ -23,27 +23,10 @@
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Support All Frameworks -->
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`)) OR $(TargetFramework.StartsWith(`net9`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.NetCore" Version="1.9.0" Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))" />
|
||||
<PackageReference Include="BouncyCastle.NetCore" Version="2.2.1" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`))" />
|
||||
<PackageReference Include="SabreTools.Hashing" Version="1.5.0" />
|
||||
<PackageReference Include="SabreTools.IO" Version="1.7.2" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.7.1" />
|
||||
<PackageReference Include="SabreTools.Serialization" Version="1.9.1" />
|
||||
<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>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using SabreTools.Models.N3DS;
|
||||
using static NDecrypt.Core.CommonOperations;
|
||||
using SabreTools.Data.Models.N3DS;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
@@ -43,8 +42,8 @@ namespace NDecrypt.Core
|
||||
// Validate inputs
|
||||
if (args.IsReady != true)
|
||||
throw new InvalidOperationException($"{nameof(args)} must be initialized before use");
|
||||
if (signature != null && signature.Length < 16)
|
||||
throw new DataLengthException($"{nameof(signature)} must be at least 16 bytes");
|
||||
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;
|
||||
@@ -56,16 +55,16 @@ namespace NDecrypt.Core
|
||||
|
||||
// Backup headers can't have a KeyY value set
|
||||
KeyY = new byte[16];
|
||||
if (signature != null)
|
||||
if (signature is not null)
|
||||
Array.Copy(signature, KeyY, 16);
|
||||
|
||||
// Set the standard normal key values
|
||||
NormalKey = new byte[16];
|
||||
|
||||
NormalKey2C = RotateLeft(KeyX2C, 2);
|
||||
NormalKey2C = Xor(NormalKey2C, KeyY);
|
||||
NormalKey2C = Add(NormalKey2C, args.AESHardwareConstant);
|
||||
NormalKey2C = RotateLeft(NormalKey2C, 87);
|
||||
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
|
||||
@@ -105,10 +104,10 @@ namespace NDecrypt.Core
|
||||
}
|
||||
|
||||
// Set the normal key based on the new KeyX value
|
||||
NormalKey = RotateLeft(KeyX, 2);
|
||||
NormalKey = Xor(NormalKey, KeyY);
|
||||
NormalKey = Add(NormalKey, args.AESHardwareConstant);
|
||||
NormalKey = RotateLeft(NormalKey, 87);
|
||||
NormalKey = KeyX.RotateLeft(2);
|
||||
NormalKey = NormalKey.Xor(KeyY);
|
||||
NormalKey = NormalKey.Add(args.AESHardwareConstant);
|
||||
NormalKey = NormalKey.RotateLeft(87);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -130,10 +129,10 @@ namespace NDecrypt.Core
|
||||
// Encrypting RomFS for partitions 1 and up always use Key0x2C
|
||||
KeyX = _development ? _decryptArgs.DevKeyX0x2C : _decryptArgs.KeyX0x2C;
|
||||
|
||||
NormalKey = RotateLeft(KeyX, 2);
|
||||
NormalKey = Xor(NormalKey, KeyY);
|
||||
NormalKey = Add(NormalKey, _decryptArgs.AESHardwareConstant);
|
||||
NormalKey = RotateLeft(NormalKey, 87);
|
||||
NormalKey = KeyX.RotateLeft(2);
|
||||
NormalKey = NormalKey.Xor(KeyY);
|
||||
NormalKey = NormalKey.Add(_decryptArgs.AESHardwareConstant);
|
||||
NormalKey = NormalKey.RotateLeft(87);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.Data.Models.N3DS;
|
||||
using SabreTools.IO.Encryption;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.N3DS;
|
||||
using SabreTools.Serialization.Wrappers;
|
||||
using static NDecrypt.Core.CommonOperations;
|
||||
using static SabreTools.Models.N3DS.Constants;
|
||||
using static SabreTools.Data.Models.N3DS.Constants;
|
||||
|
||||
namespace NDecrypt.Core
|
||||
{
|
||||
@@ -47,7 +47,7 @@ namespace NDecrypt.Core
|
||||
try
|
||||
{
|
||||
// If the output is provided, copy the input file
|
||||
if (output != null)
|
||||
if (output is not null)
|
||||
File.Copy(input, output, overwrite: true);
|
||||
else
|
||||
output = input;
|
||||
@@ -58,7 +58,7 @@ namespace NDecrypt.Core
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = N3DS.Create(reader);
|
||||
if (cart?.Model == null)
|
||||
if (cart?.Model is null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS cart image!");
|
||||
return false;
|
||||
@@ -86,7 +86,7 @@ namespace NDecrypt.Core
|
||||
private void DecryptAllPartitions(N3DS cart, bool force, Stream reader, Stream writer)
|
||||
{
|
||||
// Check the partitions table
|
||||
if (cart.PartitionsTable == null || cart.Partitions == null)
|
||||
if (cart.PartitionsTable is null || cart.Partitions is null)
|
||||
{
|
||||
Console.WriteLine("Invalid partitions table!");
|
||||
return;
|
||||
@@ -96,7 +96,7 @@ namespace NDecrypt.Core
|
||||
for (int p = 0; p < 8; p++)
|
||||
{
|
||||
var partition = cart.Partitions[p];
|
||||
if (partition == null || partition.MagicID != NCCHMagicNumber)
|
||||
if (partition is null || partition.MagicID != NCCHMagicNumber)
|
||||
{
|
||||
Console.WriteLine($"Partition {p} Not found... Skipping...");
|
||||
continue;
|
||||
@@ -104,7 +104,7 @@ namespace NDecrypt.Core
|
||||
|
||||
// Check the partition has data
|
||||
var partitionEntry = cart.PartitionsTable[p];
|
||||
if (partitionEntry == null || partitionEntry.Length == 0)
|
||||
if (partitionEntry is null || partitionEntry.Length == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {p} No data... Skipping...");
|
||||
continue;
|
||||
@@ -168,7 +168,7 @@ namespace NDecrypt.Core
|
||||
{
|
||||
// Get the partition
|
||||
var partition = cart.Partitions?[index];
|
||||
if (partition?.Flags == null)
|
||||
if (partition?.Flags is null)
|
||||
return;
|
||||
|
||||
// Get partition-specific values
|
||||
@@ -211,10 +211,10 @@ namespace NDecrypt.Core
|
||||
Console.WriteLine($"Partition {index}: Decrypting - ExHeader");
|
||||
|
||||
// Create the Plain AES cipher for this partition
|
||||
var cipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey2C, cart.PlainIV(index));
|
||||
var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, cart.PlainIV(index));
|
||||
|
||||
// Process the extended header
|
||||
PerformAESOperation(Constants.CXTExtendedDataHeaderLength, cipher, reader, writer, null);
|
||||
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
|
||||
@@ -264,16 +264,16 @@ namespace NDecrypt.Core
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
uint ctroffsetE = cart.MediaUnitSize / 0x10;
|
||||
byte[] exefsIVWithOffset = Add(cart.ExeFSIV(index), ctroffsetE);
|
||||
var cipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffset);
|
||||
byte[] exefsIVWithOffset = cart.ExeFSIV(index).Add(ctroffsetE);
|
||||
var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffset);
|
||||
|
||||
// Setup and perform the decryption
|
||||
exeFsSize -= cart.MediaUnitSize;
|
||||
PerformAESOperation(exeFsSize,
|
||||
AESCTR.PerformOperation(exeFsSize,
|
||||
cipher,
|
||||
reader,
|
||||
writer,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting - {s}"));
|
||||
s => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting - {s}"));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -302,7 +302,7 @@ namespace NDecrypt.Core
|
||||
Console.WriteLine($"Partition {index} ExeFS: Decrypting - ExeFS Filename Table");
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
var cipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey2C, cart.ExeFSIV(index));
|
||||
var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey2C, cart.ExeFSIV(index));
|
||||
|
||||
// Process the filename table
|
||||
byte[] readBytes = reader.ReadBytes((int)cart.MediaUnitSize);
|
||||
@@ -325,7 +325,7 @@ namespace NDecrypt.Core
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private void DecryptExeFSFileEntries(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
if (cart.ExeFSHeaders == null || index < 0 || index > cart.ExeFSHeaders.Length)
|
||||
if (cart.ExeFSHeaders is null || index < 0 || index > cart.ExeFSHeaders.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return;
|
||||
@@ -334,11 +334,11 @@ namespace NDecrypt.Core
|
||||
// Reread the decrypted ExeFS header
|
||||
uint exeFsHeaderOffset = cart.GetExeFSOffset(index);
|
||||
reader.Seek(exeFsHeaderOffset, SeekOrigin.Begin);
|
||||
cart.ExeFSHeaders[index] = SabreTools.Serialization.Deserializers.N3DS.ParseExeFSHeader(reader);
|
||||
cart.ExeFSHeaders[index] = SabreTools.Serialization.Readers.N3DS.ParseExeFSHeader(reader);
|
||||
|
||||
// Get the ExeFS header
|
||||
var exeFsHeader = cart.ExeFSHeaders[index];
|
||||
if (exeFsHeader?.FileHeaders == null)
|
||||
if (exeFsHeader?.FileHeaders is null)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS header does not exist. Skipping...");
|
||||
return;
|
||||
@@ -356,26 +356,26 @@ namespace NDecrypt.Core
|
||||
|
||||
// Get the file header
|
||||
var fileHeader = exeFsHeader.FileHeaders[i];
|
||||
if (fileHeader == null)
|
||||
if (fileHeader is null)
|
||||
continue;
|
||||
|
||||
// Create the ExeFS AES ciphers for this partition
|
||||
uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize) / 0x10;
|
||||
byte[] exefsIVWithOffsetForHeader = Add(cart.ExeFSIV(index), ctroffset);
|
||||
var firstCipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey, exefsIVWithOffsetForHeader);
|
||||
var secondCipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffsetForHeader);
|
||||
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
|
||||
PerformAESOperation(fileHeader.FileSize,
|
||||
AESCTR.PerformOperation(fileHeader.FileSize,
|
||||
firstCipher,
|
||||
secondCipher,
|
||||
reader,
|
||||
writer,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting - {fileHeader.FileName}...{s}"));
|
||||
s => Console.WriteLine($"\rPartition {index} ExeFS: Decrypting - {fileHeader.FileName}...{s}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,14 +408,14 @@ namespace NDecrypt.Core
|
||||
writer.Seek(romFsOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the RomFS AES cipher for this partition
|
||||
var cipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey, cart.RomFSIV(index));
|
||||
var cipher = AESCTR.CreateDecryptionCipher(_keysMap[index].NormalKey, cart.RomFSIV(index));
|
||||
|
||||
// Setup and perform the decryption
|
||||
PerformAESOperation(romFsSize,
|
||||
AESCTR.PerformOperation(romFsSize,
|
||||
cipher,
|
||||
reader,
|
||||
writer,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} RomFS: Decrypting - {s}"));
|
||||
s => Console.WriteLine($"\rPartition {index} RomFS: Decrypting - {s}"));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -466,7 +466,7 @@ namespace NDecrypt.Core
|
||||
try
|
||||
{
|
||||
// If the output is provided, copy the input file
|
||||
if (output != null)
|
||||
if (output is not null)
|
||||
File.Copy(input, output, overwrite: true);
|
||||
else
|
||||
output = input;
|
||||
@@ -477,7 +477,7 @@ namespace NDecrypt.Core
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = N3DS.Create(reader);
|
||||
if (cart?.Model == null)
|
||||
if (cart?.Model is null)
|
||||
{
|
||||
Console.WriteLine("Error: Not a 3DS cart image!");
|
||||
return false;
|
||||
@@ -505,7 +505,7 @@ namespace NDecrypt.Core
|
||||
private void EncryptAllPartitions(N3DS cart, bool force, Stream reader, Stream writer)
|
||||
{
|
||||
// Check the partitions table
|
||||
if (cart.PartitionsTable == null || cart.Partitions == null)
|
||||
if (cart.PartitionsTable is null || cart.Partitions is null)
|
||||
{
|
||||
Console.WriteLine("Invalid partitions table!");
|
||||
return;
|
||||
@@ -516,7 +516,7 @@ namespace NDecrypt.Core
|
||||
{
|
||||
// Check the partition exists
|
||||
var partition = cart.Partitions[p];
|
||||
if (partition == null || partition.MagicID != NCCHMagicNumber)
|
||||
if (partition is null || partition.MagicID != NCCHMagicNumber)
|
||||
{
|
||||
Console.WriteLine($"Partition {p} Not found... Skipping...");
|
||||
continue;
|
||||
@@ -524,7 +524,7 @@ namespace NDecrypt.Core
|
||||
|
||||
// Check the partition has data
|
||||
var partitionEntry = cart.PartitionsTable[p];
|
||||
if (partitionEntry == null || partitionEntry.Length == 0)
|
||||
if (partitionEntry is null || partitionEntry.Length == 0)
|
||||
{
|
||||
Console.WriteLine($"Partition {p} No data... Skipping...");
|
||||
continue;
|
||||
@@ -588,12 +588,12 @@ namespace NDecrypt.Core
|
||||
{
|
||||
// Get the partition
|
||||
var partition = cart.Partitions?[index];
|
||||
if (partition == null)
|
||||
if (partition is null)
|
||||
return;
|
||||
|
||||
// Get the backup header
|
||||
var backupHeader = cart.BackupHeader;
|
||||
if (backupHeader?.Flags == null)
|
||||
if (backupHeader?.Flags is null)
|
||||
return;
|
||||
|
||||
// Get partition-specific values
|
||||
@@ -636,10 +636,10 @@ namespace NDecrypt.Core
|
||||
Console.WriteLine($"Partition {index}: Encrypting - ExHeader");
|
||||
|
||||
// Create the Plain AES cipher for this partition
|
||||
var cipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey2C, cart.PlainIV(index));
|
||||
var cipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey2C, cart.PlainIV(index));
|
||||
|
||||
// Process the extended header
|
||||
PerformAESOperation(Constants.CXTExtendedDataHeaderLength, cipher, reader, writer, null);
|
||||
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
|
||||
@@ -658,7 +658,7 @@ namespace NDecrypt.Core
|
||||
/// <param name="writer">Stream representing the output</param>
|
||||
private bool EncryptExeFS(N3DS cart, int index, Stream reader, Stream writer)
|
||||
{
|
||||
if (cart.ExeFSHeaders == null || index < 0 || index > cart.ExeFSHeaders.Length)
|
||||
if (cart.ExeFSHeaders is null || index < 0 || index > cart.ExeFSHeaders.Length)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS: No Data... Skipping...");
|
||||
return false;
|
||||
@@ -666,7 +666,7 @@ namespace NDecrypt.Core
|
||||
|
||||
// Get the ExeFS header
|
||||
var exefsHeader = cart.ExeFSHeaders[index];
|
||||
if (exefsHeader == null)
|
||||
if (exefsHeader is null)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS header does not exist. Skipping...");
|
||||
return false;
|
||||
@@ -690,16 +690,16 @@ namespace NDecrypt.Core
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
uint ctroffsetE = cart.MediaUnitSize / 0x10;
|
||||
byte[] exefsIVWithOffset = Add(cart.ExeFSIV(index), ctroffsetE);
|
||||
var cipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffset);
|
||||
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;
|
||||
PerformAESOperation(exeFsSize,
|
||||
AESCTR.PerformOperation(exeFsSize,
|
||||
cipher,
|
||||
reader,
|
||||
writer,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting - {s}"));
|
||||
s => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting - {s}"));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -728,7 +728,7 @@ namespace NDecrypt.Core
|
||||
Console.WriteLine($"Partition {index} ExeFS: Encrypting - ExeFS Filename Table");
|
||||
|
||||
// Create the ExeFS AES cipher for this partition
|
||||
var cipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey2C, cart.ExeFSIV(index));
|
||||
var cipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey2C, cart.ExeFSIV(index));
|
||||
|
||||
// Process the filename table
|
||||
byte[] readBytes = reader.ReadBytes((int)cart.MediaUnitSize);
|
||||
@@ -764,7 +764,7 @@ namespace NDecrypt.Core
|
||||
|
||||
// If the header failed to read, log and return
|
||||
var exeFsHeader = cart.ExeFSHeaders?[index];
|
||||
if (exeFsHeader?.FileHeaders == null)
|
||||
if (exeFsHeader?.FileHeaders is null)
|
||||
{
|
||||
Console.WriteLine($"Partition {index} ExeFS header does not exist. Skipping...");
|
||||
return;
|
||||
@@ -779,26 +779,26 @@ namespace NDecrypt.Core
|
||||
|
||||
// Get the file header
|
||||
var fileHeader = exeFsHeader.FileHeaders[i];
|
||||
if (fileHeader == null)
|
||||
if (fileHeader is null)
|
||||
continue;
|
||||
|
||||
// Create the ExeFS AES ciphers for this partition
|
||||
uint ctroffset = (fileHeader.FileOffset + cart.MediaUnitSize) / 0x10;
|
||||
byte[] exefsIVWithOffsetForHeader = Add(cart.ExeFSIV(index), ctroffset);
|
||||
var firstCipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey, exefsIVWithOffsetForHeader);
|
||||
var secondCipher = CreateAESDecryptionCipher(_keysMap[index].NormalKey2C, exefsIVWithOffsetForHeader);
|
||||
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
|
||||
PerformAESOperation(fileHeader.FileSize,
|
||||
AESCTR.PerformOperation(fileHeader.FileSize,
|
||||
firstCipher,
|
||||
secondCipher,
|
||||
reader,
|
||||
writer,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting - {fileHeader.FileName}...{s}"));
|
||||
s => Console.WriteLine($"\rPartition {index} ExeFS: Encrypting - {fileHeader.FileName}...{s}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -838,14 +838,14 @@ namespace NDecrypt.Core
|
||||
}
|
||||
|
||||
// Create the RomFS AES cipher for this partition
|
||||
var cipher = CreateAESEncryptionCipher(_keysMap[index].NormalKey, cart.RomFSIV(index));
|
||||
var cipher = AESCTR.CreateEncryptionCipher(_keysMap[index].NormalKey, cart.RomFSIV(index));
|
||||
|
||||
// Setup and perform the decryption
|
||||
PerformAESOperation(romFsSize,
|
||||
AESCTR.PerformOperation(romFsSize,
|
||||
cipher,
|
||||
reader,
|
||||
writer,
|
||||
(string s) => Console.WriteLine($"\rPartition {index} RomFS: Encrypting - {s}"));
|
||||
s => Console.WriteLine($"\rPartition {index} RomFS: Encrypting - {s}"));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -863,7 +863,7 @@ namespace NDecrypt.Core
|
||||
|
||||
// Get the backup header
|
||||
var backupHeader = cart.BackupHeader;
|
||||
if (backupHeader?.Flags == null)
|
||||
if (backupHeader?.Flags is null)
|
||||
return;
|
||||
|
||||
// Seek to the CryptoMethod location
|
||||
@@ -901,7 +901,7 @@ namespace NDecrypt.Core
|
||||
|
||||
// Deserialize the cart information
|
||||
var cart = N3DS.Create(input);
|
||||
if (cart?.Model == null)
|
||||
if (cart?.Model is null)
|
||||
return "Error: Not a 3DS cart image!";
|
||||
|
||||
// Get a string builder for the status
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
namespace NDecrypt
|
||||
{
|
||||
/// <summary>
|
||||
/// Functionality to use from the program
|
||||
/// </summary>
|
||||
internal enum Feature
|
||||
{
|
||||
NULL,
|
||||
Decrypt,
|
||||
Encrypt,
|
||||
Info,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of the detected file
|
||||
/// </summary>
|
||||
@@ -23,4 +12,4 @@ namespace NDecrypt
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,15 +19,15 @@ namespace NDecrypt
|
||||
// Get the file information, if possible
|
||||
HashType[] hashTypes = [HashType.CRC32, HashType.MD5, HashType.SHA1, HashType.SHA256];
|
||||
var hashDict = HashTool.GetFileHashesAndSize(input, hashTypes, out long size);
|
||||
if (hashDict == null)
|
||||
if (hashDict is null)
|
||||
return null;
|
||||
|
||||
// Get the results
|
||||
return $"Size: {size}\n"
|
||||
+ $"CRC-32: {(hashDict.ContainsKey(HashType.CRC32) ? hashDict[HashType.CRC32] : string.Empty)}\n"
|
||||
+ $"MD5: {(hashDict.ContainsKey(HashType.MD5) ? hashDict[HashType.MD5] : string.Empty)}\n"
|
||||
+ $"SHA-1: {(hashDict.ContainsKey(HashType.SHA1) ? hashDict[HashType.SHA1] : string.Empty)}\n"
|
||||
+ $"CSHA-256: {(hashDict.ContainsKey(HashType.SHA256) ? hashDict[HashType.SHA256] : string.Empty)}\n";
|
||||
+ $"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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<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>
|
||||
@@ -11,7 +11,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>0.4.2</VersionPrefix>
|
||||
<VersionPrefix>0.5.0</VersionPrefix>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Title>NDecrypt</Title>
|
||||
@@ -31,11 +31,11 @@
|
||||
<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`))">
|
||||
<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</TargetFrameworks>
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -43,7 +43,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="SabreTools.CommandLine" Version="[1.4.0]" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET20 || NET35 || NET40 || NET452
|
||||
using System.Reflection;
|
||||
#endif
|
||||
|
||||
namespace NDecrypt
|
||||
{
|
||||
/// <summary>
|
||||
/// Set of options for the test executable
|
||||
/// </summary>
|
||||
internal sealed class Options
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Feature to process input files with
|
||||
/// </summary>
|
||||
public Feature Feature { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to config.json
|
||||
/// </summary>
|
||||
public string? ConfigPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable using development keys, if available
|
||||
/// </summary>
|
||||
public bool Development { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Force operation by avoiding sanity checks
|
||||
/// </summary>
|
||||
public bool Force { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Set of input paths to use for operations
|
||||
/// </summary>
|
||||
public List<string> InputPaths { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Output size and hashes to a companion file
|
||||
/// </summary>
|
||||
public bool OutputHashes { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Enable overwriting of the original file
|
||||
/// </summary>
|
||||
/// TODO: Change this to default false when hooked up
|
||||
public bool Overwrite { get; private set; } = true;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Parse commandline arguments into an Options object
|
||||
/// </summary>
|
||||
public static Options? ParseOptions(string[] args)
|
||||
{
|
||||
// If we have invalid arguments
|
||||
if (args == null || args.Length < 2)
|
||||
{
|
||||
Console.WriteLine("Not enough arguments");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create an Options object
|
||||
var options = new Options();
|
||||
|
||||
// Derive the feature
|
||||
switch (args[0])
|
||||
{
|
||||
case "-?":
|
||||
case "-h":
|
||||
case "--help":
|
||||
return null;
|
||||
|
||||
case "d":
|
||||
case "decrypt":
|
||||
options.Feature = Feature.Decrypt;
|
||||
break;
|
||||
|
||||
case "e":
|
||||
case "encrypt":
|
||||
options.Feature = Feature.Encrypt;
|
||||
break;
|
||||
|
||||
case "i":
|
||||
case "info":
|
||||
options.Feature = Feature.Info;
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.WriteLine($"Invalid operation: {args[0]}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse the options and paths
|
||||
for (int index = 1; index < args.Length; index++)
|
||||
{
|
||||
string arg = args[index];
|
||||
switch (arg)
|
||||
{
|
||||
case "-?":
|
||||
case "-h":
|
||||
case "--help":
|
||||
return null;
|
||||
|
||||
case "-c":
|
||||
case "--config":
|
||||
if (index == args.Length - 1)
|
||||
{
|
||||
Console.WriteLine("Invalid config path: no additional arguments found!");
|
||||
continue;
|
||||
}
|
||||
|
||||
index++;
|
||||
options.ConfigPath = args[index];
|
||||
if (string.IsNullOrEmpty(options.ConfigPath))
|
||||
Console.WriteLine($"Invalid config path: null or empty path found!");
|
||||
|
||||
options.ConfigPath = Path.GetFullPath(options.ConfigPath);
|
||||
if (!File.Exists(options.ConfigPath))
|
||||
{
|
||||
Console.WriteLine($"Invalid config path: file {options.ConfigPath} not found!");
|
||||
options.ConfigPath = null;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "-d":
|
||||
case "--development":
|
||||
options.Development = true;
|
||||
break;
|
||||
|
||||
case "-f":
|
||||
case "--force":
|
||||
options.Force = true;
|
||||
break;
|
||||
|
||||
case "--hash":
|
||||
options.OutputHashes = true;
|
||||
break;
|
||||
|
||||
case "-o":
|
||||
case "--overwrite":
|
||||
options.Overwrite = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
options.InputPaths.Add(arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate we have any input paths to work on
|
||||
if (options.InputPaths.Count == 0)
|
||||
{
|
||||
Console.WriteLine("At least one path is required!");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Derive the config path based on the runtime folder if not already set
|
||||
options.ConfigPath = DeriveConfigFile(options.ConfigPath);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display help text
|
||||
/// </summary>
|
||||
/// <param name="err">Additional error text to display, can be null to ignore</param>
|
||||
public static void DisplayHelp(string? err = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(err))
|
||||
Console.WriteLine($"Error: {err}");
|
||||
|
||||
Console.WriteLine("Cart Image Encrypt/Decrypt Tool");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("NDecrypt <operation> [options] <path> ...");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Operations:");
|
||||
Console.WriteLine("e, encrypt Encrypt the input files");
|
||||
Console.WriteLine("d, decrypt Decrypt the input files");
|
||||
Console.WriteLine("i, info Output file information");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Options:");
|
||||
Console.WriteLine("-?, -h, --help Display this help text and quit");
|
||||
Console.WriteLine("-c, --config <path> Path to config.json");
|
||||
Console.WriteLine("-d, --development Enable using development keys, if available");
|
||||
Console.WriteLine("-f, --force Force operation by avoiding sanity checks");
|
||||
Console.WriteLine("--hash Output size and hashes to a companion file");
|
||||
// Console.WriteLine("-o, --overwrite Overwrite input files instead of creating new ones"); // TODO: Print this when enabled
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("<path> can be any file or folder that contains uncompressed items.");
|
||||
Console.WriteLine("More than one path can be specified at a time.");
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Derive the full path to the config file, if possible
|
||||
/// </summary>
|
||||
private static string? DeriveConfigFile(string? config)
|
||||
{
|
||||
// If a path is passed in
|
||||
if (!string.IsNullOrEmpty(config))
|
||||
{
|
||||
config = Path.GetFullPath(config);
|
||||
if (File.Exists(config))
|
||||
return config;
|
||||
}
|
||||
|
||||
// Derive the keyfile path, if possible
|
||||
return GetFileLocation("config.json");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search for a file in local and config directories
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to check in local and config directories</param>
|
||||
/// <returns>The full path to the file if found, null otherwise</returns>
|
||||
/// <remarks>
|
||||
/// This method looks in the following locations:
|
||||
/// - %HOME%/.config/ndecrypt
|
||||
/// - Assembly location directory
|
||||
/// - Process runtime directory
|
||||
/// </remarks>
|
||||
private static string? GetFileLocation(string filename)
|
||||
{
|
||||
// User home directory
|
||||
#if NET20 || NET35
|
||||
string homeDir = Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%");
|
||||
homeDir = Path.Combine(Path.Combine(homeDir, ".config"), "ndecrypt");
|
||||
#else
|
||||
string homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
homeDir = Path.Combine(homeDir, ".config", "ndecrypt");
|
||||
#endif
|
||||
if (File.Exists(Path.Combine(homeDir, filename)))
|
||||
return Path.Combine(homeDir, filename);
|
||||
|
||||
// Local directory
|
||||
#if NET20 || NET35 || NET40 || NET452
|
||||
string runtimeDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
#else
|
||||
string runtimeDir = AppContext.BaseDirectory;
|
||||
#endif
|
||||
if (File.Exists(Path.Combine(runtimeDir, filename)))
|
||||
return Path.Combine(runtimeDir, filename);
|
||||
|
||||
// Process directory
|
||||
using var processModule = System.Diagnostics.Process.GetCurrentProcess().MainModule;
|
||||
string applicationDirectory = Path.GetDirectoryName(processModule?.FileName) ?? string.Empty;
|
||||
if (File.Exists(Path.Combine(applicationDirectory, filename)))
|
||||
return Path.Combine(applicationDirectory, filename);
|
||||
|
||||
// No file was found
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,204 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NDecrypt.Core;
|
||||
using NDecrypt.Features;
|
||||
using SabreTools.CommandLine;
|
||||
using SabreTools.CommandLine.Features;
|
||||
|
||||
namespace NDecrypt
|
||||
{
|
||||
class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// Mapping of reusable tools
|
||||
/// </summary>
|
||||
private static readonly Dictionary<FileType, ITool> _tools = [];
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
// Get the options from the arguments
|
||||
var options = Options.ParseOptions(args);
|
||||
// Create the command set
|
||||
var commandSet = CreateCommands();
|
||||
|
||||
// If we have an invalid state
|
||||
if (options == null)
|
||||
// If we have no args, show the help and quit
|
||||
if (args is null || args.Length == 0)
|
||||
{
|
||||
Options.DisplayHelp();
|
||||
commandSet.OutputAllHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the decrypt args, if possible
|
||||
var decryptArgs = new DecryptArgs(options.ConfigPath); ;
|
||||
// Get the first argument as a feature flag
|
||||
string featureName = args[0];
|
||||
|
||||
// Create reusable tools
|
||||
_tools[FileType.NDS] = new DSTool(decryptArgs);
|
||||
_tools[FileType.N3DS] = new ThreeDSTool(options.Development, decryptArgs);
|
||||
|
||||
for (int i = 0; i < options.InputPaths.Count; i++)
|
||||
// Get the associated feature
|
||||
var topLevel = commandSet.GetTopLevel(featureName);
|
||||
if (topLevel is null || topLevel is not Feature feature)
|
||||
{
|
||||
if (File.Exists(options.InputPaths[i]))
|
||||
{
|
||||
ProcessFile(options.InputPaths[i], options);
|
||||
}
|
||||
else if (Directory.Exists(options.InputPaths[i]))
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(options.InputPaths[i], "*", SearchOption.AllDirectories))
|
||||
{
|
||||
ProcessFile(file, options);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"{options.InputPaths[i]} is not a file or folder. Please check your spelling and formatting and try again.");
|
||||
}
|
||||
Console.WriteLine($"'{featureName}' is not valid feature flag");
|
||||
commandSet.OutputFeatureHelp(featureName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle default help functionality
|
||||
if (topLevel is Help helpFeature)
|
||||
{
|
||||
helpFeature.ProcessArgs(args, 0, commandSet);
|
||||
return;
|
||||
}
|
||||
|
||||
// Now verify that all other flags are valid
|
||||
if (!feature.ProcessArgs(args, 1))
|
||||
return;
|
||||
|
||||
// If inputs are required
|
||||
if (feature.RequiresInputs && !feature.VerifyInputs())
|
||||
{
|
||||
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>
|
||||
/// Process a single file path
|
||||
/// Create the command set for the program
|
||||
/// </summary>
|
||||
/// <param name="input">File path to process</param>
|
||||
/// <param name="options">Options indicating how to process the file</param>
|
||||
private static void ProcessFile(string input, Options options)
|
||||
private static CommandSet CreateCommands()
|
||||
{
|
||||
// Attempt to derive the tool for the path
|
||||
var tool = DeriveTool(input);
|
||||
if (tool == null)
|
||||
return;
|
||||
List<string> header = [
|
||||
"Cart Image Encrypt/Decrypt Tool",
|
||||
string.Empty,
|
||||
"NDecrypt <operation> [options] <path> ...",
|
||||
string.Empty,
|
||||
];
|
||||
|
||||
Console.WriteLine($"Processing {input}");
|
||||
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.",
|
||||
];
|
||||
|
||||
// Derive the output filename, if required
|
||||
string? output = null;
|
||||
if (!options.Overwrite)
|
||||
output = GetOutputFile(input, options);
|
||||
var commandSet = new CommandSet(header, footer);
|
||||
|
||||
// Encrypt or decrypt the file as requested
|
||||
if (options.Feature == Feature.Encrypt && !tool.EncryptFile(input, output, options.Force))
|
||||
{
|
||||
Console.WriteLine("Encryption failed!");
|
||||
return;
|
||||
}
|
||||
else if (options.Feature == Feature.Decrypt && !tool.DecryptFile(input, output, options.Force))
|
||||
{
|
||||
Console.WriteLine("Decryption failed!");
|
||||
return;
|
||||
}
|
||||
else if (options.Feature == Feature.Info)
|
||||
{
|
||||
string? infoString = tool.GetInformation(input);
|
||||
infoString ??= "There was a problem getting file information!";
|
||||
commandSet.Add(new Help());
|
||||
commandSet.Add(new EncryptFeature());
|
||||
commandSet.Add(new DecryptFeature());
|
||||
commandSet.Add(new InfoFeature());
|
||||
|
||||
Console.WriteLine(infoString);
|
||||
}
|
||||
|
||||
// Output the file hashes, if expected
|
||||
if (options.OutputHashes)
|
||||
WriteHashes(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Derive the encryption tool to be used for the given file
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to derive the tool from</param>
|
||||
private static ITool? DeriveTool(string filename)
|
||||
{
|
||||
if (!File.Exists(filename))
|
||||
{
|
||||
Console.WriteLine($"{filename} does not exist! Skipping...");
|
||||
return null;
|
||||
}
|
||||
|
||||
FileType type = DetermineFileType(filename);
|
||||
return type switch
|
||||
{
|
||||
FileType.NDS => _tools[FileType.NDS],
|
||||
FileType.NDSi => _tools[FileType.NDS],
|
||||
FileType.iQueDS => _tools[FileType.NDS],
|
||||
FileType.N3DS => _tools[FileType.N3DS],
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine the file type from the filename extension
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to derive the type from</param>
|
||||
/// <returns>FileType value, if possible</returns>
|
||||
private static FileType DetermineFileType(string filename)
|
||||
{
|
||||
if (filename.EndsWith(".nds", StringComparison.OrdinalIgnoreCase) // Standard carts
|
||||
|| filename.EndsWith(".nds.dec", StringComparison.OrdinalIgnoreCase) // Carts/images with secure area decrypted
|
||||
|| filename.EndsWith(".nds.enc", StringComparison.OrdinalIgnoreCase) // Carts/images with secure area encrypted
|
||||
|| filename.EndsWith(".srl", StringComparison.OrdinalIgnoreCase)) // Development carts/images
|
||||
{
|
||||
Console.WriteLine("File recognized as Nintendo DS");
|
||||
return FileType.NDS;
|
||||
}
|
||||
else if (filename.EndsWith(".dsi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteLine("File recognized as Nintendo DSi");
|
||||
return FileType.NDSi;
|
||||
}
|
||||
else if (filename.EndsWith(".ids", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteLine("File recognized as iQue DS");
|
||||
return FileType.iQueDS;
|
||||
}
|
||||
else if (filename.EndsWith(".3ds", StringComparison.OrdinalIgnoreCase) // Standard carts
|
||||
|| filename.EndsWith(".3ds.dec", StringComparison.OrdinalIgnoreCase) // Decrypted carts/images
|
||||
|| filename.EndsWith(".3ds.enc", StringComparison.OrdinalIgnoreCase) // Encrypted carts/images
|
||||
|| 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;
|
||||
}
|
||||
|
||||
/// <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="options">Options indicating how to process the file</param>
|
||||
/// <returns>Output filename based on the input</returns>
|
||||
private static string GetOutputFile(string filename, Options options)
|
||||
{
|
||||
// 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
|
||||
|
||||
// Append '.enc' or '.dec' based on the feature
|
||||
if (options.Feature == Feature.Decrypt)
|
||||
filename += ".dec";
|
||||
else if (options.Feature == Feature.Encrypt)
|
||||
filename += ".enc";
|
||||
|
||||
// Return the reformatted name
|
||||
return filename;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write out the hashes of a file to a named file
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to get hashes for/param>
|
||||
private static void WriteHashes(string filename)
|
||||
{
|
||||
// If the file doesn't exist, don't try anything
|
||||
if (!File.Exists(filename))
|
||||
return;
|
||||
|
||||
// Get the hash string from the file
|
||||
string? hashString = HashingHelper.GetInfo(filename);
|
||||
if (hashString == null)
|
||||
return;
|
||||
|
||||
// Open the output file and write the hashes
|
||||
using var fs = File.Create(Path.GetFullPath(filename) + ".hash");
|
||||
using var sw = new StreamWriter(fs);
|
||||
sw.WriteLine(hashString);
|
||||
return commandSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,18 +49,18 @@ echo " No archive (-a) $NO_ARCHIVE"
|
||||
echo " "
|
||||
|
||||
# Create the build matrix arrays
|
||||
FRAMEWORKS=("net9.0")
|
||||
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")
|
||||
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")
|
||||
VALID_APPLE_FRAMEWORKS=("net6.0" "net7.0" "net8.0" "net9.0")
|
||||
VALID_CROSS_PLATFORM_FRAMEWORKS=("netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0" "net9.0")
|
||||
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
|
||||
|
||||
@@ -40,18 +40,18 @@ Write-Host " No archive (-NoArchive) $NO_ARCHIVE"
|
||||
Write-Host " "
|
||||
|
||||
# Create the build matrix arrays
|
||||
$FRAMEWORKS = @('net9.0')
|
||||
$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')
|
||||
$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')
|
||||
$VALID_APPLE_FRAMEWORKS = @('net6.0', 'net7.0', 'net8.0', 'net9.0')
|
||||
$VALID_CROSS_PLATFORM_FRAMEWORKS = @('netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0', 'net9.0')
|
||||
$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
|
||||
|
||||
Reference in New Issue
Block a user