diff --git a/CHANGELIST.md b/CHANGELIST.md index 274972e0..09c162ab 100644 --- a/CHANGELIST.md +++ b/CHANGELIST.md @@ -9,6 +9,7 @@ - Minor cleanup around last added - Add placeholder command set creation - Reduce unnecessary shared code +- Create and use main features for CLI and Check ### 3.4.2 (2025-09-30) diff --git a/MPF.CLI/Features/InteractiveFeature.cs b/MPF.CLI/Features/InteractiveFeature.cs index 40d83f79..c84e29e2 100644 --- a/MPF.CLI/Features/InteractiveFeature.cs +++ b/MPF.CLI/Features/InteractiveFeature.cs @@ -45,6 +45,7 @@ namespace MPF.CLI.Features Options = OptionsLoader.LoadFromConfig(); } + /// public override bool Execute() { // Create return values diff --git a/MPF.CLI/Features/MainFeature.cs b/MPF.CLI/Features/MainFeature.cs new file mode 100644 index 00000000..0cc83608 --- /dev/null +++ b/MPF.CLI/Features/MainFeature.cs @@ -0,0 +1,157 @@ + +using MPF.Frontend; +using MPF.Frontend.Tools; +using SabreTools.CommandLine.Inputs; + +namespace MPF.CLI.Features +{ + internal sealed class MainFeature : SabreTools.CommandLine.Feature + { + #region Feature Definition + + public const string DisplayName = "main"; + + /// Flags are unused + private static readonly string[] _flags = []; + + /// Description is unused + internal const string _description = ""; + + #endregion + + #region Inputs + + private const string _customName = "custom"; + private static readonly StringInput _customInput = new(_customName, ["-c", "--custom"], "Custom parameters to use"); + + private const string _deviceName = "device"; + private static readonly StringInput _deviceInput = new(_deviceName, ["-d", "--device"], "Physical drive path (Required if no custom parameters set)"); + + private const string _fileName = "file"; + private static readonly StringInput _fileInput = new(_fileName, ["-f", "--file"], "Output file path (Required if no custom parameters set)"); + + private const string _mediaTypeName = "media-type"; + private static readonly StringInput _mediaTypeInput = new(_mediaTypeName, ["-t", "--mediatype"], "Set media type for dumping (Required for DIC)"); + + private const string _mountedName = "mounted"; + private static readonly StringInput _mountedInput = new(_mountedName, ["-m", "--mounted"], "Mounted filesystem path for additional checks"); + + private const string _speedName = "speed"; + private static readonly Int32Input _speedInput = new(_speedName, ["-s", "--speed"], "Override default dumping speed"); + + private const string _useName = "use"; + private static readonly StringInput _useInput = new(_useName, ["-u", "--use"], "Override configured dumping program name"); + + #endregion + + #region Properties + + /// + /// Progrma-specific options + /// + public Program.CommandOptions CommandOptions { get; private set; } + + /// + /// User-defined options + /// + public Options Options { get; } + + #endregion + + public MainFeature() + : base(DisplayName, _flags, _description) + { + CommandOptions = new Program.CommandOptions(); + Options = new Options() + { + // Internal Program + InternalProgram = InternalProgram.NONE, + + // Extra Dumping Options + ScanForProtection = false, + AddPlaceholders = true, + PullAllInformation = false, + AddFilenameSuffix = false, + OutputSubmissionJSON = false, + IncludeArtifacts = false, + CompressLogFiles = false, + DeleteUnnecessaryFiles = false, + CreateIRDAfterDumping = false, + + // Protection Scanning Options + ScanArchivesForProtection = true, + IncludeDebugProtectionInformation = false, + HideDriveLetters = false, + + // Redump Login Information + RetrieveMatchInformation = true, + RedumpUsername = null, + RedumpPassword = null, + }; + + Add(_useInput); + Add(_mediaTypeInput); + Add(_deviceInput); + Add(_mountedInput); + Add(_fileInput); + Add(_speedInput); + Add(_customInput); + } + + /// + public override bool ProcessArgs(string[] args, int index) + { + // If we have no arguments, just return + if (args == null || args.Length == 0) + return true; + + // If we have an invalid start index, just return + if (index < 0 || index >= args.Length) + return false; + + // Loop through the arguments and parse out values + for (; index < args.Length; index++) + { + // Use specific program + if (_useInput.ProcessInput(args, ref index)) + Options.InternalProgram = _useInput.Value.ToInternalProgram(); + + // Set a media type + else if (_mediaTypeInput.ProcessInput(args, ref index)) + CommandOptions.MediaType = OptionsLoader.ToMediaType(_mediaTypeInput.Value?.Trim('"')); + + // Use a device path + else if (_deviceInput.ProcessInput(args, ref index)) + CommandOptions.DevicePath = _deviceInput.Value; + + // Use a mounted path for physical checks + else if (_mountedInput.ProcessInput(args, ref index)) + CommandOptions.MountedPath = _mountedInput.Value; + + // Use a file path + else if (_fileInput.ProcessInput(args, ref index)) + CommandOptions.FilePath = _fileInput.Value; + + // Set an override speed + else if (_speedInput.ProcessInput(args, ref index)) + CommandOptions.DriveSpeed = _speedInput.Value; + + // Use a custom parameters + else if (_customInput.ProcessInput(args, ref index)) + CommandOptions.CustomParams = _customInput.Value; + + // Default, add to inputs + else + Inputs.Add(args[index]); + } + + return true; + } + + /// + public override bool Execute() => true; + + /// + public override bool VerifyInputs() => true; + } +} diff --git a/MPF.CLI/Program.cs b/MPF.CLI/Program.cs index 0589106a..5374741b 100644 --- a/MPF.CLI/Program.cs +++ b/MPF.CLI/Program.cs @@ -104,7 +104,11 @@ namespace MPF.CLI // Default Behavior default: - // Parse the system from the first argument + var mainFeature = new MainFeature(); + mainFeature.ProcessArgs(args, 0); + + opts = mainFeature.CommandOptions; + options = mainFeature.Options; knownSystem = Extensions.ToRedumpSystem(featureName.Trim('"')); // Validate the supplied credentials @@ -123,9 +127,6 @@ namespace MPF.CLI Console.WriteLine(message); } - // Process any custom parameters - int startIndex = 1; - opts = LoadFromArguments(args, options, ref startIndex); break; } @@ -332,64 +333,6 @@ namespace MPF.CLI Console.WriteLine(); } - /// - /// Load the current set of options from application arguments - /// - private static CommandOptions LoadFromArguments(string[] args, Options options, ref int startIndex) - { - // Create return values - var opts = new CommandOptions(); - - // If we have no arguments, just return - if (args == null || args.Length == 0) - { - startIndex = 0; - return opts; - } - - // If we have an invalid start index, just return - if (startIndex < 0 || startIndex >= args.Length) - return opts; - - // Loop through the arguments and parse out values - for (; startIndex < args.Length; startIndex++) - { - // Use specific program - if (_useInput.ProcessInput(args, ref startIndex)) - options.InternalProgram = _useInput.Value.ToInternalProgram(); - - // Set a media type - else if (_mediaTypeInput.ProcessInput(args, ref startIndex)) - opts.MediaType = OptionsLoader.ToMediaType(_mediaTypeInput.Value?.Trim('"')); - - // Use a device path - else if (_deviceInput.ProcessInput(args, ref startIndex)) - opts.DevicePath = _deviceInput.Value; - - // Use a mounted path for physical checks - else if (_mountedInput.ProcessInput(args, ref startIndex)) - opts.MountedPath = _mountedInput.Value; - - // Use a file path - else if (_fileInput.ProcessInput(args, ref startIndex)) - opts.FilePath = _fileInput.Value; - - // Set an override speed - else if (_speedInput.ProcessInput(args, ref startIndex)) - opts.DriveSpeed = _speedInput.Value; - - // Use a custom parameters - else if (_customInput.ProcessInput(args, ref startIndex)) - opts.CustomParams = _customInput.Value; - - // Default, we fall out - else - break; - } - - return opts; - } - /// /// Represents commandline options /// diff --git a/MPF.Check/Features/InteractiveFeature.cs b/MPF.Check/Features/InteractiveFeature.cs index efffd7c7..7b3e193c 100644 --- a/MPF.Check/Features/InteractiveFeature.cs +++ b/MPF.Check/Features/InteractiveFeature.cs @@ -69,6 +69,7 @@ namespace MPF.Check.Features }; } + /// public override bool Execute() { // Create return values diff --git a/MPF.Check/Features/MainFeature.cs b/MPF.Check/Features/MainFeature.cs new file mode 100644 index 00000000..ef1c649a --- /dev/null +++ b/MPF.Check/Features/MainFeature.cs @@ -0,0 +1,256 @@ + +using MPF.Frontend; +using SabreTools.CommandLine.Inputs; +using SabreTools.RedumpLib; + +namespace MPF.Check.Features +{ + internal sealed class MainFeature : SabreTools.CommandLine.Feature + { + #region Feature Definition + + public const string DisplayName = "main"; + + /// Flags are unused + private static readonly string[] _flags = []; + + /// Description is unused + internal const string _description = ""; + + #endregion + + #region Inputs + + internal const string _createIrdName = "create-ird"; + private readonly FlagInput _createIrdInput = new(_createIrdName, "--create-ird", "Create IRD from output files (PS3 only)"); + + internal const string _deleteName = "delete"; + private readonly FlagInput _deleteInput = new(_deleteName, ["-d", "--delete"], "Enable unnecessary file deletion"); + + internal const string _disableArchivesName = "disable-archives"; + private readonly FlagInput _disableArchivesInput = new(_disableArchivesName, "--disable-archives", "Disable scanning archives (requires --scan)"); + + internal const string _enableDebugName = "enable-debug"; + private readonly FlagInput _enableDebugInput = new(_enableDebugName, "--enable-debug", "Enable debug protection information (requires --scan)"); + + internal const string _hideDriveLettersName = "hide-drive-letters"; + private readonly FlagInput _hideDriveLettersInput = new(_hideDriveLettersName, "--hide-drive-letters", "Hide drive letters from scan output (requires --scan)"); + + internal const string _includeArtifactsName = "include-artifacts"; + private readonly FlagInput _includeArtifactsInput = new(_includeArtifactsName, "--include-artifacts", "Include artifacts in JSON (requires --json)"); + + internal const string _jsonName = "json"; + private readonly FlagInput _jsonInput = new(_jsonName, ["-j", "--json"], "Enable submission JSON output"); + + internal const string _loadSeedName = "load-seed"; + private readonly StringInput _loadSeedInput = new(_loadSeedName, "--load-seed", "Load a seed submission JSON for user information"); + + internal const string _noPlaceholdersName = "no-placeholders"; + private readonly FlagInput _noPlaceholdersInput = new(_noPlaceholdersName, "--no-placeholders", "Disable placeholder values in submission info"); + + internal const string _noRetrieveName = "no-retrieve"; + private readonly FlagInput _noRetrieveInput = new(_noRetrieveName, "--no-retrieve", "Disable retrieving match information from Redump"); + + internal const string _pathName = "path"; + private readonly StringInput _pathInput = new(_pathName, ["-p", "--path"], "Physical drive path for additional checks"); + + internal const string _pullAllName = "pull-all"; + private readonly FlagInput _pullAllInput = new(_pullAllName, "--pull-all", "Pull all information from Redump (requires --credentials)"); + + internal const string _scanName = "scan"; + private readonly FlagInput _scanInput = new(_scanName, ["-s", "--scan"], "Enable copy protection scan (requires --path)"); + + internal const string _suffixName = "suffix"; + private readonly FlagInput _suffixInput = new(_suffixName, ["-x", "--suffix"], "Enable adding filename suffix"); + + internal const string _useName = "use"; + private readonly StringInput _useInput = new(_useName, ["-u", "--use"], "Override configured dumping program name"); + + internal const string _zipName = "zip"; + private readonly FlagInput _zipInput = new(_zipName, ["-z", "--zip"], "Enable log file compression"); + + #endregion + + #region Properties + + /// + /// Progrma-specific options + /// + public Program.CommandOptions CommandOptions { get; private set; } + + /// + /// User-defined options + /// + public Options Options { get; } + + #endregion + + public MainFeature() + : base(DisplayName, _flags, _description) + { + CommandOptions = new Program.CommandOptions(); + Options = new Options() + { + // Internal Program + InternalProgram = InternalProgram.NONE, + + // Extra Dumping Options + ScanForProtection = false, + AddPlaceholders = true, + PullAllInformation = false, + AddFilenameSuffix = false, + OutputSubmissionJSON = false, + IncludeArtifacts = false, + CompressLogFiles = false, + DeleteUnnecessaryFiles = false, + CreateIRDAfterDumping = false, + + // Protection Scanning Options + ScanArchivesForProtection = true, + IncludeDebugProtectionInformation = false, + HideDriveLetters = false, + + // Redump Login Information + RetrieveMatchInformation = true, + RedumpUsername = null, + RedumpPassword = null, + }; + + Add(_useInput); + Add(_loadSeedInput); + Add(_noPlaceholdersInput); + Add(_createIrdInput); + Add(_noRetrieveInput); + // TODO: Figure out how to work with the credentials input + Add(_pullAllInput); + Add(_pathInput); + Add(_scanInput); + Add(_disableArchivesInput); + Add(_enableDebugInput); + Add(_hideDriveLettersInput); + Add(_suffixInput); + Add(_jsonInput); + Add(_includeArtifactsInput); + Add(_zipInput); + Add(_deleteInput); + } + + /// + public override bool ProcessArgs(string[] args, int index) + { + // These values require multiple parts to be active + bool scan = false, + enableArchives = true, + enableDebug = false, + hideDriveLetters = false; + + // If we have no arguments, just return + if (args == null || args.Length == 0) + return true; + + // Invalid index values are not processed + if (index < 0 || index >= args.Length) + return false; + + // Loop through the arguments and parse out values + for (; index < args.Length; index++) + { + // Use specific program + if (_useInput.ProcessInput(args, ref index)) + Options.InternalProgram = _useInput.Value.ToInternalProgram(); + + // Include seed info file + else if (_loadSeedInput.ProcessInput(args, ref index)) + CommandOptions.Seed = Builder.CreateFromFile(_loadSeedInput.Value); + + // Disable placeholder values in submission info + else if (_noPlaceholdersInput.ProcessInput(args, ref index)) + Options.AddPlaceholders = false; + + // Create IRD from output files (PS3 only) + else if (_createIrdInput.ProcessInput(args, ref index)) + Options.CreateIRDAfterDumping = true; + + // Retrieve Redump match information + else if (_noRetrieveInput.ProcessInput(args, ref index)) + Options.RetrieveMatchInformation = false; + + // Redump login + else if (args[index].StartsWith("-c=") || args[index].StartsWith("--credentials=")) + { + string[] credentials = args[index].Split('=')[1].Split(';'); + Options.RedumpUsername = credentials[0]; + Options.RedumpPassword = credentials[1]; + } + else if (args[index] == "-c" || args[index] == "--credentials") + { + Options.RedumpUsername = args[index + 1]; + Options.RedumpPassword = args[index + 2]; + index += 2; + } + + // Pull all information (requires Redump login) + else if (_pullAllInput.ProcessInput(args, ref index)) + Options.PullAllInformation = true; + + // Use a device path for physical checks + else if (_pathInput.ProcessInput(args, ref index)) + CommandOptions.DevicePath = _pathInput.Value; + + // Scan for protection (requires device path) + else if (_scanInput.ProcessInput(args, ref index)) + scan = true; + + // Disable scanning archives (requires --scan) + else if (_scanInput.ProcessInput(args, ref index)) + enableArchives = false; + + // Enable debug protection information (requires --scan) + else if (_enableDebugInput.ProcessInput(args, ref index)) + enableDebug = true; + + // Hide drive letters from scan output (requires --scan) + else if (_hideDriveLettersInput.ProcessInput(args, ref index)) + hideDriveLetters = true; + + // Add filename suffix + else if (_suffixInput.ProcessInput(args, ref index)) + Options.AddFilenameSuffix = true; + + // Output submission JSON + else if (_jsonInput.ProcessInput(args, ref index)) + Options.OutputSubmissionJSON = true; + + // Include JSON artifacts + else if (_includeArtifactsInput.ProcessInput(args, ref index)) + Options.IncludeArtifacts = true; + + // Compress log and extraneous files + else if (_zipInput.ProcessInput(args, ref index)) + Options.CompressLogFiles = true; + + // Delete unnecessary files + else if (_deleteInput.ProcessInput(args, ref index)) + Options.DeleteUnnecessaryFiles = true; + + // Default, add to inputs + else + Inputs.Add(args[index]); + } + + // Now deal with the complex options + Options.ScanForProtection = scan && !string.IsNullOrEmpty(CommandOptions.DevicePath); + Options.ScanArchivesForProtection = enableArchives && scan && !string.IsNullOrEmpty(CommandOptions.DevicePath); + Options.IncludeDebugProtectionInformation = enableDebug && scan && !string.IsNullOrEmpty(CommandOptions.DevicePath); + Options.HideDriveLetters = hideDriveLetters && scan && !string.IsNullOrEmpty(CommandOptions.DevicePath); + + return true; + } + + /// + public override bool Execute() => true; + + /// + public override bool VerifyInputs() => true; + } +} diff --git a/MPF.Check/Program.cs b/MPF.Check/Program.cs index ef28fe25..b32337be 100644 --- a/MPF.Check/Program.cs +++ b/MPF.Check/Program.cs @@ -10,7 +10,6 @@ using MPF.Frontend.Features; using SabreTools.CommandLine; using SabreTools.CommandLine.Features; using SabreTools.CommandLine.Inputs; -using SabreTools.RedumpLib; using SabreTools.RedumpLib.Data; using SabreTools.RedumpLib.Web; @@ -115,40 +114,16 @@ namespace MPF.Check // Default Behavior default: - // Create a default options object - options = new Options() - { - // Internal Program - InternalProgram = InternalProgram.NONE, + startIndex = 0; - // Extra Dumping Options - ScanForProtection = false, - AddPlaceholders = true, - PullAllInformation = false, - AddFilenameSuffix = false, - OutputSubmissionJSON = false, - IncludeArtifacts = false, - CompressLogFiles = false, - DeleteUnnecessaryFiles = false, - CreateIRDAfterDumping = false, + var mainFeature = new MainFeature(); + mainFeature.ProcessArgs(args, 0); - // Protection Scanning Options - ScanArchivesForProtection = true, - IncludeDebugProtectionInformation = false, - HideDriveLetters = false, - - // Redump Login Information - RetrieveMatchInformation = true, - RedumpUsername = null, - RedumpPassword = null, - }; - - // Parse the system from the first argument + opts = mainFeature.CommandOptions; + options = mainFeature.Options; knownSystem = Extensions.ToRedumpSystem(featureName.Trim('"')); - // Loop through and process options - startIndex = 1; - opts = LoadFromArguments(args, options, ref startIndex); + args = [.. mainFeature.Inputs]; break; } @@ -302,126 +277,6 @@ namespace MPF.Check Console.WriteLine(); } - /// - /// Load the current set of options from application arguments - /// - private static CommandOptions LoadFromArguments(string[] args, Options options, ref int startIndex) - { - // Create return values - var opts = new CommandOptions(); - - // These values require multiple parts to be active - bool scan = false, - enableArchives = true, - enableDebug = false, - hideDriveLetters = false; - - // If we have no arguments, just return - if (args == null || args.Length == 0) - { - startIndex = 0; - return opts; - } - - // If we have an invalid start index, just return - if (startIndex < 0 || startIndex >= args.Length) - return opts; - - // Loop through the arguments and parse out values - for (; startIndex < args.Length; startIndex++) - { - // Use specific program - if (_useInput.ProcessInput(args, ref startIndex)) - options.InternalProgram = _useInput.Value.ToInternalProgram(); - - // Include seed info file - else if (_loadSeedInput.ProcessInput(args, ref startIndex)) - opts.Seed = Builder.CreateFromFile(_loadSeedInput.Value); - - // Disable placeholder values in submission info - else if (_noPlaceholdersInput.ProcessInput(args, ref startIndex)) - options.AddPlaceholders = false; - - // Create IRD from output files (PS3 only) - else if (_createIrdInput.ProcessInput(args, ref startIndex)) - options.CreateIRDAfterDumping = true; - - // Retrieve Redump match information - else if (_noRetrieveInput.ProcessInput(args, ref startIndex)) - options.RetrieveMatchInformation = false; - - // Redump login - else if (args[startIndex].StartsWith("-c=") || args[startIndex].StartsWith("--credentials=")) - { - string[] credentials = args[startIndex].Split('=')[1].Split(';'); - options.RedumpUsername = credentials[0]; - options.RedumpPassword = credentials[1]; - } - else if (args[startIndex] == "-c" || args[startIndex] == "--credentials") - { - options.RedumpUsername = args[startIndex + 1]; - options.RedumpPassword = args[startIndex + 2]; - startIndex += 2; - } - - // Pull all information (requires Redump login) - else if (_pullAllInput.ProcessInput(args, ref startIndex)) - options.PullAllInformation = true; - - // Use a device path for physical checks - else if (_pathInput.ProcessInput(args, ref startIndex)) - opts.DevicePath = _pathInput.Value; - - // Scan for protection (requires device path) - else if (_scanInput.ProcessInput(args, ref startIndex)) - scan = true; - - // Disable scanning archives (requires --scan) - else if (_scanInput.ProcessInput(args, ref startIndex)) - enableArchives = false; - - // Enable debug protection information (requires --scan) - else if (_enableDebugInput.ProcessInput(args, ref startIndex)) - enableDebug = true; - - // Hide drive letters from scan output (requires --scan) - else if (_hideDriveLettersInput.ProcessInput(args, ref startIndex)) - hideDriveLetters = true; - - // Add filename suffix - else if (_suffixInput.ProcessInput(args, ref startIndex)) - options.AddFilenameSuffix = true; - - // Output submission JSON - else if (_jsonInput.ProcessInput(args, ref startIndex)) - options.OutputSubmissionJSON = true; - - // Include JSON artifacts - else if (_includeArtifactsInput.ProcessInput(args, ref startIndex)) - options.IncludeArtifacts = true; - - // Compress log and extraneous files - else if (_zipInput.ProcessInput(args, ref startIndex)) - options.CompressLogFiles = true; - - // Delete unnecessary files - else if (_deleteInput.ProcessInput(args, ref startIndex)) - options.DeleteUnnecessaryFiles = true; - - // Default, we fall out - else - break; - } - - // Now deal with the complex options - options.ScanForProtection = scan && !string.IsNullOrEmpty(opts.DevicePath); - options.ScanArchivesForProtection = enableArchives && scan && !string.IsNullOrEmpty(opts.DevicePath); - options.IncludeDebugProtectionInformation = enableDebug && scan && !string.IsNullOrEmpty(opts.DevicePath); - options.HideDriveLetters = hideDriveLetters && scan && !string.IsNullOrEmpty(opts.DevicePath); - - return opts; - } - /// /// Represents commandline options ///