using System.IO; using System.Linq; using System.Xml.Serialization; using SabreTools.Core; using SabreTools.DatFiles.Formats; using SabreTools.Logging; using Newtonsoft.Json; namespace SabreTools.DatFiles { /// /// Represents a format-agnostic DAT /// /// /// The fact that this one class could be separated into as many partial /// classes as it did means that the functionality here should probably /// be split out into either separate classes or even an entirely separate /// namespace. Also, with that in mind, each of the individual DatFile types /// probably should only need to inherit from a thin abstract class and /// should not be exposed as part of the library, instead being taken care /// of behind the scenes as part of the reading and writing. /// [JsonObject("datfile"), XmlRoot("datfile")] public abstract partial class DatFile { #region Fields /// /// Header values /// [JsonProperty("header"), XmlElement("header")] public DatHeader Header { get; set; } = new DatHeader(); /// /// DatItems and related statistics /// [JsonProperty("items"), XmlElement("items")] public ItemDictionary Items { get; set; } = new ItemDictionary(); #endregion #region Logging /// /// Logging object /// [JsonIgnore, XmlIgnore] protected Logger logger; #endregion #region Constructors /// /// Create a new DatFile from an existing one /// /// DatFile to get the values from public DatFile(DatFile datFile) { logger = new Logger(this); if (datFile != null) { Header = datFile.Header; Items = datFile.Items; } } /// /// Create a specific type of DatFile to be used based on a format and a base DAT /// /// Format of the DAT to be created /// DatFile containing the information to use in specific operations /// For relevant types, assume the usage of quotes /// DatFile of the specific internal type that corresponds to the inputs public static DatFile Create(DatFormat? datFormat = null, DatFile baseDat = null, bool quotes = true) { switch (datFormat) { case DatFormat.AttractMode: return new AttractMode(baseDat); case DatFormat.ClrMamePro: return new ClrMamePro(baseDat, quotes); case DatFormat.CSV: return new SeparatedValue(baseDat, ','); case DatFormat.DOSCenter: return new DosCenter(baseDat); case DatFormat.EverdriveSMDB: return new EverdriveSMDB(baseDat); case DatFormat.Listrom: return new Listrom(baseDat); case DatFormat.Listxml: return new Listxml(baseDat); case DatFormat.Logiqx: return new Logiqx(baseDat, false); case DatFormat.LogiqxDeprecated: return new Logiqx(baseDat, true); case DatFormat.MissFile: return new Missfile(baseDat); case DatFormat.OfflineList: return new OfflineList(baseDat); case DatFormat.OpenMSX: return new OpenMSX(baseDat); case DatFormat.RedumpMD5: return new Hashfile(baseDat, Hash.MD5); #if NET_FRAMEWORK case DatFormat.RedumpRIPEMD160: return new Hashfile(baseDat, Hash.RIPEMD160); #endif case DatFormat.RedumpSFV: return new Hashfile(baseDat, Hash.CRC); case DatFormat.RedumpSHA1: return new Hashfile(baseDat, Hash.SHA1); case DatFormat.RedumpSHA256: return new Hashfile(baseDat, Hash.SHA256); case DatFormat.RedumpSHA384: return new Hashfile(baseDat, Hash.SHA384); case DatFormat.RedumpSHA512: return new Hashfile(baseDat, Hash.SHA512); case DatFormat.RedumpSpamSum: return new Hashfile(baseDat, Hash.SpamSum); case DatFormat.RomCenter: return new RomCenter(baseDat); case DatFormat.SabreJSON: return new SabreJSON(baseDat); case DatFormat.SabreXML: return new SabreXML(baseDat); case DatFormat.SoftwareList: return new SoftwareList(baseDat); case DatFormat.SSV: return new SeparatedValue(baseDat, ';'); case DatFormat.TSV: return new SeparatedValue(baseDat, '\t'); // We use new-style Logiqx as a backup for generic DatFile case null: default: return new Logiqx(baseDat, false); } } /// /// Create a new DatFile from an existing DatHeader /// /// DatHeader to get the values from public static DatFile Create(DatHeader datHeader) { DatFile datFile = Create(datHeader.DatFormat); datFile.Header = (DatHeader)datHeader.Clone(); return datFile; } /// /// Add items from another DatFile to the existing DatFile /// /// DatFile to add from /// If items should be deleted from the source DatFile public void AddFromExisting(DatFile datFile, bool delete = false) { // Get the list of keys from the DAT var keys = datFile.Items.Keys.ToList(); foreach (string key in keys) { // Add everything from the key to the internal DAT Items.AddRange(key, datFile.Items[key]); // Now remove the key from the source DAT if (delete) datFile.Items.Remove(key); } // Now remove the file dictionary from the source DAT if (delete) datFile.Items = null; } /// /// Apply a DatHeader to an existing DatFile /// /// DatHeader to get the values from public void ApplyDatHeader(DatHeader datHeader) { Header.ConditionalCopy(datHeader); } /// /// Fill the header values based on existing Header and path /// /// Path used for creating a name, if necessary /// True if the date should be omitted from name and description, false otherwise public void FillHeaderFromPath(string path, bool bare) { // If the description is defined but not the name, set the name from the description if (string.IsNullOrWhiteSpace(Header.Name) && !string.IsNullOrWhiteSpace(Header.Description)) { Header.Name = Header.Description; } // If the name is defined but not the description, set the description from the name else if (!string.IsNullOrWhiteSpace(Header.Name) && string.IsNullOrWhiteSpace(Header.Description)) { Header.Description = Header.Name + (bare ? string.Empty : $" ({Header.Date})"); } // If neither the name or description are defined, set them from the automatic values else if (string.IsNullOrWhiteSpace(Header.Name) && string.IsNullOrWhiteSpace(Header.Description)) { string[] splitpath = path.TrimEnd(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar); Header.Name = splitpath.Last(); Header.Description = Header.Name + (bare ? string.Empty : $" ({Header.Date})"); } } #endregion } }