using System; using System.Collections.Generic; using System.IO; #if NET40 using System.Threading.Tasks; #endif using MPF.CLI.Features; using MPF.Frontend; using MPF.Frontend.Features; using MPF.Frontend.Tools; using SabreTools.CommandLine; using SabreTools.CommandLine.Features; using SabreTools.CommandLine.Inputs; using SabreTools.RedumpLib.Data; using SabreTools.RedumpLib.Web; namespace MPF.CLI { public class Program { #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 public static void Main(string[] args) { // Load options from the config file var options = OptionsLoader.LoadFromConfig(); if (options.FirstRun) { // Application paths options.AaruPath = "FILL ME IN"; options.DiscImageCreatorPath = "FILL ME IN"; options.RedumperPath = "FILL ME IN"; options.InternalProgram = InternalProgram.NONE; // Reset first run options.FirstRun = false; OptionsLoader.SaveToConfig(options); // Display non-error message DisplayHelp("First-run detected! Please fill out config.json and run again."); return; } // Create the command set var commandSet = CreateCommands(); // If we have no args, show the help and quit if (args == null || args.Length == 0) { DisplayHelp(); return; } // Setup common outputs CommandOptions opts; RedumpSystem? knownSystem; // Get the first argument as a feature flag string featureName = args[0]; // Try processing the standalone arguments var topLevel = commandSet.GetTopLevel(featureName); switch (topLevel) { // Standalone Options case Help: DisplayHelp(); return; case VersionFeature version: version.Execute(); return; case ListCodesFeature lc: lc.Execute(); return; case ListMediaTypesFeature lm: lm.Execute(); return; case ListProgramsFeature lp: lp.Execute(); return; case ListSystemsFeature ls: ls.Execute(); return; // Interactive Mode case InteractiveFeature interactive: interactive.Execute(); opts = interactive.CommandOptions; options = interactive.Options; knownSystem = interactive.System; break; // Default Behavior default: var mainFeature = new MainFeature(); mainFeature.ProcessArgs(args, 0); opts = mainFeature.CommandOptions; options = mainFeature.Options; knownSystem = Extensions.ToRedumpSystem(featureName.Trim('"')); // Validate the supplied credentials if (options.RetrieveMatchInformation && !string.IsNullOrEmpty(options.RedumpUsername) && !string.IsNullOrEmpty(options.RedumpPassword)) { bool? validated = RedumpClient.ValidateCredentials(options.RedumpUsername!, options.RedumpPassword!).GetAwaiter().GetResult(); string message = validated switch { true => "Redump username and password accepted!", false => "Redump username and password denied!", null => "An error occurred validating your credentials!", }; Console.WriteLine(message); } break; } // Validate the internal program switch (options.InternalProgram) { case InternalProgram.Aaru: if (!File.Exists(options.AaruPath)) { DisplayHelp("A path needs to be supplied in config.json for Aaru, exiting..."); return; } break; case InternalProgram.DiscImageCreator: if (!File.Exists(options.DiscImageCreatorPath)) { DisplayHelp("A path needs to be supplied in config.json for DIC, exiting..."); return; } break; case InternalProgram.Redumper: if (!File.Exists(options.RedumperPath)) { DisplayHelp("A path needs to be supplied in config.json for Redumper, exiting..."); return; } break; default: DisplayHelp($"{options.InternalProgram} is not a supported dumping program, exiting..."); break; } // Ensure we have the values we need if (opts.CustomParams == null && (opts.DevicePath == null || opts.FilePath == null)) { DisplayHelp("Both a device path and file path need to be supplied, exiting..."); return; } if (options.InternalProgram == InternalProgram.DiscImageCreator && opts.CustomParams == null && (opts.MediaType == null || opts.MediaType == MediaType.NONE)) { DisplayHelp("Media type is required for DiscImageCreator, exiting..."); return; } // Normalize the file path if (opts.FilePath != null) opts.FilePath = FrontendTool.NormalizeOutputPaths(opts.FilePath, getFullPath: true); // Get the speed from the options int speed = opts.DriveSpeed ?? FrontendTool.GetDefaultSpeedForMediaType(opts.MediaType, options); // Populate an environment var drive = Drive.Create(null, opts.DevicePath ?? string.Empty); var env = new DumpEnvironment(options, opts.FilePath, drive, knownSystem, options.InternalProgram); env.SetExecutionContext(opts.MediaType, null); env.SetProcessor(); // Process the parameters string? paramStr = opts.CustomParams ?? env.GetFullParameters(opts.MediaType, speed); if (string.IsNullOrEmpty(paramStr)) { DisplayHelp("No valid environment could be created, exiting..."); return; } env.SetExecutionContext(opts.MediaType, paramStr); // Invoke the dumping program Console.WriteLine($"Invoking {options.InternalProgram} using '{paramStr}'"); var dumpResult = env.Run(opts.MediaType).GetAwaiter().GetResult(); Console.WriteLine(dumpResult.Message); if (!dumpResult) return; // If it was not a dumping command if (!env.IsDumpingCommand()) { Console.WriteLine("Execution not recognized as dumping command, skipping processing..."); return; } // If we have a mounted path, replace the environment if (opts.MountedPath != null && Directory.Exists(opts.MountedPath)) { drive = Drive.Create(null, opts.MountedPath); env = new DumpEnvironment(options, opts.FilePath, drive, knownSystem, internalProgram: null); env.SetExecutionContext(opts.MediaType, null); env.SetProcessor(); } // Finally, attempt to do the output dance var verifyResult = env.VerifyAndSaveDumpOutput() .ConfigureAwait(false).GetAwaiter().GetResult(); Console.WriteLine(verifyResult.Message); } /// /// Create the command set for the program /// private static CommandSet CreateCommands() { List header = [ "MPF.CLI [standalone|system] [options] ...", string.Empty, ]; List footer = [ string.Empty, "Dumping program paths and other settings can be found in the config.json file", "generated next to the program by default. Ensure that all settings are to user", "preference before running MPF.CLI.", string.Empty, "Custom dumping parameters, if used, will fully replace the default parameters.", "All dumping parameters need to be supplied if doing this.", "Otherwise, both a drive path and output file path are required.", string.Empty, "Mounted filesystem path is only recommended on OSes that require block", "device dumping, usually Linux and macOS.", string.Empty, ]; var commandSet = new CommandSet(header, footer); // Standalone Options commandSet.Add(new Help()); commandSet.Add(new VersionFeature()); commandSet.Add(new ListCodesFeature()); commandSet.Add(new ListMediaTypesFeature()); commandSet.Add(new ListSystemsFeature()); commandSet.Add(new ListProgramsFeature()); commandSet.Add(new InteractiveFeature()); // CLI Options commandSet.Add(_useInput); commandSet.Add(_mediaTypeInput); commandSet.Add(_deviceInput); commandSet.Add(_mountedInput); commandSet.Add(_fileInput); commandSet.Add(_speedInput); commandSet.Add(_customInput); return commandSet; } /// /// Display help for MPF.CLI /// /// Error string to prefix the help text with private static void DisplayHelp(string? error = null) { if (error != null) Console.WriteLine(error); Console.WriteLine("Usage:"); Console.WriteLine("MPF.CLI [options]"); Console.WriteLine(); Console.WriteLine("Standalone Options:"); Console.WriteLine("?, h, help Show this help text"); Console.WriteLine("version Print the program version"); Console.WriteLine("lc, listcodes List supported comment/content site codes"); Console.WriteLine("lm, listmedia List supported media types"); Console.WriteLine("ls, listsystems List supported system types"); Console.WriteLine("lp, listprograms List supported dumping program outputs"); Console.WriteLine("i, interactive Enable interactive mode"); Console.WriteLine(); Console.WriteLine("CLI Options:"); Console.WriteLine("-u, --use Override configured dumping program name"); Console.WriteLine("-t, --mediatype Set media type for dumping (Required for DIC)"); Console.WriteLine("-d, --device Physical drive path (Required if no custom parameters set)"); Console.WriteLine("-m, --mounted Mounted filesystem path for additional checks"); Console.WriteLine("-f, --file \"\" Output file path (Required if no custom parameters set)"); Console.WriteLine("-s, --speed Override default dumping speed"); Console.WriteLine("-c, --custom \"\" Custom parameters to use"); Console.WriteLine(); Console.WriteLine("Dumping program paths and other settings can be found in the config.json file"); Console.WriteLine("generated next to the program by default. Ensure that all settings are to user"); Console.WriteLine("preference before running MPF.CLI."); Console.WriteLine(); Console.WriteLine("Custom dumping parameters, if used, will fully replace the default parameters."); Console.WriteLine("All dumping parameters need to be supplied if doing this."); Console.WriteLine("Otherwise, both a drive path and output file path are required."); Console.WriteLine(); Console.WriteLine("Mounted filesystem path is only recommended on OSes that require block"); Console.WriteLine("device dumping, usually Linux and macOS."); Console.WriteLine(); } /// /// Represents commandline options /// internal class CommandOptions { /// /// Media type to dump /// /// Required for DIC and if custom parameters not set public MediaType? MediaType { get; set; } = null; /// /// Path to the device to dump /// /// Required if custom parameters are not set public string? DevicePath { get; set; } = null; /// /// Path to the mounted filesystem to check /// /// Should only be used when the device path is not readable public string? MountedPath { get; set; } = null; /// /// Path to the output file /// /// Required if custom parameters are not set public string? FilePath { get; set; } = null; /// /// Override drive speed /// public int? DriveSpeed { get; set; } = null; /// /// Custom parameters for dumping /// public string? CustomParams { get; set; } = null; } } }