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+ }
0 commit comments