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 MPF.Core.Utilities; using SabreTools.RedumpLib.Data; namespace MPF.Modules.Redumper { /// /// Represents a generic set of Redumper parameters /// /// /// TODO: Redumper natively supports multiple, chained commands but MPF /// doesn't have an easy way of dealing with that right now. Maybe have /// BaseCommand be a space-deliminated list of the commands? /// TODO: With chained commands, how do we tell what parameters are /// actually supported? Do we go one by one and if it doesn't match /// any, then it's incorrect? /// 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; } /// /// 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, char driveLetter, string filename, int? driveSpeed, Options options) : base(system, type, driveLetter, filename, driveSpeed, options) { } #region BaseParameters Implementations /// public override (bool, List) CheckAllOutputFilesExist(string basePath, bool preCheck) { List 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}.dat"); if (!File.Exists($"{basePath}.state")) missingFiles.Add($"{basePath}.state"); if (!File.Exists($"{basePath}.subcode")) missingFiles.Add($"{basePath}.subcode"); if (!File.Exists($"{basePath}.toc")) missingFiles.Add($"{basePath}.toc"); } // Removed or inconsistent files if (false) { // Depends on the disc if (!File.Exists($"{basePath}.cdtext")) missingFiles.Add($"{basePath}.cdtext"); } break; 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}.physical") && !File.Exists($"{basePath}.1.physical") && !File.Exists($"{basePath}.2.physical")) missingFiles.Add($"{basePath}.physical"); if (!File.Exists($"{basePath}.state")) missingFiles.Add($"{basePath}.state"); } 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) { // Get the dumping program and version info.DumpingInfo.DumpingProgram = $"{EnumConverter.LongName(this.InternalProgram)} {GetVersion($"{basePath}.log") ?? "Unknown Version"}"; info.DumpingInfo.DumpingDate = GetFileModifiedDate($"{basePath}.log")?.ToString("yyyy-MM-dd HH:mm:ss"); // Fill in the hardware data if (GetHardwareInfo($"{basePath}.log", out string manufacturer, out string model, out string firmware)) { info.DumpingInfo.Manufacturer = manufacturer; info.DumpingInfo.Model = model; info.DumpingInfo.Firmware = firmware; } 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 long errorCount = GetErrorCount($"{basePath}.log"); info.CommonDiscInfo.ErrorsCount = (errorCount == -1 ? "Error retrieving error count" : errorCount.ToString()); // Attempt to get multisession data string cdMultiSessionInfo = GetMultisessionInformation($"{basePath}.log") ?? string.Empty; 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: 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 (GetISOHashValues(info.TracksAndWriteOffsets.ClrMameProData, out long size, out string crc32, out string md5, out string sha1)) { info.SizeAndChecksums.Size = size; info.SizeAndChecksums.CRC32 = crc32; info.SizeAndChecksums.MD5 = md5; info.SizeAndChecksums.SHA1 = sha1; } string layerbreak = GetLayerbreak($"{basePath}.log") ?? string.Empty; info.SizeAndChecksums.Layerbreak = !string.IsNullOrEmpty(layerbreak) ? Int64.Parse(layerbreak) : default; break; } switch (this.System) { // case RedumpSystem.AppleMacintosh: case RedumpSystem.EnhancedCD: case RedumpSystem.IBMPCcompatible: case RedumpSystem.RainbowDisc: case RedumpSystem.SonyElectronicBook: // TODO: Support SecuROM data when generated break; case RedumpSystem.DVDAudio: case RedumpSystem.DVDVideo: info.CopyProtection.Protection = GetDVDProtection($"{basePath}.log") ?? string.Empty; break; case RedumpSystem.KonamiPython2: if (GetPlayStationExecutableInfo(drive?.Letter, out string pythonTwoSerial, out Region? pythonTwoRegion, out string pythonTwoDate)) { // Ensure internal serial is pulled from local data info.CommonDiscInfo.CommentsSpecialFields[SiteCode.InternalSerialName] = pythonTwoSerial ?? string.Empty; info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? pythonTwoRegion; info.CommonDiscInfo.EXEDateBuildDate = pythonTwoDate; } info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? 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 string scdBuildDate, out string scdSerial, out string scdRegion) ?? 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)); if (GetSaturnBuildInfo(info.Extras.Header, out string saturnSerial, out string saturnVersion, out string 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: if (GetPlayStationExecutableInfo(drive?.Letter, out string playstationSerial, out Region? playstationRegion, out string playstationDate)) { // Ensure internal serial is pulled from local data info.CommonDiscInfo.CommentsSpecialFields[SiteCode.InternalSerialName] = playstationSerial ?? string.Empty; info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationRegion; info.CommonDiscInfo.EXEDateBuildDate = playstationDate; } 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: if (GetPlayStationExecutableInfo(drive?.Letter, out string playstationTwoSerial, out Region? playstationTwoRegion, out string playstationTwoDate)) { // Ensure internal serial is pulled from local data info.CommonDiscInfo.CommentsSpecialFields[SiteCode.InternalSerialName] = playstationTwoSerial ?? string.Empty; info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationTwoRegion; info.CommonDiscInfo.EXEDateBuildDate = playstationTwoDate; } info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? string.Empty; break; case RedumpSystem.SonyPlayStation3: info.VersionAndEditions.Version = GetPlayStation3Version(drive?.Letter) ?? string.Empty; info.CommonDiscInfo.CommentsSpecialFields[SiteCode.InternalSerialName] = GetPlayStation3Serial(drive?.Letter) ?? string.Empty; break; case RedumpSystem.SonyPlayStation4: info.VersionAndEditions.Version = GetPlayStation4Version(drive?.Letter) ?? string.Empty; info.CommonDiscInfo.CommentsSpecialFields[SiteCode.InternalSerialName] = GetPlayStation4Serial(drive?.Letter) ?? string.Empty; break; case RedumpSystem.SonyPlayStation5: info.VersionAndEditions.Version = GetPlayStation5Version(drive?.Letter) ?? string.Empty; info.CommonDiscInfo.CommentsSpecialFields[SiteCode.InternalSerialName] = GetPlayStation5Serial(drive?.Letter) ?? string.Empty; break; } // Fill in any artifacts that exist, Base64-encoded, if we need to if (includeArtifacts) { if (File.Exists($"{basePath}.cdtext")) info.Artifacts["cdtext"] = GetBase64(GetFullFile($"{basePath}.cdtext")); if (File.Exists($"{basePath}.cue")) info.Artifacts["cue"] = GetBase64(GetFullFile($"{basePath}.cue")); if (File.Exists($"{basePath}.fulltoc")) info.Artifacts["fulltoc"] = GetBase64(GetFullFile($"{basePath}.fulltoc")); if (File.Exists($"{basePath}.log")) info.Artifacts["log"] = GetBase64(GetFullFile($"{basePath}.log")); if (File.Exists($"{basePath}.manufacturer")) info.Artifacts["manufacturer"] = GetBase64(GetFullFile($"{basePath}.manufacturer")); if (File.Exists($"{basePath}.1.manufacturer")) info.Artifacts["manufacturer1"] = GetBase64(GetFullFile($"{basePath}.1.manufacturer")); if (File.Exists($"{basePath}.2.manufacturer")) info.Artifacts["manufacturer2"] = GetBase64(GetFullFile($"{basePath}.2.manufacturer")); if (File.Exists($"{basePath}.physical")) info.Artifacts["physical"] = GetBase64(GetFullFile($"{basePath}.physical")); if (File.Exists($"{basePath}.1.physical")) info.Artifacts["physical1"] = GetBase64(GetFullFile($"{basePath}.1.physical")); if (File.Exists($"{basePath}.2.physical")) info.Artifacts["physical2"] = GetBase64(GetFullFile($"{basePath}.2.physical")); // if (File.Exists($"{basePath}.scram")) // info.Artifacts["scram"] = GetBase64(GetFullFile($"{basePath}.scram")); // if (File.Exists($"{basePath}.scrap")) // info.Artifacts["scrap"] = GetBase64(GetFullFile($"{basePath}.scrap")); if (File.Exists($"{basePath}.state")) info.Artifacts["state"] = GetBase64(GetFullFile($"{basePath}.state")); if (File.Exists($"{basePath}.subcode")) info.Artifacts["subcode"] = GetBase64(GetFullFile($"{basePath}.subcode")); if (File.Exists($"{basePath}.toc")) info.Artifacts["toc"] = GetBase64(GetFullFile($"{basePath}.toc")); } } /// /// /// 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() { List parameters = new List(); if (ModeValues == null) ModeValues = new List { CommandStrings.NONE }; // Modes parameters.AddRange(ModeValues); #region General // Help if (this[FlagStrings.HelpLong] == true) parameters.Add(FlagStrings.HelpLong); // Verbose if (this[FlagStrings.Verbose] == true) parameters.Add(FlagStrings.Verbose); // 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.PlextorLeadinSkip] == true) parameters.Add(FlagStrings.PlextorLeadinSkip); // 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.IsNullOrWhiteSpace(SkipValue)) parameters.Add($"{FlagStrings.Skip}={SkipValue}"); } // Dump Read Size if (this[FlagStrings.DumpReadSize] == true) { if (DumpReadSizeValue != null && DumpReadSizeValue > 0) parameters.Add($"{FlagStrings.DumpReadSize}={DumpReadSizeValue}"); } #endregion return string.Join(" ", parameters); } /// /// Command support is irrelevant for redumper public override Dictionary> GetCommandSupport() { return new Dictionary>() { [CommandStrings.NONE] = new List { // General FlagStrings.HelpLong, FlagStrings.HelpShort, 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.PlextorLeadinSkip, 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.DumpReadSize, }, }; } /// public override string GetDefaultExtension(MediaType? mediaType) => Converters.Extension(mediaType); /// public override List GetLogFilePaths(string basePath) { List 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}.scram")) // logFiles.Add($"{basePath}.scram"); // if (File.Exists($"{basePath}.scrap")) // logFiles.Add($"{basePath}.scrap"); if (File.Exists($"{basePath}.state")) logFiles.Add($"{basePath}.state"); if (File.Exists($"{basePath}.subcode")) logFiles.Add($"{basePath}.subcode"); if (File.Exists($"{basePath}.toc")) logFiles.Add($"{basePath}.toc"); break; case MediaType.DVD: 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}.1.physical")) logFiles.Add($"{basePath}.1.physical"); if (File.Exists($"{basePath}.2.physical")) logFiles.Add($"{basePath}.2.physical"); 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) || this.BaseCommand.Contains(CommandStrings.DVD) || this.BaseCommand.Contains(CommandStrings.BluRay) || this.BaseCommand.Contains(CommandStrings.SACD) || this.BaseCommand.Contains(CommandStrings.Dump); } /// protected override void ResetValues() { BaseCommand = CommandStrings.NONE; flags = new Dictionary(); // 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(char driveLetter, string filename, int? driveSpeed, Options options) { // If we don't have a CD or DVD, we can't dump using redumper if (this.Type != MediaType.CDROM && this.Type != MediaType.DVD) return; BaseCommand = CommandStrings.NONE; switch (this.Type) { case MediaType.CDROM: switch (this.System) { case RedumpSystem.SuperAudioCD: ModeValues = new List { CommandStrings.SACD }; break; default: ModeValues = new List { CommandStrings.CD }; break; } break; case MediaType.DVD: ModeValues = new List { CommandStrings.DVD }; break; case MediaType.BluRay: ModeValues = new List { CommandStrings.BluRay }; break; default: BaseCommand = null; return; } this[FlagStrings.Drive] = true; DriveValue = driveLetter.ToString(); 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; // Set the output paths if (!string.IsNullOrWhiteSpace(filename)) { string imagePath = Path.GetDirectoryName(filename); if (!string.IsNullOrWhiteSpace(imagePath)) { this[FlagStrings.ImagePath] = true; ImagePathValue = $"\"{imagePath}\""; } string imageName = Path.GetFileNameWithoutExtension(filename); if (!string.IsNullOrWhiteSpace(imageName)) { this[FlagStrings.ImageName] = true; ImageNameValue = $"\"{imageName}\""; } } this[FlagStrings.Retries] = true; RetriesValue = options.RedumperRereadCount; } /// protected override bool ValidateAndSetParameters(string parameters) { BaseCommand = CommandStrings.NONE; // The string has to be valid by itself first if (string.IsNullOrWhiteSpace(parameters)) return false; // Now split the string into parts for easier validation // https://stackoverflow.com/questions/14655023/split-a-string-that-has-white-spaces-unless-they-are-enclosed-within-quotes parameters = parameters.Trim(); List parts = Regex.Matches(parameters, @"([a-zA-Z\-]*=)?[\""].+?[\""]|[^ ]+") .Cast() .Select(m => m.Value) .ToList(); // Setup the modes ModeValues = new List(); // 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.Dump: case CommandStrings.Protection: case CommandStrings.Refine: case CommandStrings.Split: case CommandStrings.Verify: case CommandStrings.DVDKey: case CommandStrings.DVDIsoKey: case CommandStrings.Info: ModeValues.Add(part); break; // Default is either a flag or an invalid mode default: if (part.StartsWith("-")) { isFlag = true; break; } else { return false; } } // If we had a flag, break out if (isFlag) break; } // Loop through all auxiliary flags, if necessary for (int i = index; i < parts.Count; i++) { // Flag read-out values byte? byteValue = null; int? intValue = null; string stringValue = null; #region General // Help ProcessFlagParameter(parts, FlagStrings.HelpShort, FlagStrings.HelpLong, ref i); // Verbose ProcessFlagParameter(parts, FlagStrings.Verbose, ref i); // Debug ProcessFlagParameter(parts, FlagStrings.Debug, ref i); // Drive stringValue = ProcessStringParameter(parts, FlagStrings.Drive, ref i); if (!string.IsNullOrWhiteSpace(stringValue)) DriveValue = stringValue; // Speed intValue = ProcessInt32Parameter(parts, FlagStrings.Speed, ref i); if (intValue != null && intValue != Int32.MinValue) SpeedValue = intValue; // Retries intValue = ProcessInt32Parameter(parts, FlagStrings.Retries, ref i); if (intValue != null && intValue != Int32.MinValue) RetriesValue = intValue; // Image Path stringValue = ProcessStringParameter(parts, FlagStrings.ImagePath, ref i); if (!string.IsNullOrWhiteSpace(stringValue)) ImagePathValue = $"\"{stringValue.Trim('"')}\""; // Image Name stringValue = ProcessStringParameter(parts, FlagStrings.ImageName, ref i); if (!string.IsNullOrWhiteSpace(stringValue)) ImageNameValue = $"\"{stringValue.Trim('"')}\""; // Overwrite ProcessFlagParameter(parts, FlagStrings.Overwrite, ref i); #endregion #region Drive Configuration // Drive Type stringValue = ProcessStringParameter(parts, FlagStrings.DriveType, ref i); if (!string.IsNullOrWhiteSpace(stringValue)) DriveTypeValue = stringValue; // Drive Read Offset intValue = ProcessInt32Parameter(parts, FlagStrings.DriveReadOffset, ref i); if (intValue != null && intValue != Int32.MinValue) DriveReadOffsetValue = intValue; // Drive C2 Shift intValue = ProcessInt32Parameter(parts, FlagStrings.DriveC2Shift, ref i); if (intValue != null && intValue != Int32.MinValue) DriveC2ShiftValue = intValue; // Drive Pregap Start intValue = ProcessInt32Parameter(parts, FlagStrings.DrivePregapStart, ref i); if (intValue != null && intValue != Int32.MinValue) DrivePregapStartValue = intValue; // Drive Read Method stringValue = ProcessStringParameter(parts, FlagStrings.DriveReadMethod, ref i); if (!string.IsNullOrWhiteSpace(stringValue)) DriveReadMethodValue = stringValue; // Drive Sector Order stringValue = ProcessStringParameter(parts, FlagStrings.DriveSectorOrder, ref i); if (!string.IsNullOrWhiteSpace(stringValue)) DriveSectorOrderValue = stringValue; #endregion #region Drive Specific // Plextor Leadin Skip ProcessFlagParameter(parts, FlagStrings.PlextorLeadinSkip, 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.IsNullOrWhiteSpace(stringValue)) SkipValue = stringValue; // Skip intValue = ProcessInt32Parameter(parts, FlagStrings.DumpReadSize, ref i); if (!string.IsNullOrWhiteSpace(stringValue)) DumpReadSizeValue = intValue; #endregion } // If the image name was not set, set it with a default value if (string.IsNullOrWhiteSpace(this.ImageNameValue)) this.ImageNameValue = "track"; return true; } #endregion #region Information Extraction Methods /// /// 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; using (StreamReader sr = File.OpenText(log)) { try { // Fast forward to the dat line while (!sr.EndOfStream && !sr.ReadLine().TrimStart().StartsWith("CUE [")) ; if (sr.EndOfStream) return null; // Now that we're at the relevant entries, read each line in and concatenate string cueString = "", line = sr.ReadLine().Trim(); while (!string.IsNullOrWhiteSpace(line)) { cueString += line + "\n"; line = sr.ReadLine().Trim(); } return cueString.TrimEnd('\n'); } catch { // We don't care what the exception is right now return null; } } } /// /// 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; using (StreamReader sr = File.OpenText(log)) { try { // Fast forward to the dat line while (!sr.EndOfStream && !sr.ReadLine().TrimStart().StartsWith("dat:")) ; if (sr.EndOfStream) return null; // Now that we're at the relevant entries, read each line in and concatenate string datString = "", line = sr.ReadLine().Trim(); while (line.StartsWith(" /// Get the DVD protection information, if possible /// /// Log file location /// Formatted string representing the DVD protection, null on error private static string GetDVDProtection(string log) { // 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 (StreamReader sr = File.OpenText(log)) { try { // Fast forward to the copyright information while (!sr.ReadLine().Trim().StartsWith("copyright:")) ; // Now read until we hit the manufacturing information string 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.IsNullOrWhiteSpace(line)) { var match = Regex.Match(line, @"^(.*?): (.*?)$"); 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 { } } // 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 count from the input files, if possible /// /// Log file location /// Error count if possible, -1 on error public static long GetErrorCount(string log) { // If the file doesn't exist, we can't get info from it if (!File.Exists(log)) return -1; using (StreamReader sr = File.OpenText(log)) { try { // Fast forward to the errors lines while (!sr.EndOfStream && !sr.ReadLine().Trim().StartsWith("CD-ROM [")) ; if (sr.EndOfStream) return 0; // Now that we're at the relevant lines, find the error count while (!sr.EndOfStream) { // Skip forward to the "REDUMP.ORG" line string line = string.Empty; while (!sr.EndOfStream && !(line = sr.ReadLine().Trim()).StartsWith("REDUMP.ORG errors")) ; if (line == string.Empty) break; // REDUMP.ORG errors: string[] parts = line.Split(' '); if (long.TryParse(parts[2], out long redump)) return redump; else return -1; } return -1; } catch { // We don't care what the exception is right now return -1; } } } /// /// Get the layerbreak from the input file, if possible /// /// Log file location /// Layerbreak if possible, null on error private static string GetLayerbreak(string log) { // If the file doesn't exist, we can't get info from it if (!File.Exists(log)) return null; using (StreamReader sr = File.OpenText(log)) { try { // Fast forward to the disc structure lines while (!sr.EndOfStream && !sr.ReadLine().Trim().StartsWith("layer 0")) ; if (sr.EndOfStream) return null; // Now that we're at the relevant lines, find the layerbreak string layerbreak = null; while (!sr.EndOfStream) { string 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 null; } // Dual-layer discs have a regular layerbreak (new) else if (line.StartsWith("layer break:")) { // layer break: layerbreak = line.Substring("layer break: ".Length).Trim(); } // 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(); layerbreak = layerbreak == null ? split[7].TrimEnd(',') : layerbreak; } } // Return the layerbreak, if possible return layerbreak; } catch { // We don't care what the exception is right now return null; } } } /// /// 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; using (StreamReader sr = File.OpenText(log)) { try { // Fast forward to the multisession lines while (!sr.EndOfStream && !sr.ReadLine().Trim().StartsWith("multisession:")) ; 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) { string 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; using (StreamReader sr = File.OpenText(log)) { try { // Check for the anti-modchip strings string 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; using (StreamReader sr = File.OpenText(log)) { try { // Check for the EDC strings string line = sr.ReadLine().Trim(); while (!sr.EndOfStream) { if (line == null) return false; if (line.StartsWith("EDC: no")) return false; else if (line.StartsWith("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; using (StreamReader sr = File.OpenText(log)) { try { // Fast forward to the LibCrypt line while (!sr.EndOfStream && !sr.ReadLine().TrimStart().StartsWith("libcrypt:")) ; 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:")) { 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; using (StreamReader sr = File.OpenText(log)) { try { // Check for the libcrypt strings string 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; using (StreamReader sr = File.OpenText(log)) { try { // Fast forward to the PVD line while (!sr.EndOfStream && !sr.ReadLine().TrimStart().StartsWith("PVD:")) ; if (sr.EndOfStream) return null; // Now that we're at the relevant entries, read each line in and concatenate string pvdString = "", line = sr.ReadLine().Trim(); while (line.StartsWith("03")) { pvdString += line + "\n"; line = sr.ReadLine().Trim(); } return pvdString.TrimEnd('\n'); } catch { // We don't care what the exception is right now return null; } } } /// /// 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; using (StreamReader sr = File.OpenText(log)) { try { // If we find the sample range, return the start value only string line; while (!sr.EndOfStream) { line = sr.ReadLine().TrimStart(); if (line.StartsWith("non-zero data sample range")) return line.Substring("non-zero data sample range: [".Length).Trim().Split(' ')[0]; } // We couldn't detect it then return null; } catch { // We don't care what the exception is right now return null; } } } /// /// 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 Redumpe 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.IsNullOrWhiteSpace(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; using (StreamReader sr = File.OpenText(log)) { try { // Fast forward to the SS line while (!sr.EndOfStream && !sr.ReadLine().TrimStart().StartsWith("SS [")) ; if (sr.EndOfStream) return null; string line, headerString = ""; while (!sr.EndOfStream) { line = sr.ReadLine().TrimStart(); if (line.StartsWith("header:")) { line = sr.ReadLine().TrimStart(); while (line.StartsWith("00")) { 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 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; using (StreamReader sr = File.OpenText(log)) { try { // Fast forward to the MCD line while (!sr.EndOfStream && !sr.ReadLine().TrimStart().StartsWith("MCD [")) ; if (sr.EndOfStream) return null; string line, headerString = ""; while (!sr.EndOfStream) { line = sr.ReadLine().TrimStart(); 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")) { 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; using (StreamReader sr = File.OpenText(log)) { try { // If we find the universal hash line, return the hash only string line; while (!sr.EndOfStream) { line = sr.ReadLine().TrimStart(); if (line.StartsWith("Universal Hash")) return line.Substring("Universal Hash (SHA-1): ".Length).Trim(); } // We couldn't detect it then return null; } catch { // We don't care what the exception is right now return null; } } } /// /// 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; using (StreamReader sr = File.OpenText(log)) { try { // If we find the disc write offset line, return the offset string line; while (!sr.EndOfStream) { line = sr.ReadLine().TrimStart(); if (line.StartsWith("disc write offset")) return line.Substring("disc write offset: ".Length).Trim(); } // We couldn't detect it then return null; } catch { // We don't care what the exception is right now return null; } } } /// /// 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] using (StreamReader sr = File.OpenText(log)) { try { // Skip first line (dump date) sr.ReadLine(); // Generate regex // Permissive var regex = new Regex(@"^redumper (v.+) \[.+\]"); // Strict //var regex = new Regex(@"^redumper (v\d{4}\.\d{2}\.\d{2}(| build_\d+)) \[.+\]"); // Extract the version string var match = regex.Match(sr.ReadLine().Trim()); var version = match.Groups[1].Value; return string.IsNullOrWhiteSpace(version) ? null : version; } catch { // We don't care what the exception is right now return null; } } } /// /// 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; using (StreamReader sr = File.OpenText(log)) { try { // Fast forward to the drive information line 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: (.+)\)"); 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; } } } #endregion } }