diff --git a/SabreTools.Helper/Objects/DatObjects/DatItem.cs b/SabreTools.Helper/Objects/DatObjects/DatItem.cs new file mode 100644 index 00000000..a967dbbf --- /dev/null +++ b/SabreTools.Helper/Objects/DatObjects/DatItem.cs @@ -0,0 +1,784 @@ +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.Nodump) + { + return false; + } + if (nodump == false && rom.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.Nodump) + { + return false; + } + if (nodump == false && rom.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).Nodump) + { + outfiles.Add(file); + continue; + } + else if (file.Type == ItemType.Disk && ((Disk)file).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 + } +}