using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace SabreTools.Helper { public abstract class DatItem : IEquatable, IComparable { #region Protected instance variables // Standard item information protected string _name; protected ItemType _itemType; protected DupeType _dupeType; // Machine information protected string _machineName; protected string _comment; protected string _machineDescription; protected string _year; protected string _manufacturer; protected string _romOf; protected string _cloneOf; protected string _sampleOf; protected string _sourceFile; protected bool _isBios; protected string _board; protected string _rebuildTo; // Source metadata information protected int _systemId; protected string _systemName; protected int _sourceId; protected string _sourceName; #endregion #region Publicly facing variables // Standard item information public string Name { get { return _name; } set { _name = value; } } public ItemType Type { get { return _itemType; } set { _itemType = value; } } public DupeType Dupe { get { return _dupeType; } set { _dupeType = value; } } // Machine information public string MachineName { get { return _machineName; } set { _machineName = value; } } public string Comment { get { return _comment; } set { _comment = value; } } public string MachineDescription { get { return _machineDescription; } set { _machineDescription = value; } } public string Year { get { return _year; } set { _year = value; } } public string Manufacturer { get { return _manufacturer; } set { _manufacturer = value; } } public string RomOf { get { return _romOf; } set { _romOf = value; } } public string CloneOf { get { return _cloneOf; } set { _cloneOf = value; } } public string SampleOf { get { return _sampleOf; } set { _sampleOf = value; } } public string SourceFile { get { return _sourceFile; } set { _sourceFile = value; } } public bool IsBios { get { return _isBios; } set { _isBios = value; } } public string Board { get { return _board; } set { _board = value; } } public string RebuildTo { get { return _rebuildTo; } set { _rebuildTo = value; } } // Source metadata information public int SystemID { get { return _systemId; } set { _systemId = value; } } public string System { get { return _systemName; } set { _systemName = value; } } public int SourceID { get { return _sourceId; } set { _sourceId = value; } } public string Source { get { return _sourceName; } set { _sourceName = value; } } #endregion #region Comparision Methods public int CompareTo(DatItem other) { int ret = 0; try { if (_name == other.Name) { ret = (this.Equals(other) ? 0 : 1); } ret = String.Compare(_name, other.Name); } catch { ret = 1; } return ret; } public abstract bool Equals(DatItem other); /// /// Determine if an item is a duplicate using partial matching logic /// /// DatItem to use as a baseline /// Logger object for console and/or file output /// True if the roms are duplicates, false otherwise public bool IsDuplicate(DatItem lastItem, Logger logger) { bool dupefound = this.Equals(lastItem); // More wonderful SHA-1 logging that has to be done if (_itemType == ItemType.Rom) { if (((Rom)this).SHA1 == ((Rom)lastItem).SHA1 && ((Rom)this).Size != ((Rom)lastItem).Size) { logger.User("SHA-1 mismatch - Hash: " + ((Rom)this).SHA1); } } return dupefound; } /// /// Return the duplicate status of two items /// /// DatItem to check against /// Logger object for console and/or file output /// The DupeType corresponding to the relationship between the two public DupeType GetDuplicateStatus(DatItem lastItem, Logger logger) { DupeType output = DupeType.None; // If we don't have a duplicate at all, return none if (!this.IsDuplicate(lastItem, logger)) { return output; } // If the duplicate is external already or should be, set it if (lastItem.Dupe >= DupeType.ExternalHash || lastItem.SystemID != this.SystemID || lastItem.SourceID != this.SourceID) { if (lastItem.MachineName == this.MachineName && lastItem.Name == this.Name) { output = DupeType.ExternalAll; } else { output = DupeType.ExternalHash; } } // Otherwise, it's considered an internal dupe else { if (lastItem.MachineName == this.MachineName && lastItem.Name == this.Name) { output = DupeType.InternalAll; } else { output = DupeType.InternalHash; } } return output; } #endregion #region Sorting and merging /// /// Determine if a rom should be included based on filters /// /// User supplied item 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(DatItem itemdata, string gamename, string romname, string romtype, long sgt, long slt, long seq, string crc, string md5, string sha1, bool? nodump, Logger logger) { // Take care of Rom and Disk specific differences if (itemdata.Type == ItemType.Rom) { Rom rom = (Rom)itemdata; // Filter on nodump status if (nodump == true && rom.ItemStatus != ItemStatus.Nodump) { return false; } if (nodump == false && rom.ItemStatus == ItemStatus.Nodump) { return false; } // Filter on rom size if (seq != -1 && rom.Size != seq) { return false; } else { if (sgt != -1 && rom.Size < sgt) { return false; } if (slt != -1 && rom.Size > slt) { return false; } } // Filter on crc if (!String.IsNullOrEmpty(crc)) { if (crc.StartsWith("*") && crc.EndsWith("*")) { if (!rom.CRC.ToLowerInvariant().Contains(crc.ToLowerInvariant().Replace("*", ""))) { return false; } } else if (crc.StartsWith("*")) { if (!rom.CRC.EndsWith(crc.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else if (crc.EndsWith("*")) { if (!rom.CRC.StartsWith(crc.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else { if (!String.Equals(rom.CRC, crc, StringComparison.InvariantCultureIgnoreCase)) { return false; } } } // Filter on md5 if (!String.IsNullOrEmpty(md5)) { if (md5.StartsWith("*") && md5.EndsWith("*")) { if (!rom.MD5.ToLowerInvariant().Contains(md5.ToLowerInvariant().Replace("*", ""))) { return false; } } else if (md5.StartsWith("*")) { if (!rom.MD5.EndsWith(md5.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else if (md5.EndsWith("*")) { if (!rom.MD5.StartsWith(md5.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else { if (!String.Equals(rom.MD5, md5, StringComparison.InvariantCultureIgnoreCase)) { return false; } } } // Filter on sha1 if (!String.IsNullOrEmpty(sha1)) { if (sha1.StartsWith("*") && sha1.EndsWith("*")) { if (!rom.SHA1.ToLowerInvariant().Contains(sha1.ToLowerInvariant().Replace("*", ""))) { return false; } } else if (sha1.StartsWith("*")) { if (!rom.SHA1.EndsWith(sha1.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else if (sha1.EndsWith("*")) { if (!rom.SHA1.StartsWith(sha1.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else { if (!String.Equals(rom.SHA1, sha1, StringComparison.InvariantCultureIgnoreCase)) { return false; } } } } else if (itemdata.Type == ItemType.Disk) { Disk rom = (Disk)itemdata; // Filter on nodump status if (nodump == true && rom.ItemStatus != ItemStatus.Nodump) { return false; } if (nodump == false && rom.ItemStatus == ItemStatus.Nodump) { return false; } // Filter on md5 if (!String.IsNullOrEmpty(md5)) { if (md5.StartsWith("*") && md5.EndsWith("*")) { if (!rom.MD5.ToLowerInvariant().Contains(md5.ToLowerInvariant().Replace("*", ""))) { return false; } } else if (md5.StartsWith("*")) { if (!rom.MD5.EndsWith(md5.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else if (md5.EndsWith("*")) { if (!rom.MD5.StartsWith(md5.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else { if (!String.Equals(rom.MD5, md5, StringComparison.InvariantCultureIgnoreCase)) { return false; } } } // Filter on sha1 if (!String.IsNullOrEmpty(sha1)) { if (sha1.StartsWith("*") && sha1.EndsWith("*")) { if (!rom.SHA1.ToLowerInvariant().Contains(sha1.ToLowerInvariant().Replace("*", ""))) { return false; } } else if (sha1.StartsWith("*")) { if (!rom.SHA1.EndsWith(sha1.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else if (sha1.EndsWith("*")) { if (!rom.SHA1.StartsWith(sha1.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else { if (!String.Equals(rom.SHA1, sha1, StringComparison.InvariantCultureIgnoreCase)) { return false; } } } } // Filter on game name if (!String.IsNullOrEmpty(gamename)) { if (gamename.StartsWith("*") && gamename.EndsWith("*")) { if (!itemdata.MachineName.ToLowerInvariant().Contains(gamename.ToLowerInvariant().Replace("*", ""))) { return false; } } else if (gamename.StartsWith("*")) { if (!itemdata.MachineName.EndsWith(gamename.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else if (gamename.EndsWith("*")) { if (!itemdata.MachineName.StartsWith(gamename.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else { if (!String.Equals(itemdata.MachineName, gamename, StringComparison.InvariantCultureIgnoreCase)) { return false; } } } // Filter on rom name if (!String.IsNullOrEmpty(romname)) { if (romname.StartsWith("*") && romname.EndsWith("*")) { if (!itemdata.Name.ToLowerInvariant().Contains(romname.ToLowerInvariant().Replace("*", ""))) { return false; } } else if (romname.StartsWith("*")) { if (!itemdata.Name.EndsWith(romname.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else if (romname.EndsWith("*")) { if (!itemdata.Name.StartsWith(romname.Replace("*", ""), StringComparison.InvariantCultureIgnoreCase)) { return false; } } else { if (!String.Equals(itemdata.Name, romname, StringComparison.InvariantCultureIgnoreCase)) { return false; } } } // Filter on rom type if (String.IsNullOrEmpty(romtype) && itemdata.Type != ItemType.Rom && itemdata.Type != ItemType.Disk) { return false; } if (!String.IsNullOrEmpty(romtype) && !String.Equals(itemdata.Type.ToString(), romtype, StringComparison.InvariantCultureIgnoreCase)) { return false; } return true; } /// /// Merge an arbitrary set of ROMs based on the supplied information /// /// List of File 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 infiles, Logger logger) { // Check for null or blank roms first if (infiles == null || infiles.Count == 0) { return new List(); } // Create output list List outfiles = new List(); // Then deduplicate them by checking to see if data matches previous saved roms foreach (DatItem file in infiles) { // If it's a nodump, add and skip if (file.Type == ItemType.Rom && ((Rom)file).ItemStatus == ItemStatus.Nodump) { outfiles.Add(file); continue; } else if (file.Type == ItemType.Disk && ((Disk)file).ItemStatus == ItemStatus.Nodump) { outfiles.Add(file); continue; } // If it's the first rom in the list, don't touch it if (outfiles.Count != 0) { // Check if the rom is a duplicate DupeType dupetype = DupeType.None; DatItem saveditem = new Rom(); int pos = -1; for (int i = 0; i < outfiles.Count; i++) { DatItem lastrom = outfiles[i]; // Get the duplicate status dupetype = file.GetDuplicateStatus(lastrom, logger); // If it's a duplicate, skip adding it to the output but add any missing information if (dupetype != DupeType.None) { // If we don't have a rom or disk, then just skip adding if (file.Type != ItemType.Rom && file.Type != ItemType.Disk) { continue; } saveditem = lastrom; pos = i; // Roms have more infomration to save if (file.Type == ItemType.Rom) { ((Rom)saveditem).Size = ((Rom)saveditem).Size; ((Rom)saveditem).CRC = (String.IsNullOrEmpty(((Rom)saveditem).CRC) && !String.IsNullOrEmpty(((Rom)file).CRC) ? ((Rom)file).CRC : ((Rom)saveditem).CRC); ((Rom)saveditem).MD5 = (String.IsNullOrEmpty(((Rom)saveditem).MD5) && !String.IsNullOrEmpty(((Rom)file).MD5) ? ((Rom)file).MD5 : ((Rom)saveditem).MD5); ((Rom)saveditem).SHA1 = (String.IsNullOrEmpty(((Rom)saveditem).SHA1) && !String.IsNullOrEmpty(((Rom)file).SHA1) ? ((Rom)file).SHA1 : ((Rom)saveditem).SHA1); } else { ((Disk)saveditem).MD5 = (String.IsNullOrEmpty(((Disk)saveditem).MD5) && !String.IsNullOrEmpty(((Disk)file).MD5) ? ((Disk)file).MD5 : ((Disk)saveditem).MD5); ((Disk)saveditem).SHA1 = (String.IsNullOrEmpty(((Disk)saveditem).SHA1) && !String.IsNullOrEmpty(((Disk)file).SHA1) ? ((Disk)file).SHA1 : ((Disk)saveditem).SHA1); } saveditem.Dupe = dupetype; // If the current system has a lower ID than the previous, set the system accordingly if (file.SystemID < saveditem.SystemID) { saveditem.SystemID = file.SystemID; saveditem.System = file.System; saveditem.MachineName = file.MachineName; saveditem.Name = file.Name; } // If the current source has a lower ID than the previous, set the source accordingly if (file.SourceID < saveditem.SourceID) { saveditem.SourceID = file.SourceID; saveditem.Source = file.Source; saveditem.MachineName = file.MachineName; saveditem.Name = file.Name; } break; } } // If no duplicate is found, add it to the list if (dupetype == DupeType.None) { outfiles.Add(file); } // Otherwise, if a new rom information is found, add that else { outfiles.RemoveAt(pos); outfiles.Insert(pos, saveditem); } } else { outfiles.Add(file); } } // Then return the result return outfiles; } /// /// List all duplicates found in a DAT based on a rom /// /// Item 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 DatItem objects public static List GetDuplicates(DatItem lastitem, DatFile 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 (DatItem rom in roms) { if (rom.IsDuplicate(lastitem, 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; } /// /// Sort a list of File objects by SystemID, SourceID, Game, and Name (in order) /// /// List of File 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 (DatItem x, DatItem y) { if (x.SystemID == y.SystemID) { if (x.SourceID == y.SourceID) { if (x.MachineName == y.MachineName) { 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.MachineName, y.MachineName); } return (norename ? Style.CompareNumeric(x.MachineName, y.MachineName) : x.SourceID - y.SourceID); } return (norename ? Style.CompareNumeric(x.MachineName, y.MachineName) : x.SystemID - y.SystemID); }); return true; } catch (Exception) { // Absorb the error return false; } } #endregion } }