mirror of
https://github.com/SabreTools/MPF.git
synced 2026-02-14 05:36:10 +00:00
2465 lines
97 KiB
C#
2465 lines
97 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 SabreTools.Models.CueSheets;
|
|
using SabreTools.RedumpLib;
|
|
using SabreTools.RedumpLib.Data;
|
|
|
|
namespace MPF.Core.Modules.Redumper
|
|
{
|
|
/// <summary>
|
|
/// Represents a generic set of Redumper parameters
|
|
/// </summary>
|
|
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) + GetDefaultExtension(this.Type);
|
|
|
|
/// <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; }
|
|
|
|
/// <summary>
|
|
/// Write offset for dumps when reading as data
|
|
/// </summary>
|
|
public int? DumpWriteOffsetValue { get; set; }
|
|
|
|
/// <summary>
|
|
/// Number of sectors to read at once on initial dump, DVD only (Default 32)
|
|
/// </summary>
|
|
public int? DumpReadSizeValue { get; set; }
|
|
|
|
/// <summary>
|
|
/// Maximum number of lead-in retries per session (Default 4)
|
|
/// </summary>
|
|
public int? PlextorLeadinRetriesValue { get; set; }
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
/// <inheritdoc/>
|
|
public Parameters(string? parameters) : base(parameters) { }
|
|
|
|
/// <inheritdoc/>
|
|
public Parameters(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Options options)
|
|
: base(system, type, drivePath, filename, driveSpeed, options)
|
|
{
|
|
}
|
|
|
|
#region BaseParameters Implementations
|
|
|
|
/// <inheritdoc/>
|
|
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
|
|
{
|
|
var 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");
|
|
else if (GetDatfile($"{basePath}.log") == null)
|
|
missingFiles.Add($"{basePath}.log (dat section)");
|
|
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
|
|
//{
|
|
// // Depends on the disc
|
|
// if (!File.Exists($"{basePath}.cdtext"))
|
|
// missingFiles.Add($"{basePath}.cdtext");
|
|
//
|
|
// // Not available in all versions
|
|
// if (!File.Exists($"{basePath}.hash"))
|
|
// missingFiles.Add($"{basePath}.hash");
|
|
// // Also: "{basePath} (Track X).hash" (get from cuesheet)
|
|
// if (!File.Exists($"{basePath}.skeleton"))
|
|
// missingFiles.Add($"{basePath}.skeleton");
|
|
// // Also: "{basePath} (Track X).skeleton" (get from cuesheet)
|
|
//}
|
|
|
|
break;
|
|
|
|
case MediaType.DVD:
|
|
if (!File.Exists($"{basePath}_logs.zip") || !preCheck)
|
|
{
|
|
if (!File.Exists($"{basePath}.log"))
|
|
missingFiles.Add($"{basePath}.log");
|
|
else if (GetDatfile($"{basePath}.log") == null)
|
|
missingFiles.Add($"{basePath}.dat");
|
|
if (!File.Exists($"{basePath}.manufacturer") && !File.Exists($"{basePath}.1.manufacturer") && !File.Exists($"{basePath}.2.manufacturer"))
|
|
missingFiles.Add($"{basePath}.manufacturer");
|
|
if (!File.Exists($"{basePath}.physical") && !File.Exists($"{basePath}.0.physical") && !File.Exists($"{basePath}.1.physical") && !File.Exists($"{basePath}.2.physical"))
|
|
missingFiles.Add($"{basePath}.physical");
|
|
if (!File.Exists($"{basePath}.state"))
|
|
missingFiles.Add($"{basePath}.state");
|
|
}
|
|
|
|
// Removed or inconsistent files
|
|
//{
|
|
// // Not available in all versions
|
|
// if (!File.Exists($"{basePath}.hash"))
|
|
// missingFiles.Add($"{basePath}.hash");
|
|
// if (!File.Exists($"{basePath}.skeleton"))
|
|
// missingFiles.Add($"{basePath}.skeleton");
|
|
//}
|
|
|
|
break;
|
|
|
|
case MediaType.HDDVD: // TODO: Verify that this is output
|
|
case MediaType.BluRay:
|
|
if (!File.Exists($"{basePath}_logs.zip") || !preCheck)
|
|
{
|
|
if (!File.Exists($"{basePath}.log"))
|
|
missingFiles.Add($"{basePath}.log");
|
|
else if (GetDatfile($"{basePath}.log") == null)
|
|
missingFiles.Add($"{basePath}.dat");
|
|
if (!File.Exists($"{basePath}.physical") && !File.Exists($"{basePath}.0.physical") && !File.Exists($"{basePath}.1.physical") && !File.Exists($"{basePath}.2.physical"))
|
|
missingFiles.Add($"{basePath}.physical");
|
|
if (!File.Exists($"{basePath}.state"))
|
|
missingFiles.Add($"{basePath}.state");
|
|
}
|
|
|
|
// Removed or inconsistent files
|
|
//{
|
|
// // Not available in all versions
|
|
// if (!File.Exists($"{basePath}.hash"))
|
|
// missingFiles.Add($"{basePath}.hash");
|
|
// if (!File.Exists($"{basePath}.skeleton"))
|
|
// missingFiles.Add($"{basePath}.skeleton");
|
|
//}
|
|
|
|
break;
|
|
|
|
default:
|
|
missingFiles.Add("Media and system combination not supported for Redumper");
|
|
break;
|
|
}
|
|
|
|
return (!missingFiles.Any(), missingFiles);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void GenerateSubmissionInfo(SubmissionInfo info, Options options, string basePath, Drive? drive, bool includeArtifacts)
|
|
{
|
|
// Ensure that required sections exist
|
|
info = Builder.EnsureAllSections(info);
|
|
|
|
// Get the dumping program and version
|
|
info.DumpingInfo!.DumpingProgram = $"{EnumConverter.LongName(this.InternalProgram)} {GetVersion($"{basePath}.log") ?? "Unknown Version"}";
|
|
info.DumpingInfo.DumpingDate = InfoTool.GetFileModifiedDate($"{basePath}.log")?.ToString("yyyy-MM-dd HH:mm:ss");
|
|
|
|
// Fill in the hardware data
|
|
if (GetHardwareInfo($"{basePath}.log", out var manufacturer, out var model, out var firmware))
|
|
{
|
|
info.DumpingInfo.Manufacturer = manufacturer;
|
|
info.DumpingInfo.Model = model;
|
|
info.DumpingInfo.Firmware = firmware;
|
|
}
|
|
|
|
// Fill in the disc type data
|
|
if (GetDiscType($"{basePath}.log", out var discTypeOrBookType))
|
|
info.DumpingInfo.ReportedDiscType = discTypeOrBookType;
|
|
|
|
// Fill in the volume labels
|
|
if (GetVolumeLabels($"{basePath}.log", out var volLabels))
|
|
VolumeLabels = volLabels;
|
|
|
|
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.Empty;
|
|
|
|
// Attempt to get the write offset
|
|
string cdWriteOffset = GetWriteOffset($"{basePath}.log") ?? string.Empty;
|
|
info.CommonDiscInfo!.RingWriteOffset = cdWriteOffset;
|
|
info.TracksAndWriteOffsets.OtherWriteOffsets = cdWriteOffset;
|
|
|
|
// Attempt to get the error count
|
|
if (GetErrorCount($"{basePath}.log", out long redumpErrors, out long c2Errors))
|
|
{
|
|
info.CommonDiscInfo.ErrorsCount = (redumpErrors == -1 ? "Error retrieving error count" : redumpErrors.ToString());
|
|
info.DumpingInfo.C2ErrorsCount = (c2Errors == -1 ? "Error retrieving error count" : c2Errors.ToString());
|
|
}
|
|
|
|
// Attempt to get multisession data
|
|
string cdMultiSessionInfo = GetMultisessionInformation($"{basePath}.log") ?? string.Empty;
|
|
if (!string.IsNullOrEmpty(cdMultiSessionInfo))
|
|
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.Multisession] = cdMultiSessionInfo;
|
|
|
|
// Attempt to get the universal hash, if it's an audio disc
|
|
if (this.System.IsAudio())
|
|
{
|
|
string universalHash = GetUniversalHash($"{basePath}.log") ?? string.Empty;
|
|
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.UniversalHash] = universalHash;
|
|
}
|
|
|
|
// Attempt to get the non-zero data start, if it's an audio disc
|
|
if (this.System.IsAudio())
|
|
{
|
|
string ringNonZeroDataStart = GetRingNonZeroDataStart($"{basePath}.log") ?? string.Empty;
|
|
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.RingNonZeroDataStart] = ringNonZeroDataStart;
|
|
}
|
|
|
|
break;
|
|
|
|
case MediaType.DVD:
|
|
case MediaType.HDDVD:
|
|
case MediaType.BluRay:
|
|
info.Extras!.PVD = GetPVD($"{basePath}.log") ?? "Disc has no PVD";
|
|
info.TracksAndWriteOffsets!.ClrMameProData = GetDatfile($"{basePath}.log");
|
|
|
|
// Get the individual hash data, as per internal
|
|
if (InfoTool.GetISOHashValues(info.TracksAndWriteOffsets.ClrMameProData, out long size, out var crc32, out var md5, out var sha1))
|
|
{
|
|
info.SizeAndChecksums!.Size = size;
|
|
info.SizeAndChecksums.CRC32 = crc32;
|
|
info.SizeAndChecksums.MD5 = md5;
|
|
info.SizeAndChecksums.SHA1 = sha1;
|
|
}
|
|
|
|
// Deal with the layerbreaks
|
|
if (GetLayerbreaks($"{basePath}.log", out var layerbreak1, out var layerbreak2, out var layerbreak3))
|
|
{
|
|
info.SizeAndChecksums!.Layerbreak = !string.IsNullOrEmpty(layerbreak1) ? Int64.Parse(layerbreak1) : default;
|
|
info.SizeAndChecksums!.Layerbreak2 = !string.IsNullOrEmpty(layerbreak2) ? Int64.Parse(layerbreak2) : default;
|
|
info.SizeAndChecksums!.Layerbreak3 = !string.IsNullOrEmpty(layerbreak3) ? Int64.Parse(layerbreak3) : default;
|
|
}
|
|
|
|
// Bluray-specific options
|
|
if (this.Type == MediaType.BluRay)
|
|
{
|
|
int trimLength = -1;
|
|
switch (this.System)
|
|
{
|
|
case RedumpSystem.SonyPlayStation3:
|
|
case RedumpSystem.SonyPlayStation4:
|
|
case RedumpSystem.SonyPlayStation5:
|
|
if (info.SizeAndChecksums!.Layerbreak3 != default)
|
|
trimLength = 520;
|
|
else if (info.SizeAndChecksums!.Layerbreak2 != default)
|
|
trimLength = 392;
|
|
else
|
|
trimLength = 264;
|
|
break;
|
|
}
|
|
|
|
info.Extras!.PIC = GetPIC($"{basePath}.physical", trimLength)
|
|
?? GetPIC($"{basePath}.0.physical", trimLength)
|
|
?? GetPIC($"{basePath}.1.physical", trimLength)
|
|
?? string.Empty;
|
|
|
|
var di = InfoTool.GetDiscInformation($"{basePath}.physical")
|
|
?? InfoTool.GetDiscInformation($"{basePath}.0.physical")
|
|
?? InfoTool.GetDiscInformation($"{basePath}.1.physical");
|
|
info.SizeAndChecksums!.PICIdentifier = InfoTool.GetPICIdentifier(di);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
switch (this.System)
|
|
{
|
|
case RedumpSystem.AppleMacintosh:
|
|
case RedumpSystem.EnhancedCD:
|
|
case RedumpSystem.IBMPCcompatible:
|
|
case RedumpSystem.RainbowDisc:
|
|
case RedumpSystem.SonyElectronicBook:
|
|
info.CopyProtection!.SecuROMData = GetSecuROMData($"{basePath}.log") ?? string.Empty;
|
|
|
|
// Needed for some odd copy protections
|
|
info.CopyProtection!.Protection = GetDVDProtection($"{basePath}.log", false) ?? string.Empty;
|
|
break;
|
|
|
|
case RedumpSystem.DVDAudio:
|
|
case RedumpSystem.DVDVideo:
|
|
info.CopyProtection!.Protection = GetDVDProtection($"{basePath}.log", true) ?? string.Empty;
|
|
break;
|
|
|
|
case RedumpSystem.KonamiPython2:
|
|
info.CommonDiscInfo!.EXEDateBuildDate = GetEXEDate($"{basePath}.log");
|
|
if (InfoTool.GetPlayStationExecutableInfo(drive?.Name, out var pythonTwoSerial, out Region? pythonTwoRegion, out var 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 = InfoTool.GetPlayStation2Version(drive?.Name) ?? string.Empty;
|
|
break;
|
|
|
|
case RedumpSystem.MicrosoftXbox:
|
|
// TODO: Support DMI and additional file information when generated
|
|
break;
|
|
|
|
case RedumpSystem.MicrosoftXbox360:
|
|
// TODO: Support DMI and additional file information when generated
|
|
break;
|
|
|
|
case RedumpSystem.NamcoSegaNintendoTriforce:
|
|
// TODO: Support header information and GD-ROM info when generated
|
|
break;
|
|
|
|
case RedumpSystem.SegaMegaCDSegaCD:
|
|
info.Extras!.Header = GetSegaCDHeader($"{basePath}.log", out var scdBuildDate, out var scdSerial, out _) ?? string.Empty;
|
|
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = scdSerial ?? string.Empty;
|
|
info.CommonDiscInfo.EXEDateBuildDate = scdBuildDate ?? string.Empty;
|
|
// TODO: Support region setting from parsed value
|
|
break;
|
|
|
|
case RedumpSystem.SegaChihiro:
|
|
// TODO: Support header information and GD-ROM info when generated
|
|
break;
|
|
|
|
case RedumpSystem.SegaDreamcast:
|
|
// TODO: Support header information and GD-ROM info when generated
|
|
break;
|
|
|
|
case RedumpSystem.SegaNaomi:
|
|
// TODO: Support header information and GD-ROM info when generated
|
|
break;
|
|
|
|
case RedumpSystem.SegaNaomi2:
|
|
// TODO: Support header information and GD-ROM info when generated
|
|
break;
|
|
|
|
case RedumpSystem.SegaSaturn:
|
|
info.Extras!.Header = GetSaturnHeader($"{basePath}.log") ?? string.Empty;
|
|
|
|
// Take only the first 16 lines for Saturn
|
|
if (!string.IsNullOrEmpty(info.Extras.Header))
|
|
info.Extras.Header = string.Join("\n", info.Extras.Header.Split('\n').Take(16).ToArray());
|
|
|
|
if (GetSaturnBuildInfo(info.Extras.Header, out var saturnSerial, out var saturnVersion, out var buildDate))
|
|
{
|
|
// Ensure internal serial is pulled from local data
|
|
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = saturnSerial ?? string.Empty;
|
|
info.VersionAndEditions!.Version = saturnVersion ?? string.Empty;
|
|
info.CommonDiscInfo.EXEDateBuildDate = buildDate ?? string.Empty;
|
|
}
|
|
|
|
break;
|
|
|
|
case RedumpSystem.SonyPlayStation:
|
|
info.CommonDiscInfo!.EXEDateBuildDate = GetEXEDate($"{basePath}.log");
|
|
if (InfoTool.GetPlayStationExecutableInfo(drive?.Name, out var playstationSerial, out Region? playstationRegion, out var 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;
|
|
}
|
|
|
|
info.CopyProtection!.AntiModchip = GetPlayStationAntiModchipDetected($"{basePath}.log").ToYesNo();
|
|
info.EDC!.EDC = GetPlayStationEDCStatus($"{basePath}.log").ToYesNo();
|
|
info.CopyProtection.LibCrypt = GetPlayStationLibCryptStatus($"{basePath}.log").ToYesNo();
|
|
info.CopyProtection.LibCryptData = GetPlayStationLibCryptData($"{basePath}.log");
|
|
break;
|
|
|
|
case RedumpSystem.SonyPlayStation2:
|
|
info.CommonDiscInfo!.EXEDateBuildDate = GetEXEDate($"{basePath}.log");
|
|
if (InfoTool.GetPlayStationExecutableInfo(drive?.Name, out var playstationTwoSerial, out Region? playstationTwoRegion, out var 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 = InfoTool.GetPlayStation2Version(drive?.Name) ?? string.Empty;
|
|
break;
|
|
|
|
case RedumpSystem.SonyPlayStation3:
|
|
info.VersionAndEditions!.Version = InfoTool.GetPlayStation3Version(drive?.Name) ?? string.Empty;
|
|
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = InfoTool.GetPlayStation3Serial(drive?.Name) ?? string.Empty;
|
|
string? firmwareVersion = InfoTool.GetPlayStation3FirmwareVersion(drive?.Name);
|
|
if (firmwareVersion != null)
|
|
info.CommonDiscInfo!.ContentsSpecialFields![SiteCode.Patches] = $"PS3 Firmware {firmwareVersion}";
|
|
break;
|
|
|
|
case RedumpSystem.SonyPlayStation4:
|
|
info.VersionAndEditions!.Version = InfoTool.GetPlayStation4Version(drive?.Name) ?? string.Empty;
|
|
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = InfoTool.GetPlayStation4Serial(drive?.Name) ?? string.Empty;
|
|
break;
|
|
|
|
case RedumpSystem.SonyPlayStation5:
|
|
info.VersionAndEditions!.Version = InfoTool.GetPlayStation5Version(drive?.Name) ?? string.Empty;
|
|
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = InfoTool.GetPlayStation5Serial(drive?.Name) ?? string.Empty;
|
|
break;
|
|
}
|
|
|
|
// Fill in any artifacts that exist, Base64-encoded, if we need to
|
|
if (includeArtifacts)
|
|
{
|
|
info.Artifacts ??= [];
|
|
|
|
if (File.Exists($"{basePath}.cdtext"))
|
|
info.Artifacts["cdtext"] = GetBase64(GetFullFile($"{basePath}.cdtext")) ?? string.Empty;
|
|
if (File.Exists($"{basePath}.cue"))
|
|
info.Artifacts["cue"] = GetBase64(GetFullFile($"{basePath}.cue")) ?? string.Empty;
|
|
if (File.Exists($"{basePath}.fulltoc"))
|
|
info.Artifacts["fulltoc"] = GetBase64(GetFullFile($"{basePath}.fulltoc")) ?? string.Empty;
|
|
if (File.Exists($"{basePath}.hash"))
|
|
info.Artifacts["hash"] = GetBase64(GetFullFile($"{basePath}.hash")) ?? string.Empty;
|
|
// TODO: "{basePath} (Track X).hash" (get from cuesheet)
|
|
if (File.Exists($"{basePath}.log"))
|
|
info.Artifacts["log"] = GetBase64(GetFullFile($"{basePath}.log")) ?? string.Empty;
|
|
if (File.Exists($"{basePath}.manufacturer"))
|
|
info.Artifacts["manufacturer"] = GetBase64(GetFullFile($"{basePath}.manufacturer")) ?? string.Empty;
|
|
if (File.Exists($"{basePath}.1.manufacturer"))
|
|
info.Artifacts["manufacturer1"] = GetBase64(GetFullFile($"{basePath}.1.manufacturer")) ?? string.Empty;
|
|
if (File.Exists($"{basePath}.2.manufacturer"))
|
|
info.Artifacts["manufacturer2"] = GetBase64(GetFullFile($"{basePath}.2.manufacturer")) ?? string.Empty;
|
|
if (File.Exists($"{basePath}.physical"))
|
|
info.Artifacts["physical"] = GetBase64(GetFullFile($"{basePath}.physical")) ?? string.Empty;
|
|
if (File.Exists($"{basePath}.0.physical"))
|
|
info.Artifacts["physical0"] = GetBase64(GetFullFile($"{basePath}.0.physical")) ?? string.Empty;
|
|
if (File.Exists($"{basePath}.1.physical"))
|
|
info.Artifacts["physical1"] = GetBase64(GetFullFile($"{basePath}.1.physical")) ?? string.Empty;
|
|
if (File.Exists($"{basePath}.2.physical"))
|
|
info.Artifacts["physical2"] = GetBase64(GetFullFile($"{basePath}.2.physical")) ?? string.Empty;
|
|
// if (File.Exists($"{basePath}.skeleton"))
|
|
// info.Artifacts["skeleton"] = GetBase64(GetFullFile($"{basePath}.skeleton")) ?? string.Empty;
|
|
// // Also: "{basePath} (Track X).skeleton" (get from cuesheet)
|
|
// if (File.Exists($"{basePath}.scram"))
|
|
// info.Artifacts["scram"] = GetBase64(GetFullFile($"{basePath}.scram")) ?? string.Empty;
|
|
// if (File.Exists($"{basePath}.scrap"))
|
|
// info.Artifacts["scrap"] = GetBase64(GetFullFile($"{basePath}.scrap")) ?? string.Empty;
|
|
if (File.Exists($"{basePath}.state"))
|
|
info.Artifacts["state"] = GetBase64(GetFullFile($"{basePath}.state")) ?? string.Empty;
|
|
if (File.Exists($"{basePath}.subcode"))
|
|
info.Artifacts["subcode"] = GetBase64(GetFullFile($"{basePath}.subcode")) ?? string.Empty;
|
|
if (File.Exists($"{basePath}.toc"))
|
|
info.Artifacts["toc"] = GetBase64(GetFullFile($"{basePath}.toc")) ?? string.Empty;
|
|
}
|
|
}
|
|
|
|
/// <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()
|
|
{
|
|
var parameters = new List<string>();
|
|
|
|
ModeValues ??= [CommandStrings.NONE];
|
|
|
|
// Modes
|
|
parameters.AddRange(ModeValues);
|
|
|
|
#region General
|
|
|
|
// Help
|
|
if (this[FlagStrings.HelpLong] == true)
|
|
parameters.Add(FlagStrings.HelpLong);
|
|
|
|
// Version
|
|
if (this[FlagStrings.Version] == true)
|
|
parameters.Add(FlagStrings.Version);
|
|
|
|
// Verbose
|
|
if (this[FlagStrings.Verbose] == true)
|
|
parameters.Add(FlagStrings.Verbose);
|
|
|
|
// Debug
|
|
if (this[FlagStrings.Debug] == true)
|
|
parameters.Add(FlagStrings.Debug);
|
|
|
|
// 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 Leadin Skip
|
|
if (this[FlagStrings.PlextorSkipLeadin] == true)
|
|
parameters.Add(FlagStrings.PlextorSkipLeadin);
|
|
|
|
// Plextor Leadin Retries
|
|
if (this[FlagStrings.PlextorLeadinRetries] == true)
|
|
{
|
|
if (PlextorLeadinRetriesValue != null)
|
|
parameters.Add($"{FlagStrings.PlextorLeadinRetries}={PlextorLeadinRetriesValue}");
|
|
}
|
|
|
|
// 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);
|
|
|
|
// Offset Shift Relocate
|
|
if (this[FlagStrings.OffsetShiftRelocate] == true)
|
|
parameters.Add(FlagStrings.OffsetShiftRelocate);
|
|
|
|
#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);
|
|
|
|
#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.IsNullOrEmpty(SkipValue))
|
|
parameters.Add($"{FlagStrings.Skip}={SkipValue}");
|
|
}
|
|
|
|
// Dump Write Offset
|
|
if (this[FlagStrings.DumpWriteOffset] == true)
|
|
{
|
|
if (DumpWriteOffsetValue != null)
|
|
parameters.Add($"{FlagStrings.DumpWriteOffset}={DumpWriteOffsetValue}");
|
|
}
|
|
|
|
// Dump Read Size
|
|
if (this[FlagStrings.DumpReadSize] == true)
|
|
{
|
|
if (DumpReadSizeValue != null && DumpReadSizeValue > 0)
|
|
parameters.Add($"{FlagStrings.DumpReadSize}={DumpReadSizeValue}");
|
|
}
|
|
|
|
// Overread Leadout
|
|
if (this[FlagStrings.OverreadLeadout] == true)
|
|
parameters.Add(FlagStrings.OverreadLeadout);
|
|
|
|
// Legacy Subs
|
|
if (this[FlagStrings.LegacySubs] == true)
|
|
parameters.Add(FlagStrings.LegacySubs);
|
|
|
|
// Disable CD Text
|
|
if (this[FlagStrings.DisableCDText] == true)
|
|
parameters.Add(FlagStrings.DisableCDText);
|
|
|
|
#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] =
|
|
[
|
|
// General
|
|
FlagStrings.HelpLong,
|
|
FlagStrings.HelpShort,
|
|
FlagStrings.Version,
|
|
FlagStrings.Verbose,
|
|
FlagStrings.Debug,
|
|
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.PlextorLeadinRetries,
|
|
FlagStrings.AsusSkipLeadout,
|
|
|
|
// Offset
|
|
FlagStrings.ForceOffset,
|
|
FlagStrings.AudioSilenceThreshold,
|
|
FlagStrings.CorrectOffsetShift,
|
|
FlagStrings.OffsetShiftRelocate,
|
|
|
|
// Split
|
|
FlagStrings.ForceSplit,
|
|
FlagStrings.LeaveUnchanged,
|
|
FlagStrings.ForceQTOC,
|
|
FlagStrings.SkipFill,
|
|
FlagStrings.ISO9660Trim,
|
|
|
|
// Miscellaneous
|
|
FlagStrings.LBAStart,
|
|
FlagStrings.LBAEnd,
|
|
FlagStrings.RefineSubchannel,
|
|
FlagStrings.Skip,
|
|
FlagStrings.DumpWriteOffset,
|
|
FlagStrings.DumpReadSize,
|
|
FlagStrings.OverreadLeadout,
|
|
FlagStrings.LegacySubs,
|
|
FlagStrings.DisableCDText,
|
|
],
|
|
};
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override string? GetDefaultExtension(MediaType? mediaType) => Converters.Extension(mediaType);
|
|
|
|
/// <inheritdoc/>
|
|
public override List<string> GetDeleteableFilePaths(string basePath)
|
|
{
|
|
var deleteableFiles = new List<string>();
|
|
|
|
if (File.Exists($"{basePath}.scram"))
|
|
deleteableFiles.Add($"{basePath}.scram");
|
|
if (File.Exists($"{basePath}.scrap"))
|
|
deleteableFiles.Add($"{basePath}.scrap");
|
|
|
|
return deleteableFiles;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override List<string> GetLogFilePaths(string basePath)
|
|
{
|
|
var 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}.state"))
|
|
logFiles.Add($"{basePath}.state");
|
|
if (File.Exists($"{basePath}.subcode"))
|
|
logFiles.Add($"{basePath}.subcode");
|
|
if (File.Exists($"{basePath}.toc"))
|
|
logFiles.Add($"{basePath}.toc");
|
|
|
|
// Include .hash and .skeleton for all files in cuesheet
|
|
var cueSheet = new SabreTools.Serialization.Files.CueSheet().Deserialize($"{basePath}.cue");
|
|
string? baseDir = Path.GetDirectoryName(basePath);
|
|
if (cueSheet?.Files != null && baseDir != null)
|
|
{
|
|
foreach (CueFile? file in cueSheet.Files)
|
|
{
|
|
string? trackName = Path.GetFileNameWithoutExtension(file?.FileName);
|
|
if (trackName == null)
|
|
continue;
|
|
|
|
string trackPath = Path.Combine(baseDir, trackName);
|
|
if (File.Exists($"{trackPath}.hash"))
|
|
logFiles.Add($"{trackPath}.hash");
|
|
if (File.Exists($"{trackPath}.skeleton"))
|
|
logFiles.Add($"{trackPath}.skeleton");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (File.Exists($"{basePath}.hash"))
|
|
logFiles.Add($"{basePath}.hash");
|
|
if (File.Exists($"{basePath}.skeleton"))
|
|
logFiles.Add($"{basePath}.skeleton");
|
|
}
|
|
|
|
break;
|
|
|
|
case MediaType.DVD:
|
|
if (File.Exists($"{basePath}.hash"))
|
|
logFiles.Add($"{basePath}.hash");
|
|
if (File.Exists($"{basePath}.log"))
|
|
logFiles.Add($"{basePath}.log");
|
|
if (File.Exists($"{basePath}.manufacturer"))
|
|
logFiles.Add($"{basePath}.manufacturer");
|
|
if (File.Exists($"{basePath}.1.manufacturer"))
|
|
logFiles.Add($"{basePath}.1.manufacturer");
|
|
if (File.Exists($"{basePath}.2.manufacturer"))
|
|
logFiles.Add($"{basePath}.2.manufacturer");
|
|
if (File.Exists($"{basePath}.physical"))
|
|
logFiles.Add($"{basePath}.physical");
|
|
if (File.Exists($"{basePath}.0.physical"))
|
|
logFiles.Add($"{basePath}.0.physical");
|
|
if (File.Exists($"{basePath}.1.physical"))
|
|
logFiles.Add($"{basePath}.1.physical");
|
|
if (File.Exists($"{basePath}.2.physical"))
|
|
logFiles.Add($"{basePath}.2.physical");
|
|
if (File.Exists($"{basePath}.skeleton"))
|
|
logFiles.Add($"{basePath}.skeleton");
|
|
if (File.Exists($"{basePath}.state"))
|
|
logFiles.Add($"{basePath}.state");
|
|
break;
|
|
|
|
case MediaType.HDDVD: // TODO: Confirm that this information outputs
|
|
case MediaType.BluRay:
|
|
if (File.Exists($"{basePath}.hash"))
|
|
logFiles.Add($"{basePath}.hash");
|
|
if (File.Exists($"{basePath}.log"))
|
|
logFiles.Add($"{basePath}.log");
|
|
if (File.Exists($"{basePath}.physical"))
|
|
logFiles.Add($"{basePath}.physical");
|
|
if (File.Exists($"{basePath}.0.physical"))
|
|
logFiles.Add($"{basePath}.0.physical");
|
|
if (File.Exists($"{basePath}.1.physical"))
|
|
logFiles.Add($"{basePath}.1.physical");
|
|
if (File.Exists($"{basePath}.2.physical"))
|
|
logFiles.Add($"{basePath}.2.physical");
|
|
if (File.Exists($"{basePath}.skeleton"))
|
|
logFiles.Add($"{basePath}.skeleton");
|
|
if (File.Exists($"{basePath}.state"))
|
|
logFiles.Add($"{basePath}.state");
|
|
break;
|
|
}
|
|
|
|
return logFiles;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override bool IsDumpingCommand()
|
|
{
|
|
return this.BaseCommand == CommandStrings.NONE
|
|
|| this.BaseCommand?.Contains(CommandStrings.CD) == true
|
|
|| this.BaseCommand?.Contains(CommandStrings.DVD) == true
|
|
|| this.BaseCommand?.Contains(CommandStrings.BluRay) == true
|
|
|| this.BaseCommand?.Contains(CommandStrings.SACD) == true
|
|
|| this.BaseCommand?.Contains(CommandStrings.Dump) == true;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void ResetValues()
|
|
{
|
|
BaseCommand = CommandStrings.NONE;
|
|
|
|
flags = [];
|
|
|
|
// 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;
|
|
DumpReadSizeValue = null;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void SetDefaultParameters(string? drivePath, string filename, int? driveSpeed, Options options)
|
|
{
|
|
// If we don't have a CD, DVD, HD-DVD, or BD, we can't dump using redumper
|
|
if (this.Type != MediaType.CDROM
|
|
&& this.Type != MediaType.DVD
|
|
&& this.Type != MediaType.HDDVD
|
|
&& this.Type != MediaType.BluRay)
|
|
{
|
|
return;
|
|
}
|
|
|
|
BaseCommand = CommandStrings.NONE;
|
|
switch (this.Type)
|
|
{
|
|
case MediaType.CDROM:
|
|
ModeValues = this.System switch
|
|
{
|
|
RedumpSystem.SuperAudioCD => [CommandStrings.SACD],
|
|
_ => [CommandStrings.CD],
|
|
};
|
|
break;
|
|
case MediaType.DVD:
|
|
ModeValues = [CommandStrings.DVD];
|
|
break;
|
|
case MediaType.HDDVD: // TODO: Keep in sync if another command string shows up
|
|
ModeValues = [CommandStrings.DVD];
|
|
break;
|
|
case MediaType.BluRay:
|
|
ModeValues = [CommandStrings.BluRay];
|
|
break;
|
|
default:
|
|
BaseCommand = null;
|
|
return;
|
|
}
|
|
|
|
this[FlagStrings.Drive] = true;
|
|
DriveValue = drivePath;
|
|
|
|
this[FlagStrings.Speed] = true;
|
|
SpeedValue = driveSpeed;
|
|
|
|
// Set user-defined options
|
|
if (options.RedumperEnableVerbose)
|
|
this[FlagStrings.Verbose] = options.RedumperEnableVerbose;
|
|
if (options.RedumperEnableDebug)
|
|
this[FlagStrings.Debug] = options.RedumperEnableDebug;
|
|
if (options.RedumperUseBEReading)
|
|
{
|
|
this[FlagStrings.DriveReadMethod] = true;
|
|
DriveReadMethodValue = "BE_CDDA";
|
|
}
|
|
if (options.RedumperUseGenericDriveType)
|
|
{
|
|
this[FlagStrings.DriveType] = true;
|
|
DriveTypeValue = "GENERIC";
|
|
}
|
|
|
|
// Set the output paths
|
|
if (!string.IsNullOrEmpty(filename))
|
|
{
|
|
var imagePath = Path.GetDirectoryName(filename);
|
|
if (!string.IsNullOrEmpty(imagePath))
|
|
{
|
|
this[FlagStrings.ImagePath] = true;
|
|
ImagePathValue = $"\"{imagePath}\"";
|
|
}
|
|
|
|
string imageName = Path.GetFileNameWithoutExtension(filename);
|
|
if (!string.IsNullOrEmpty(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.IsNullOrEmpty(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, @"([a-zA-Z\-]*=)?[\""].+?[\""]|[^ ]+", RegexOptions.Compiled)
|
|
.Cast<Match>()
|
|
.Select(m => m.Value)
|
|
.ToList();
|
|
|
|
// Setup the modes
|
|
ModeValues = [];
|
|
|
|
// 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.DVD:
|
|
case CommandStrings.BluRay:
|
|
case CommandStrings.SACD:
|
|
case CommandStrings.Rings:
|
|
case CommandStrings.Dump:
|
|
case CommandStrings.DumpNew: // Temporary command, to be removed later
|
|
case CommandStrings.Refine:
|
|
case CommandStrings.RefineNew: // Temporary command, to be removed later
|
|
case CommandStrings.Verify:
|
|
case CommandStrings.DVDKey:
|
|
case CommandStrings.DVDIsoKey:
|
|
case CommandStrings.Protection:
|
|
case CommandStrings.Split:
|
|
case CommandStrings.Hash:
|
|
case CommandStrings.Info:
|
|
case CommandStrings.Skeleton:
|
|
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);
|
|
|
|
// Version
|
|
ProcessFlagParameter(parts, FlagStrings.Version, ref i);
|
|
|
|
// Verbose
|
|
ProcessFlagParameter(parts, FlagStrings.Verbose, ref i);
|
|
|
|
// Debug
|
|
ProcessFlagParameter(parts, FlagStrings.Debug, ref i);
|
|
|
|
// Drive
|
|
stringValue = ProcessStringParameter(parts, FlagStrings.Drive, ref i);
|
|
if (!string.IsNullOrEmpty(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.IsNullOrEmpty(stringValue))
|
|
ImagePathValue = $"\"{stringValue!.Trim('"')}\"";
|
|
|
|
// Image Name
|
|
stringValue = ProcessStringParameter(parts, FlagStrings.ImageName, ref i);
|
|
if (!string.IsNullOrEmpty(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.IsNullOrEmpty(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.IsNullOrEmpty(stringValue))
|
|
DriveReadMethodValue = stringValue;
|
|
|
|
// Drive Sector Order
|
|
stringValue = ProcessStringParameter(parts, FlagStrings.DriveSectorOrder, ref i);
|
|
if (!string.IsNullOrEmpty(stringValue))
|
|
DriveSectorOrderValue = stringValue;
|
|
|
|
#endregion
|
|
|
|
#region Drive Specific
|
|
|
|
// Plextor Skip Leadin
|
|
ProcessFlagParameter(parts, FlagStrings.PlextorSkipLeadin, ref i);
|
|
|
|
// Plextor Leadin Retries
|
|
intValue = ProcessInt32Parameter(parts, FlagStrings.PlextorLeadinRetries, ref i);
|
|
if (intValue != null && intValue != Int32.MinValue)
|
|
PlextorLeadinRetriesValue = intValue;
|
|
|
|
// 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);
|
|
|
|
// Correct Shift Relocate
|
|
ProcessFlagParameter(parts, FlagStrings.OffsetShiftRelocate, 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);
|
|
|
|
#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.IsNullOrEmpty(stringValue))
|
|
SkipValue = stringValue;
|
|
|
|
// Dump Write Offset
|
|
intValue = ProcessInt32Parameter(parts, FlagStrings.DumpWriteOffset, ref i);
|
|
if (intValue != null && intValue != Int32.MinValue)
|
|
DumpWriteOffsetValue = intValue;
|
|
|
|
// Dump Read Size
|
|
intValue = ProcessInt32Parameter(parts, FlagStrings.DumpReadSize, ref i);
|
|
if (intValue != null && intValue != Int32.MinValue)
|
|
DumpReadSizeValue = intValue;
|
|
|
|
// Overread Leadout
|
|
ProcessFlagParameter(parts, FlagStrings.OverreadLeadout, ref i);
|
|
|
|
// Legacy Subs
|
|
ProcessFlagParameter(parts, FlagStrings.LegacySubs, ref i);
|
|
|
|
// Disable CD Text
|
|
ProcessFlagParameter(parts, FlagStrings.DisableCDText, ref i);
|
|
|
|
#endregion
|
|
}
|
|
|
|
// If the image name was not set, set it with a default value
|
|
if (string.IsNullOrEmpty(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;
|
|
|
|
try
|
|
{
|
|
// Fast forward to the dat line
|
|
using var sr = File.OpenText(log);
|
|
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("CUE [") == false) ;
|
|
if (sr.EndOfStream)
|
|
return null;
|
|
|
|
// Now that we're at the relevant entries, read each line in and concatenate
|
|
string? cueString = string.Empty, line = sr.ReadLine()?.Trim();
|
|
while (!string.IsNullOrEmpty(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;
|
|
|
|
try
|
|
{
|
|
using var sr = File.OpenText(log);
|
|
string? datString = null;
|
|
|
|
// Find all occurrences of the hash information
|
|
while (!sr.EndOfStream)
|
|
{
|
|
// Fast forward to the dat line
|
|
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("dat:") == false) ;
|
|
if (sr.EndOfStream)
|
|
break;
|
|
|
|
// Now that we're at the relevant entries, read each line in and concatenate
|
|
datString = string.Empty;
|
|
var line = sr.ReadLine()?.Trim();
|
|
while (line?.StartsWith("<rom") == true)
|
|
{
|
|
datString += line + "\n";
|
|
if (sr.EndOfStream)
|
|
break;
|
|
|
|
line = sr.ReadLine()?.Trim();
|
|
}
|
|
}
|
|
|
|
return datString?.TrimEnd('\n');
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get reported disc type information, if possible
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <returns>True if disc type info was set, false otherwise</returns>
|
|
private static bool GetDiscType(string log, out string? discTypeOrBookType)
|
|
{
|
|
// Set the default values
|
|
discTypeOrBookType = null;
|
|
|
|
// If the file doesn't exist, we can't get the info
|
|
if (!File.Exists(log))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
using var sr = File.OpenText(log);
|
|
var line = sr.ReadLine();
|
|
while (line != null)
|
|
{
|
|
// Trim the line for later use
|
|
line = line.Trim();
|
|
|
|
// The profile is listed in a single line
|
|
if (line.StartsWith("current profile:"))
|
|
{
|
|
// current profile: <discType>
|
|
discTypeOrBookType = line.Substring("current profile: ".Length);
|
|
}
|
|
|
|
line = sr.ReadLine();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
discTypeOrBookType = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get all Volume Identifiers
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <returns>Volume labels (by type), or null if none present</returns>
|
|
private static bool GetVolumeLabels(string log, out Dictionary<string, List<string>> volLabels)
|
|
{
|
|
// If the file doesn't exist, can't get the volume labels
|
|
volLabels = [];
|
|
if (!File.Exists(log))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
using var sr = File.OpenText(log);
|
|
var line = sr.ReadLine();
|
|
|
|
while (line != null)
|
|
{
|
|
// Trim the line for later use
|
|
line = line.Trim();
|
|
|
|
// ISO9660 Volume Identifier
|
|
if (line.StartsWith("volume identifier: "))
|
|
{
|
|
string label = line.Substring("volume identifier: ".Length);
|
|
|
|
// Skip if label is blank
|
|
if (label == null || label.Length <= 0)
|
|
break;
|
|
|
|
if (volLabels.ContainsKey(label))
|
|
volLabels[label].Add("ISO");
|
|
else
|
|
volLabels[label] = ["ISO"];
|
|
|
|
// Redumper log currently only outputs ISO9660 label, end here
|
|
break;
|
|
}
|
|
|
|
line = sr.ReadLine();
|
|
}
|
|
|
|
// Return true if a volume label was found
|
|
return volLabels.Count > 0;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
volLabels = [];
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the DVD protection information, if possible
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <param name="includeAlways">Indicates whether region and protection type are always included</param>
|
|
/// <returns>Formatted string representing the DVD protection, null on error</returns>
|
|
private static string? GetDVDProtection(string log, bool includeAlways)
|
|
{
|
|
// If one of the files doesn't exist, we can't get info from them
|
|
if (!File.Exists(log))
|
|
return null;
|
|
|
|
// Setup all of the individual pieces
|
|
string? region = null, rceProtection = null, copyrightProtectionSystemType = null, vobKeys = null, decryptedDiscKey = null;
|
|
using (var sr = File.OpenText(log))
|
|
{
|
|
try
|
|
{
|
|
// Fast forward to the copyright information
|
|
while (sr.ReadLine()?.Trim().StartsWith("copyright:") == false) ;
|
|
|
|
// Now read until we hit the manufacturing information
|
|
var line = sr.ReadLine()?.Trim();
|
|
while (line != null && !sr.EndOfStream)
|
|
{
|
|
if (line.StartsWith("protection system type"))
|
|
{
|
|
copyrightProtectionSystemType = line.Substring("protection system type: ".Length);
|
|
if (copyrightProtectionSystemType == "none" || copyrightProtectionSystemType == "<none>")
|
|
copyrightProtectionSystemType = "No";
|
|
}
|
|
else if (line.StartsWith("region management information:"))
|
|
{
|
|
region = line.Substring("region management information: ".Length);
|
|
}
|
|
else if (line.StartsWith("disc key"))
|
|
{
|
|
decryptedDiscKey = line.Substring("disc key: ".Length).Replace(':', ' ');
|
|
}
|
|
else if (line.StartsWith("title keys"))
|
|
{
|
|
vobKeys = string.Empty;
|
|
|
|
line = sr.ReadLine()?.Trim();
|
|
while (!string.IsNullOrEmpty(line))
|
|
{
|
|
var match = Regex.Match(line, @"^(.*?): (.*?)$", RegexOptions.Compiled);
|
|
if (match.Success)
|
|
{
|
|
string normalizedKey = match.Groups[2].Value.Replace(':', ' ');
|
|
if (normalizedKey == "none" || normalizedKey == "<none>")
|
|
normalizedKey = "No Title Key";
|
|
else if (normalizedKey == "<error>")
|
|
normalizedKey = "Error Retrieving Title Key";
|
|
|
|
vobKeys += $"{match.Groups[1].Value} Title Key: {match.Groups[2].Value.Replace(':', ' ')}\n";
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
line = sr.ReadLine()?.Trim();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
line = sr.ReadLine()?.Trim();
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
// Filter out if we're not always including information
|
|
if (!includeAlways)
|
|
{
|
|
if (region == "1 2 3 4 5 6 7 8")
|
|
region = null;
|
|
if (copyrightProtectionSystemType == "No")
|
|
copyrightProtectionSystemType = null;
|
|
}
|
|
|
|
// Now we format everything we can
|
|
string protection = string.Empty;
|
|
if (!string.IsNullOrEmpty(region))
|
|
protection += $"Region: {region}\n";
|
|
if (!string.IsNullOrEmpty(rceProtection))
|
|
protection += $"RCE Protection: {rceProtection}\n";
|
|
if (!string.IsNullOrEmpty(copyrightProtectionSystemType))
|
|
protection += $"Copyright Protection System Type: {copyrightProtectionSystemType}\n";
|
|
if (!string.IsNullOrEmpty(vobKeys))
|
|
protection += vobKeys;
|
|
if (!string.IsNullOrEmpty(decryptedDiscKey))
|
|
protection += $"Decrypted Disc Key: {decryptedDiscKey}\n";
|
|
|
|
return protection;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the detected error counts from the input files, if possible
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <returns>True if error counts could be retrieved, false otherwise</returns>
|
|
public static bool GetErrorCount(string log, out long redumpErrors, out long c2Errors)
|
|
{
|
|
// Set the default values for error counts
|
|
redumpErrors = -1; c2Errors = -1;
|
|
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(log))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
using var sr = File.OpenText(log);
|
|
|
|
// Find the error counts
|
|
while (!sr.EndOfStream)
|
|
{
|
|
var line = sr.ReadLine()?.Trim();
|
|
if (line == null)
|
|
break;
|
|
|
|
// C2: <error count>
|
|
if (line.StartsWith("C2:"))
|
|
{
|
|
string[] parts = line.Split(' ');
|
|
if (!long.TryParse(parts[1], out c2Errors))
|
|
c2Errors = -1;
|
|
}
|
|
|
|
// REDUMP.ORG errors: <error count>
|
|
else if (line.StartsWith("REDUMP.ORG errors:"))
|
|
{
|
|
string[] parts = line!.Split(' ');
|
|
if (!long.TryParse(parts[2], out redumpErrors))
|
|
redumpErrors = -1;
|
|
}
|
|
}
|
|
|
|
// If the Redump error count is -1, then an issue occurred
|
|
return redumpErrors != -1;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the EXE Date from the log, if possible
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <returns>EXE date if possible, null otherwise</returns>
|
|
public static string? GetEXEDate(string log)
|
|
{
|
|
// If the file doesn't exist, we can't get the info
|
|
if (!File.Exists(log))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
using var sr = File.OpenText(log);
|
|
var line = sr.ReadLine();
|
|
while (line != null)
|
|
{
|
|
// Trim the line for later use
|
|
line = line.Trim();
|
|
|
|
// The exe date is listed in a single line
|
|
if (line.StartsWith("EXE date:"))
|
|
{
|
|
// exe date: yyyy-MM-dd
|
|
return line.Substring("EXE date: ".Length);
|
|
}
|
|
|
|
line = sr.ReadLine();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get hardware information from the input file, if possible
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <returns>True if hardware info was set, false otherwise</returns>
|
|
private static bool GetHardwareInfo(string log, out string? manufacturer, out string? model, out string? firmware)
|
|
{
|
|
// Set the default values
|
|
manufacturer = null; model = null; firmware = null;
|
|
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(log))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
// Fast forward to the drive information line
|
|
using var sr = File.OpenText(log);
|
|
while (!(sr.ReadLine()?.Trim().StartsWith("drive path:") ?? true)) ;
|
|
|
|
// If we find the hardware info line, return each value
|
|
// drive: <vendor_id> - <product_id> (revision level: <product_revision_level>, vendor specific: <vendor_specific>)
|
|
var regex = new Regex(@"drive: (.+) - (.+) \(revision level: (.+), vendor specific: (.+)\)", RegexOptions.Compiled);
|
|
|
|
string? line;
|
|
while ((line = sr.ReadLine()) != null)
|
|
{
|
|
var match = regex.Match(line.Trim());
|
|
if (match.Success)
|
|
{
|
|
manufacturer = match.Groups[1].Value;
|
|
model = match.Groups[2].Value;
|
|
firmware = match.Groups[3].Value;
|
|
firmware += match.Groups[4].Value == "<empty>" ? "" : $" ({match.Groups[4].Value})";
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// We couldn't detect it then
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the layerbreaks from the input file, if possible
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <returns>True if any layerbreaks were found, false otherwise</returns>
|
|
private static bool GetLayerbreaks(string log, out string? layerbreak1, out string? layerbreak2, out string? layerbreak3)
|
|
{
|
|
// Set the default values
|
|
layerbreak1 = null; layerbreak2 = null; layerbreak3 = null;
|
|
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(log))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
// Find the layerbreak
|
|
using var sr = File.OpenText(log);
|
|
while (!sr.EndOfStream)
|
|
{
|
|
var line = sr.ReadLine()?.Trim();
|
|
|
|
// If we have a null line, just break
|
|
if (line == null)
|
|
break;
|
|
|
|
// Single-layer discs have no layerbreak
|
|
if (line.Contains("layers count: 1"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Dual-layer discs have a regular layerbreak (old)
|
|
else if (line.StartsWith("data "))
|
|
{
|
|
// data { LBA: <startLBA> .. <endLBA>, length: <length>, hLBA: <startLBA> .. <endLBA> }
|
|
string[] split = line.Split(' ').Where(s => !string.IsNullOrEmpty(s)).ToArray();
|
|
layerbreak1 ??= split[7].TrimEnd(',');
|
|
}
|
|
|
|
// Dual-layer discs have a regular layerbreak (new)
|
|
else if (line.StartsWith("layer break:"))
|
|
{
|
|
// layer break: <layerbreak>
|
|
layerbreak1 = line.Substring("layer break: ".Length).Trim();
|
|
}
|
|
|
|
// Multi-layer discs have the layer in the name
|
|
else if (line.StartsWith("layer break (layer: 0):"))
|
|
{
|
|
// layer break (layer: 0): <layerbreak>
|
|
layerbreak1 = line.Substring("layer break (layer: 0): ".Length).Trim();
|
|
}
|
|
else if (line.StartsWith("layer break (layer: 1):"))
|
|
{
|
|
// layer break (layer: 1): <layerbreak>
|
|
layerbreak2 = line.Substring("layer break (layer: 1): ".Length).Trim();
|
|
}
|
|
else if (line.StartsWith("layer break (layer: 2):"))
|
|
{
|
|
// layer break (layer: 2): <layerbreak>
|
|
layerbreak3 = line.Substring("layer break (layer: 2): ".Length).Trim();
|
|
}
|
|
}
|
|
|
|
// Return the layerbreak, if possible
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get multisession information from the input file, if possible
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <returns>Formatted multisession information, null on error</returns>
|
|
private static string? GetMultisessionInformation(string log)
|
|
{
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(log))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
// Fast forward to the multisession lines
|
|
using var sr = File.OpenText(log);
|
|
while (!sr.EndOfStream && sr.ReadLine()?.Trim()?.StartsWith("multisession:") == false) ;
|
|
if (sr.EndOfStream)
|
|
return null;
|
|
|
|
// Now that we're at the relevant lines, find the session info
|
|
string? firstSession = null, secondSession = null;
|
|
while (!sr.EndOfStream)
|
|
{
|
|
var line = sr.ReadLine()?.Trim();
|
|
|
|
// If we have a null line, just break
|
|
if (line == null)
|
|
break;
|
|
|
|
// Store the first session range
|
|
if (line.Contains("session 1:"))
|
|
firstSession = line.Substring("session 1: ".Length).Trim();
|
|
|
|
// Store the secomd session range
|
|
else if (line.Contains("session 2:"))
|
|
secondSession = line.Substring("session 2: ".Length).Trim();
|
|
}
|
|
|
|
// If either is blank, we don't have multisession
|
|
if (string.IsNullOrEmpty(firstSession) || string.IsNullOrEmpty(secondSession))
|
|
return null;
|
|
|
|
// Create and return the formatted output
|
|
return $"Session 1: {firstSession}\nSession 2: {secondSession}";
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the existence of an anti-modchip string from the input file, if possible
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <returns>Anti-modchip existence if possible, false on error</returns>
|
|
private static bool? GetPlayStationAntiModchipDetected(string log)
|
|
{
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(log))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
// Check for the anti-modchip strings
|
|
using var sr = File.OpenText(log);
|
|
var line = sr.ReadLine()?.Trim();
|
|
while (!sr.EndOfStream)
|
|
{
|
|
if (line == null)
|
|
return false;
|
|
|
|
if (line.StartsWith("anti-modchip: no"))
|
|
return false;
|
|
else if (line.StartsWith("anti-modchip: yes"))
|
|
return true;
|
|
|
|
line = sr.ReadLine()?.Trim();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the detected missing EDC count from the input files, if possible
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <returns>Status of PS1 EDC, if possible</returns>
|
|
private static bool? GetPlayStationEDCStatus(string log)
|
|
{
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(log))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
// Check for the EDC strings
|
|
using var sr = File.OpenText(log);
|
|
var line = sr.ReadLine()?.Trim();
|
|
while (!sr.EndOfStream)
|
|
{
|
|
if (line == null)
|
|
return false;
|
|
|
|
if (line.Contains("EDC: no"))
|
|
return false;
|
|
else if (line.Contains("EDC: yes"))
|
|
return true;
|
|
|
|
line = sr.ReadLine()?.Trim();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the LibCrypt data from the input file, if possible
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <returns>PS1 LibCrypt data, if possible</returns>
|
|
private static string? GetPlayStationLibCryptData(string log)
|
|
{
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(log))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
// Fast forward to the LibCrypt line
|
|
using var sr = File.OpenText(log);
|
|
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("libcrypt:") == false) ;
|
|
if (sr.EndOfStream)
|
|
return null;
|
|
|
|
// Now that we're at the relevant entries, read each line in and concatenate
|
|
string? libCryptString = "", line = sr.ReadLine()?.Trim();
|
|
while (line?.StartsWith("MSF:") == true)
|
|
{
|
|
libCryptString += line + "\n";
|
|
line = sr.ReadLine()?.Trim();
|
|
}
|
|
|
|
return libCryptString.TrimEnd('\n');
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the existence of LibCrypt from the input file, if possible
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <returns>Status of PS1 LibCrypt, if possible</returns>
|
|
private static bool? GetPlayStationLibCryptStatus(string log)
|
|
{
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(log))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
// Check for the libcrypt strings
|
|
using var sr = File.OpenText(log);
|
|
var line = sr.ReadLine()?.Trim();
|
|
while (!sr.EndOfStream)
|
|
{
|
|
if (line == null)
|
|
return false;
|
|
|
|
if (line.StartsWith("libcrypt: no"))
|
|
return false;
|
|
else if (line.StartsWith("libcrypt: yes"))
|
|
return true;
|
|
|
|
line = sr.ReadLine()?.Trim();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
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;
|
|
|
|
try
|
|
{
|
|
// Fast forward to the PVD line
|
|
using var sr = File.OpenText(log);
|
|
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("PVD:") == false) ;
|
|
if (sr.EndOfStream)
|
|
return null;
|
|
|
|
// Now that we're at the relevant entries, read each line in and concatenate
|
|
string? pvdString = "", line = sr.ReadLine();
|
|
while (line?.StartsWith("03") == true)
|
|
{
|
|
pvdString += line + "\n";
|
|
line = sr.ReadLine();
|
|
}
|
|
|
|
return pvdString.TrimEnd('\n');
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the non-zero data 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;
|
|
|
|
try
|
|
{
|
|
// If we find the sample range, return the start value only
|
|
using var sr = File.OpenText(log);
|
|
while (!sr.EndOfStream)
|
|
{
|
|
string? line = sr.ReadLine()?.TrimStart();
|
|
if (line?.StartsWith("non-zero data sample range") == true)
|
|
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 build info from a Saturn disc, if possible
|
|
/// </summary>
|
|
/// <<param name="segaHeader">String representing a formatter variant of the Saturn header</param>
|
|
/// <returns>True on successful extraction of info, false otherwise</returns>
|
|
/// TODO: Remove when Redumper gets native reading support
|
|
private static bool GetSaturnBuildInfo(string? segaHeader, out string? serial, out string? version, out string? date)
|
|
{
|
|
serial = null; version = null; date = null;
|
|
|
|
// If the input header is null, we can't do a thing
|
|
if (string.IsNullOrEmpty(segaHeader))
|
|
return false;
|
|
|
|
// Now read it in cutting it into lines for easier parsing
|
|
try
|
|
{
|
|
string[] header = segaHeader!.Split('\n');
|
|
string serialVersionLine = header[2].Substring(58);
|
|
string dateLine = header[3].Substring(58);
|
|
serial = serialVersionLine.Substring(0, 10).Trim();
|
|
version = serialVersionLine.Substring(10, 6).TrimStart('V', 'v');
|
|
date = dateLine.Substring(0, 8);
|
|
date = $"{date[0]}{date[1]}{date[2]}{date[3]}-{date[4]}{date[5]}-{date[6]}{date[7]}";
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the error is
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the header from a Saturn, if possible
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <returns>Header as a byte array if possible, null on error</returns>
|
|
private static string? GetSaturnHeader(string log)
|
|
{
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(log))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
// Fast forward to the SS line
|
|
using var sr = File.OpenText(log);
|
|
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("SS [") == false) ;
|
|
if (sr.EndOfStream)
|
|
return null;
|
|
|
|
string? line, headerString = "";
|
|
while (!sr.EndOfStream)
|
|
{
|
|
line = sr.ReadLine()?.TrimStart();
|
|
if (line?.StartsWith("header:") == true)
|
|
{
|
|
line = sr.ReadLine()?.TrimStart();
|
|
while (line?.StartsWith("00") == true)
|
|
{
|
|
headerString += line + "\n";
|
|
line = sr.ReadLine()?.Trim();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return headerString.TrimEnd('\n');
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the header from a Saturn, if possible
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <returns>Header as a byte array if possible, null on error</returns>
|
|
private static string? GetSecuROMData(string log)
|
|
{
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(log))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
// Fast forward to the SecuROM line
|
|
using var sr = File.OpenText(log);
|
|
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("SecuROM [") == false) ;
|
|
if (sr.EndOfStream)
|
|
return null;
|
|
|
|
var lines = new List<string>();
|
|
while (!sr.EndOfStream)
|
|
{
|
|
var line = sr.ReadLine()?.TrimStart();
|
|
|
|
// Skip the "version"/"scheme" line
|
|
if (line?.StartsWith("version:") == true || line?.StartsWith("scheme:") == true)
|
|
continue;
|
|
|
|
// Only read until while there are MSF lines
|
|
if (line?.StartsWith("MSF:") != true)
|
|
break;
|
|
|
|
lines.Add(line);
|
|
}
|
|
|
|
return string.Join("\n", [.. lines]).TrimEnd('\n');
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the header from a Sega CD / Mega CD, if possible
|
|
/// </summary>
|
|
/// <param name="log">Log file location</param>
|
|
/// <returns>Header as a byte array if possible, null on error</returns>
|
|
private static string? GetSegaCDHeader(string log, out string? buildDate, out string? serial, out string? region)
|
|
{
|
|
// Set the default values
|
|
buildDate = null; serial = null; region = null;
|
|
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(log))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
// Fast forward to the MCD line
|
|
using var sr = File.OpenText(log);
|
|
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("MCD [") == false) ;
|
|
if (sr.EndOfStream)
|
|
return null;
|
|
|
|
string? line, headerString = string.Empty;
|
|
while (!sr.EndOfStream)
|
|
{
|
|
line = sr.ReadLine()?.TrimStart();
|
|
if (line == null)
|
|
break;
|
|
|
|
if (line.StartsWith("build date:"))
|
|
{
|
|
buildDate = line.Substring("build date: ".Length).Trim();
|
|
}
|
|
else if (line.StartsWith("serial:"))
|
|
{
|
|
serial = line.Substring("serial: ".Length).Trim();
|
|
}
|
|
else if (line.StartsWith("region:"))
|
|
{
|
|
region = line.Substring("region: ".Length).Trim();
|
|
}
|
|
else if (line.StartsWith("regions:"))
|
|
{
|
|
region = line.Substring("regions: ".Length).Trim();
|
|
}
|
|
else if (line.StartsWith("header:"))
|
|
{
|
|
line = sr.ReadLine()?.TrimStart();
|
|
while (line?.StartsWith("01") == true)
|
|
{
|
|
headerString += line + "\n";
|
|
line = sr.ReadLine()?.Trim();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return headerString.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;
|
|
|
|
try
|
|
{
|
|
// If we find the universal hash line, return the hash only
|
|
using var sr = File.OpenText(log);
|
|
while (!sr.EndOfStream)
|
|
{
|
|
string? line = sr.ReadLine()?.TrimStart();
|
|
if (line?.StartsWith("Universal Hash") == true)
|
|
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;
|
|
|
|
try
|
|
{
|
|
// If we find the disc write offset line, return the offset
|
|
using var sr = File.OpenText(log);
|
|
while (!sr.EndOfStream)
|
|
{
|
|
string? line = sr.ReadLine()?.TrimStart();
|
|
if (line?.StartsWith("disc write offset") == true)
|
|
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]
|
|
|
|
try
|
|
{
|
|
// Skip first line (dump date)
|
|
using var sr = File.OpenText(log);
|
|
sr.ReadLine();
|
|
|
|
// Get the next non-warning line
|
|
string nextLine = sr.ReadLine()?.Trim() ?? string.Empty;
|
|
if (nextLine.StartsWith("warning:", StringComparison.OrdinalIgnoreCase))
|
|
nextLine = sr.ReadLine()?.Trim() ?? string.Empty;
|
|
|
|
// Generate regex
|
|
// Permissive
|
|
var regex = new Regex(@"^redumper (v.+) \[.+\]", RegexOptions.Compiled);
|
|
// Strict
|
|
//var regex = new Regex(@"^redumper (v\d{4}\.\d{2}\.\d{2}(| build_\d+)) \[.+\]", RegexOptions.Compiled);
|
|
|
|
// Extract the version string
|
|
var match = regex.Match(nextLine);
|
|
var version = match.Groups[1].Value;
|
|
return string.IsNullOrEmpty(version) ? null : version;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|