mirror of
https://github.com/SabreTools/MPF.git
synced 2026-07-02 17:24:48 +00:00
Clean up parameters and related
This makes some former virtual methods into properties, cleans up the location of some logic, and makes the BaseParameters class a little neater
This commit is contained in:
@@ -171,7 +171,6 @@ namespace MPF.Check
|
||||
drive = new Drive(null, new DriveInfo(path));
|
||||
|
||||
var env = new DumpEnvironment(options, "", filepath, drive, knownSystem, mediaType, null);
|
||||
env.FixOutputPaths();
|
||||
|
||||
// Finally, attempt to do the output dance
|
||||
var result = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
|
||||
@@ -19,11 +19,35 @@ namespace MPF.Aaru
|
||||
/// </summary>
|
||||
public class Parameters : BaseParameters
|
||||
{
|
||||
#region Generic Dumping Information
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string InputPath => InputValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string OutputPath => OutputValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int? Speed
|
||||
{
|
||||
get { return SpeedValue; }
|
||||
set { SpeedValue = (sbyte?)value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Metadata
|
||||
|
||||
/// <summary>
|
||||
/// Base command to run
|
||||
/// </summary>
|
||||
public Command BaseCommand { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override InternalProgram InternalProgram => InternalProgram.Aaru;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Set of flags to pass to the executable
|
||||
/// </summary>
|
||||
@@ -125,134 +149,235 @@ namespace MPF.Aaru
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Parameters(string parameters)
|
||||
: base(parameters)
|
||||
{
|
||||
this.InternalProgram = InternalProgram.Aaru;
|
||||
}
|
||||
public Parameters(string parameters) : base(parameters) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Parameters(KnownSystem? system, MediaType? type, char driveLetter, string filename, int? driveSpeed, Options options)
|
||||
: base(system, type, driveLetter, filename, driveSpeed, options)
|
||||
{
|
||||
this.InternalProgram = InternalProgram.Aaru;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ResetValues()
|
||||
{
|
||||
BaseCommand = Command.NONE;
|
||||
|
||||
_flags = new Dictionary<Flag, bool?>();
|
||||
|
||||
BlockSizeValue = null;
|
||||
CommentsValue = null;
|
||||
CreatorValue = null;
|
||||
CountValue = null;
|
||||
DriveManufacturerValue = null;
|
||||
DriveModelValue = null;
|
||||
DriveRevisionValue = null;
|
||||
DriveSerialValue = null;
|
||||
EncodingValue = null;
|
||||
FormatConvertValue = null;
|
||||
FormatDumpValue = null;
|
||||
ImgBurnLogValue = null;
|
||||
InputValue = null;
|
||||
Input1Value = null;
|
||||
Input2Value = null;
|
||||
LengthValue = null;
|
||||
MediaBarcodeValue = null;
|
||||
MediaLastSequenceValue = null;
|
||||
MediaManufacturerValue = null;
|
||||
MediaModelValue = null;
|
||||
MediaPartNumberValue = null;
|
||||
MediaSequenceValue = null;
|
||||
MediaSerialValue = null;
|
||||
MediaTitleValue = null;
|
||||
MHDDLogValue = null;
|
||||
NamespaceValue = null;
|
||||
OptionsValue = null;
|
||||
OutputValue = null;
|
||||
OutputPrefixValue = null;
|
||||
RemoteHostValue = null;
|
||||
ResumeFileValue = null;
|
||||
RetryPassesValue = null;
|
||||
SkipValue = null;
|
||||
SpeedValue = null;
|
||||
StartValue = null;
|
||||
SubchannelValue = null;
|
||||
WidthValue = null;
|
||||
XMLSidecarValue = null;
|
||||
}
|
||||
#region BaseParameters Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void SetDefaultParameters(char driveLetter, string filename, int? driveSpeed, Options options)
|
||||
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath)
|
||||
{
|
||||
BaseCommand = Command.MediaDump;
|
||||
|
||||
InputValue = $"\\\\?\\{driveLetter}:";
|
||||
OutputValue = filename;
|
||||
|
||||
if (driveSpeed != null)
|
||||
{
|
||||
this[Flag.Speed] = true;
|
||||
SpeedValue = (sbyte?)driveSpeed;
|
||||
}
|
||||
|
||||
// First check to see if the combination of system and MediaType is valid
|
||||
var validTypes = Validators.GetValidMediaTypes(this.System);
|
||||
if (!validTypes.Contains(this.Type))
|
||||
return;
|
||||
|
||||
// Set retry count
|
||||
if (options.AaruRereadCount > 0)
|
||||
{
|
||||
this[Flag.RetryPasses] = true;
|
||||
RetryPassesValue = (short)options.AaruRereadCount;
|
||||
}
|
||||
|
||||
// Set user-defined options
|
||||
this[Flag.Debug] = options.AaruEnableDebug;
|
||||
this[Flag.Verbose] = options.AaruEnableVerbose;
|
||||
this[Flag.Force] = options.AaruForceDumping;
|
||||
this[Flag.Private] = options.AaruStripPersonalData;
|
||||
|
||||
// TODO: Look at dump-media formats and the like and see what options there are there to fill in defaults
|
||||
// Now sort based on disc type
|
||||
List<string> missingFiles = new List<string>();
|
||||
switch (this.Type)
|
||||
{
|
||||
case MediaType.CDROM:
|
||||
this[Flag.FirstPregap] = true;
|
||||
this[Flag.FixOffset] = true;
|
||||
this[Flag.Subchannel] = true;
|
||||
SubchannelValue = "any";
|
||||
if (!File.Exists($"{basePath}.cicm.xml"))
|
||||
missingFiles.Add($"{basePath}.cicm.xml");
|
||||
if (!File.Exists($"{basePath}.ibg"))
|
||||
missingFiles.Add($"{basePath}.ibg");
|
||||
if (!File.Exists($"{basePath}.log"))
|
||||
missingFiles.Add($"{basePath}.log");
|
||||
if (!File.Exists($"{basePath}.mhddlog.bin"))
|
||||
missingFiles.Add($"{basePath}.mhddlog.bin");
|
||||
if (!File.Exists($"{basePath}.resume.xml"))
|
||||
missingFiles.Add($"{basePath}.resume.xml");
|
||||
if (!File.Exists($"{basePath}.sub.log"))
|
||||
missingFiles.Add($"{basePath}.sub.log");
|
||||
|
||||
break;
|
||||
|
||||
case MediaType.DVD:
|
||||
// Currently no defaults set
|
||||
break;
|
||||
case MediaType.GDROM:
|
||||
// Currently no defaults set
|
||||
break;
|
||||
case MediaType.HDDVD:
|
||||
// Currently no defaults set
|
||||
break;
|
||||
case MediaType.BluRay:
|
||||
// Currently no defaults set
|
||||
if (!File.Exists($"{basePath}.cicm.xml"))
|
||||
missingFiles.Add($"{basePath}.cicm.xml");
|
||||
if (!File.Exists($"{basePath}.ibg"))
|
||||
missingFiles.Add($"{basePath}.ibg");
|
||||
if (!File.Exists($"{basePath}.log"))
|
||||
missingFiles.Add($"{basePath}.log");
|
||||
if (!File.Exists($"{basePath}.mhddlog.bin"))
|
||||
missingFiles.Add($"{basePath}.mhddlog.bin");
|
||||
if (!File.Exists($"{basePath}.resume.xml"))
|
||||
missingFiles.Add($"{basePath}.resume.xml");
|
||||
|
||||
break;
|
||||
|
||||
// Special Formats
|
||||
case MediaType.NintendoGameCubeGameDisc:
|
||||
// Currently no defaults set
|
||||
break;
|
||||
case MediaType.NintendoWiiOpticalDisc:
|
||||
// Currently no defaults set
|
||||
default:
|
||||
return (false, missingFiles); // TODO: Figure out more formats
|
||||
}
|
||||
|
||||
return (!missingFiles.Any(), missingFiles);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, Drive drive)
|
||||
{
|
||||
// TODO: Fill in submission info specifics for Aaru
|
||||
string outputDirectory = Path.GetDirectoryName(basePath);
|
||||
|
||||
// Deserialize the sidecar, if possible
|
||||
var sidecar = GenerateSidecar(basePath + ".cicm.xml");
|
||||
|
||||
// Fill in the hash data
|
||||
info.TracksAndWriteOffsets.ClrMameProData = GenerateDatfile(sidecar, basePath);
|
||||
|
||||
switch (this.Type)
|
||||
{
|
||||
case MediaType.CDROM:
|
||||
// TODO: Can this do GD-ROM?
|
||||
info.Extras.PVD = GeneratePVD(sidecar) ?? "Disc has no PVD";
|
||||
|
||||
long errorCount = -1;
|
||||
if (File.Exists(basePath + ".resume.xml"))
|
||||
errorCount = GetErrorCount(basePath + ".resume.xml");
|
||||
|
||||
info.CommonDiscInfo.ErrorsCount = (errorCount == -1 ? "Error retrieving error count" : errorCount.ToString());
|
||||
|
||||
info.TracksAndWriteOffsets.Cuesheet = GenerateCuesheet(sidecar, basePath) ?? "";
|
||||
|
||||
string cdWriteOffset = GetWriteOffset(sidecar) ?? "";
|
||||
info.CommonDiscInfo.RingWriteOffset = cdWriteOffset;
|
||||
info.TracksAndWriteOffsets.OtherWriteOffsets = cdWriteOffset;
|
||||
break;
|
||||
|
||||
// Non-optical
|
||||
case MediaType.FloppyDisk:
|
||||
// Currently no defaults set
|
||||
case MediaType.DVD:
|
||||
case MediaType.HDDVD:
|
||||
case MediaType.BluRay:
|
||||
// Get the individual hash data, as per internal
|
||||
if (GetISOHashValues(info.TracksAndWriteOffsets.ClrMameProData, out long size, out string crc32, out string md5, out string sha1))
|
||||
{
|
||||
info.SizeAndChecksums.Size = size;
|
||||
info.SizeAndChecksums.CRC32 = crc32;
|
||||
info.SizeAndChecksums.MD5 = md5;
|
||||
info.SizeAndChecksums.SHA1 = sha1;
|
||||
}
|
||||
|
||||
// Deal with the layerbreak
|
||||
string layerbreak = null;
|
||||
if (this.Type == MediaType.DVD)
|
||||
layerbreak = GetLayerbreak(sidecar) ?? "";
|
||||
else if (this.Type == MediaType.BluRay)
|
||||
layerbreak = info.SizeAndChecksums.Size > 25_025_314_816 ? "25025314816" : null;
|
||||
|
||||
// If we have a single-layer disc
|
||||
if (string.IsNullOrWhiteSpace(layerbreak))
|
||||
{
|
||||
info.Extras.PVD = GeneratePVD(sidecar) ?? "Disc has no PVD";
|
||||
}
|
||||
// If we have a dual-layer disc
|
||||
else
|
||||
{
|
||||
info.Extras.PVD = GeneratePVD(sidecar) ?? "Disc has no PVD";
|
||||
info.SizeAndChecksums.Layerbreak = Int64.Parse(layerbreak);
|
||||
}
|
||||
|
||||
// TODO: Investigate XGD disc outputs
|
||||
// TODO: Investigate BD specifics like PIC
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
switch (this.System)
|
||||
{
|
||||
// TODO: Can we get SecuROM data?
|
||||
// TODO: Can we get SS version/ranges?
|
||||
// TODO: Can we get DMI info?
|
||||
// TODO: Can we get Sega Header info?
|
||||
// TODO: Can we get PS1 EDC status?
|
||||
// TODO: Can we get PS1 LibCrypt status?
|
||||
|
||||
case KnownSystem.DVDAudio:
|
||||
case KnownSystem.DVDVideo:
|
||||
info.CopyProtection.Protection = GetDVDProtection(sidecar) ?? "";
|
||||
break;
|
||||
|
||||
case KnownSystem.KonamiPython2:
|
||||
if (GetPlayStationExecutableInfo(drive?.Letter, out string pythonTwoSerial, out RedumpRegion? pythonTwoRegion, out string pythonTwoDate))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"Internal Disc Serial: {pythonTwoSerial}\n";
|
||||
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? pythonTwoRegion;
|
||||
info.CommonDiscInfo.EXEDateBuildDate = pythonTwoDate;
|
||||
}
|
||||
|
||||
info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
|
||||
case KnownSystem.MicrosoftXBOX:
|
||||
if (GetXgdAuxInfo(sidecar, out string dmihash, out string pfihash, out string sshash, out string ss, out string ssver))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"{Template.XBOXDMIHash}: {dmihash ?? ""}\n" +
|
||||
$"{Template.XBOXPFIHash}: {pfihash ?? ""}\n" +
|
||||
$"{Template.XBOXSSHash}: {sshash ?? ""}\n" +
|
||||
$"{Template.XBOXSSVersion}: {ssver ?? ""}\n";
|
||||
info.Extras.SecuritySectorRanges = ss ?? "";
|
||||
}
|
||||
|
||||
if (GetXboxDMIInfo(sidecar, out string serial, out string version, out RedumpRegion? region))
|
||||
{
|
||||
info.CommonDiscInfo.Serial = serial ?? "";
|
||||
info.VersionAndEditions.Version = version ?? "";
|
||||
info.CommonDiscInfo.Region = region;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case KnownSystem.MicrosoftXBOX360:
|
||||
if (GetXgdAuxInfo(sidecar, out string dmi360hash, out string pfi360hash, out string ss360hash, out string ss360, out string ssver360))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"{Template.XBOXDMIHash}: {dmi360hash ?? ""}\n" +
|
||||
$"{Template.XBOXPFIHash}: {pfi360hash ?? ""}\n" +
|
||||
$"{Template.XBOXSSHash}: {ss360hash ?? ""}\n" +
|
||||
$"{Template.XBOXSSVersion}: {ssver360 ?? ""}\n";
|
||||
info.Extras.SecuritySectorRanges = ss360 ?? "";
|
||||
}
|
||||
|
||||
if (GetXbox360DMIInfo(sidecar, out string serial360, out string version360, out RedumpRegion? region360))
|
||||
{
|
||||
info.CommonDiscInfo.Serial = serial360 ?? "";
|
||||
info.VersionAndEditions.Version = version360 ?? "";
|
||||
info.CommonDiscInfo.Region = region360;
|
||||
}
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation:
|
||||
if (GetPlayStationExecutableInfo(drive?.Letter, out string playstationSerial, out RedumpRegion? playstationRegion, out string playstationDate))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"Internal Disc Serial: {playstationSerial}\n";
|
||||
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationRegion;
|
||||
info.CommonDiscInfo.EXEDateBuildDate = playstationDate;
|
||||
}
|
||||
|
||||
info.CopyProtection.AntiModchip = GetPlayStationAntiModchipDetected(drive?.Letter) ? YesNo.Yes : YesNo.No;
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation2:
|
||||
if (GetPlayStationExecutableInfo(drive?.Letter, out string playstationTwoSerial, out RedumpRegion? playstationTwoRegion, out string playstationTwoDate))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"Internal Disc Serial: {playstationTwoSerial}\n";
|
||||
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationTwoRegion;
|
||||
info.CommonDiscInfo.EXEDateBuildDate = playstationTwoDate;
|
||||
}
|
||||
|
||||
info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation4:
|
||||
info.VersionAndEditions.Version = GetPlayStation4Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation5:
|
||||
info.VersionAndEditions.Version = GetPlayStation5Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
}
|
||||
|
||||
// Fill in any artifacts that exist, Base64-encoded
|
||||
if (File.Exists(basePath + ".cicm.xml"))
|
||||
info.Artifacts["cicm"] = GetBase64(GetFullFile(basePath + ".cicm.xml"));
|
||||
if (File.Exists(basePath + ".ibg"))
|
||||
info.Artifacts["ibg"] = Convert.ToBase64String(File.ReadAllBytes(basePath + ".ibg"));
|
||||
if (File.Exists(basePath + ".log"))
|
||||
info.Artifacts["log"] = GetBase64(GetFullFile(basePath + ".log"));
|
||||
if (File.Exists(basePath + ".mhddlog.bin"))
|
||||
info.Artifacts["mhddlog_bin"] = Convert.ToBase64String(File.ReadAllBytes(basePath + ".mhddlog.bin"));
|
||||
if (File.Exists(basePath + ".resume.xml"))
|
||||
info.Artifacts["resume"] = GetBase64(GetFullFile(basePath + ".resume.xml"));
|
||||
if (File.Exists(basePath + ".sub.log"))
|
||||
info.Artifacts["sub_log"] = GetBase64(GetFullFile(basePath + ".sub.log"));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -903,16 +1028,7 @@ namespace MPF.Aaru
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string InputPath() => InputValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string OutputPath() => OutputValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int? GetSpeed() => SpeedValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetSpeed(int? speed) => SpeedValue = (sbyte?)speed;
|
||||
public override string GetDefaultExtension(MediaType? mediaType) => Converters.Extension(mediaType);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsDumpingCommand()
|
||||
@@ -927,6 +1043,123 @@ namespace MPF.Aaru
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ResetValues()
|
||||
{
|
||||
BaseCommand = Command.NONE;
|
||||
|
||||
_flags = new Dictionary<Flag, bool?>();
|
||||
|
||||
BlockSizeValue = null;
|
||||
CommentsValue = null;
|
||||
CreatorValue = null;
|
||||
CountValue = null;
|
||||
DriveManufacturerValue = null;
|
||||
DriveModelValue = null;
|
||||
DriveRevisionValue = null;
|
||||
DriveSerialValue = null;
|
||||
EncodingValue = null;
|
||||
FormatConvertValue = null;
|
||||
FormatDumpValue = null;
|
||||
ImgBurnLogValue = null;
|
||||
InputValue = null;
|
||||
Input1Value = null;
|
||||
Input2Value = null;
|
||||
LengthValue = null;
|
||||
MediaBarcodeValue = null;
|
||||
MediaLastSequenceValue = null;
|
||||
MediaManufacturerValue = null;
|
||||
MediaModelValue = null;
|
||||
MediaPartNumberValue = null;
|
||||
MediaSequenceValue = null;
|
||||
MediaSerialValue = null;
|
||||
MediaTitleValue = null;
|
||||
MHDDLogValue = null;
|
||||
NamespaceValue = null;
|
||||
OptionsValue = null;
|
||||
OutputValue = null;
|
||||
OutputPrefixValue = null;
|
||||
RemoteHostValue = null;
|
||||
ResumeFileValue = null;
|
||||
RetryPassesValue = null;
|
||||
SkipValue = null;
|
||||
SpeedValue = null;
|
||||
StartValue = null;
|
||||
SubchannelValue = null;
|
||||
WidthValue = null;
|
||||
XMLSidecarValue = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void SetDefaultParameters(char driveLetter, string filename, int? driveSpeed, Options options)
|
||||
{
|
||||
BaseCommand = Command.MediaDump;
|
||||
|
||||
InputValue = $"\\\\?\\{driveLetter}:";
|
||||
OutputValue = filename;
|
||||
|
||||
if (driveSpeed != null)
|
||||
{
|
||||
this[Flag.Speed] = true;
|
||||
SpeedValue = (sbyte?)driveSpeed;
|
||||
}
|
||||
|
||||
// First check to see if the combination of system and MediaType is valid
|
||||
var validTypes = Validators.GetValidMediaTypes(this.System);
|
||||
if (!validTypes.Contains(this.Type))
|
||||
return;
|
||||
|
||||
// Set retry count
|
||||
if (options.AaruRereadCount > 0)
|
||||
{
|
||||
this[Flag.RetryPasses] = true;
|
||||
RetryPassesValue = (short)options.AaruRereadCount;
|
||||
}
|
||||
|
||||
// Set user-defined options
|
||||
this[Flag.Debug] = options.AaruEnableDebug;
|
||||
this[Flag.Verbose] = options.AaruEnableVerbose;
|
||||
this[Flag.Force] = options.AaruForceDumping;
|
||||
this[Flag.Private] = options.AaruStripPersonalData;
|
||||
|
||||
// TODO: Look at dump-media formats and the like and see what options there are there to fill in defaults
|
||||
// Now sort based on disc type
|
||||
switch (this.Type)
|
||||
{
|
||||
case MediaType.CDROM:
|
||||
this[Flag.FirstPregap] = true;
|
||||
this[Flag.FixOffset] = true;
|
||||
this[Flag.Subchannel] = true;
|
||||
SubchannelValue = "any";
|
||||
break;
|
||||
case MediaType.DVD:
|
||||
// Currently no defaults set
|
||||
break;
|
||||
case MediaType.GDROM:
|
||||
// Currently no defaults set
|
||||
break;
|
||||
case MediaType.HDDVD:
|
||||
// Currently no defaults set
|
||||
break;
|
||||
case MediaType.BluRay:
|
||||
// Currently no defaults set
|
||||
break;
|
||||
|
||||
// Special Formats
|
||||
case MediaType.NintendoGameCubeGameDisc:
|
||||
// Currently no defaults set
|
||||
break;
|
||||
case MediaType.NintendoWiiOpticalDisc:
|
||||
// Currently no defaults set
|
||||
break;
|
||||
|
||||
// Non-optical
|
||||
case MediaType.FloppyDisk:
|
||||
// Currently no defaults set
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool ValidateAndSetParameters(string parameters)
|
||||
{
|
||||
@@ -1410,226 +1643,9 @@ namespace MPF.Aaru
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath)
|
||||
{
|
||||
List<string> missingFiles = new List<string>();
|
||||
switch (this.Type)
|
||||
{
|
||||
case MediaType.CDROM:
|
||||
if (!File.Exists($"{basePath}.cicm.xml"))
|
||||
missingFiles.Add($"{basePath}.cicm.xml");
|
||||
if (!File.Exists($"{basePath}.ibg"))
|
||||
missingFiles.Add($"{basePath}.ibg");
|
||||
if (!File.Exists($"{basePath}.log"))
|
||||
missingFiles.Add($"{basePath}.log");
|
||||
if (!File.Exists($"{basePath}.mhddlog.bin"))
|
||||
missingFiles.Add($"{basePath}.mhddlog.bin");
|
||||
if (!File.Exists($"{basePath}.resume.xml"))
|
||||
missingFiles.Add($"{basePath}.resume.xml");
|
||||
if (!File.Exists($"{basePath}.sub.log"))
|
||||
missingFiles.Add($"{basePath}.sub.log");
|
||||
#endregion
|
||||
|
||||
break;
|
||||
|
||||
case MediaType.DVD:
|
||||
case MediaType.HDDVD:
|
||||
case MediaType.BluRay:
|
||||
if (!File.Exists($"{basePath}.cicm.xml"))
|
||||
missingFiles.Add($"{basePath}.cicm.xml");
|
||||
if (!File.Exists($"{basePath}.ibg"))
|
||||
missingFiles.Add($"{basePath}.ibg");
|
||||
if (!File.Exists($"{basePath}.log"))
|
||||
missingFiles.Add($"{basePath}.log");
|
||||
if (!File.Exists($"{basePath}.mhddlog.bin"))
|
||||
missingFiles.Add($"{basePath}.mhddlog.bin");
|
||||
if (!File.Exists($"{basePath}.resume.xml"))
|
||||
missingFiles.Add($"{basePath}.resume.xml");
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return (false, missingFiles); // TODO: Figure out more formats
|
||||
}
|
||||
|
||||
return (!missingFiles.Any(), missingFiles);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, Drive drive)
|
||||
{
|
||||
// TODO: Fill in submission info specifics for Aaru
|
||||
string outputDirectory = Path.GetDirectoryName(basePath);
|
||||
|
||||
// Deserialize the sidecar, if possible
|
||||
var sidecar = GenerateSidecar(basePath + ".cicm.xml");
|
||||
|
||||
// Fill in the hash data
|
||||
info.TracksAndWriteOffsets.ClrMameProData = GenerateDatfile(sidecar, basePath);
|
||||
|
||||
switch (this.Type)
|
||||
{
|
||||
case MediaType.CDROM:
|
||||
// TODO: Can this do GD-ROM?
|
||||
info.Extras.PVD = GeneratePVD(sidecar) ?? "Disc has no PVD";
|
||||
|
||||
long errorCount = -1;
|
||||
if (File.Exists(basePath + ".resume.xml"))
|
||||
errorCount = GetErrorCount(basePath + ".resume.xml");
|
||||
|
||||
info.CommonDiscInfo.ErrorsCount = (errorCount == -1 ? "Error retrieving error count" : errorCount.ToString());
|
||||
|
||||
info.TracksAndWriteOffsets.Cuesheet = GenerateCuesheet(sidecar, basePath) ?? "";
|
||||
|
||||
string cdWriteOffset = GetWriteOffset(sidecar) ?? "";
|
||||
info.CommonDiscInfo.RingWriteOffset = cdWriteOffset;
|
||||
info.TracksAndWriteOffsets.OtherWriteOffsets = cdWriteOffset;
|
||||
break;
|
||||
|
||||
case MediaType.DVD:
|
||||
case MediaType.HDDVD:
|
||||
case MediaType.BluRay:
|
||||
// Get the individual hash data, as per internal
|
||||
if (GetISOHashValues(info.TracksAndWriteOffsets.ClrMameProData, out long size, out string crc32, out string md5, out string sha1))
|
||||
{
|
||||
info.SizeAndChecksums.Size = size;
|
||||
info.SizeAndChecksums.CRC32 = crc32;
|
||||
info.SizeAndChecksums.MD5 = md5;
|
||||
info.SizeAndChecksums.SHA1 = sha1;
|
||||
}
|
||||
|
||||
// Deal with the layerbreak
|
||||
string layerbreak = null;
|
||||
if (this.Type == MediaType.DVD)
|
||||
layerbreak = GetLayerbreak(sidecar) ?? "";
|
||||
else if (this.Type == MediaType.BluRay)
|
||||
layerbreak = info.SizeAndChecksums.Size > 25_025_314_816 ? "25025314816" : null;
|
||||
|
||||
// If we have a single-layer disc
|
||||
if (string.IsNullOrWhiteSpace(layerbreak))
|
||||
{
|
||||
info.Extras.PVD = GeneratePVD(sidecar) ?? "Disc has no PVD";
|
||||
}
|
||||
// If we have a dual-layer disc
|
||||
else
|
||||
{
|
||||
info.Extras.PVD = GeneratePVD(sidecar) ?? "Disc has no PVD";
|
||||
info.SizeAndChecksums.Layerbreak = Int64.Parse(layerbreak);
|
||||
}
|
||||
|
||||
// TODO: Investigate XGD disc outputs
|
||||
// TODO: Investigate BD specifics like PIC
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
switch (this.System)
|
||||
{
|
||||
// TODO: Can we get SecuROM data?
|
||||
// TODO: Can we get SS version/ranges?
|
||||
// TODO: Can we get DMI info?
|
||||
// TODO: Can we get Sega Header info?
|
||||
// TODO: Can we get PS1 EDC status?
|
||||
// TODO: Can we get PS1 LibCrypt status?
|
||||
|
||||
case KnownSystem.DVDAudio:
|
||||
case KnownSystem.DVDVideo:
|
||||
info.CopyProtection.Protection = GetDVDProtection(sidecar) ?? "";
|
||||
break;
|
||||
|
||||
case KnownSystem.KonamiPython2:
|
||||
if (GetPlayStationExecutableInfo(drive?.Letter, out string pythonTwoSerial, out RedumpRegion? pythonTwoRegion, out string pythonTwoDate))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"Internal Disc Serial: {pythonTwoSerial}\n";
|
||||
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? pythonTwoRegion;
|
||||
info.CommonDiscInfo.EXEDateBuildDate = pythonTwoDate;
|
||||
}
|
||||
|
||||
info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
|
||||
case KnownSystem.MicrosoftXBOX:
|
||||
if (GetXgdAuxInfo(sidecar, out string dmihash, out string pfihash, out string sshash, out string ss, out string ssver))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"{Template.XBOXDMIHash}: {dmihash ?? ""}\n" +
|
||||
$"{Template.XBOXPFIHash}: {pfihash ?? ""}\n" +
|
||||
$"{Template.XBOXSSHash}: {sshash ?? ""}\n" +
|
||||
$"{Template.XBOXSSVersion}: {ssver ?? ""}\n";
|
||||
info.Extras.SecuritySectorRanges = ss ?? "";
|
||||
}
|
||||
|
||||
if (GetXboxDMIInfo(sidecar, out string serial, out string version, out RedumpRegion? region))
|
||||
{
|
||||
info.CommonDiscInfo.Serial = serial ?? "";
|
||||
info.VersionAndEditions.Version = version ?? "";
|
||||
info.CommonDiscInfo.Region = region;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case KnownSystem.MicrosoftXBOX360:
|
||||
if (GetXgdAuxInfo(sidecar, out string dmi360hash, out string pfi360hash, out string ss360hash, out string ss360, out string ssver360))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"{Template.XBOXDMIHash}: {dmi360hash ?? ""}\n" +
|
||||
$"{Template.XBOXPFIHash}: {pfi360hash ?? ""}\n" +
|
||||
$"{Template.XBOXSSHash}: {ss360hash ?? ""}\n" +
|
||||
$"{Template.XBOXSSVersion}: {ssver360 ?? ""}\n";
|
||||
info.Extras.SecuritySectorRanges = ss360 ?? "";
|
||||
}
|
||||
|
||||
if (GetXbox360DMIInfo(sidecar, out string serial360, out string version360, out RedumpRegion? region360))
|
||||
{
|
||||
info.CommonDiscInfo.Serial = serial360 ?? "";
|
||||
info.VersionAndEditions.Version = version360 ?? "";
|
||||
info.CommonDiscInfo.Region = region360;
|
||||
}
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation:
|
||||
if (GetPlayStationExecutableInfo(drive?.Letter, out string playstationSerial, out RedumpRegion? playstationRegion, out string playstationDate))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"Internal Disc Serial: {playstationSerial}\n";
|
||||
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationRegion;
|
||||
info.CommonDiscInfo.EXEDateBuildDate = playstationDate;
|
||||
}
|
||||
|
||||
info.CopyProtection.AntiModchip = GetPlayStationAntiModchipDetected(drive?.Letter) ? YesNo.Yes : YesNo.No;
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation2:
|
||||
if (GetPlayStationExecutableInfo(drive?.Letter, out string playstationTwoSerial, out RedumpRegion? playstationTwoRegion, out string playstationTwoDate))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"Internal Disc Serial: {playstationTwoSerial}\n";
|
||||
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationTwoRegion;
|
||||
info.CommonDiscInfo.EXEDateBuildDate = playstationTwoDate;
|
||||
}
|
||||
|
||||
info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation4:
|
||||
info.VersionAndEditions.Version = GetPlayStation4Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation5:
|
||||
info.VersionAndEditions.Version = GetPlayStation5Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
}
|
||||
|
||||
// Fill in any artifacts that exist, Base64-encoded
|
||||
if (File.Exists(basePath + ".cicm.xml"))
|
||||
info.Artifacts["cicm"] = GetBase64(GetFullFile(basePath + ".cicm.xml"));
|
||||
if (File.Exists(basePath + ".ibg"))
|
||||
info.Artifacts["ibg"] = Convert.ToBase64String(File.ReadAllBytes(basePath + ".ibg"));
|
||||
if (File.Exists(basePath + ".log"))
|
||||
info.Artifacts["log"] = GetBase64(GetFullFile(basePath + ".log"));
|
||||
if (File.Exists(basePath + ".mhddlog.bin"))
|
||||
info.Artifacts["mhddlog_bin"] = Convert.ToBase64String(File.ReadAllBytes(basePath + ".mhddlog.bin"));
|
||||
if (File.Exists(basePath + ".resume.xml"))
|
||||
info.Artifacts["resume"] = GetBase64(GetFullFile(basePath + ".resume.xml"));
|
||||
if (File.Exists(basePath + ".sub.log"))
|
||||
info.Artifacts["sub_log"] = GetBase64(GetFullFile(basePath + ".sub.log"));
|
||||
}
|
||||
#region Private Extra Methods
|
||||
|
||||
/// <summary>
|
||||
/// Get the list of commands that use a given flag
|
||||
@@ -1907,6 +1923,8 @@ namespace MPF.Aaru
|
||||
return commands;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Process Parameter Helpers
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -12,20 +12,24 @@ namespace MPF.CleanRip
|
||||
/// </summary>
|
||||
public class Parameters : BaseParameters
|
||||
{
|
||||
#region Metadata
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Parameters(string parameters)
|
||||
: base(parameters)
|
||||
{
|
||||
this.InternalProgram = InternalProgram.CleanRip;
|
||||
}
|
||||
public override InternalProgram InternalProgram => InternalProgram.CleanRip;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Parameters(string parameters) : base(parameters) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Parameters(KnownSystem? system, MediaType? type, char driveLetter, string filename, int? driveSpeed, Options options)
|
||||
: base(system, type, driveLetter, filename, driveSpeed, options)
|
||||
{
|
||||
this.InternalProgram = InternalProgram.CleanRip;
|
||||
}
|
||||
|
||||
#region BaseParameters Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath)
|
||||
{
|
||||
@@ -92,6 +96,8 @@ namespace MPF.CleanRip
|
||||
info.Artifacts["dumpinfo"] = GetBase64(GetFullFile(basePath + "-dumpinfo.txt"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Information Extraction Methods
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,11 +13,36 @@ namespace MPF.DD
|
||||
/// </summary>
|
||||
public class Parameters : BaseParameters
|
||||
{
|
||||
#region Generic Dumping Information
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string InputPath => InputFileValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string OutputPath => OutputFileValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <inheritdoc/>
|
||||
public override int? Speed
|
||||
{
|
||||
get { return 1; }
|
||||
set { }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Metadata
|
||||
|
||||
/// <summary>
|
||||
/// Base command to run
|
||||
/// </summary>
|
||||
public Command BaseCommand { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override InternalProgram InternalProgram => InternalProgram.DD;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Set of flags to pass to the executable
|
||||
/// </summary>
|
||||
@@ -58,17 +83,77 @@ namespace MPF.DD
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Parameters(string parameters)
|
||||
: base(parameters)
|
||||
{
|
||||
this.InternalProgram = InternalProgram.DD;
|
||||
}
|
||||
public Parameters(string parameters) : base(parameters) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Parameters(KnownSystem? system, MediaType? type, char driveLetter, string filename, int? driveSpeed, Options options)
|
||||
: base(system, type, driveLetter, filename, driveSpeed, options)
|
||||
{
|
||||
this.InternalProgram = InternalProgram.DD;
|
||||
}
|
||||
|
||||
#region BaseParameters Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath)
|
||||
{
|
||||
// TODO: Figure out what sort of output files are expected... just `.bin`?
|
||||
return (true, new List<string>());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, Drive drive)
|
||||
{
|
||||
// TODO: Fill in submission info specifics for DD
|
||||
string outputDirectory = Path.GetDirectoryName(basePath);
|
||||
|
||||
switch (this.Type)
|
||||
{
|
||||
// Determine type-specific differences
|
||||
}
|
||||
|
||||
switch (this.System)
|
||||
{
|
||||
case KnownSystem.KonamiPython2:
|
||||
if (GetPlayStationExecutableInfo(drive?.Letter, out string pythonTwoSerial, out RedumpRegion? pythonTwoRegion, out string pythonTwoDate))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"Internal Disc Serial: {pythonTwoSerial}\n";
|
||||
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? pythonTwoRegion;
|
||||
info.CommonDiscInfo.EXEDateBuildDate = pythonTwoDate;
|
||||
}
|
||||
|
||||
info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation:
|
||||
if (GetPlayStationExecutableInfo(drive?.Letter, out string playstationSerial, out RedumpRegion? playstationRegion, out string playstationDate))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"Internal Disc Serial: {playstationSerial}\n";
|
||||
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationRegion;
|
||||
info.CommonDiscInfo.EXEDateBuildDate = playstationDate;
|
||||
}
|
||||
|
||||
info.CopyProtection.AntiModchip = GetPlayStationAntiModchipDetected(drive?.Letter) ? YesNo.Yes : YesNo.No;
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation2:
|
||||
if (GetPlayStationExecutableInfo(drive?.Letter, out string playstationTwoSerial, out RedumpRegion? playstationTwoRegion, out string playstationTwoDate))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"Internal Disc Serial: {playstationTwoSerial}\n";
|
||||
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationTwoRegion;
|
||||
info.CommonDiscInfo.EXEDateBuildDate = playstationTwoDate;
|
||||
}
|
||||
|
||||
info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation4:
|
||||
info.VersionAndEditions.Version = GetPlayStation4Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation5:
|
||||
info.VersionAndEditions.Version = GetPlayStation5Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -162,13 +247,7 @@ namespace MPF.DD
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string InputPath() => InputFileValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string OutputPath() => OutputFileValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int? GetSpeed() => 1;
|
||||
public override string GetDefaultExtension(MediaType? mediaType) => Converters.Extension(mediaType);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsDumpingCommand()
|
||||
@@ -326,68 +405,9 @@ namespace MPF.DD
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath)
|
||||
{
|
||||
// TODO: Figure out what sort of output files are expected... just `.bin`?
|
||||
return (true, new List<string>());
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, Drive drive)
|
||||
{
|
||||
// TODO: Fill in submission info specifics for DD
|
||||
string outputDirectory = Path.GetDirectoryName(basePath);
|
||||
|
||||
switch (this.Type)
|
||||
{
|
||||
// Determine type-specific differences
|
||||
}
|
||||
|
||||
switch (this.System)
|
||||
{
|
||||
case KnownSystem.KonamiPython2:
|
||||
if (GetPlayStationExecutableInfo(drive?.Letter, out string pythonTwoSerial, out RedumpRegion? pythonTwoRegion, out string pythonTwoDate))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"Internal Disc Serial: {pythonTwoSerial}\n";
|
||||
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? pythonTwoRegion;
|
||||
info.CommonDiscInfo.EXEDateBuildDate = pythonTwoDate;
|
||||
}
|
||||
|
||||
info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation:
|
||||
if (GetPlayStationExecutableInfo(drive?.Letter, out string playstationSerial, out RedumpRegion? playstationRegion, out string playstationDate))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"Internal Disc Serial: {playstationSerial}\n";
|
||||
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationRegion;
|
||||
info.CommonDiscInfo.EXEDateBuildDate = playstationDate;
|
||||
}
|
||||
|
||||
info.CopyProtection.AntiModchip = GetPlayStationAntiModchipDetected(drive?.Letter) ? YesNo.Yes : YesNo.No;
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation2:
|
||||
if (GetPlayStationExecutableInfo(drive?.Letter, out string playstationTwoSerial, out RedumpRegion? playstationTwoRegion, out string playstationTwoDate))
|
||||
{
|
||||
info.CommonDiscInfo.Comments += $"Internal Disc Serial: {playstationTwoSerial}\n";
|
||||
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationTwoRegion;
|
||||
info.CommonDiscInfo.EXEDateBuildDate = playstationTwoDate;
|
||||
}
|
||||
|
||||
info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation4:
|
||||
info.VersionAndEditions.Version = GetPlayStation4Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
|
||||
case KnownSystem.SonyPlayStation5:
|
||||
info.VersionAndEditions.Version = GetPlayStation5Version(drive?.Letter) ?? "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
#region Private Extra Methods
|
||||
|
||||
/// <summary>
|
||||
/// Get the list of commands that use a given flag
|
||||
@@ -449,6 +469,10 @@ namespace MPF.DD
|
||||
return commands;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Process Parameter Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Process a boolean parameter
|
||||
/// </summary>
|
||||
@@ -587,5 +611,7 @@ namespace MPF.DD
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,43 @@ namespace MPF.Data
|
||||
|
||||
#endregion
|
||||
|
||||
#region Generic Dumping Information
|
||||
|
||||
/// <summary>
|
||||
/// Input path for operations
|
||||
/// </summary>
|
||||
public virtual string InputPath => null;
|
||||
|
||||
/// <summary>
|
||||
/// Output path for operations
|
||||
/// </summary>
|
||||
/// <returns>String representing the path, null on error</returns>
|
||||
public virtual string OutputPath => null;
|
||||
|
||||
/// <summary>
|
||||
/// Get the processing speed from the implementation
|
||||
/// </summary>
|
||||
public virtual int? Speed { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Process to track external program
|
||||
/// </summary>
|
||||
private Process process;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Metadata
|
||||
|
||||
/// <summary>
|
||||
/// Path to the executable
|
||||
/// </summary>
|
||||
public string ExecutablePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Program that this set of parameters represents
|
||||
/// </summary>
|
||||
public virtual InternalProgram InternalProgram { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Currently represented system
|
||||
/// </summary>
|
||||
@@ -47,15 +79,7 @@ namespace MPF.Data
|
||||
/// </summary>
|
||||
public MediaType? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Program that this set of parameters represents
|
||||
/// </summary>
|
||||
public InternalProgram InternalProgram { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Process to track external program
|
||||
/// </summary>
|
||||
private Process process;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Populate a Parameters object from a param string
|
||||
@@ -84,35 +108,39 @@ namespace MPF.Data
|
||||
SetDefaultParameters(driveLetter, filename, driveSpeed, options);
|
||||
}
|
||||
|
||||
#region Abstract Methods
|
||||
|
||||
/// <summary>
|
||||
/// Validate if all required output files exist
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <returns>Tuple of true if all required files exist, false otherwise and a list representing missing files</returns>
|
||||
public abstract (bool, List<string>) CheckAllOutputFilesExist(string basePath);
|
||||
|
||||
/// <summary>
|
||||
/// Generate a SubmissionInfo for the output files
|
||||
/// </summary>
|
||||
/// <param name="submissionInfo">Base submission info to fill in specifics for</param>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <param name="drive">Drive representing the disc to get information from</param>
|
||||
public abstract void GenerateSubmissionInfo(SubmissionInfo submissionInfo, string basePath, Drive drive);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Virtual Methods
|
||||
|
||||
/// <summary>
|
||||
/// Blindly generate a parameter string based on the inputs
|
||||
/// </summary>
|
||||
/// <returns>Correctly formatted parameter string, null on error</returns>
|
||||
/// <returns>Parameter string for invocation, null on error</returns>
|
||||
public virtual string GenerateParameters() => null;
|
||||
|
||||
/// <summary>
|
||||
/// Get the input path from the implementation
|
||||
/// Get the default extension for a given media type
|
||||
/// </summary>
|
||||
/// <returns>String representing the path, null on error</returns>
|
||||
public virtual string InputPath() => null;
|
||||
|
||||
/// <summary>
|
||||
/// Get the output path from the implementation
|
||||
/// </summary>
|
||||
/// <returns>String representing the path, null on error</returns>
|
||||
public virtual string OutputPath() => null;
|
||||
|
||||
/// <summary>
|
||||
/// Get the processing speed from the implementation
|
||||
/// </summary>
|
||||
/// <returns>int? representing the speed, null on error</returns>
|
||||
public virtual int? GetSpeed() => null;
|
||||
|
||||
/// <summary>
|
||||
/// Set the processing speed int the implementation
|
||||
/// </summary>
|
||||
/// <param name="speed">int? representing the speed</param>
|
||||
public virtual void SetSpeed(int? speed) { }
|
||||
/// <param name="mediaType">MediaType value to check</param>
|
||||
/// <returns>String representing the media type, null on error</returns>
|
||||
public virtual string GetDefaultExtension(MediaType? mediaType) => null;
|
||||
|
||||
/// <summary>
|
||||
/// Get the MediaType from the current set of parameters
|
||||
@@ -153,20 +181,9 @@ namespace MPF.Data
|
||||
/// <returns>True if the parameters were set correctly, false otherwise</returns>
|
||||
protected virtual bool ValidateAndSetParameters(string parameters) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Validate if all required output files exist
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <returns>Tuple of true if all required files exist, false otherwise and a list representing missing files</returns>
|
||||
public abstract (bool, List<string>) CheckAllOutputFilesExist(string basePath);
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Generate a SubmissionInfo for the output files
|
||||
/// </summary>
|
||||
/// <param name="submissionInfo">Base submission info to fill in specifics for</param>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <param name="drive">Drive representing the disc to get information from</param>
|
||||
public abstract void GenerateSubmissionInfo(SubmissionInfo submissionInfo, string basePath, Drive drive);
|
||||
#region Execution
|
||||
|
||||
/// <summary>
|
||||
/// Run internal program
|
||||
@@ -209,43 +226,6 @@ namespace MPF.Data
|
||||
process.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run internal program async with an input set of parameters
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns>Standard output from commandline window</returns>
|
||||
public async Task<string> ExecuteInternalProgram(BaseParameters parameters)
|
||||
{
|
||||
Process childProcess;
|
||||
string output = await Task.Run(() =>
|
||||
{
|
||||
childProcess = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo()
|
||||
{
|
||||
FileName = parameters.ExecutablePath,
|
||||
Arguments = parameters.GenerateParameters(),
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
},
|
||||
};
|
||||
childProcess.Start();
|
||||
childProcess.WaitForExit(1000);
|
||||
|
||||
// Just in case, we want to push a button 5 times to clear any errors
|
||||
for (int i = 0; i < 5; i++)
|
||||
childProcess.StandardInput.WriteLine("Y");
|
||||
|
||||
string stdout = childProcess.StandardOutput.ReadToEnd();
|
||||
childProcess.Dispose();
|
||||
return stdout;
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel an in-progress dumping process
|
||||
/// </summary>
|
||||
@@ -262,6 +242,8 @@ namespace MPF.Data
|
||||
{ }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Parameter Parsing
|
||||
|
||||
/// <summary>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,20 +12,24 @@ namespace MPF.UmdImageCreator
|
||||
/// </summary>
|
||||
public class Parameters : BaseParameters
|
||||
{
|
||||
#region Metadata
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Parameters(string parameters)
|
||||
: base(parameters)
|
||||
{
|
||||
this.InternalProgram = InternalProgram.UmdImageCreator;
|
||||
}
|
||||
public override InternalProgram InternalProgram => InternalProgram.UmdImageCreator;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Parameters(string parameters) : base(parameters) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Parameters(KnownSystem? system, MediaType? type, char driveLetter, string filename, int? driveSpeed, Options options)
|
||||
: base(system, type, driveLetter, filename, driveSpeed, options)
|
||||
{
|
||||
this.InternalProgram = InternalProgram.UmdImageCreator;
|
||||
}
|
||||
|
||||
#region BaseParameters Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath)
|
||||
{
|
||||
@@ -93,6 +97,8 @@ namespace MPF.UmdImageCreator
|
||||
info.Artifacts["volDesc"] = GetBase64(GetFullFile(basePath + "_volDesc.txt"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Information Extraction Methods
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -24,12 +24,12 @@ namespace MPF.Utilities
|
||||
/// <summary>
|
||||
/// Base output directory to write files to
|
||||
/// </summary>
|
||||
public string OutputDirectory { get; set; }
|
||||
public string OutputDirectory { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Base output filename for DiscImageCreator
|
||||
/// Base output filename for output
|
||||
/// </summary>
|
||||
public string OutputFilename { get; set; }
|
||||
public string OutputFilename { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -38,27 +38,27 @@ namespace MPF.Utilities
|
||||
/// <summary>
|
||||
/// Drive object representing the current drive
|
||||
/// </summary>
|
||||
public Drive Drive { get; set; }
|
||||
public Drive Drive { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected system
|
||||
/// </summary>
|
||||
public KnownSystem? System { get; set; }
|
||||
public KnownSystem? System { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected media type
|
||||
/// </summary>
|
||||
public MediaType? Type { get; set; }
|
||||
public MediaType? Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Options object representing user-defined options
|
||||
/// </summary>
|
||||
public Options Options { get; set; }
|
||||
public Options Options { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parameters object representing what to send to the internal program
|
||||
/// </summary>
|
||||
public BaseParameters Parameters { get; set; }
|
||||
public BaseParameters Parameters { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -102,17 +102,15 @@ namespace MPF.Utilities
|
||||
this.Options = options;
|
||||
|
||||
// Output paths
|
||||
this.OutputDirectory = outputDirectory;
|
||||
this.OutputFilename = outputFilename;
|
||||
(this.OutputDirectory, this.OutputFilename) = NormalizeOutputPaths(outputDirectory, outputFilename);
|
||||
|
||||
// UI information
|
||||
this.Drive = drive;
|
||||
this.System = system;
|
||||
this.Type = type;
|
||||
|
||||
this.System = system ?? options.DefaultSystem;
|
||||
this.Type = type ?? MediaType.NONE;
|
||||
|
||||
// Dumping program
|
||||
SetParameters(parameters);
|
||||
this.Parameters.System = system;
|
||||
this.Parameters.Type = type;
|
||||
}
|
||||
|
||||
#region Public Functionality
|
||||
@@ -163,6 +161,10 @@ namespace MPF.Utilities
|
||||
this.Parameters.ExecutablePath = Options.DiscImageCreatorPath;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set system and type
|
||||
this.Parameters.System = this.System;
|
||||
this.Parameters.Type = this.Type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -202,64 +204,6 @@ namespace MPF.Utilities
|
||||
await ExecuteInternalProgram(parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fix output paths to strip out any invalid characters
|
||||
/// </summary>
|
||||
public void FixOutputPaths()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Cache if we had a directory separator or not
|
||||
bool endedWithDirectorySeparator = OutputDirectory.EndsWith(Path.DirectorySeparatorChar.ToString())
|
||||
|| OutputDirectory.EndsWith(Path.AltDirectorySeparatorChar.ToString());
|
||||
bool endedWithSpace = OutputDirectory.EndsWith(" ");
|
||||
|
||||
// Combine the path to make things separate easier
|
||||
string combinedPath = Path.Combine(OutputDirectory, OutputFilename);
|
||||
|
||||
// If we have have a blank path, just return
|
||||
if (string.IsNullOrWhiteSpace(combinedPath))
|
||||
return;
|
||||
|
||||
// Now get the normalized paths
|
||||
OutputDirectory = Path.GetDirectoryName(combinedPath);
|
||||
OutputFilename = Path.GetFileName(combinedPath);
|
||||
|
||||
// Take care of extra path characters
|
||||
OutputDirectory = new StringBuilder(OutputDirectory)
|
||||
.Replace(':', '_', 0, OutputDirectory.LastIndexOf(':') == -1 ? 0 : OutputDirectory.LastIndexOf(':')).ToString();
|
||||
|
||||
// Sanitize everything else
|
||||
foreach (char c in Path.GetInvalidPathChars())
|
||||
OutputDirectory = OutputDirectory.Replace(c, '_');
|
||||
foreach (char c in Path.GetInvalidFileNameChars())
|
||||
OutputFilename = OutputFilename.Replace(c, '_');
|
||||
|
||||
// If we had a space at the end before, add it again
|
||||
if (endedWithSpace)
|
||||
OutputDirectory += " ";
|
||||
|
||||
// If we had a directory separator at the end before, add it again
|
||||
if (endedWithDirectorySeparator)
|
||||
OutputDirectory += Path.DirectorySeparatorChar;
|
||||
|
||||
// If we have a root directory, sanitize
|
||||
if (Directory.Exists(OutputDirectory))
|
||||
{
|
||||
var possibleRootDir = new DirectoryInfo(OutputDirectory);
|
||||
if (possibleRootDir.Parent == null)
|
||||
{
|
||||
OutputDirectory = OutputDirectory.Replace($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}", $"{Path.DirectorySeparatorChar}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that all required output files have been created
|
||||
/// </summary>
|
||||
@@ -277,30 +221,6 @@ namespace MPF.Utilities
|
||||
return Parameters.CheckAllOutputFilesExist(basePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the extension for a given media type and internal program
|
||||
/// </summary>
|
||||
/// <param name="mediaType"></param>
|
||||
/// <returns></returns>
|
||||
public string GetExtension(MediaType? mediaType)
|
||||
{
|
||||
switch (Options.InternalProgram)
|
||||
{
|
||||
case InternalProgram.Aaru:
|
||||
return Aaru.Converters.Extension(mediaType);
|
||||
|
||||
case InternalProgram.DD:
|
||||
return DD.Converters.Extension(mediaType);
|
||||
|
||||
case InternalProgram.DiscImageCreator:
|
||||
return DiscImageCreator.Converters.Extension(mediaType);
|
||||
|
||||
// This should never happen, but it needs a fallback
|
||||
default:
|
||||
return DiscImageCreator.Converters.Extension(mediaType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the full parameter string for either DiscImageCreator or Aaru
|
||||
/// </summary>
|
||||
@@ -344,6 +264,62 @@ namespace MPF.Utilities
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalize a split set of paths
|
||||
/// </summary>
|
||||
/// <param name="directory">Directory name to normalize</param>
|
||||
/// <param name="filename">Filename to normalize</param>
|
||||
public static (string, string) NormalizeOutputPaths(string directory, string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Cache if we had a directory separator or not
|
||||
bool endedWithDirectorySeparator = directory.EndsWith(Path.DirectorySeparatorChar.ToString())
|
||||
|| directory.EndsWith(Path.AltDirectorySeparatorChar.ToString());
|
||||
bool endedWithSpace = directory.EndsWith(" ");
|
||||
|
||||
// Combine the path to make things separate easier
|
||||
string combinedPath = Path.Combine(directory, filename);
|
||||
|
||||
// If we have have a blank path, just return
|
||||
if (string.IsNullOrWhiteSpace(combinedPath))
|
||||
return (directory, filename);
|
||||
|
||||
// Now get the normalized paths
|
||||
directory = Path.GetDirectoryName(combinedPath);
|
||||
filename = Path.GetFileName(combinedPath);
|
||||
|
||||
// Take care of extra path characters
|
||||
directory = new StringBuilder(directory)
|
||||
.Replace(':', '_', 0, directory.LastIndexOf(':') == -1 ? 0 : directory.LastIndexOf(':')).ToString();
|
||||
|
||||
// Sanitize everything else
|
||||
foreach (char c in Path.GetInvalidPathChars())
|
||||
directory = directory.Replace(c, '_');
|
||||
foreach (char c in Path.GetInvalidFileNameChars())
|
||||
filename = filename.Replace(c, '_');
|
||||
|
||||
// If we had a space at the end before, add it again
|
||||
if (endedWithSpace)
|
||||
directory += " ";
|
||||
|
||||
// If we had a directory separator at the end before, add it again
|
||||
if (endedWithDirectorySeparator)
|
||||
directory += Path.DirectorySeparatorChar;
|
||||
|
||||
// If we have a root directory, sanitize
|
||||
if (Directory.Exists(directory))
|
||||
{
|
||||
var possibleRootDir = new DirectoryInfo(directory);
|
||||
if (possibleRootDir.Parent == null)
|
||||
directory = directory.Replace($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}", $"{Path.DirectorySeparatorChar}");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return (directory, filename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the current drive using DiscImageCreator
|
||||
/// </summary>
|
||||
@@ -1266,13 +1242,21 @@ namespace MPF.Utilities
|
||||
return Result.Failure("Error! Current configuration is not supported!");
|
||||
|
||||
// Fix the output paths, just in case
|
||||
FixOutputPaths();
|
||||
(OutputDirectory, OutputFilename) = NormalizeOutputPaths(OutputDirectory, OutputFilename);
|
||||
|
||||
// Validate that the output path isn't on the dumping drive
|
||||
string fullOutputPath = Path.GetFullPath(Path.Combine(OutputDirectory, OutputFilename));
|
||||
if (fullOutputPath[0] == Drive.Letter)
|
||||
return Result.Failure($"Error! Cannot output to same drive that is being dumped!");
|
||||
|
||||
// Validate that the required program exists
|
||||
if (!File.Exists(Parameters.ExecutablePath))
|
||||
return Result.Failure($"Error! {Parameters.ExecutablePath} does not exist!");
|
||||
|
||||
// TODO: Ensure output path not the same as input drive OR executable location
|
||||
// Validate that the dumping drive doesn't contain the executable
|
||||
string fullExecutablePath = Path.GetFullPath(Parameters.ExecutablePath);
|
||||
if (fullExecutablePath[0] == Drive.Letter)
|
||||
return Result.Failure("$Error! Cannot dump same drive that executable resides on!");
|
||||
|
||||
// Validate that the current configuration is supported
|
||||
return Validators.GetSupportStatus(System, Type);
|
||||
|
||||
@@ -38,12 +38,9 @@ namespace MPF.Test
|
||||
[InlineData("superhero", "blah&foo.bin", "superhero", "blah&foo.bin")]
|
||||
public void FixOutputPathsTest(string outputDirectory, string outputFilename, string expectedOutputDirectory, string expectedOutputFilename)
|
||||
{
|
||||
var options = new Options() { InternalProgram = InternalProgram.DiscImageCreator };
|
||||
var env = new DumpEnvironment(options, outputDirectory, outputFilename, null, KnownSystem.IBMPCCompatible, MediaType.CDROM, string.Empty);
|
||||
|
||||
env.FixOutputPaths();
|
||||
Assert.Equal(expectedOutputDirectory, env.OutputDirectory);
|
||||
Assert.Equal(expectedOutputFilename, env.OutputFilename);
|
||||
(string actualOutputDirectory, string actualOutputFilename) = DumpEnvironment.NormalizeOutputPaths(outputDirectory, outputFilename);
|
||||
Assert.Equal(expectedOutputDirectory, actualOutputDirectory);
|
||||
Assert.Equal(expectedOutputFilename, actualOutputFilename);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -389,12 +389,6 @@ namespace MPF.Windows
|
||||
// Get the current environment information
|
||||
Env = DetermineEnvironment();
|
||||
|
||||
// Take care of null cases
|
||||
if (Env.System == null)
|
||||
Env.System = UIOptions.Options.DefaultSystem;
|
||||
if (Env.Type == null)
|
||||
Env.Type = MediaType.NONE;
|
||||
|
||||
// Get the status to write out
|
||||
Result result = Validators.GetSupportStatus(Env.System, Env.Type);
|
||||
StatusLabel.Content = result.Message;
|
||||
@@ -431,7 +425,7 @@ namespace MPF.Windows
|
||||
OutputDirectoryTextBox.Text = Path.Combine(UIOptions.Options.DefaultOutputPath, drive?.VolumeLabel ?? string.Empty);
|
||||
|
||||
// Get the extension for the file for the next two statements
|
||||
string extension = Env.GetExtension(mediaType);
|
||||
string extension = Env.Parameters?.GetDefaultExtension(mediaType);
|
||||
|
||||
// Set the output filename, if we changed drives or it's not already
|
||||
if (driveChanged || string.IsNullOrEmpty(OutputFilenameTextBox.Text))
|
||||
@@ -451,19 +445,20 @@ namespace MPF.Windows
|
||||
if (Env.Parameters == null)
|
||||
return;
|
||||
|
||||
int driveIndex = Drives.Select(d => d.Letter).ToList().IndexOf(Env.Parameters.InputPath()[0]);
|
||||
int driveIndex = Drives.Select(d => d.Letter).ToList().IndexOf(Env.Parameters.InputPath[0]);
|
||||
if (driveIndex > -1)
|
||||
DriveLetterComboBox.SelectedIndex = driveIndex;
|
||||
|
||||
int driveSpeed = Env.Parameters.GetSpeed() ?? -1;
|
||||
int driveSpeed = Env.Parameters.Speed ?? -1;
|
||||
if (driveSpeed > 0)
|
||||
DriveSpeedComboBox.SelectedValue = driveSpeed;
|
||||
else
|
||||
Env.Parameters.SetSpeed((int?)DriveSpeedComboBox.SelectedValue);
|
||||
Env.Parameters.Speed = DriveSpeedComboBox.SelectedValue as int?;
|
||||
|
||||
string trimmedPath = Env.Parameters.OutputPath()?.Trim('"') ?? string.Empty;
|
||||
string trimmedPath = Env.Parameters.OutputPath?.Trim('"') ?? string.Empty;
|
||||
string outputDirectory = Path.GetDirectoryName(trimmedPath);
|
||||
string outputFilename = Path.GetFileName(trimmedPath);
|
||||
(outputDirectory, outputFilename) = DumpEnvironment.NormalizeOutputPaths(outputDirectory, outputFilename);
|
||||
if (!string.IsNullOrWhiteSpace(outputDirectory))
|
||||
OutputDirectoryTextBox.Text = outputDirectory;
|
||||
else
|
||||
@@ -592,9 +587,6 @@ namespace MPF.Windows
|
||||
// If "No", then we continue with the current known environment
|
||||
}
|
||||
|
||||
// Fix the output paths
|
||||
Env.FixOutputPaths();
|
||||
|
||||
try
|
||||
{
|
||||
// Validate that the user explicitly wants an inactive drive to be considered for dumping
|
||||
|
||||
Reference in New Issue
Block a user