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 { /// /// Represents a generic set of Redumper parameters /// public class Parameters : BaseParameters { #region Generic Dumping Information /// public override string? InputPath => DriveValue; /// public override string? OutputPath => Path.Combine(ImagePathValue?.Trim('"') ?? string.Empty, ImageNameValue?.Trim('"') ?? string.Empty) + GetDefaultExtension(this.Type); /// public override int? Speed => SpeedValue; #endregion #region Metadata /// public override InternalProgram InternalProgram => InternalProgram.Redumper; #endregion #region Flag Values /// /// List of all modes being run /// public List? ModeValues { get; set; } #region General /// /// Drive to use, first available drive with disc, if not provided /// public string? DriveValue { get; set; } /// /// Drive read speed, optimal drive speed will be used if not provided /// public int? SpeedValue { get; set; } /// /// Number of sector retries in case of SCSI/C2 error (default: 0) /// public int? RetriesValue { get; set; } /// /// Dump files base directory /// public string? ImagePathValue { get; set; } /// /// Dump files prefix, autogenerated in dump mode, if not provided /// public string? ImageNameValue { get; set; } #endregion #region Drive Configuration /// /// Override drive type, possible values: GENERIC, PLEXTOR, LG_ASUS /// public string? DriveTypeValue { get; set; } /// /// Override drive read offset /// public int? DriveReadOffsetValue { get; set; } /// /// Override drive C2 shift /// public int? DriveC2ShiftValue { get; set; } /// /// Override drive pre-gap start LBA /// public int? DrivePregapStartValue { get; set; } /// /// Override drive read method, possible values: BE, D8, BE_CDDA /// public string? DriveReadMethodValue { get; set; } /// /// Override drive sector order, possible values: DATA_C2_SUB, DATA_SUB_C2 /// public string? DriveSectorOrderValue { get; set; } #endregion #region Offset /// /// Override offset autodetection and use supplied value /// public int? ForceOffsetValue { get; set; } /// /// Maximum absolute sample value to treat it as silence (default: 32) /// public int? AudioSilenceThresholdValue { get; set; } #endregion #region Split /// /// Fill byte value for skipped sectors (default: 0x55) /// public byte? SkipFillValue { get; set; } #endregion #region Miscellaneous /// /// LBA to start dumping from /// public int? LBAStartValue { get; set; } /// /// LBA to stop dumping at (everything before the value), useful for discs with fake TOC /// public int? LBAEndValue { get; set; } /// /// LBA ranges of sectors to skip /// public string? SkipValue { get; set; } /// /// Write offset for dumps when reading as data /// public int? DumpWriteOffsetValue { get; set; } /// /// Number of sectors to read at once on initial dump, DVD only (Default 32) /// public int? DumpReadSizeValue { get; set; } /// /// Maximum number of lead-in retries per session (Default 4) /// public int? PlextorLeadinRetriesValue { get; set; } #endregion #endregion /// public Parameters(string? parameters) : base(parameters) { } /// public Parameters(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Options options) : base(system, type, drivePath, filename, driveSpeed, options) { } #region BaseParameters Implementations /// public override (bool, List) CheckAllOutputFilesExist(string basePath, bool preCheck) { var missingFiles = new List(); 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); } /// 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; } } /// /// /// 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 /// public override string GenerateParameters() { var parameters = new List(); 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]); } /// /// Command support is irrelevant for redumper public override Dictionary> GetCommandSupport() { return new Dictionary>() { [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, ], }; } /// public override string? GetDefaultExtension(MediaType? mediaType) => Converters.Extension(mediaType); /// public override List GetDeleteableFilePaths(string basePath) { var deleteableFiles = new List(); if (File.Exists($"{basePath}.scram")) deleteableFiles.Add($"{basePath}.scram"); if (File.Exists($"{basePath}.scrap")) deleteableFiles.Add($"{basePath}.scrap"); return deleteableFiles; } /// public override List GetLogFilePaths(string basePath) { var logFiles = new List(); 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; } /// 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; } /// 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; } /// 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; } /// 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 parts = Regex.Matches(parameters, @"([a-zA-Z\-]*=)?[\""].+?[\""]|[^ ]+", RegexOptions.Compiled) .Cast() .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 /// /// Get the cuesheet from the input file, if possible /// /// Log file location /// Newline-delimited cuesheet if possible, null on error 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; } } /// /// Get the datfile from the input file, if possible /// /// Log file location /// Newline-delimited datfile if possible, null on error 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(" /// Get reported disc type information, if possible /// /// Log file location /// True if disc type info was set, false otherwise 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: 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; } } /// /// Get all Volume Identifiers /// /// Log file location /// Volume labels (by type), or null if none present private static bool GetVolumeLabels(string log, out Dictionary> 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; } } /// /// Get the DVD protection information, if possible /// /// Log file location /// Indicates whether region and protection type are always included /// Formatted string representing the DVD protection, null on error 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 == "") 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 == "") normalizedKey = "No Title Key"; else if (normalizedKey == "") 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; } /// /// Get the detected error counts from the input files, if possible /// /// Log file location /// True if error counts could be retrieved, false otherwise 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: if (line.StartsWith("C2:")) { string[] parts = line.Split(' '); if (!long.TryParse(parts[1], out c2Errors)) c2Errors = -1; } // REDUMP.ORG errors: 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; } } /// /// Get the EXE Date from the log, if possible /// /// Log file location /// EXE date if possible, null otherwise 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; } } /// /// Get hardware information from the input file, if possible /// /// Log file location /// True if hardware info was set, false otherwise 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: - (revision level: , 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 == "" ? "" : $" ({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; } } /// /// Get the layerbreaks from the input file, if possible /// /// Log file location /// True if any layerbreaks were found, false otherwise 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: .. , length: , hLBA: .. } 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: 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): layerbreak1 = line.Substring("layer break (layer: 0): ".Length).Trim(); } else if (line.StartsWith("layer break (layer: 1):")) { // layer break (layer: 1): layerbreak2 = line.Substring("layer break (layer: 1): ".Length).Trim(); } else if (line.StartsWith("layer break (layer: 2):")) { // layer break (layer: 2): 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; } } /// /// Get multisession information from the input file, if possible /// /// Log file location /// Formatted multisession information, null on error 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; } } /// /// Get the existence of an anti-modchip string from the input file, if possible /// /// Log file location /// Anti-modchip existence if possible, false on error 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; } } /// /// Get the detected missing EDC count from the input files, if possible /// /// Log file location /// Status of PS1 EDC, if possible 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; } } /// /// Get the LibCrypt data from the input file, if possible /// /// Log file location /// PS1 LibCrypt data, if possible 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; } } /// /// Get the existence of LibCrypt from the input file, if possible /// /// Log file location /// Status of PS1 LibCrypt, if possible 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; } } /// /// Get the PVD from the input file, if possible /// /// Log file location /// Newline-delimited PVD if possible, null on error 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; } } /// /// Get the non-zero data start from the input file, if possible /// /// Log file location /// Non-zero dta start if possible, null on error 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; } } /// /// Get the build info from a Saturn disc, if possible /// /// <String representing a formatter variant of the Saturn header /// True on successful extraction of info, false otherwise /// 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; } } /// /// Get the header from a Saturn, if possible /// /// Log file location /// Header as a byte array if possible, null on error 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; } } /// /// Get the header from a Saturn, if possible /// /// Log file location /// Header as a byte array if possible, null on error 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(); 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; } } /// /// Get the header from a Sega CD / Mega CD, if possible /// /// Log file location /// Header as a byte array if possible, null on error 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; } } /// /// Get the universal hash from the input file, if possible /// /// Log file location /// Universal hash if possible, null on error 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; } } /// /// Get the write offset from the input file, if possible /// /// Log file location /// Sample write offset if possible, null on error 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; } } /// /// Get the version. if possible /// /// Log file location /// Version if possible, null on error 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 } }