Skip to content

Commit 07676f4

Browse files
committed
Use CommandLine library for executable
1 parent 1a69113 commit 07676f4

File tree

9 files changed

+412
-175
lines changed

9 files changed

+412
-175
lines changed

NDecrypt/BaseFeature.cs

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using NDecrypt.Core;
5+
using SabreTools.CommandLine;
6+
using SabreTools.CommandLine.Inputs;
7+
8+
namespace NDecrypt
9+
{
10+
internal abstract class BaseFeature : Feature
11+
{
12+
#region Common Inputs
13+
14+
protected const string ConfigName = "config";
15+
protected readonly StringInput ConfigString = new(ConfigName, ["-c", "--config"], "Path to config.json");
16+
17+
protected const string DevelopmentName = "development";
18+
protected readonly FlagInput DevelopmentFlag = new(DevelopmentName, ["-d", "--development"], "Enable using development keys, if available");
19+
20+
protected const string ForceName = "force";
21+
protected readonly FlagInput ForceFlag = new(ForceName, ["-f", "--force"], "Force operation by avoiding sanity checks");
22+
23+
protected const string HashName = "hash";
24+
protected readonly FlagInput HashFlag = new(HashName, "--hash", "Output size and hashes to a companion file");
25+
26+
protected const string OverwriteName = "overwrite";
27+
protected readonly FlagInput OverwriteFlag = new(OverwriteName, ["-o", "--overwrite"], "Overwrite input files instead of creating new ones");
28+
29+
#endregion
30+
31+
/// <summary>
32+
/// Mapping of reusable tools
33+
/// </summary>
34+
private readonly Dictionary<FileType, ITool> _tools = [];
35+
36+
protected BaseFeature(string name, string[] flags, string description, string? detailed = null)
37+
: base(name, flags, description, detailed)
38+
{
39+
}
40+
41+
/// <inheritdoc/>
42+
public override bool Execute()
43+
{
44+
// Initialize required pieces
45+
InitializeTools();
46+
47+
for (int i = 0; i < Inputs.Count; i++)
48+
{
49+
if (File.Exists(Inputs[i]))
50+
{
51+
ProcessFile(Inputs[i]);
52+
}
53+
else if (Directory.Exists(Inputs[i]))
54+
{
55+
foreach (string file in Directory.GetFiles(Inputs[i], "*", SearchOption.AllDirectories))
56+
{
57+
ProcessFile(file);
58+
}
59+
}
60+
else
61+
{
62+
Console.WriteLine($"{Inputs[i]} is not a file or folder. Please check your spelling and formatting and try again.");
63+
}
64+
}
65+
66+
return true;
67+
}
68+
69+
/// <inheritdoc/>
70+
public override bool VerifyInputs() => Inputs.Count > 0;
71+
72+
/// <summary>
73+
/// Process a single file path
74+
/// </summary>
75+
/// <param name="input">File path to process</param>
76+
protected abstract void ProcessFile(string input);
77+
78+
/// <summary>
79+
/// Initialize the tools to be used by the feature
80+
/// </summary>
81+
private void InitializeTools()
82+
{
83+
84+
var decryptArgs = new DecryptArgs(GetString(ConfigName));
85+
_tools[FileType.NDS] = new DSTool(decryptArgs);
86+
_tools[FileType.N3DS] = new ThreeDSTool(GetBoolean(DevelopmentName), decryptArgs);
87+
}
88+
89+
/// <summary>
90+
/// Derive the encryption tool to be used for the given file
91+
/// </summary>
92+
/// <param name="filename">Filename to derive the tool from</param>
93+
protected ITool? DeriveTool(string filename)
94+
{
95+
if (!File.Exists(filename))
96+
{
97+
Console.WriteLine($"{filename} does not exist! Skipping...");
98+
return null;
99+
}
100+
101+
FileType type = DetermineFileType(filename);
102+
return type switch
103+
{
104+
FileType.NDS => _tools[FileType.NDS],
105+
FileType.NDSi => _tools[FileType.NDS],
106+
FileType.iQueDS => _tools[FileType.NDS],
107+
FileType.N3DS => _tools[FileType.N3DS],
108+
_ => null,
109+
};
110+
}
111+
112+
/// <summary>
113+
/// Derive an output filename from the input, if possible
114+
/// </summary>
115+
/// <param name="filename">Name of the input file to derive from</param>
116+
/// <param name="extension">Preferred extension set by the feature implementation</param>
117+
/// <returns>Output filename based on the input</returns>
118+
protected static string GetOutputFile(string filename, string extension)
119+
{
120+
// Empty filenames are passed back
121+
if (filename.Length == 0)
122+
return filename;
123+
124+
// TODO: Replace the suffix instead of just appending
125+
// TODO: Ensure that the input and output aren't the same
126+
127+
// If the extension does not include a leading period
128+
if (!extension.StartsWith("."))
129+
extension = $".{extension}";
130+
131+
// Append the extension and return
132+
return $"{filename}{extension}";
133+
}
134+
135+
/// <summary>
136+
/// Write out the hashes of a file to a named file
137+
/// </summary>
138+
/// <param name="filename">Filename to get hashes for/param>
139+
protected static void WriteHashes(string filename)
140+
{
141+
// If the file doesn't exist, don't try anything
142+
if (!File.Exists(filename))
143+
return;
144+
145+
// Get the hash string from the file
146+
string? hashString = HashingHelper.GetInfo(filename);
147+
if (hashString == null)
148+
return;
149+
150+
// Open the output file and write the hashes
151+
using var fs = File.Open(Path.GetFullPath(filename) + ".hash", FileMode.Create, FileAccess.Write, FileShare.None);
152+
using var sw = new StreamWriter(fs);
153+
sw.Write(hashString);
154+
}
155+
156+
/// <summary>
157+
/// Determine the file type from the filename extension
158+
/// </summary>
159+
/// <param name="filename">Filename to derive the type from</param>
160+
/// <returns>FileType value, if possible</returns>
161+
private FileType DetermineFileType(string filename)
162+
{
163+
if (filename.EndsWith(".nds", StringComparison.OrdinalIgnoreCase) // Standard carts
164+
|| filename.EndsWith(".nds.dec", StringComparison.OrdinalIgnoreCase) // Carts/images with secure area decrypted
165+
|| filename.EndsWith(".nds.enc", StringComparison.OrdinalIgnoreCase) // Carts/images with secure area encrypted
166+
|| filename.EndsWith(".srl", StringComparison.OrdinalIgnoreCase)) // Development carts/images
167+
{
168+
Console.WriteLine("File recognized as Nintendo DS");
169+
return FileType.NDS;
170+
}
171+
else if (filename.EndsWith(".dsi", StringComparison.OrdinalIgnoreCase))
172+
{
173+
Console.WriteLine("File recognized as Nintendo DSi");
174+
return FileType.NDSi;
175+
}
176+
else if (filename.EndsWith(".ids", StringComparison.OrdinalIgnoreCase))
177+
{
178+
Console.WriteLine("File recognized as iQue DS");
179+
return FileType.iQueDS;
180+
}
181+
else if (filename.EndsWith(".3ds", StringComparison.OrdinalIgnoreCase) // Standard carts
182+
|| filename.EndsWith(".3ds.dec", StringComparison.OrdinalIgnoreCase) // Decrypted carts/images
183+
|| filename.EndsWith(".3ds.enc", StringComparison.OrdinalIgnoreCase) // Encrypted carts/images
184+
|| filename.EndsWith(".cci", StringComparison.OrdinalIgnoreCase)) // Development carts/images
185+
{
186+
Console.WriteLine("File recognized as Nintendo 3DS");
187+
return FileType.N3DS;
188+
}
189+
190+
Console.WriteLine($"Unrecognized file format for {filename}. Expected *.nds, *.srl, *.dsi, *.3ds, *.cci");
191+
return FileType.NULL;
192+
}
193+
}
194+
}

NDecrypt/DecryptFeature.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
3+
namespace NDecrypt
4+
{
5+
internal sealed class DecryptFeature : BaseFeature
6+
{
7+
#region Feature Definition
8+
9+
public const string DisplayName = "decrypt";
10+
11+
private static readonly string[] _flags = ["d", "decrypt"];
12+
13+
private const string _description = "Decrypt the input files";
14+
15+
#endregion
16+
17+
public DecryptFeature()
18+
: base(DisplayName, _flags, _description)
19+
{
20+
RequiresInputs = true;
21+
22+
Add(ConfigString);
23+
Add(DevelopmentFlag);
24+
Add(ForceFlag);
25+
Add(HashFlag);
26+
27+
// TODO: Include this when enabled
28+
// Add(OverwriteFlag);
29+
}
30+
31+
/// <inheritdoc/>
32+
protected override void ProcessFile(string input)
33+
{
34+
// Attempt to derive the tool for the path
35+
var tool = DeriveTool(input);
36+
if (tool == null)
37+
return;
38+
39+
// Derive the output filename, if required
40+
string? output = null;
41+
if (!GetBoolean(OverwriteName))
42+
output = GetOutputFile(input, ".dec");
43+
44+
Console.WriteLine($"Processing {input}");
45+
46+
if (!tool.DecryptFile(input, output, GetBoolean(ForceName)))
47+
{
48+
Console.WriteLine("Decryption failed!");
49+
return;
50+
}
51+
52+
// Output the file hashes, if expected
53+
if (GetBoolean(HashName))
54+
WriteHashes(input);
55+
}
56+
}
57+
}

NDecrypt/EncryptFeature.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
3+
namespace NDecrypt
4+
{
5+
internal sealed class EncryptFeature : BaseFeature
6+
{
7+
#region Feature Definition
8+
9+
public const string DisplayName = "encrypt";
10+
11+
private static readonly string[] _flags = ["e", "encrypt"];
12+
13+
private const string _description = "Encrypt the input files";
14+
15+
#endregion
16+
17+
public EncryptFeature()
18+
: base(DisplayName, _flags, _description)
19+
{
20+
RequiresInputs = true;
21+
22+
Add(ConfigString);
23+
Add(DevelopmentFlag);
24+
Add(ForceFlag);
25+
Add(HashFlag);
26+
27+
// TODO: Include this when enabled
28+
// Add(OverwriteFlag);
29+
}
30+
31+
/// <inheritdoc/>
32+
protected override void ProcessFile(string input)
33+
{
34+
// Attempt to derive the tool for the path
35+
var tool = DeriveTool(input);
36+
if (tool == null)
37+
return;
38+
39+
// Derive the output filename, if required
40+
string? output = null;
41+
if (!GetBoolean(OverwriteName))
42+
output = GetOutputFile(input, ".enc");
43+
44+
Console.WriteLine($"Processing {input}");
45+
46+
if (!tool.EncryptFile(input, output, GetBoolean(ForceName)))
47+
{
48+
Console.WriteLine("Encryption failed!");
49+
return;
50+
}
51+
52+
// Output the file hashes, if expected
53+
if (GetBoolean(HashName))
54+
WriteHashes(input);
55+
}
56+
}
57+
}

NDecrypt/Enumerations.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ namespace NDecrypt
33
/// <summary>
44
/// Functionality to use from the program
55
/// </summary>
6-
internal enum Feature
6+
internal enum FeatureFlag
77
{
88
NULL,
99
Decrypt,

NDecrypt/HashingHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ internal static class HashingHelper
2727
+ $"CRC-32: {(hashDict.ContainsKey(HashType.CRC32) ? hashDict[HashType.CRC32] : string.Empty)}\n"
2828
+ $"MD5: {(hashDict.ContainsKey(HashType.MD5) ? hashDict[HashType.MD5] : string.Empty)}\n"
2929
+ $"SHA-1: {(hashDict.ContainsKey(HashType.SHA1) ? hashDict[HashType.SHA1] : string.Empty)}\n"
30-
+ $"CSHA-256: {(hashDict.ContainsKey(HashType.SHA256) ? hashDict[HashType.SHA256] : string.Empty)}\n";
30+
+ $"SHA-256: {(hashDict.ContainsKey(HashType.SHA256) ? hashDict[HashType.SHA256] : string.Empty)}\n";
3131
}
3232
}
3333
}

NDecrypt/InfoFeature.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System;
2+
3+
namespace NDecrypt
4+
{
5+
internal sealed class InfoFeature : BaseFeature
6+
{
7+
#region Feature Definition
8+
9+
public const string DisplayName = "info";
10+
11+
private static readonly string[] _flags = ["i", "info"];
12+
13+
private const string _description = "Output file information";
14+
15+
#endregion
16+
17+
public InfoFeature()
18+
: base(DisplayName, _flags, _description)
19+
{
20+
RequiresInputs = true;
21+
22+
Add(HashFlag);
23+
}
24+
25+
/// <inheritdoc/>
26+
protected override void ProcessFile(string input)
27+
{
28+
// Attempt to derive the tool for the path
29+
var tool = DeriveTool(input);
30+
if (tool == null)
31+
return;
32+
33+
Console.WriteLine($"Processing {input}");
34+
35+
string? infoString = tool.GetInformation(input);
36+
infoString ??= "There was a problem getting file information!";
37+
38+
Console.WriteLine(infoString);
39+
40+
// Output the file hashes, if expected
41+
if (GetBoolean(HashName))
42+
WriteHashes(input);
43+
}
44+
}
45+
}

NDecrypt/NDecrypt.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444

4545
<ItemGroup>
4646
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
47+
<PackageReference Include="SabreTools.CommandLine" Version="[1.3.2]" />
4748
</ItemGroup>
4849

4950
</Project>

0 commit comments

Comments
 (0)