using System; using System.Collections.Generic; using System.Linq; namespace SabreTools.Helper { public class RomTools { #region Rom-based sorting and merging /// /// 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(List roms, bool norename) { 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) { return String.Compare(x.Name, y.Name); } return String.Compare(x.Machine.Name, y.Machine.Name); } return (norename ? String.Compare(x.Machine.Name, y.Machine.Name) : x.Metadata.SourceID - y.Metadata.SourceID); } return (norename ? String.Compare(x.Machine.Name, y.Machine.Name) : x.Metadata.SystemID - y.Metadata.SystemID); }); return true; } #endregion #region HashData-based sorting and merging /// /// Merge an arbitrary set of ROMs based on the supplied information /// /// List of HashData objects representing the roms to be merged /// Logger object for console and/or file output /// A List of HashData objects representing the merged roms public static List Merge(List inhashes, Logger logger) { // Create output list List outroms = new List(); // Check for null or blank roms first if (inhashes == null || inhashes.Count == 0) { return outroms; } // Then deduplicate them by checking to see if data matches previous saved roms foreach (HashData hash in inhashes) { // If it's a nodump, add and skip if (hash.Roms[0].Nodump) { outroms.Add(hash); 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; HashData savedHash = new HashData(); int pos = -1; for (int i = 0; i < outroms.Count; i++) { HashData lastrom = outroms[i]; RomData savedRom = savedHash.Roms[0]; MachineData savedMachine = savedRom.Machine; // Get the duplicate status dupetype = GetDuplicateStatus(hash, lastrom, logger); // If it's a duplicate, skip adding it to the output but add any missing information if (dupetype != DupeType.None) { savedHash = lastrom; pos = i; savedHash.CRC = (savedHash.CRC == null && hash.CRC != null ? hash.CRC : savedHash.CRC); savedHash.MD5 = (savedHash.MD5 == null && hash.MD5 != null ? hash.MD5 : savedHash.MD5); savedHash.SHA1 = (savedHash.SHA1 == null && hash.SHA1 != null ? hash.SHA1 : savedHash.SHA1); savedRom.DupeType = dupetype; // If the current system has a lower ID than the previous, set the system accordingly if (hash.Roms[0].Machine.SystemID < savedMachine.SystemID) { savedMachine.SystemID = hash.Roms[0].Machine.SystemID; savedMachine.System = hash.Roms[0].Machine.System; savedMachine.Name = hash.Roms[0].Machine.Name; savedRom.Name = hash.Roms[0].Name; } // If the current source has a lower ID than the previous, set the source accordingly if (hash.Roms[0].Machine.SourceID < savedMachine.SourceID) { savedMachine.SourceID = hash.Roms[0].Machine.SourceID; savedMachine.Source = hash.Roms[0].Machine.Source; savedMachine.Name = hash.Roms[0].Machine.Name; savedRom.Name = hash.Roms[0].Name; } break; } } // If no duplicate is found, add it to the list if (dupetype == DupeType.None) { outroms.Add(hash); } // Otherwise, if a new rom information is found, add that else { outroms.RemoveAt(pos); outroms.Insert(pos, savedHash); } } else { outroms.Add(hash); } } // Then return the result return outroms; } /* /// /// List all duplicates found in a DAT based on a rom /// /// Hash 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 HashData objects public static List GetDuplicates(HashData lastrom, DatData datdata, Logger logger, bool remove = false) { List output = new List(); // Check for an empty rom list first if (datdata.Hashes == null || datdata.Hashes.Count == 0) { return output; } // Try to find duplicates for (int i = 0; i < datdata.Hashes.Count; i++) { 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 /// /// Hash to check for duplicate status /// Hash to use as a baseline /// Logger object for console and/or file output /// True if the hashes are duplicates, false otherwise public static bool IsDuplicate(HashData hash, int hashIndex, HashData lastHash, int lastHashIndex, Logger logger) { bool dupefound = hash.Equals(lastHash); // More wonderful SHA-1 logging that has to be done if (hash.SHA1 == lastHash.SHA1 && hash.Size != lastHash.Size) { logger.User("SHA-1 mismatch - Hash: " + hash.SHA1); } return dupefound; } /// /// Return the duplicate status of two hashes /// /// Current hash to check /// Last hash to check against /// Logger object for console and/or file output /// The DupeType corresponding to the relationship between the two public static DupeType GetDuplicateStatus(HashData hash, HashData lasthash, Logger logger) { DupeType output = DupeType.None; /* // If we don't have a duplicate at all, return none if (!IsDuplicate(hash, lasthash, logger)) { return output; } */ // If the duplicate is external already or should be, set it if (lasthash.Roms[0].DupeType >= DupeType.ExternalHash || lasthash.Roms[0].Machine.SystemID != hash.Roms[0].Machine.SystemID || lasthash.Roms[0].Machine.SourceID != hash.Roms[0].Machine.SourceID) { if (lasthash.Roms[0].Machine.Name == hash.Roms[0].Machine.Name && lasthash.Roms[0].Name == hash.Roms[0].Name) { output = DupeType.ExternalAll; } else { output = DupeType.ExternalHash; } } // Otherwise, it's considered an internal dupe else { if (lasthash.Roms[0].Machine.Name == hash.Roms[0].Machine.Name && lasthash.Roms[0].Name == hash.Roms[0].Name) { output = DupeType.InternalAll; } else { output = DupeType.InternalHash; } } return output; } #endregion } }