Compare commits

...

52 Commits
3.4.1 ... 3.5.0

Author SHA1 Message Date
Matt Nadareski
9012ff85a9 Bump version 2025-10-10 08:44:07 -04:00
Matt Nadareski
ccc33bebbd Try to handle Windows-specific compression issue 2025-10-10 08:09:49 -04:00
Matt Nadareski
57b07aee02 Support detecting split Wii for CleanRip 2025-10-09 15:35:38 -04:00
Matt Nadareski
069d676492 Fix test broken by last commit 2025-10-09 15:05:57 -04:00
Matt Nadareski
a26dfb7e7a Enable skeleton output for all CLI runs 2025-10-09 12:06:16 -04:00
Matt Nadareski
a9ea457808 Use block-based reading instead of CopyTo 2025-10-09 10:31:59 -04:00
Matt Nadareski
41bc410452 Pre-compress state files with Zstd 2025-10-09 10:01:24 -04:00
Matt Nadareski
bbfdf462d0 Fix broken file count tests 2025-10-08 12:54:18 -04:00
Matt Nadareski
5a1d51c05f Pre-compress skeleton files with Zstd 2025-10-08 12:48:50 -04:00
Matt Nadareski
16e80f75cf Add preemptive helper for Zstd handling 2025-10-08 12:04:04 -04:00
Matt Nadareski
2a6e066707 Add preemptive new file support 2025-10-08 11:53:19 -04:00
Matt Nadareski
d6102107fb Only allow skeleton creation for CD and DVD
This was a hard decision to limit again, but with the inability to make Zstd the default for log compression, this is not a reasonable thing to enable for most users.
2025-10-08 11:31:12 -04:00
Matt Nadareski
79163dcb35 Fix default value tests 2025-10-08 11:15:16 -04:00
Matt Nadareski
c1aa863c91 Allow skeleton creation for all media types 2025-10-08 11:10:13 -04:00
Matt Nadareski
dbd4b55dda Use ZipWriterOptions instead of generic 2025-10-08 10:59:51 -04:00
Matt Nadareski
1f58521f51 Fix incorrect flagging of a failed check 2025-10-08 10:54:20 -04:00
Matt Nadareski
4da1ab9c29 Guard against unzippable files 2025-10-08 10:35:28 -04:00
Matt Nadareski
5771add8c0 Revert "Use Deflate64 instead of Deflate for compression"
This reverts commit 977a71d9cf.
2025-10-08 10:20:10 -04:00
Matt Nadareski
977a71d9cf Use Deflate64 instead of Deflate for compression 2025-10-08 09:59:41 -04:00
Matt Nadareski
b2d09d04ea Use null or empty instead of just null 2025-10-08 09:49:22 -04:00
Matt Nadareski
78df92e5d3 More gracefully handle "missing" media types 2025-10-08 09:29:34 -04:00
Matt Nadareski
9770b7c917 Add more useful credentials inputs for Check 2025-10-07 20:37:11 -04:00
Matt Nadareski
1ddb287977 Minor cleanup on interactive modes 2025-10-07 20:27:22 -04:00
Matt Nadareski
b9e4bbf744 Finalize wire-through and clean up 2025-10-07 19:43:34 -04:00
Matt Nadareski
08829ed811 Fix minor issues with options loading 2025-10-07 18:27:12 -04:00
Matt Nadareski
bf181e2294 Start wiring through log compression changes 2025-10-07 18:07:46 -04:00
Matt Nadareski
49571c6bfc Use GC.SharpCompress as archive handling library 2025-10-07 17:33:01 -04:00
Matt Nadareski
9a0bc868f8 More consistency in commandline programs 2025-10-07 16:40:30 -04:00
Matt Nadareski
bc2c08690d Update packages 2025-10-07 16:21:02 -04:00
Matt Nadareski
493cb80624 Allow but do not require config for Check 2025-10-07 15:19:50 -04:00
Matt Nadareski
49b8ecf6c3 Fix minor typo in verify inputs check 2025-10-06 18:05:14 -04:00
Matt Nadareski
c9b7ad7819 Exit early on parsing failures 2025-10-06 17:53:15 -04:00
Matt Nadareski
fe76387f6a Remove CommandOptions implementations 2025-10-06 17:49:16 -04:00
Matt Nadareski
0a60fe5a37 Fix strange invocations of extension methods 2025-10-06 16:37:28 -04:00
Matt Nadareski
baffdb8b29 Remove duplicate input declarations 2025-10-06 16:34:19 -04:00
Matt Nadareski
4a3c585a8d Assign inputs for interactive modes 2025-10-06 16:25:41 -04:00
Matt Nadareski
1ee7ea1948 Create and use base feature in CLI 2025-10-06 16:13:51 -04:00
Matt Nadareski
ee08bfe0fc Create and use base feature in Check 2025-10-06 16:06:23 -04:00
Matt Nadareski
896caec9cd Create and use main features for CLI and Check 2025-10-06 15:54:14 -04:00
Matt Nadareski
68932ab473 Reduce unnecessary shared code 2025-10-06 15:25:11 -04:00
Matt Nadareski
d105d04146 Add placeholder command set creation 2025-10-06 11:01:25 -04:00
Matt Nadareski
7023c78d40 Minor cleanup around last added 2025-10-06 10:34:11 -04:00
Matt Nadareski
81156a3c63 Create interactive mode features 2025-10-06 10:32:16 -04:00
Matt Nadareski
7b2b06a36f Use CommandLine library for CLI executables 2025-10-06 10:11:44 -04:00
Matt Nadareski
3f974ab336 Update packages 2025-10-05 17:41:38 -04:00
Matt Nadareski
b238616685 Rename log zip on collision 2025-10-04 20:49:56 -04:00
Matt Nadareski
2984459823 Tweaks to how failure cases are reported 2025-10-04 18:57:25 -04:00
Matt Nadareski
ce979b3c3f Add failure if media type could not be determined 2025-10-02 22:06:22 -04:00
Matt Nadareski
cb8e1fd34b Bump version 2025-09-30 12:02:06 -04:00
Matt Nadareski
cc148735f8 Require exact versions for build 2025-09-30 11:18:59 -04:00
Matt Nadareski
8238d14f7b Fix missed package update 2025-09-30 10:55:57 -04:00
Matt Nadareski
a56676501e Fix starting index for CLI 2025-09-29 22:43:33 -04:00
45 changed files with 2173 additions and 1262 deletions

View File

@@ -1,3 +1,57 @@
### 3.5.0 (2025-10-10)
- Add failure if media type could not be determined
- Tweaks to how failure cases are reported
- Rename log zip on collision
- Update packages
- Use CommandLine library for CLI executables
- Create interactive mode features
- Minor cleanup around last added
- Add placeholder command set creation
- Reduce unnecessary shared code
- Create and use main features for CLI and Check
- Create and use base feature in Check
- Create and use base feature in CLI
- Assign inputs for interactive modes
- Remove duplicate input declarations
- Fix strange invocations of extension methods
- Remove CommandOptions implementations
- Exit early on parsing failures
- Fix minor typo in verify inputs check
- Allow but do not require config for Check
- Update packages
- More consistency in commandline programs
- Use GC.SharpCompress as archive handling library
- Start wiring through log compression changes
- Fix minor issues with options loading
- Finalize wire-through and clean up
- Minor cleanup on interactive modes
- Add more useful credentials inputs for Check
- More gracefully handle "missing" media types
- Use null or empty instead of just null
- Guard against unzippable files
- Fix incorrect flagging of a failed check
- Use ZipWriterOptions instead of generic
- Allow skeleton creation for all media types
- Fix default value tests
- Only allow skeleton creation for CD and DVD
- Add preemptive new file support
- Add preemptive helper for Zstd handling
- Pre-compress skeleton files with Zstd
- Fix broken file count tests
- Pre-compress state files with Zstd
- Use block-based reading instead of CopyTo
- Enable skeleton output for all CLI runs
- Fix test broken by last commit
- Support detecting split Wii for CleanRip
- Try to handle Windows-specific compression issue
### 3.4.2 (2025-09-30)
- Fix starting index for CLI
- Fix missed package update
- Require exact versions for build
### 3.4.1 (2025-09-29)
- Experiment with only showing media type box for DIC

View File

@@ -0,0 +1,269 @@
using System;
using System.IO;
#if NET40
using System.Threading.Tasks;
#endif
using MPF.Frontend;
using MPF.Frontend.Tools;
using SabreTools.RedumpLib.Data;
using SabreTools.RedumpLib.Web;
using LogCompression = MPF.Processors.LogCompression;
namespace MPF.CLI.Features
{
internal abstract class BaseFeature : SabreTools.CommandLine.Feature
{
#region Properties
/// <summary>
/// User-defined options
/// </summary>
public Options Options { get; protected set; }
/// <summary>
/// Currently-selected system
/// </summary>
public RedumpSystem? System { get; protected set; }
/// <summary>
/// Media type to dump
/// </summary>
/// <remarks>Required for DIC and if custom parameters not set</remarks>
public MediaType? MediaType { get; protected set; }
/// <summary>
/// Path to the device to dump
/// </summary>
/// <remarks>Required if custom parameters are not set</remarks>
public string? DevicePath { get; protected set; }
/// <summary>
/// Path to the mounted filesystem to check
/// </summary>
/// <remarks>Should only be used when the device path is not readable</remarks>
public string? MountedPath { get; protected set; }
/// <summary>
/// Path to the output file
/// </summary>
/// <remarks>Required if custom parameters are not set</remarks>
public string? FilePath { get; protected set; }
/// <summary>
/// Override drive speed
/// </summary>
public int? DriveSpeed { get; protected set; }
/// <summary>
/// Custom parameters for dumping
/// </summary>
public string? CustomParams { get; protected set; }
#endregion
protected BaseFeature(string name, string[] flags, string description, string? detailed = null)
: base(name, flags, description, detailed)
{
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,
LogCompression = LogCompression.DeflateMaximum,
DeleteUnnecessaryFiles = false,
CreateIRDAfterDumping = false,
// Protection Scanning Options
ScanArchivesForProtection = true,
IncludeDebugProtectionInformation = false,
HideDriveLetters = false,
// Redump Login Information
RetrieveMatchInformation = true,
RedumpUsername = null,
RedumpPassword = null,
};
}
/// <inheritdoc/>
public override bool Execute()
{
// 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);
}
// Validate the internal program
switch (Options.InternalProgram)
{
case InternalProgram.Aaru:
if (!File.Exists(Options.AaruPath))
{
Console.Error.WriteLine("A path needs to be supplied in config.json for Aaru, exiting...");
return false;
}
break;
case InternalProgram.DiscImageCreator:
if (!File.Exists(Options.DiscImageCreatorPath))
{
Console.Error.WriteLine("A path needs to be supplied in config.json for DIC, exiting...");
return false;
}
break;
case InternalProgram.Redumper:
if (!File.Exists(Options.RedumperPath))
{
Console.Error.WriteLine("A path needs to be supplied in config.json for Redumper, exiting...");
return false;
}
break;
default:
Console.Error.WriteLine($"{Options.InternalProgram} is not a supported dumping program, exiting...");
break;
}
// Ensure we have the values we need
if (CustomParams == null && (DevicePath == null || FilePath == null))
{
Console.Error.WriteLine("Both a device path and file path need to be supplied, exiting...");
return false;
}
if (Options.InternalProgram == InternalProgram.DiscImageCreator
&& CustomParams == null
&& (MediaType == null || MediaType == SabreTools.RedumpLib.Data.MediaType.NONE))
{
Console.Error.WriteLine("Media type is required for DiscImageCreator, exiting...");
return false;
}
// Normalize the file path
if (FilePath != null)
FilePath = FrontendTool.NormalizeOutputPaths(FilePath, getFullPath: true);
// Get the speed from the options
int speed = DriveSpeed ?? FrontendTool.GetDefaultSpeedForMediaType(MediaType, Options);
// Populate an environment
var drive = Drive.Create(null, DevicePath ?? string.Empty);
var env = new DumpEnvironment(Options,
FilePath,
drive,
System,
Options.InternalProgram);
env.SetExecutionContext(MediaType, null);
env.SetProcessor();
// Process the parameters
string? paramStr = CustomParams ?? env.GetFullParameters(MediaType, speed);
if (string.IsNullOrEmpty(paramStr))
{
Console.Error.WriteLine("No valid environment could be created, exiting...");
return false;
}
env.SetExecutionContext(MediaType, paramStr);
// Invoke the dumping program
Console.WriteLine($"Invoking {Options.InternalProgram} using '{paramStr}'");
var dumpResult = env.Run(MediaType).GetAwaiter().GetResult();
Console.WriteLine(dumpResult.Message);
if (!dumpResult)
return false;
// If it was not a dumping command
if (!env.IsDumpingCommand())
{
Console.Error.WriteLine();
Console.WriteLine("Execution not recognized as dumping command, skipping processing...");
return true;
}
// If we have a mounted path, replace the environment
if (MountedPath != null && Directory.Exists(MountedPath))
{
drive = Drive.Create(null, MountedPath);
env = new DumpEnvironment(Options,
FilePath,
drive,
System,
internalProgram: null);
env.SetExecutionContext(MediaType, null);
env.SetProcessor();
}
// Finally, attempt to do the output dance
var verifyResult = env.VerifyAndSaveDumpOutput()
.ConfigureAwait(false).GetAwaiter().GetResult();
Console.WriteLine(verifyResult.Message);
return true;
}
/// <summary>
/// Display help for MPF.CLI
/// </summary>
/// <param name="error">Error string to prefix the help text with</param>
public static void DisplayHelp()
{
Console.WriteLine("Usage:");
Console.WriteLine("MPF.CLI <system> [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 <program> Override configured dumping program name");
Console.WriteLine("-t, --mediatype <mediatype> Set media type for dumping (Required for DIC)");
Console.WriteLine("-d, --device <devicepath> Physical drive path (Required if no custom parameters set)");
Console.WriteLine("-m, --mounted <dirpath> Mounted filesystem path for additional checks");
Console.WriteLine("-f, --file \"<filepath>\" Output file path (Required if no custom parameters set)");
Console.WriteLine("-s, --speed <speed> Override default dumping speed");
Console.WriteLine("-c, --custom \"<params>\" 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();
}
}
}

View File

@@ -0,0 +1,190 @@
using System;
using System.IO;
using MPF.Frontend;
using MPF.Frontend.Tools;
using SabreTools.RedumpLib.Data;
namespace MPF.CLI.Features
{
internal sealed class InteractiveFeature : BaseFeature
{
#region Feature Definition
public const string DisplayName = "interactive";
private static readonly string[] _flags = ["i", "interactive"];
private const string _description = "Enable interactive mode";
#endregion
public InteractiveFeature()
: base(DisplayName, _flags, _description)
{
}
/// <inheritdoc/>
public override bool ProcessArgs(string[] args, int index)
{
// Cache all args as inputs
for (int i = 1; i < args.Length; i++)
{
Inputs.Add(args[i]);
}
// Read the options from config, if possible
Options = OptionsLoader.LoadFromConfig();
// Create return values
MediaType = SabreTools.RedumpLib.Data.MediaType.NONE;
FilePath = Path.Combine(Options.DefaultOutputPath ?? "ISO", "track.bin");
System = Options.DefaultSystem;
// Create state values
string? result = string.Empty;
root:
Console.Clear();
Console.WriteLine("MPF.CLI Interactive Mode - Main Menu");
Console.WriteLine("-------------------------");
Console.WriteLine();
Console.WriteLine($"1) Set system (Currently '{System}')");
Console.WriteLine($"2) Set dumping program (Currently '{Options.InternalProgram}')");
Console.WriteLine($"3) Set media type (Currently '{MediaType}')");
Console.WriteLine($"4) Set device path (Currently '{DevicePath}')");
Console.WriteLine($"5) Set mounted path (Currently '{MountedPath}')");
Console.WriteLine($"6) Set file path (Currently '{FilePath}')");
Console.WriteLine($"7) Set override speed (Currently '{DriveSpeed}')");
Console.WriteLine($"8) Set custom parameters (Currently '{CustomParams}')");
Console.WriteLine();
Console.WriteLine($"Q) Exit the program");
Console.WriteLine($"X) Start dumping");
Console.Write("> ");
result = Console.ReadLine();
switch (result)
{
case "1":
goto system;
case "2":
goto dumpingProgram;
case "3":
goto mediaType;
case "4":
goto devicePath;
case "5":
goto mountedPath;
case "6":
goto filePath;
case "7":
goto overrideSpeed;
case "8":
goto customParams;
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;
}
system:
Console.WriteLine();
Console.WriteLine("For possible inputs, use the List Systems commandline option");
Console.WriteLine();
Console.WriteLine("Input the system and press Enter:");
Console.Write("> ");
result = Console.ReadLine();
System = result.ToRedumpSystem();
goto root;
dumpingProgram:
Console.WriteLine();
Console.WriteLine("Options:");
Console.WriteLine($"{InternalProgram.Redumper.ToString().ToLowerInvariant().PadRight(15)} => {InternalProgram.Redumper.LongName()}");
Console.WriteLine($"{InternalProgram.DiscImageCreator.ToString().ToLowerInvariant().PadRight(15)} => {InternalProgram.DiscImageCreator.LongName()}");
Console.WriteLine($"{InternalProgram.Aaru.ToString().ToLowerInvariant().PadRight(15)} => {InternalProgram.Aaru.LongName()}");
Console.WriteLine();
Console.WriteLine("Input the dumping program and press Enter:");
Console.Write("> ");
result = Console.ReadLine();
Options.InternalProgram = result.ToInternalProgram();
goto root;
mediaType:
Console.WriteLine();
Console.WriteLine("For possible inputs, use the List Media commandline option");
Console.WriteLine();
Console.WriteLine("Input the media type and press Enter:");
Console.Write("> ");
result = Console.ReadLine();
MediaType = OptionsLoader.ToMediaType(result);
goto root;
devicePath:
Console.WriteLine();
Console.WriteLine("Input the device path and press Enter:");
Console.Write("> ");
DevicePath = Console.ReadLine();
goto root;
mountedPath:
Console.WriteLine();
Console.WriteLine("Input the mounted path and press Enter:");
Console.Write("> ");
MountedPath = Console.ReadLine();
goto root;
filePath:
Console.WriteLine();
Console.WriteLine("Input the file path and press Enter:");
Console.Write("> ");
result = Console.ReadLine();
if (!string.IsNullOrEmpty(result))
result = Path.GetFullPath(result!);
FilePath = result;
goto root;
overrideSpeed:
Console.WriteLine();
Console.WriteLine("Input the override speed and press Enter:");
Console.Write("> ");
result = Console.ReadLine();
if (!int.TryParse(result, out int speed))
speed = -1;
DriveSpeed = speed;
goto root;
customParams:
Console.WriteLine();
Console.WriteLine("Input the custom parameters and press Enter:");
Console.Write("> ");
CustomParams = Console.ReadLine();
goto root;
exit:
return true;
}
/// <inheritdoc/>
public override bool VerifyInputs() => true;
}
}

View File

@@ -0,0 +1,115 @@
using MPF.Frontend;
using MPF.Frontend.Tools;
using SabreTools.CommandLine.Inputs;
using SabreTools.RedumpLib.Data;
namespace MPF.CLI.Features
{
internal sealed class MainFeature : BaseFeature
{
#region Feature Definition
public const string DisplayName = "main";
/// <remarks>Flags are unused</remarks>
private static readonly string[] _flags = [];
/// <remarks>Description is unused</remarks>
private const string _description = "";
#endregion
#region Inputs
private const string _customName = "custom";
internal readonly StringInput CustomInput = new(_customName, ["-c", "--custom"], "Custom parameters to use");
private const string _deviceName = "device";
internal readonly StringInput DeviceInput = new(_deviceName, ["-d", "--device"], "Physical drive path (Required if no custom parameters set)");
private const string _fileName = "file";
internal readonly StringInput FileInput = new(_fileName, ["-f", "--file"], "Output file path (Required if no custom parameters set)");
private const string _mediaTypeName = "media-type";
internal readonly StringInput MediaTypeInput = new(_mediaTypeName, ["-t", "--mediatype"], "Set media type for dumping (Required for DIC)");
private const string _mountedName = "mounted";
internal readonly StringInput MountedInput = new(_mountedName, ["-m", "--mounted"], "Mounted filesystem path for additional checks");
private const string _speedName = "speed";
internal readonly Int32Input SpeedInput = new(_speedName, ["-s", "--speed"], "Override default dumping speed");
private const string _useName = "use";
internal readonly StringInput UseInput = new(_useName, ["-u", "--use"], "Override configured dumping program name");
#endregion
public MainFeature()
: base(DisplayName, _flags, _description)
{
Add(UseInput);
Add(MediaTypeInput);
Add(DeviceInput);
Add(MountedInput);
Add(FileInput);
Add(SpeedInput);
Add(CustomInput);
}
/// <inheritdoc/>
public override bool ProcessArgs(string[] args, int index)
{
// If we have no arguments, just return
if (args == null || args.Length == 0)
return true;
// Read the options from config, if possible
Options = OptionsLoader.LoadFromConfig();
// The first argument is the system type
System = args[0].Trim('"').ToRedumpSystem();
// Loop through the arguments and parse out values
for (index = 1; 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))
MediaType = OptionsLoader.ToMediaType(MediaTypeInput.Value?.Trim('"'));
// Use a device path
else if (DeviceInput.ProcessInput(args, ref index))
DevicePath = DeviceInput.Value;
// Use a mounted path for physical checks
else if (MountedInput.ProcessInput(args, ref index))
MountedPath = MountedInput.Value;
// Use a file path
else if (FileInput.ProcessInput(args, ref index))
FilePath = FileInput.Value;
// Set an override speed
else if (SpeedInput.ProcessInput(args, ref index))
DriveSpeed = SpeedInput.Value;
// Use a custom parameters
else if (CustomInput.ProcessInput(args, ref index))
CustomParams = CustomInput.Value;
// Default, add to inputs
else
Inputs.Add(args[index]);
}
return true;
}
/// <inheritdoc/>
public override bool VerifyInputs() => true;
}
}

View File

@@ -12,7 +12,7 @@
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.4.1</VersionPrefix>
<VersionPrefix>3.5.0</VersionPrefix>
<!-- Package Properties -->
<Title>MPF CLI</Title>
@@ -43,7 +43,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.RedumpLib" Version="1.7.4" />
<PackageReference Include="SabreTools.CommandLine" Version="[1.3.2]" />
<PackageReference Include="SabreTools.RedumpLib" Version="[1.7.4]" />
</ItemGroup>
</Project>

View File

@@ -1,12 +1,14 @@
using System;
using System.IO;
using System.Collections.Generic;
#if NET40
using System.Threading.Tasks;
#endif
using MPF.CLI.Features;
using MPF.Frontend;
using MPF.Frontend.Features;
using MPF.Frontend.Tools;
using SabreTools.RedumpLib.Data;
using SabreTools.RedumpLib.Web;
using SabreTools.CommandLine;
using SabreTools.CommandLine.Features;
namespace MPF.CLI
{
@@ -29,516 +31,117 @@ namespace MPF.CLI
OptionsLoader.SaveToConfig(options);
// Display non-error message
DisplayHelp("First-run detected! Please fill out config.json and run again.");
Console.WriteLine("First-run detected! Please fill out config.json and run again.");
BaseFeature.DisplayHelp();
return;
}
// Try processing the standalone arguments
bool? standaloneProcessed = OptionsLoader.ProcessStandaloneArguments(args);
if (standaloneProcessed != false)
{
if (standaloneProcessed == null)
DisplayHelp();
return;
}
// Create the command set
var mainFeature = new MainFeature();
var commandSet = CreateCommands(mainFeature);
// Setup common outputs
CommandOptions opts;
RedumpSystem? knownSystem;
// Use interactive mode
if (args.Length > 0 && (args[0] == "-i" || args[0] == "--interactive"))
{
opts = InteractiveMode(options, out knownSystem);
}
// Use normal commandline parameters
else
{
// Try processing the common arguments
bool success = OptionsLoader.ProcessCommonArguments(args, out knownSystem, out var error);
if (!success)
{
DisplayHelp(error);
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);
}
// Process any custom parameters
int startIndex = 2;
opts = LoadFromArguments(args, options, ref startIndex);
}
// 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);
}
/// <summary>
/// Display help for MPF.CLI
/// </summary>
/// <param name="error">Error string to prefix the help text with</param>
private static void DisplayHelp(string? error = null)
{
if (error != null)
Console.WriteLine(error);
Console.WriteLine("Usage:");
Console.WriteLine("MPF.CLI <system> [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 <program> Override configured dumping program name");
Console.WriteLine("-t, --mediatype <mediatype> Set media type for dumping (Required for DIC)");
Console.WriteLine("-d, --device <devicepath> Physical drive path (Required if no custom parameters set)");
Console.WriteLine("-m, --mounted <dirpath> Mounted filesystem path for additional checks");
Console.WriteLine("-f, --file \"<filepath>\" Output file path (Required if no custom parameters set)");
Console.WriteLine("-s, --speed <speed> Override default dumping speed");
Console.WriteLine("-c, --custom \"<params>\" 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();
}
/// <summary>
/// Enable interactive mode for entering information
/// </summary>
private static CommandOptions InteractiveMode(Options options, out RedumpSystem? system)
{
// Create return values
var opts = new CommandOptions
{
MediaType = MediaType.NONE,
FilePath = Path.Combine(options.DefaultOutputPath ?? "ISO", "track.bin"),
};
system = options.DefaultSystem;
// Create state values
string? result = string.Empty;
root:
Console.Clear();
Console.WriteLine("MPF.CLI Interactive Mode - Main Menu");
Console.WriteLine("-------------------------");
Console.WriteLine();
Console.WriteLine($"1) Set system (Currently '{system}')");
Console.WriteLine($"2) Set dumping program (Currently '{options.InternalProgram}')");
Console.WriteLine($"3) Set media type (Currently '{opts.MediaType}')");
Console.WriteLine($"4) Set device path (Currently '{opts.DevicePath}')");
Console.WriteLine($"5) Set mounted path (Currently '{opts.MountedPath}')");
Console.WriteLine($"6) Set file path (Currently '{opts.FilePath}')");
Console.WriteLine($"7) Set override speed (Currently '{opts.DriveSpeed}')");
Console.WriteLine($"8) Set custom parameters (Currently '{opts.CustomParams}')");
Console.WriteLine();
Console.WriteLine($"Q) Exit the program");
Console.WriteLine($"X) Start dumping");
Console.Write("> ");
result = Console.ReadLine();
switch (result)
{
case "1":
goto system;
case "2":
goto dumpingProgram;
case "3":
goto mediaType;
case "4":
goto devicePath;
case "5":
goto mountedPath;
case "6":
goto filePath;
case "7":
goto overrideSpeed;
case "8":
goto customParams;
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;
}
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;
mediaType:
Console.WriteLine();
Console.WriteLine("Input the media type and press Enter:");
Console.Write("> ");
result = Console.ReadLine();
opts.MediaType = OptionsLoader.ToMediaType(result);
goto root;
devicePath:
Console.WriteLine();
Console.WriteLine("Input the device path and press Enter:");
Console.Write("> ");
opts.DevicePath = Console.ReadLine();
goto root;
mountedPath:
Console.WriteLine();
Console.WriteLine("Input the mounted path and press Enter:");
Console.Write("> ");
opts.MountedPath = Console.ReadLine();
goto root;
filePath:
Console.WriteLine();
Console.WriteLine("Input the file path and press Enter:");
Console.Write("> ");
result = Console.ReadLine();
if (!string.IsNullOrEmpty(result))
result = Path.GetFullPath(result!);
opts.FilePath = result;
goto root;
overrideSpeed:
Console.WriteLine();
Console.WriteLine("Input the override speed and press Enter:");
Console.Write("> ");
result = Console.ReadLine();
if (!int.TryParse(result, out int speed))
speed = -1;
opts.DriveSpeed = speed;
goto root;
customParams:
Console.WriteLine();
Console.WriteLine("Input the custom parameters and press Enter:");
Console.Write("> ");
opts.CustomParams = Console.ReadLine();
goto root;
exit:
return opts;
}
/// <summary>
/// Load the current set of options from application arguments
/// </summary>
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 we have no args, show the help and quit
if (args == null || args.Length == 0)
{
startIndex = 0;
return opts;
BaseFeature.DisplayHelp();
return;
}
// If we have an invalid start index, just return
if (startIndex < 0 || startIndex >= args.Length)
return opts;
// Get the first argument as a feature flag
string featureName = args[0];
// Loop through the arguments and parse out values
for (; startIndex < args.Length; startIndex++)
// Try processing the standalone arguments
var topLevel = commandSet.GetTopLevel(featureName);
switch (topLevel)
{
// 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++;
}
// Standalone Options
case Help: BaseFeature.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;
// Use a device path
else if (args[startIndex].StartsWith("-t=") || args[startIndex].StartsWith("--mediatype="))
{
opts.MediaType = OptionsLoader.ToMediaType(args[startIndex].Split('=')[1].Trim('"'));
}
else if (args[startIndex] == "-t" || args[startIndex] == "--mediatype")
{
opts.MediaType = OptionsLoader.ToMediaType(args[startIndex + 1].Trim('"'));
startIndex++;
}
// Interactive Mode
case InteractiveFeature interactive:
if (!interactive.ProcessArgs(args, 0))
{
BaseFeature.DisplayHelp();
return;
}
if (!interactive.Execute())
{
BaseFeature.DisplayHelp();
return;
}
// Use a device path
else if (args[startIndex].StartsWith("-d=") || args[startIndex].StartsWith("--device="))
{
opts.DevicePath = args[startIndex].Split('=')[1].Trim('"');
}
else if (args[startIndex] == "-d" || args[startIndex] == "--device")
{
opts.DevicePath = args[startIndex + 1].Trim('"');
startIndex++;
}
// Use a mounted path for physical checks
else if (args[startIndex].StartsWith("-m=") || args[startIndex].StartsWith("--mounted="))
{
opts.MountedPath = args[startIndex].Split('=')[1];
}
else if (args[startIndex] == "-m" || args[startIndex] == "--mounted")
{
opts.MountedPath = args[startIndex + 1];
startIndex++;
}
// Use a file path
else if (args[startIndex].StartsWith("-f=") || args[startIndex].StartsWith("--file="))
{
opts.FilePath = args[startIndex].Split('=')[1].Trim('"');
}
else if (args[startIndex] == "-f" || args[startIndex] == "--file")
{
opts.FilePath = args[startIndex + 1].Trim('"');
startIndex++;
}
// Set an override speed
else if (args[startIndex].StartsWith("-s=") || args[startIndex].StartsWith("--speed="))
{
if (!int.TryParse(args[startIndex].Split('=')[1].Trim('"'), out int speed))
speed = -1;
opts.DriveSpeed = speed;
}
else if (args[startIndex] == "-s" || args[startIndex] == "--speed")
{
if (!int.TryParse(args[startIndex + 1].Trim('"'), out int speed))
speed = -1;
opts.DriveSpeed = speed;
startIndex++;
}
// Use a custom parameters
else if (args[startIndex].StartsWith("-c=") || args[startIndex].StartsWith("--custom="))
{
opts.CustomParams = args[startIndex].Split('=')[1].Trim('"');
}
else if (args[startIndex] == "-c" || args[startIndex] == "--custom")
{
opts.CustomParams = args[startIndex + 1].Trim('"');
startIndex++;
}
// Default, we fall out
else
{
break;
}
}
return opts;
// Default Behavior
default:
if (!mainFeature.ProcessArgs(args, 0))
{
BaseFeature.DisplayHelp();
return;
}
if (!mainFeature.Execute())
{
BaseFeature.DisplayHelp();
return;
}
break;
}
}
/// <summary>
/// Represents commandline options
/// Create the command set for the program
/// </summary>
private class CommandOptions
private static CommandSet CreateCommands(MainFeature mainFeature)
{
/// <summary>
/// Media type to dump
/// </summary>
/// <remarks>Required for DIC and if custom parameters not set</remarks>
public MediaType? MediaType { get; set; } = null;
List<string> header = [
"MPF.CLI [standalone|system] [options] <path> ...",
string.Empty,
];
/// <summary>
/// Path to the device to dump
/// </summary>
/// <remarks>Required if custom parameters are not set</remarks>
public string? DevicePath { get; set; } = null;
List<string> 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,
/// <summary>
/// Path to the mounted filesystem to check
/// </summary>
/// <remarks>Should only be used when the device path is not readable</remarks>
public string? MountedPath { get; set; } = null;
"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,
/// <summary>
/// Path to the output file
/// </summary>
/// <remarks>Required if custom parameters are not set</remarks>
public string? FilePath { get; set; } = null;
"Mounted filesystem path is only recommended on OSes that require block",
"device dumping, usually Linux and macOS.",
string.Empty,
];
/// <summary>
/// Override drive speed
/// </summary>
public int? DriveSpeed { get; set; } = null;
var commandSet = new CommandSet(header, footer);
/// <summary>
/// Custom parameters for dumping
/// </summary>
public string? CustomParams { get; set; } = null;
// 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(mainFeature.UseInput);
commandSet.Add(mainFeature.MediaTypeInput);
commandSet.Add(mainFeature.DeviceInput);
commandSet.Add(mainFeature.MountedInput);
commandSet.Add(mainFeature.FileInput);
commandSet.Add(mainFeature.SpeedInput);
commandSet.Add(mainFeature.CustomInput);
return commandSet;
}
}
}

View File

@@ -0,0 +1,177 @@
using System;
using System.IO;
#if NET40
using System.Threading.Tasks;
#endif
using MPF.Frontend;
using SabreTools.RedumpLib.Data;
using SabreTools.RedumpLib.Web;
using LogCompression = MPF.Processors.LogCompression;
namespace MPF.Check.Features
{
internal abstract class BaseFeature : SabreTools.CommandLine.Feature
{
#region Properties
/// <summary>
/// User-defined options
/// </summary>
public Options Options { get; protected set; }
/// <summary>
/// Currently-selected system
/// </summary>
public RedumpSystem? System { get; protected set; }
/// <summary>
/// Seed submission info from an input file
/// </summary>
public SubmissionInfo? Seed { get; protected set; }
/// <summary>
/// Path to the device to scan
/// </summary>
public string? DevicePath { get; protected set; }
#endregion
protected BaseFeature(string name, string[] flags, string description, string? detailed = null)
: base(name, flags, description, detailed)
{
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,
LogCompression = LogCompression.DeflateMaximum,
DeleteUnnecessaryFiles = false,
CreateIRDAfterDumping = false,
// Protection Scanning Options
ScanArchivesForProtection = true,
IncludeDebugProtectionInformation = false,
HideDriveLetters = false,
// Redump Login Information
RetrieveMatchInformation = true,
RedumpUsername = null,
RedumpPassword = null,
};
}
/// <inheritdoc/>
public override bool Execute()
{
if (Options.InternalProgram == InternalProgram.NONE)
{
Console.Error.WriteLine("A program name needs to be provided");
return false;
}
// 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 = 0; i < Inputs.Count; i++)
{
// Check for a file
if (!File.Exists(Inputs[i].Trim('"')))
{
Console.Error.WriteLine($"{Inputs[i].Trim('"')} does not exist");
return false;
}
// Get the full file path
string filepath = Path.GetFullPath(Inputs[i].Trim('"'));
// Now populate an environment
Drive? drive = null;
if (!string.IsNullOrEmpty(DevicePath))
drive = Drive.Create(null, DevicePath!);
var env = new DumpEnvironment(Options,
filepath,
drive,
System,
internalProgram: null);
env.SetProcessor();
// Finally, attempt to do the output dance
var result = env.VerifyAndSaveDumpOutput(seedInfo: Seed)
.ConfigureAwait(false).GetAwaiter().GetResult();
Console.WriteLine(result.Message);
}
return true;
}
/// <summary>
/// Display help for MPF.Check
/// </summary>
/// <param name="error">Error string to prefix the help text with</param>
public static void DisplayHelp()
{
Console.WriteLine("Usage:");
Console.WriteLine("MPF.Check <system> [options] </path/to/output.cue/iso> ...");
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("Check Options:");
Console.WriteLine("-u, --use <program> Dumping program output type [REQUIRED]");
Console.WriteLine(" --load-seed <path> 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 <user> <pw> Redump username and password (incompatible with --no-retrieve) [WILL BE REMOVED]");
Console.WriteLine("-U, --username <user> Redump username (incompatible with --no-retrieve)");
Console.WriteLine("-P, --password <pw> Redump password (incompatible with --no-retrieve)");
Console.WriteLine(" --pull-all Pull all information from Redump (requires --username and --password)");
Console.WriteLine("-p, --path <drivepath> 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(" --log-compression Set the log compression type (requires --zip)");
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();
}
}
}

View File

@@ -0,0 +1,288 @@
using System;
using MPF.Frontend;
using MPF.Frontend.Tools;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
using LogCompression = MPF.Processors.LogCompression;
namespace MPF.Check.Features
{
internal sealed class InteractiveFeature : BaseFeature
{
#region Feature Definition
public const string DisplayName = "interactive";
private static readonly string[] _flags = ["i", "interactive"];
private const string _description = "Enable interactive mode";
#endregion
public InteractiveFeature()
: base(DisplayName, _flags, _description)
{
}
/// <inheritdoc/>
public override bool ProcessArgs(string[] args, int index)
{
// Cache all args as inputs
for (int i = 1; i < args.Length; i++)
{
Inputs.Add(args[i]);
}
// Read the options from config, if possible
Options = OptionsLoader.LoadFromConfig();
if (Options.FirstRun)
{
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,
LogCompression = LogCompression.DeflateMaximum,
DeleteUnnecessaryFiles = false,
CreateIRDAfterDumping = false,
// Protection Scanning Options
ScanArchivesForProtection = true,
IncludeDebugProtectionInformation = false,
HideDriveLetters = false,
// Redump Login Information
RetrieveMatchInformation = true,
RedumpUsername = null,
RedumpPassword = null,
};
}
// Create return values
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 system (Currently '{System}')");
Console.WriteLine($"2) Set dumping program (Currently '{Options.InternalProgram}')");
Console.WriteLine($"3) Set seed path (Currently '{Seed}')");
Console.WriteLine($"4) Add placeholders (Currently '{Options.AddPlaceholders}')");
Console.WriteLine($"5) Create IRD (Currently '{Options.CreateIRDAfterDumping}')");
Console.WriteLine($"6) Attempt Redump matches (Currently '{Options.RetrieveMatchInformation}')");
Console.WriteLine($"7) Redump credentials (Currently '{Options.RedumpUsername}')");
Console.WriteLine($"8) Pull all information (Currently '{Options.PullAllInformation}')");
Console.WriteLine($"9) Set device path (Currently '{DevicePath}')");
Console.WriteLine($"A) Scan for protection (Currently '{scan}')");
Console.WriteLine($"B) Scan archives for protection (Currently '{enableArchives}')");
Console.WriteLine($"C) Debug protection scan output (Currently '{enableDebug}')");
Console.WriteLine($"D) Hide drive letters in protection output (Currently '{hideDriveLetters}')");
Console.WriteLine($"E) Hide filename suffix (Currently '{Options.AddFilenameSuffix}')");
Console.WriteLine($"F) Output submission JSON (Currently '{Options.OutputSubmissionJSON}')");
Console.WriteLine($"G) Include JSON artifacts (Currently '{Options.IncludeArtifacts}')");
Console.WriteLine($"H) Compress logs (Currently '{Options.CompressLogFiles}')");
Console.WriteLine($"I) Log compression (Currently '{Options.LogCompression.LongName()}')");
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 system;
case "2":
goto dumpingProgram;
case "3":
goto seedPath;
case "4":
Options.AddPlaceholders = !Options.AddPlaceholders;
goto root;
case "5":
Options.CreateIRDAfterDumping = !Options.CreateIRDAfterDumping;
goto root;
case "6":
Options.RetrieveMatchInformation = !Options.RetrieveMatchInformation;
goto root;
case "7":
goto redumpCredentials;
case "8":
Options.PullAllInformation = !Options.PullAllInformation;
goto root;
case "9":
goto devicePath;
case "a":
case "A":
scan = !scan;
goto root;
case "b":
case "B":
enableArchives = !enableArchives;
goto root;
case "c":
case "C":
enableDebug = !enableDebug;
goto root;
case "d":
case "D":
hideDriveLetters = !hideDriveLetters;
goto root;
case "e":
case "E":
Options.AddFilenameSuffix = !Options.AddFilenameSuffix;
goto root;
case "f":
case "F":
Options.OutputSubmissionJSON = !Options.OutputSubmissionJSON;
goto root;
case "g":
case "G":
Options.IncludeArtifacts = !Options.IncludeArtifacts;
goto root;
case "h":
case "H":
Options.CompressLogFiles = !Options.CompressLogFiles;
goto root;
case "i":
case "I":
goto logCompression;
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;
}
system:
Console.WriteLine();
Console.WriteLine("For possible inputs, use the List Systems commandline option");
Console.WriteLine();
Console.WriteLine("Input the system and press Enter:");
Console.Write("> ");
result = Console.ReadLine();
System = result.ToRedumpSystem();
goto root;
dumpingProgram:
Console.WriteLine();
Console.WriteLine("Options:");
foreach (var program in (InternalProgram[])Enum.GetValues(typeof(InternalProgram)))
{
// Skip the placeholder values
if (program == InternalProgram.NONE)
continue;
Console.WriteLine($"{program.ToString().ToLowerInvariant().PadRight(15)} => {program.LongName()}");
}
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();
Seed = Builder.CreateFromFile(result);
goto root;
redumpCredentials:
Console.WriteLine();
Console.WriteLine("Enter your Redump username and press Enter:");
Console.Write("> ");
Options.RedumpUsername = Console.ReadLine();
Console.WriteLine("Enter your Redump 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("> ");
DevicePath = Console.ReadLine();
goto root;
logCompression:
Console.WriteLine();
Console.WriteLine("Options:");
foreach (var compressionType in (LogCompression[])Enum.GetValues(typeof(LogCompression)))
{
Console.WriteLine($"{compressionType.ToString().ToLowerInvariant().PadRight(15)} => {compressionType.LongName()}");
}
Console.WriteLine();
Console.WriteLine("Input the log compression type and press Enter:");
Console.Write("> ");
result = Console.ReadLine();
Options.LogCompression = result.ToLogCompression();
goto root;
exit:
// Now deal with the complex options
Options.ScanForProtection = scan && !string.IsNullOrEmpty(DevicePath);
Options.ScanArchivesForProtection = enableArchives && scan && !string.IsNullOrEmpty(DevicePath);
Options.IncludeDebugProtectionInformation = enableDebug && scan && !string.IsNullOrEmpty(DevicePath);
Options.HideDriveLetters = hideDriveLetters && scan && !string.IsNullOrEmpty(DevicePath);
return true;
}
/// <inheritdoc/>
public override bool VerifyInputs() => Inputs.Count > 0;
}
}

View File

@@ -0,0 +1,267 @@
using MPF.Frontend;
using MPF.Frontend.Tools;
using SabreTools.CommandLine.Inputs;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
using LogCompression = MPF.Processors.LogCompression;
namespace MPF.Check.Features
{
internal sealed class MainFeature : BaseFeature
{
#region Feature Definition
public const string DisplayName = "main";
/// <remarks>Flags are unused</remarks>
private static readonly string[] _flags = [];
/// <remarks>Description is unused</remarks>
private const string _description = "";
#endregion
#region Inputs
private const string _createIrdName = "create-ird";
internal readonly FlagInput CreateIrdInput = new(_createIrdName, "--create-ird", "Create IRD from output files (PS3 only)");
private const string _deleteName = "delete";
internal readonly FlagInput DeleteInput = new(_deleteName, ["-d", "--delete"], "Enable unnecessary file deletion");
private const string _disableArchivesName = "disable-archives";
internal readonly FlagInput DisableArchivesInput = new(_disableArchivesName, "--disable-archives", "Disable scanning archives (requires --scan)");
private const string _enableDebugName = "enable-debug";
internal readonly FlagInput EnableDebugInput = new(_enableDebugName, "--enable-debug", "Enable debug protection information (requires --scan)");
private const string _hideDriveLettersName = "hide-drive-letters";
internal readonly FlagInput HideDriveLettersInput = new(_hideDriveLettersName, "--hide-drive-letters", "Hide drive letters from scan output (requires --scan)");
private const string _includeArtifactsName = "include-artifacts";
internal readonly FlagInput IncludeArtifactsInput = new(_includeArtifactsName, "--include-artifacts", "Include artifacts in JSON (requires --json)");
private const string _jsonName = "json";
internal readonly FlagInput JsonInput = new(_jsonName, ["-j", "--json"], "Enable submission JSON output");
private const string _loadSeedName = "load-seed";
internal readonly StringInput LoadSeedInput = new(_loadSeedName, "--load-seed", "Load a seed submission JSON for user information");
private const string _logCompressionName = "log-compression";
internal readonly StringInput LogCompressionInput = new(_logCompressionName, "--log-compression", "Set the log compression type (requires --zip)");
private const string _noPlaceholdersName = "no-placeholders";
internal readonly FlagInput NoPlaceholdersInput = new(_noPlaceholdersName, "--no-placeholders", "Disable placeholder values in submission info");
private const string _noRetrieveName = "no-retrieve";
internal readonly FlagInput NoRetrieveInput = new(_noRetrieveName, "--no-retrieve", "Disable retrieving match information from Redump");
private const string _passwordName = "password";
internal readonly StringInput PasswordInput = new(_passwordName, ["-P", "--password"], "Redump password (incompatible with --no-retrieve)");
private const string _pathName = "path";
internal readonly StringInput PathInput = new(_pathName, ["-p", "--path"], "Physical drive path for additional checks");
private const string _pullAllName = "pull-all";
internal readonly FlagInput PullAllInput = new(_pullAllName, "--pull-all", "Pull all information from Redump (requires --username and --password)");
private const string _scanName = "scan";
internal readonly FlagInput ScanInput = new(_scanName, ["-s", "--scan"], "Enable copy protection scan (requires --path)");
private const string _suffixName = "suffix";
internal readonly FlagInput SuffixInput = new(_suffixName, ["-x", "--suffix"], "Enable adding filename suffix");
private const string _useName = "use";
internal readonly StringInput UseInput = new(_useName, ["-u", "--use"], "Override configured dumping program name");
private const string _usernameName = "username";
internal readonly StringInput UsernameInput = new(_usernameName, ["-U", "--username"], "Redump username (incompatible with --no-retrieve)");
private const string _zipName = "zip";
internal readonly FlagInput ZipInput = new(_zipName, ["-z", "--zip"], "Enable log file compression");
#endregion
public MainFeature()
: base(DisplayName, _flags, _description)
{
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(LogCompressionInput);
Add(DeleteInput);
}
/// <inheritdoc/>
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;
// Read the options from config, if possible
Options = OptionsLoader.LoadFromConfig();
if (Options.FirstRun)
{
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,
LogCompression = LogCompression.DeflateMaximum,
DeleteUnnecessaryFiles = false,
CreateIRDAfterDumping = false,
// Protection Scanning Options
ScanArchivesForProtection = true,
IncludeDebugProtectionInformation = false,
HideDriveLetters = false,
// Redump Login Information
RetrieveMatchInformation = true,
RedumpUsername = null,
RedumpPassword = null,
};
}
// The first argument is the system type
System = args[0].Trim('"').ToRedumpSystem();
// Loop through the arguments and parse out values
for (index = 1; 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))
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;
// Set the log compression type (requires --zip)
else if (LogCompressionInput.ProcessInput(args, ref index))
Options.LogCompression = LogCompressionInput.Value.ToLogCompression();
// 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;
}
// Redump username
else if (UsernameInput.ProcessInput(args, ref index))
Options.RedumpUsername = UsernameInput.Value;
// Redump password
else if (PasswordInput.ProcessInput(args, ref index))
Options.RedumpPassword = PasswordInput.Value;
// 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))
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(DevicePath);
Options.ScanArchivesForProtection = enableArchives && scan && !string.IsNullOrEmpty(DevicePath);
Options.IncludeDebugProtectionInformation = enableDebug && scan && !string.IsNullOrEmpty(DevicePath);
Options.HideDriveLetters = hideDriveLetters && scan && !string.IsNullOrEmpty(DevicePath);
return true;
}
/// <inheritdoc/>
public override bool VerifyInputs() => Inputs.Count > 0;
}
}

View File

@@ -12,7 +12,7 @@
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.4.1</VersionPrefix>
<VersionPrefix>3.5.0</VersionPrefix>
<!-- Package Properties -->
<Title>MPF Check</Title>
@@ -43,7 +43,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.RedumpLib" Version="1.7.4" />
<PackageReference Include="SabreTools.CommandLine" Version="[1.3.2]" />
<PackageReference Include="SabreTools.RedumpLib" Version="[1.7.4]" />
</ItemGroup>
</Project>

View File

@@ -1,13 +1,12 @@
using System;
using System.IO;
using System.Collections.Generic;
#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;
using MPF.Check.Features;
using MPF.Frontend.Features;
using SabreTools.CommandLine;
using SabreTools.CommandLine.Features;
namespace MPF.Check
{
@@ -15,547 +14,126 @@ namespace MPF.Check
{
public static void Main(string[] args)
{
// Create a default options object
var options = new Options()
{
// Internal Program
InternalProgram = InternalProgram.NONE,
// Create the command set
var mainFeature = new MainFeature();
var commandSet = CreateCommands(mainFeature);
// 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;
RedumpSystem? knownSystem;
int startIndex;
// Use interactive mode
if (args.Length > 0 && (args[0] == "-i" || args[0] == "--interactive"))
{
startIndex = 1;
opts = InteractiveMode(options, out knownSystem);
}
// Use normal commandline parameters
else
{
// Try processing the common arguments
bool success = OptionsLoader.ProcessCommonArguments(args, out knownSystem, out var error);
if (!success)
{
DisplayHelp(error);
return;
}
// Loop through and process options
startIndex = 1;
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,
internalProgram: null);
env.SetProcessor();
// Finally, attempt to do the output dance
var result = env.VerifyAndSaveDumpOutput(seedInfo: opts.Seed)
.ConfigureAwait(false).GetAwaiter().GetResult();
Console.WriteLine(result.Message);
}
}
/// <summary>
/// Display help for MPF.Check
/// </summary>
/// <param name="error">Error string to prefix the help text with</param>
private static void DisplayHelp(string? error = null)
{
if (error != null)
Console.WriteLine(error);
Console.WriteLine("Usage:");
Console.WriteLine("MPF.Check <system> [options] </path/to/output.cue/iso> ...");
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("Check Options:");
Console.WriteLine("-u, --use <program> Dumping program output type [REQUIRED]");
Console.WriteLine(" --load-seed <path> 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 <user> <pw> Redump username and password (incompatible with --no-retrieve)");
Console.WriteLine(" --pull-all Pull all information from Redump (requires --credentials)");
Console.WriteLine("-p, --path <drivepath> 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();
}
/// <summary>
/// Enable interactive mode for entering information
/// </summary>
private static CommandOptions InteractiveMode(Options options, out RedumpSystem? system)
{
// Create return values
var opts = new CommandOptions();
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 system (Currently '{system}')");
Console.WriteLine($"2) Set dumping program (Currently '{options.InternalProgram}')");
Console.WriteLine($"3) Set seed path (Currently '{opts.Seed}')");
Console.WriteLine($"4) Add placeholders (Currently '{options.AddPlaceholders}')");
Console.WriteLine($"5) Create IRD (Currently '{options.CreateIRDAfterDumping}')");
Console.WriteLine($"6) Attempt Redump matches (Currently '{options.RetrieveMatchInformation}')");
Console.WriteLine($"7) Redump credentials (Currently '{options.RedumpUsername}')");
Console.WriteLine($"8) Pull all information (Currently '{options.PullAllInformation}')");
Console.WriteLine($"9) Set device path (Currently '{opts.DevicePath}')");
Console.WriteLine($"A) Scan for protection (Currently '{scan}')");
Console.WriteLine($"B) Scan archives for protection (Currently '{enableArchives}')");
Console.WriteLine($"C) Debug protection scan output (Currently '{enableDebug}')");
Console.WriteLine($"D) Hide drive letters in protection output (Currently '{hideDriveLetters}')");
Console.WriteLine($"E) Hide filename suffix (Currently '{options.AddFilenameSuffix}')");
Console.WriteLine($"F) Output submission JSON (Currently '{options.OutputSubmissionJSON}')");
Console.WriteLine($"G) Include JSON artifacts (Currently '{options.IncludeArtifacts}')");
Console.WriteLine($"H) Compress logs (Currently '{options.CompressLogFiles}')");
Console.WriteLine($"I) 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 system;
case "2":
goto dumpingProgram;
case "3":
goto seedPath;
case "4":
options.AddPlaceholders = !options.AddPlaceholders;
goto root;
case "5":
options.CreateIRDAfterDumping = !options.CreateIRDAfterDumping;
goto root;
case "6":
options.RetrieveMatchInformation = !options.RetrieveMatchInformation;
goto root;
case "7":
goto redumpCredentials;
case "8":
options.PullAllInformation = !options.PullAllInformation;
goto root;
case "9":
goto devicePath;
case "a":
case "A":
scan = !scan;
goto root;
case "b":
case "B":
enableArchives = !enableArchives;
goto root;
case "c":
case "C":
enableDebug = !enableDebug;
goto root;
case "d":
case "D":
hideDriveLetters = !hideDriveLetters;
goto root;
case "e":
case "E":
options.AddFilenameSuffix = !options.AddFilenameSuffix;
goto root;
case "f":
case "F":
options.OutputSubmissionJSON = !options.OutputSubmissionJSON;
goto root;
case "g":
case "G":
options.IncludeArtifacts = !options.IncludeArtifacts;
goto root;
case "h":
case "H":
options.CompressLogFiles = !options.CompressLogFiles;
goto root;
case "i":
case "I":
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;
}
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;
}
/// <summary>
/// Load the current set of options from application arguments
/// </summary>
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 we have no args, show the help and quit
if (args == null || args.Length == 0)
{
startIndex = 0;
return opts;
BaseFeature.DisplayHelp();
return;
}
// If we have an invalid start index, just return
if (startIndex < 0 || startIndex >= args.Length)
return opts;
// Get the first argument as a feature flag
string featureName = args[0];
// Loop through the arguments and parse out values
for (; startIndex < args.Length; startIndex++)
// Try processing the standalone arguments
var topLevel = commandSet.GetTopLevel(featureName);
switch (topLevel)
{
// 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++;
}
// Standalone Options
case Help: BaseFeature.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;
// 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++;
}
// Interactive Mode
case InteractiveFeature interactive:
if (!interactive.ProcessArgs(args, 0))
{
BaseFeature.DisplayHelp();
return;
}
if (!interactive.VerifyInputs())
{
Console.Error.WriteLine("At least one input is required");
BaseFeature.DisplayHelp();
return;
}
if (!interactive.Execute())
{
BaseFeature.DisplayHelp();
return;
}
break;
// Default Behavior
default:
if (!mainFeature.ProcessArgs(args, 0))
{
BaseFeature.DisplayHelp();
return;
}
if (!mainFeature.VerifyInputs())
{
Console.Error.WriteLine("At least one input is required");
BaseFeature.DisplayHelp();
return;
}
if (!mainFeature.Execute())
{
BaseFeature.DisplayHelp();
return;
}
// 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;
}
/// <summary>
/// Represents commandline options
/// Create the command set for the program
/// </summary>
private class CommandOptions
private static CommandSet CreateCommands(MainFeature mainFeature)
{
/// <summary>
/// Seed submission info from an input file
/// </summary>
public SubmissionInfo? Seed { get; set; } = null;
List<string> header = [
"MPF.CLI [standalone|system] [options] <path> ...",
string.Empty,
];
/// <summary>
/// Path to the device to scan
/// </summary>
public string? DevicePath { get; set; } = null;
List<string> footer = [
string.Empty,
"WARNING: Check will overwrite both any existing submission information files as well",
"as any log archives. Please make backups of those if you need to before running Check.",
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());
// Check Options
commandSet.Add(mainFeature.UseInput);
commandSet.Add(mainFeature.LoadSeedInput);
commandSet.Add(mainFeature.NoPlaceholdersInput);
commandSet.Add(mainFeature.CreateIrdInput);
commandSet.Add(mainFeature.NoRetrieveInput);
commandSet.Add(mainFeature.UsernameInput);
commandSet.Add(mainFeature.PasswordInput);
commandSet.Add(mainFeature.PullAllInput);
commandSet.Add(mainFeature.PathInput);
commandSet.Add(mainFeature.ScanInput);
commandSet.Add(mainFeature.DisableArchivesInput);
commandSet.Add(mainFeature.EnableDebugInput);
commandSet.Add(mainFeature.HideDriveLettersInput);
commandSet.Add(mainFeature.SuffixInput);
commandSet.Add(mainFeature.JsonInput);
commandSet.Add(mainFeature.IncludeArtifactsInput);
commandSet.Add(mainFeature.ZipInput);
commandSet.Add(mainFeature.LogCompressionInput);
commandSet.Add(mainFeature.DeleteInput);
return commandSet;
}
}
}

View File

@@ -17,7 +17,7 @@
<PackageReference Include="Microsoft.CodeCoverage" Version="17.14.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.7.4" />
<PackageReference Include="SabreTools.RedumpLib" Version="[1.7.4]" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="xunit.analyzers" Version="1.24.0" />

View File

@@ -41,9 +41,9 @@ namespace MPF.ExecutionContexts.Test
};
[Theory]
[InlineData(null, null, null, "filename.bin", null, "disc --verbose --retries=1000 --image-name=\"filename\" --drive-type=GENERIC --drive-read-method=BE --drive-sector-order=DATA_C2_SUB --plextor-leadin-retries=1000")]
[InlineData(null, null, null, "filename.bin", null, "disc --verbose --skeleton --retries=1000 --image-name=\"filename\" --drive-type=GENERIC --drive-read-method=BE --drive-sector-order=DATA_C2_SUB --plextor-leadin-retries=1000")]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.CDROM, "/dev/sr0", "path/filename.bin", 2, "disc --verbose --skeleton --drive=/dev/sr0 --speed=2 --retries=1000 --image-path=\"path\" --image-name=\"filename\" --drive-type=GENERIC --drive-read-method=BE --drive-sector-order=DATA_C2_SUB --plextor-leadin-retries=1000")]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, "/dev/sr0", "path/filename.bin", 2, "disc --verbose --drive=/dev/sr0 --speed=2 --retries=1000 --image-path=\"path\" --image-name=\"filename\" --drive-type=GENERIC --drive-read-method=BE --drive-sector-order=DATA_C2_SUB --plextor-leadin-retries=1000")]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, "/dev/sr0", "path/filename.bin", 2, "disc --verbose --skeleton --drive=/dev/sr0 --speed=2 --retries=1000 --image-path=\"path\" --image-name=\"filename\" --drive-type=GENERIC --drive-read-method=BE --drive-sector-order=DATA_C2_SUB --plextor-leadin-retries=1000")]
[InlineData(RedumpSystem.NintendoGameCube, MediaType.NintendoGameCubeGameDisc, "/dev/sr0", "path/filename.bin", 2, "disc --verbose --drive=/dev/sr0 --speed=2 --retries=1000 --image-path=\"path\" --image-name=\"filename\" --drive-type=GENERIC --drive-read-method=BE --drive-sector-order=DATA_C2_SUB --plextor-leadin-retries=1000")]
[InlineData(RedumpSystem.NintendoWii, MediaType.NintendoWiiOpticalDisc, "/dev/sr0", "path/filename.bin", 2, "disc --verbose --drive=/dev/sr0 --speed=2 --retries=1000 --image-path=\"path\" --image-name=\"filename\" --drive-type=GENERIC --drive-read-method=BE --drive-sector-order=DATA_C2_SUB --plextor-leadin-retries=1000")]
[InlineData(RedumpSystem.HDDVDVideo, MediaType.HDDVD, "/dev/sr0", "path/filename.bin", 2, "disc --verbose --drive=/dev/sr0 --speed=2 --retries=1000 --image-path=\"path\" --image-name=\"filename\" --drive-type=GENERIC --drive-read-method=BE --drive-sector-order=DATA_C2_SUB --plextor-leadin-retries=1000")]

View File

@@ -11,7 +11,7 @@
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.4.1</VersionPrefix>
<VersionPrefix>3.5.0</VersionPrefix>
<WarningsNotAsErrors>NU5104</WarningsNotAsErrors>
<!-- Package Properties -->
@@ -32,7 +32,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.RedumpLib" Version="1.7.4" />
<PackageReference Include="SabreTools.RedumpLib" Version="[1.7.4]" />
</ItemGroup>
</Project>

View File

@@ -313,20 +313,21 @@ namespace MPF.ExecutionContexts.Redumper
}
if (GetBooleanSetting(options, SettingConstants.EnableSkeleton, SettingConstants.EnableSkeletonDefault))
{
// Enable skeleton for CD dumps only by default
// Enable skeleton for CD and DVD only, by default
switch (MediaType)
{
case SabreTools.RedumpLib.Data.MediaType.CDROM:
switch (RedumpSystem)
{
case SabreTools.RedumpLib.Data.RedumpSystem.SuperAudioCD:
break;
default:
this[FlagStrings.Skeleton] = true;
(_inputs[FlagStrings.Skeleton] as FlagInput)?.SetValue(true);
break;
}
case SabreTools.RedumpLib.Data.MediaType.DVD:
this[FlagStrings.Skeleton] = true;
(_inputs[FlagStrings.Skeleton] as FlagInput)?.SetValue(true);
break;
// If the type is unknown, also enable
case null:
this[FlagStrings.Skeleton] = true;
(_inputs[FlagStrings.Skeleton] as FlagInput)?.SetValue(true);
break;
default:
break;
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using SabreTools.RedumpLib.Data;
using Xunit;
using LogCompression = MPF.Processors.LogCompression;
using RedumperDriveType = MPF.ExecutionContexts.Redumper.DriveType;
using RedumperReadMethod = MPF.ExecutionContexts.Redumper.ReadMethod;
using RedumperSectorOrder = MPF.ExecutionContexts.Redumper.SectorOrder;
@@ -27,6 +28,29 @@ namespace MPF.Frontend.Test
{
string? actual = prog.LongName();
Assert.Equal(expected, actual);
if (prog != null)
{
actual = EnumExtensions.GetLongName(prog);
Assert.Equal(expected, actual);
}
}
[Theory]
[InlineData(null, "Unknown")]
[InlineData(LogCompression.DeflateDefault, "ZIP using Deflate (Level 5)")]
[InlineData(LogCompression.DeflateMaximum, "ZIP using Deflate (Level 9)")]
[InlineData(LogCompression.Zstd19, "ZIP using Zstd (Level 19)")]
public void LongName_LogCompression(LogCompression? comp, string? expected)
{
string? actual = comp.LongName();
Assert.Equal(expected, actual);
if (comp != null)
{
actual = EnumExtensions.GetLongName(comp);
Assert.Equal(expected, actual);
}
}
[Theory]
@@ -38,6 +62,12 @@ namespace MPF.Frontend.Test
{
string? actual = method.LongName();
Assert.Equal(expected, actual);
if (method != null)
{
actual = EnumExtensions.GetLongName(method);
Assert.Equal(expected, actual);
}
}
[Theory]
@@ -51,6 +81,12 @@ namespace MPF.Frontend.Test
{
string? actual = order.LongName();
Assert.Equal(expected, actual);
if (order != null)
{
actual = EnumExtensions.GetLongName(order);
Assert.Equal(expected, actual);
}
}
[Theory]
@@ -67,6 +103,12 @@ namespace MPF.Frontend.Test
{
string? actual = type.LongName();
Assert.Equal(expected, actual);
if (type != null)
{
actual = EnumExtensions.GetLongName(type);
Assert.Equal(expected, actual);
}
}
#endregion

View File

@@ -17,7 +17,7 @@
<PackageReference Include="Microsoft.CodeCoverage" Version="17.14.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.7.4" />
<PackageReference Include="SabreTools.RedumpLib" Version="[1.7.4]" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="xunit.analyzers" Version="1.24.0" />

View File

@@ -356,6 +356,9 @@ namespace MPF.Frontend
// If we're on an unsupported type, update the status accordingly
return mediaType switch
{
// Null means it will be handled by the program
null => ResultEventArgs.Success("Ready to dump"),
// Fully supported types
MediaType.BluRay
or MediaType.CDROM
@@ -377,7 +380,7 @@ namespace MPF.Frontend
MediaType.UMD => ResultEventArgs.Failure($"{mediaType.LongName()} supported for submission info parsing"),
// Specifically unknown type
MediaType.NONE => ResultEventArgs.Failure($"Please select a valid media type"),
MediaType.NONE => ResultEventArgs.Failure("Please select a valid media type"),
// Undumpable but recognized types
_ => ResultEventArgs.Failure($"{mediaType.LongName()} media are not supported for dumping"),
@@ -485,6 +488,8 @@ namespace MPF.Frontend
// Determine the media type from the processor
MediaType? mediaType = _processor.DetermineMediaType(outputDirectory, outputFilename);
if (mediaType == null)
return ResultEventArgs.Failure("Could not determine the media type from output files...");
// Extract the information from the output files
resultProgress.Report(ResultEventArgs.Success("Extracting output information from output files..."));
@@ -498,14 +503,9 @@ namespace MPF.Frontend
resultProgress,
protectionProgress);
if (submissionInfo == null)
{
resultProgress.Report(ResultEventArgs.Failure("There was an issue extracting information!"));
return ResultEventArgs.Failure();
}
return ResultEventArgs.Failure("There was an issue extracting information!");
else
{
resultProgress.Report(ResultEventArgs.Success("Extracting information complete!"));
}
// Inject seed submission info data, if necessary
if (seedInfo != null)
@@ -585,7 +585,7 @@ namespace MPF.Frontend
await Task.Run(() =>
#endif
{
bool compressSuccess = _processor.CompressLogFiles(mediaType, outputDirectory, outputFilename, filenameSuffix, out string compressResult);
bool compressSuccess = _processor.CompressLogFiles(mediaType, _options.LogCompression, outputDirectory, outputFilename, filenameSuffix, out string compressResult);
if (compressSuccess)
resultProgress.Report(ResultEventArgs.Success(compressResult));
else

View File

@@ -6,6 +6,7 @@ using System.Collections.Concurrent;
#endif
using System.Reflection;
using SabreTools.RedumpLib.Data;
using LogCompression = MPF.Processors.LogCompression;
using RedumperDriveType = MPF.ExecutionContexts.Redumper.DriveType;
using RedumperReadMethod = MPF.ExecutionContexts.Redumper.ReadMethod;
using RedumperSectorOrder = MPF.ExecutionContexts.Redumper.SectorOrder;
@@ -61,6 +62,14 @@ namespace MPF.Frontend
}
}
/// <summary>
/// Get the string representation of the InternalProgram enum values
/// </summary>
/// <param name="prog">InternalProgram value to convert</param>
/// <returns>String representing the value, if possible</returns>
public static string LongName(this InternalProgram prog)
=> ((InternalProgram?)prog).LongName();
/// <summary>
/// Get the string representation of the InternalProgram enum values
/// </summary>
@@ -92,6 +101,31 @@ namespace MPF.Frontend
};
}
/// <summary>
/// Get the string representation of the LogCompression enum values
/// </summary>
/// <param name="comp">LogCompression value to convert</param>
/// <returns>String representing the value, if possible</returns>
public static string LongName(this LogCompression comp)
=> ((LogCompression?)comp).LongName();
/// <summary>
/// Get the string representation of the LogCompression enum values
/// </summary>
/// <param name="comp">LogCompression value to convert</param>
/// <returns>String representing the value, if possible</returns>
public static string LongName(this LogCompression? comp)
{
return comp switch
{
LogCompression.DeflateDefault => "ZIP using Deflate (Level 5)",
LogCompression.DeflateMaximum => "ZIP using Deflate (Level 9)",
LogCompression.Zstd19 => "ZIP using Zstd (Level 19)",
_ => "Unknown",
};
}
/// <summary>
/// Get the string representation of the RedumperReadMethod enum values
/// </summary>
@@ -232,6 +266,28 @@ namespace MPF.Frontend
};
}
/// <summary>
/// Get the LogCompression enum value for a given string
/// </summary>
/// <param name="logCompression">String value to convert</param>
/// <returns>LogCompression represented by teh string, if possible</returns>
public static LogCompression ToLogCompression(this string? logCompression)
{
return (logCompression?.ToLowerInvariant()) switch
{
"deflate"
or "deflatedefault"
or "zip" => LogCompression.DeflateDefault,
"deflatemaximum"
or "max"
or "maximum" => LogCompression.DeflateMaximum,
"zstd"
or "zstd19" => LogCompression.Zstd19,
_ => LogCompression.DeflateDefault,
};
}
/// <summary>
/// Get the RedumperReadMethod enum value for a given string
/// </summary>

View File

@@ -0,0 +1,38 @@
using System;
using SabreTools.CommandLine;
namespace MPF.Frontend.Features
{
public class ListCodesFeature : Feature
{
#region Feature Definition
public const string DisplayName = "listcodes";
private static readonly string[] _flags = ["lc", "listcodes"];
private const string _description = "List supported comment/content site codes";
#endregion
public ListCodesFeature()
: base(DisplayName, _flags, _description)
{
}
/// <inheritdoc/>
public override bool Execute()
{
Console.WriteLine("Supported Site Codes:");
foreach (string siteCode in SabreTools.RedumpLib.Data.Extensions.ListSiteCodes())
{
Console.WriteLine(siteCode);
}
return true;
}
/// <inheritdoc/>
public override bool VerifyInputs() => true;
}
}

View File

@@ -0,0 +1,38 @@
using System;
using SabreTools.CommandLine;
namespace MPF.Frontend.Features
{
public class ListMediaTypesFeature : Feature
{
#region Feature Definition
public const string DisplayName = "listmedia";
private static readonly string[] _flags = ["lm", "listmedia"];
private const string _description = "List supported media types";
#endregion
public ListMediaTypesFeature()
: base(DisplayName, _flags, _description)
{
}
/// <inheritdoc/>
public override bool Execute()
{
Console.WriteLine("Supported Media Types:");
foreach (string mediaType in SabreTools.RedumpLib.Data.Extensions.ListMediaTypes())
{
Console.WriteLine(mediaType);
}
return true;
}
/// <inheritdoc/>
public override bool VerifyInputs() => true;
}
}

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using SabreTools.CommandLine;
namespace MPF.Frontend.Features
{
public class ListProgramsFeature : Feature
{
#region Feature Definition
public const string DisplayName = "listprograms";
private static readonly string[] _flags = ["lp", "listprograms"];
private const string _description = "List supported dumping program outputs";
#endregion
public ListProgramsFeature()
: base(DisplayName, _flags, _description)
{
}
/// <inheritdoc/>
public override bool Execute()
{
Console.WriteLine("Supported Programs:");
foreach (string program in ListPrograms())
{
Console.WriteLine(program);
}
return true;
}
/// <inheritdoc/>
public override bool VerifyInputs() => true;
/// <summary>
/// List all programs with their short usable names
/// </summary>
private static List<string> ListPrograms()
{
var programs = new List<string>();
foreach (var val in Enum.GetValues(typeof(InternalProgram)))
{
if (((InternalProgram)val!) == InternalProgram.NONE)
continue;
programs.Add($"{((InternalProgram?)val).ShortName()} - {((InternalProgram?)val).LongName()}");
}
return programs;
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using SabreTools.CommandLine;
namespace MPF.Frontend.Features
{
public class ListSystemsFeature : Feature
{
#region Feature Definition
public const string DisplayName = "listsystems";
private static readonly string[] _flags = ["ls", "listsystems"];
private const string _description = "List supported system types";
#endregion
public ListSystemsFeature()
: base(DisplayName, _flags, _description)
{
}
/// <inheritdoc/>
public override bool Execute()
{
Console.WriteLine("Supported Systems:");
foreach (string system in SabreTools.RedumpLib.Data.Extensions.ListSystems())
{
Console.WriteLine(system);
}
return true;
}
/// <inheritdoc/>
public override bool VerifyInputs() => true;
}
}

View File

@@ -0,0 +1,34 @@
using System;
using MPF.Frontend.Tools;
using SabreTools.CommandLine;
namespace MPF.Frontend.Features
{
public class VersionFeature : Feature
{
#region Feature Definition
public const string DisplayName = "version";
private static readonly string[] _flags = ["version"];
private const string _description = "Display the program version";
#endregion
public VersionFeature()
: base(DisplayName, _flags, _description)
{
}
/// <inheritdoc/>
public override bool Execute()
{
Console.WriteLine(FrontendTool.GetCurrentVersion() ?? "Unknown version");
return true;
}
/// <inheritdoc/>
public override bool VerifyInputs() => true;
}
}

View File

@@ -10,7 +10,7 @@
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.4.1</VersionPrefix>
<VersionPrefix>3.5.0</VersionPrefix>
<!-- Package Properties -->
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
@@ -31,13 +31,14 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BinaryObjectScanner" Version="3.4.2" />
<PackageReference Include="LibIRD" Version="1.0.0" />
<PackageReference Include="BinaryObjectScanner" Version="[3.4.5]" />
<PackageReference Include="LibIRD" Version="[1.0.0]" />
<PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" />
<PackageReference Include="Microsoft.Net.Http" Version="2.2.29" Condition="$(TargetFramework.StartsWith(`net452`))" />
<PackageReference Include="MinThreadingBridge" Version="0.11.4" Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.7.4" />
<PackageReference Include="SabreTools.CommandLine" Version="[1.3.2]" />
<PackageReference Include="SabreTools.RedumpLib" Version="[1.7.4]" />
<PackageReference Include="System.Net.Http" Version="4.3.4" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" />
</ItemGroup>

View File

@@ -2,6 +2,7 @@
using SabreTools.RedumpLib.Data;
using AaruSettings = MPF.ExecutionContexts.Aaru.SettingConstants;
using DICSettings = MPF.ExecutionContexts.DiscImageCreator.SettingConstants;
using LogCompression = MPF.Processors.LogCompression;
using RedumperDriveType = MPF.ExecutionContexts.Redumper.DriveType;
using RedumperReadMethod = MPF.ExecutionContexts.Redumper.ReadMethod;
using RedumperSectorOrder = MPF.ExecutionContexts.Redumper.SectorOrder;
@@ -158,7 +159,7 @@ namespace MPF.Frontend
get
{
var valueString = GetStringSetting(Settings, "DefaultSystem", RedumpSystem.IBMPCcompatible.LongName());
var valueEnum = Extensions.ToRedumpSystem(valueString ?? string.Empty);
var valueEnum = (valueString ?? string.Empty).ToRedumpSystem();
return valueEnum;
}
set
@@ -557,6 +558,22 @@ namespace MPF.Frontend
set { Settings["CompressLogFiles"] = value.ToString(); }
}
/// <summary>
/// Compression type used during log compression
/// </summary>
public LogCompression LogCompression
{
get
{
var valueString = GetStringSetting(Settings, "LogCompression", LogCompression.DeflateMaximum.ToString());
return valueString.ToLogCompression();
}
set
{
Settings["LogCompression"] = value.ToString();
}
}
/// <summary>
/// Delete unnecessary files to reduce space
/// </summary>

View File

@@ -32,93 +32,6 @@ namespace MPF.Frontend.Tools
#region Arguments
/// <summary>
/// Process any standalone arguments for the program
/// </summary>
/// <returns>True if one of the arguments was processed, false otherwise</returns>
public static bool? ProcessStandaloneArguments(string[] args)
{
// Help options
if (args.Length == 0 || args[0] == "-h" || args[0] == "-?" || args[0] == "--help")
return null;
if (args[0] == "--version")
{
Console.WriteLine(FrontendTool.GetCurrentVersion() ?? "Unknown version");
return true;
}
// List options
if (args[0] == "-lc" || args[0] == "--listcodes")
{
Console.WriteLine("Supported Site Codes:");
foreach (string siteCode in Extensions.ListSiteCodes())
{
Console.WriteLine(siteCode);
}
return true;
}
else if (args[0] == "-lm" || args[0] == "--listmedia")
{
Console.WriteLine("Supported Media Types:");
foreach (string mediaType in Extensions.ListMediaTypes())
{
Console.WriteLine(mediaType);
}
return true;
}
else if (args[0] == "-lp" || args[0] == "--listprograms")
{
Console.WriteLine("Supported Programs:");
foreach (string program in ListPrograms())
{
Console.WriteLine(program);
}
return true;
}
else if (args[0] == "-ls" || args[0] == "--listsystems")
{
Console.WriteLine("Supported Systems:");
foreach (string system in Extensions.ListSystems())
{
Console.WriteLine(system);
}
return true;
}
return false;
}
/// <summary>
/// Process common arguments for all functionality
/// </summary>
/// <returns>True if all arguments pass, false otherwise</returns>
public static bool ProcessCommonArguments(string[] args, out RedumpSystem? system, out string? message)
{
// All other use requires at least 3 arguments
if (args.Length < 2)
{
system = null;
message = "Invalid number of arguments";
return false;
}
// Check the RedumpSystem
system = Extensions.ToRedumpSystem(args[0].Trim('"'));
if (system == null)
{
message = $"{args[0]} is not a recognized system";
return false;
}
message = null;
return true;
}
/// <summary>
/// Get the MediaType enum value for a given string
/// </summary>
@@ -262,24 +175,6 @@ namespace MPF.Frontend.Tools
};
}
/// <summary>
/// List all programs with their short usable names
/// </summary>
private static List<string> ListPrograms()
{
var programs = new List<string>();
foreach (var val in Enum.GetValues(typeof(InternalProgram)))
{
if (((InternalProgram)val!) == InternalProgram.NONE)
continue;
programs.Add($"{((InternalProgram?)val).ShortName()} - {((InternalProgram?)val).LongName()}");
}
return programs;
}
#endregion
#region Configuration
@@ -293,12 +188,9 @@ namespace MPF.Frontend.Tools
if (string.IsNullOrEmpty(ConfigurationPath))
return new Options();
// Ensure the file exists
// If the file does not exist
if (!File.Exists(ConfigurationPath) || new FileInfo(ConfigurationPath).Length == 0)
{
File.Create(ConfigurationPath).Dispose();
return new Options();
}
var serializer = JsonSerializer.Create();
var stream = File.Open(ConfigurationPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

View File

@@ -166,7 +166,7 @@ namespace MPF.Frontend.Tools
try
{
var antiModchip = new BinaryObjectScanner.Protection.PSXAntiModchip();
foreach (string file in IOExtensions.SafeGetFiles(path!, "*", SearchOption.AllDirectories))
foreach (string file in path!.SafeGetFiles("*", SearchOption.AllDirectories))
{
try
{

View File

@@ -167,9 +167,9 @@ namespace MPF.Frontend.Tools
// Login to Redump, if possible
var wc = new RedumpClient();
if (options.RedumpUsername != null && options.RedumpPassword != null)
if (!string.IsNullOrEmpty(options.RedumpUsername) && !string.IsNullOrEmpty(options.RedumpPassword))
{
bool? loggedIn = await wc.Login(options.RedumpUsername, options.RedumpPassword);
bool? loggedIn = await wc.Login(options.RedumpUsername!, options.RedumpPassword!);
if (loggedIn == null)
{
resultProgress?.Report(ResultEventArgs.Failure("There was an unknown error connecting to Redump, skipping..."));

View File

@@ -1613,7 +1613,7 @@ namespace MPF.Frontend.ViewModels
try
{
if (Directory.Exists(Path.Combine(drive.Name, "$SystemUpdate"))
&& IOExtensions.SafeGetFiles(Path.Combine(drive.Name, "$SystemUpdate")).Length > 0
&& Path.Combine(drive.Name, "$SystemUpdate").SafeGetFiles().Length > 0
&& drive.TotalSize <= 500_000_000)
{
return RedumpSystem.MicrosoftXbox360;
@@ -1828,13 +1828,13 @@ namespace MPF.Frontend.ViewModels
try
{
if (Directory.Exists(Path.Combine(drive.Name, "AUDIO_TS"))
&& IOExtensions.SafeGetFiles(Path.Combine(drive.Name, "AUDIO_TS")).Length > 0)
&& Path.Combine(drive.Name, "AUDIO_TS").SafeGetFiles().Length > 0)
{
return RedumpSystem.DVDAudio;
}
else if (Directory.Exists(Path.Combine(drive.Name, "VIDEO_TS"))
&& IOExtensions.SafeGetFiles(Path.Combine(drive.Name, "VIDEO_TS")).Length > 0)
&& Path.Combine(drive.Name, "VIDEO_TS").SafeGetFiles().Length > 0)
{
return RedumpSystem.DVDVideo;
}
@@ -1845,7 +1845,7 @@ namespace MPF.Frontend.ViewModels
try
{
if (Directory.Exists(Path.Combine(drive.Name, "HVDVD_TS"))
&& IOExtensions.SafeGetFiles(Path.Combine(drive.Name, "HVDVD_TS")).Length > 0)
&& Path.Combine(drive.Name, "HVDVD_TS").SafeGetFiles().Length > 0)
{
return RedumpSystem.HDDVDVideo;
}
@@ -1856,7 +1856,7 @@ namespace MPF.Frontend.ViewModels
try
{
if (Directory.Exists(Path.Combine(drive.Name, "PHOTO_CD"))
&& IOExtensions.SafeGetFiles(Path.Combine(drive.Name, "PHOTO_CD")).Length > 0)
&& Path.Combine(drive.Name, "PHOTO_CD").SafeGetFiles().Length > 0)
{
return RedumpSystem.PhotoCD;
}
@@ -1867,7 +1867,7 @@ namespace MPF.Frontend.ViewModels
try
{
if (Directory.Exists(Path.Combine(drive.Name, "VCD"))
&& IOExtensions.SafeGetFiles(Path.Combine(drive.Name, "VCD")).Length > 0)
&& Path.Combine(drive.Name, "VCD").SafeGetFiles().Length > 0)
{
return RedumpSystem.VideoCD;
}
@@ -2248,6 +2248,9 @@ namespace MPF.Frontend.ViewModels
resultProgress: resultProgress,
protectionProgress: protectionProgress,
processUserInfo: _processUserInfo);
if (!result)
ErrorLogLn(result.Message);
}
else
{

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using MPF.Frontend.ComboBoxItems;
using LogCompression = MPF.Processors.LogCompression;
using RedumperDriveType = MPF.ExecutionContexts.Redumper.DriveType;
using RedumperReadMethod = MPF.ExecutionContexts.Redumper.ReadMethod;
using RedumperSectorOrder = MPF.ExecutionContexts.Redumper.SectorOrder;
@@ -50,6 +51,11 @@ namespace MPF.Frontend.ViewModels
/// </summary>
public static List<Element<InternalProgram>> InternalPrograms => PopulateInternalPrograms();
/// <summary>
/// List of available log compression methods
/// </summary>
public static List<Element<LogCompression>> LogCompressions => PopulateLogCompressions();
/// <summary>
/// Current list of supported Redumper read methods
/// </summary>
@@ -99,6 +105,16 @@ namespace MPF.Frontend.ViewModels
return internalPrograms.ConvertAll(ip => new Element<InternalProgram>(ip));
}
/// <summary>
/// Get a complete list of supported log compression methods
/// </summary>
/// <returns></returns>
private static List<Element<LogCompression>> PopulateLogCompressions()
{
var logCompressions = new List<LogCompression> { LogCompression.DeflateDefault, LogCompression.DeflateMaximum, LogCompression.Zstd19 };
return logCompressions.ConvertAll(lc => new Element<LogCompression>(lc));
}
/// <summary>
/// Get a complete list of supported redumper drive read methods
/// </summary>

View File

@@ -27,7 +27,7 @@
<PackageReference Include="Microsoft.CodeCoverage" Version="17.14.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.7.4" />
<PackageReference Include="SabreTools.RedumpLib" Version="[1.7.4]" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="xunit.analyzers" Version="1.24.0" />

View File

@@ -103,7 +103,7 @@ namespace MPF.Processors.Test
var processor = new Redumper(RedumpSystem.IBMPCcompatible);
var actual = processor.GetOutputFiles(MediaType.CDROM, outputDirectory, outputFilename);
Assert.Equal(17, actual.Count);
Assert.Equal(19, actual.Count);
}
[Fact]
@@ -114,7 +114,7 @@ namespace MPF.Processors.Test
var processor = new Redumper(RedumpSystem.IBMPCcompatible);
var actual = processor.GetOutputFiles(MediaType.DVD, outputDirectory, outputFilename);
Assert.Equal(15, actual.Count);
Assert.Equal(17, actual.Count);
}
[Fact]
@@ -125,7 +125,7 @@ namespace MPF.Processors.Test
var processor = new Redumper(RedumpSystem.IBMPCcompatible);
var actual = processor.GetOutputFiles(MediaType.HDDVD, outputDirectory, outputFilename);
Assert.Equal(10, actual.Count);
Assert.Equal(12, actual.Count);
}
[Fact]
@@ -136,7 +136,7 @@ namespace MPF.Processors.Test
var processor = new Redumper(RedumpSystem.IBMPCcompatible);
var actual = processor.GetOutputFiles(MediaType.BluRay, outputDirectory, outputFilename);
Assert.Equal(10, actual.Count);
Assert.Equal(12, actual.Count);
}
[Fact]
@@ -161,7 +161,7 @@ namespace MPF.Processors.Test
string outputFilename = string.Empty;
var processor = new Redumper(RedumpSystem.IBMPCcompatible);
var actual = processor.FoundAllFiles(MediaType.CDROM, outputDirectory, outputFilename);
Assert.Equal(8, actual.Count);
Assert.Equal(7, actual.Count);
}
[Fact]

View File

@@ -1,13 +1,17 @@
using System;
using System.Collections.Generic;
using System.IO;
#if NET452_OR_GREATER || NETCOREAPP
using System.IO.Compression;
#endif
using System.Text;
using SabreTools.Hashing;
using SabreTools.Data.Models.Logiqx;
using SabreTools.RedumpLib.Data;
#if NET462_OR_GREATER || NETCOREAPP
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using SharpCompress.Compressors.Deflate;
using SharpCompress.Writers.Zip;
#endif
namespace MPF.Processors
{
@@ -73,6 +77,7 @@ namespace MPF.Processors
/// Compress log files to save space
/// </summary>
/// <param name="mediaType">Media type for controlling expected file sets</param>
/// <param name="logCompression">Compression type to use for logs</param>
/// <param name="outputDirectory">Output folder to use as the base path</param>
/// <param name="outputFilename">Output filename to use as the base path</param>
/// <param name="filenameSuffix">Output filename to use as the base path</param>
@@ -80,12 +85,13 @@ namespace MPF.Processors
/// <returns>True if the process succeeded, false otherwise</returns>
/// <remarks>Assumes filename has an extension</remarks>
public bool CompressLogFiles(MediaType? mediaType,
LogCompression logCompression,
string? outputDirectory,
string outputFilename,
string? filenameSuffix,
out string status)
{
#if NET20 || NET35 || NET40
#if NET20 || NET35 || NET40 || NET452
status = "Log compression is not available for this framework version";
return false;
#else
@@ -108,26 +114,46 @@ namespace MPF.Processors
return true;
}
// If the file already exists, we want to delete the old one
try
// If the file already exists, change the archive name
if (File.Exists(archiveName))
{
if (File.Exists(archiveName))
File.Delete(archiveName);
}
catch
{
status = "Could not delete old archive!";
return false;
string now = DateTime.Now.ToString("yyyyMMdd-HHmmss");
archiveName = $"{basePath}_logs_{now}.zip";
}
// Add the log files to the archive and delete the uncompressed file after
ZipArchive? zf = null;
bool disposed = false;
try
{
zf = ZipFile.Open(archiveName, ZipArchiveMode.Create);
zf = ZipArchive.Create();
_ = AddToArchive(zf, zippableFiles, outputDirectory, true);
_ = AddToArchive(zf, generatedFiles, outputDirectory, false);
List<string> successful = AddToArchive(zf, zippableFiles, outputDirectory);
_ = AddToArchive(zf, generatedFiles, outputDirectory);
switch (logCompression)
{
case LogCompression.DeflateMaximum:
zf.SaveTo(archiveName, new ZipWriterOptions(CompressionType.Deflate, CompressionLevel.BestCompression) { UseZip64 = true });
break;
case LogCompression.Zstd19:
zf.SaveTo(archiveName, new ZipWriterOptions(CompressionType.ZStandard, (CompressionLevel)19) { UseZip64 = true });
break;
case LogCompression.DeflateDefault:
default:
zf.SaveTo(archiveName, new ZipWriterOptions(CompressionType.Deflate, CompressionLevel.Default) { UseZip64 = true });
break;
}
// Dispose the archive
zf?.Dispose();
disposed = true;
// Delete all successful files
foreach (string file in successful)
{
try { File.Delete(file); } catch { }
}
status = "Compression complete!";
return true;
@@ -139,7 +165,8 @@ namespace MPF.Processors
}
finally
{
zf?.Dispose();
if (!disposed)
zf?.Dispose();
}
#endif
}
@@ -203,17 +230,17 @@ namespace MPF.Processors
// Check for the log file
bool logArchiveExists = false;
#if NET452_OR_GREATER || NETCOREAPP
#if NET462_OR_GREATER || NETCOREAPP
ZipArchive? logArchive = null;
#endif
if (File.Exists($"{basePath}_logs.zip"))
{
logArchiveExists = true;
#if NET452_OR_GREATER || NETCOREAPP
#if NET462_OR_GREATER || NETCOREAPP
try
{
// Try to open the archive
logArchive = ZipFile.OpenRead($"{basePath}_logs.zip");
logArchive = ZipArchive.Open($"{basePath}_logs.zip");
}
catch
{
@@ -241,7 +268,7 @@ namespace MPF.Processors
continue;
}
#if NET20 || NET35 || NET40
#if NET20 || NET35 || NET40 || NET452
// Assume the zipfile has the file in it
continue;
#else
@@ -254,7 +281,7 @@ namespace MPF.Processors
#endif
}
#if NET452_OR_GREATER || NETCOREAPP
#if NET462_OR_GREATER || NETCOREAPP
// Close the log archive, if it exists
logArchive?.Dispose();
#endif
@@ -358,29 +385,29 @@ namespace MPF.Processors
}
}
#if NET452_OR_GREATER || NETCOREAPP
#if NET462_OR_GREATER || NETCOREAPP
/// <summary>
/// Try to add a set of files to an existing archive
/// </summary>
/// <param name="archive">Archive to add the file to</param>
/// <param name="files">Full path to a set of existing files</param>
/// <param name="outputDirectory">Directory that the existing files live in</param>
/// <param name="delete">Indicates if the files should be deleted after adding</param>
/// <returns>True if all files were added successfully, false otherwise</returns>
private static bool AddToArchive(ZipArchive archive, List<string> files, string? outputDirectory, bool delete)
/// <returns>List of all files that were successfully added</returns>
private static List<string> AddToArchive(ZipArchive archive, List<string> files, string? outputDirectory)
{
// An empty list means success
if (files.Count == 0)
return true;
return [];
// Loop through and add all files
bool allAdded = true;
List<string> added = [];
foreach (string file in files)
{
allAdded &= AddToArchive(archive, file, outputDirectory, delete);
if (AddToArchive(archive, file, outputDirectory))
added.Add(file);
}
return allAdded;
return added;
}
/// <summary>
@@ -389,9 +416,8 @@ namespace MPF.Processors
/// <param name="archive">Archive to add the file to</param>
/// <param name="file">Full path to an existing file</param>
/// <param name="outputDirectory">Directory that the existing file lives in</param>
/// <param name="delete">Indicates if the file should be deleted after adding</param>
/// <returns>True if the file was added successfully, false otherwise</returns>
private static bool AddToArchive(ZipArchive archive, string file, string? outputDirectory, bool delete)
private static bool AddToArchive(ZipArchive archive, string file, string? outputDirectory)
{
// Check if the file exists
if (!File.Exists(file))
@@ -408,23 +434,13 @@ namespace MPF.Processors
// Create and add the entry
try
{
#if NETFRAMEWORK || NETCOREAPP3_1 || NET5_0
archive.CreateEntryFromFile(file, entryName, CompressionLevel.Optimal);
#else
archive.CreateEntryFromFile(file, entryName, CompressionLevel.SmallestSize);
#endif
archive.AddEntry(entryName, file);
}
catch
{
return false;
}
// Try to delete the file if requested
if (delete)
{
try { File.Delete(file); } catch { }
}
return true;
}
#endif

View File

@@ -82,7 +82,7 @@ namespace MPF.Processors
| OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"bca"),
new($"{outputFilename}.iso", OutputFileFlags.Required),
new([$"{outputFilename}.iso", $"{outputFilename}.part0.iso"], OutputFileFlags.Required),
new($"{outputFilename}-dumpinfo.txt", OutputFileFlags.Required
| OutputFileFlags.Artifact

View File

@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
#if NET452_OR_GREATER || NETCOREAPP
using System.IO.Compression;
#if NET462_OR_GREATER || NETCOREAPP
using SharpCompress.Archives.Zip;
#endif
namespace MPF.Processors
@@ -78,7 +78,7 @@ namespace MPF.Processors
return false;
}
#if NET452_OR_GREATER || NETCOREAPP
#if NET462_OR_GREATER || NETCOREAPP
/// <inheritdoc/>
public override bool Exists(ZipArchive? archive)
{

View File

@@ -1,5 +1,26 @@
namespace MPF.Processors
{
/// <summary>
/// Indicates the type of compression used for logs
/// </summary>
public enum LogCompression
{
/// <summary>
/// PKZIP using DEFLATE level 5
/// </summary>
DeflateDefault,
/// <summary>
/// PKZIP using DEFLATE level 9
/// </summary>
DeflateMaximum,
/// <summary>
/// PKZIP using Zstd level 19
/// </summary>
Zstd19,
}
/// <summary>
/// Enum for SecuROM scheme type
/// </summary>

View File

@@ -12,7 +12,7 @@
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.4.1</VersionPrefix>
<VersionPrefix>3.5.0</VersionPrefix>
<WarningsNotAsErrors>NU5104</WarningsNotAsErrors>
<!-- Package Properties -->
@@ -33,12 +33,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.Hashing" Version="1.5.0" />
<PackageReference Include="SabreTools.IO" Version="1.7.5" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.7.4" />
<PackageReference Include="SabreTools.Serialization" Version="2.0.0" />
<PackageReference Include="System.IO.Compression" Version="4.3.0" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`))" />
<PackageReference Include="System.IO.Compression.ZipFile" Version="4.3.0" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" />
<PackageReference Include="GrindCore.SharpCompress" Version="0.40.4-alpha" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" />
<PackageReference Include="SabreTools.Hashing" Version="[1.5.1]" />
<PackageReference Include="SabreTools.IO" Version="[1.7.6]" />
<PackageReference Include="SabreTools.RedumpLib" Version="[1.7.4]" />
<PackageReference Include="SabreTools.Serialization" Version="[2.0.2]" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.9" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" />
</ItemGroup>

View File

@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
#if NET452_OR_GREATER || NETCOREAPP
using System.IO.Compression;
#if NET462_OR_GREATER || NETCOREAPP
using System.Linq;
using SharpCompress.Archives.Zip;
#endif
namespace MPF.Processors
@@ -218,7 +218,7 @@ namespace MPF.Processors
return false;
}
#if NET452_OR_GREATER || NETCOREAPP
#if NET462_OR_GREATER || NETCOREAPP
/// <summary>
/// Indicates if an output file exists in an archive
/// </summary>
@@ -238,7 +238,7 @@ namespace MPF.Processors
try
{
// Check all entries on filename alone
if (archive.Entries.Any(e => e.Name == filename))
if (archive.Entries.Any(e => e.Key == filename))
return true;
}
catch { }

View File

@@ -1,14 +1,16 @@
using System;
using System.Collections.Generic;
using System.IO;
#if NET452_OR_GREATER || NETCOREAPP
using System.IO.Compression;
#endif
using System.Text;
using System.Text.RegularExpressions;
using SabreTools.Hashing;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
#if NET462_OR_GREATER || NETCOREAPP
using SharpCompress.Archives.Zip;
using SharpCompress.Compressors;
using SharpCompress.Compressors.ZStandard;
#endif
namespace MPF.Processors
{
@@ -97,6 +99,12 @@ namespace MPF.Processors
VolumeLabels = volLabels;
}
// Pre-compress the skeleton and state using Zstandard
if (File.Exists($"{basePath}.skeleton"))
_ = CompressZstandard($"{basePath}.skeleton");
if (File.Exists($"{basePath}.state"))
_ = CompressZstandard($"{basePath}.state");
// Extract info based generically on MediaType
switch (mediaType)
{
@@ -471,10 +479,13 @@ namespace MPF.Processors
"pma"),
new([$"{outputFilename}.scram", $"{outputFilename}.scrap"], OutputFileFlags.Required
| OutputFileFlags.Deleteable),
new($"{outputFilename}.state", OutputFileFlags.Required
| OutputFileFlags.Binary
// TODO: Required if Zstandard version doesn't exist
new($"{outputFilename}.state", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"state"),
new($"{outputFilename}.state.zst", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"state_zst"),
new($"{outputFilename}.subcode", OutputFileFlags.Required
| OutputFileFlags.Binary
| OutputFileFlags.Zippable,
@@ -515,6 +526,9 @@ namespace MPF.Processors
cdrom.Add(new($"{trackName}.skeleton", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
$"skeleton_{trackNumber}"));
cdrom.Add(new($"{trackName}.skeleton.zst", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
$"skeleton_zst_{trackNumber}"));
trackNumber++;
}
}
@@ -526,6 +540,9 @@ namespace MPF.Processors
cdrom.Add(new($"{outputFilename}.skeleton", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"skeleton"));
cdrom.Add(new($"{outputFilename}.skeleton.zst", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"skeleton_zst"));
}
return cdrom;
@@ -570,6 +587,9 @@ namespace MPF.Processors
new($"{outputFilename}.skeleton", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"skeleton"),
new($"{outputFilename}.skeleton.zst", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"skeleton_zst"),
new($"{outputFilename}.ss", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"ss"),
@@ -580,10 +600,13 @@ namespace MPF.Processors
new($"{outputFilename}.ssv2", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"ssv2"),
new($"{outputFilename}.state", OutputFileFlags.Required
| OutputFileFlags.Binary
// TODO: Required if Zstandard version doesn't exist
new($"{outputFilename}.state", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"state"),
new($"{outputFilename}.state.zst", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"state_zst"),
];
case MediaType.HDDVD: // TODO: Confirm that this information outputs
@@ -618,10 +641,16 @@ namespace MPF.Processors
new($"{outputFilename}.skeleton", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"skeleton"),
new($"{outputFilename}.state", OutputFileFlags.Required
| OutputFileFlags.Binary
new($"{outputFilename}.skeleton.zst", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"skeleton_zst"),
// TODO: Required if Zstandard version doesn't exist
new($"{outputFilename}.state", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"state"),
new($"{outputFilename}.state.zst", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"state_zst"),
];
}
@@ -632,6 +661,57 @@ namespace MPF.Processors
#region Private Extra Methods
/// <summary>
/// Attempt to compress a file to Zstandard, removing the original on success
/// </summary>
/// <param name="file">Full path to an existing file</param>
/// <returns>True if the compression was a success, false otherwise</returns>
private static bool CompressZstandard(string file)
{
#if NET20 || NET35 || NET40 || NET452
// Compression is not available for this framework version
return false;
#else
// Ensure the file exists
if (!File.Exists(file))
return false;
try
{
// Prepare the input and output streams
using var ifs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var ofs = File.Open($"{file}.zst", FileMode.Create, FileAccess.Write, FileShare.None);
using var zst = new ZStandardStream(ofs, CompressionMode.Compress, compressionLevel: 19, leaveOpen: true);
// Compress and write in blocks
int read = 0;
do
{
byte[] buffer = new byte[3 * 1024 * 1024];
read = ifs.Read(buffer, 0, buffer.Length);
if (read == 0)
break;
zst.Write(buffer, 0, read);
zst.Flush();
} while (read > 0);
}
catch
{
// Try to delete the incomplete
try { File.Delete($"{file}.zst"); } catch { }
return false;
}
// Try to delete the file
try { File.Delete(file); } catch { }
return true;
#endif
}
/// <summary>
/// Get if the datfile exists in the log
/// </summary>
@@ -649,7 +729,7 @@ namespace MPF.Processors
if (!string.IsNullOrEmpty(outputDirectory))
basePath = Path.Combine(outputDirectory, basePath);
#if NET20 || NET35 || NET40
#if NET20 || NET35 || NET40 || NET452
// Assume the zipfile has the file in it
return File.Exists($"{basePath}_logs.zip");
#else
@@ -660,14 +740,23 @@ namespace MPF.Processors
try
{
// Try to open the archive
using ZipArchive archive = ZipFile.OpenRead($"{basePath}_logs.zip");
using ZipArchive archive = ZipArchive.Open($"{basePath}_logs.zip");
// Get the log entry and check it, if possible
var entry = archive.GetEntry(outputFilename);
if (entry == null)
ZipArchiveEntry? logEntry = null;
foreach (var entry in archive.Entries)
{
if (entry.Key == outputFilename)
{
logEntry = entry;
break;
}
}
if (logEntry == null)
return false;
using var sr = new StreamReader(entry.Open());
using var sr = new StreamReader(logEntry.OpenEntryStream());
return GetDatfile(sr) != null;
}
catch

View File

@@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
#if NET452_OR_GREATER || NETCOREAPP
using System.IO.Compression;
using System.Linq;
#endif
using System.Text.RegularExpressions;
#if NET462_OR_GREATER || NETCOREAPP
using SharpCompress.Archives.Zip;
#endif
namespace MPF.Processors
{
@@ -52,7 +51,7 @@ namespace MPF.Processors
// Ensure the directory exists
if (!Directory.Exists(outputDirectory))
return false;
// Get list of all files in directory
var directoryFiles = Directory.GetFiles(outputDirectory);
foreach (string file in directoryFiles)
@@ -64,7 +63,7 @@ namespace MPF.Processors
return false;
}
#if NET452_OR_GREATER || NETCOREAPP
#if NET462_OR_GREATER || NETCOREAPP
/// <inheritdoc/>
public override bool Exists(ZipArchive? archive)
{
@@ -73,10 +72,12 @@ namespace MPF.Processors
return false;
// Get list of all files in archive
var archiveFiles = archive.Entries.Select(e => e.Name).ToList();
foreach (string file in archiveFiles)
foreach (var entry in archive.Entries)
{
if (Array.Exists(Filenames, pattern => Regex.IsMatch(file, pattern)))
if (entry.Key == null)
continue;
if (Array.Exists(Filenames, pattern => Regex.IsMatch(entry.Key, pattern)))
return true;
}
@@ -92,7 +93,7 @@ namespace MPF.Processors
return [];
List<string> paths = [];
// Get list of all files in directory
var directoryFiles = Directory.GetFiles(outputDirectory);
foreach (string file in directoryFiles)

View File

@@ -4,6 +4,7 @@ using System.Windows.Data;
using MPF.Frontend;
using MPF.Frontend.ComboBoxItems;
using SabreTools.RedumpLib.Data;
using LogCompression = MPF.Processors.LogCompression;
using RedumperDriveType = MPF.ExecutionContexts.Redumper.DriveType;
using RedumperReadMethod = MPF.ExecutionContexts.Redumper.ReadMethod;
using RedumperSectorOrder = MPF.ExecutionContexts.Redumper.SectorOrder;
@@ -18,6 +19,7 @@ namespace MPF.UI
{
DiscCategory discCategory => new Element<DiscCategory>(discCategory),
InternalProgram internalProgram => new Element<InternalProgram>(internalProgram),
LogCompression logCompression => new Element<LogCompression>(logCompression),
MediaType mediaType => new Element<MediaType>(mediaType),
RedumperReadMethod readMethod => new Element<RedumperReadMethod>(readMethod),
RedumperSectorOrder sectorOrder => new Element<RedumperSectorOrder>(sectorOrder),
@@ -40,6 +42,7 @@ namespace MPF.UI
{
Element<DiscCategory> dcElement => dcElement.Value,
Element<InternalProgram> ipElement => ipElement.Value,
Element<LogCompression> lcElement => lcElement.Value,
Element<MediaType> mtElement => mtElement.Value,
Element<RedumperReadMethod> rmElement => rmElement.Value,
Element<RedumperSectorOrder> soElement => soElement.Value,

View File

@@ -18,7 +18,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
<VersionPrefix>3.4.1</VersionPrefix>
<VersionPrefix>3.5.0</VersionPrefix>
<!-- Package Properties -->
<AssemblyName>MPF</AssemblyName>
@@ -70,7 +70,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.RedumpLib" Version="1.7.4" />
<PackageReference Include="SabreTools.RedumpLib" Version="[1.7.4]" />
</ItemGroup>
<ItemGroup>

View File

@@ -169,7 +169,7 @@ namespace MPF.UI.Windows
}
else
{
string? message = result.Message ?? "Please check all files exist and try again!";
string message = result.Message.Length > 0 ? result.Message : "Please check all files exist and try again!";
DisplayUserMessage("Check Failed", message, 1, false);
}
}

View File

@@ -191,7 +191,7 @@
<TabItem Header="Dumping" Style="{DynamicResource CustomTabItemStyle}">
<StackPanel>
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Header="Dumping">
<UniformGrid Columns="2" Rows="6">
<UniformGrid Columns="2" Rows="7">
<CheckBox VerticalAlignment="Center" Content="Show Disc Info"
IsChecked="{Binding Options.PromptForDiscInformation}"
ToolTip="Enable showing the media information output after dumping" Margin="0,4"
@@ -243,6 +243,12 @@
ToolTip="Compress output log files to reduce space" Margin="0,4"
/>
<Label VerticalAlignment="Center" Content="Log Compression:" HorizontalAlignment="Right" />
<ComboBox x:Name="LogCompressionComboBox" Height="22" Width="200" HorizontalAlignment="Left"
ItemsSource="{Binding LogCompressions}" SelectedItem="{Binding Options.LogCompression, Converter={StaticResource ElementConverter}, Mode=TwoWay}"
Style="{DynamicResource CustomComboBoxStyle}" IsEnabled="{Binding Options.CompressLogFiles}"
/>
<CheckBox VerticalAlignment="Center" Content="Delete Unnecessary Files"
IsChecked="{Binding Options.DeleteUnnecessaryFiles}"
ToolTip="Delete unnecesary output files to reduce space" Margin="0,4"