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;
- }
- }
}
}