Files
MPF/MPF.Core/Processors/Redumper.cs
2024-05-21 13:14:15 -04:00

1700 lines
73 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using MPF.Core.Converters;
using MPF.Core.Data;
using SabreTools.Models.CueSheets;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Processors
{
/// <summary>
/// Represents processing Redumper outputs
/// </summary>
public sealed class Redumper : BaseProcessor
{
/// <inheritdoc/>
public Redumper(RedumpSystem? system, MediaType? type) : base(system, type) { }
#region BaseProcessor Implementations
/// <inheritdoc/>
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
{
var missingFiles = new List<string>();
switch (Type)
{
case MediaType.CDROM:
if (!File.Exists($"{basePath}.cue"))
missingFiles.Add($"{basePath}.cue");
if (!File.Exists($"{basePath}.scram") && !File.Exists($"{basePath}.scrap"))
missingFiles.Add($"{basePath}.scram");
if (!File.Exists($"{basePath}_logs.zip") || !preCheck)
{
if (!File.Exists($"{basePath}.fulltoc"))
missingFiles.Add($"{basePath}.fulltoc");
if (!File.Exists($"{basePath}.log"))
missingFiles.Add($"{basePath}.log");
else if (GetDatfile($"{basePath}.log") == null)
missingFiles.Add($"{basePath}.log (dat section)");
if (!File.Exists($"{basePath}.state"))
missingFiles.Add($"{basePath}.state");
if (!File.Exists($"{basePath}.subcode"))
missingFiles.Add($"{basePath}.subcode");
if (!File.Exists($"{basePath}.toc"))
missingFiles.Add($"{basePath}.toc");
}
// Removed or inconsistent files
//{
// // Depends on the disc
// if (!File.Exists($"{basePath}.cdtext"))
// missingFiles.Add($"{basePath}.cdtext");
//
// // Not available in all versions
// if (!File.Exists($"{basePath}.hash"))
// missingFiles.Add($"{basePath}.hash");
// // Also: "{basePath} (Track X).hash" (get from cuesheet)
// if (!File.Exists($"{basePath}.skeleton"))
// missingFiles.Add($"{basePath}.skeleton");
// // Also: "{basePath} (Track X).skeleton" (get from cuesheet)
//}
break;
case MediaType.DVD:
if (!File.Exists($"{basePath}_logs.zip") || !preCheck)
{
if (!File.Exists($"{basePath}.log"))
missingFiles.Add($"{basePath}.log");
else if (GetDatfile($"{basePath}.log") == null)
missingFiles.Add($"{basePath}.dat");
if (!File.Exists($"{basePath}.manufacturer") && !File.Exists($"{basePath}.1.manufacturer") && !File.Exists($"{basePath}.2.manufacturer"))
missingFiles.Add($"{basePath}.manufacturer");
if (!File.Exists($"{basePath}.physical") && !File.Exists($"{basePath}.0.physical") && !File.Exists($"{basePath}.1.physical") && !File.Exists($"{basePath}.2.physical"))
missingFiles.Add($"{basePath}.physical");
if (!File.Exists($"{basePath}.state"))
missingFiles.Add($"{basePath}.state");
}
// Removed or inconsistent files
//{
// // Not available in all versions
// if (!File.Exists($"{basePath}.hash"))
// missingFiles.Add($"{basePath}.hash");
// if (!File.Exists($"{basePath}.skeleton"))
// missingFiles.Add($"{basePath}.skeleton");
//}
break;
case MediaType.HDDVD: // TODO: Verify that this is output
case MediaType.BluRay:
if (!File.Exists($"{basePath}_logs.zip") || !preCheck)
{
if (!File.Exists($"{basePath}.log"))
missingFiles.Add($"{basePath}.log");
else if (GetDatfile($"{basePath}.log") == null)
missingFiles.Add($"{basePath}.dat");
if (!File.Exists($"{basePath}.physical") && !File.Exists($"{basePath}.0.physical") && !File.Exists($"{basePath}.1.physical") && !File.Exists($"{basePath}.2.physical"))
missingFiles.Add($"{basePath}.physical");
if (!File.Exists($"{basePath}.state"))
missingFiles.Add($"{basePath}.state");
}
// Removed or inconsistent files
//{
// // Not available in all versions
// if (!File.Exists($"{basePath}.hash"))
// missingFiles.Add($"{basePath}.hash");
// if (!File.Exists($"{basePath}.skeleton"))
// missingFiles.Add($"{basePath}.skeleton");
//}
break;
default:
missingFiles.Add("Media and system combination not supported for Redumper");
break;
}
return (!missingFiles.Any(), missingFiles);
}
/// <inheritdoc/>
public override void GenerateSubmissionInfo(SubmissionInfo info, Options options, string basePath, Drive? drive, bool includeArtifacts)
{
// Ensure that required sections exist
info = Builder.EnsureAllSections(info);
// Get the dumping program and version
info.DumpingInfo!.DumpingProgram = $"{EnumConverter.LongName(InternalProgram.Redumper)} {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 (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 (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 (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 (Type == MediaType.BluRay)
{
int trimLength = -1;
switch (System)
{
case RedumpSystem.MicrosoftXboxOne:
case RedumpSystem.MicrosoftXboxSeriesXS:
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 (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:
// Get metadata from log if possible
if (GetPlayStationInfo($"{basePath}.log", out string? kp2EXEDate, out string? kp2Serial, out string? kp2Version, out var _))
{
info.CommonDiscInfo!.EXEDateBuildDate = kp2EXEDate;
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = kp2Serial ?? string.Empty;
if (!string.IsNullOrEmpty(kp2Serial))
info.CommonDiscInfo.Region = InfoTool.GetPlayStationRegion(info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName]);
info.VersionAndEditions!.Version = kp2Version ?? string.Empty;
}
// Get metadata from drive if not available from log
else if (InfoTool.GetPlayStationExecutableInfo(drive?.Name, out var pythonTwoSerial, out Region? pythonTwoRegion, out var pythonTwoDate))
{
info.CommonDiscInfo!.EXEDateBuildDate = pythonTwoDate;
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = pythonTwoSerial ?? string.Empty;
info.CommonDiscInfo.Region ??= pythonTwoRegion;
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:
// Get metadata from log if possible
if (GetPlayStationInfo($"{basePath}.log", out string? psxEXEDate, out string? psxSerial, out var _, out var _))
{
info.CommonDiscInfo!.EXEDateBuildDate = psxEXEDate;
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = psxSerial ?? string.Empty;
if (!string.IsNullOrEmpty(psxSerial))
info.CommonDiscInfo.Region = InfoTool.GetPlayStationRegion(info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName]);
}
// Get metadata from drive if not available from log
else if (InfoTool.GetPlayStationExecutableInfo(drive?.Name, out var playstationSerial, out Region? playstationRegion, out var playstationDate))
{
info.CommonDiscInfo!.EXEDateBuildDate = playstationDate;
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = playstationSerial ?? string.Empty;
info.CommonDiscInfo.Region ??= playstationRegion;
}
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:
// Get metadata from log if possible
if (GetPlayStationInfo($"{basePath}.log", out string? ps2EXEDate, out string? ps2Serial, out var ps2Version, out var _))
{
info.CommonDiscInfo!.EXEDateBuildDate = ps2EXEDate;
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = ps2Serial ?? string.Empty;
if (!string.IsNullOrEmpty(ps2Serial))
info.CommonDiscInfo.Region = InfoTool.GetPlayStationRegion(info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName]);
info.VersionAndEditions!.Version = ps2Version ?? string.Empty;
}
// Get metadata from drive if not available from log
else if (InfoTool.GetPlayStationExecutableInfo(drive?.Name, out var playstationTwoSerial, out Region? playstationTwoRegion, out var playstationTwoDate))
{
info.CommonDiscInfo!.EXEDateBuildDate ??= playstationTwoDate;
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = playstationTwoSerial ?? string.Empty;
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationTwoRegion;
info.VersionAndEditions!.Version = InfoTool.GetPlayStation2Version(drive?.Name) ?? string.Empty;
}
break;
case RedumpSystem.SonyPlayStation3:
// Get metadata from log if possible
if (GetPlayStationInfo($"{basePath}.log", out var _, out string? ps3Serial, out var ps3Version, out string? firmwareVersion))
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = ps3Serial ?? string.Empty;
info.VersionAndEditions!.Version = ps3Version ?? string.Empty;
}
// Get metadata from drive if not available from log
else
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = InfoTool.GetPlayStation3Serial(drive?.Name) ?? string.Empty;
info.VersionAndEditions!.Version = InfoTool.GetPlayStation3Version(drive?.Name) ?? string.Empty;
firmwareVersion = InfoTool.GetPlayStation3FirmwareVersion(drive?.Name);
}
// Set the firmware version as a piece of content
if (firmwareVersion != null)
info.CommonDiscInfo.ContentsSpecialFields![SiteCode.Patches] = $"PS3 Firmware {firmwareVersion}";
break;
case RedumpSystem.SonyPlayStation4:
// Get metadata from log if possible
if (GetPlayStationInfo($"{basePath}.log", out var _, out string? ps4Serial, out var ps4Version, out var _))
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = ps4Serial ?? string.Empty;
info.VersionAndEditions!.Version = ps4Version ?? string.Empty;
}
// Get metadata from drive if not available from log
else
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = InfoTool.GetPlayStation4Serial(drive?.Name) ?? string.Empty;
info.VersionAndEditions!.Version = InfoTool.GetPlayStation4Version(drive?.Name) ?? string.Empty;
}
break;
case RedumpSystem.SonyPlayStation5:
// Get metadata from log if possible
if (GetPlayStationInfo($"{basePath}.log", out var _, out string? ps5Serial, out var ps5Version, out var _))
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = ps5Serial ?? string.Empty;
info.VersionAndEditions!.Version = ps5Version ?? string.Empty;
}
// Get metadata from drive if not available from log
else
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = InfoTool.GetPlayStation5Serial(drive?.Name) ?? string.Empty;
info.VersionAndEditions!.Version = InfoTool.GetPlayStation5Version(drive?.Name) ?? string.Empty;
}
break;
}
// Fill in any artifacts that exist, Base64-encoded, if we need to
if (includeArtifacts)
{
info.Artifacts ??= [];
if (File.Exists($"{basePath}.cdtext"))
info.Artifacts["cdtext"] = GetBase64(GetFullFile($"{basePath}.cdtext")) ?? string.Empty;
if (File.Exists($"{basePath}.cue"))
info.Artifacts["cue"] = GetBase64(GetFullFile($"{basePath}.cue")) ?? string.Empty;
if (File.Exists($"{basePath}.fulltoc"))
info.Artifacts["fulltoc"] = GetBase64(GetFullFile($"{basePath}.fulltoc")) ?? string.Empty;
if (File.Exists($"{basePath}.hash"))
info.Artifacts["hash"] = GetBase64(GetFullFile($"{basePath}.hash")) ?? string.Empty;
// TODO: "{basePath} (Track X).hash" (get from cuesheet)
if (File.Exists($"{basePath}.log"))
info.Artifacts["log"] = GetBase64(GetFullFile($"{basePath}.log")) ?? string.Empty;
if (File.Exists($"{basePath}.manufacturer"))
info.Artifacts["manufacturer"] = GetBase64(GetFullFile($"{basePath}.manufacturer")) ?? string.Empty;
if (File.Exists($"{basePath}.1.manufacturer"))
info.Artifacts["manufacturer1"] = GetBase64(GetFullFile($"{basePath}.1.manufacturer")) ?? string.Empty;
if (File.Exists($"{basePath}.2.manufacturer"))
info.Artifacts["manufacturer2"] = GetBase64(GetFullFile($"{basePath}.2.manufacturer")) ?? string.Empty;
if (File.Exists($"{basePath}.physical"))
info.Artifacts["physical"] = GetBase64(GetFullFile($"{basePath}.physical")) ?? string.Empty;
if (File.Exists($"{basePath}.0.physical"))
info.Artifacts["physical0"] = GetBase64(GetFullFile($"{basePath}.0.physical")) ?? string.Empty;
if (File.Exists($"{basePath}.1.physical"))
info.Artifacts["physical1"] = GetBase64(GetFullFile($"{basePath}.1.physical")) ?? string.Empty;
if (File.Exists($"{basePath}.2.physical"))
info.Artifacts["physical2"] = GetBase64(GetFullFile($"{basePath}.2.physical")) ?? string.Empty;
// if (File.Exists($"{basePath}.skeleton"))
// info.Artifacts["skeleton"] = GetBase64(GetFullFile($"{basePath}.skeleton")) ?? string.Empty;
// // Also: "{basePath} (Track X).skeleton" (get from cuesheet)
// if (File.Exists($"{basePath}.scram"))
// info.Artifacts["scram"] = GetBase64(GetFullFile($"{basePath}.scram")) ?? string.Empty;
// if (File.Exists($"{basePath}.scrap"))
// info.Artifacts["scrap"] = GetBase64(GetFullFile($"{basePath}.scrap")) ?? string.Empty;
if (File.Exists($"{basePath}.state"))
info.Artifacts["state"] = GetBase64(GetFullFile($"{basePath}.state")) ?? string.Empty;
if (File.Exists($"{basePath}.subcode"))
info.Artifacts["subcode"] = GetBase64(GetFullFile($"{basePath}.subcode")) ?? string.Empty;
if (File.Exists($"{basePath}.toc"))
info.Artifacts["toc"] = GetBase64(GetFullFile($"{basePath}.toc")) ?? string.Empty;
}
}
/// <inheritdoc/>
public override List<string> GetDeleteableFilePaths(string basePath)
{
var deleteableFiles = new List<string>();
if (File.Exists($"{basePath}.scram"))
deleteableFiles.Add($"{basePath}.scram");
if (File.Exists($"{basePath}.scrap"))
deleteableFiles.Add($"{basePath}.scrap");
return deleteableFiles;
}
/// <inheritdoc/>
public override List<string> GetLogFilePaths(string basePath)
{
var logFiles = new List<string>();
switch (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 = SabreTools.Serialization.Deserializers.CueSheet.DeserializeFile($"{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;
}
#endregion
#region Information Extraction Methods
/// <summary>
/// Get the cuesheet from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Newline-delimited cuesheet if possible, null on error</returns>
private static string? GetCuesheet(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
try
{
// Fast forward to the dat line
using var sr = File.OpenText(log);
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("CUE [") == false) ;
if (sr.EndOfStream)
return null;
// Now that we're at the relevant entries, read each line in and concatenate
string? cueString = string.Empty, line = sr.ReadLine()?.Trim();
while (!string.IsNullOrEmpty(line))
{
cueString += line + "\n";
line = sr.ReadLine()?.Trim();
}
return cueString.TrimEnd('\n');
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the datfile from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Newline-delimited datfile if possible, null on error</returns>
private static string? GetDatfile(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
try
{
using var sr = File.OpenText(log);
string? datString = null;
// Find all occurrences of the hash information
while (!sr.EndOfStream)
{
// Fast forward to the dat line
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("dat:") == false) ;
if (sr.EndOfStream)
break;
// Now that we're at the relevant entries, read each line in and concatenate
datString = string.Empty;
var line = sr.ReadLine()?.Trim();
while (line?.StartsWith("<rom") == true)
{
datString += line + "\n";
if (sr.EndOfStream)
break;
line = sr.ReadLine()?.Trim();
}
}
return datString?.TrimEnd('\n');
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get reported disc type information, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>True if disc type info was set, false otherwise</returns>
private static bool GetDiscType(string log, out string? discTypeOrBookType)
{
// Set the default values
discTypeOrBookType = null;
// If the file doesn't exist, we can't get the info
if (!File.Exists(log))
return false;
try
{
using var sr = File.OpenText(log);
var line = sr.ReadLine();
while (line != null)
{
// Trim the line for later use
line = line.Trim();
// The profile is listed in a single line
if (line.StartsWith("current profile:"))
{
// current profile: <discType>
discTypeOrBookType = line.Substring("current profile: ".Length);
}
line = sr.ReadLine();
}
return true;
}
catch
{
// We don't care what the exception is right now
discTypeOrBookType = null;
return false;
}
}
/// <summary>
/// Get all Volume Identifiers
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Volume labels (by type), or null if none present</returns>
private static bool GetVolumeLabels(string log, out Dictionary<string, List<string>> volLabels)
{
// If the file doesn't exist, can't get the volume labels
volLabels = [];
if (!File.Exists(log))
return false;
try
{
using var sr = File.OpenText(log);
var line = sr.ReadLine();
while (line != null)
{
// Trim the line for later use
line = line.Trim();
// ISO9660 Volume Identifier
if (line.StartsWith("volume identifier: "))
{
string label = line.Substring("volume identifier: ".Length);
// Skip if label is blank
if (label == null || label.Length <= 0)
break;
if (volLabels.ContainsKey(label))
volLabels[label].Add("ISO");
else
volLabels[label] = ["ISO"];
// Redumper log currently only outputs ISO9660 label, end here
break;
}
line = sr.ReadLine();
}
// Return true if a volume label was found
return volLabels.Count > 0;
}
catch
{
// We don't care what the exception is right now
volLabels = [];
return false;
}
}
/// <summary>
/// Get the DVD protection information, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <param name="includeAlways">Indicates whether region and protection type are always included</param>
/// <returns>Formatted string representing the DVD protection, null on error</returns>
private static string? GetDVDProtection(string log, bool includeAlways)
{
// If one of the files doesn't exist, we can't get info from them
if (!File.Exists(log))
return null;
// Setup all of the individual pieces
string? region = null, rceProtection = null, copyrightProtectionSystemType = null, vobKeys = null, decryptedDiscKey = null;
using (var sr = File.OpenText(log))
{
try
{
// Fast forward to the copyright information
while (sr.ReadLine()?.Trim().StartsWith("copyright:") == false) ;
// Now read until we hit the manufacturing information
var line = sr.ReadLine()?.Trim();
while (line != null && !sr.EndOfStream)
{
if (line.StartsWith("protection system type"))
{
copyrightProtectionSystemType = line.Substring("protection system type: ".Length);
if (copyrightProtectionSystemType == "none" || copyrightProtectionSystemType == "<none>")
copyrightProtectionSystemType = "No";
}
else if (line.StartsWith("region management information:"))
{
region = line.Substring("region management information: ".Length);
}
else if (line.StartsWith("disc key"))
{
decryptedDiscKey = line.Substring("disc key: ".Length).Replace(':', ' ');
}
else if (line.StartsWith("title keys"))
{
vobKeys = string.Empty;
line = sr.ReadLine()?.Trim();
while (!string.IsNullOrEmpty(line))
{
var match = Regex.Match(line, @"^(.*?): (.*?)$", RegexOptions.Compiled);
if (match.Success)
{
string normalizedKey = match.Groups[2].Value.Replace(':', ' ');
if (normalizedKey == "none" || normalizedKey == "<none>")
normalizedKey = "No Title Key";
else if (normalizedKey == "<error>")
normalizedKey = "Error Retrieving Title Key";
vobKeys += $"{match.Groups[1].Value} Title Key: {match.Groups[2].Value.Replace(':', ' ')}\n";
}
else
{
break;
}
line = sr.ReadLine()?.Trim();
}
}
else
{
break;
}
line = sr.ReadLine()?.Trim();
}
}
catch { }
}
// Filter out if we're not always including information
if (!includeAlways)
{
if (region == "1 2 3 4 5 6 7 8")
region = null;
if (copyrightProtectionSystemType == "No")
copyrightProtectionSystemType = null;
}
// Now we format everything we can
string protection = string.Empty;
if (!string.IsNullOrEmpty(region))
protection += $"Region: {region}\n";
if (!string.IsNullOrEmpty(rceProtection))
protection += $"RCE Protection: {rceProtection}\n";
if (!string.IsNullOrEmpty(copyrightProtectionSystemType))
protection += $"Copyright Protection System Type: {copyrightProtectionSystemType}\n";
if (!string.IsNullOrEmpty(vobKeys))
protection += vobKeys;
if (!string.IsNullOrEmpty(decryptedDiscKey))
protection += $"Decrypted Disc Key: {decryptedDiscKey}\n";
return protection;
}
/// <summary>
/// Get the detected error counts from the input files, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>True if error counts could be retrieved, false otherwise</returns>
public static bool GetErrorCount(string log, out long redumpErrors, out long c2Errors)
{
// Set the default values for error counts
redumpErrors = -1; c2Errors = -1;
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return false;
try
{
using var sr = File.OpenText(log);
// Find the error counts
while (!sr.EndOfStream)
{
var line = sr.ReadLine()?.Trim();
if (line == null)
break;
// C2: <error count>
if (line.StartsWith("C2:"))
{
string[] parts = line.Split(' ');
if (!long.TryParse(parts[1], out c2Errors))
c2Errors = -1;
}
// REDUMP.ORG errors: <error count>
else if (line.StartsWith("REDUMP.ORG errors:"))
{
string[] parts = line!.Split(' ');
if (!long.TryParse(parts[2], out redumpErrors))
redumpErrors = -1;
}
}
// If the Redump error count is -1, then an issue occurred
return redumpErrors != -1;
}
catch
{
// We don't care what the exception is right now
return false;
}
}
/// <summary>
/// Get hardware information from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>True if hardware info was set, false otherwise</returns>
private static bool GetHardwareInfo(string log, out string? manufacturer, out string? model, out string? firmware)
{
// Set the default values
manufacturer = null; model = null; firmware = null;
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return false;
try
{
// Fast forward to the drive information line
using var sr = File.OpenText(log);
while (!(sr.ReadLine()?.Trim().StartsWith("drive path:") ?? true)) ;
// If we find the hardware info line, return each value
// drive: <vendor_id> - <product_id> (revision level: <product_revision_level>, vendor specific: <vendor_specific>)
var regex = new Regex(@"drive: (.+) - (.+) \(revision level: (.+), vendor specific: (.+)\)", RegexOptions.Compiled);
string? line;
while ((line = sr.ReadLine()) != null)
{
var match = regex.Match(line.Trim());
if (match.Success)
{
manufacturer = match.Groups[1].Value;
model = match.Groups[2].Value;
firmware = match.Groups[3].Value;
firmware += match.Groups[4].Value == "<empty>" ? "" : $" ({match.Groups[4].Value})";
return true;
}
}
// We couldn't detect it then
return false;
}
catch
{
// We don't care what the exception is right now
return false;
}
}
/// <summary>
/// Get the layerbreaks from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>True if any layerbreaks were found, false otherwise</returns>
private static bool GetLayerbreaks(string log, out string? layerbreak1, out string? layerbreak2, out string? layerbreak3)
{
// Set the default values
layerbreak1 = null; layerbreak2 = null; layerbreak3 = null;
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return false;
try
{
// Find the layerbreak
using var sr = File.OpenText(log);
while (!sr.EndOfStream)
{
var line = sr.ReadLine()?.Trim();
// If we have a null line, just break
if (line == null)
break;
// Single-layer discs have no layerbreak
if (line.Contains("layers count: 1"))
{
return false;
}
// Dual-layer discs have a regular layerbreak (old)
else if (line.StartsWith("data "))
{
// data { LBA: <startLBA> .. <endLBA>, length: <length>, hLBA: <startLBA> .. <endLBA> }
string[] split = line.Split(' ').Where(s => !string.IsNullOrEmpty(s)).ToArray();
layerbreak1 ??= split[7].TrimEnd(',');
}
// Dual-layer discs have a regular layerbreak (new)
else if (line.StartsWith("layer break:"))
{
// layer break: <layerbreak>
layerbreak1 = line.Substring("layer break: ".Length).Trim();
}
// Multi-layer discs have the layer in the name
else if (line.StartsWith("layer break (layer: 0):"))
{
// layer break (layer: 0): <layerbreak>
layerbreak1 = line.Substring("layer break (layer: 0): ".Length).Trim();
}
else if (line.StartsWith("layer break (layer: 1):"))
{
// layer break (layer: 1): <layerbreak>
layerbreak2 = line.Substring("layer break (layer: 1): ".Length).Trim();
}
else if (line.StartsWith("layer break (layer: 2):"))
{
// layer break (layer: 2): <layerbreak>
layerbreak3 = line.Substring("layer break (layer: 2): ".Length).Trim();
}
}
// Return the layerbreak, if possible
return true;
}
catch
{
// We don't care what the exception is right now
return false;
}
}
/// <summary>
/// Get multisession information from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Formatted multisession information, null on error</returns>
private static string? GetMultisessionInformation(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
try
{
// Fast forward to the multisession lines
using var sr = File.OpenText(log);
while (!sr.EndOfStream && sr.ReadLine()?.Trim()?.StartsWith("multisession:") == false) ;
if (sr.EndOfStream)
return null;
// Now that we're at the relevant lines, find the session info
string? firstSession = null, secondSession = null;
while (!sr.EndOfStream)
{
var line = sr.ReadLine()?.Trim();
// If we have a null line, just break
if (line == null)
break;
// Store the first session range
if (line.Contains("session 1:"))
firstSession = line.Substring("session 1: ".Length).Trim();
// Store the secomd session range
else if (line.Contains("session 2:"))
secondSession = line.Substring("session 2: ".Length).Trim();
}
// If either is blank, we don't have multisession
if (string.IsNullOrEmpty(firstSession) || string.IsNullOrEmpty(secondSession))
return null;
// Create and return the formatted output
return $"Session 1: {firstSession}\nSession 2: {secondSession}";
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the existence of an anti-modchip string from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Anti-modchip existence if possible, false on error</returns>
private static bool? GetPlayStationAntiModchipDetected(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
try
{
// Check for the anti-modchip strings
using var sr = File.OpenText(log);
var line = sr.ReadLine()?.Trim();
while (!sr.EndOfStream)
{
if (line == null)
return false;
if (line.StartsWith("anti-modchip: no"))
return false;
else if (line.StartsWith("anti-modchip: yes"))
return true;
line = sr.ReadLine()?.Trim();
}
return false;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the detected missing EDC count from the input files, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Status of PS1 EDC, if possible</returns>
private static bool? GetPlayStationEDCStatus(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
try
{
// Check for the EDC strings
using var sr = File.OpenText(log);
var line = sr.ReadLine()?.Trim();
while (!sr.EndOfStream)
{
if (line == null)
return false;
if (line.Contains("EDC: no"))
return false;
else if (line.Contains("EDC: yes"))
return true;
line = sr.ReadLine()?.Trim();
}
return false;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the info from a PlayStation disc, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>True if section found, null on error</returns>
private static bool GetPlayStationInfo(string log, out string? exeDate, out string? serial, out string? version, out string? firmware)
{
// Set the default values
exeDate = null; serial = null; version = 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 PS info line
using var sr = File.OpenText(log);
string? line;
while (!sr.EndOfStream)
{
line = sr.ReadLine()?.TrimStart();
if (line?.StartsWith("PSX [") == true ||
line?.StartsWith("PS2 [") == true ||
line?.StartsWith("PS3 [") == true ||
line?.StartsWith("PS4 [") == true ||
line?.StartsWith("PS5 [") == true)
break;
}
if (sr.EndOfStream)
return false;
while (!sr.EndOfStream)
{
line = sr.ReadLine()?.TrimStart();
if (line == null)
break;
if (line.StartsWith("EXE date:"))
{
exeDate = line.Substring("EXE date: ".Length).Trim();
}
else if (line.StartsWith("serial:"))
{
serial = line.Substring("serial: ".Length).Trim();
}
else if (line.StartsWith("version:"))
{
version = line.Substring("version: ".Length).Trim();
}
else if (line.StartsWith("firmware:"))
{
firmware = line.Substring("firmware: ".Length).Trim();
}
else
{
break;
}
}
return true;
}
catch
{
// We don't care what the exception is right now
return false;
}
}
/// <summary>
/// Get the LibCrypt data from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>PS1 LibCrypt data, if possible</returns>
private static string? GetPlayStationLibCryptData(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
try
{
// Fast forward to the LibCrypt line
using var sr = File.OpenText(log);
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("libcrypt:") == false) ;
if (sr.EndOfStream)
return null;
// Now that we're at the relevant entries, read each line in and concatenate
string? libCryptString = "", line = sr.ReadLine()?.Trim();
while (line?.StartsWith("MSF:") == true)
{
libCryptString += line + "\n";
line = sr.ReadLine()?.Trim();
}
return libCryptString.TrimEnd('\n');
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the existence of LibCrypt from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Status of PS1 LibCrypt, if possible</returns>
private static bool? GetPlayStationLibCryptStatus(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
try
{
// Check for the libcrypt strings
using var sr = File.OpenText(log);
var line = sr.ReadLine()?.Trim();
while (!sr.EndOfStream)
{
if (line == null)
return false;
if (line.StartsWith("libcrypt: no"))
return false;
else if (line.StartsWith("libcrypt: yes"))
return true;
line = sr.ReadLine()?.Trim();
}
return false;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the PVD from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Newline-delimited PVD if possible, null on error</returns>
private static string? GetPVD(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
try
{
// Fast forward to the PVD line
using var sr = File.OpenText(log);
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("PVD:") == false) ;
if (sr.EndOfStream)
return null;
// Now that we're at the relevant entries, read each line in and concatenate
string? pvdString = "", line = sr.ReadLine();
while (line?.StartsWith("03") == true)
{
pvdString += line + "\n";
line = sr.ReadLine();
}
return pvdString.TrimEnd('\n');
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the non-zero data start from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Non-zero dta start if possible, null on error</returns>
private static string? GetRingNonZeroDataStart(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
try
{
// If we find the sample range, return the start value only
using var sr = File.OpenText(log);
while (!sr.EndOfStream)
{
string? line = sr.ReadLine()?.TrimStart();
if (line?.StartsWith("non-zero data sample range") == true)
return line.Substring("non-zero data sample range: [".Length).Trim().Split(' ')[0];
}
// We couldn't detect it then
return null;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the build info from a Saturn disc, if possible
/// </summary>
/// <<param name="segaHeader">String representing a formatter variant of the Saturn header</param>
/// <returns>True on successful extraction of info, false otherwise</returns>
/// TODO: Remove when Redumper gets native reading support
private static bool GetSaturnBuildInfo(string? segaHeader, out string? serial, out string? version, out string? date)
{
serial = null; version = null; date = null;
// If the input header is null, we can't do a thing
if (string.IsNullOrEmpty(segaHeader))
return false;
// Now read it in cutting it into lines for easier parsing
try
{
string[] header = segaHeader!.Split('\n');
string serialVersionLine = header[2].Substring(58);
string dateLine = header[3].Substring(58);
serial = serialVersionLine.Substring(0, 10).Trim();
version = serialVersionLine.Substring(10, 6).TrimStart('V', 'v');
date = dateLine.Substring(0, 8);
date = $"{date[0]}{date[1]}{date[2]}{date[3]}-{date[4]}{date[5]}-{date[6]}{date[7]}";
return true;
}
catch
{
// We don't care what the error is
return false;
}
}
/// <summary>
/// Get the header from a Saturn, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Header as a byte array if possible, null on error</returns>
private static string? GetSaturnHeader(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
try
{
// Fast forward to the SS line
using var sr = File.OpenText(log);
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("SS [") == false) ;
if (sr.EndOfStream)
return null;
string? line, headerString = "";
while (!sr.EndOfStream)
{
line = sr.ReadLine()?.TrimStart();
if (line?.StartsWith("header:") == true)
{
line = sr.ReadLine()?.TrimStart();
while (line?.StartsWith("00") == true)
{
headerString += line + "\n";
line = sr.ReadLine()?.Trim();
}
}
else
{
break;
}
}
return headerString.TrimEnd('\n');
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the header from a Saturn, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Header as a byte array if possible, null on error</returns>
private static string? GetSecuROMData(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
try
{
// Fast forward to the SecuROM line
using var sr = File.OpenText(log);
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("SecuROM [") == false) ;
if (sr.EndOfStream)
return null;
var lines = new List<string>();
while (!sr.EndOfStream)
{
var line = sr.ReadLine()?.TrimStart();
// Skip the "version"/"scheme" line
if (line?.StartsWith("version:") == true || line?.StartsWith("scheme:") == true)
continue;
// Only read until while there are MSF lines
if (line?.StartsWith("MSF:") != true)
break;
lines.Add(line);
}
return string.Join("\n", [.. lines]).TrimEnd('\n');
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the header from a Sega CD / Mega CD, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Header as a byte array if possible, null on error</returns>
private static string? GetSegaCDHeader(string log, out string? buildDate, out string? serial, out string? region)
{
// Set the default values
buildDate = null; serial = null; region = null;
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
try
{
// Fast forward to the MCD line
using var sr = File.OpenText(log);
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("MCD [") == false) ;
if (sr.EndOfStream)
return null;
string? line, headerString = string.Empty;
while (!sr.EndOfStream)
{
line = sr.ReadLine()?.TrimStart();
if (line == null)
break;
if (line.StartsWith("build date:"))
{
buildDate = line.Substring("build date: ".Length).Trim();
}
else if (line.StartsWith("serial:"))
{
serial = line.Substring("serial: ".Length).Trim();
}
else if (line.StartsWith("region:"))
{
region = line.Substring("region: ".Length).Trim();
}
else if (line.StartsWith("regions:"))
{
region = line.Substring("regions: ".Length).Trim();
}
else if (line.StartsWith("header:"))
{
line = sr.ReadLine()?.TrimStart();
while (line?.StartsWith("01") == true)
{
headerString += line + "\n";
line = sr.ReadLine()?.Trim();
}
}
else
{
break;
}
}
return headerString.TrimEnd('\n');
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the universal hash from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Universal hash if possible, null on error</returns>
private static string? GetUniversalHash(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
try
{
// If we find the universal hash line, return the hash only
using var sr = File.OpenText(log);
while (!sr.EndOfStream)
{
string? line = sr.ReadLine()?.TrimStart();
if (line?.StartsWith("Universal Hash") == true)
return line.Substring("Universal Hash (SHA-1): ".Length).Trim();
}
// We couldn't detect it then
return null;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the write offset from the input file, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Sample write offset if possible, null on error</returns>
private static string? GetWriteOffset(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
try
{
// If we find the disc write offset line, return the offset
using var sr = File.OpenText(log);
while (!sr.EndOfStream)
{
string? line = sr.ReadLine()?.TrimStart();
if (line?.StartsWith("disc write offset") == true)
return line.Substring("disc write offset: ".Length).Trim();
}
// We couldn't detect it then
return null;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the version. if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Version if possible, null on error</returns>
private static string? GetVersion(string log)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return null;
// Samples:
// redumper v2022.10.28 [Oct 28 2022, 05:41:43] (print usage: --help,-h)
// redumper v2022.12.22 build_87 [Dec 22 2022, 01:56:26]
try
{
// Skip first line (dump date)
using var sr = File.OpenText(log);
sr.ReadLine();
// Get the next non-warning line
string nextLine = sr.ReadLine()?.Trim() ?? string.Empty;
if (nextLine.StartsWith("warning:", StringComparison.OrdinalIgnoreCase))
nextLine = sr.ReadLine()?.Trim() ?? string.Empty;
// Generate regex
// Permissive
var regex = new Regex(@"^redumper (v.+) \[.+\]", RegexOptions.Compiled);
// Strict
//var regex = new Regex(@"^redumper (v\d{4}\.\d{2}\.\d{2}(| build_\d+)) \[.+\]", RegexOptions.Compiled);
// Extract the version string
var match = regex.Match(nextLine);
var version = match.Groups[1].Value;
return string.IsNullOrEmpty(version) ? null : version;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
#endregion
}
}