diff --git a/ExtractionTool/ExtractionTool.csproj b/ExtractionTool/ExtractionTool.csproj index 893fff93..ee21a5e6 100644 --- a/ExtractionTool/ExtractionTool.csproj +++ b/ExtractionTool/ExtractionTool.csproj @@ -58,6 +58,7 @@ + diff --git a/ExtractionTool/Options.cs b/ExtractionTool/Options.cs index 18956c25..e3df600a 100644 --- a/ExtractionTool/Options.cs +++ b/ExtractionTool/Options.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; namespace ExtractionTool @@ -14,96 +13,22 @@ namespace ExtractionTool /// /// Enable debug output for relevant operations /// - public bool Debug { get; private set; } = false; - - /// - /// Set of input paths to use for operations - /// - public List InputPaths { get; private set; } = []; + public bool Debug { get; set; } /// /// Output path for archive extraction /// - public string OutputPath { get; private set; } = string.Empty; + public string OutputPath { get; set; } = string.Empty; #endregion - /// - /// Parse commandline arguments into an Options object - /// - public static Options? ParseOptions(string[] args) - { - // If we have invalid arguments - if (args == null || args.Length == 0) - return null; - - // Create an Options object - var options = new Options(); - - // Parse the options and paths - for (int index = 0; index < args.Length; index++) - { - string arg = args[index]; - switch (arg) - { - case "-?": - case "-h": - case "--help": - return null; - - case "-d": - case "--debug": - options.Debug = true; - break; - - case "-o": - case "--outdir": - options.OutputPath = index + 1 < args.Length ? args[++index] : string.Empty; - 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; - } - - // Validate the output path - bool validPath = ValidateExtractionPath(options); - if (!validPath) - return null; - - return options; - } - - /// - /// Display help text - /// - public static void DisplayHelp() - { - Console.WriteLine("Extraction Tool"); - Console.WriteLine(); - Console.WriteLine("ExtractionTool.exe file|directory ..."); - Console.WriteLine(); - Console.WriteLine("Options:"); - Console.WriteLine("-?, -h, --help Display this help text and quit"); - Console.WriteLine("-d, --debug Enable debug mode"); - Console.WriteLine("-o, --outdir [PATH] Set output path for extraction (required)"); - } - /// /// Validate the extraction path /// - private static bool ValidateExtractionPath(Options options) + public bool ValidateExtractionPath() { // Null or empty output path - if (string.IsNullOrEmpty(options.OutputPath)) + if (string.IsNullOrEmpty(OutputPath)) { Console.WriteLine("Output directory required for extraction!"); Console.WriteLine(); @@ -113,8 +38,8 @@ namespace ExtractionTool // Malformed output path or invalid location try { - options.OutputPath = Path.GetFullPath(options.OutputPath); - Directory.CreateDirectory(options.OutputPath); + OutputPath = Path.GetFullPath(OutputPath); + Directory.CreateDirectory(OutputPath); } catch { diff --git a/ExtractionTool/Program.cs b/ExtractionTool/Program.cs index 8f202f8f..d9186297 100644 --- a/ExtractionTool/Program.cs +++ b/ExtractionTool/Program.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; +using SabreTools.CommandLine; +using SabreTools.CommandLine.Inputs; using SabreTools.IO.Extensions; using SabreTools.Serialization; using SabreTools.Serialization.Wrappers; @@ -8,6 +11,14 @@ namespace ExtractionTool { class Program { + #region Constants + + private const string _debugName = "debug"; + private const string _helpName = "help"; + private const string _outputPathName = "output-path"; + + #endregion + static void Main(string[] args) { #if NET462_OR_GREATER || NETCOREAPP @@ -15,30 +26,85 @@ namespace ExtractionTool System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); #endif - // 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 == null || args.Length == 0) { - Options.DisplayHelp(); + commandSet.OutputAllHelp(); + return; + } + + // Loop through and process the options + int firstFileIndex = 0; + for (; firstFileIndex < args.Length; firstFileIndex++) + { + string arg = args[firstFileIndex]; + + var input = commandSet.GetTopLevel(arg); + if (input == null) + 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), + OutputPath = commandSet.GetString(_outputPathName) ?? string.Empty, + }; + + // Validate the output path + if (!options.ValidateExtractionPath()) + { + commandSet.OutputAllHelp(); return; } // Loop through the input paths - foreach (string inputPath in options.InputPaths) + for (int i = firstFileIndex; i < args.Length; i++) { - ExtractPath(inputPath, options.OutputPath, options.Debug); + string arg = args[i]; + ExtractPath(arg, options); } } + /// + /// Create the command set for the program + /// + private static CommandSet CreateCommands() + { + List header = [ + "Extraction Tool", + string.Empty, + "ExtractionTool file|directory ...", + string.Empty, + ]; + + 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 StringInput(_outputPathName, ["-o", "--outdir"], "Set output path for extraction (required)")); + + return commandSet; + } + /// /// Wrapper to extract data for a single path /// /// File or directory path - /// Output directory path - /// Enable including debug information - private static void ExtractPath(string path, string outputDirectory, bool includeDebug) + /// User-defined options + private static void ExtractPath(string path, Options options) { // Normalize by getting the full path path = Path.GetFullPath(path); @@ -47,13 +113,13 @@ namespace ExtractionTool // Check if the file or directory exists if (File.Exists(path)) { - ExtractFile(path, outputDirectory, includeDebug); + ExtractFile(path, options); } else if (Directory.Exists(path)) { foreach (string file in IOExtensions.SafeEnumerateFiles(path, "*", SearchOption.AllDirectories)) { - ExtractFile(file, outputDirectory, includeDebug); + ExtractFile(file, options); } } else @@ -65,7 +131,9 @@ namespace ExtractionTool /// /// Print information for a single file, if possible /// - private static void ExtractFile(string file, string outputDirectory, bool includeDebug) + /// File path + /// User-defined options + private static void ExtractFile(string file, Options options) { Console.WriteLine($"Attempting to extract all files from {file}"); using Stream stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); @@ -82,7 +150,7 @@ namespace ExtractionTool } catch (Exception ex) { - if (includeDebug) Console.Error.WriteLine(ex); + if (options.Debug) Console.Error.WriteLine(ex); return; } @@ -91,7 +159,7 @@ namespace ExtractionTool var wrapper = WrapperFactory.CreateWrapper(ft, stream); // Create the output directory - Directory.CreateDirectory(outputDirectory); + Directory.CreateDirectory(options.OutputPath); // Print the preamble Console.WriteLine($"Attempting to extract from '{wrapper?.Description() ?? "UNKNOWN"}'"); @@ -101,142 +169,142 @@ namespace ExtractionTool { // 7-zip case SevenZip sz: - sz.Extract(outputDirectory, includeDebug); + sz.Extract(options.OutputPath, options.Debug); break; // BFPK archive case BFPK bfpk: - bfpk.Extract(outputDirectory, includeDebug); + bfpk.Extract(options.OutputPath, options.Debug); break; // BSP case BSP bsp: - bsp.Extract(outputDirectory, includeDebug); + bsp.Extract(options.OutputPath, options.Debug); break; // bzip2 case BZip2 bzip2: - bzip2.Extract(outputDirectory, includeDebug); + bzip2.Extract(options.OutputPath, options.Debug); break; // CFB case CFB cfb: - cfb.Extract(outputDirectory, includeDebug); + cfb.Extract(options.OutputPath, options.Debug); break; // GCF case GCF gcf: - gcf.Extract(outputDirectory, includeDebug); + gcf.Extract(options.OutputPath, options.Debug); break; // gzip case GZip gzip: - gzip.Extract(outputDirectory, includeDebug); + gzip.Extract(options.OutputPath, options.Debug); break; // InstallShield Archive V3 (Z) case InstallShieldArchiveV3 isv3: - isv3.Extract(outputDirectory, includeDebug); + isv3.Extract(options.OutputPath, options.Debug); break; // IS-CAB archive case InstallShieldCabinet iscab: - iscab.Extract(outputDirectory, includeDebug); + iscab.Extract(options.OutputPath, options.Debug); break; // LZ-compressed file, KWAJ variant case LZKWAJ kwaj: - kwaj.Extract(outputDirectory, includeDebug); + kwaj.Extract(options.OutputPath, options.Debug); break; // LZ-compressed file, QBasic variant case LZQBasic qbasic: - qbasic.Extract(outputDirectory, includeDebug); + qbasic.Extract(options.OutputPath, options.Debug); break; // LZ-compressed file, SZDD variant case LZSZDD szdd: - szdd.Extract(outputDirectory, includeDebug); + szdd.Extract(options.OutputPath, options.Debug); break; // Microsoft Cabinet archive case MicrosoftCabinet mscab: - mscab.Extract(outputDirectory, includeDebug); + mscab.Extract(options.OutputPath, options.Debug); break; // MoPaQ (MPQ) archive case MoPaQ mpq: - mpq.Extract(outputDirectory, includeDebug); + mpq.Extract(options.OutputPath, options.Debug); break; // New Executable case NewExecutable nex: - nex.Extract(outputDirectory, includeDebug); + nex.Extract(options.OutputPath, options.Debug); break; // PAK case PAK pak: - pak.Extract(outputDirectory, includeDebug); + pak.Extract(options.OutputPath, options.Debug); break; // PFF case PFF pff: - pff.Extract(outputDirectory, includeDebug); + pff.Extract(options.OutputPath, options.Debug); break; // PKZIP case PKZIP pkzip: - pkzip.Extract(outputDirectory, includeDebug); + pkzip.Extract(options.OutputPath, options.Debug); break; // Portable Executable case PortableExecutable pex: - pex.Extract(outputDirectory, includeDebug); + pex.Extract(options.OutputPath, options.Debug); break; // Quantum case Quantum quantum: - quantum.Extract(outputDirectory, includeDebug); + quantum.Extract(options.OutputPath, options.Debug); break; // RAR case RAR rar: - rar.Extract(outputDirectory, includeDebug); + rar.Extract(options.OutputPath, options.Debug); break; // SGA case SGA sga: - sga.Extract(outputDirectory, includeDebug); + sga.Extract(options.OutputPath, options.Debug); break; // Tape Archive case TapeArchive tar: - tar.Extract(outputDirectory, includeDebug); + tar.Extract(options.OutputPath, options.Debug); break; // VBSP case VBSP vbsp: - vbsp.Extract(outputDirectory, includeDebug); + vbsp.Extract(options.OutputPath, options.Debug); break; // VPK case VPK vpk: - vpk.Extract(outputDirectory, includeDebug); + vpk.Extract(options.OutputPath, options.Debug); break; // WAD3 case WAD3 wad: - wad.Extract(outputDirectory, includeDebug); + wad.Extract(options.OutputPath, options.Debug); break; // xz case XZ xz: - xz.Extract(outputDirectory, includeDebug); + xz.Extract(options.OutputPath, options.Debug); break; // XZP case XZP xzp: - xzp.Extract(outputDirectory, includeDebug); + xzp.Extract(options.OutputPath, options.Debug); break; // Everything else diff --git a/InfoPrint/InfoPrint.csproj b/InfoPrint/InfoPrint.csproj index 11cb82a5..bdcebf34 100644 --- a/InfoPrint/InfoPrint.csproj +++ b/InfoPrint/InfoPrint.csproj @@ -32,6 +32,7 @@ + diff --git a/InfoPrint/Options.cs b/InfoPrint/Options.cs index 6d80dec0..2689ffc8 100644 --- a/InfoPrint/Options.cs +++ b/InfoPrint/Options.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; - namespace InfoPrint { /// @@ -8,134 +5,26 @@ namespace InfoPrint /// internal sealed class Options { - #region Properties - /// /// Enable debug output for relevant operations /// - public bool Debug { get; private set; } = false; + public bool Debug { get; set; } /// /// Output information to file only, skip printing to console /// - public bool FileOnly { get; private set; } = false; + public bool FileOnly { get; set; } /// /// Print external file hashes /// - public bool Hash { get; private set; } = false; - - /// - /// Set of input paths to use for operations - /// - public List InputPaths { get; private set; } = []; + public bool Hash { get; set; } #if NETCOREAPP /// /// Enable JSON output /// - public bool Json { get; private set; } = false; + public bool Json { get; set; } #endif - - #endregion - - /// - /// Parse commandline arguments into an Options object - /// - public static Options? ParseOptions(string[] args) - { - // If we have invalid arguments - if (args == null || args.Length == 0) - return null; - - // Create an Options object - var options = new Options(); - - // Parse the features - int index = 0; - for (; index < args.Length; index++) - { - string arg = args[index]; - bool featureFound = false; - switch (arg) - { - case "-?": - case "-h": - case "--help": - return null; - - default: - break; - } - - // If the flag wasn't a feature - if (!featureFound) - break; - } - - // Parse the options and paths - for (; index < args.Length; index++) - { - string arg = args[index]; - switch (arg) - { - case "-d": - case "--debug": - options.Debug = true; - break; - - case "-c": - case "--hash": - options.Hash = true; - break; - - case "-f": - case "--file": - options.FileOnly = true; - break; - - case "-j": - case "--json": -#if NETCOREAPP - options.Json = true; -#else - Console.WriteLine("JSON output not available in .NET Framework"); -#endif - 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; - } - - return options; - } - - /// - /// Display help text - /// - public static void DisplayHelp() - { - Console.WriteLine("Information Printing Program"); - Console.WriteLine(); - Console.WriteLine("InfoPrint file|directory ..."); - Console.WriteLine(); - Console.WriteLine("Options:"); - Console.WriteLine("-?, -h, --help Display this help text and quit"); - Console.WriteLine("-d, --debug Enable debug mode"); - Console.WriteLine("-c, --hash Output file hashes"); - Console.WriteLine("-f, --file Print to file only"); -#if NETCOREAPP - Console.WriteLine("-j, --json Print info as JSON"); -#endif - } } } diff --git a/InfoPrint/Program.cs b/InfoPrint/Program.cs index 55a7d35f..83555e2a 100644 --- a/InfoPrint/Program.cs +++ b/InfoPrint/Program.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.IO; using System.Text; +using SabreTools.CommandLine; +using SabreTools.CommandLine.Inputs; using SabreTools.Hashing; using SabreTools.IO.Extensions; using SabreTools.Serialization; @@ -10,23 +13,92 @@ 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) { - // 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 == null || args.Length == 0) { - Options.DisplayHelp(); + commandSet.OutputAllHelp(); return; } - // Loop through the input paths - foreach (string inputPath in options.InputPaths) + // Loop through and process the options + int firstFileIndex = 0; + for (; firstFileIndex < args.Length; firstFileIndex++) { - PrintPathInfo(inputPath, options); + string arg = args[firstFileIndex]; + + var input = commandSet.GetTopLevel(arg); + if (input == null) + 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() + { + List header = [ + "Information Printing Program", + string.Empty, + "InfoPrint file|directory ...", + string.Empty, + ]; + + 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")); +#if NETCOREAPP + commandSet.Add(new FlagInput(_jsonName, ["-j", "--json"], "Print info as JSON")); +#endif + + return commandSet; } /// diff --git a/README.MD b/README.MD index c6bdfbd0..69b5e1b9 100644 --- a/README.MD +++ b/README.MD @@ -30,7 +30,7 @@ For the latest WIP build here: [Rolling Release](https://github.com/SabreTools/S InfoPrint file|directory ... Options: --?, -h, --help Display this help text and quit +-?, -h, --help Display this help text -d, --debug Enable debug mode -c, --hash Output file hashes -f, --file Print to file only @@ -42,10 +42,10 @@ Options: **ExtractionTool** is a reference implementation for the extraction features of the library, packaged as a standalone executable for all supported platforms. It will attempt to detect and extract many supported file types. See the table below for supported extraction functionality. ```text -ExtractionTool.exe file|directory ... +ExtractionTool file|directory ... Options: --?, -h, --help Display this help text and quit +-?, -h, --help Display this help text -d, --debug Enable debug mode -o, --outdir [PATH] Set output path for extraction (required) ```