diff --git a/ExtractionTool/Features/MainFeature.cs b/ExtractionTool/Features/MainFeature.cs index f35521d5..455ef2ee 100644 --- a/ExtractionTool/Features/MainFeature.cs +++ b/ExtractionTool/Features/MainFeature.cs @@ -37,12 +37,12 @@ namespace ExtractionTool.Features /// /// Enable debug output for relevant operations /// - public bool Debug { get; set; } + public bool Debug { get; private set; } /// /// Output path for archive extraction /// - public string OutputPath { get; set; } = string.Empty; + public string OutputPath { get; private set; } = string.Empty; #endregion diff --git a/InfoPrint/Features/MainFeature.cs b/InfoPrint/Features/MainFeature.cs new file mode 100644 index 00000000..fe421ff0 --- /dev/null +++ b/InfoPrint/Features/MainFeature.cs @@ -0,0 +1,279 @@ +using System; +using System.IO; +using System.Text; +using SabreTools.CommandLine; +using SabreTools.CommandLine.Inputs; +using SabreTools.Hashing; +using SabreTools.IO.Extensions; +using SabreTools.Serialization; +using SabreTools.Serialization.Wrappers; + +namespace InfoPrint.Features +{ + internal sealed class MainFeature : Feature + { + #region Feature Definition + + public const string DisplayName = "main"; + + /// Flags are unused + private static readonly string[] _flags = []; + + /// Description is unused + private const string _description = ""; + + #endregion + + #region Inputs + + private const string _debugName = "debug"; + internal readonly FlagInput DebugInput = new(_debugName, ["-d", "--debug"], "Enable debug mode"); + + private const string _fileOnlyName = "file-only"; + internal readonly FlagInput FileOnlyInput = new(_fileOnlyName, ["-f", "--file"], "Print to file only"); + + private const string _hashName = "hash"; + internal readonly FlagInput HashInput = new(_hashName, ["-c", "--hash"], "Output file hashes"); + +#if NETCOREAPP + private const string _jsonName = "json"; + internal readonly FlagInput JsonInput = new(_jsonName, ["-j", "--json"], "Print info as JSON"); +#endif + + #endregion + + /// + /// Enable debug output for relevant operations + /// + public bool Debug { get; private set; } + + /// + /// Output information to file only, skip printing to console + /// + public bool FileOnly { get; private set; } + + /// + /// Print external file hashes + /// + public bool Hash { get; private set; } + +#if NETCOREAPP + /// + /// Enable JSON output + /// + public bool Json { get; private set; } +#endif + + public MainFeature() + : base(DisplayName, _flags, _description) + { + RequiresInputs = true; + + Add(DebugInput); + Add(HashInput); + Add(FileOnlyInput); +#if NETCOREAPP + Add(JsonInput); +#endif + } + + /// + public override bool Execute() + { + // Get the options from the arguments + Debug = GetBoolean(_debugName); + Hash = GetBoolean(_hashName); + FileOnly = GetBoolean(_fileOnlyName); +#if NETCOREAPP + Json = GetBoolean(_jsonName); +#endif + + // Loop through the input paths + for (int i = 0; i < Inputs.Count; i++) + { + string arg = Inputs[i]; + PrintPathInfo(arg); + } + + return true; + } + + /// + public override bool VerifyInputs() => Inputs.Count > 0; + + /// + /// Wrapper to print information for a single path + /// + /// File or directory path + private void PrintPathInfo(string path) + { + Console.WriteLine($"Checking possible path: {path}"); + + // Check if the file or directory exists + if (File.Exists(path)) + { + PrintFileInfo(path); + } + else if (Directory.Exists(path)) + { + foreach (string file in path.SafeEnumerateFiles("*", SearchOption.AllDirectories)) + { + PrintFileInfo(file); + } + } + else + { + Console.WriteLine($"{path} does not exist, skipping..."); + } + } + + /// + /// Print information for a single file, if possible + /// + /// File path + private void PrintFileInfo(string file) + { + Console.WriteLine($"Attempting to print info for {file}"); + + // Get the base info output name + string filenameBase = $"info-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}"; + + // If we have the hash flag + if (Hash) + { + var hashBuilder = PrintHashInfo(file); + if (hashBuilder != null) + { + // Create the output data + string hashData = hashBuilder.ToString(); + + // Write the output data + using var hsw = new StreamWriter(File.OpenWrite($"{filenameBase}.hashes")); + hsw.WriteLine(hashData); + hsw.Flush(); + } + } + + try + { + using Stream stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + + // Read the first 8 bytes + byte[]? magic = stream.ReadBytes(8); + stream.Seek(0, SeekOrigin.Begin); + + // Get the file type + string extension = Path.GetExtension(file).TrimStart('.'); + WrapperType ft = WrapperFactory.GetFileType(magic ?? [], extension); + + // Print out the file format + Console.WriteLine($"File format found: {ft}"); + + // Setup the wrapper to print + var wrapper = WrapperFactory.CreateWrapper(ft, stream); + + // If we don't have a wrapper + if (wrapper == null) + { + Console.WriteLine($"Either {ft} is not supported or something went wrong during parsing!"); + Console.WriteLine(); + return; + } + +#if NETCOREAPP + // If we have the JSON flag + if (Json) + { + // Create the output data + string serializedData = wrapper.ExportJSON(); + + // Write the output data + using var jsw = new StreamWriter(File.OpenWrite($"{filenameBase}.json")); + jsw.WriteLine(serializedData); + jsw.Flush(); + } +#endif + + // Create the output data + var builder = wrapper.ExportStringBuilder(); + if (builder == null) + { + Console.WriteLine("No item information could be generated"); + return; + } + + // Only print to console if enabled + if (FileOnly) + Console.WriteLine(builder); + + using var sw = new StreamWriter(File.OpenWrite($"{filenameBase}.txt")); + sw.WriteLine(file); + sw.WriteLine(); + sw.WriteLine(builder.ToString()); + sw.Flush(); + } + catch (Exception ex) + { + Console.WriteLine(Debug ? ex : "[Exception opening file, please try again]"); + Console.WriteLine(); + } + } + + /// + /// Print hash information for a single file, if possible + /// + /// File path + /// StringBuilder representing the hash information, if possible + private StringBuilder? PrintHashInfo(string file) + { + // Ignore missing files + if (!File.Exists(file)) + return null; + + Console.WriteLine($"Attempting to hash {file}, this may take a while..."); + + try + { + // Get all file hashes for flexibility + var hashes = HashTool.GetFileHashes(file); + if (hashes == null) + { + if (Debug) Console.WriteLine($"Hashes for {file} could not be retrieved"); + return null; + } + + // Output subset of available hashes + var builder = new StringBuilder(); + if (hashes.TryGetValue(HashType.CRC16, out string? crc16) && crc16 != null) + builder.AppendLine($"CRC-16 checksum: {crc16}"); + if (hashes.TryGetValue(HashType.CRC32, out string? crc32) && crc32 != null) + builder.AppendLine($"CRC-32 checksum: {crc32}"); + if (hashes.TryGetValue(HashType.MD2, out string? md2) && md2 != null) + builder.AppendLine($"MD2 hash: {md2}"); + if (hashes.TryGetValue(HashType.MD4, out string? md4) && md4 != null) + builder.AppendLine($"MD4 hash: {md4}"); + if (hashes.TryGetValue(HashType.MD5, out string? md5) && md5 != null) + builder.AppendLine($"MD5 hash: {md5}"); + if (hashes.TryGetValue(HashType.RIPEMD128, out string? ripemd128) && ripemd128 != null) + builder.AppendLine($"RIPEMD-128 hash: {ripemd128}"); + if (hashes.TryGetValue(HashType.RIPEMD160, out string? ripemd160) && ripemd160 != null) + builder.AppendLine($"RIPEMD-160 hash: {ripemd160}"); + if (hashes.TryGetValue(HashType.SHA1, out string? sha1) && sha1 != null) + builder.AppendLine($"SHA-1 hash: {sha1}"); + if (hashes.TryGetValue(HashType.SHA256, out string? sha256) && sha256 != null) + builder.AppendLine($"SHA-256 hash: {sha256}"); + if (hashes.TryGetValue(HashType.SHA384, out string? sha384) && sha384 != null) + builder.AppendLine($"SHA-384 hash: {sha384}"); + if (hashes.TryGetValue(HashType.SHA512, out string? sha512) && sha512 != null) + builder.AppendLine($"SHA-512 hash: {sha512}"); + + return builder; + } + catch (Exception ex) + { + Console.WriteLine(Debug ? ex : "[Exception opening file, please try again]"); + return null; + } + } + } +} diff --git a/InfoPrint/Options.cs b/InfoPrint/Options.cs deleted file mode 100644 index 2689ffc8..00000000 --- a/InfoPrint/Options.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace InfoPrint -{ - /// - /// Set of options for the test executable - /// - internal sealed class Options - { - /// - /// Enable debug output for relevant operations - /// - public bool Debug { get; set; } - - /// - /// Output information to file only, skip printing to console - /// - public bool FileOnly { get; set; } - - /// - /// Print external file hashes - /// - public bool Hash { get; set; } - -#if NETCOREAPP - /// - /// Enable JSON output - /// - public bool Json { get; set; } -#endif - } -} diff --git a/InfoPrint/Program.cs b/InfoPrint/Program.cs index 83555e2a..5c1990f7 100644 --- a/InfoPrint/Program.cs +++ b/InfoPrint/Program.cs @@ -1,34 +1,18 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Text; +using InfoPrint.Features; using SabreTools.CommandLine; -using SabreTools.CommandLine.Inputs; -using SabreTools.Hashing; -using SabreTools.IO.Extensions; -using SabreTools.Serialization; -using SabreTools.Serialization.Wrappers; +using SabreTools.CommandLine.Features; namespace InfoPrint { public static class Program { - #region Constants - - private const string _debugName = "debug"; - private const string _fileOnlyName = "file-only"; - private const string _hashName = "hash"; - private const string _helpName = "help"; -#if NETCOREAPP - private const string _jsonName = "json"; -#endif - - #endregion - public static void Main(string[] args) { // Create the command set - var commandSet = CreateCommands(); + var mainFeature = new MainFeature(); + var commandSet = CreateCommands(mainFeature); // If we have no args, show the help and quit if (args == null || args.Length == 0) @@ -37,49 +21,39 @@ namespace InfoPrint return; } - // Loop through and process the options - int firstFileIndex = 0; - for (; firstFileIndex < args.Length; firstFileIndex++) - { - string arg = args[firstFileIndex]; + // Cache the first argument and starting index + string featureName = args[0]; - var input = commandSet.GetTopLevel(arg); - if (input == null) + // Try processing the standalone arguments + var topLevel = commandSet.GetTopLevel(featureName); + switch (topLevel) + { + // Standalone Options + case Help help: help.ProcessArgs(args, 0, commandSet); return; + + // Default Behavior + default: + if (!mainFeature.ProcessArgs(args, 0)) + { + commandSet.OutputAllHelp(); + return; + } + else if (!mainFeature.VerifyInputs()) + { + Console.Error.WriteLine("At least one input is required"); + commandSet.OutputAllHelp(); + return; + } + + mainFeature.Execute(); break; - - input.ProcessInput(args, ref firstFileIndex); - } - - // If help was specified - if (commandSet.GetBoolean(_helpName)) - { - commandSet.OutputAllHelp(); - return; - } - - // Get the options from the arguments - var options = new Options - { - Debug = commandSet.GetBoolean(_debugName), - Hash = commandSet.GetBoolean(_hashName), - FileOnly = commandSet.GetBoolean(_fileOnlyName), -#if NETCOREAPP - Json = commandSet.GetBoolean(_jsonName), -#endif - }; - - // Loop through the input paths - for (int i = firstFileIndex; i < args.Length; i++) - { - string arg = args[i]; - PrintPathInfo(arg, options); } } /// /// Create the command set for the program /// - private static CommandSet CreateCommands() + private static CommandSet CreateCommands(MainFeature mainFeature) { List header = [ "Information Printing Program", @@ -90,193 +64,15 @@ namespace InfoPrint var commandSet = new CommandSet(header); - commandSet.Add(new FlagInput(_helpName, ["-?", "-h", "--help"], "Display this help text")); - commandSet.Add(new FlagInput(_debugName, ["-d", "--debug"], "Enable debug mode")); - commandSet.Add(new FlagInput(_hashName, ["-c", "--hash"], "Output file hashes")); - commandSet.Add(new FlagInput(_fileOnlyName, ["-f", "--file"], "Print to file only")); + commandSet.Add(new Help(["-?", "-h", "--help"])); + commandSet.Add(mainFeature.DebugInput); + commandSet.Add(mainFeature.HashInput); + commandSet.Add(mainFeature.FileOnlyInput); #if NETCOREAPP - commandSet.Add(new FlagInput(_jsonName, ["-j", "--json"], "Print info as JSON")); + commandSet.Add(mainFeature.JsonInput); #endif return commandSet; } - - /// - /// Wrapper to print information for a single path - /// - /// File or directory path - /// User-defined options - private static void PrintPathInfo(string path, Options options) - { - Console.WriteLine($"Checking possible path: {path}"); - - // Check if the file or directory exists - if (File.Exists(path)) - { - PrintFileInfo(path, options); - } - else if (Directory.Exists(path)) - { - foreach (string file in IOExtensions.SafeEnumerateFiles(path, "*", SearchOption.AllDirectories)) - { - PrintFileInfo(file, options); - } - } - else - { - Console.WriteLine($"{path} does not exist, skipping..."); - } - } - - /// - /// Print information for a single file, if possible - /// - /// File path - /// User-defined options - private static void PrintFileInfo(string file, Options options) - { - Console.WriteLine($"Attempting to print info for {file}"); - - // Get the base info output name - string filenameBase = $"info-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}"; - - // If we have the hash flag - if (options.Hash) - { - var hashBuilder = PrintHashInfo(file, options.Debug); - if (hashBuilder != null) - { - // Create the output data - string hashData = hashBuilder.ToString(); - - // Write the output data - using var hsw = new StreamWriter(File.OpenWrite($"{filenameBase}.hashes")); - hsw.WriteLine(hashData); - hsw.Flush(); - } - } - - try - { - using Stream stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - - // Read the first 8 bytes - byte[]? magic = stream.ReadBytes(8); - stream.Seek(0, SeekOrigin.Begin); - - // Get the file type - string extension = Path.GetExtension(file).TrimStart('.'); - WrapperType ft = WrapperFactory.GetFileType(magic ?? [], extension); - - // Print out the file format - Console.WriteLine($"File format found: {ft}"); - - // Setup the wrapper to print - var wrapper = WrapperFactory.CreateWrapper(ft, stream); - - // If we don't have a wrapper - if (wrapper == null) - { - Console.WriteLine($"Either {ft} is not supported or something went wrong during parsing!"); - Console.WriteLine(); - return; - } - -#if NETCOREAPP - // If we have the JSON flag - if (options.Json) - { - // Create the output data - string serializedData = wrapper.ExportJSON(); - - // Write the output data - using var jsw = new StreamWriter(File.OpenWrite($"{filenameBase}.json")); - jsw.WriteLine(serializedData); - jsw.Flush(); - } -#endif - - // Create the output data - var builder = wrapper.ExportStringBuilder(); - if (builder == null) - { - Console.WriteLine("No item information could be generated"); - return; - } - - // Only print to console if enabled - if (!options.FileOnly) - Console.WriteLine(builder); - - using var sw = new StreamWriter(File.OpenWrite($"{filenameBase}.txt")); - sw.WriteLine(file); - sw.WriteLine(); - sw.WriteLine(builder.ToString()); - sw.Flush(); - } - catch (Exception ex) - { - Console.WriteLine(options.Debug ? ex : "[Exception opening file, please try again]"); - Console.WriteLine(); - } - } - - /// - /// Print hash information for a single file, if possible - /// - /// File path - /// Enable debug output - /// StringBuilder representing the hash information, if possible - private static StringBuilder? PrintHashInfo(string file, bool debug) - { - // Ignore missing files - if (!File.Exists(file)) - return null; - - Console.WriteLine($"Attempting to hash {file}, this may take a while..."); - - try - { - // Get all file hashes for flexibility - var hashes = HashTool.GetFileHashes(file); - if (hashes == null) - { - if (debug) Console.WriteLine($"Hashes for {file} could not be retrieved"); - return null; - } - - // Output subset of available hashes - var builder = new StringBuilder(); - if (hashes.TryGetValue(HashType.CRC16, out string? crc16) && crc16 != null) - builder.AppendLine($"CRC-16 checksum: {crc16}"); - if (hashes.TryGetValue(HashType.CRC32, out string? crc32) && crc32 != null) - builder.AppendLine($"CRC-32 checksum: {crc32}"); - if (hashes.TryGetValue(HashType.MD2, out string? md2) && md2 != null) - builder.AppendLine($"MD2 hash: {md2}"); - if (hashes.TryGetValue(HashType.MD4, out string? md4) && md4 != null) - builder.AppendLine($"MD4 hash: {md4}"); - if (hashes.TryGetValue(HashType.MD5, out string? md5) && md5 != null) - builder.AppendLine($"MD5 hash: {md5}"); - if (hashes.TryGetValue(HashType.RIPEMD128, out string? ripemd128) && ripemd128 != null) - builder.AppendLine($"RIPEMD-128 hash: {ripemd128}"); - if (hashes.TryGetValue(HashType.RIPEMD160, out string? ripemd160) && ripemd160 != null) - builder.AppendLine($"RIPEMD-160 hash: {ripemd160}"); - if (hashes.TryGetValue(HashType.SHA1, out string? sha1) && sha1 != null) - builder.AppendLine($"SHA-1 hash: {sha1}"); - if (hashes.TryGetValue(HashType.SHA256, out string? sha256) && sha256 != null) - builder.AppendLine($"SHA-256 hash: {sha256}"); - if (hashes.TryGetValue(HashType.SHA384, out string? sha384) && sha384 != null) - builder.AppendLine($"SHA-384 hash: {sha384}"); - if (hashes.TryGetValue(HashType.SHA512, out string? sha512) && sha512 != null) - builder.AppendLine($"SHA-512 hash: {sha512}"); - - return builder; - } - catch (Exception ex) - { - Console.WriteLine(debug ? ex : "[Exception opening file, please try again]"); - return null; - } - } } }