using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using SabreTools.Hashing; using SabreTools.Models.Logiqx; using SabreTools.RedumpLib; using SabreTools.RedumpLib.Data; namespace MPF.Processors { /// /// Represents processing CleanRip outputs /// public sealed class CleanRip : BaseProcessor { /// public CleanRip(RedumpSystem? system, MediaType? type) : base(system, type) { } #region BaseProcessor Implementations /// public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, bool redumpCompat) { // Ensure that required sections exist info = Builder.EnsureAllSections(info); // TODO: Determine if there's a CleanRip version anywhere info.DumpingInfo!.DumpingDate = ProcessingTool.GetFileModifiedDate(basePath + "-dumpinfo.txt")?.ToString("yyyy-MM-dd HH:mm:ss"); // Get the Datafile information var datafile = GenerateCleanripDatafile(basePath + ".iso", basePath + "-dumpinfo.txt"); info.TracksAndWriteOffsets!.ClrMameProData = ProcessingTool.GenerateDatfile(datafile); // Get the individual hash data, as per internal if (ProcessingTool.GetISOHashValues(datafile, 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; // Dual-layer discs have the same size and layerbreak if (size == 8511160320) info.SizeAndChecksums.Layerbreak = 2084960; } // Extract info based generically on MediaType switch (Type) { case MediaType.DVD: // Only added here to help users; not strictly correct case MediaType.NintendoGameCubeGameDisc: case MediaType.NintendoWiiOpticalDisc: if (File.Exists(basePath + ".bca")) info.Extras!.BCA = GetBCA(basePath + ".bca"); if (GetGameCubeWiiInformation(basePath + "-dumpinfo.txt", out Region? gcRegion, out var gcVersion, out var gcName, out var gcSerial)) { info.CommonDiscInfo!.Region = gcRegion ?? info.CommonDiscInfo.Region; info.VersionAndEditions!.Version = gcVersion ?? info.VersionAndEditions.Version; info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalName] = gcName ?? string.Empty; info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = gcSerial ?? string.Empty; } break; } } /// internal override List GetOutputFiles(string baseFilename) { switch (Type) { case MediaType.DVD: // Only added here to help users; not strictly correct case MediaType.NintendoGameCubeGameDisc: case MediaType.NintendoWiiOpticalDisc: return [ new($"{baseFilename}.bca", OutputFileFlags.Required | OutputFileFlags.Binary | OutputFileFlags.Zippable, "bca"), new($"{baseFilename}.iso", OutputFileFlags.Required), new($"{baseFilename}-dumpinfo.txt", OutputFileFlags.Required | OutputFileFlags.Artifact | OutputFileFlags.Zippable, "dumpinfo"), ]; } return []; } #endregion #region Information Extraction Methods /// /// Get a formatted datfile from the cleanrip output, if possible /// /// Path to ISO file /// Path to discinfo file /// private static Datafile? GenerateCleanripDatafile(string iso, string dumpinfo) { // If the files don't exist, we can't get info from it if (!File.Exists(iso) || !File.Exists(dumpinfo)) return null; long size = new FileInfo(iso).Length; string crc = string.Empty; string md5 = string.Empty; string sha1 = string.Empty; try { // Make sure this file is a dumpinfo using var sr = File.OpenText(dumpinfo); if (sr.ReadLine()?.Contains("--File Generated by CleanRip") != true) return null; // Read all lines and gather dat information while (!sr.EndOfStream) { var line = sr.ReadLine()?.Trim(); if (string.IsNullOrEmpty(line)) continue; else if (line!.StartsWith("CRC32")) crc = line.Substring(7).ToLowerInvariant(); else if (line.StartsWith("MD5")) md5 = line.Substring(5); else if (line.StartsWith("SHA-1")) sha1 = line.Substring(7); } // Ensure all checksums were found in log if (crc == string.Empty || md5 == string.Empty || sha1 == string.Empty) { if (HashTool.GetStandardHashes(iso, out long isoSize, out string? isoCRC, out string? isoMD5, out string? isoSHA1)) { crc = isoCRC ?? crc; md5 = isoMD5 ?? md5; sha1 = isoSHA1 ?? sha1; } } return new Datafile { Game = [new Game() { Rom = [new Rom { Name = Path.GetFileName(iso), Size = size.ToString(), CRC = crc, MD5 = md5, SHA1 = sha1 }] }] }; } catch { // We don't care what the exception is right now return null; } } /// /// Get the hex contents of the BCA file /// /// Path to the BCA file associated with the dump /// BCA data as a hex string if possible, null on error /// https://stackoverflow.com/questions/9932096/add-separator-to-string-at-every-n-characters private static string? GetBCA(string bcaPath) { // If the file doesn't exist, we can't get the info if (!File.Exists(bcaPath)) return null; try { var hex = ProcessingTool.GetFullFile(bcaPath, true); if (hex == null) return null; return Regex.Replace(hex, ".{32}", "$0\n"); } catch { // We don't care what the error was right now return null; } } /// /// Get the extracted GC and Wii version /// /// Path to discinfo file /// Output region, if possible /// Output internal version of the game /// Output internal name of the game /// Output internal serial of the game /// private static bool GetGameCubeWiiInformation(string dumpinfo, out Region? region, out string? version, out string? name, out string? serial) { region = null; version = null; name = null; serial = null; // If the file doesn't exist, we can't get info from it if (!File.Exists(dumpinfo)) return false; try { // Make sure this file is a dumpinfo using var sr = File.OpenText(dumpinfo); if (sr.ReadLine()?.Contains("--File Generated by CleanRip") != true) return false; // Read all lines and gather dat information while (!sr.EndOfStream) { var line = sr.ReadLine()?.Trim(); if (string.IsNullOrEmpty(line)) { continue; } else if (line!.StartsWith("Version")) { version = line.Substring("Version: ".Length); } else if (line.StartsWith("Internal Name")) { name = line.Substring("Internal Name: ".Length); } else if (line.StartsWith("Filename")) { serial = line.Substring("Filename: ".Length); if (serial.EndsWith("-disc2")) serial = serial.Replace("-disc2", string.Empty); // char gameType = serial[0]; // string gameid = serial[1] + serial[2]; // string version = serial[4] + serial[5] switch (serial[3]) { case 'A': region = Region.World; break; case 'D': region = Region.Germany; break; case 'E': region = Region.UnitedStatesOfAmerica; break; case 'F': region = Region.France; break; case 'I': region = Region.Italy; break; case 'J': region = Region.Japan; break; case 'K': region = Region.SouthKorea; break; case 'L': region = Region.Europe; // Japanese import to Europe break; case 'M': region = Region.Europe; // American import to Europe break; case 'N': region = Region.UnitedStatesOfAmerica; // Japanese import to USA break; case 'P': region = Region.Europe; break; case 'R': region = Region.RussianFederation; break; case 'S': region = Region.Spain; break; case 'Q': region = Region.SouthKorea; // Korea with Japanese language break; case 'T': region = Region.SouthKorea; // Korea with English language break; case 'X': region = null; // Not a real region code break; } } } return true; } catch { // We don't care what the exception is right now return false; } } #endregion } }