using System; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; using SabreTools.Models.Logiqx; using SabreTools.Models.PIC; using SabreTools.RedumpLib.Data; namespace MPF.Processors { /// /// Includes processing-specific utility functionality /// public static class ProcessingTool { #region Information Extraction /// /// Generate the proper datfile from the input Datafile, if possible /// /// .dat file location /// Relevant pieces of the datfile, null on error public static string? GenerateDatfile(Datafile? datafile) { // If we don't have a valid datafile, we can't do anything if (datafile?.Game == null || datafile.Game.Length == 0) return null; var roms = datafile.Game[0].Rom; if (roms == null || roms.Length == 0) return null; // Otherwise, reconstruct the hash data with only the required info try { string datString = string.Empty; for (int i = 0; i < roms.Length; i++) { var rom = roms[i]; datString += $"\n"; } return datString.TrimEnd('\n'); } catch { // We don't care what the exception is right now return null; } } /// /// Get the Base64 representation of a string /// /// String content to encode /// Base64-encoded contents, if possible public static string? GetBase64(string? content) { if (string.IsNullOrEmpty(content)) return null; byte[] temp = Encoding.UTF8.GetBytes(content); return Convert.ToBase64String(temp); } /// /// Get Datafile from a standard DAT /// /// Path to the DAT file to parse /// Filled Datafile on success, null on error public static Datafile? GetDatafile(string? dat) { // If there's no path, we can't read the file if (string.IsNullOrEmpty(dat)) return null; // If the file doesn't exist, we can't read it if (!File.Exists(dat)) return null; try { // Open and read in the XML file XmlReader xtr = XmlReader.Create(dat, new XmlReaderSettings { CheckCharacters = false, #if NET40_OR_GREATER || NETCOREAPP DtdProcessing = DtdProcessing.Ignore, #endif IgnoreComments = true, IgnoreWhitespace = true, ValidationFlags = XmlSchemaValidationFlags.None, ValidationType = ValidationType.None, }); // If the reader is null for some reason, we can't do anything if (xtr == null) return null; var serializer = new XmlSerializer(typeof(Datafile)); return serializer.Deserialize(xtr) as Datafile; } catch { // We don't care what the exception is right now return null; } } /// /// Gets disc information from a PIC file /// /// Path to a PIC.bin file /// Filled DiscInformation on success, null on error /// This omits the emergency brake information, if it exists public static DiscInformation? GetDiscInformation(string pic) { try { return SabreTools.Serialization.Deserializers.PIC.DeserializeFile(pic); } catch { // We don't care what the error was return null; } } /// /// Get the last modified date from a file path, if possible /// /// Path to the input file /// Filled DateTime on success, null on failure public static DateTime? GetFileModifiedDate(string? filename, bool fallback = false) { if (string.IsNullOrEmpty(filename)) return fallback ? (DateTime?)DateTime.UtcNow : null; else if (!File.Exists(filename)) return fallback ? (DateTime?)DateTime.UtcNow : null; var fi = new FileInfo(filename); return fi.LastWriteTimeUtc; } /// /// 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 public 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 File.ReadAllText(filename); } /// /// Get the split values for ISO-based media /// /// Datafile represenging the hash data /// True if extraction was successful, false otherwise public static bool GetISOHashValues(Datafile? datafile, out long size, out string? crc32, out string? md5, out string? sha1) { size = -1; crc32 = null; md5 = null; sha1 = null; if (datafile?.Game == null || datafile.Game.Length == 0) return false; var roms = datafile.Game[0].Rom; if (roms == null || roms.Length == 0) return false; var rom = roms[0]; _ = Int64.TryParse(rom.Size, out size); crc32 = rom.CRC; md5 = rom.MD5; sha1 = rom.SHA1; return true; } /// /// Get the split values for ISO-based media /// /// String representing the combined hash data /// True if extraction was successful, false otherwise public 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.IsNullOrEmpty(hashData)) return false; var hashreg = new Regex(@" /// Get the layerbreak info associated from the disc information /// /// Disc information containing unformatted data /// True if layerbreak info was set, false otherwise public static bool GetLayerbreaks(DiscInformation? di, out long? layerbreak1, out long? layerbreak2, out long? layerbreak3) { // Set the default values layerbreak1 = null; layerbreak2 = null; layerbreak3 = null; // If we don't have valid disc information, we can't do anything if (di?.Units == null || di.Units.Length <= 1) return false; static int ReadFromArrayBigEndian(byte[]? bytes, int offset) { if (bytes == null) return default; #if NET20 || NET35 || NET40 byte[] rev = new byte[0x04]; Array.Copy(bytes, offset, rev, 0, 0x04); #else var span = new ReadOnlySpan(bytes, offset, 0x04); byte[] rev = span.ToArray(); #endif Array.Reverse(rev); return BitConverter.ToInt32(rev, 0); } // Layerbreak 1 (2+ layers) if (di.Units.Length >= 2) { long offset = ReadFromArrayBigEndian(di.Units[0]?.Body?.FormatDependentContents, 0x0C); long value = ReadFromArrayBigEndian(di.Units[0]?.Body?.FormatDependentContents, 0x10); layerbreak1 = value - offset + 2; } // Layerbreak 2 (3+ layers) if (di.Units.Length >= 3) { long offset = ReadFromArrayBigEndian(di.Units[1]?.Body?.FormatDependentContents, 0x0C); long value = ReadFromArrayBigEndian(di.Units[1]?.Body?.FormatDependentContents, 0x10); layerbreak2 = layerbreak1 + value - offset + 2; } // Layerbreak 3 (4 layers) if (di.Units.Length >= 4) { long offset = ReadFromArrayBigEndian(di.Units[2]?.Body?.FormatDependentContents, 0x0C); long value = ReadFromArrayBigEndian(di.Units[2]?.Body?.FormatDependentContents, 0x10); layerbreak3 = layerbreak2 + value - offset + 2; } return true; } /// /// Get the PIC identifier from the first disc information unit, if possible /// /// Disc information containing the data /// String representing the PIC identifier, null on error public static string? GetPICIdentifier(DiscInformation? di) { // If we don't have valid disc information, we can't do anything if (di?.Units == null || di.Units.Length <= 1) return null; // We assume the identifier is consistent across all units return di.Units[0]?.Body?.DiscTypeIdentifier; } #endregion #region Category Extraction /// /// Determine the category based on the UMDImageCreator string /// /// String representing the category /// Category, if possible public static DiscCategory? GetUMDCategory(string? category) { return category switch { "GAME" => DiscCategory.Games, "VIDEO" => DiscCategory.Video, "AUDIO" => DiscCategory.Audio, _ => null, }; } #endregion #region Region Extraction /// /// Determine the region based on the XGD serial character /// /// Character denoting the region /// Region, if possible public static Region? GetXGDRegion(char? region) { return region switch { 'W' => (Region?)Region.World, 'A' => (Region?)Region.UnitedStatesOfAmerica, 'J' => (Region?)Region.JapanAsia, 'E' => (Region?)Region.Europe, 'K' => (Region?)Region.USAJapan, 'L' => (Region?)Region.USAEurope, 'H' => (Region?)Region.JapanEurope, _ => null, }; } #endregion } }