using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace SabreTools.Helper { public class RomTools { #region Rom-based sorting and merging /// /// Determine if a rom should be included based on filters /// /// User supplied Rom to check /// Name of the game to match (can use asterisk-partials) /// Name of the rom to match (can use asterisk-partials) /// Type of the rom to match /// Find roms greater than or equal to this size /// Find roms less than or equal to this size /// Find roms equal to this size /// CRC of the rom to match (can use asterisk-partials) /// MD5 of the rom to match (can use asterisk-partials) /// SHA-1 of the rom to match (can use asterisk-partials) /// Select roms with nodump status as follows: null (match all), true (match Nodump only), false (exclude Nodump) /// Logging object for console and file output /// Returns true if it should be included, false otherwise public static bool Filter(Rom romdata, string gamename, string romname, string romtype, long sgt, long slt, long seq, string crc, string md5, string sha1, bool? nodump, Logger logger) { // Filter on nodump status if (nodump == true && !romdata.Nodump) { return false; } if (nodump == false && romdata.Nodump) { return false; } // Filter on game name if (!String.IsNullOrEmpty(gamename)) { if (gamename.StartsWith("*") && gamename.EndsWith("*")) { if (!romdata.Machine.Name.ToLowerInvariant().Contains(gamename.ToLowerInvariant().Replace("*", ""))) { return false; } } else if (gamename.StartsWith("*")) { if (!romdata.Machine.Name.EndsWith(gamename.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else if (gamename.EndsWith("*")) { if (!romdata.Machine.Name.StartsWith(gamename.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else { if (!String.Equals(romdata.Machine.Name, gamename, StringComparison.InvariantCultureIgnoreCase)) { return false; } } } // Filter on rom name if (!String.IsNullOrEmpty(romname)) { if (romname.StartsWith("*") && romname.EndsWith("*")) { if (!romdata.Name.ToLowerInvariant().Contains(romname.ToLowerInvariant().Replace("*", ""))) { return false; } } else if (romname.StartsWith("*")) { if (!romdata.Name.EndsWith(romname.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else if (romname.EndsWith("*")) { if (!romdata.Name.StartsWith(romname.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else { if (!String.Equals(romdata.Name, romname, StringComparison.InvariantCultureIgnoreCase)) { return false; } } } // Filter on rom type if (String.IsNullOrEmpty(romtype) && romdata.Type != ItemType.Rom && romdata.Type != ItemType.Disk) { return false; } if (!String.IsNullOrEmpty(romtype) && !String.Equals(romdata.Type.ToString(), romtype, StringComparison.InvariantCultureIgnoreCase)) { return false; } // Filter on rom size if (seq != -1 && romdata.HashData.Size != seq) { return false; } else { if (sgt != -1 && romdata.HashData.Size < sgt) { return false; } if (slt != -1 && romdata.HashData.Size > slt) { return false; } } // Filter on crc if (!String.IsNullOrEmpty(crc)) { if (crc.StartsWith("*") && crc.EndsWith("*")) { if (!romdata.HashData.CRC.ToLowerInvariant().Contains(crc.ToLowerInvariant().Replace("*", ""))) { return false; } } else if (crc.StartsWith("*")) { if (!romdata.HashData.CRC.EndsWith(crc.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else if (crc.EndsWith("*")) { if (!romdata.HashData.CRC.StartsWith(crc.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else { if (!String.Equals(romdata.HashData.CRC, crc, StringComparison.InvariantCultureIgnoreCase)) { return false; } } } // Filter on md5 if (!String.IsNullOrEmpty(md5)) { if (md5.StartsWith("*") && md5.EndsWith("*")) { if (!romdata.HashData.MD5.ToLowerInvariant().Contains(md5.ToLowerInvariant().Replace("*", ""))) { return false; } } else if (md5.StartsWith("*")) { if (!romdata.HashData.MD5.EndsWith(md5.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else if (md5.EndsWith("*")) { if (!romdata.HashData.MD5.StartsWith(md5.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else { if (!String.Equals(romdata.HashData.MD5, md5, StringComparison.InvariantCultureIgnoreCase)) { return false; } } } // Filter on sha1 if (!String.IsNullOrEmpty(sha1)) { if (sha1.StartsWith("*") && sha1.EndsWith("*")) { if (!romdata.HashData.SHA1.ToLowerInvariant().Contains(sha1.ToLowerInvariant().Replace("*", ""))) { return false; } } else if (sha1.StartsWith("*")) { if (!romdata.HashData.SHA1.EndsWith(sha1.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else if (sha1.EndsWith("*")) { if (!romdata.HashData.SHA1.StartsWith(sha1.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else { if (!String.Equals(romdata.HashData.SHA1, sha1, StringComparison.InvariantCultureIgnoreCase)) { return false; } } } return true; } /// /// Merge an arbitrary set of ROMs based on the supplied information /// /// List of RomData objects representing the roms to be merged /// Logger object for console and/or file output /// A List of RomData objects representing the merged roms public static List Merge(List inroms, Logger logger) { // Check for null or blank roms first if (inroms == null || inroms.Count == 0) { return new List(); } // Create output list List outroms = new List(); // Then deduplicate them by checking to see if data matches previous saved roms foreach (Rom rom in inroms) { // If it's a nodump, add and skip if (rom.Nodump) { outroms.Add(rom); continue; } // If it's the first rom in the list, don't touch it if (outroms.Count != 0) { // Check if the rom is a duplicate DupeType dupetype = DupeType.None; Rom savedrom = new Rom(); int pos = -1; for (int i = 0; i < outroms.Count; i++) { Rom lastrom = outroms[i]; // Get the duplicate status dupetype = GetDuplicateStatus(rom, lastrom, logger); // If it's a duplicate, skip adding it to the output but add any missing information if (dupetype != DupeType.None) { savedrom = lastrom; pos = i; savedrom.HashData.CRC = (String.IsNullOrEmpty(savedrom.HashData.CRC) && !String.IsNullOrEmpty(rom.HashData.CRC) ? rom.HashData.CRC : savedrom.HashData.CRC); savedrom.HashData.MD5 = (String.IsNullOrEmpty(savedrom.HashData.MD5) && !String.IsNullOrEmpty(rom.HashData.MD5) ? rom.HashData.MD5 : savedrom.HashData.MD5); savedrom.HashData.SHA1 = (String.IsNullOrEmpty(savedrom.HashData.SHA1) && !String.IsNullOrEmpty(rom.HashData.SHA1) ? rom.HashData.SHA1 : savedrom.HashData.SHA1); savedrom.Dupe = dupetype; // If the current system has a lower ID than the previous, set the system accordingly if (rom.Metadata.SystemID < savedrom.Metadata.SystemID) { savedrom.Metadata.SystemID = rom.Metadata.SystemID; savedrom.Metadata.System = rom.Metadata.System; savedrom.Machine.Name = rom.Machine.Name; savedrom.Name = rom.Name; } // If the current source has a lower ID than the previous, set the source accordingly if (rom.Metadata.SourceID < savedrom.Metadata.SourceID) { savedrom.Metadata.SourceID = rom.Metadata.SourceID; savedrom.Metadata.Source = rom.Metadata.Source; savedrom.Machine.Name = rom.Machine.Name; savedrom.Name = rom.Name; } break; } } // If no duplicate is found, add it to the list if (dupetype == DupeType.None) { outroms.Add(rom); } // Otherwise, if a new rom information is found, add that else { outroms.RemoveAt(pos); outroms.Insert(pos, savedrom); } } else { outroms.Add(rom); } } // Then return the result return outroms; } /// /// List all duplicates found in a DAT based on a rom /// /// Rom to use as a base /// DAT to match against /// Logger object for console and/or file output /// True to remove matched roms from the input, false otherwise (default) /// List of matched RomData objects public static List GetDuplicates(Rom lastrom, Dat datdata, Logger logger, bool remove = false) { List output = new List(); // Check for an empty rom list first if (datdata.Files == null || datdata.Files.Count == 0) { return output; } // Try to find duplicates List keys = datdata.Files.Keys.ToList(); foreach (string key in keys) { List roms = datdata.Files[key]; List left = new List(); foreach (Rom rom in roms) { if (IsDuplicate(rom, lastrom, logger)) { output.Add(rom); } else { left.Add(rom); } } // If we're in removal mode, replace the list with the new one if (remove) { datdata.Files[key] = left; } } return output; } /// /// Determine if a file is a duplicate using partial matching logic /// /// Rom to check for duplicate status /// Rom to use as a baseline /// Logger object for console and/or file output /// True if the roms are duplicates, false otherwise public static bool IsDuplicate(Rom rom, Rom lastrom, Logger logger) { bool dupefound = rom.Equals(lastrom); // More wonderful SHA-1 logging that has to be done if (rom.HashData.SHA1 == lastrom.HashData.SHA1 && rom.HashData.Size != lastrom.HashData.Size) { logger.User("SHA-1 mismatch - Hash: " + rom.HashData.SHA1); } return dupefound; } /// /// Return the duplicate status of two roms /// /// Current rom to check /// Last rom to check against /// Logger object for console and/or file output /// The DupeType corresponding to the relationship between the two public static DupeType GetDuplicateStatus(Rom rom, Rom lastrom, Logger logger) { DupeType output = DupeType.None; // If we don't have a duplicate at all, return none if (!IsDuplicate(rom, lastrom, logger)) { return output; } // If the duplicate is external already or should be, set it if (lastrom.Dupe >= DupeType.ExternalHash || lastrom.Metadata.SystemID != rom.Metadata.SystemID || lastrom.Metadata.SourceID != rom.Metadata.SourceID) { if (lastrom.Machine.Name == rom.Machine.Name && lastrom.Name == rom.Name) { output = DupeType.ExternalAll; } else { output = DupeType.ExternalHash; } } // Otherwise, it's considered an internal dupe else { if (lastrom.Machine.Name == rom.Machine.Name && lastrom.Name == rom.Name) { output = DupeType.InternalAll; } else { output = DupeType.InternalHash; } } return output; } /// /// Sort a list of RomData objects by SystemID, SourceID, Game, and Name (in order) /// /// List of RomData objects representing the roms to be sorted /// True if files are not renamed, false otherwise /// True if it sorted correctly, false otherwise public static bool Sort(ref List roms, bool norename) { try { roms.Sort(delegate (Rom x, Rom y) { if (x.Metadata.SystemID == y.Metadata.SystemID) { if (x.Metadata.SourceID == y.Metadata.SourceID) { if (x.Machine.Name == y.Machine.Name) { if ((x.Type == ItemType.Rom || x.Type == ItemType.Disk) && (y.Type == ItemType.Rom || y.Type == ItemType.Disk)) { if (Path.GetDirectoryName(x.Name) == Path.GetDirectoryName(y.Name)) { return Style.CompareNumeric(Path.GetFileName(x.Name), Path.GetFileName(y.Name)); } return Style.CompareNumeric(Path.GetDirectoryName(x.Name), Path.GetDirectoryName(y.Name)); } else if ((x.Type == ItemType.Rom || x.Type == ItemType.Disk) && (y.Type != ItemType.Rom && y.Type != ItemType.Disk)) { return -1; } else if ((x.Type != ItemType.Rom && x.Type != ItemType.Disk) && (y.Type == ItemType.Rom || y.Type == ItemType.Disk)) { return 1; } else { if (Path.GetDirectoryName(x.Name) == Path.GetDirectoryName(y.Name)) { return Style.CompareNumeric(Path.GetFileName(x.Name), Path.GetFileName(y.Name)); } return Style.CompareNumeric(Path.GetDirectoryName(x.Name), Path.GetDirectoryName(y.Name)); } } return Style.CompareNumeric(x.Machine.Name, y.Machine.Name); } return (norename ? Style.CompareNumeric(x.Machine.Name, y.Machine.Name) : x.Metadata.SourceID - y.Metadata.SourceID); } return (norename ? Style.CompareNumeric(x.Machine.Name, y.Machine.Name) : x.Metadata.SystemID - y.Metadata.SystemID); }); return true; } catch (Exception) { // Absorb the error return false; } } #endregion } }