using System; using System.Collections.Generic; using SabreTools.Helper.Data; using SabreTools.Helper.Tools; #if mono using System.IO; #else using Alphaleonis.Win32.Filesystem; #endif using NaturalSort; namespace SabreTools.Helper.Dats { 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 Machine _machine; // Software list information protected bool? _supported; protected string _publisher; protected List> _infos; protected string _partName; protected string _partInterface; protected List> _features; protected string _areaName; protected long? _areaSize; // 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 Machine Machine { get { return _machine; } set { _machine = value; } } // Software list information public bool? Supported { get { return _supported; } set { _supported = value; } } public string Publisher { get { return _publisher; } set { _publisher = value; } } public List> Infos { get { return _infos; } set { _infos = value; } } public string PartName { get { return _partName; } set { _partName = value; } } public string PartInterface { get { return _partInterface; } set { _partInterface = value; } } public List> Features { get { return _features; } set { _features = value; } } public string AreaName { get { return _areaName; } set { _areaName = value; } } public long? AreaSize { get { return _areaSize; } set { _areaSize = 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 = 0x00; // 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.External) != 0 || lastItem.SystemID != this.SystemID || lastItem.SourceID != this.SourceID) { if (lastItem.Machine.Name == this.Machine.Name && lastItem.Name == this.Name) { output = DupeType.External | DupeType.All; } else { output = DupeType.External | DupeType.Hash; } } // Otherwise, it's considered an internal dupe else { if (lastItem.Machine.Name == this.Machine.Name && lastItem.Name == this.Name) { output = DupeType.Internal | DupeType.All; } else { output = DupeType.Internal | DupeType.Hash; } } return output; } #endregion #region Instance Methods #region Sorting and Merging /// /// Check if a DAT contains the given rom /// /// Dat to match against /// Logger object for console and/or file output /// True if it contains the rom, false otherwise public bool HasDuplicates(DatFile datdata, Logger logger) { // Check for an empty rom list first if (datdata.Files == null || datdata.Files.Count == 0) { return false; } // Get the correct dictionary based on what is available string key = ""; if (_itemType == ItemType.Rom && ((Rom)this).CRC != null) { key = ((Rom)this).CRC; datdata.BucketByCRC(false, logger, false); } else if (_itemType == ItemType.Rom && ((Rom)this).MD5 != null) { key = ((Rom)this).MD5; datdata.BucketByMD5(false, logger, false); } else if (_itemType == ItemType.Disk && ((Disk)this).MD5 != null) { key = ((Disk)this).MD5; datdata.BucketByMD5(false, logger, false); } else if (_itemType == ItemType.Rom && ((Rom)this).SHA1 != null) { key = ((Rom)this).SHA1; datdata.BucketBySHA1(false, logger, false); } else if (_itemType == ItemType.Disk && ((Disk)this).SHA1 != null) { key = ((Disk)this).SHA1; datdata.BucketBySHA1(false, logger, false); } else if (_itemType == ItemType.Rom) { key = ((Rom)this).Size.ToString(); datdata.BucketBySize(false, logger, false); } else { key = "-1"; datdata.BucketBySize(false, logger, false); } // If the key doesn't exist, return the empty list if (!datdata.Files.ContainsKey(key)) { return false; } // Try to find duplicates List roms = datdata.Files[key]; foreach (DatItem rom in roms) { if (IsDuplicate(rom, logger)) { return true; } } return false; } /// /// List all duplicates found in a DAT based on a rom /// /// 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 List GetDuplicates(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; } // Get the correct dictionary based on what is available string key = ""; if (_itemType == ItemType.Rom && ((Rom)this).CRC != null) { key = ((Rom)this).CRC; datdata.BucketByCRC(false, logger, false); } else if (_itemType == ItemType.Rom && ((Rom)this).MD5 != null) { key = ((Rom)this).MD5; datdata.BucketByMD5(false, logger, false); } else if (_itemType == ItemType.Disk && ((Disk)this).MD5 != null) { key = ((Disk)this).MD5; datdata.BucketByMD5(false, logger, false); } else if (_itemType == ItemType.Rom && ((Rom)this).SHA1 != null) { key = ((Rom)this).SHA1; datdata.BucketBySHA1(false, logger, false); } else if (_itemType == ItemType.Disk && ((Disk)this).SHA1 != null) { key = ((Disk)this).SHA1; datdata.BucketBySHA1(false, logger, false); } else if (_itemType == ItemType.Rom) { key = ((Rom)this).Size.ToString(); datdata.BucketBySize(false, logger, false); } else { key = "-1"; datdata.BucketBySize(false, logger, false); } // If the key doesn't exist, return the empty list if (!datdata.Files.ContainsKey(key)) { return output; } // Try to find duplicates List roms = datdata.Files[key]; List left = new List(); foreach (DatItem rom in roms) { if (IsDuplicate(rom, 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; } #endregion #endregion // Instance Methods #region Static Methods #region Sorting and Merging /// /// 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 = 0x00; 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 != 0x00) { // 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.Machine.Name = file.Machine.Name; 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.Machine.Name = file.Machine.Name; saveditem.Name = file.Name; } break; } } // If no duplicate is found, add it to the list if (dupetype == 0x00) { 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; } /// /// 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) { roms.Sort(delegate (DatItem x, DatItem y) { try { NaturalComparer nc = new NaturalComparer(); if (x.SystemID == y.SystemID) { if (x.SourceID == y.SourceID) { if (x.Machine != null && y.Machine != null && 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(Style.RemovePathUnsafeCharacters(x.Name)) == Path.GetDirectoryName(Style.RemovePathUnsafeCharacters(y.Name))) { return nc.Compare(Path.GetFileName(Style.RemovePathUnsafeCharacters(x.Name)), Path.GetFileName(Style.RemovePathUnsafeCharacters(y.Name))); } return nc.Compare(Path.GetDirectoryName(Style.RemovePathUnsafeCharacters(x.Name)), Path.GetDirectoryName(Style.RemovePathUnsafeCharacters(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 nc.Compare(Path.GetFileName(x.Name), Path.GetFileName(y.Name)); } return nc.Compare(Path.GetDirectoryName(x.Name), Path.GetDirectoryName(y.Name)); } } return nc.Compare(x.Machine.Name, y.Machine.Name); } return (norename ? nc.Compare(x.Machine.Name, y.Machine.Name) : x.SourceID - y.SourceID); } return (norename ? nc.Compare(x.Machine.Name, y.Machine.Name) : x.SystemID - y.SystemID); } catch (Exception) { // Absorb the error return 0; } }); return true; } #endregion #endregion // Static Methods } }