Files
MPF/MPF.Modules/Redumper/Parameters.cs
2023-03-10 16:13:19 -05:00

1205 lines
43 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using MPF.Core.Converters;
using MPF.Core.Data;
using RedumpLib.Data;
namespace MPF.Modules.Redumper
{
/// <summary>
/// Represents a generic set of Redumper parameters
/// </summary>
/// <remarks>
/// TODO: Redumper natively supports multiple, chained commands but MPF
/// doesn't have an easy way of dealing with that right now. Maybe have
/// BaseCommand be a space-deliminated list of the commands?
/// TODO: With chained commands, how do we tell what parameters are
/// actually supported? Do we go one by one and if it doesn't match
/// any, then it's incorrect?
/// </remarks>
public class Parameters : BaseParameters
{
#region Generic Dumping Information
/// <inheritdoc/>
public override string InputPath => DriveValue;
/// <inheritdoc/>
public override string OutputPath => Path.Combine(ImagePathValue?.Trim('"') ?? string.Empty, ImageNameValue?.Trim('"') ?? string.Empty);
/// <inheritdoc/>
public override int? Speed => SpeedValue;
#endregion
#region Metadata
/// <inheritdoc/>
public override InternalProgram InternalProgram => InternalProgram.Redumper;
#endregion
#region Flag Values
/// <summary>
/// List of all modes being run
/// </summary>
public List<string> ModeValues { get; set; }
#region General
/// <summary>
/// Drive to use, first available drive with disc, if not provided
/// </summary>
public string DriveValue { get; set; }
/// <summary>
/// Drive read speed, optimal drive speed will be used if not provided
/// </summary>
public int? SpeedValue { get; set; }
/// <summary>
/// Number of sector retries in case of SCSI/C2 error (default: 0)
/// </summary>
public int? RetriesValue { get; set; }
/// <summary>
/// Dump files base directory
/// </summary>
public string ImagePathValue { get; set; }
/// <summary>
/// Dump files prefix, autogenerated in dump mode, if not provided
/// </summary>
public string ImageNameValue { get; set; }
#endregion
#region Drive Configuration
/// <summary>
/// Override drive type, possible values: GENERIC, PLEXTOR, LG_ASUS
/// </summary>
public string DriveTypeValue { get; set; }
/// <summary>
/// Override drive read offset
/// </summary>
public int? DriveReadOffsetValue { get; set; }
/// <summary>
/// Override drive C2 shift
/// </summary>
public int? DriveC2ShiftValue { get; set; }
/// <summary>
/// Override drive pre-gap start LBA
/// </summary>
public int? DrivePregapStartValue { get; set; }
/// <summary>
/// Override drive read method, possible values: BE, D8, BE_CDDA
/// </summary>
public string DriveReadMethodValue { get; set; }
/// <summary>
/// Override drive sector order, possible values: DATA_C2_SUB, DATA_SUB_C2
/// </summary>
public string DriveSectorOrderValue { get; set; }
#endregion
#region Offset
/// <summary>
/// Override offset autodetection and use supplied value
/// </summary>
public int? ForceOffsetValue { get; set; }
/// <summary>
/// Maximum absolute sample value to treat it as silence (default: 32)
/// </summary>
public int? AudioSilenceThresholdValue { get; set; }
#endregion
#region Split
/// <summary>
/// Fill byte value for skipped sectors (default: 0x55)
/// </summary>
public byte? SkipFillValue { get; set; }
#endregion
#region Miscellaneous
/// <summary>
/// LBA to start dumping from
/// </summary>
public int? LBAStartValue { get; set; }
/// <summary>
/// LBA to stop dumping at (everything before the value), useful for discs with fake TOC
/// </summary>
public int? LBAEndValue { get; set; }
/// <summary>
/// LBA ranges of sectors to skip
/// </summary>
public string SkipValue { get; set; }
#endregion
#endregion
/// <inheritdoc/>
public Parameters(string parameters) : base(parameters) { }
/// <inheritdoc/>
public Parameters(RedumpSystem? system, MediaType? type, char driveLetter, string filename, int? driveSpeed, Options options)
: base(system, type, driveLetter, filename, driveSpeed, options)
{
}
#region BaseParameters Implementations
/// <inheritdoc/>
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
{
List<string> missingFiles = new List<string>();
switch (this.Type)
{
case MediaType.CDROM:
if (!File.Exists($"{basePath}.cue"))
missingFiles.Add($"{basePath}.cue");
if (!File.Exists($"{basePath}.scram") && !File.Exists($"{basePath}.scrap"))
missingFiles.Add($"{basePath}.scram");
if (!File.Exists($"{basePath}_logs.zip") || !preCheck)
{
if (!File.Exists($"{basePath}.fulltoc"))
missingFiles.Add($"{basePath}.fulltoc");
if (!File.Exists($"{basePath}.log"))
missingFiles.Add($"{basePath}.log");
if (!File.Exists($"{basePath}.state"))
missingFiles.Add($"{basePath}.state");
if (!File.Exists($"{basePath}.subcode"))
missingFiles.Add($"{basePath}.subcode");
if (!File.Exists($"{basePath}.toc"))
missingFiles.Add($"{basePath}.toc");
}
// Removed or inconsistent files
if (false)
{
// Depends on the disc
if (!File.Exists($"{basePath}.cdtext"))
missingFiles.Add($"{basePath}.cdtext");
}
break;
}
return (true, new List<string>());
}
/// <inheritdoc/>
public override void GenerateSubmissionInfo(SubmissionInfo info, Options options, string basePath, Drive drive, bool includeArtifacts)
{
// Get the dumping program and version
info.DumpingInfo.DumpingProgram = $"{EnumConverter.LongName(this.InternalProgram)} {GetVersion($"{basePath}.log") ?? "Unknown Version"}";
switch (this.Type)
{
case MediaType.CDROM:
info.Extras.PVD = GetPVD($"{basePath}.log") ?? "Disc has no PVD"; ;
info.TracksAndWriteOffsets.ClrMameProData = GetDatfile($"{basePath}.log");
info.TracksAndWriteOffsets.Cuesheet = GetFullFile($"{basePath}.cue") ?? "";
string cdWriteOffset = GetWriteOffset($"{basePath}.log") ?? "";
info.CommonDiscInfo.RingWriteOffset = cdWriteOffset;
info.TracksAndWriteOffsets.OtherWriteOffsets = cdWriteOffset;
long errorCount = GetErrorCount($"{basePath}.log");
info.CommonDiscInfo.ErrorsCount = (errorCount == -1 ? "Error retrieving error count" : errorCount.ToString());
string universalHash = GetUniversalHash($"{basePath}.log") ?? "";
info.CommonDiscInfo.CommentsSpecialFields[SiteCode.UniversalHash] = universalHash;
string ringNonZeroDataStart = GetRingNonZeroDataStart($"{basePath}.log") ?? "";
info.CommonDiscInfo.CommentsSpecialFields[SiteCode.RingNonZeroDataStart] = ringNonZeroDataStart;
break;
}
switch (this.System)
{
case RedumpSystem.KonamiPython2:
if (GetPlayStationExecutableInfo(drive?.Letter, out string pythonTwoSerial, out Region? pythonTwoRegion, out string pythonTwoDate))
{
// Ensure internal serial is pulled from local data
info.CommonDiscInfo.CommentsSpecialFields[SiteCode.InternalSerialName] = pythonTwoSerial ?? string.Empty;
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? pythonTwoRegion;
info.CommonDiscInfo.EXEDateBuildDate = pythonTwoDate;
}
info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? "";
break;
case RedumpSystem.SonyPlayStation:
if (GetPlayStationExecutableInfo(drive?.Letter, out string playstationSerial, out Region? playstationRegion, out string playstationDate))
{
// Ensure internal serial is pulled from local data
info.CommonDiscInfo.CommentsSpecialFields[SiteCode.InternalSerialName] = playstationSerial ?? string.Empty;
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationRegion;
info.CommonDiscInfo.EXEDateBuildDate = playstationDate;
}
break;
case RedumpSystem.SonyPlayStation2:
if (GetPlayStationExecutableInfo(drive?.Letter, out string playstationTwoSerial, out Region? playstationTwoRegion, out string playstationTwoDate))
{
// Ensure internal serial is pulled from local data
info.CommonDiscInfo.CommentsSpecialFields[SiteCode.InternalSerialName] = playstationTwoSerial ?? string.Empty;
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationTwoRegion;
info.CommonDiscInfo.EXEDateBuildDate = playstationTwoDate;
}
info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? "";
break;
case RedumpSystem.SonyPlayStation3:
info.VersionAndEditions.Version = GetPlayStation3Version(drive?.Letter) ?? "";
info.CommonDiscInfo.CommentsSpecialFields[SiteCode.InternalSerialName] = GetPlayStation3Serial(drive?.Letter) ?? "";
break;
case RedumpSystem.SonyPlayStation4:
info.VersionAndEditions.Version = GetPlayStation4Version(drive?.Letter) ?? "";
info.CommonDiscInfo.CommentsSpecialFields[SiteCode.InternalSerialName] = GetPlayStation4Serial(drive?.Letter) ?? "";
break;
case RedumpSystem.SonyPlayStation5:
info.VersionAndEditions.Version = GetPlayStation5Version(drive?.Letter) ?? "";
info.CommonDiscInfo.CommentsSpecialFields[SiteCode.InternalSerialName] = GetPlayStation5Serial(drive?.Letter) ?? "";
break;
}
}
/// <inheritdoc/>
/// <remarks>
/// Redumper is unique in that the base command can be multiple
/// modes all listed together. It is also unique in that "all
/// flags are supported for everything" and it filters out internally
/// </remarks>
public override string GenerateParameters()
{
List<string> parameters = new List<string>();
if (ModeValues == null)
ModeValues = new List<string> { CommandStrings.NONE };
// Modes
parameters.AddRange(ModeValues);
#region General
// Help
if (this[FlagStrings.HelpLong] == true)
parameters.Add(FlagStrings.HelpLong);
// Verbose
if (this[FlagStrings.Verbose] == true)
parameters.Add(FlagStrings.Verbose);
// Drive
if (this[FlagStrings.Drive] == true)
{
if (DriveValue != null)
parameters.Add($"{FlagStrings.Drive}={DriveValue}");
}
// Speed
if (this[FlagStrings.Speed] == true)
{
if (SpeedValue != null)
parameters.Add($"{FlagStrings.Speed}={SpeedValue}");
}
// Retries
if (this[FlagStrings.Retries] == true)
{
if (RetriesValue != null)
parameters.Add($"{FlagStrings.Retries}={RetriesValue}");
}
// Image Path
if (this[FlagStrings.ImagePath] == true)
{
if (ImagePathValue != null)
parameters.Add($"{FlagStrings.ImagePath}={ImagePathValue}");
}
// Image Name
if (this[FlagStrings.ImageName] == true)
{
if (ImageNameValue != null)
parameters.Add($"{FlagStrings.ImageName}={ImageNameValue}");
}
// Overwrite
if (this[FlagStrings.Overwrite] == true)
parameters.Add(FlagStrings.Overwrite);
#endregion
#region Drive Configuration
// Drive Type
if (this[FlagStrings.DriveType] == true)
{
if (DriveTypeValue != null)
parameters.Add($"{FlagStrings.DriveType}={DriveTypeValue}");
}
// Drive Read Offset
if (this[FlagStrings.DriveReadOffset] == true)
{
if (DriveReadOffsetValue != null)
parameters.Add($"{FlagStrings.DriveReadOffset}={DriveReadOffsetValue}");
}
// Drive C2 Shift
if (this[FlagStrings.DriveC2Shift] == true)
{
if (DriveC2ShiftValue != null)
parameters.Add($"{FlagStrings.DriveC2Shift}={DriveC2ShiftValue}");
}
// Drive Pregap Start
if (this[FlagStrings.DrivePregapStart] == true)
{
if (DrivePregapStartValue != null)
parameters.Add($"{FlagStrings.DrivePregapStart}={DrivePregapStartValue}");
}
// Drive Read Method
if (this[FlagStrings.DriveReadMethod] == true)
{
if (DriveReadMethodValue != null)
parameters.Add($"{FlagStrings.DriveReadMethod}={DriveReadMethodValue}");
}
// Drive Sector Order
if (this[FlagStrings.DriveSectorOrder] == true)
{
if (DriveSectorOrderValue != null)
parameters.Add($"{FlagStrings.DriveSectorOrder}={DriveSectorOrderValue}");
}
#endregion
#region Drive Specific
// Plextor Skip Leadin
if (this[FlagStrings.PlextorSkipLeadin] == true)
parameters.Add(FlagStrings.PlextorSkipLeadin);
// Asus Skip Leadout
if (this[FlagStrings.AsusSkipLeadout] == true)
parameters.Add(FlagStrings.AsusSkipLeadout);
#endregion
#region Offset
// Force Offset
if (this[FlagStrings.ForceOffset] == true)
{
if (ForceOffsetValue != null)
parameters.Add($"{FlagStrings.ForceOffset}={ForceOffsetValue}");
}
// Audio Silence Threshold
if (this[FlagStrings.AudioSilenceThreshold] == true)
{
if (AudioSilenceThresholdValue != null)
parameters.Add($"{FlagStrings.AudioSilenceThreshold}={AudioSilenceThresholdValue}");
}
// Correct Offset Shift
if (this[FlagStrings.CorrectOffsetShift] == true)
parameters.Add(FlagStrings.CorrectOffsetShift);
#endregion
#region Split
// Force Split
if (this[FlagStrings.ForceSplit] == true)
parameters.Add(FlagStrings.ForceSplit);
// Leave Unchanged
if (this[FlagStrings.LeaveUnchanged] == true)
parameters.Add(FlagStrings.LeaveUnchanged);
// Force QTOC
if (this[FlagStrings.ForceQTOC] == true)
parameters.Add(FlagStrings.ForceQTOC);
// Skip Fill
if (this[FlagStrings.SkipFill] == true)
{
if (SkipFillValue != null)
parameters.Add($"{FlagStrings.SkipFill}={SkipFillValue:x}");
}
// ISO9660 Trim
if (this[FlagStrings.ISO9660Trim] == true)
parameters.Add(FlagStrings.ISO9660Trim);
// CD-i Ready Normalize
if (this[FlagStrings.CDiReadyNormalize] == true)
parameters.Add(FlagStrings.CDiReadyNormalize);
#endregion
#region Miscellaneous
// LBA Start
if (this[FlagStrings.LBAStart] == true)
{
if (LBAStartValue != null)
parameters.Add($"{FlagStrings.LBAStart}={LBAStartValue}");
}
// LBA End
if (this[FlagStrings.LBAEnd] == true)
{
if (LBAEndValue != null)
parameters.Add($"{FlagStrings.LBAEnd}={LBAEndValue}");
}
// Refine Subchannel
if (this[FlagStrings.RefineSubchannel] == true)
parameters.Add(FlagStrings.RefineSubchannel);
// Skip
if (this[FlagStrings.Skip] == true)
{
if (!string.IsNullOrWhiteSpace(SkipValue))
parameters.Add($"{FlagStrings.Skip}={SkipValue}");
}
#endregion
return string.Join(" ", parameters);
}
/// <inheritdoc/>
/// <remarks>Command support is irrelevant for redumper</remarks>
public override Dictionary<string, List<string>> GetCommandSupport()
{
return new Dictionary<string, List<string>>()
{
[CommandStrings.NONE] = new List<string>
{
// General
FlagStrings.HelpLong,
FlagStrings.HelpShort,
FlagStrings.Verbose,
FlagStrings.Drive,
FlagStrings.Speed,
FlagStrings.Retries,
FlagStrings.ImagePath,
FlagStrings.ImageName,
FlagStrings.Overwrite,
// Drive Configuration
FlagStrings.DriveType,
FlagStrings.DriveReadOffset,
FlagStrings.DriveC2Shift,
FlagStrings.DrivePregapStart,
FlagStrings.DriveReadMethod,
FlagStrings.DriveSectorOrder,
// Drive Specific
FlagStrings.PlextorSkipLeadin,
FlagStrings.AsusSkipLeadout,
// Offset
FlagStrings.ForceOffset,
FlagStrings.AudioSilenceThreshold,
FlagStrings.CorrectOffsetShift,
// Split
FlagStrings.ForceSplit,
FlagStrings.LeaveUnchanged,
FlagStrings.ForceQTOC,
FlagStrings.SkipFill,
FlagStrings.ISO9660Trim,
FlagStrings.CDiReadyNormalize,
// Miscellaneous
FlagStrings.LBAStart,
FlagStrings.LBAEnd,
FlagStrings.RefineSubchannel,
FlagStrings.Skip,
},
};
}
/// <inheritdoc/>
public override string GetDefaultExtension(MediaType? mediaType) => Converters.Extension(mediaType);
/// <inheritdoc/>
public override List<string> GetLogFilePaths(string basePath)
{
List<string> logFiles = new List<string>();
switch (this.Type)
{
case MediaType.CDROM:
if (File.Exists($"{basePath}.cdtext"))
logFiles.Add($"{basePath}.cdtext");
if (File.Exists($"{basePath}.fulltoc"))
logFiles.Add($"{basePath}.fulltoc");
if (File.Exists($"{basePath}.log"))
logFiles.Add($"{basePath}.log");
// if (File.Exists($"{basePath}.scram"))
// logFiles.Add($"{basePath}.scram");
// if (File.Exists($"{basePath}.scrap"))
// logFiles.Add($"{basePath}.scrap");
if (File.Exists($"{basePath}.state"))
logFiles.Add($"{basePath}.state");
if (File.Exists($"{basePath}.subcode"))
logFiles.Add($"{basePath}.subcode");
if (File.Exists($"{basePath}.toc"))
logFiles.Add($"{basePath}.toc");
break;
}
return logFiles;
}
/// <inheritdoc/>
public override bool IsDumpingCommand()
{
return this.BaseCommand == CommandStrings.NONE
|| this.BaseCommand.Contains(CommandStrings.CD)
|| this.BaseCommand.Contains(CommandStrings.Dump);
}
/// <inheritdoc/>
protected override void ResetValues()
{
BaseCommand = CommandStrings.NONE;
flags = new Dictionary<string, bool?>();
// General
DriveValue = null;
SpeedValue = null;
RetriesValue = null;
ImagePathValue = null;
ImageNameValue = null;
// Drive Configuration
DriveTypeValue = null;
DriveReadOffsetValue = null;
DriveC2ShiftValue = null;
DrivePregapStartValue = null;
DriveReadMethodValue = null;
DriveSectorOrderValue = null;
// Offset
ForceOffsetValue = null;
AudioSilenceThresholdValue = null;
// Split
SkipFillValue = null;
// Miscellaneous
LBAStartValue = null;
LBAEndValue = null;
SkipValue = null;
}
/// <inheritdoc/>
protected override void SetDefaultParameters(char driveLetter, string filename, int? driveSpeed, Options options)
{
// If we don't have a CD, we can't dump using redumper
if (this.Type != MediaType.CDROM)
return;
BaseCommand = CommandStrings.NONE;
ModeValues = new List<string> { CommandStrings.CD };
DriveValue = driveLetter.ToString();
SpeedValue = driveSpeed;
// Set the output paths
if (!string.IsNullOrWhiteSpace(filename))
{
string imagePath = Path.GetDirectoryName(filename);
if (!string.IsNullOrWhiteSpace(imagePath))
{
this[FlagStrings.ImagePath] = true;
ImagePathValue = $"\"{imagePath}\"";
}
string imageName = Path.GetFileNameWithoutExtension(filename);
if (!string.IsNullOrWhiteSpace(imageName))
{
this[FlagStrings.ImageName] = true;
ImageNameValue = $"\"{imageName}\"";
}
}
this[FlagStrings.Retries] = true;
RetriesValue = options.RedumperRereadCount;
}
/// <inheritdoc/>
protected override bool ValidateAndSetParameters(string parameters)
{
BaseCommand = CommandStrings.NONE;
// The string has to be valid by itself first
if (string.IsNullOrWhiteSpace(parameters))
return false;
// Now split the string into parts for easier validation
// https://stackoverflow.com/questions/14655023/split-a-string-that-has-white-spaces-unless-they-are-enclosed-within-quotes
parameters = parameters.Trim();
List<string> parts = Regex.Matches(parameters, @"[\""].+?[\""]|[^ ]+")
.Cast<Match>()
.Select(m => m.Value)
.ToList();
// Setup the modes
ModeValues = new List<string>();
// All modes should be cached separately
int index = 0;
for (; index < parts.Count; index++)
{
// Flag to see if we have a flag
bool isFlag = false;
string part = parts[index];
switch (part)
{
case CommandStrings.CD:
case CommandStrings.Dump:
case CommandStrings.Protection:
case CommandStrings.Refine:
case CommandStrings.Split:
case CommandStrings.Info:
// case CommandStrings.Rings:
ModeValues.Add(part);
break;
// Default is either a flag or an invalid mode
default:
if (part.StartsWith("-"))
{
isFlag = true;
break;
}
else
{
return false;
}
}
// If we had a flag, break out
if (isFlag)
break;
}
// Loop through all auxiliary flags, if necessary
for (int i = index; i < parts.Count; i++)
{
// Flag read-out values
byte? byteValue = null;
int? intValue = null;
string stringValue = null;
#region General
// Help
ProcessFlagParameter(parts, FlagStrings.HelpShort, FlagStrings.HelpLong, ref i);
// Verbose
ProcessFlagParameter(parts, FlagStrings.Verbose, ref i);
// Drive
stringValue = ProcessStringParameter(parts, FlagStrings.Drive, ref i);
if (!string.IsNullOrWhiteSpace(stringValue))
DriveValue = stringValue;
// Speed
intValue = ProcessInt32Parameter(parts, FlagStrings.Speed, ref i);
if (intValue != null && intValue != Int32.MinValue)
SpeedValue = intValue;
// Retries
intValue = ProcessInt32Parameter(parts, FlagStrings.Retries, ref i);
if (intValue != null && intValue != Int32.MinValue)
RetriesValue = intValue;
// Image Path
stringValue = ProcessStringParameter(parts, FlagStrings.ImagePath, ref i);
if (!string.IsNullOrWhiteSpace(stringValue))
ImagePathValue = $"\"{stringValue.Trim('"')}\"";
// Image Name
stringValue = ProcessStringParameter(parts, FlagStrings.ImageName, ref i);
if (!string.IsNullOrWhiteSpace(stringValue))
ImageNameValue = $"\"{stringValue.Trim('"')}\"";
// Overwrite
ProcessFlagParameter(parts, FlagStrings.Overwrite, ref i);
#endregion
#region Drive Configuration
// Drive Type
stringValue = ProcessStringParameter(parts, FlagStrings.DriveType, ref i);
if (!string.IsNullOrWhiteSpace(stringValue))
DriveTypeValue = stringValue;
// Drive Read Offset
intValue = ProcessInt32Parameter(parts, FlagStrings.DriveReadOffset, ref i);
if (intValue != null && intValue != Int32.MinValue)
DriveReadOffsetValue = intValue;
// Drive C2 Shift
intValue = ProcessInt32Parameter(parts, FlagStrings.DriveC2Shift, ref i);
if (intValue != null && intValue != Int32.MinValue)
DriveC2ShiftValue = intValue;
// Drive Pregap Start
intValue = ProcessInt32Parameter(parts, FlagStrings.DrivePregapStart, ref i);
if (intValue != null && intValue != Int32.MinValue)
DrivePregapStartValue = intValue;
// Drive Read Method
stringValue = ProcessStringParameter(parts, FlagStrings.DriveReadMethod, ref i);
if (!string.IsNullOrWhiteSpace(stringValue))
DriveReadMethodValue = stringValue;
// Drive Sector Order
stringValue = ProcessStringParameter(parts, FlagStrings.DriveSectorOrder, ref i);
if (!string.IsNullOrWhiteSpace(stringValue))
DriveSectorOrderValue = stringValue;
#endregion
#region Drive Specific
// Plextor Skip Leadin
ProcessFlagParameter(parts, FlagStrings.PlextorSkipLeadin, ref i);
// Asus Skip Leadout
ProcessFlagParameter(parts, FlagStrings.AsusSkipLeadout, ref i);
#endregion
#region Offset
// Force Offset
intValue = ProcessInt32Parameter(parts, FlagStrings.ForceOffset, ref i);
if (intValue != null && intValue != Int32.MinValue)
ForceOffsetValue = intValue;
// Audio Silence Threshold
intValue = ProcessInt32Parameter(parts, FlagStrings.AudioSilenceThreshold, ref i);
if (intValue != null && intValue != Int32.MinValue)
AudioSilenceThresholdValue = intValue;
// Correct Offset Shift
ProcessFlagParameter(parts, FlagStrings.CorrectOffsetShift, ref i);
#endregion
#region Split
// Force Split
ProcessFlagParameter(parts, FlagStrings.ForceSplit, ref i);
// Leave Unchanged
ProcessFlagParameter(parts, FlagStrings.LeaveUnchanged, ref i);
// Force QTOC
ProcessFlagParameter(parts, FlagStrings.ForceQTOC, ref i);
// Skip Fill
byteValue = ProcessUInt8Parameter(parts, FlagStrings.SkipFill, ref i);
if (byteValue != null && byteValue != Byte.MinValue)
SkipFillValue = byteValue;
// ISO9660 Trim
ProcessFlagParameter(parts, FlagStrings.ISO9660Trim, ref i);
// CD-i Ready Normalize
ProcessFlagParameter(parts, FlagStrings.CDiReadyNormalize, ref i);
#endregion
#region Miscellaneous
// LBA Start
intValue = ProcessInt32Parameter(parts, FlagStrings.LBAStart, ref i);
if (intValue != null && intValue != Int32.MinValue)
LBAStartValue = intValue;
// LBA End
intValue = ProcessInt32Parameter(parts, FlagStrings.LBAEnd, ref i);
if (intValue != null && intValue != Int32.MinValue)
LBAEndValue = intValue;
// Refine Subchannel
ProcessFlagParameter(parts, FlagStrings.RefineSubchannel, ref i);
// Skip
stringValue = ProcessStringParameter(parts, FlagStrings.Skip, ref i);
if (!string.IsNullOrWhiteSpace(stringValue))
SkipValue = stringValue;
#endregion
}
// If the image name was not set, set it with a default value
if (string.IsNullOrWhiteSpace(this.ImageNameValue))
this.ImageNameValue = "track";
return true;
}
#endregion
#region Information Extraction Methods
/// <summary>
/// Get the cuesheet from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Newline-delimited cuesheet if possible, null on error</returns>
private static string GetCuesheet(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
using (StreamReader sr = File.OpenText(log))
{
try
{
// Fast forward to the dat line
while (!sr.EndOfStream && !sr.ReadLine().TrimStart().StartsWith("CUE ["));
if (sr.EndOfStream)
return null;
// Now that we're at the relevant entries, read each line in and concatenate
string cueString = "", line = sr.ReadLine().Trim();
while (!string.IsNullOrWhiteSpace(line))
{
cueString += line + "\n";
line = sr.ReadLine().Trim();
}
return cueString.TrimEnd('\n');
}
catch
{
// We don't care what the exception is right now
return null;
}
}
}
/// <summary>
/// Get the datfile from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Newline-delimited datfile if possible, null on error</returns>
private static string GetDatfile(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
using (StreamReader sr = File.OpenText(log))
{
try
{
// Fast forward to the dat line
while (!sr.EndOfStream && !sr.ReadLine().TrimStart().StartsWith("dat:"));
if (sr.EndOfStream)
return null;
// Now that we're at the relevant entries, read each line in and concatenate
string datString = "", line = sr.ReadLine().Trim();
while (line.StartsWith("<rom"))
{
datString += line + "\n";
line = sr.ReadLine().Trim();
}
return datString.TrimEnd('\n');
}
catch
{
// We don't care what the exception is right now
return null;
}
}
}
/// <summary>
/// Get the detected error count from the input files, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Error count if possible, -1 on error</returns>
public static long GetErrorCount(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return -1;
using (StreamReader sr = File.OpenText(log))
{
try
{
// Fast forward to the errors lines
while (!sr.EndOfStream && !sr.ReadLine().Trim().StartsWith("CD-ROM ["));
if (sr.EndOfStream)
return 0;
// Now that we're at the relevant lines, find the error count
long errorCount = 0;
while (!sr.EndOfStream)
{
// Skip forward to the "REDUMP.ORG" line
string line;
while (!(line = sr.ReadLine().Trim()).StartsWith("REDUMP.ORG errors"));
// REDUMP.ORG errors: <error count>
string[] parts = line.Split(' ');
if (long.TryParse(parts[2], out long redump))
errorCount += redump;
else
return -1;
}
return errorCount;
}
catch
{
// We don't care what the exception is right now
return -1;
}
}
}
/// <summary>
/// Get the non-zero dta start from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Non-zero dta start if possible, null on error</returns>
private static string GetRingNonZeroDataStart(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
using (StreamReader sr = File.OpenText(log))
{
try
{
// If we find the universal hash line, return the hash only
string line;
while (!sr.EndOfStream)
{
line = sr.ReadLine().TrimStart();
if (line.StartsWith("non-zero data sample range"))
return line.Substring("non-zero data sample range: [".Length).Trim().Split(' ')[0];
}
// We couldn't detect it then
return null;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
}
/// <summary>
/// Get the PVD from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Newline-delimited PVD if possible, null on error</returns>
private static string GetPVD(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
using (StreamReader sr = File.OpenText(log))
{
try
{
// Fast forward to the dat line
while (!sr.EndOfStream && !sr.ReadLine().TrimStart().StartsWith("PVD:"));
if (sr.EndOfStream)
return null;
// Now that we're at the relevant entries, read each line in and concatenate
string pvdString = "", line = sr.ReadLine().Trim();
while (line.StartsWith("03"))
{
pvdString += line + "\n";
line = sr.ReadLine().Trim();
}
return pvdString.TrimEnd('\n');
}
catch
{
// We don't care what the exception is right now
return null;
}
}
}
/// <summary>
/// Get the universal hash from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Universal hash if possible, null on error</returns>
private static string GetUniversalHash(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
using (StreamReader sr = File.OpenText(log))
{
try
{
// If we find the universal hash line, return the hash only
string line;
while (!sr.EndOfStream)
{
line = sr.ReadLine().TrimStart();
if (line.StartsWith("Universal Hash"))
return line.Substring("Universal Hash (SHA-1): ".Length).Trim();
}
// We couldn't detect it then
return null;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
}
/// <summary>
/// Get the write offset from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Sample write offset if possible, null on error</returns>
private static string GetWriteOffset(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
using (StreamReader sr = File.OpenText(log))
{
try
{
// Fast forward to the offset lines
while (!sr.EndOfStream && !sr.ReadLine().TrimStart().StartsWith("detecting offset"));
if (sr.EndOfStream)
return null;
// If we find the disc write offset line, return the offset
string line;
while (!sr.EndOfStream)
{
line = sr.ReadLine().TrimStart();
if (line.StartsWith("disc write offset"))
return line.Substring("disc write offset: ".Length).Trim();
}
// We couldn't detect it then
return null;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
}
/// <summary>
/// Get the version. if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Version if possible, null on error</returns>
private static string GetVersion(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
// Samples:
// redumper v2022.10.28 [Oct 28 2022, 05:41:43] (print usage: --help,-h)
// redumper v2022.12.22 build_87 [Dec 22 2022, 01:56:26]
using (StreamReader sr = File.OpenText(log))
{
try
{
// Skip first line (dump date)
sr.ReadLine();
// Generate regex
// Permissive
var regex = new Regex(@"^redumper (v.+) \[.+\]");
// Strict
//var regex = new Regex(@"^redumper (v\d{4}\.\d{2}\.\d{2}(| build_\d+)) \[.+\]");
// Extract the version string
var match = regex.Match(sr.ReadLine().Trim());
var version = match.Groups[1].Value;
return string.IsNullOrWhiteSpace(version) ? null : version;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
}
#endregion
}
}