diff --git a/MPF.Library/DumpEnvironment.cs b/MPF.Library/DumpEnvironment.cs index 2892d9b6..48aa17f5 100644 --- a/MPF.Library/DumpEnvironment.cs +++ b/MPF.Library/DumpEnvironment.cs @@ -2,20 +2,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net; -using System.Text; -using System.Text.RegularExpressions; using System.Threading.Tasks; using BurnOutSharp; -using MPF.Core.Converters; using MPF.Core.Data; using MPF.Core.Utilities; using MPF.Modules; -using Newtonsoft.Json; using RedumpLib.Data; -using RedumpLib.Web; namespace MPF.Library { @@ -120,7 +112,7 @@ namespace MPF.Library this.Options = options; // Output paths - (this.OutputDirectory, this.OutputFilename) = NormalizeOutputPaths(outputDirectory, outputFilename, options.InternalProgram == InternalProgram.DiscImageCreator); + (this.OutputDirectory, this.OutputFilename) = InfoTool.NormalizeOutputPaths(outputDirectory, outputFilename, options.InternalProgram == InternalProgram.DiscImageCreator); // UI information this.Drive = drive; @@ -178,23 +170,6 @@ namespace MPF.Library this.Parameters.Type = this.Type; } - /// - /// Ensures that all required output files have been created - /// - /// True if this is a check done before a dump, false if done after - /// Tuple of true if all required files exist, false otherwise and a list representing missing files - public (bool, List) FoundAllFiles(bool preCheck) - { - // First, sanitized the output filename to strip off any potential extension - string outputFilename = Path.GetFileNameWithoutExtension(OutputFilename); - - // Then get the base path for all checking - string basePath = Path.Combine(OutputDirectory, outputFilename); - - // Finally, let the parameters say if all files exist - return Parameters.CheckAllOutputFilesExist(basePath, preCheck); - } - /// /// Get the full parameter string for either DiscImageCreator or Aaru /// @@ -238,60 +213,6 @@ namespace MPF.Library return null; } - /// - /// Normalize a split set of paths - /// - /// Directory name to normalize - /// Filename to normalize - /// True to replace '.' with '_' in filenames, false otherwise - public static (string, string) NormalizeOutputPaths(string directory, string filename, bool replacePeriods) - { - try - { - // Cache if we had a directory separator or not - bool endedWithDirectorySeparator = directory.EndsWith(Path.DirectorySeparatorChar.ToString()) - || directory.EndsWith(Path.AltDirectorySeparatorChar.ToString()); - - // Combine the path to make things separate easier - string combinedPath = Path.Combine(directory, filename); - - // If we have have a blank path, just return - if (string.IsNullOrWhiteSpace(combinedPath)) - return (directory, filename); - - // Now get the normalized paths - directory = Path.GetDirectoryName(combinedPath); - filename = Path.GetFileName(combinedPath); - - // Take care of extra path characters - directory = new StringBuilder(directory) - .Replace(':', '_', 0, directory.LastIndexOf(':') == -1 ? 0 : directory.LastIndexOf(':')).ToString(); - - // Sanitize everything else - foreach (char c in Path.GetInvalidPathChars()) - directory = directory.Replace(c, '_'); - foreach (char c in Path.GetInvalidFileNameChars()) - filename = filename.Replace(c, '_'); - if (replacePeriods) - filename = Path.GetFileNameWithoutExtension(filename).Replace('.', '_') + "." + Path.GetExtension(filename).TrimStart('.'); - - // If we had a directory separator at the end before, add it again - if (endedWithDirectorySeparator) - directory += Path.DirectorySeparatorChar; - - // If we have a root directory, sanitize - if (Directory.Exists(directory)) - { - var possibleRootDir = new DirectoryInfo(directory); - if (possibleRootDir.Parent == null) - directory = directory.Replace($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}", $"{Path.DirectorySeparatorChar}"); - } - } - catch { } - - return (directory, filename); - } - #endregion #region Dumping @@ -417,7 +338,7 @@ namespace MPF.Library resultProgress?.Report(Result.Success("Gathering submission information... please wait!")); // Check to make sure that the output had all the correct files - (bool foundFiles, List missingFiles) = FoundAllFiles(false); + (bool foundFiles, List missingFiles) = InfoTool.FoundAllFiles(this.OutputDirectory, this.OutputFilename, this.Parameters, false); if (!foundFiles) { resultProgress.Report(Result.Failure($"There were files missing from the output:\n{string.Join("\n", missingFiles)}")); @@ -426,7 +347,16 @@ namespace MPF.Library // Extract the information from the output files resultProgress?.Report(Result.Success("Extracting output information from output files...")); - SubmissionInfo submissionInfo = await ExtractOutputInformation(resultProgress, protectionProgress); + SubmissionInfo submissionInfo = await InfoTool.ExtractOutputInformation( + this.OutputDirectory, + this.OutputFilename, + this.Drive, + this.System, + this.Type, + this.Options, + this.Parameters, + resultProgress, + protectionProgress); resultProgress?.Report(Result.Success("Extracting information complete!")); // Eject the disc automatically if confugured to @@ -459,12 +389,12 @@ namespace MPF.Library // Format the information for the text output resultProgress?.Report(Result.Success("Formatting information...")); - List formattedValues = FormatOutputData(submissionInfo); + List formattedValues = InfoTool.FormatOutputData(submissionInfo); resultProgress?.Report(Result.Success("Formatting complete!")); // Write the text output resultProgress?.Report(Result.Success("Writing information to !submissionInfo.txt...")); - bool success = WriteOutputData(formattedValues); + bool success = InfoTool.WriteOutputData(this.OutputDirectory, formattedValues); if (success) resultProgress?.Report(Result.Success("Writing complete!")); else @@ -474,7 +404,7 @@ namespace MPF.Library if (Options.OutputSubmissionJSON) { resultProgress?.Report(Result.Success("Writing information to !submissionInfo.json.gz...")); - success = WriteOutputData(submissionInfo); + success = InfoTool.WriteOutputData(this.OutputDirectory, submissionInfo); if (success) resultProgress?.Report(Result.Success("Writing complete!")); else @@ -485,7 +415,7 @@ namespace MPF.Library if (Options.CompressLogFiles) { resultProgress?.Report(Result.Success("Compressing log files...")); - success = CompressLogFiles(); + success = InfoTool.CompressLogFiles(this.OutputDirectory, this.OutputFilename, this.Parameters); if (success) resultProgress?.Report(Result.Success("Compression complete!")); else @@ -558,689 +488,6 @@ namespace MPF.Library return output; } - /// - /// Extract all of the possible information from a given input combination - /// - /// Optional result progress callback - /// Optional protection progress callback - /// SubmissionInfo populated based on outputs, null on error - private async Task ExtractOutputInformation( - IProgress resultProgress = null, - IProgress protectionProgress = null) - { - // Ensure the current disc combination should exist - if (!System.MediaTypes().Contains(Type)) - return null; - - // Sanitize the output filename to strip off any potential extension - string outputFilename = Path.GetFileNameWithoutExtension(OutputFilename); - - // Check that all of the relevant files are there - (bool foundFiles, List missingFiles) = FoundAllFiles(false); - if (!foundFiles) - { - resultProgress.Report(Result.Failure($"There were files missing from the output:\n{string.Join("\n", missingFiles)}")); - return null; - } - - // Create the SubmissionInfo object with all user-inputted values by default - string combinedBase = Path.Combine(OutputDirectory, outputFilename); - SubmissionInfo info = new SubmissionInfo() - { - CommonDiscInfo = new CommonDiscInfoSection() - { - System = this.System, - Media = this.Type.ToDiscType(), - Title = (Options.AddPlaceholders ? Template.RequiredValue : ""), - ForeignTitleNonLatin = (Options.AddPlaceholders ? Template.OptionalValue : ""), - DiscNumberLetter = (Options.AddPlaceholders ? Template.OptionalValue : ""), - DiscTitle = (Options.AddPlaceholders ? Template.OptionalValue : ""), - Category = null, - Region = null, - Languages = null, - Serial = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""), - Barcode = (Options.AddPlaceholders ? Template.OptionalValue : ""), - Contents = (Options.AddPlaceholders ? Template.OptionalValue : ""), - }, - VersionAndEditions = new VersionAndEditionsSection() - { - Version = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""), - OtherEditions = (Options.AddPlaceholders ? "Original (VERIFY THIS)" : ""), - }, - TracksAndWriteOffsets = new TracksAndWriteOffsetsSection(), - }; - - // Get specific tool output handling - Parameters.GenerateSubmissionInfo(info, combinedBase, this.Drive, Options.IncludeArtifacts); - - // Get a list of matching IDs for each line in the DAT - if (!string.IsNullOrEmpty(info.TracksAndWriteOffsets.ClrMameProData) && Options.HasRedumpLogin) - { - // Set the current dumper based on username - info.DumpersAndStatus.Dumpers = new string[] { Options.RedumpUsername }; - - info.MatchedIDs = new List(); - using (RedumpWebClient wc = new RedumpWebClient()) - { - // Login to Redump - bool? loggedIn = wc.Login(Options.RedumpUsername, Options.RedumpPassword); - if (loggedIn == null) - { - resultProgress?.Report(Result.Failure("There was an unknown error connecting to Redump")); - } - else if (loggedIn == true) - { - // Loop through all of the hashdata to find matching IDs - resultProgress?.Report(Result.Success("Finding disc matches on Redump...")); - string[] splitData = info.TracksAndWriteOffsets.ClrMameProData.Split('\n'); - foreach (string hashData in splitData) - { - if (GetISOHashValues(hashData, out long _, out string _, out string _, out string sha1)) - { - // Get all matching IDs for the track - List newIds = ListSearchResults(wc, sha1); - - // If we got null back, there was an error - if (newIds == null) - { - resultProgress?.Report(Result.Failure("There was an unknown error retrieving information from Redump")); - break; - } - - // If no IDs match any track, then we don't match a disc at all - if (!newIds.Any()) - { - info.MatchedIDs = new List(); - break; - } - - // If we have multiple tracks, only take IDs that are in common - if (info.MatchedIDs.Any()) - info.MatchedIDs = info.MatchedIDs.Intersect(newIds).ToList(); - - // If we're on the first track, all IDs are added - else - info.MatchedIDs = newIds; - } - } - - resultProgress?.Report(Result.Success("Match finding complete! " + (info.MatchedIDs.Count > 0 ? "Matched IDs: " + string.Join(",", info.MatchedIDs) : "No matches found"))); - - // If we have exactly 1 ID, we can grab a bunch of info from it - if (info.MatchedIDs.Count == 1) - { - resultProgress?.Report(Result.Success($"Filling fields from existing ID {info.MatchedIDs[0]}...")); - FillFromId(wc, info, info.MatchedIDs[0]); - resultProgress?.Report(Result.Success("Information filling complete!")); - } - } - } - } - - // If we have both ClrMamePro and Size and Checksums data, remove the ClrMamePro - if (!string.IsNullOrWhiteSpace(info.SizeAndChecksums.CRC32)) - info.TracksAndWriteOffsets.ClrMameProData = null; - - // Extract info based generically on MediaType - switch (Type) - { - case MediaType.CDROM: - case MediaType.GDROM: // TODO: Verify GD-ROM outputs this - info.CommonDiscInfo.Layer0MasteringRing = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MasteringSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0ToolstampMasteringCode = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MouldSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer1MouldSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0AdditionalMould = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - break; - - case MediaType.DVD: - case MediaType.HDDVD: - case MediaType.BluRay: - // If we have a single-layer disc - if (info.SizeAndChecksums.Layerbreak == default) - { - info.CommonDiscInfo.Layer0MasteringRing = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MasteringSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0ToolstampMasteringCode = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MouldSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer1MouldSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0AdditionalMould = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - } - // If we have a dual-layer disc - else - { - info.CommonDiscInfo.Layer0MasteringRing = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MasteringSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0ToolstampMasteringCode = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MouldSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0AdditionalMould = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - - info.CommonDiscInfo.Layer1MasteringRing = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer1MasteringSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer1ToolstampMasteringCode = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer1MouldSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - } - - break; - - case MediaType.NintendoGameCubeGameDisc: - info.CommonDiscInfo.Layer0MasteringRing = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MasteringSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0ToolstampMasteringCode = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MouldSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer1MouldSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0AdditionalMould = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.Extras.BCA = info.Extras.BCA ?? (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case MediaType.NintendoWiiOpticalDisc: - // If we have a single-layer disc - if (info.SizeAndChecksums.Layerbreak == default) - { - info.CommonDiscInfo.Layer0MasteringRing = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MasteringSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0ToolstampMasteringCode = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MouldSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer1MouldSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0AdditionalMould = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - } - // If we have a dual-layer disc - else - { - info.CommonDiscInfo.Layer0MasteringRing = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MasteringSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0ToolstampMasteringCode = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MouldSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0AdditionalMould = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - - info.CommonDiscInfo.Layer1MasteringRing = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer1MasteringSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer1ToolstampMasteringCode = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer1MouldSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - } - - info.Extras.DiscKey = (Options.AddPlaceholders ? Template.RequiredValue : ""); - info.Extras.BCA = info.Extras.BCA ?? (Options.AddPlaceholders ? Template.RequiredValue : ""); - - break; - - case MediaType.UMD: - // If we have a single-layer disc - if (info.SizeAndChecksums.Layerbreak == default) - { - info.CommonDiscInfo.Layer0MasteringRing = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MasteringSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0ToolstampMasteringCode = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MouldSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - } - // If we have a dual-layer disc - else - { - info.CommonDiscInfo.Layer0MasteringRing = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MasteringSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0ToolstampMasteringCode = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer0MouldSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - - info.CommonDiscInfo.Layer1MasteringRing = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer1MasteringSID = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - info.CommonDiscInfo.Layer1ToolstampMasteringCode = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - } - - info.SizeAndChecksums.CRC32 = info.SizeAndChecksums.CRC32 ?? (Options.AddPlaceholders ? Template.RequiredValue + " [Not automatically generated for UMD]" : ""); - info.SizeAndChecksums.MD5 = info.SizeAndChecksums.MD5 ?? (Options.AddPlaceholders ? Template.RequiredValue + " [Not automatically generated for UMD]" : ""); - info.SizeAndChecksums.SHA1 = info.SizeAndChecksums.SHA1 ?? (Options.AddPlaceholders ? Template.RequiredValue + " [Not automatically generated for UMD]" : ""); - info.TracksAndWriteOffsets.ClrMameProData = null; - break; - } - - // Extract info based specifically on RedumpSystem - switch (System) - { - case RedumpSystem.AcornArchimedes: - info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.UK; - break; - - case RedumpSystem.AppleMacintosh: - case RedumpSystem.EnhancedCD: - case RedumpSystem.IBMPCcompatible: - case RedumpSystem.PalmOS: - case RedumpSystem.PocketPC: - case RedumpSystem.RainbowDisc: - if (string.IsNullOrWhiteSpace(info.CommonDiscInfo.Comments)) - info.CommonDiscInfo.Comments += $"[T:ISBN] {(Options.AddPlaceholders ? Template.OptionalValue : "")}"; - - resultProgress?.Report(Result.Success("Running copy protection scan... this might take a while!")); - info.CopyProtection.Protection = await GetCopyProtection(protectionProgress); - resultProgress?.Report(Result.Success("Copy protection scan complete!")); - - break; - - case RedumpSystem.AudioCD: - case RedumpSystem.DVDAudio: - case RedumpSystem.SuperAudioCD: - info.CommonDiscInfo.Category = info.CommonDiscInfo.Category ?? DiscCategory.Audio; - break; - - case RedumpSystem.BandaiPlaydiaQuickInteractiveSystem: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; - break; - - case RedumpSystem.BDVideo: - info.CommonDiscInfo.Category = info.CommonDiscInfo.Category ?? DiscCategory.BonusDiscs; - info.CopyProtection.Protection = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - break; - - case RedumpSystem.CommodoreAmigaCD: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.CommodoreAmigaCD32: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Europe; - break; - - case RedumpSystem.CommodoreAmigaCDTV: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Europe; - break; - - case RedumpSystem.DVDVideo: - info.CommonDiscInfo.Category = info.CommonDiscInfo.Category ?? DiscCategory.BonusDiscs; - break; - - case RedumpSystem.FujitsuFMTownsseries: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; - break; - - case RedumpSystem.FujitsuFMTownsMarty: - info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; - break; - - case RedumpSystem.IncredibleTechnologiesEagle: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.KonamieAmusement: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.KonamiFireBeat: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.KonamiSystemGV: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.KonamiSystem573: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.KonamiTwinkle: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.MattelHyperScan: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.NamcoSegaNintendoTriforce: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.NavisoftNaviken21: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; - break; - - case RedumpSystem.NECPC88series: - info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; - break; - - case RedumpSystem.NECPC98series: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; - break; - - case RedumpSystem.NECPCFXPCFXGA: - info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; - break; - - case RedumpSystem.SegaChihiro: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.SegaDreamcast: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.SegaNaomi: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.SegaNaomi2: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.SegaTitanVideo: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.SharpX68000: - info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; - break; - - case RedumpSystem.SNKNeoGeoCD: - info.CommonDiscInfo.EXEDateBuildDate = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.SonyPlayStation: - resultProgress?.Report(Result.Success("Checking for anti-modchip strings... this might take a while!")); - info.CopyProtection.AntiModchip = await GetPlayStationAntiModchipDetected() ? YesNo.Yes : YesNo.No; - resultProgress?.Report(Result.Success("Anti-modchip string scan complete!")); - - // Special case for DIC only - if (Parameters.InternalProgram == InternalProgram.DiscImageCreator) - { - resultProgress?.Report(Result.Success("Checking for LibCrypt status... this might take a while!")); - GetLibCryptDetected(info, combinedBase); - resultProgress?.Report(Result.Success("LibCrypt status checking complete!")); - } - - break; - - case RedumpSystem.SonyPlayStation2: - info.CommonDiscInfo.LanguageSelection = new LanguageSelection?[] { LanguageSelection.BiosSettings, LanguageSelection.LanguageSelector, LanguageSelection.OptionsMenu }; - break; - - case RedumpSystem.SonyPlayStation3: - info.Extras.DiscKey = (Options.AddPlaceholders ? Template.RequiredValue : ""); - info.Extras.DiscID = (Options.AddPlaceholders ? Template.RequiredValue : ""); - break; - - case RedumpSystem.TomyKissSite: - info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; - break; - - case RedumpSystem.ZAPiTGamesGameWaveFamilyEntertainmentSystem: - info.CopyProtection.Protection = (Options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); - break; - } - - // Set the category if it's not overriden - info.CommonDiscInfo.Category = info.CommonDiscInfo.Category ?? DiscCategory.Games; - - // Comments is one of the few fields with odd handling - if (string.IsNullOrEmpty(info.CommonDiscInfo.Comments)) - info.CommonDiscInfo.Comments = (Options.AddPlaceholders ? Template.OptionalValue : ""); - - return info; - } - - /// - /// Format the output data in a human readable way, separating each printed line into a new item in the list - /// - /// Information object that should contain normalized values - /// List of strings representing each line of an output file, null on error - private List FormatOutputData(SubmissionInfo info) - { - // Check to see if the inputs are valid - if (info == null) - return null; - - try - { - // Sony-printed discs have layers in the opposite order - var system = info.CommonDiscInfo.System; - bool reverseOrder = (system == RedumpSystem.SonyPlayStation2 - || system == RedumpSystem.SonyPlayStation3 - || system == RedumpSystem.SonyPlayStation4); - - // Common Disc Info section - List output = new List { "Common Disc Info:" }; - AddIfExists(output, Template.TitleField, info.CommonDiscInfo.Title, 1); - AddIfExists(output, Template.ForeignTitleField, info.CommonDiscInfo.ForeignTitleNonLatin, 1); - AddIfExists(output, Template.DiscNumberField, info.CommonDiscInfo.DiscNumberLetter, 1); - AddIfExists(output, Template.DiscTitleField, info.CommonDiscInfo.DiscTitle, 1); - AddIfExists(output, Template.SystemField, info.CommonDiscInfo.System.LongName(), 1); - AddIfExists(output, Template.MediaTypeField, GetFixedMediaType( - info.CommonDiscInfo.Media.ToMediaType(), - info.SizeAndChecksums.Size, - info.SizeAndChecksums.Layerbreak, - info.SizeAndChecksums.Layerbreak2, - info.SizeAndChecksums.Layerbreak3), - 1); - AddIfExists(output, Template.CategoryField, info.CommonDiscInfo.Category.LongName(), 1); - AddIfExists(output, Template.MatchingIDsField, info.MatchedIDs, 1); - AddIfExists(output, Template.RegionField, info.CommonDiscInfo.Region.LongName() ?? "SPACE! (CHANGE THIS)", 1); - AddIfExists(output, Template.LanguagesField, (info.CommonDiscInfo.Languages ?? new Language?[] { null }).Select(l => l.LongName() ?? "Klingon (CHANGE THIS)").ToArray(), 1); - AddIfExists(output, Template.PlaystationLanguageSelectionViaField, (info.CommonDiscInfo.LanguageSelection ?? new LanguageSelection?[] { }).Select(l => l.LongName()).ToArray(), 1); - AddIfExists(output, Template.DiscSerialField, info.CommonDiscInfo.Serial, 1); - - // All ringcode information goes in an indented area - output.Add(""); output.Add("\tRingcode Information:"); - - // If we have a triple-layer disc - if (info.SizeAndChecksums.Layerbreak3 != default) - { - AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.MasteringRingField, info.CommonDiscInfo.Layer0MasteringRing, 2); - AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.MasteringSIDField, info.CommonDiscInfo.Layer0MasteringSID, 2); - AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.ToolstampField, info.CommonDiscInfo.Layer0ToolstampMasteringCode, 2); - AddIfExists(output, "Data Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer0MouldSID, 2); - AddIfExists(output, "Data Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer0AdditionalMould, 2); - - AddIfExists(output, "Layer 1 " + Template.MasteringRingField, info.CommonDiscInfo.Layer1MasteringRing, 2); - AddIfExists(output, "Layer 1 " + Template.MasteringSIDField, info.CommonDiscInfo.Layer1MasteringSID, 2); - AddIfExists(output, "Layer 1 " + Template.ToolstampField, info.CommonDiscInfo.Layer1ToolstampMasteringCode, 2); - AddIfExists(output, "Label Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer1MouldSID, 2); - AddIfExists(output, "Label Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer1AdditionalMould, 2); - - AddIfExists(output, "Layer 2 " + Template.MasteringRingField, info.CommonDiscInfo.Layer2MasteringRing, 2); - AddIfExists(output, "Layer 2 " + Template.MasteringSIDField, info.CommonDiscInfo.Layer2MasteringSID, 2); - AddIfExists(output, "Layer 2 " + Template.ToolstampField, info.CommonDiscInfo.Layer2ToolstampMasteringCode, 2); - - AddIfExists(output, (reverseOrder ? "Layer 3 (Inner) " : "Layer 3 (Outer) ") + Template.MasteringRingField, info.CommonDiscInfo.Layer3MasteringRing, 2); - AddIfExists(output, (reverseOrder ? "Layer 3 (Inner) " : "Layer 3 (Outer) ") + Template.MasteringSIDField, info.CommonDiscInfo.Layer3MasteringSID, 2); - AddIfExists(output, (reverseOrder ? "Layer 3 (Inner) " : "Layer 3 (Outer) ") + Template.ToolstampField, info.CommonDiscInfo.Layer3ToolstampMasteringCode, 2); - } - // If we have a triple-layer disc - else if (info.SizeAndChecksums.Layerbreak2 != default) - { - AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.MasteringRingField, info.CommonDiscInfo.Layer0MasteringRing, 2); - AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.MasteringSIDField, info.CommonDiscInfo.Layer0MasteringSID, 2); - AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.ToolstampField, info.CommonDiscInfo.Layer0ToolstampMasteringCode, 2); - AddIfExists(output, "Data Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer0MouldSID, 2); - AddIfExists(output, "Data Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer0AdditionalMould, 2); - - AddIfExists(output, "Layer 1 " + Template.MasteringRingField, info.CommonDiscInfo.Layer1MasteringRing, 2); - AddIfExists(output, "Layer 1 " + Template.MasteringSIDField, info.CommonDiscInfo.Layer1MasteringSID, 2); - AddIfExists(output, "Layer 1 " + Template.ToolstampField, info.CommonDiscInfo.Layer1ToolstampMasteringCode, 2); - AddIfExists(output, "Label Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer1MouldSID, 2); - AddIfExists(output, "Label Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer1AdditionalMould, 2); - - AddIfExists(output, (reverseOrder ? "Layer 2 (Inner) " : "Layer 2 (Outer) ") + Template.MasteringRingField, info.CommonDiscInfo.Layer2MasteringRing, 2); - AddIfExists(output, (reverseOrder ? "Layer 2 (Inner) " : "Layer 2 (Outer) ") + Template.MasteringSIDField, info.CommonDiscInfo.Layer2MasteringSID, 2); - AddIfExists(output, (reverseOrder ? "Layer 2 (Inner) " : "Layer 2 (Outer) ") + Template.ToolstampField, info.CommonDiscInfo.Layer2ToolstampMasteringCode, 2); - } - // If we have a dual-layer disc - else if (info.SizeAndChecksums.Layerbreak != default) - { - AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.MasteringRingField, info.CommonDiscInfo.Layer0MasteringRing, 2); - AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.MasteringSIDField, info.CommonDiscInfo.Layer0MasteringSID, 2); - AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.ToolstampField, info.CommonDiscInfo.Layer0ToolstampMasteringCode, 2); - AddIfExists(output, "Data Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer0MouldSID, 2); - AddIfExists(output, "Data Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer0AdditionalMould, 2); - - AddIfExists(output, (reverseOrder ? "Layer 1 (Inner) " : "Layer 1 (Outer) ") + Template.MasteringRingField, info.CommonDiscInfo.Layer1MasteringRing, 2); - AddIfExists(output, (reverseOrder ? "Layer 1 (Inner) " : "Layer 1 (Outer) ") + Template.MasteringSIDField, info.CommonDiscInfo.Layer1MasteringSID, 2); - AddIfExists(output, (reverseOrder ? "Layer 1 (Inner) " : "Layer 1 (Outer) ") + Template.ToolstampField, info.CommonDiscInfo.Layer1ToolstampMasteringCode, 2); - AddIfExists(output, "Label Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer1MouldSID, 2); - AddIfExists(output, "Label Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer1AdditionalMould, 2); - } - // If we have a single-layer disc - else - { - AddIfExists(output, "Data Side " + Template.MasteringRingField, info.CommonDiscInfo.Layer0MasteringRing, 2); - AddIfExists(output, "Data Side " + Template.MasteringSIDField, info.CommonDiscInfo.Layer0MasteringSID, 2); - AddIfExists(output, "Data Side " + Template.ToolstampField, info.CommonDiscInfo.Layer0ToolstampMasteringCode, 2); - AddIfExists(output, "Data Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer0MouldSID, 2); - AddIfExists(output, "Data Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer0AdditionalMould, 2); - - AddIfExists(output, "Label Side " + Template.MasteringRingField, info.CommonDiscInfo.Layer1MasteringRing, 2); - AddIfExists(output, "Label Side " + Template.MasteringSIDField, info.CommonDiscInfo.Layer1MasteringSID, 2); - AddIfExists(output, "Label Side " + Template.ToolstampField, info.CommonDiscInfo.Layer1ToolstampMasteringCode, 2); - AddIfExists(output, "Label Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer1MouldSID, 2); - AddIfExists(output, "Label Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer1AdditionalMould, 2); - } - - AddIfExists(output, Template.BarcodeField, info.CommonDiscInfo.Barcode, 1); - AddIfExists(output, Template.EXEDateBuildDate, info.CommonDiscInfo.EXEDateBuildDate, 1); - AddIfExists(output, Template.ErrorCountField, info.CommonDiscInfo.ErrorsCount, 1); - AddIfExists(output, Template.CommentsField, info.CommonDiscInfo.Comments.Trim(), 1); - AddIfExists(output, Template.ContentsField, info.CommonDiscInfo.Contents.Trim(), 1); - - // Version and Editions section - output.Add(""); output.Add("Version and Editions:"); - AddIfExists(output, Template.VersionField, info.VersionAndEditions.Version, 1); - AddIfExists(output, Template.EditionField, info.VersionAndEditions.OtherEditions, 1); - - // EDC section - if (info.CommonDiscInfo.System == RedumpSystem.SonyPlayStation) - { - output.Add(""); output.Add("EDC:"); - AddIfExists(output, Template.PlayStationEDCField, info.EDC.EDC.LongName(), 1); - } - - // Parent/Clone Relationship section - // output.Add(""); output.Add("Parent/Clone Relationship:"); - // AddIfExists(output, Template.ParentIDField, info.ParentID); - // AddIfExists(output, Template.RegionalParentField, info.RegionalParent.ToString()); - - // Extras section - if (info.Extras.PVD != null || info.Extras.PIC != null || info.Extras.BCA != null) - { - output.Add(""); output.Add("Extras:"); - AddIfExists(output, Template.PVDField, info.Extras.PVD?.Trim(), 1); - AddIfExists(output, Template.PlayStation3WiiDiscKeyField, info.Extras.DiscKey, 1); - AddIfExists(output, Template.PlayStation3DiscIDField, info.Extras.DiscID, 1); - AddIfExists(output, Template.PICField, info.Extras.PIC, 1); - AddIfExists(output, Template.HeaderField, info.Extras.Header, 1); - AddIfExists(output, Template.GameCubeWiiBCAField, info.Extras.BCA, 1); - AddIfExists(output, Template.XBOXSSRanges, info.Extras.SecuritySectorRanges, 1); - } - - // Copy Protection section - if (info.CopyProtection.Protection != null - || info.CopyProtection.AntiModchip != YesNo.NULL - || info.CopyProtection.LibCrypt != YesNo.NULL) - { - output.Add(""); output.Add("Copy Protection:"); - if (info.CommonDiscInfo.System == RedumpSystem.SonyPlayStation) - { - AddIfExists(output, Template.PlayStationAntiModchipField, info.CopyProtection.AntiModchip.LongName(), 1); - AddIfExists(output, Template.PlayStationLibCryptField, info.CopyProtection.LibCrypt.LongName(), 1); - AddIfExists(output, Template.SubIntentionField, info.CopyProtection.LibCryptData, 1); - } - - AddIfExists(output, Template.CopyProtectionField, info.CopyProtection.Protection, 1); - AddIfExists(output, Template.SubIntentionField, info.CopyProtection.SecuROMData, 1); - } - - // Dumpers and Status section - // output.Add(""); output.Add("Dumpers and Status"); - // AddIfExists(output, Template.StatusField, info.Status.Name()); - // AddIfExists(output, Template.OtherDumpersField, info.OtherDumpers); - - // Tracks and Write Offsets section - if (!string.IsNullOrWhiteSpace(info.TracksAndWriteOffsets.ClrMameProData)) - { - output.Add(""); output.Add("Tracks and Write Offsets:"); - AddIfExists(output, Template.DATField, info.TracksAndWriteOffsets.ClrMameProData + "\n", 1); - AddIfExists(output, Template.CuesheetField, info.TracksAndWriteOffsets.Cuesheet, 1); - AddIfExists(output, Template.WriteOffsetField, info.TracksAndWriteOffsets.OtherWriteOffsets, 1); - } - // Size & Checksum section - else - { - output.Add(""); output.Add("Size & Checksum:"); - AddIfExists(output, Template.LayerbreakField, (info.SizeAndChecksums.Layerbreak == default ? null : info.SizeAndChecksums.Layerbreak.ToString()), 1); - AddIfExists(output, Template.SizeField, info.SizeAndChecksums.Size.ToString(), 1); - AddIfExists(output, Template.CRC32Field, info.SizeAndChecksums.CRC32, 1); - AddIfExists(output, Template.MD5Field, info.SizeAndChecksums.MD5, 1); - AddIfExists(output, Template.SHA1Field, info.SizeAndChecksums.SHA1, 1); - } - - // Make sure there aren't any instances of two blank lines in a row - string last = null; - for (int i = 0; i < output.Count;) - { - if (output[i] == last && string.IsNullOrWhiteSpace(last)) - { - output.RemoveAt(i); - } - else - { - last = output[i]; - i++; - } - } - - return output; - } - catch - { - // We don't care what the error is - return null; - } - } - - /// - /// Get the adjusted name of the media baed on layers, if applicable - /// - /// MediaType to get the proper name for - /// Size of the current media - /// First layerbreak value, as applicable - /// Second layerbreak value, as applicable - /// Third ayerbreak value, as applicable - /// String representation of the media, including layer specification - private string GetFixedMediaType(MediaType? mediaType, long size, long layerbreak, long layerbreak2, long layerbreak3) - { - switch (mediaType) - { - case MediaType.DVD: - if (layerbreak != default) - return $"{mediaType.LongName()}-9"; - else - return $"{mediaType.LongName()}-5"; - - case MediaType.BluRay: - if (layerbreak3 != default) - return $"{mediaType.LongName()}-128"; - else if (layerbreak2 != default) - return $"{mediaType.LongName()}-100"; - else if (layerbreak != default && size > 53_687_063_712) - return $"{mediaType.LongName()}-66"; - else if (layerbreak != default) - return $"{mediaType.LongName()}-50"; - else if (size > 26_843_531_856) - return $"{mediaType.LongName()}-33"; - else - return $"{mediaType.LongName()}-25"; - - case MediaType.UMD: - if (layerbreak != default) - return $"{mediaType.LongName()}-DL"; - else - return $"{mediaType.LongName()}-SL"; - - default: - return mediaType.LongName(); - } - } - /// /// Validate the current environment is ready for a dump /// @@ -1252,7 +499,7 @@ namespace MPF.Library return Result.Failure("Error! Current configuration is not supported!"); // Fix the output paths, just in case - (OutputDirectory, OutputFilename) = NormalizeOutputPaths(OutputDirectory, OutputFilename, Options.InternalProgram == InternalProgram.DiscImageCreator); + (OutputDirectory, OutputFilename) = InfoTool.NormalizeOutputPaths(OutputDirectory, OutputFilename, Options.InternalProgram == InternalProgram.DiscImageCreator); // Validate that the output path isn't on the dumping drive string fullOutputPath = Path.GetFullPath(Path.Combine(OutputDirectory, OutputFilename)); @@ -1272,499 +519,6 @@ namespace MPF.Library return Tools.GetSupportStatus(System, Type); } - /// - /// Write the data to the output folder - /// - /// Preformatted list of lines to write out to the file - /// True on success, false on error - private bool WriteOutputData(List lines) - { - // Check to see if the inputs are valid - if (lines == null) - return false; - - // Now write out to a generic file - try - { - using (StreamWriter sw = new StreamWriter(File.Open(Path.Combine(OutputDirectory, "!submissionInfo.txt"), FileMode.Create, FileAccess.Write))) - { - foreach (string line in lines) - sw.WriteLine(line); - } - } - catch - { - // We don't care what the error is right now - return false; - } - - return true; - } - - /// - /// Write the data to the output folder - /// - /// SubmissionInfo object representing the JSON to write out to the file - /// True on success, false on error - private bool WriteOutputData(SubmissionInfo info) - { - // Check to see if the input is valid - if (info == null) - return false; - - // Now write out to the JSON - try - { - using (var fs = File.Create(Path.Combine(OutputDirectory, "!submissionInfo.json.gz"))) - using (var gs = new GZipStream(fs, CompressionMode.Compress)) - { - string json = JsonConvert.SerializeObject(info, Formatting.Indented); - byte[] jsonBytes = Encoding.UTF8.GetBytes(json); - gs.Write(jsonBytes, 0, jsonBytes.Length); - } - } - catch - { - // We don't care what the error is right now - return false; - } - - return true; - } - - /// - /// Compress log files to save space - /// - /// True if the process succeeded, false otherwise - private bool CompressLogFiles() - { - // Prepare the necessary paths - string outputFilename = Path.GetFileNameWithoutExtension(OutputFilename); - string combinedBase = Path.Combine(OutputDirectory, outputFilename); - string archiveName = combinedBase + "_logs.zip"; - - // Get the list of log files from the parameters object - var files = Parameters.GetLogFilePaths(combinedBase); - if (!files.Any()) - return true; - - // Add the log files to the archive and delete the uncompressed file after - ZipArchive zf = null; - try - { - zf = ZipFile.Open(archiveName, ZipArchiveMode.Create); - foreach (string file in files) - { - string entryName = file.Substring(OutputDirectory.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); - zf.CreateEntryFromFile(file, entryName); - - try - { - File.Delete(file); - } - catch { } - } - - return true; - } - catch - { - return false; - } - finally - { - zf?.Dispose(); - } - } - - #endregion - - #region Information Extraction - - /// - /// Get the current copy protection scheme, if possible - /// - /// Optional progress callback - /// Copy protection scheme if possible, null on error - private async Task GetCopyProtection(IProgress progress = null) - { - if (Options.ScanForProtection) - { - (bool success, string output) = await Protection.RunProtectionScanOnPath($"{Drive.Letter}:\\", this.Options, progress); - if (success) - return output; - else - return "An error occurred while scanning!"; - } - - return "(CHECK WITH PROTECTIONID)"; - } - - /// - /// Get the full lines from the input file, if possible - /// - /// file location - /// True if should read as binary, false otherwise (default) - /// Full text of the file, null on error - private static string GetFullFile(string filename, bool binary = false) - { - // If the file doesn't exist, we can't get info from it - if (!File.Exists(filename)) - return null; - - // If we're reading as binary - if (binary) - { - byte[] bytes = File.ReadAllBytes(filename); - return BitConverter.ToString(bytes).Replace("-", string.Empty); - } - - return string.Join("\n", File.ReadAllLines(filename)); - } - - /// - /// Get if LibCrypt data is detected in the subchannel file, if possible - /// - /// Base submission info to fill in specifics for - /// Base filename and path to use for checking - /// Status of the LibCrypt data, if possible - private void GetLibCryptDetected(SubmissionInfo info, string basePath) - { - bool? psLibCryptStatus = Protection.GetLibCryptDetected(basePath + ".sub"); - if (psLibCryptStatus == true) - { - // Guard against false positives - if (File.Exists(basePath + "_subIntention.txt")) - { - string libCryptData = GetFullFile(basePath + "_subIntention.txt") ?? ""; - if (string.IsNullOrEmpty(libCryptData)) - { - info.CopyProtection.LibCrypt = YesNo.No; - } - else - { - info.CopyProtection.LibCrypt = YesNo.Yes; - info.CopyProtection.LibCryptData = libCryptData; - } - } - else - { - info.CopyProtection.LibCrypt = YesNo.No; - } - } - else if (psLibCryptStatus == false) - { - info.CopyProtection.LibCrypt = YesNo.No; - } - else - { - info.CopyProtection.LibCrypt = YesNo.NULL; - info.CopyProtection.LibCryptData = "LibCrypt could not be detected because subchannel file is missing"; - } - } - - /// - /// Get the existance of an anti-modchip string from a PlayStation disc, if possible - /// - /// Anti-modchip existance if possible, false on error - private async Task GetPlayStationAntiModchipDetected() - { - return await Protection.GetPlayStationAntiModchipDetected($"{Drive.Letter}:\\"); - } - - /// - /// Get the split values for ISO-based media - /// - /// String representing the combined hash data - /// True if extraction was successful, false otherwise - private bool GetISOHashValues(string hashData, out long size, out string crc32, out string md5, out string sha1) - { - size = -1; crc32 = null; md5 = null; sha1 = null; - - if (string.IsNullOrWhiteSpace(hashData)) - return false; - - Regex hashreg = new Regex(@" - /// Add the properly formatted key and value, if possible - /// - /// Output list - /// Name of the output key to write - /// Name of the output value to write - /// Number of tabs to indent the line - private void AddIfExists(List output, string key, string value, int indent) - { - // If there's no valid value to write - if (value == null) - return; - - string prefix = ""; - for (int i = 0; i < indent; i++) - prefix += "\t"; - - // If the value contains a newline - value = value.Replace("\r\n", "\n"); - if (value.Contains("\n")) - { - output.Add(prefix + key + ":"); output.Add(""); - string[] values = value.Split('\n'); - foreach (string val in values) - output.Add(val); - - output.Add(""); - } - - // For all regular values - else - { - output.Add(prefix + key + ": " + value); - } - } - - /// - /// Add the properly formatted key and value, if possible - /// - /// Output list - /// Name of the output key to write - /// Name of the output value to write - /// Number of tabs to indent the line - private void AddIfExists(List output, string key, string[] value, int indent) - { - // If there's no valid value to write - if (value == null || value.Length == 0) - return; - - AddIfExists(output, key, string.Join(", ", value), indent); - } - - /// - /// Add the properly formatted key and value, if possible - /// - /// Output list - /// Name of the output key to write - /// Name of the output value to write - /// Number of tabs to indent the line - private void AddIfExists(List output, string key, List value, int indent) - { - // If there's no valid value to write - if (value == null || value.Count() == 0) - return; - - AddIfExists(output, key, string.Join(", ", value.Select(o => o.ToString())), indent); - } - - #endregion - - #region Web Calls - - /// - /// Fill out an existing SubmissionInfo object based on a disc page - /// - /// RedumpWebClient for making the connection - /// Existing SubmissionInfo object to fill - /// Redump disc ID to retrieve - private void FillFromId(RedumpWebClient wc, SubmissionInfo info, int id) - { - string discData = wc.DownloadSingleSiteID(id); - if (string.IsNullOrEmpty(discData)) - return; - - // Title, Disc Number/Letter, Disc Title - var match = Constants.TitleRegex.Match(discData); - if (match.Success) - { - string title = WebUtility.HtmlDecode(match.Groups[1].Value); - - // If we have parenthesis, title is everything before the first one - int firstParenLocation = title.IndexOf(" ("); - if (firstParenLocation >= 0) - { - info.CommonDiscInfo.Title = title.Substring(0, firstParenLocation); - var subMatches = Constants.DiscNumberLetterRegex.Match(title); - for (int i = 1; i < subMatches.Groups.Count; i++) - { - string subMatch = subMatches.Groups[i].Value; - - // Disc number or letter - if (subMatch.StartsWith("Disc")) - info.CommonDiscInfo.DiscNumberLetter = subMatch.Remove(0, "Disc ".Length); - - // Disc title - else - info.CommonDiscInfo.DiscTitle = subMatch; - } - } - // Otherwise, leave the title as-is - else - { - info.CommonDiscInfo.Title = title; - } - } - - // Foreign Title - match = Constants.ForeignTitleRegex.Match(discData); - if (match.Success) - info.CommonDiscInfo.ForeignTitleNonLatin = WebUtility.HtmlDecode(match.Groups[1].Value); - else - info.CommonDiscInfo.ForeignTitleNonLatin = null; - - // Category - match = Constants.CategoryRegex.Match(discData); - if (match.Success) - info.CommonDiscInfo.Category = Extensions.ToDiscCategory(match.Groups[1].Value); - else - info.CommonDiscInfo.Category = DiscCategory.Games; - - // Region - match = Constants.RegionRegex.Match(discData); - if (match.Success) - info.CommonDiscInfo.Region = Extensions.ToRegion(match.Groups[1].Value); - - // Languages - var matches = Constants.LanguagesRegex.Matches(discData); - if (matches.Count > 0) - { - List tempLanguages = new List(); - foreach (Match submatch in matches) - tempLanguages.Add(Extensions.ToLanguage(submatch.Groups[1].Value)); - - info.CommonDiscInfo.Languages = tempLanguages.Where(l => l != null).ToArray(); - } - - // Error count - match = Constants.ErrorCountRegex.Match(discData); - if (match.Success) - { - // If the error count is empty, fill from the page - if (string.IsNullOrEmpty(info.CommonDiscInfo.ErrorsCount)) - info.CommonDiscInfo.ErrorsCount = match.Groups[1].Value; - } - - // Version - match = Constants.VersionRegex.Match(discData); - if (match.Success) - info.VersionAndEditions.Version = WebUtility.HtmlDecode(match.Groups[1].Value); - - // Dumpers - matches = Constants.DumpersRegex.Matches(discData); - if (matches.Count > 0) - { - // Start with any currently listed dumpers - List tempDumpers = new List(); - if (info.DumpersAndStatus.Dumpers.Length > 0) - { - foreach (string dumper in info.DumpersAndStatus.Dumpers) - tempDumpers.Add(dumper); - } - - foreach (Match submatch in matches) - tempDumpers.Add(WebUtility.HtmlDecode(submatch.Groups[1].Value)); - - info.DumpersAndStatus.Dumpers = tempDumpers.ToArray(); - } - - // Comments - match = Constants.CommentsRegex.Match(discData); - if (match.Success) - { - info.CommonDiscInfo.Comments += (string.IsNullOrEmpty(info.CommonDiscInfo.Comments) ? string.Empty : "\n") - + WebUtility.HtmlDecode(match.Groups[1].Value) - .Replace("
", "\n") - .Replace("ISBN", "[T:ISBN]") + "\n"; - } - - // Contents - match = Constants.ContentsRegex.Match(discData); - if (match.Success) - { - info.CommonDiscInfo.Contents = WebUtility.HtmlDecode(match.Groups[1].Value) - .Replace("
", "\n") - .Replace("", ""); - info.CommonDiscInfo.Contents = Regex.Replace(info.CommonDiscInfo.Contents, @"
", ""); - } - - // Added - match = Constants.AddedRegex.Match(discData); - if (match.Success) - { - if (DateTime.TryParse(match.Groups[1].Value, out DateTime added)) - info.Added = added; - else - info.Added = null; - } - - // Last Modified - match = Constants.LastModifiedRegex.Match(discData); - if (match.Success) - { - if (DateTime.TryParse(match.Groups[1].Value, out DateTime lastModified)) - info.LastModified = lastModified; - else - info.LastModified = null; - } - } - - /// - /// List the disc IDs associated with a given quicksearch query - /// - /// RedumpWebClient for making the connection - /// Query string to attempt to search for - /// All disc IDs for the given query, null on error - private List ListSearchResults(RedumpWebClient wc, string query) - { - List ids = new List(); - - // Strip quotes - query = query.Trim('"', '\''); - - // Special characters become dashes - query = query.Replace(' ', '-'); - query = query.Replace('/', '-'); - query = query.Replace('\\', '/'); - - // Lowercase is defined per language - query = query.ToLowerInvariant(); - - // Keep getting quicksearch pages until there are none left - try - { - int pageNumber = 1; - while (true) - { - List pageIds = wc.CheckSingleSitePage(string.Format(Constants.QuickSearchUrl, query, pageNumber++)); - ids.AddRange(pageIds); - if (pageIds.Count <= 1) - break; - } - } - catch (Exception ex) - { - Console.WriteLine($"An exception occurred while trying to log in: {ex}"); - return null; - } - - return ids; - } - #endregion } } diff --git a/MPF.Library/InfoTool.cs b/MPF.Library/InfoTool.cs new file mode 100644 index 00000000..435a83b0 --- /dev/null +++ b/MPF.Library/InfoTool.cs @@ -0,0 +1,1295 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using BurnOutSharp; +using MPF.Core.Data; +using MPF.Modules; +using Newtonsoft.Json; +using RedumpLib.Data; +using RedumpLib.Web; + +namespace MPF.Library +{ + public static class InfoTool + { + #region Information Extraction + + /// + /// Extract all of the possible information from a given input combination + /// + /// Output folder to write to + /// Output filename to use as the base path + /// Drive object representing the current drive + /// Currently selected system + /// Currently selected media type + /// Options object representing user-defined options + /// Parameters object representing what to send to the internal program + /// Optional result progress callback + /// Optional protection progress callback + /// SubmissionInfo populated based on outputs, null on error + public static async Task ExtractOutputInformation( + string outputDirectory, + string outputFilename, + Drive drive, + RedumpSystem? system, + MediaType? mediaType, + Options options, + BaseParameters parameters, + IProgress resultProgress = null, + IProgress protectionProgress = null) + { + // Ensure the current disc combination should exist + if (!system.MediaTypes().Contains(mediaType)) + return null; + + // Sanitize the output filename to strip off any potential extension + outputFilename = Path.GetFileNameWithoutExtension(outputFilename); + + // Check that all of the relevant files are there + (bool foundFiles, List missingFiles) = FoundAllFiles(outputDirectory, outputFilename, parameters, false); + if (!foundFiles) + { + resultProgress.Report(Result.Failure($"There were files missing from the output:\n{string.Join("\n", missingFiles)}")); + return null; + } + + // Create the SubmissionInfo object with all user-inputted values by default + string combinedBase = Path.Combine(outputDirectory, outputFilename); + SubmissionInfo info = new SubmissionInfo() + { + CommonDiscInfo = new CommonDiscInfoSection() + { + System = system, + Media = mediaType.ToDiscType(), + Title = (options.AddPlaceholders ? Template.RequiredValue : ""), + ForeignTitleNonLatin = (options.AddPlaceholders ? Template.OptionalValue : ""), + DiscNumberLetter = (options.AddPlaceholders ? Template.OptionalValue : ""), + DiscTitle = (options.AddPlaceholders ? Template.OptionalValue : ""), + Category = null, + Region = null, + Languages = null, + Serial = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""), + Barcode = (options.AddPlaceholders ? Template.OptionalValue : ""), + Contents = (options.AddPlaceholders ? Template.OptionalValue : ""), + }, + VersionAndEditions = new VersionAndEditionsSection() + { + Version = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""), + OtherEditions = (options.AddPlaceholders ? "Original (VERIFY THIS)" : ""), + }, + TracksAndWriteOffsets = new TracksAndWriteOffsetsSection(), + }; + + // Get specific tool output handling + parameters.GenerateSubmissionInfo(info, combinedBase, drive, options.IncludeArtifacts); + + // Get a list of matching IDs for each line in the DAT + if (!string.IsNullOrEmpty(info.TracksAndWriteOffsets.ClrMameProData) && options.HasRedumpLogin) + { + // Set the current dumper based on username + info.DumpersAndStatus.Dumpers = new string[] { options.RedumpUsername }; + + info.MatchedIDs = new List(); + using (RedumpWebClient wc = new RedumpWebClient()) + { + // Login to Redump + bool? loggedIn = wc.Login(options.RedumpUsername, options.RedumpPassword); + if (loggedIn == null) + { + resultProgress?.Report(Result.Failure("There was an unknown error connecting to Redump")); + } + else if (loggedIn == true) + { + // Loop through all of the hashdata to find matching IDs + resultProgress?.Report(Result.Success("Finding disc matches on Redump...")); + string[] splitData = info.TracksAndWriteOffsets.ClrMameProData.Split('\n'); + foreach (string hashData in splitData) + { + if (GetISOHashValues(hashData, out long _, out string _, out string _, out string sha1)) + { + // Get all matching IDs for the track + List newIds = ListSearchResults(wc, sha1); + + // If we got null back, there was an error + if (newIds == null) + { + resultProgress?.Report(Result.Failure("There was an unknown error retrieving information from Redump")); + break; + } + + // If no IDs match any track, then we don't match a disc at all + if (!newIds.Any()) + { + info.MatchedIDs = new List(); + break; + } + + // If we have multiple tracks, only take IDs that are in common + if (info.MatchedIDs.Any()) + info.MatchedIDs = info.MatchedIDs.Intersect(newIds).ToList(); + + // If we're on the first track, all IDs are added + else + info.MatchedIDs = newIds; + } + } + + resultProgress?.Report(Result.Success("Match finding complete! " + (info.MatchedIDs.Count > 0 ? "Matched IDs: " + string.Join(",", info.MatchedIDs) : "No matches found"))); + + // If we have exactly 1 ID, we can grab a bunch of info from it + if (info.MatchedIDs.Count == 1) + { + resultProgress?.Report(Result.Success($"Filling fields from existing ID {info.MatchedIDs[0]}...")); + FillFromId(wc, info, info.MatchedIDs[0]); + resultProgress?.Report(Result.Success("Information filling complete!")); + } + } + } + } + + // If we have both ClrMamePro and Size and Checksums data, remove the ClrMamePro + if (!string.IsNullOrWhiteSpace(info.SizeAndChecksums.CRC32)) + info.TracksAndWriteOffsets.ClrMameProData = null; + + // Extract info based generically on MediaType + switch (mediaType) + { + case MediaType.CDROM: + case MediaType.GDROM: // TODO: Verify GD-ROM outputs this + info.CommonDiscInfo.Layer0MasteringRing = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MasteringSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0ToolstampMasteringCode = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MouldSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer1MouldSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0AdditionalMould = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + break; + + case MediaType.DVD: + case MediaType.HDDVD: + case MediaType.BluRay: + // If we have a single-layer disc + if (info.SizeAndChecksums.Layerbreak == default) + { + info.CommonDiscInfo.Layer0MasteringRing = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MasteringSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0ToolstampMasteringCode = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MouldSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer1MouldSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0AdditionalMould = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + } + // If we have a dual-layer disc + else + { + info.CommonDiscInfo.Layer0MasteringRing = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MasteringSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0ToolstampMasteringCode = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MouldSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0AdditionalMould = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + + info.CommonDiscInfo.Layer1MasteringRing = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer1MasteringSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer1ToolstampMasteringCode = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer1MouldSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + } + + break; + + case MediaType.NintendoGameCubeGameDisc: + info.CommonDiscInfo.Layer0MasteringRing = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MasteringSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0ToolstampMasteringCode = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MouldSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer1MouldSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0AdditionalMould = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.Extras.BCA = info.Extras.BCA ?? (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case MediaType.NintendoWiiOpticalDisc: + // If we have a single-layer disc + if (info.SizeAndChecksums.Layerbreak == default) + { + info.CommonDiscInfo.Layer0MasteringRing = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MasteringSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0ToolstampMasteringCode = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MouldSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer1MouldSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0AdditionalMould = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + } + // If we have a dual-layer disc + else + { + info.CommonDiscInfo.Layer0MasteringRing = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MasteringSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0ToolstampMasteringCode = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MouldSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0AdditionalMould = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + + info.CommonDiscInfo.Layer1MasteringRing = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer1MasteringSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer1ToolstampMasteringCode = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer1MouldSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + } + + info.Extras.DiscKey = (options.AddPlaceholders ? Template.RequiredValue : ""); + info.Extras.BCA = info.Extras.BCA ?? (options.AddPlaceholders ? Template.RequiredValue : ""); + + break; + + case MediaType.UMD: + // If we have a single-layer disc + if (info.SizeAndChecksums.Layerbreak == default) + { + info.CommonDiscInfo.Layer0MasteringRing = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MasteringSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0ToolstampMasteringCode = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MouldSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + } + // If we have a dual-layer disc + else + { + info.CommonDiscInfo.Layer0MasteringRing = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MasteringSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0ToolstampMasteringCode = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer0MouldSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + + info.CommonDiscInfo.Layer1MasteringRing = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer1MasteringSID = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + info.CommonDiscInfo.Layer1ToolstampMasteringCode = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + } + + info.SizeAndChecksums.CRC32 = info.SizeAndChecksums.CRC32 ?? (options.AddPlaceholders ? Template.RequiredValue + " [Not automatically generated for UMD]" : ""); + info.SizeAndChecksums.MD5 = info.SizeAndChecksums.MD5 ?? (options.AddPlaceholders ? Template.RequiredValue + " [Not automatically generated for UMD]" : ""); + info.SizeAndChecksums.SHA1 = info.SizeAndChecksums.SHA1 ?? (options.AddPlaceholders ? Template.RequiredValue + " [Not automatically generated for UMD]" : ""); + info.TracksAndWriteOffsets.ClrMameProData = null; + break; + } + + // Extract info based specifically on RedumpSystem + switch (system) + { + case RedumpSystem.AcornArchimedes: + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.UK; + break; + + case RedumpSystem.AppleMacintosh: + case RedumpSystem.EnhancedCD: + case RedumpSystem.IBMPCcompatible: + case RedumpSystem.PalmOS: + case RedumpSystem.PocketPC: + case RedumpSystem.RainbowDisc: + if (string.IsNullOrWhiteSpace(info.CommonDiscInfo.Comments)) + info.CommonDiscInfo.Comments += $"[T:ISBN] {(options.AddPlaceholders ? Template.OptionalValue : "")}"; + + resultProgress?.Report(Result.Success("Running copy protection scan... this might take a while!")); + info.CopyProtection.Protection = await GetCopyProtection(drive, options, protectionProgress); + resultProgress?.Report(Result.Success("Copy protection scan complete!")); + + break; + + case RedumpSystem.AudioCD: + case RedumpSystem.DVDAudio: + case RedumpSystem.SuperAudioCD: + info.CommonDiscInfo.Category = info.CommonDiscInfo.Category ?? DiscCategory.Audio; + break; + + case RedumpSystem.BandaiPlaydiaQuickInteractiveSystem: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; + break; + + case RedumpSystem.BDVideo: + info.CommonDiscInfo.Category = info.CommonDiscInfo.Category ?? DiscCategory.BonusDiscs; + info.CopyProtection.Protection = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + break; + + case RedumpSystem.CommodoreAmigaCD: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.CommodoreAmigaCD32: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Europe; + break; + + case RedumpSystem.CommodoreAmigaCDTV: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Europe; + break; + + case RedumpSystem.DVDVideo: + info.CommonDiscInfo.Category = info.CommonDiscInfo.Category ?? DiscCategory.BonusDiscs; + break; + + case RedumpSystem.FujitsuFMTownsseries: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; + break; + + case RedumpSystem.FujitsuFMTownsMarty: + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; + break; + + case RedumpSystem.IncredibleTechnologiesEagle: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.KonamieAmusement: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.KonamiFireBeat: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.KonamiSystemGV: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.KonamiSystem573: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.KonamiTwinkle: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.MattelHyperScan: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.NamcoSegaNintendoTriforce: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.NavisoftNaviken21: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; + break; + + case RedumpSystem.NECPC88series: + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; + break; + + case RedumpSystem.NECPC98series: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; + break; + + case RedumpSystem.NECPCFXPCFXGA: + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; + break; + + case RedumpSystem.SegaChihiro: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.SegaDreamcast: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.SegaNaomi: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.SegaNaomi2: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.SegaTitanVideo: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.SharpX68000: + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; + break; + + case RedumpSystem.SNKNeoGeoCD: + info.CommonDiscInfo.EXEDateBuildDate = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.SonyPlayStation: + resultProgress?.Report(Result.Success("Checking for anti-modchip strings... this might take a while!")); + info.CopyProtection.AntiModchip = await GetAntiModchipDetected(drive) ? YesNo.Yes : YesNo.No; + resultProgress?.Report(Result.Success("Anti-modchip string scan complete!")); + + // Special case for DIC only + if (parameters.InternalProgram == InternalProgram.DiscImageCreator) + { + resultProgress?.Report(Result.Success("Checking for LibCrypt status... this might take a while!")); + GetLibCryptDetected(info, combinedBase); + resultProgress?.Report(Result.Success("LibCrypt status checking complete!")); + } + + break; + + case RedumpSystem.SonyPlayStation2: + info.CommonDiscInfo.LanguageSelection = new LanguageSelection?[] { LanguageSelection.BiosSettings, LanguageSelection.LanguageSelector, LanguageSelection.OptionsMenu }; + break; + + case RedumpSystem.SonyPlayStation3: + info.Extras.DiscKey = (options.AddPlaceholders ? Template.RequiredValue : ""); + info.Extras.DiscID = (options.AddPlaceholders ? Template.RequiredValue : ""); + break; + + case RedumpSystem.TomyKissSite: + info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan; + break; + + case RedumpSystem.ZAPiTGamesGameWaveFamilyEntertainmentSystem: + info.CopyProtection.Protection = (options.AddPlaceholders ? Template.RequiredIfExistsValue : ""); + break; + } + + // Set the category if it's not overriden + info.CommonDiscInfo.Category = info.CommonDiscInfo.Category ?? DiscCategory.Games; + + // Comments is one of the few fields with odd handling + if (string.IsNullOrEmpty(info.CommonDiscInfo.Comments)) + info.CommonDiscInfo.Comments = (options.AddPlaceholders ? Template.OptionalValue : ""); + + return info; + } + + /// + /// Ensures that all required output files have been created + /// + /// Output folder to write to + /// Output filename to use as the base path + /// Parameters object representing what to send to the internal program + /// True if this is a check done before a dump, false if done after + /// Tuple of true if all required files exist, false otherwise and a list representing missing files + public static (bool, List) FoundAllFiles(string outputDirectory, string outputFilename, BaseParameters parameters, bool preCheck) + { + // First, sanitized the output filename to strip off any potential extension + outputFilename = Path.GetFileNameWithoutExtension(outputFilename); + + // Then get the base path for all checking + string basePath = Path.Combine(outputDirectory, outputFilename); + + // Finally, let the parameters say if all files exist + return parameters.CheckAllOutputFilesExist(basePath, preCheck); + } + + /// + /// Get the existance of an anti-modchip string from a PlayStation disc, if possible + /// + /// Drive object representing the current drive + /// Anti-modchip existence if possible, false on error + private static async Task GetAntiModchipDetected(Drive drive) + => await Protection.GetPlayStationAntiModchipDetected($"{drive.Letter}:\\"); + + /// + /// Get the current copy protection scheme, if possible + /// + /// Drive object representing the current drive + /// Options object that determines what to scan + /// Optional progress callback + /// Copy protection scheme if possible, null on error + private static async Task GetCopyProtection(Drive drive, Options options, IProgress progress = null) + { + if (options.ScanForProtection) + { + (bool success, string output) = await Protection.RunProtectionScanOnPath($"{drive.Letter}:\\", options, progress); + if (success) + return output; + else + return "An error occurred while scanning!"; + } + + return "(CHECK WITH PROTECTIONID)"; + } + + /// + /// Get the full lines from the input file, if possible + /// + /// file location + /// True if should read as binary, false otherwise (default) + /// Full text of the file, null on error + private static string GetFullFile(string filename, bool binary = false) + { + // If the file doesn't exist, we can't get info from it + if (!File.Exists(filename)) + return null; + + // If we're reading as binary + if (binary) + { + byte[] bytes = File.ReadAllBytes(filename); + return BitConverter.ToString(bytes).Replace("-", string.Empty); + } + + return string.Join("\n", File.ReadAllLines(filename)); + } + + /// + /// Get the split values for ISO-based media + /// + /// String representing the combined hash data + /// True if extraction was successful, false otherwise + private static bool GetISOHashValues(string hashData, out long size, out string crc32, out string md5, out string sha1) + { + size = -1; crc32 = null; md5 = null; sha1 = null; + + if (string.IsNullOrWhiteSpace(hashData)) + return false; + + Regex hashreg = new Regex(@" + /// Get if LibCrypt data is detected in the subchannel file, if possible + /// + /// Base submission info to fill in specifics for + /// Base filename and path to use for checking + /// Status of the LibCrypt data, if possible + private static void GetLibCryptDetected(SubmissionInfo info, string basePath) + { + bool? psLibCryptStatus = Protection.GetLibCryptDetected(basePath + ".sub"); + if (psLibCryptStatus == true) + { + // Guard against false positives + if (File.Exists(basePath + "_subIntention.txt")) + { + string libCryptData = GetFullFile(basePath + "_subIntention.txt") ?? ""; + if (string.IsNullOrEmpty(libCryptData)) + { + info.CopyProtection.LibCrypt = YesNo.No; + } + else + { + info.CopyProtection.LibCrypt = YesNo.Yes; + info.CopyProtection.LibCryptData = libCryptData; + } + } + else + { + info.CopyProtection.LibCrypt = YesNo.No; + } + } + else if (psLibCryptStatus == false) + { + info.CopyProtection.LibCrypt = YesNo.No; + } + else + { + info.CopyProtection.LibCrypt = YesNo.NULL; + info.CopyProtection.LibCryptData = "LibCrypt could not be detected because subchannel file is missing"; + } + } + + #endregion + + #region Information Output + + /// + /// Compress log files to save space + /// + /// Output folder to write to + /// Output filename to use as the base path + /// Parameters object to use to derive log file paths + /// True if the process succeeded, false otherwise + public static bool CompressLogFiles(string outputDirectory, string outputFilename, BaseParameters parameters) + { + // Prepare the necessary paths + outputFilename = Path.GetFileNameWithoutExtension(outputFilename); + string combinedBase = Path.Combine(outputDirectory, outputFilename); + string archiveName = combinedBase + "_logs.zip"; + + // Get the list of log files from the parameters object + var files = parameters.GetLogFilePaths(combinedBase); + if (!files.Any()) + return true; + + // Add the log files to the archive and delete the uncompressed file after + ZipArchive zf = null; + try + { + zf = ZipFile.Open(archiveName, ZipArchiveMode.Create); + foreach (string file in files) + { + string entryName = file.Substring(outputDirectory.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + zf.CreateEntryFromFile(file, entryName); + + try + { + File.Delete(file); + } + catch { } + } + + return true; + } + catch + { + return false; + } + finally + { + zf?.Dispose(); + } + } + + /// + /// Format the output data in a human readable way, separating each printed line into a new item in the list + /// + /// Information object that should contain normalized values + /// List of strings representing each line of an output file, null on error + public static List FormatOutputData(SubmissionInfo info) + { + // Check to see if the inputs are valid + if (info == null) + return null; + + try + { + // Sony-printed discs have layers in the opposite order + var system = info.CommonDiscInfo.System; + bool reverseOrder = (system == RedumpSystem.SonyPlayStation2 + || system == RedumpSystem.SonyPlayStation3 + || system == RedumpSystem.SonyPlayStation4); + + // Common Disc Info section + List output = new List { "Common Disc Info:" }; + AddIfExists(output, Template.TitleField, info.CommonDiscInfo.Title, 1); + AddIfExists(output, Template.ForeignTitleField, info.CommonDiscInfo.ForeignTitleNonLatin, 1); + AddIfExists(output, Template.DiscNumberField, info.CommonDiscInfo.DiscNumberLetter, 1); + AddIfExists(output, Template.DiscTitleField, info.CommonDiscInfo.DiscTitle, 1); + AddIfExists(output, Template.SystemField, info.CommonDiscInfo.System.LongName(), 1); + AddIfExists(output, Template.MediaTypeField, GetFixedMediaType( + info.CommonDiscInfo.Media.ToMediaType(), + info.SizeAndChecksums.Size, + info.SizeAndChecksums.Layerbreak, + info.SizeAndChecksums.Layerbreak2, + info.SizeAndChecksums.Layerbreak3), + 1); + AddIfExists(output, Template.CategoryField, info.CommonDiscInfo.Category.LongName(), 1); + AddIfExists(output, Template.MatchingIDsField, info.MatchedIDs, 1); + AddIfExists(output, Template.RegionField, info.CommonDiscInfo.Region.LongName() ?? "SPACE! (CHANGE THIS)", 1); + AddIfExists(output, Template.LanguagesField, (info.CommonDiscInfo.Languages ?? new Language?[] { null }).Select(l => l.LongName() ?? "Klingon (CHANGE THIS)").ToArray(), 1); + AddIfExists(output, Template.PlaystationLanguageSelectionViaField, (info.CommonDiscInfo.LanguageSelection ?? new LanguageSelection?[] { }).Select(l => l.LongName()).ToArray(), 1); + AddIfExists(output, Template.DiscSerialField, info.CommonDiscInfo.Serial, 1); + + // All ringcode information goes in an indented area + output.Add(""); output.Add("\tRingcode Information:"); + + // If we have a triple-layer disc + if (info.SizeAndChecksums.Layerbreak3 != default) + { + AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.MasteringRingField, info.CommonDiscInfo.Layer0MasteringRing, 2); + AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.MasteringSIDField, info.CommonDiscInfo.Layer0MasteringSID, 2); + AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.ToolstampField, info.CommonDiscInfo.Layer0ToolstampMasteringCode, 2); + AddIfExists(output, "Data Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer0MouldSID, 2); + AddIfExists(output, "Data Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer0AdditionalMould, 2); + + AddIfExists(output, "Layer 1 " + Template.MasteringRingField, info.CommonDiscInfo.Layer1MasteringRing, 2); + AddIfExists(output, "Layer 1 " + Template.MasteringSIDField, info.CommonDiscInfo.Layer1MasteringSID, 2); + AddIfExists(output, "Layer 1 " + Template.ToolstampField, info.CommonDiscInfo.Layer1ToolstampMasteringCode, 2); + AddIfExists(output, "Label Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer1MouldSID, 2); + AddIfExists(output, "Label Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer1AdditionalMould, 2); + + AddIfExists(output, "Layer 2 " + Template.MasteringRingField, info.CommonDiscInfo.Layer2MasteringRing, 2); + AddIfExists(output, "Layer 2 " + Template.MasteringSIDField, info.CommonDiscInfo.Layer2MasteringSID, 2); + AddIfExists(output, "Layer 2 " + Template.ToolstampField, info.CommonDiscInfo.Layer2ToolstampMasteringCode, 2); + + AddIfExists(output, (reverseOrder ? "Layer 3 (Inner) " : "Layer 3 (Outer) ") + Template.MasteringRingField, info.CommonDiscInfo.Layer3MasteringRing, 2); + AddIfExists(output, (reverseOrder ? "Layer 3 (Inner) " : "Layer 3 (Outer) ") + Template.MasteringSIDField, info.CommonDiscInfo.Layer3MasteringSID, 2); + AddIfExists(output, (reverseOrder ? "Layer 3 (Inner) " : "Layer 3 (Outer) ") + Template.ToolstampField, info.CommonDiscInfo.Layer3ToolstampMasteringCode, 2); + } + // If we have a triple-layer disc + else if (info.SizeAndChecksums.Layerbreak2 != default) + { + AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.MasteringRingField, info.CommonDiscInfo.Layer0MasteringRing, 2); + AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.MasteringSIDField, info.CommonDiscInfo.Layer0MasteringSID, 2); + AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.ToolstampField, info.CommonDiscInfo.Layer0ToolstampMasteringCode, 2); + AddIfExists(output, "Data Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer0MouldSID, 2); + AddIfExists(output, "Data Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer0AdditionalMould, 2); + + AddIfExists(output, "Layer 1 " + Template.MasteringRingField, info.CommonDiscInfo.Layer1MasteringRing, 2); + AddIfExists(output, "Layer 1 " + Template.MasteringSIDField, info.CommonDiscInfo.Layer1MasteringSID, 2); + AddIfExists(output, "Layer 1 " + Template.ToolstampField, info.CommonDiscInfo.Layer1ToolstampMasteringCode, 2); + AddIfExists(output, "Label Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer1MouldSID, 2); + AddIfExists(output, "Label Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer1AdditionalMould, 2); + + AddIfExists(output, (reverseOrder ? "Layer 2 (Inner) " : "Layer 2 (Outer) ") + Template.MasteringRingField, info.CommonDiscInfo.Layer2MasteringRing, 2); + AddIfExists(output, (reverseOrder ? "Layer 2 (Inner) " : "Layer 2 (Outer) ") + Template.MasteringSIDField, info.CommonDiscInfo.Layer2MasteringSID, 2); + AddIfExists(output, (reverseOrder ? "Layer 2 (Inner) " : "Layer 2 (Outer) ") + Template.ToolstampField, info.CommonDiscInfo.Layer2ToolstampMasteringCode, 2); + } + // If we have a dual-layer disc + else if (info.SizeAndChecksums.Layerbreak != default) + { + AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.MasteringRingField, info.CommonDiscInfo.Layer0MasteringRing, 2); + AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.MasteringSIDField, info.CommonDiscInfo.Layer0MasteringSID, 2); + AddIfExists(output, (reverseOrder ? "Layer 0 (Outer) " : "Layer 0 (Inner) ") + Template.ToolstampField, info.CommonDiscInfo.Layer0ToolstampMasteringCode, 2); + AddIfExists(output, "Data Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer0MouldSID, 2); + AddIfExists(output, "Data Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer0AdditionalMould, 2); + + AddIfExists(output, (reverseOrder ? "Layer 1 (Inner) " : "Layer 1 (Outer) ") + Template.MasteringRingField, info.CommonDiscInfo.Layer1MasteringRing, 2); + AddIfExists(output, (reverseOrder ? "Layer 1 (Inner) " : "Layer 1 (Outer) ") + Template.MasteringSIDField, info.CommonDiscInfo.Layer1MasteringSID, 2); + AddIfExists(output, (reverseOrder ? "Layer 1 (Inner) " : "Layer 1 (Outer) ") + Template.ToolstampField, info.CommonDiscInfo.Layer1ToolstampMasteringCode, 2); + AddIfExists(output, "Label Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer1MouldSID, 2); + AddIfExists(output, "Label Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer1AdditionalMould, 2); + } + // If we have a single-layer disc + else + { + AddIfExists(output, "Data Side " + Template.MasteringRingField, info.CommonDiscInfo.Layer0MasteringRing, 2); + AddIfExists(output, "Data Side " + Template.MasteringSIDField, info.CommonDiscInfo.Layer0MasteringSID, 2); + AddIfExists(output, "Data Side " + Template.ToolstampField, info.CommonDiscInfo.Layer0ToolstampMasteringCode, 2); + AddIfExists(output, "Data Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer0MouldSID, 2); + AddIfExists(output, "Data Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer0AdditionalMould, 2); + + AddIfExists(output, "Label Side " + Template.MasteringRingField, info.CommonDiscInfo.Layer1MasteringRing, 2); + AddIfExists(output, "Label Side " + Template.MasteringSIDField, info.CommonDiscInfo.Layer1MasteringSID, 2); + AddIfExists(output, "Label Side " + Template.ToolstampField, info.CommonDiscInfo.Layer1ToolstampMasteringCode, 2); + AddIfExists(output, "Label Side " + Template.MouldSIDField, info.CommonDiscInfo.Layer1MouldSID, 2); + AddIfExists(output, "Label Side " + Template.AdditionalMouldField, info.CommonDiscInfo.Layer1AdditionalMould, 2); + } + + AddIfExists(output, Template.BarcodeField, info.CommonDiscInfo.Barcode, 1); + AddIfExists(output, Template.EXEDateBuildDate, info.CommonDiscInfo.EXEDateBuildDate, 1); + AddIfExists(output, Template.ErrorCountField, info.CommonDiscInfo.ErrorsCount, 1); + AddIfExists(output, Template.CommentsField, info.CommonDiscInfo.Comments.Trim(), 1); + AddIfExists(output, Template.ContentsField, info.CommonDiscInfo.Contents.Trim(), 1); + + // Version and Editions section + output.Add(""); output.Add("Version and Editions:"); + AddIfExists(output, Template.VersionField, info.VersionAndEditions.Version, 1); + AddIfExists(output, Template.EditionField, info.VersionAndEditions.OtherEditions, 1); + + // EDC section + if (info.CommonDiscInfo.System == RedumpSystem.SonyPlayStation) + { + output.Add(""); output.Add("EDC:"); + AddIfExists(output, Template.PlayStationEDCField, info.EDC.EDC.LongName(), 1); + } + + // Parent/Clone Relationship section + // output.Add(""); output.Add("Parent/Clone Relationship:"); + // AddIfExists(output, Template.ParentIDField, info.ParentID); + // AddIfExists(output, Template.RegionalParentField, info.RegionalParent.ToString()); + + // Extras section + if (info.Extras.PVD != null || info.Extras.PIC != null || info.Extras.BCA != null) + { + output.Add(""); output.Add("Extras:"); + AddIfExists(output, Template.PVDField, info.Extras.PVD?.Trim(), 1); + AddIfExists(output, Template.PlayStation3WiiDiscKeyField, info.Extras.DiscKey, 1); + AddIfExists(output, Template.PlayStation3DiscIDField, info.Extras.DiscID, 1); + AddIfExists(output, Template.PICField, info.Extras.PIC, 1); + AddIfExists(output, Template.HeaderField, info.Extras.Header, 1); + AddIfExists(output, Template.GameCubeWiiBCAField, info.Extras.BCA, 1); + AddIfExists(output, Template.XBOXSSRanges, info.Extras.SecuritySectorRanges, 1); + } + + // Copy Protection section + if (info.CopyProtection.Protection != null + || info.CopyProtection.AntiModchip != YesNo.NULL + || info.CopyProtection.LibCrypt != YesNo.NULL) + { + output.Add(""); output.Add("Copy Protection:"); + if (info.CommonDiscInfo.System == RedumpSystem.SonyPlayStation) + { + AddIfExists(output, Template.PlayStationAntiModchipField, info.CopyProtection.AntiModchip.LongName(), 1); + AddIfExists(output, Template.PlayStationLibCryptField, info.CopyProtection.LibCrypt.LongName(), 1); + AddIfExists(output, Template.SubIntentionField, info.CopyProtection.LibCryptData, 1); + } + + AddIfExists(output, Template.CopyProtectionField, info.CopyProtection.Protection, 1); + AddIfExists(output, Template.SubIntentionField, info.CopyProtection.SecuROMData, 1); + } + + // Dumpers and Status section + // output.Add(""); output.Add("Dumpers and Status"); + // AddIfExists(output, Template.StatusField, info.Status.Name()); + // AddIfExists(output, Template.OtherDumpersField, info.OtherDumpers); + + // Tracks and Write Offsets section + if (!string.IsNullOrWhiteSpace(info.TracksAndWriteOffsets.ClrMameProData)) + { + output.Add(""); output.Add("Tracks and Write Offsets:"); + AddIfExists(output, Template.DATField, info.TracksAndWriteOffsets.ClrMameProData + "\n", 1); + AddIfExists(output, Template.CuesheetField, info.TracksAndWriteOffsets.Cuesheet, 1); + AddIfExists(output, Template.WriteOffsetField, info.TracksAndWriteOffsets.OtherWriteOffsets, 1); + } + // Size & Checksum section + else + { + output.Add(""); output.Add("Size & Checksum:"); + AddIfExists(output, Template.LayerbreakField, (info.SizeAndChecksums.Layerbreak == default ? null : info.SizeAndChecksums.Layerbreak.ToString()), 1); + AddIfExists(output, Template.SizeField, info.SizeAndChecksums.Size.ToString(), 1); + AddIfExists(output, Template.CRC32Field, info.SizeAndChecksums.CRC32, 1); + AddIfExists(output, Template.MD5Field, info.SizeAndChecksums.MD5, 1); + AddIfExists(output, Template.SHA1Field, info.SizeAndChecksums.SHA1, 1); + } + + // Make sure there aren't any instances of two blank lines in a row + string last = null; + for (int i = 0; i < output.Count;) + { + if (output[i] == last && string.IsNullOrWhiteSpace(last)) + { + output.RemoveAt(i); + } + else + { + last = output[i]; + i++; + } + } + + return output; + } + catch + { + // We don't care what the error is + return null; + } + } + + /// + /// Write the data to the output folder + /// + /// Output folder to write to + /// Preformatted list of lines to write out to the file + /// True on success, false on error + public static bool WriteOutputData(string outputDirectory, List lines) + { + // Check to see if the inputs are valid + if (lines == null) + return false; + + // Now write out to a generic file + try + { + using (StreamWriter sw = new StreamWriter(File.Open(Path.Combine(outputDirectory, "!submissionInfo.txt"), FileMode.Create, FileAccess.Write))) + { + foreach (string line in lines) + sw.WriteLine(line); + } + } + catch + { + // We don't care what the error is right now + return false; + } + + return true; + } + + /// + /// Write the data to the output folder + /// + /// Output folder to write to + /// SubmissionInfo object representing the JSON to write out to the file + /// True on success, false on error + public static bool WriteOutputData(string outputDirectory, SubmissionInfo info) + { + // Check to see if the input is valid + if (info == null) + return false; + + // Now write out to the JSON + try + { + using (var fs = File.Create(Path.Combine(outputDirectory, "!submissionInfo.json.gz"))) + using (var gs = new GZipStream(fs, CompressionMode.Compress)) + { + string json = JsonConvert.SerializeObject(info, Formatting.Indented); + byte[] jsonBytes = Encoding.UTF8.GetBytes(json); + gs.Write(jsonBytes, 0, jsonBytes.Length); + } + } + catch + { + // We don't care what the error is right now + return false; + } + + return true; + } + + /// + /// Add the properly formatted key and value, if possible + /// + /// Output list + /// Name of the output key to write + /// Name of the output value to write + /// Number of tabs to indent the line + private static void AddIfExists(List output, string key, string value, int indent) + { + // If there's no valid value to write + if (value == null) + return; + + string prefix = ""; + for (int i = 0; i < indent; i++) + prefix += "\t"; + + // If the value contains a newline + value = value.Replace("\r\n", "\n"); + if (value.Contains("\n")) + { + output.Add(prefix + key + ":"); output.Add(""); + string[] values = value.Split('\n'); + foreach (string val in values) + output.Add(val); + + output.Add(""); + } + + // For all regular values + else + { + output.Add(prefix + key + ": " + value); + } + } + + /// + /// Add the properly formatted key and value, if possible + /// + /// Output list + /// Name of the output key to write + /// Name of the output value to write + /// Number of tabs to indent the line + private static void AddIfExists(List output, string key, string[] value, int indent) + { + // If there's no valid value to write + if (value == null || value.Length == 0) + return; + + AddIfExists(output, key, string.Join(", ", value), indent); + } + + /// + /// Add the properly formatted key and value, if possible + /// + /// Output list + /// Name of the output key to write + /// Name of the output value to write + /// Number of tabs to indent the line + private static void AddIfExists(List output, string key, List value, int indent) + { + // If there's no valid value to write + if (value == null || value.Count() == 0) + return; + + AddIfExists(output, key, string.Join(", ", value.Select(o => o.ToString())), indent); + } + + /// + /// Get the adjusted name of the media baed on layers, if applicable + /// + /// MediaType to get the proper name for + /// Size of the current media + /// First layerbreak value, as applicable + /// Second layerbreak value, as applicable + /// Third ayerbreak value, as applicable + /// String representation of the media, including layer specification + private static string GetFixedMediaType(MediaType? mediaType, long size, long layerbreak, long layerbreak2, long layerbreak3) + { + switch (mediaType) + { + case MediaType.DVD: + if (layerbreak != default) + return $"{mediaType.LongName()}-9"; + else + return $"{mediaType.LongName()}-5"; + + case MediaType.BluRay: + if (layerbreak3 != default) + return $"{mediaType.LongName()}-128"; + else if (layerbreak2 != default) + return $"{mediaType.LongName()}-100"; + else if (layerbreak != default && size > 53_687_063_712) + return $"{mediaType.LongName()}-66"; + else if (layerbreak != default) + return $"{mediaType.LongName()}-50"; + else if (size > 26_843_531_856) + return $"{mediaType.LongName()}-33"; + else + return $"{mediaType.LongName()}-25"; + + case MediaType.UMD: + if (layerbreak != default) + return $"{mediaType.LongName()}-DL"; + else + return $"{mediaType.LongName()}-SL"; + + default: + return mediaType.LongName(); + } + } + + #endregion + + #region Normalization + + /// + /// Normalize a split set of paths + /// + /// Directory name to normalize + /// Filename to normalize + /// True to replace '.' with '_' in filenames, false otherwise + public static (string, string) NormalizeOutputPaths(string directory, string filename, bool replacePeriods) + { + try + { + // Cache if we had a directory separator or not + bool endedWithDirectorySeparator = directory.EndsWith(Path.DirectorySeparatorChar.ToString()) + || directory.EndsWith(Path.AltDirectorySeparatorChar.ToString()); + + // Combine the path to make things separate easier + string combinedPath = Path.Combine(directory, filename); + + // If we have have a blank path, just return + if (string.IsNullOrWhiteSpace(combinedPath)) + return (directory, filename); + + // Now get the normalized paths + directory = Path.GetDirectoryName(combinedPath); + filename = Path.GetFileName(combinedPath); + + // Take care of extra path characters + directory = new StringBuilder(directory) + .Replace(':', '_', 0, directory.LastIndexOf(':') == -1 ? 0 : directory.LastIndexOf(':')).ToString(); + + // Sanitize everything else + foreach (char c in Path.GetInvalidPathChars()) + directory = directory.Replace(c, '_'); + foreach (char c in Path.GetInvalidFileNameChars()) + filename = filename.Replace(c, '_'); + if (replacePeriods) + filename = Path.GetFileNameWithoutExtension(filename).Replace('.', '_') + "." + Path.GetExtension(filename).TrimStart('.'); + + // If we had a directory separator at the end before, add it again + if (endedWithDirectorySeparator) + directory += Path.DirectorySeparatorChar; + + // If we have a root directory, sanitize + if (Directory.Exists(directory)) + { + var possibleRootDir = new DirectoryInfo(directory); + if (possibleRootDir.Parent == null) + directory = directory.Replace($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}", $"{Path.DirectorySeparatorChar}"); + } + } + catch { } + + return (directory, filename); + } + + #endregion + + #region Web Calls + + /// + /// Fill out an existing SubmissionInfo object based on a disc page + /// + /// RedumpWebClient for making the connection + /// Existing SubmissionInfo object to fill + /// Redump disc ID to retrieve + private static void FillFromId(RedumpWebClient wc, SubmissionInfo info, int id) + { + string discData = wc.DownloadSingleSiteID(id); + if (string.IsNullOrEmpty(discData)) + return; + + // Title, Disc Number/Letter, Disc Title + var match = Constants.TitleRegex.Match(discData); + if (match.Success) + { + string title = WebUtility.HtmlDecode(match.Groups[1].Value); + + // If we have parenthesis, title is everything before the first one + int firstParenLocation = title.IndexOf(" ("); + if (firstParenLocation >= 0) + { + info.CommonDiscInfo.Title = title.Substring(0, firstParenLocation); + var subMatches = Constants.DiscNumberLetterRegex.Match(title); + for (int i = 1; i < subMatches.Groups.Count; i++) + { + string subMatch = subMatches.Groups[i].Value; + + // Disc number or letter + if (subMatch.StartsWith("Disc")) + info.CommonDiscInfo.DiscNumberLetter = subMatch.Remove(0, "Disc ".Length); + + // Disc title + else + info.CommonDiscInfo.DiscTitle = subMatch; + } + } + // Otherwise, leave the title as-is + else + { + info.CommonDiscInfo.Title = title; + } + } + + // Foreign Title + match = Constants.ForeignTitleRegex.Match(discData); + if (match.Success) + info.CommonDiscInfo.ForeignTitleNonLatin = WebUtility.HtmlDecode(match.Groups[1].Value); + else + info.CommonDiscInfo.ForeignTitleNonLatin = null; + + // Category + match = Constants.CategoryRegex.Match(discData); + if (match.Success) + info.CommonDiscInfo.Category = Extensions.ToDiscCategory(match.Groups[1].Value); + else + info.CommonDiscInfo.Category = DiscCategory.Games; + + // Region + match = Constants.RegionRegex.Match(discData); + if (match.Success) + info.CommonDiscInfo.Region = Extensions.ToRegion(match.Groups[1].Value); + + // Languages + var matches = Constants.LanguagesRegex.Matches(discData); + if (matches.Count > 0) + { + List tempLanguages = new List(); + foreach (Match submatch in matches) + tempLanguages.Add(Extensions.ToLanguage(submatch.Groups[1].Value)); + + info.CommonDiscInfo.Languages = tempLanguages.Where(l => l != null).ToArray(); + } + + // Error count + match = Constants.ErrorCountRegex.Match(discData); + if (match.Success) + { + // If the error count is empty, fill from the page + if (string.IsNullOrEmpty(info.CommonDiscInfo.ErrorsCount)) + info.CommonDiscInfo.ErrorsCount = match.Groups[1].Value; + } + + // Version + match = Constants.VersionRegex.Match(discData); + if (match.Success) + info.VersionAndEditions.Version = WebUtility.HtmlDecode(match.Groups[1].Value); + + // Dumpers + matches = Constants.DumpersRegex.Matches(discData); + if (matches.Count > 0) + { + // Start with any currently listed dumpers + List tempDumpers = new List(); + if (info.DumpersAndStatus.Dumpers.Length > 0) + { + foreach (string dumper in info.DumpersAndStatus.Dumpers) + tempDumpers.Add(dumper); + } + + foreach (Match submatch in matches) + tempDumpers.Add(WebUtility.HtmlDecode(submatch.Groups[1].Value)); + + info.DumpersAndStatus.Dumpers = tempDumpers.ToArray(); + } + + // Comments + match = Constants.CommentsRegex.Match(discData); + if (match.Success) + { + info.CommonDiscInfo.Comments += (string.IsNullOrEmpty(info.CommonDiscInfo.Comments) ? string.Empty : "\n") + + WebUtility.HtmlDecode(match.Groups[1].Value) + .Replace("
", "\n") + .Replace("ISBN", "[T:ISBN]") + "\n"; + } + + // Contents + match = Constants.ContentsRegex.Match(discData); + if (match.Success) + { + info.CommonDiscInfo.Contents = WebUtility.HtmlDecode(match.Groups[1].Value) + .Replace("
", "\n") + .Replace("
", ""); + info.CommonDiscInfo.Contents = Regex.Replace(info.CommonDiscInfo.Contents, @"
", ""); + } + + // Added + match = Constants.AddedRegex.Match(discData); + if (match.Success) + { + if (DateTime.TryParse(match.Groups[1].Value, out DateTime added)) + info.Added = added; + else + info.Added = null; + } + + // Last Modified + match = Constants.LastModifiedRegex.Match(discData); + if (match.Success) + { + if (DateTime.TryParse(match.Groups[1].Value, out DateTime lastModified)) + info.LastModified = lastModified; + else + info.LastModified = null; + } + } + + /// + /// List the disc IDs associated with a given quicksearch query + /// + /// RedumpWebClient for making the connection + /// Query string to attempt to search for + /// All disc IDs for the given query, null on error + private static List ListSearchResults(RedumpWebClient wc, string query) + { + List ids = new List(); + + // Strip quotes + query = query.Trim('"', '\''); + + // Special characters become dashes + query = query.Replace(' ', '-'); + query = query.Replace('/', '-'); + query = query.Replace('\\', '/'); + + // Lowercase is defined per language + query = query.ToLowerInvariant(); + + // Keep getting quicksearch pages until there are none left + try + { + int pageNumber = 1; + while (true) + { + List pageIds = wc.CheckSingleSitePage(string.Format(Constants.QuickSearchUrl, query, pageNumber++)); + ids.AddRange(pageIds); + if (pageIds.Count <= 1) + break; + } + } + catch (Exception ex) + { + Console.WriteLine($"An exception occurred while trying to log in: {ex}"); + return null; + } + + return ids; + } + + #endregion + } +} diff --git a/MPF.Test/Utilities/DumpEnvironmentTest.cs b/MPF.Test/Utilities/DumpEnvironmentTest.cs index 35c82759..2a900b73 100644 --- a/MPF.Test/Utilities/DumpEnvironmentTest.cs +++ b/MPF.Test/Utilities/DumpEnvironmentTest.cs @@ -39,7 +39,7 @@ namespace MPF.Test [InlineData("superhero", "blah&foo.bin", "superhero", "blah&foo.bin")] public void FixOutputPathsTest(string outputDirectory, string outputFilename, string expectedOutputDirectory, string expectedOutputFilename) { - (string actualOutputDirectory, string actualOutputFilename) = DumpEnvironment.NormalizeOutputPaths(outputDirectory, outputFilename, false); + (string actualOutputDirectory, string actualOutputFilename) = InfoTool.NormalizeOutputPaths(outputDirectory, outputFilename, false); Assert.Equal(expectedOutputDirectory, actualOutputDirectory); Assert.Equal(expectedOutputFilename, actualOutputFilename); } diff --git a/MPF/ViewModels/MainViewModel.cs b/MPF/ViewModels/MainViewModel.cs index 432725a8..2c9dd99a 100644 --- a/MPF/ViewModels/MainViewModel.cs +++ b/MPF/ViewModels/MainViewModel.cs @@ -864,7 +864,7 @@ namespace MPF.GUI.ViewModels string trimmedPath = Env.Parameters.OutputPath?.Trim('"') ?? string.Empty; string outputDirectory = Path.GetDirectoryName(trimmedPath); string outputFilename = Path.GetFileName(trimmedPath); - (outputDirectory, outputFilename) = DumpEnvironment.NormalizeOutputPaths(outputDirectory, outputFilename, App.Options.InternalProgram == InternalProgram.DiscImageCreator); + (outputDirectory, outputFilename) = InfoTool.NormalizeOutputPaths(outputDirectory, outputFilename, App.Options.InternalProgram == InternalProgram.DiscImageCreator); if (!string.IsNullOrWhiteSpace(outputDirectory)) App.Instance.OutputDirectoryTextBox.Text = outputDirectory; else @@ -1072,7 +1072,7 @@ namespace MPF.GUI.ViewModels } // If a complete dump already exists - (bool foundFiles, List _) = Env.FoundAllFiles(true); + (bool foundFiles, List _) = InfoTool.FoundAllFiles(Env.OutputDirectory, Env.OutputFilename, Env.Parameters, true); if (foundFiles) { MessageBoxResult mbresult = CustomMessageBox.Show("A complete dump already exists! Are you sure you want to overwrite?", "Overwrite?", MessageBoxButton.YesNo, MessageBoxImage.Exclamation);