using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using SabreTools.Library.Data; using SabreTools.Library.Items; using SabreTools.Library.Tools; #if MONO using System.IO; #else using Alphaleonis.Win32.Filesystem; using FileStream = System.IO.FileStream; using StreamWriter = System.IO.StreamWriter; #endif namespace SabreTools.Library.DatFiles { /// /// Represents a format-agnostic DAT /// public partial class DatFile { #region Private instance variables // Internal DatHeader values private DatHeader _datHeader = new DatHeader(); // DatItems dictionary private SortedDictionary> _items = new SortedDictionary>(); private SortedBy _sortedBy; // Internal statistical data DatStats _datStats = new DatStats(); #endregion #region Publicly facing variables // Data common to most DAT types public string FileName { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.FileName; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.FileName = value; } } public string Name { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Name; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Name = value; } } public string Description { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Description; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Description = value; } } public string RootDir { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.RootDir; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.RootDir = value; } } public string Category { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Category; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Category = value; } } public string Version { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Version; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Version = value; } } public string Date { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Date; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Date = value; } } public string Author { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Author; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Author = value; } } public string Email { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Email; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Email = value; } } public string Homepage { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Homepage; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Homepage = value; } } public string Url { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Url; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Url = value; } } public string Comment { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Comment; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Comment = value; } } public string Header { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Header; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Header = value; } } public string Type // Generally only used for SuperDAT { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Type; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Type = value; } } public ForceMerging ForceMerging { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.ForceMerging; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.ForceMerging = value; } } public ForceNodump ForceNodump { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.ForceNodump; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.ForceNodump = value; } } public ForcePacking ForcePacking { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.ForcePacking; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.ForcePacking = value; } } public DatFormat DatFormat { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.DatFormat; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.DatFormat = value; } } public bool ExcludeOf { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.ExcludeOf; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.ExcludeOf = value; } } public DedupeType DedupeRoms { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.DedupeRoms; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.DedupeRoms = value; } } public Hash StripHash { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.StripHash; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.StripHash = value; } } public bool OneGameOneRegion { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.OneGameOneRegion; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.OneGameOneRegion = value; } } public List Regions { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Regions; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Regions = value; } } public SortedBy SortedBy { get { return _sortedBy; } } // Data specific to the Miss DAT type public bool UseGame { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.UseGame; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.UseGame = value; } } public string Prefix { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Prefix; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Prefix = value; } } public string Postfix { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Postfix; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Postfix = value; } } public bool Quotes { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Quotes; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Quotes = value; } } public string RepExt { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.RepExt; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.RepExt = value; } } public string AddExt { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.AddExt; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.AddExt = value; } } public bool RemExt { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.RemExt; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.RemExt = value; } } public bool GameName { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.GameName; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.GameName = value; } } public bool Romba { get { if (_datHeader == null) { _datHeader = new DatHeader(); } return _datHeader.Romba; } set { if (_datHeader == null) { _datHeader = new DatHeader(); } _datHeader.Romba = value; } } // Statistical data related to the DAT public long Count { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.Count; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.Count = value; } } public long ArchiveCount { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.ArchiveCount; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.ArchiveCount = value; } } public long BiosSetCount { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.BiosSetCount; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.BiosSetCount = value; } } public long DiskCount { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.DiskCount; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.DiskCount = value; } } public long ReleaseCount { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.ReleaseCount; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.ReleaseCount = value; } } public long RomCount { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.RomCount; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.RomCount = value; } } public long SampleCount { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.SampleCount; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.SampleCount = value; } } public long TotalSize { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.TotalSize; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.TotalSize = value; } } public long CRCCount { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.CRCCount; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.CRCCount = value; } } public long MD5Count { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.MD5Count; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.MD5Count = value; } } public long SHA1Count { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.SHA1Count; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.SHA1Count = value; } } public long SHA256Count { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.SHA256Count; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.SHA256Count = value; } } public long SHA384Count { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.SHA384Count; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.SHA384Count = value; } } public long SHA512Count { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.SHA512Count; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.SHA512Count = value; } } public long BaddumpCount { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.BaddumpCount; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.BaddumpCount = value; } } public long GoodCount { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.GoodCount; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.GoodCount = value; } } public long NodumpCount { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.NodumpCount; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.NodumpCount = value; } } public long VerifiedCount { get { if (_datStats == null) { _datStats = new DatStats(); } return _datStats.VerifiedCount; } private set { if (_datStats == null) { _datStats = new DatStats(); } _datStats.VerifiedCount = value; } } #endregion #region Instance Methods #region Accessors /// /// Passthrough to access the file dictionary /// /// Key in the dictionary to reference /// We don't want to allow direct setting of values because it bypasses the statistics public List this[string key] { get { // If the dictionary is null, create it if (_items == null) { _items = new SortedDictionary>(); } lock (_items) { // If the key is missing from the dictionary, add it if (!_items.ContainsKey(key)) { _items.Add(key, new List()); } // Now return the value return _items[key]; } } } /// /// Add a new key to the file dictionary /// /// Key in the dictionary to add public void Add(string key) { // If the dictionary is null, create it if (_items == null) { _items = new SortedDictionary>(); } lock (_items) { // If the key is missing from the dictionary, add it if (!_items.ContainsKey(key)) { _items.Add(key, new List()); } } } /// /// Add a value to the file dictionary /// /// Key in the dictionary to add to /// Value to add to the dictionary public void Add(string key, DatItem value) { // If the dictionary is null, create it if (_items == null) { _items = new SortedDictionary>(); } // Add the key, if necessary Add(key); lock (_items) { // Now add the value _items[key].Add(value); // Now update the statistics _datStats.AddItem(value); } } /// /// Add a range of values to the file dictionary /// /// Key in the dictionary to add to /// Value to add to the dictionary public void AddRange(string key, List value) { // If the dictionary is null, create it if (_items == null) { _items = new SortedDictionary>(); } // Add the key, if necessary Add(key); lock (_items) { // Now add the value _items[key].AddRange(value); // Now update the statistics foreach (DatItem item in value) { _datStats.AddItem(item); } } } /// /// Get if the file dictionary contains the key /// /// Key in the dictionary to check /// True if the key exists, false otherwise public bool Contains(string key) { bool contains = false; // If the dictionary is null, create it if (_items == null) { _items = new SortedDictionary>(); } // If the key is null, we return false since keys can't be null if (key == null) { return contains; } lock (_items) { contains = _items.ContainsKey(key); } return contains; } /// /// Get if the file dictionary contains the key and value /// /// Key in the dictionary to check /// Value in the dictionary to check /// True if the key exists, false otherwise public bool Contains(string key, DatItem value) { bool contains = false; // If the dictionary is null, create it if (_items == null) { _items = new SortedDictionary>(); } // If the key is null, we return false since keys can't be null if (key == null) { return contains; } lock (_items) { if (_items.ContainsKey(key)) { contains = _items.ContainsKey(key); } } return contains; } /// /// Get the keys from the file dictionary /// /// IEnumerable of the keys public IEnumerable Keys { get { // If the dictionary is null, create it if (_items == null) { _items = new SortedDictionary>(); } lock (_items) { return _items.Keys; } } } /// /// Remove a key from the file dictionary if it exists /// /// Key in the dictionary to remove public void Remove(string key) { // If the dictionary is null, create it if (_items == null) { _items = new SortedDictionary>(); } // If the key doesn't exist, return if (!Contains(key)) { return; } lock (_items) { // Remove the statistics first foreach (DatItem item in _items[key]) { _datStats.RemoveItem(item); } // Remove the key from the dictionary _items.Remove(key); } } /// /// Remove a value from the file dictionary if it exists /// /// Key in the dictionary to remove from /// Value to remove from the dictionary public void Remove(string key, DatItem value) { // If the dictionary is null, create it if (_items == null) { _items = new SortedDictionary>(); } // If the key and value doesn't exist, return if (!Contains(key, value)) { return; } lock (_items) { // While the key is in the dictionary and the item is there, remove it while (_items.ContainsKey(key) && _items[key].Contains(value)) { // Remove the statistics first _datStats.RemoveItem(value); _items[key].Remove(value); } } } /// /// Remove a range of values from the file dictionary if they exists /// /// Key in the dictionary to remove from /// Value to remove from the dictionary public void RemoveRange(string key, List value) { foreach(DatItem item in value) { Remove(key, item); } } #endregion #region Constructors /// /// Create a new, empty DatFile object /// public DatFile() { _items = new SortedDictionary>(); } /// /// Create a new DatFile from an existing one using the header values only /// /// public DatFile(DatFile datFile) { _datHeader = (DatHeader)datFile._datHeader.Clone(); } #endregion #region Dictionary Manipulation /// /// Clones the files dictionary /// /// A new files dictionary instance public SortedDictionary> CloneDictionary() { // Create the placeholder dictionary to be used SortedDictionary> sorted = new SortedDictionary>(); // Now perform a deep clone on the entire dictionary List keys = Keys.ToList(); foreach (string key in keys) { // Clone each list of DATs in the dictionary List olditems = this[key]; List newitems = new List(); foreach (DatItem item in olditems) { newitems.Add((DatItem)item.Clone()); } // If the key is missing from the new dictionary, add it if (!sorted.ContainsKey(key)) { sorted.Add(key, new List()); } // Now add the list of items sorted[key].AddRange(newitems); } return sorted; } /// /// Delete the file dictionary /// public void DeleteDictionary() { _items = null; // Reset statistics _datStats.Reset(); } /// /// Reset the file dictionary /// public void ResetDictionary() { _items = new SortedDictionary>(); // Reset statistics _datStats.Reset(); } #endregion #region Parsing /// /// Parse a DAT and return all found games and roms within /// /// Name of the file to be parsed /// System ID for the DAT /// Source ID for the DAT /// The DatData object representing found roms to this point /// True if full pathnames are to be kept, false otherwise (default) /// True if game names are sanitized, false otherwise (default) /// True if we should remove non-ASCII characters from output, false otherwise (default) /// True if descriptions should be used as names, false otherwise (default) /// True if original extension should be kept, false otherwise (default) /// True if tags from the DAT should be used to merge the output, false otherwise (default) public void Parse(string filename, int sysid, int srcid, bool keep = false, bool clean = false, bool remUnicode = false, bool descAsName = false, bool keepext = false, bool useTags = false) { Parse(filename, sysid, srcid, SplitType.None, keep: keep, clean: clean, remUnicode: remUnicode, descAsName: descAsName, keepext: keepext, useTags: useTags); } /// /// Parse a DAT and return all found games and roms within /// /// Name of the file to be parsed /// System ID for the DAT /// Source ID for the DAT> /// Type of the split that should be performed (split, merged, fully merged) /// True if full pathnames are to be kept, false otherwise (default) /// True if game names are sanitized, false otherwise (default) /// True if we should remove non-ASCII characters from output, false otherwise (default) /// True if descriptions should be used as names, false otherwise (default) /// True if original extension should be kept, false otherwise (default) /// True if tags from the DAT should be used to merge the output, false otherwise (default) public void Parse( // Standard Dat parsing string filename, int sysid, int srcid, // Rom renaming SplitType splitType, // Miscellaneous bool keep = false, bool clean = false, bool remUnicode = false, bool descAsName = false, bool keepext = false, bool useTags = false) { // Check the file extension first as a safeguard string ext = Path.GetExtension(filename).ToLowerInvariant(); if (ext.StartsWith(".")) { ext = ext.Substring(1); } if (ext != "dat" && ext != "csv" && ext != "md5" && ext != "sfv" && ext != "sha1" && ext != "sha256" && ext != "sha384" && ext != "sha512" && ext != "tsv" && ext != "txt" && ext != "xml") { return; } // If the output filename isn't set already, get the internal filename FileName = (String.IsNullOrEmpty(FileName) ? (keepext ? Path.GetFileName(filename) : Path.GetFileNameWithoutExtension(filename)) : FileName); // If the output type isn't set already, get the internal output type DatFormat = (DatFormat == 0 ? FileTools.GetDatFormat(filename) : DatFormat); // Now parse the correct type of DAT try { switch (FileTools.GetDatFormat(filename)) { case DatFormat.AttractMode: AttractMode.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); break; case DatFormat.ClrMamePro: ClrMamePro.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); break; case DatFormat.CSV: SeparatedValue.Parse(this, filename, sysid, srcid, ',', keep, clean, remUnicode); break; case DatFormat.DOSCenter: DosCenter.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); break; case DatFormat.Listroms: Listroms.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); break; case DatFormat.Logiqx: Logiqx.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); break; case DatFormat.OfflineList: OfflineList.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); break; case DatFormat.RedumpMD5: Hashfile.Parse(this, filename, sysid, srcid, Hash.MD5, clean, remUnicode); break; case DatFormat.RedumpSFV: Hashfile.Parse(this, filename, sysid, srcid, Hash.CRC, clean, remUnicode); break; case DatFormat.RedumpSHA1: Hashfile.Parse(this, filename, sysid, srcid, Hash.SHA1, clean, remUnicode); break; case DatFormat.RedumpSHA256: Hashfile.Parse(this, filename, sysid, srcid, Hash.SHA256, clean, remUnicode); break; case DatFormat.RedumpSHA384: Hashfile.Parse(this, filename, sysid, srcid, Hash.SHA384, clean, remUnicode); break; case DatFormat.RedumpSHA512: Hashfile.Parse(this, filename, sysid, srcid, Hash.SHA512, clean, remUnicode); break; case DatFormat.RomCenter: RomCenter.Parse(this, filename, sysid, srcid, clean, remUnicode); break; case DatFormat.SabreDat: SabreDat.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); break; case DatFormat.SoftwareList: SoftwareList.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); break; case DatFormat.TSV: SeparatedValue.Parse(this, filename, sysid, srcid, '\t', keep, clean, remUnicode); break; default: return; } } catch (Exception ex) { Globals.Logger.Error("Error with file '{0}': {1}", filename, ex); } // If we want to use descriptions as names, update everything if (descAsName) { MachineDescriptionToName(); } // If we are using tags from the DAT, set the proper input for split type unless overridden if (useTags && splitType == SplitType.None) { switch (ForceMerging) { case ForceMerging.None: // No-op break; case ForceMerging.Split: splitType = SplitType.Split; break; case ForceMerging.Merged: splitType = SplitType.Merged; break; case ForceMerging.NonMerged: splitType = SplitType.NonMerged; break; case ForceMerging.Full: splitType = SplitType.FullNonMerged; break; } } // Now we pre-process the DAT with the splitting/merging mode switch (splitType) { case SplitType.None: // No-op break; case SplitType.DeviceNonMerged: CreateDeviceNonMergedSets(DedupeType.None); break; case SplitType.FullNonMerged: CreateFullyNonMergedSets(DedupeType.None); break; case SplitType.NonMerged: CreateNonMergedSets(DedupeType.None); break; case SplitType.Merged: CreateMergedSets(DedupeType.None); break; case SplitType.Split: CreateSplitSets(DedupeType.None); break; } } /// /// Add a rom to the Dat after checking /// /// Item data to check against /// True if the names should be cleaned to WoD standards, false otherwise /// True if we should remove non-ASCII characters from output, false otherwise (default) /// The key for the item public string ParseAddHelper(DatItem item, bool clean, bool remUnicode) { string key = ""; // If there's no name in the rom, we log and skip it if (item.Name == null) { Globals.Logger.Warning("{0}: Rom with no name found! Skipping...", FileName); return key; } // If the name ends with a directory separator, we log and skip it (DOSCenter only?) if (item.Name.EndsWith("/") || item.Name.EndsWith("\\")) { Globals.Logger.Warning("{0}: Rom ending with directory separator found: '{1}'. Skipping...", FileName, item.Name); return key; } // If we're in cleaning mode, sanitize the game name item.MachineName = (clean ? Style.CleanGameName(item.MachineName) : item.MachineName); // If we're stripping unicode characters, do so from all relevant things if (remUnicode) { item.Name = Style.RemoveUnicodeCharacters(item.Name); item.MachineName = Style.RemoveUnicodeCharacters(item.MachineName); item.MachineDescription = Style.RemoveUnicodeCharacters(item.MachineDescription); } // If we have a Rom or a Disk, clean the hash data if (item.Type == ItemType.Rom) { Rom itemRom = (Rom)item; // Sanitize the hashes from null, hex sizes, and "true blank" strings itemRom.CRC = Style.CleanHashData(itemRom.CRC, Constants.CRCLength); itemRom.MD5 = Style.CleanHashData(itemRom.MD5, Constants.MD5Length); itemRom.SHA1 = Style.CleanHashData(itemRom.SHA1, Constants.SHA1Length); itemRom.SHA256 = Style.CleanHashData(itemRom.SHA256, Constants.SHA256Length); itemRom.SHA384 = Style.CleanHashData(itemRom.SHA384, Constants.SHA384Length); itemRom.SHA512 = Style.CleanHashData(itemRom.SHA512, Constants.SHA512Length); // If we have a rom and it's missing size AND the hashes match a 0-byte file, fill in the rest of the info if ((itemRom.Size == 0 || itemRom.Size == -1) && ((itemRom.CRC == Constants.CRCZero || String.IsNullOrEmpty(itemRom.CRC)) || itemRom.MD5 == Constants.MD5Zero || itemRom.SHA1 == Constants.SHA1Zero || itemRom.SHA256 == Constants.SHA256Zero || itemRom.SHA384 == Constants.SHA384Zero || itemRom.SHA512 == Constants.SHA512Zero)) { // TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually itemRom.Size = Constants.SizeZero; itemRom.CRC = Constants.CRCZero; itemRom.MD5 = Constants.MD5Zero; itemRom.SHA1 = Constants.SHA1Zero; itemRom.SHA256 = null; itemRom.SHA384 = null; itemRom.SHA512 = null; //itemRom.SHA256 = Constants.SHA256Zero; //itemRom.SHA384 = Constants.SHA384Zero; //itemRom.SHA512 = Constants.SHA512Zero; } // If the file has no size and it's not the above case, skip and log else if (itemRom.ItemStatus != ItemStatus.Nodump && (itemRom.Size == 0 || itemRom.Size == -1)) { Globals.Logger.Verbose("{0}: Incomplete entry for '{1}' will be output as nodump", FileName, itemRom.Name); itemRom.ItemStatus = ItemStatus.Nodump; } // If the file has a size but aboslutely no hashes, skip and log else if (itemRom.ItemStatus != ItemStatus.Nodump && itemRom.Size > 0 && String.IsNullOrEmpty(itemRom.CRC) && String.IsNullOrEmpty(itemRom.MD5) && String.IsNullOrEmpty(itemRom.SHA1) && String.IsNullOrEmpty(itemRom.SHA256) && String.IsNullOrEmpty(itemRom.SHA384) && String.IsNullOrEmpty(itemRom.SHA512)) { Globals.Logger.Verbose("{0}: Incomplete entry for '{1}' will be output as nodump", FileName, itemRom.Name); itemRom.ItemStatus = ItemStatus.Nodump; } item = itemRom; } else if (item.Type == ItemType.Disk) { Disk itemDisk = (Disk)item; // Sanitize the hashes from null, hex sizes, and "true blank" strings itemDisk.MD5 = Style.CleanHashData(itemDisk.MD5, Constants.MD5Length); itemDisk.SHA1 = Style.CleanHashData(itemDisk.SHA1, Constants.SHA1Length); itemDisk.SHA256 = Style.CleanHashData(itemDisk.SHA256, Constants.SHA256Length); itemDisk.SHA384 = Style.CleanHashData(itemDisk.SHA384, Constants.SHA384Length); itemDisk.SHA512 = Style.CleanHashData(itemDisk.SHA512, Constants.SHA512Length); // If the file has aboslutely no hashes, skip and log if (itemDisk.ItemStatus != ItemStatus.Nodump && String.IsNullOrEmpty(itemDisk.MD5) && String.IsNullOrEmpty(itemDisk.SHA1) && String.IsNullOrEmpty(itemDisk.SHA256) && String.IsNullOrEmpty(itemDisk.SHA384) && String.IsNullOrEmpty(itemDisk.SHA512)) { Globals.Logger.Verbose("Incomplete entry for '{0}' will be output as nodump", itemDisk.Name); itemDisk.ItemStatus = ItemStatus.Nodump; } item = itemDisk; } // Get the key and add statistical data switch (item.Type) { case ItemType.Archive: case ItemType.BiosSet: case ItemType.Release: case ItemType.Sample: key = item.Type.ToString(); break; case ItemType.Disk: key = ((Disk)item).MD5; break; case ItemType.Rom: key = ((Rom)item).Size + "-" + ((Rom)item).CRC; break; default: key = "default"; break; } // Add the item to the DAT Add(key, item); return key; } /// /// Add a rom to the Dat after checking /// /// Item data to check against /// True if the names should be cleaned to WoD standards, false otherwise /// True if we should remove non-ASCII characters from output, false otherwise (default) /// The key for the item public async Task ParseAddHelperAsync(DatItem item, bool clean, bool remUnicode) { return await Task.Run(() => ParseAddHelper(item, clean, remUnicode)); } #endregion #region Writing /// /// Create and open an output file for writing direct from a dictionary /// /// All information for creating the datfile header /// Set the output directory /// True if games should only be compared on game and file name (default), false if system and source are counted /// True if DAT statistics should be output on write, false otherwise (default) /// True if blank roms should be skipped on output, false otherwise (default) /// True if files should be overwritten (default), false if they should be renamed instead /// True if the DAT was written correctly, false otherwise public bool WriteToFile(string outDir, bool norename = true, bool stats = false, bool ignoreblanks = false, bool overwrite = true) { // If there's nothing there, abort if (Count == 0) { Globals.Logger.User("There were no items to write out!"); return false; } // If output directory is empty, use the current folder if (outDir == null || outDir.Trim() == "") { Globals.Logger.Verbose("No output directory defined, defaulting to curent folder"); outDir = Environment.CurrentDirectory; } // Create the output directory if it doesn't already exist if (!Directory.Exists(outDir)) { Directory.CreateDirectory(outDir); } // If the DAT has no output format, default to XML if (DatFormat == 0) { Globals.Logger.Verbose("No DAT format defined, defaulting to XML"); DatFormat = DatFormat.Logiqx; } // Make sure that the three essential fields are filled in if (String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description)) { FileName = Name = Description = "Default"; } else if (String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description)) { FileName = Name = Description; } else if (String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description)) { FileName = Description = Name; } else if (String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description)) { FileName = Description; } else if (!String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description)) { Name = Description = FileName; } else if (!String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description)) { Name = Description; } else if (!String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description)) { Description = Name; } else if (!String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description)) { // Nothing is needed } // Output initial statistics, for kicks if (stats) { OutputStats(new Dictionary(), StatDatFormat.None, recalculate: (RomCount + DiskCount == 0), baddumpCol: true, nodumpCol: true); } // Bucket and dedupe according to the flag if (DedupeRoms == DedupeType.Full) { BucketBy(SortedBy.CRC, DedupeRoms, norename: norename); } else if (DedupeRoms == DedupeType.Game) { BucketBy(SortedBy.Game, DedupeRoms, norename: norename); } // Bucket roms by game name, if not already BucketBy(SortedBy.Game, DedupeType.None, norename: norename); // Output the number of items we're going to be writing Globals.Logger.User("A total of {0} items will be written out to '{1}'", Count, FileName); // Filter the DAT by 1G1R rules, if we're supposed to // TODO: Create 1G1R logic before write // If we are removing hashes, do that now if (StripHash != 0x0) { StripHashesFromItems(); } // Get the outfile names Dictionary outfiles = Style.CreateOutfileNames(outDir, this, overwrite); try { // Write out all required formats Parallel.ForEach(outfiles.Keys, Globals.ParallelOptions, datFormat => { string outfile = outfiles[datFormat]; switch (datFormat) { case DatFormat.AttractMode: AttractMode.WriteToFile(this, outfile); break; case DatFormat.ClrMamePro: ClrMamePro.WriteToFile(this, outfile, ignoreblanks); break; case DatFormat.CSV: SeparatedValue.WriteToFile(this, outfile, ',', ignoreblanks); break; case DatFormat.DOSCenter: DosCenter.WriteToFile(this, outfile, ignoreblanks); break; case DatFormat.Listroms: Listroms.WriteToFile(this, outfile, ignoreblanks); break; case DatFormat.Logiqx: Logiqx.WriteToFile(this, outfile, ignoreblanks); break; case DatFormat.MissFile: Missfile.WriteToFile(this, outfile, ignoreblanks); break; case DatFormat.OfflineList: OfflineList.WriteToFile(this, outfile, ignoreblanks); break; case DatFormat.RedumpMD5: Hashfile.WriteToFile(this, outfile, Hash.MD5, ignoreblanks); break; case DatFormat.RedumpSFV: Hashfile.WriteToFile(this, outfile, Hash.CRC, ignoreblanks); break; case DatFormat.RedumpSHA1: Hashfile.WriteToFile(this, outfile, Hash.SHA1, ignoreblanks); break; case DatFormat.RedumpSHA256: Hashfile.WriteToFile(this, outfile, Hash.SHA256, ignoreblanks); break; case DatFormat.RedumpSHA384: Hashfile.WriteToFile(this, outfile, Hash.SHA384, ignoreblanks); break; case DatFormat.RedumpSHA512: Hashfile.WriteToFile(this, outfile, Hash.SHA512, ignoreblanks); break; case DatFormat.RomCenter: RomCenter.WriteToFile(this, outfile, ignoreblanks); break; case DatFormat.SabreDat: SabreDat.WriteToFile(this, outfile, ignoreblanks); break; case DatFormat.SoftwareList: SoftwareList.WriteToFile(this, outfile, ignoreblanks); break; case DatFormat.TSV: SeparatedValue.WriteToFile(this, outfile, '\t', ignoreblanks); break; } }); } catch (Exception ex) { Globals.Logger.Error(ex.ToString()); return false; } return true; } #endregion #endregion // Instance Methods } }