using System; using System.IO; #if NET40 using System.Threading.Tasks; #endif using MPF.Frontend; using MPF.Frontend.Tools; using SabreTools.RedumpLib; using SabreTools.RedumpLib.Data; using SabreTools.RedumpLib.Web; namespace MPF.Check { public class Program { public static void Main(string[] args) { // Create a default options object var 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, }; // Try processing the standalone arguments bool? standaloneProcessed = OptionsLoader.ProcessStandaloneArguments(args); if (standaloneProcessed != false) { if (standaloneProcessed == null) DisplayHelp(); return; } // Setup common outputs CommandOptions opts; MediaType mediaType; RedumpSystem? knownSystem; int startIndex; // Use interactive mode if (args.Length > 0 && (args[0] == "-i" || args[0] == "--interactive")) { startIndex = 1; opts = InteractiveMode(options, out mediaType, out knownSystem); } // Use normal commandline parameters else { // Try processing the common arguments bool success = OptionsLoader.ProcessCommonArguments(args, out mediaType, out knownSystem, out var error); if (!success) { DisplayHelp(error); return; } // Loop through and process options startIndex = 2; opts = LoadFromArguments(args, options, ref startIndex); } if (options.InternalProgram == InternalProgram.NONE) { DisplayHelp("A program name needs to be provided"); return; } // 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); } // Loop through all the rest of the args for (int i = startIndex; i < args.Length; i++) { // Check for a file if (!File.Exists(args[i].Trim('"'))) { DisplayHelp($"{args[i].Trim('"')} does not exist"); return; } // Get the full file path string filepath = Path.GetFullPath(args[i].Trim('"')); // Now populate an environment Drive? drive = null; if (!string.IsNullOrEmpty(opts.DevicePath)) drive = Drive.Create(null, opts.DevicePath!); var env = new DumpEnvironment(options, filepath, drive, knownSystem, mediaType, internalProgram: null, parameters: null); // Finally, attempt to do the output dance var result = env.VerifyAndSaveDumpOutput(seedInfo: opts.Seed) .ConfigureAwait(false).GetAwaiter().GetResult(); Console.WriteLine(result.Message); } } /// /// Display help for MPF.Check /// /// 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.Check [options] ..."); Console.WriteLine(); Console.WriteLine("Standalone Options:"); Console.WriteLine("-h, -? Show this help text"); 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("Check Options:"); Console.WriteLine("-u, --use Dumping program output type [REQUIRED]"); Console.WriteLine(" --load-seed Load a seed submission JSON for user information"); Console.WriteLine(" --no-placeholders Disable placeholder values in submission info"); Console.WriteLine(" --create-ird Create IRD from output files (PS3 only)"); Console.WriteLine(" --no-retrieve Disable retrieving match information from Redump"); Console.WriteLine("-c, --credentials Redump username and password (incompatible with --no-retrieve)"); Console.WriteLine(" --pull-all Pull all information from Redump (requires --credentials)"); Console.WriteLine("-p, --path Physical drive path for additional checks"); Console.WriteLine("-s, --scan Enable copy protection scan (requires --path)"); Console.WriteLine(" --disable-archives Disable scanning archives (requires --scan)"); Console.WriteLine(" --enable-debug Enable debug protection information (requires --scan)"); Console.WriteLine(" --hide-drive-letters Hide drive letters from scan output (requires --scan)"); Console.WriteLine("-x, --suffix Enable adding filename suffix"); Console.WriteLine("-j, --json Enable submission JSON output"); Console.WriteLine(" --include-artifacts Include artifacts in JSON (requires --json)"); Console.WriteLine("-z, --zip Enable log file compression"); Console.WriteLine("-d, --delete Enable unnecessary file deletion"); Console.WriteLine(); Console.WriteLine("WARNING: Check will overwrite both any existing submission information files as well"); Console.WriteLine("as any log archives. Please make backups of those if you need to before running Check."); Console.WriteLine(); } /// /// Enable interactive mode for entering information /// private static CommandOptions InteractiveMode(Options options, out MediaType mediaType, out RedumpSystem? system) { // Create return values var opts = new CommandOptions(); mediaType = MediaType.NONE; system = null; // These values require multiple parts to be active bool scan = false, enableArchives = true, enableDebug = false, hideDriveLetters = false; // Create state values string? result = string.Empty; root: Console.Clear(); Console.WriteLine("MPF.Check Interactive Mode - Main Menu"); Console.WriteLine("-------------------------"); Console.WriteLine(); Console.WriteLine($"1) Set media type (Currently '{mediaType}')"); Console.WriteLine($"2) Set system (Currently '{system}')"); Console.WriteLine($"3) Set dumping program (Currently '{options.InternalProgram}')"); Console.WriteLine($"4) Set seed path (Currently '{opts.Seed}')"); Console.WriteLine($"5) Add placeholders (Currently '{options.AddPlaceholders}')"); Console.WriteLine($"6) Create IRD (Currently '{options.CreateIRDAfterDumping}')"); Console.WriteLine($"7) Attempt Redump matches (Currently '{options.RetrieveMatchInformation}')"); Console.WriteLine($"8) Redump credentials (Currently '{options.RedumpUsername}')"); Console.WriteLine($"9) Pull all information (Currently '{options.PullAllInformation}')"); Console.WriteLine($"A) Set device path (Currently '{opts.DevicePath}')"); Console.WriteLine($"B) Scan for protection (Currently '{scan}')"); Console.WriteLine($"C) Scan archives for protection (Currently '{enableArchives}')"); Console.WriteLine($"D) Debug protection scan output (Currently '{enableDebug}')"); Console.WriteLine($"E) Hide drive letters in protection output (Currently '{hideDriveLetters}')"); Console.WriteLine($"F) Hide filename suffix (Currently '{options.AddFilenameSuffix}')"); Console.WriteLine($"G) Output submission JSON (Currently '{options.OutputSubmissionJSON}')"); Console.WriteLine($"H) Include JSON artifacts (Currently '{options.IncludeArtifacts}')"); Console.WriteLine($"I) Compress logs (Currently '{options.CompressLogFiles}')"); Console.WriteLine($"J) Delete unnecessary files (Currently '{options.DeleteUnnecessaryFiles}')"); Console.WriteLine(); Console.WriteLine($"Q) Exit the program"); Console.WriteLine($"X) Start checking"); Console.Write("> "); result = Console.ReadLine(); switch (result) { case "1": goto mediaType; case "2": goto system; case "3": goto dumpingProgram; case "4": goto seedPath; case "5": options.AddPlaceholders = !options.AddPlaceholders; goto root; case "6": options.CreateIRDAfterDumping = !options.CreateIRDAfterDumping; goto root; case "7": options.RetrieveMatchInformation = !options.RetrieveMatchInformation; goto root; case "8": goto redumpCredentials; case "9": options.PullAllInformation = !options.PullAllInformation; goto root; case "a": case "A": goto devicePath; case "b": case "B": scan = !scan; goto root; case "c": case "C": enableArchives = !enableArchives; goto root; case "d": case "D": enableDebug = !enableDebug; goto root; case "e": case "E": hideDriveLetters = !hideDriveLetters; goto root; case "f": case "F": options.AddFilenameSuffix = !options.AddFilenameSuffix; goto root; case "g": case "G": options.OutputSubmissionJSON = !options.OutputSubmissionJSON; goto root; case "h": case "H": options.IncludeArtifacts = !options.IncludeArtifacts; goto root; case "i": case "I": options.CompressLogFiles = !options.CompressLogFiles; goto root; case "j": case "J": options.DeleteUnnecessaryFiles = !options.DeleteUnnecessaryFiles; goto root; case "q": case "Q": Environment.Exit(0); break; case "x": case "X": Console.Clear(); goto exit; case "z": case "Z": Console.WriteLine("It is pitch black. You are likely to be eaten by a grue."); Console.Write("> "); Console.ReadLine(); goto root; default: Console.WriteLine($"Invalid selection: {result}"); Console.ReadLine(); goto root; } mediaType: Console.WriteLine(); Console.WriteLine("Input the media type and press Enter:"); Console.Write("> "); result = Console.ReadLine(); mediaType = OptionsLoader.ToMediaType(result); goto root; system: Console.WriteLine(); Console.WriteLine("Input the system and press Enter:"); Console.Write("> "); result = Console.ReadLine(); system = Extensions.ToRedumpSystem(result); goto root; dumpingProgram: Console.WriteLine(); Console.WriteLine("Input the dumping program and press Enter:"); Console.Write("> "); result = Console.ReadLine(); options.InternalProgram = result.ToInternalProgram(); goto root; seedPath: Console.WriteLine(); Console.WriteLine("Input the seed path and press Enter:"); Console.Write("> "); result = Console.ReadLine(); opts.Seed = Builder.CreateFromFile(result); goto root; redumpCredentials: Console.WriteLine(); Console.WriteLine("Enter your Redumper username and press Enter:"); Console.Write("> "); options.RedumpUsername = Console.ReadLine(); Console.WriteLine("Enter your Redumper password (hidden) and press Enter:"); Console.Write("> "); options.RedumpPassword = string.Empty; while (true) { var key = Console.ReadKey(true); if (key.Key == ConsoleKey.Enter) break; options.RedumpPassword += key.KeyChar; } goto root; devicePath: Console.WriteLine(); Console.WriteLine("Input the device path and press Enter:"); Console.Write("> "); opts.DevicePath = Console.ReadLine(); goto root; exit: // 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; } /// /// 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 (args[startIndex].StartsWith("-u=") || args[startIndex].StartsWith("--use=")) { string internalProgram = args[startIndex].Split('=')[1]; options.InternalProgram = internalProgram.ToInternalProgram(); } else if (args[startIndex] == "-u" || args[startIndex] == "--use") { string internalProgram = args[startIndex + 1]; options.InternalProgram = internalProgram.ToInternalProgram(); startIndex++; } // Include seed info file else if (args[startIndex].StartsWith("--load-seed=")) { string seedInfo = args[startIndex].Split('=')[1]; opts.Seed = Builder.CreateFromFile(seedInfo); } else if (args[startIndex] == "--load-seed") { string seedInfo = args[startIndex + 1]; opts.Seed = Builder.CreateFromFile(seedInfo); startIndex++; } // Disable placeholder values in submission info else if (args[startIndex].Equals("--no-placeholders")) { options.AddPlaceholders = false; } // Create IRD from output files (PS3 only) else if (args[startIndex].Equals("--create-ird")) { options.CreateIRDAfterDumping = true; } // Retrieve Redump match information else if (args[startIndex] == "--no-retrieve") { 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 (args[startIndex].Equals("--pull-all")) { options.PullAllInformation = true; } // Use a device path for physical checks else if (args[startIndex].StartsWith("-p=") || args[startIndex].StartsWith("--path=")) { opts.DevicePath = args[startIndex].Split('=')[1]; } else if (args[startIndex] == "-p" || args[startIndex] == "--path") { opts.DevicePath = args[startIndex + 1]; startIndex++; } // Scan for protection (requires device path) else if (args[startIndex].Equals("-s") || args[startIndex].Equals("--scan")) { scan = true; } // Disable scanning archives (requires --scan) else if (args[startIndex].Equals("--disable-archives")) { enableArchives = false; } // Enable debug protection information (requires --scan) else if (args[startIndex].Equals("--enable-debug")) { enableDebug = true; } // Hide drive letters from scan output (requires --scan) else if (args[startIndex].Equals("--hide-drive-letters")) { hideDriveLetters = true; } // Add filename suffix else if (args[startIndex].Equals("-x") || args[startIndex].Equals("--suffix")) { options.AddFilenameSuffix = true; } // Output submission JSON else if (args[startIndex].Equals("-j") || args[startIndex].Equals("--json")) { options.OutputSubmissionJSON = true; } // Include JSON artifacts else if (args[startIndex].Equals("--include-artifacts")) { options.IncludeArtifacts = true; } // Compress log and extraneous files else if (args[startIndex].Equals("-z") || args[startIndex].Equals("--zip")) { options.CompressLogFiles = true; } // Delete unnecessary files else if (args[startIndex].Equals("-d") || args[startIndex].Equals("--delete")) { 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 /// private class CommandOptions { /// /// Seed submission info from an input file /// public SubmissionInfo? Seed { get; set; } = null; /// /// Path to the device to scan /// public string? DevicePath { get; set; } = null; } } }