diff --git a/SabreTools.DatFiles/Formats/Logiqx.Reader.cs b/SabreTools.DatFiles/Formats/Logiqx.Reader.cs new file mode 100644 index 00000000..aa7d96e7 --- /dev/null +++ b/SabreTools.DatFiles/Formats/Logiqx.Reader.cs @@ -0,0 +1,645 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.Schema; +using SabreTools.Core; +using SabreTools.Core.Tools; +using SabreTools.DatItems; +using SabreTools.DatItems.Formats; + +namespace SabreTools.DatFiles.Formats +{ + /// + /// Represents parsing a Logiqx-derived DAT + /// + internal partial class Logiqx : DatFile + { + /// + public override void ParseFile(string filename, int indexId, bool keep, bool statsOnly = false, bool throwOnError = false) + { + // Prepare all internal variables + XmlReader xtr = XmlReader.Create(filename, new XmlReaderSettings + { + CheckCharacters = false, + DtdProcessing = DtdProcessing.Ignore, + IgnoreComments = true, + IgnoreWhitespace = true, + ValidationFlags = XmlSchemaValidationFlags.None, + ValidationType = ValidationType.None, + }); + + List dirs = new(); + + // If we got a null reader, just return + if (xtr == null) + return; + + // Otherwise, read the file to the end + try + { + xtr.MoveToContent(); + while (!xtr.EOF) + { + // We only want elements + if (xtr.NodeType != XmlNodeType.Element) + { + // If we're ending a dir, remove the last item from the dirs list, if possible + if (xtr.Name == "dir" && dirs.Count > 0) + dirs.RemoveAt(dirs.Count - 1); + + xtr.Read(); + continue; + } + + switch (xtr.Name) + { + // The datafile tag can have some attributes + case "datafile": + Header.Build ??= xtr.GetAttribute("build"); + Header.Debug ??= xtr.GetAttribute("debug").AsYesNo(); + xtr.Read(); + break; + + // We want to process the entire subtree of the header + case "header": + ReadHeader(xtr.ReadSubtree(), keep); + + // Skip the header node now that we've processed it + xtr.Skip(); + break; + + // Unique to RomVault-created DATs + case "dir": + Header.Type = "SuperDAT"; + dirs.Add(xtr.GetAttribute("name") ?? string.Empty); + xtr.Read(); + break; + + // We want to process the entire subtree of the game + case "machine": // New-style Logiqx + case "game": // Old-style Logiqx + ReadMachine(xtr.ReadSubtree(), dirs, statsOnly, filename, indexId, keep); + + // Skip the machine now that we've processed it + xtr.Skip(); + break; + + default: + xtr.Read(); + break; + } + } + } + catch (Exception ex) when (!throwOnError) + { + logger.Warning(ex, $"Exception found while parsing '{filename}'"); + + // For XML errors, just skip the affected node + xtr?.Read(); + } + + xtr.Dispose(); + } + + /// + /// Read header information + /// + /// XmlReader to use to parse the header + /// True if full pathnames are to be kept, false otherwise (default) + private void ReadHeader(XmlReader reader, bool keep) + { + bool superdat = false; + + // If there's no subtree to the header, skip it + if (reader == null) + return; + + // Otherwise, add what is possible + reader.MoveToContent(); + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element || reader.Name == "header") + { + reader.Read(); + continue; + } + + // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) + string content; + switch (reader.Name) + { + case "id": + content = reader.ReadElementContentAsString(); + Header.NoIntroID ??= content; + break; + + case "name": + content = reader.ReadElementContentAsString(); + Header.Name ??= content; + superdat |= content.Contains(" - SuperDAT"); + if (keep && superdat) + { + Header.Type ??= "SuperDAT"; + } + break; + + case "description": + content = reader.ReadElementContentAsString(); + Header.Description ??= content; + break; + + case "rootdir": // This is exclusive to TruRip XML + content = reader.ReadElementContentAsString(); + Header.RootDir ??= content; + break; + + case "category": + content = reader.ReadElementContentAsString(); + Header.Category = (Header.Category == null ? content : $"{Header.Category};{content}"); + break; + + case "version": + content = reader.ReadElementContentAsString(); + Header.Version ??= content; + break; + + case "date": + content = reader.ReadElementContentAsString(); + Header.Date ??= content.Replace(".", "/"); + break; + + case "author": + content = reader.ReadElementContentAsString(); + Header.Author ??= content; + break; + + case "email": + content = reader.ReadElementContentAsString(); + Header.Email ??= content; + break; + + case "homepage": + content = reader.ReadElementContentAsString(); + Header.Homepage ??= content; + break; + + case "url": + content = reader.ReadElementContentAsString(); + Header.Url ??= content; + break; + + case "comment": + content = reader.ReadElementContentAsString(); + Header.Comment = (Header.Comment ?? content); + break; + + case "type": // This is exclusive to TruRip XML + content = reader.ReadElementContentAsString(); + Header.Type ??= content; + superdat |= content.Contains("SuperDAT"); + break; + + case "clrmamepro": + if (Header.HeaderSkipper == null) + Header.HeaderSkipper = reader.GetAttribute("header"); + + if (Header.ForceMerging == MergingFlag.None) + Header.ForceMerging = reader.GetAttribute("forcemerging").AsMergingFlag(); + + if (Header.ForceNodump == NodumpFlag.None) + Header.ForceNodump = reader.GetAttribute("forcenodump").AsNodumpFlag(); + + if (Header.ForcePacking == PackingFlag.None) + Header.ForcePacking = reader.GetAttribute("forcepacking").AsPackingFlag(); + + reader.Read(); + break; + + case "romcenter": + if (Header.System == null) + Header.System = reader.GetAttribute("plugin"); + + if (Header.RomMode == MergingFlag.None) + Header.RomMode = reader.GetAttribute("rommode").AsMergingFlag(); + + if (Header.BiosMode == MergingFlag.None) + Header.BiosMode = reader.GetAttribute("biosmode").AsMergingFlag(); + + if (Header.SampleMode == MergingFlag.None) + Header.SampleMode = reader.GetAttribute("samplemode").AsMergingFlag(); + + if (Header.LockRomMode == null) + Header.LockRomMode = reader.GetAttribute("lockrommode").AsYesNo(); + + if (Header.LockBiosMode == null) + Header.LockBiosMode = reader.GetAttribute("lockbiosmode").AsYesNo(); + + if (Header.LockSampleMode == null) + Header.LockSampleMode = reader.GetAttribute("locksamplemode").AsYesNo(); + + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Read game/machine information + /// + /// XmlReader to use to parse the machine + /// List of dirs to prepend to the game name + /// True to only add item statistics while parsing, false otherwise + /// Name of the file to be parsed + /// Index ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + private void ReadMachine( + XmlReader reader, + List dirs, + bool statsOnly, + + // Standard Dat parsing + string filename, + int indexId, + + // Miscellaneous + bool keep) + { + // If we have an empty machine, skip it + if (reader == null) + return; + + // Otherwise, add what is possible + reader.MoveToContent(); + + bool containsItems = false; + + // Create a new machine + MachineType machineType = 0x0; + if (reader.GetAttribute("isbios").AsYesNo() == true) + machineType |= MachineType.Bios; + + string dirsString = (dirs != null && dirs.Count > 0 ? string.Join("/", dirs) + "/" : string.Empty); + Machine machine = new() + { + Name = dirsString + reader.GetAttribute("name"), + Description = dirsString + reader.GetAttribute("name"), + SourceFile = reader.GetAttribute("sourcefile"), + Board = reader.GetAttribute("board"), + RebuildTo = reader.GetAttribute("rebuildto"), + NoIntroId = reader.GetAttribute("id"), + NoIntroCloneOfId = reader.GetAttribute("cloneofid"), + Runnable = reader.GetAttribute("runnable").AsRunnable(), // Used by older DATs + + CloneOf = reader.GetAttribute("cloneof"), + RomOf = reader.GetAttribute("romof"), + SampleOf = reader.GetAttribute("sampleof"), + + MachineType = (machineType == 0x0 ? MachineType.None : machineType), + }; + + if (Header.Type == "SuperDAT" && !keep) + { + string tempout = Regex.Match(machine.Name, @".*?\\(.*)").Groups[1].Value; + if (!string.IsNullOrWhiteSpace(tempout)) + machine.Name = tempout; + } + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the roms from the machine + switch (reader.Name) + { + case "comment": // There can be multiple comments by spec + machine.Comment += reader.ReadElementContentAsString(); + break; + + case "description": + machine.Description = reader.ReadElementContentAsString(); + break; + + case "year": + machine.Year = reader.ReadElementContentAsString(); + break; + + case "manufacturer": + machine.Manufacturer = reader.ReadElementContentAsString(); + break; + + case "publisher": // Not technically supported but used by some legacy DATs + machine.Publisher = reader.ReadElementContentAsString(); + break; + + case "category": // Not technically supported but used by some legacy DATs + machine.Category = reader.ReadElementContentAsString(); + break; + + case "trurip": // This is special metadata unique to EmuArc + ReadTruRip(reader.ReadSubtree(), machine); + + // Skip the trurip node now that we've processed it + reader.Skip(); + break; + + case "archive": + containsItems = true; + + DatItem archive = new Archive + { + Name = reader.GetAttribute("name"), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + archive.CopyMachineInformation(machine); + + // Now process and add the archive + ParseAddHelper(archive, statsOnly); + + reader.Read(); + break; + + case "biosset": + containsItems = true; + + DatItem biosSet = new BiosSet + { + Name = reader.GetAttribute("name"), + Description = reader.GetAttribute("description"), + Default = reader.GetAttribute("default").AsYesNo(), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + biosSet.CopyMachineInformation(machine); + + // Now process and add the biosSet + ParseAddHelper(biosSet, statsOnly); + + reader.Read(); + break; + + case "disk": + containsItems = true; + + DatItem disk = new Disk + { + Name = reader.GetAttribute("name"), + MD5 = reader.GetAttribute("md5"), + SHA1 = reader.GetAttribute("sha1"), + MergeTag = reader.GetAttribute("merge"), + ItemStatus = reader.GetAttribute("status").AsItemStatus(), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + disk.CopyMachineInformation(machine); + + // Now process and add the disk + ParseAddHelper(disk, statsOnly); + + reader.Read(); + break; + + case "media": + containsItems = true; + + DatItem media = new Media + { + Name = reader.GetAttribute("name"), + MD5 = reader.GetAttribute("md5"), + SHA1 = reader.GetAttribute("sha1"), + SHA256 = reader.GetAttribute("sha256"), + SpamSum = reader.GetAttribute("spamsum"), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + media.CopyMachineInformation(machine); + + // Now process and add the media + ParseAddHelper(media, statsOnly); + + reader.Read(); + break; + + case "release": + containsItems = true; + + DatItem release = new Release + { + Name = reader.GetAttribute("name"), + Region = reader.GetAttribute("region"), + Language = reader.GetAttribute("language"), + Date = reader.GetAttribute("date"), + Default = reader.GetAttribute("default").AsYesNo(), + }; + + release.CopyMachineInformation(machine); + + // Now process and add the release + ParseAddHelper(release, statsOnly); + + reader.Read(); + break; + + case "rom": + containsItems = true; + + DatItem rom = new Rom + { + Name = reader.GetAttribute("name"), + Size = Utilities.CleanLong(reader.GetAttribute("size")), + CRC = reader.GetAttribute("crc"), + MD5 = reader.GetAttribute("md5"), + SHA1 = reader.GetAttribute("sha1"), + SHA256 = reader.GetAttribute("sha256"), + SHA384 = reader.GetAttribute("sha384"), + SHA512 = reader.GetAttribute("sha512"), + SpamSum = reader.GetAttribute("spamsum"), + MergeTag = reader.GetAttribute("merge"), + ItemStatus = reader.GetAttribute("status").AsItemStatus(), + Date = CleanDate(reader.GetAttribute("date")), + Inverted = reader.GetAttribute("inverted").AsYesNo(), + MIA = reader.GetAttribute("mia").AsYesNo(), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + rom.CopyMachineInformation(machine); + + // Now process and add the rom + ParseAddHelper(rom, statsOnly); + + reader.Read(); + break; + + case "sample": + containsItems = true; + + DatItem sample = new Sample + { + Name = reader.GetAttribute("name"), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + sample.CopyMachineInformation(machine); + + // Now process and add the sample + ParseAddHelper(sample, statsOnly); + + reader.Read(); + break; + + default: + reader.Read(); + break; + } + } + + // If no items were found for this machine, add a Blank placeholder + if (!containsItems) + { + Blank blank = new() + { + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + blank.CopyMachineInformation(machine); + + // Now process and add the rom + ParseAddHelper(blank, statsOnly); + } + } + + /// + /// Read EmuArc information + /// + /// True if full pathnames are to be kept, false otherwise (default) + /// Machine information to pass to contained items + private void ReadTruRip(XmlReader reader, Machine machine) + { + // If we have an empty trurip, skip it + if (reader == null) + return; + + // Otherwise, add what is possible + reader.MoveToContent(); + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the information from the trurip + switch (reader.Name) + { + case "titleid": + machine.TitleID = reader.ReadElementContentAsString(); + break; + + case "publisher": + machine.Publisher = reader.ReadElementContentAsString(); + break; + + case "developer": + machine.Developer = reader.ReadElementContentAsString(); + break; + + case "year": + machine.Year = reader.ReadElementContentAsString(); + break; + + case "genre": + machine.Genre = reader.ReadElementContentAsString(); + break; + + case "subgenre": + machine.Subgenre = reader.ReadElementContentAsString(); + break; + + case "ratings": + machine.Ratings = reader.ReadElementContentAsString(); + break; + + case "score": + machine.Score = reader.ReadElementContentAsString(); + break; + + case "players": + machine.Players = reader.ReadElementContentAsString(); + break; + + case "enabled": + machine.Enabled = reader.ReadElementContentAsString(); + break; + + case "crc": + machine.Crc = reader.ReadElementContentAsString().AsYesNo(); + break; + + case "source": + machine.SourceFile = reader.ReadElementContentAsString(); + break; + + case "cloneof": + machine.CloneOf = reader.ReadElementContentAsString(); + break; + + case "relatedto": + machine.RelatedTo = reader.ReadElementContentAsString(); + break; + + default: + reader.Read(); + break; + } + } + } + } +} diff --git a/SabreTools.DatFiles/Formats/Logiqx.Writer.cs b/SabreTools.DatFiles/Formats/Logiqx.Writer.cs new file mode 100644 index 00000000..7c7969e7 --- /dev/null +++ b/SabreTools.DatFiles/Formats/Logiqx.Writer.cs @@ -0,0 +1,404 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Xml; +using SabreTools.Core; +using SabreTools.Core.Tools; +using SabreTools.DatItems; +using SabreTools.DatItems.Formats; +using SabreTools.IO; + +namespace SabreTools.DatFiles.Formats +{ + /// + /// Represents parsing and writing of a Logiqx-derived DAT + /// + internal partial class Logiqx : DatFile + { + /// + protected override ItemType[] GetSupportedTypes() + { + return new ItemType[] + { + ItemType.Archive, + ItemType.BiosSet, + ItemType.Disk, + ItemType.Media, + ItemType.Release, + ItemType.Rom, + ItemType.Sample, + }; + } + + /// + protected override List GetMissingRequiredFields(DatItem datItem) + { + // TODO: Check required fields + return null; + } + + /// + public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false) + { + try + { + logger.User($"Writing to '{outfile}'..."); + FileStream fs = System.IO.File.Create(outfile); + + // If we get back null for some reason, just log and return + if (fs == null) + { + logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable"); + return false; + } + + XmlTextWriter xtw = new(fs, new UTF8Encoding(false)) + { + Formatting = Formatting.Indented, + IndentChar = '\t', + Indentation = 1 + }; + + // Write out the header + WriteHeader(xtw); + + // Write out each of the machines and roms + string lastgame = null; + + // Use a sorted list of games to output + foreach (string key in Items.SortedKeys) + { + ConcurrentList datItems = Items.FilteredItems(key); + + // If this machine doesn't contain any writable items, skip + if (!ContainsWritable(datItems)) + continue; + + // Resolve the names in the block + datItems = DatItem.ResolveNames(datItems); + + for (int index = 0; index < datItems.Count; index++) + { + DatItem datItem = datItems[index]; + + // If we have a different game and we're not at the start of the list, output the end of last item + if (lastgame != null && lastgame.ToLowerInvariant() != datItem.Machine.Name.ToLowerInvariant()) + WriteEndGame(xtw); + + // If we have a new game, output the beginning of the new item + if (lastgame == null || lastgame.ToLowerInvariant() != datItem.Machine.Name.ToLowerInvariant()) + WriteStartGame(xtw, datItem); + + // Check for a "null" item + datItem = ProcessNullifiedItem(datItem); + + // Write out the item if we're not ignoring + if (!ShouldIgnore(datItem, ignoreblanks)) + WriteDatItem(xtw, datItem); + + // Set the new data to compare against + lastgame = datItem.Machine.Name; + } + } + + // Write the file footer out + WriteFooter(xtw); + + logger.User($"'{outfile}' written!{Environment.NewLine}"); + xtw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) when (!throwOnError) + { + logger.Error(ex); + return false; + } + + return true; + } + + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// XmlTextWriter to output to + private void WriteHeader(XmlTextWriter xtw) + { + xtw.WriteStartDocument(); + if (Header.NoIntroID == null) + xtw.WriteDocType("datafile", "-//Logiqx//DTD ROM Management Datafile//EN", "http://www.logiqx.com/Dats/datafile.dtd", null); + + xtw.WriteStartElement("datafile"); + xtw.WriteOptionalAttributeString("build", Header.Build); + xtw.WriteOptionalAttributeString("debug", Header.Debug.FromYesNo()); + if (Header.NoIntroID != null) + { + xtw.WriteRequiredAttributeString("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); + xtw.WriteRequiredAttributeString("xsi:schemaLocation", "https://datomatic.no-intro.org/stuff https://datomatic.no-intro.org/stuff/schema_nointro_datfile_v3.xsd"); + } + + xtw.WriteStartElement("header"); + + xtw.WriteOptionalElementString("id", Header.NoIntroID); + xtw.WriteRequiredElementString("name", Header.Name); + xtw.WriteRequiredElementString("description", Header.Description); + xtw.WriteOptionalElementString("rootdir", Header.RootDir); + if (!string.IsNullOrWhiteSpace(Header.Category)) + { + var categories = Header.Category.Split(';'); + foreach (string category in categories) + { + xtw.WriteOptionalElementString("category", category); + } + } + xtw.WriteRequiredElementString("version", Header.Version); + xtw.WriteOptionalElementString("date", Header.Date); + xtw.WriteRequiredElementString("author", Header.Author); + xtw.WriteOptionalElementString("email", Header.Email); + xtw.WriteOptionalElementString("homepage", Header.Homepage); + xtw.WriteOptionalElementString("url", Header.Url); + xtw.WriteOptionalElementString("comment", Header.Comment); + xtw.WriteOptionalElementString("type", Header.Type); + + if (Header.ForcePacking != PackingFlag.None + || Header.ForceMerging != MergingFlag.None + || Header.ForceNodump != NodumpFlag.None + || !string.IsNullOrWhiteSpace(Header.HeaderSkipper)) + { + xtw.WriteStartElement("clrmamepro"); + + if (Header.ForcePacking != PackingFlag.None) + xtw.WriteOptionalAttributeString("forcepacking", Header.ForcePacking.FromPackingFlag(false)); + if (Header.ForceMerging != MergingFlag.None) + xtw.WriteOptionalAttributeString("forcemerging", Header.ForceMerging.FromMergingFlag(false)); + if (Header.ForceNodump != NodumpFlag.None) + xtw.WriteOptionalAttributeString("forcenodump", Header.ForceNodump.FromNodumpFlag()); + xtw.WriteOptionalAttributeString("header", Header.HeaderSkipper); + + // End clrmamepro + xtw.WriteEndElement(); + } + + if (Header.System != null + || Header.RomMode != MergingFlag.None || Header.LockRomMode != null + || Header.BiosMode != MergingFlag.None || Header.LockBiosMode != null + || Header.SampleMode != MergingFlag.None || Header.LockSampleMode != null) + { + xtw.WriteStartElement("romcenter"); + + xtw.WriteOptionalAttributeString("plugin", Header.System); + if (Header.RomMode != MergingFlag.None) + xtw.WriteOptionalAttributeString("rommode", Header.RomMode.FromMergingFlag(true)); + if (Header.BiosMode != MergingFlag.None) + xtw.WriteOptionalAttributeString("biosmode", Header.BiosMode.FromMergingFlag(true)); + if (Header.SampleMode != MergingFlag.None) + xtw.WriteOptionalAttributeString("samplemode", Header.SampleMode.FromMergingFlag(true)); + xtw.WriteOptionalAttributeString("lockrommode", Header.LockRomMode.FromYesNo()); + xtw.WriteOptionalAttributeString("lockbiosmode", Header.LockBiosMode.FromYesNo()); + xtw.WriteOptionalAttributeString("locksamplemode", Header.LockSampleMode.FromYesNo()); + + // End romcenter + xtw.WriteEndElement(); + } + + // End header + xtw.WriteEndElement(); + + xtw.Flush(); + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// XmlTextWriter to output to + /// DatItem object to be output + private void WriteStartGame(XmlTextWriter xtw, DatItem datItem) + { + // No game should start with a path separator + datItem.Machine.Name = datItem.Machine.Name.TrimStart(Path.DirectorySeparatorChar); + + // Build the state + xtw.WriteStartElement(_deprecated ? "game" : "machine"); + xtw.WriteRequiredAttributeString("name", datItem.Machine.Name); + + if (datItem.Machine.MachineType.HasFlag(MachineType.Bios)) + xtw.WriteAttributeString("isbios", "yes"); + if (datItem.Machine.MachineType.HasFlag(MachineType.Device)) + xtw.WriteAttributeString("isdevice", "yes"); + if (datItem.Machine.MachineType.HasFlag(MachineType.Mechanical)) + xtw.WriteAttributeString("ismechanical", "yes"); + + xtw.WriteOptionalAttributeString("runnable", datItem.Machine.Runnable.FromRunnable()); + xtw.WriteOptionalAttributeString("id", datItem.Machine.NoIntroId); + xtw.WriteOptionalAttributeString("cloneofid", datItem.Machine.NoIntroCloneOfId); + + if (!string.Equals(datItem.Machine.Name, datItem.Machine.CloneOf, StringComparison.OrdinalIgnoreCase)) + xtw.WriteOptionalAttributeString("cloneof", datItem.Machine.CloneOf); + if (!string.Equals(datItem.Machine.Name, datItem.Machine.RomOf, StringComparison.OrdinalIgnoreCase)) + xtw.WriteOptionalAttributeString("romof", datItem.Machine.RomOf); + if (!string.Equals(datItem.Machine.Name, datItem.Machine.SampleOf, StringComparison.OrdinalIgnoreCase)) + xtw.WriteOptionalAttributeString("sampleof", datItem.Machine.SampleOf); + + xtw.WriteOptionalElementString("comment", datItem.Machine.Comment); + xtw.WriteOptionalElementString("description", datItem.Machine.Description); + xtw.WriteOptionalElementString("year", datItem.Machine.Year); + xtw.WriteOptionalElementString("publisher", datItem.Machine.Publisher); + xtw.WriteOptionalElementString("manufacturer", datItem.Machine.Manufacturer); + xtw.WriteOptionalElementString("category", datItem.Machine.Category); + + if (datItem.Machine.TitleID != null + || datItem.Machine.Developer != null + || datItem.Machine.Genre != null + || datItem.Machine.Subgenre != null + || datItem.Machine.Ratings != null + || datItem.Machine.Score != null + || datItem.Machine.Enabled != null + || datItem.Machine.Crc != null + || datItem.Machine.RelatedTo != null) + { + xtw.WriteStartElement("trurip"); + + xtw.WriteOptionalElementString("titleid", datItem.Machine.TitleID); + xtw.WriteOptionalElementString("publisher", datItem.Machine.Publisher); + xtw.WriteOptionalElementString("developer", datItem.Machine.Developer); + xtw.WriteOptionalElementString("year", datItem.Machine.Year); + xtw.WriteOptionalElementString("genre", datItem.Machine.Genre); + xtw.WriteOptionalElementString("subgenre", datItem.Machine.Subgenre); + xtw.WriteOptionalElementString("ratings", datItem.Machine.Ratings); + xtw.WriteOptionalElementString("score", datItem.Machine.Score); + xtw.WriteOptionalElementString("players", datItem.Machine.Players); + xtw.WriteOptionalElementString("enabled", datItem.Machine.Enabled); + xtw.WriteOptionalElementString("titleid", datItem.Machine.TitleID); + xtw.WriteOptionalElementString("crc", datItem.Machine.Crc.FromYesNo()); + xtw.WriteOptionalElementString("source", datItem.Machine.SourceFile); + xtw.WriteOptionalElementString("cloneof", datItem.Machine.CloneOf); + xtw.WriteOptionalElementString("relatedto", datItem.Machine.RelatedTo); + + // End trurip + xtw.WriteEndElement(); + } + + xtw.Flush(); + } + + /// + /// Write out Game end using the supplied StreamWriter + /// + /// XmlTextWriter to output to + private void WriteEndGame(XmlTextWriter xtw) + { + // End machine + xtw.WriteEndElement(); + + xtw.Flush(); + } + + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// XmlTextWriter to output to + /// DatItem object to be output + private void WriteDatItem(XmlTextWriter xtw, DatItem datItem) + { + // Pre-process the item name + ProcessItemName(datItem, true); + + // Build the state + switch (datItem.ItemType) + { + case ItemType.Archive: + var archive = datItem as Archive; + xtw.WriteStartElement("archive"); + xtw.WriteRequiredAttributeString("name", archive.Name); + xtw.WriteEndElement(); + break; + + case ItemType.BiosSet: + var biosSet = datItem as BiosSet; + xtw.WriteStartElement("biosset"); + xtw.WriteRequiredAttributeString("name", biosSet.Name); + xtw.WriteOptionalAttributeString("description", biosSet.Description); + xtw.WriteOptionalAttributeString("default", biosSet.Default.FromYesNo()); + xtw.WriteEndElement(); + break; + + case ItemType.Disk: + var disk = datItem as Disk; + xtw.WriteStartElement("disk"); + xtw.WriteRequiredAttributeString("name", disk.Name); + xtw.WriteOptionalAttributeString("md5", disk.MD5?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("sha1", disk.SHA1?.ToLowerInvariant()); + if (disk.ItemStatus != ItemStatus.None) + xtw.WriteOptionalAttributeString("status", disk.ItemStatus.FromItemStatus(false)); + xtw.WriteEndElement(); + break; + + case ItemType.Media: + var media = datItem as Media; + xtw.WriteStartElement("media"); + xtw.WriteRequiredAttributeString("name", media.Name); + xtw.WriteOptionalAttributeString("md5", media.MD5?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("sha1", media.SHA1?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("sha256", media.SHA256?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("spamsum", media.SpamSum?.ToLowerInvariant()); + xtw.WriteEndElement(); + break; + + case ItemType.Release: + var release = datItem as Release; + xtw.WriteStartElement("release"); + xtw.WriteRequiredAttributeString("name", release.Name); + xtw.WriteOptionalAttributeString("region", release.Region); + xtw.WriteOptionalAttributeString("language", release.Language); + xtw.WriteOptionalAttributeString("date", release.Date); + xtw.WriteOptionalAttributeString("default", release.Default.FromYesNo()); + xtw.WriteEndElement(); + break; + + case ItemType.Rom: + var rom = datItem as Rom; + xtw.WriteStartElement("rom"); + xtw.WriteRequiredAttributeString("name", rom.Name); + xtw.WriteAttributeString("size", rom.Size?.ToString()); + xtw.WriteOptionalAttributeString("crc", rom.CRC?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("md5", rom.MD5?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("sha1", rom.SHA1?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("sha256", rom.SHA256?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("sha384", rom.SHA384?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("sha512", rom.SHA512?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("spamsum", rom.SpamSum?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("date", rom.Date); + if (rom.ItemStatus != ItemStatus.None) + xtw.WriteOptionalAttributeString("status", rom.ItemStatus.FromItemStatus(false)); + xtw.WriteOptionalAttributeString("inverted", rom.Inverted.FromYesNo()); + xtw.WriteOptionalAttributeString("mia", rom.MIA.FromYesNo()); + xtw.WriteEndElement(); + break; + + case ItemType.Sample: + var sample = datItem as Sample; + xtw.WriteStartElement("sample"); + xtw.WriteRequiredAttributeString("name", sample.Name); + xtw.WriteEndElement(); + break; + } + + xtw.Flush(); + } + + /// + /// Write out DAT footer using the supplied StreamWriter + /// + /// XmlTextWriter to output to + private void WriteFooter(XmlTextWriter xtw) + { + // End machine + xtw.WriteEndElement(); + + // End datafile + xtw.WriteEndElement(); + + xtw.Flush(); + } + } +} diff --git a/SabreTools.DatFiles/Formats/Logiqx.cs b/SabreTools.DatFiles/Formats/Logiqx.cs index 8cd214bf..2ff71685 100644 --- a/SabreTools.DatFiles/Formats/Logiqx.cs +++ b/SabreTools.DatFiles/Formats/Logiqx.cs @@ -1,22 +1,9 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Xml; -using System.Xml.Schema; -using SabreTools.Core; -using SabreTools.Core.Tools; -using SabreTools.DatItems; -using SabreTools.DatItems.Formats; -using SabreTools.IO; - -namespace SabreTools.DatFiles.Formats +namespace SabreTools.DatFiles.Formats { /// - /// Represents parsing and writing of a Logiqx-derived DAT + /// Represents a Logiqx-derived DAT /// - internal class Logiqx : DatFile + internal partial class Logiqx : DatFile { // Private instance variables specific to Logiqx DATs private readonly bool _deprecated; @@ -237,1017 +224,5 @@ namespace SabreTools.DatFiles.Formats { _deprecated = deprecated; } - - /// - public override void ParseFile(string filename, int indexId, bool keep, bool statsOnly = false, bool throwOnError = false) - { - // Prepare all internal variables - XmlReader xtr = XmlReader.Create(filename, new XmlReaderSettings - { - CheckCharacters = false, - DtdProcessing = DtdProcessing.Ignore, - IgnoreComments = true, - IgnoreWhitespace = true, - ValidationFlags = XmlSchemaValidationFlags.None, - ValidationType = ValidationType.None, - }); - - List dirs = new(); - - // If we got a null reader, just return - if (xtr == null) - return; - - // Otherwise, read the file to the end - try - { - xtr.MoveToContent(); - while (!xtr.EOF) - { - // We only want elements - if (xtr.NodeType != XmlNodeType.Element) - { - // If we're ending a dir, remove the last item from the dirs list, if possible - if (xtr.Name == "dir" && dirs.Count > 0) - dirs.RemoveAt(dirs.Count - 1); - - xtr.Read(); - continue; - } - - switch (xtr.Name) - { - // The datafile tag can have some attributes - case "datafile": - Header.Build ??= xtr.GetAttribute("build"); - Header.Debug ??= xtr.GetAttribute("debug").AsYesNo(); - xtr.Read(); - break; - - // We want to process the entire subtree of the header - case "header": - ReadHeader(xtr.ReadSubtree(), keep); - - // Skip the header node now that we've processed it - xtr.Skip(); - break; - - // Unique to RomVault-created DATs - case "dir": - Header.Type = "SuperDAT"; - dirs.Add(xtr.GetAttribute("name") ?? string.Empty); - xtr.Read(); - break; - - // We want to process the entire subtree of the game - case "machine": // New-style Logiqx - case "game": // Old-style Logiqx - ReadMachine(xtr.ReadSubtree(), dirs, statsOnly, filename, indexId, keep); - - // Skip the machine now that we've processed it - xtr.Skip(); - break; - - default: - xtr.Read(); - break; - } - } - } - catch (Exception ex) when (!throwOnError) - { - logger.Warning(ex, $"Exception found while parsing '{filename}'"); - - // For XML errors, just skip the affected node - xtr?.Read(); - } - - xtr.Dispose(); - } - - /// - /// Read header information - /// - /// XmlReader to use to parse the header - /// True if full pathnames are to be kept, false otherwise (default) - private void ReadHeader(XmlReader reader, bool keep) - { - bool superdat = false; - - // If there's no subtree to the header, skip it - if (reader == null) - return; - - // Otherwise, add what is possible - reader.MoveToContent(); - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element || reader.Name == "header") - { - reader.Read(); - continue; - } - - // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) - string content; - switch (reader.Name) - { - case "id": - content = reader.ReadElementContentAsString(); - Header.NoIntroID ??= content; - break; - - case "name": - content = reader.ReadElementContentAsString(); - Header.Name ??= content; - superdat |= content.Contains(" - SuperDAT"); - if (keep && superdat) - { - Header.Type ??= "SuperDAT"; - } - break; - - case "description": - content = reader.ReadElementContentAsString(); - Header.Description ??= content; - break; - - case "rootdir": // This is exclusive to TruRip XML - content = reader.ReadElementContentAsString(); - Header.RootDir ??= content; - break; - - case "category": - content = reader.ReadElementContentAsString(); - Header.Category = (Header.Category == null ? content : $"{Header.Category};{content}"); - break; - - case "version": - content = reader.ReadElementContentAsString(); - Header.Version ??= content; - break; - - case "date": - content = reader.ReadElementContentAsString(); - Header.Date ??= content.Replace(".", "/"); - break; - - case "author": - content = reader.ReadElementContentAsString(); - Header.Author ??= content; - break; - - case "email": - content = reader.ReadElementContentAsString(); - Header.Email ??= content; - break; - - case "homepage": - content = reader.ReadElementContentAsString(); - Header.Homepage ??= content; - break; - - case "url": - content = reader.ReadElementContentAsString(); - Header.Url ??= content; - break; - - case "comment": - content = reader.ReadElementContentAsString(); - Header.Comment = (Header.Comment ?? content); - break; - - case "type": // This is exclusive to TruRip XML - content = reader.ReadElementContentAsString(); - Header.Type ??= content; - superdat |= content.Contains("SuperDAT"); - break; - - case "clrmamepro": - if (Header.HeaderSkipper == null) - Header.HeaderSkipper = reader.GetAttribute("header"); - - if (Header.ForceMerging == MergingFlag.None) - Header.ForceMerging = reader.GetAttribute("forcemerging").AsMergingFlag(); - - if (Header.ForceNodump == NodumpFlag.None) - Header.ForceNodump = reader.GetAttribute("forcenodump").AsNodumpFlag(); - - if (Header.ForcePacking == PackingFlag.None) - Header.ForcePacking = reader.GetAttribute("forcepacking").AsPackingFlag(); - - reader.Read(); - break; - - case "romcenter": - if (Header.System == null) - Header.System = reader.GetAttribute("plugin"); - - if (Header.RomMode == MergingFlag.None) - Header.RomMode = reader.GetAttribute("rommode").AsMergingFlag(); - - if (Header.BiosMode == MergingFlag.None) - Header.BiosMode = reader.GetAttribute("biosmode").AsMergingFlag(); - - if (Header.SampleMode == MergingFlag.None) - Header.SampleMode = reader.GetAttribute("samplemode").AsMergingFlag(); - - if (Header.LockRomMode == null) - Header.LockRomMode = reader.GetAttribute("lockrommode").AsYesNo(); - - if (Header.LockBiosMode == null) - Header.LockBiosMode = reader.GetAttribute("lockbiosmode").AsYesNo(); - - if (Header.LockSampleMode == null) - Header.LockSampleMode = reader.GetAttribute("locksamplemode").AsYesNo(); - - reader.Read(); - break; - default: - reader.Read(); - break; - } - } - } - - /// - /// Read game/machine information - /// - /// XmlReader to use to parse the machine - /// List of dirs to prepend to the game name - /// True to only add item statistics while parsing, false otherwise - /// Name of the file to be parsed - /// Index ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - private void ReadMachine( - XmlReader reader, - List dirs, - bool statsOnly, - - // Standard Dat parsing - string filename, - int indexId, - - // Miscellaneous - bool keep) - { - // If we have an empty machine, skip it - if (reader == null) - return; - - // Otherwise, add what is possible - reader.MoveToContent(); - - bool containsItems = false; - - // Create a new machine - MachineType machineType = 0x0; - if (reader.GetAttribute("isbios").AsYesNo() == true) - machineType |= MachineType.Bios; - - string dirsString = (dirs != null && dirs.Count > 0 ? string.Join("/", dirs) + "/" : string.Empty); - Machine machine = new() - { - Name = dirsString + reader.GetAttribute("name"), - Description = dirsString + reader.GetAttribute("name"), - SourceFile = reader.GetAttribute("sourcefile"), - Board = reader.GetAttribute("board"), - RebuildTo = reader.GetAttribute("rebuildto"), - NoIntroId = reader.GetAttribute("id"), - NoIntroCloneOfId = reader.GetAttribute("cloneofid"), - Runnable = reader.GetAttribute("runnable").AsRunnable(), // Used by older DATs - - CloneOf = reader.GetAttribute("cloneof"), - RomOf = reader.GetAttribute("romof"), - SampleOf = reader.GetAttribute("sampleof"), - - MachineType = (machineType == 0x0 ? MachineType.None : machineType), - }; - - if (Header.Type == "SuperDAT" && !keep) - { - string tempout = Regex.Match(machine.Name, @".*?\\(.*)").Groups[1].Value; - if (!string.IsNullOrWhiteSpace(tempout)) - machine.Name = tempout; - } - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the roms from the machine - switch (reader.Name) - { - case "comment": // There can be multiple comments by spec - machine.Comment += reader.ReadElementContentAsString(); - break; - - case "description": - machine.Description = reader.ReadElementContentAsString(); - break; - - case "year": - machine.Year = reader.ReadElementContentAsString(); - break; - - case "manufacturer": - machine.Manufacturer = reader.ReadElementContentAsString(); - break; - - case "publisher": // Not technically supported but used by some legacy DATs - machine.Publisher = reader.ReadElementContentAsString(); - break; - - case "category": // Not technically supported but used by some legacy DATs - machine.Category = reader.ReadElementContentAsString(); - break; - - case "trurip": // This is special metadata unique to EmuArc - ReadTruRip(reader.ReadSubtree(), machine); - - // Skip the trurip node now that we've processed it - reader.Skip(); - break; - - case "archive": - containsItems = true; - - DatItem archive = new Archive - { - Name = reader.GetAttribute("name"), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - archive.CopyMachineInformation(machine); - - // Now process and add the archive - ParseAddHelper(archive, statsOnly); - - reader.Read(); - break; - - case "biosset": - containsItems = true; - - DatItem biosSet = new BiosSet - { - Name = reader.GetAttribute("name"), - Description = reader.GetAttribute("description"), - Default = reader.GetAttribute("default").AsYesNo(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - biosSet.CopyMachineInformation(machine); - - // Now process and add the biosSet - ParseAddHelper(biosSet, statsOnly); - - reader.Read(); - break; - - case "disk": - containsItems = true; - - DatItem disk = new Disk - { - Name = reader.GetAttribute("name"), - MD5 = reader.GetAttribute("md5"), - SHA1 = reader.GetAttribute("sha1"), - MergeTag = reader.GetAttribute("merge"), - ItemStatus = reader.GetAttribute("status").AsItemStatus(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - disk.CopyMachineInformation(machine); - - // Now process and add the disk - ParseAddHelper(disk, statsOnly); - - reader.Read(); - break; - - case "media": - containsItems = true; - - DatItem media = new Media - { - Name = reader.GetAttribute("name"), - MD5 = reader.GetAttribute("md5"), - SHA1 = reader.GetAttribute("sha1"), - SHA256 = reader.GetAttribute("sha256"), - SpamSum = reader.GetAttribute("spamsum"), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - media.CopyMachineInformation(machine); - - // Now process and add the media - ParseAddHelper(media, statsOnly); - - reader.Read(); - break; - - case "release": - containsItems = true; - - DatItem release = new Release - { - Name = reader.GetAttribute("name"), - Region = reader.GetAttribute("region"), - Language = reader.GetAttribute("language"), - Date = reader.GetAttribute("date"), - Default = reader.GetAttribute("default").AsYesNo(), - }; - - release.CopyMachineInformation(machine); - - // Now process and add the release - ParseAddHelper(release, statsOnly); - - reader.Read(); - break; - - case "rom": - containsItems = true; - - DatItem rom = new Rom - { - Name = reader.GetAttribute("name"), - Size = Utilities.CleanLong(reader.GetAttribute("size")), - CRC = reader.GetAttribute("crc"), - MD5 = reader.GetAttribute("md5"), - SHA1 = reader.GetAttribute("sha1"), - SHA256 = reader.GetAttribute("sha256"), - SHA384 = reader.GetAttribute("sha384"), - SHA512 = reader.GetAttribute("sha512"), - SpamSum = reader.GetAttribute("spamsum"), - MergeTag = reader.GetAttribute("merge"), - ItemStatus = reader.GetAttribute("status").AsItemStatus(), - Date = CleanDate(reader.GetAttribute("date")), - Inverted = reader.GetAttribute("inverted").AsYesNo(), - MIA = reader.GetAttribute("mia").AsYesNo(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - rom.CopyMachineInformation(machine); - - // Now process and add the rom - ParseAddHelper(rom, statsOnly); - - reader.Read(); - break; - - case "sample": - containsItems = true; - - DatItem sample = new Sample - { - Name = reader.GetAttribute("name"), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - sample.CopyMachineInformation(machine); - - // Now process and add the sample - ParseAddHelper(sample, statsOnly); - - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - - // If no items were found for this machine, add a Blank placeholder - if (!containsItems) - { - Blank blank = new() - { - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - blank.CopyMachineInformation(machine); - - // Now process and add the rom - ParseAddHelper(blank, statsOnly); - } - } - - /// - /// Read EmuArc information - /// - /// True if full pathnames are to be kept, false otherwise (default) - /// Machine information to pass to contained items - private void ReadTruRip(XmlReader reader, Machine machine) - { - // If we have an empty trurip, skip it - if (reader == null) - return; - - // Otherwise, add what is possible - reader.MoveToContent(); - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the information from the trurip - switch (reader.Name) - { - case "titleid": - machine.TitleID = reader.ReadElementContentAsString(); - break; - - case "publisher": - machine.Publisher = reader.ReadElementContentAsString(); - break; - - case "developer": - machine.Developer = reader.ReadElementContentAsString(); - break; - - case "year": - machine.Year = reader.ReadElementContentAsString(); - break; - - case "genre": - machine.Genre = reader.ReadElementContentAsString(); - break; - - case "subgenre": - machine.Subgenre = reader.ReadElementContentAsString(); - break; - - case "ratings": - machine.Ratings = reader.ReadElementContentAsString(); - break; - - case "score": - machine.Score = reader.ReadElementContentAsString(); - break; - - case "players": - machine.Players = reader.ReadElementContentAsString(); - break; - - case "enabled": - machine.Enabled = reader.ReadElementContentAsString(); - break; - - case "crc": - machine.Crc = reader.ReadElementContentAsString().AsYesNo(); - break; - - case "source": - machine.SourceFile = reader.ReadElementContentAsString(); - break; - - case "cloneof": - machine.CloneOf = reader.ReadElementContentAsString(); - break; - - case "relatedto": - machine.RelatedTo = reader.ReadElementContentAsString(); - break; - - default: - reader.Read(); - break; - } - } - } - - /// - protected override ItemType[] GetSupportedTypes() - { - return new ItemType[] - { - ItemType.Archive, - ItemType.BiosSet, - ItemType.Disk, - ItemType.Media, - ItemType.Release, - ItemType.Rom, - ItemType.Sample, - }; - } - - /// - protected override List GetMissingRequiredFields(DatItem datItem) - { - // TODO: Check required fields - return null; - } - - /// - public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false) - { - try - { - logger.User($"Writing to '{outfile}'..."); - FileStream fs = System.IO.File.Create(outfile); - - // If we get back null for some reason, just log and return - if (fs == null) - { - logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable"); - return false; - } - - XmlTextWriter xtw = new(fs, new UTF8Encoding(false)) - { - Formatting = Formatting.Indented, - IndentChar = '\t', - Indentation = 1 - }; - - // Write out the header - WriteHeader(xtw); - - // Write out each of the machines and roms - string lastgame = null; - - // Use a sorted list of games to output - foreach (string key in Items.SortedKeys) - { - ConcurrentList datItems = Items.FilteredItems(key); - - // If this machine doesn't contain any writable items, skip - if (!ContainsWritable(datItems)) - continue; - - // Resolve the names in the block - datItems = DatItem.ResolveNames(datItems); - - for (int index = 0; index < datItems.Count; index++) - { - DatItem datItem = datItems[index]; - - // If we have a different game and we're not at the start of the list, output the end of last item - if (lastgame != null && lastgame.ToLowerInvariant() != datItem.Machine.Name.ToLowerInvariant()) - WriteEndGame(xtw); - - // If we have a new game, output the beginning of the new item - if (lastgame == null || lastgame.ToLowerInvariant() != datItem.Machine.Name.ToLowerInvariant()) - WriteStartGame(xtw, datItem); - - // Check for a "null" item - datItem = ProcessNullifiedItem(datItem); - - // Write out the item if we're not ignoring - if (!ShouldIgnore(datItem, ignoreblanks)) - WriteDatItem(xtw, datItem); - - // Set the new data to compare against - lastgame = datItem.Machine.Name; - } - } - - // Write the file footer out - WriteFooter(xtw); - - logger.User($"'{outfile}' written!{Environment.NewLine}"); - xtw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) when (!throwOnError) - { - logger.Error(ex); - return false; - } - - return true; - } - - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// XmlTextWriter to output to - private void WriteHeader(XmlTextWriter xtw) - { - xtw.WriteStartDocument(); - if (Header.NoIntroID == null) - xtw.WriteDocType("datafile", "-//Logiqx//DTD ROM Management Datafile//EN", "http://www.logiqx.com/Dats/datafile.dtd", null); - - xtw.WriteStartElement("datafile"); - xtw.WriteOptionalAttributeString("build", Header.Build); - xtw.WriteOptionalAttributeString("debug", Header.Debug.FromYesNo()); - if (Header.NoIntroID != null) - { - xtw.WriteRequiredAttributeString("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); - xtw.WriteRequiredAttributeString("xsi:schemaLocation", "https://datomatic.no-intro.org/stuff https://datomatic.no-intro.org/stuff/schema_nointro_datfile_v3.xsd"); - } - - xtw.WriteStartElement("header"); - - xtw.WriteOptionalElementString("id", Header.NoIntroID); - xtw.WriteRequiredElementString("name", Header.Name); - xtw.WriteRequiredElementString("description", Header.Description); - xtw.WriteOptionalElementString("rootdir", Header.RootDir); - if (!string.IsNullOrWhiteSpace(Header.Category)) - { - var categories = Header.Category.Split(';'); - foreach (string category in categories) - { - xtw.WriteOptionalElementString("category", category); - } - } - xtw.WriteRequiredElementString("version", Header.Version); - xtw.WriteOptionalElementString("date", Header.Date); - xtw.WriteRequiredElementString("author", Header.Author); - xtw.WriteOptionalElementString("email", Header.Email); - xtw.WriteOptionalElementString("homepage", Header.Homepage); - xtw.WriteOptionalElementString("url", Header.Url); - xtw.WriteOptionalElementString("comment", Header.Comment); - xtw.WriteOptionalElementString("type", Header.Type); - - if (Header.ForcePacking != PackingFlag.None - || Header.ForceMerging != MergingFlag.None - || Header.ForceNodump != NodumpFlag.None - || !string.IsNullOrWhiteSpace(Header.HeaderSkipper)) - { - xtw.WriteStartElement("clrmamepro"); - - if (Header.ForcePacking != PackingFlag.None) - xtw.WriteOptionalAttributeString("forcepacking", Header.ForcePacking.FromPackingFlag(false)); - if (Header.ForceMerging != MergingFlag.None) - xtw.WriteOptionalAttributeString("forcemerging", Header.ForceMerging.FromMergingFlag(false)); - if (Header.ForceNodump != NodumpFlag.None) - xtw.WriteOptionalAttributeString("forcenodump", Header.ForceNodump.FromNodumpFlag()); - xtw.WriteOptionalAttributeString("header", Header.HeaderSkipper); - - // End clrmamepro - xtw.WriteEndElement(); - } - - if (Header.System != null - || Header.RomMode != MergingFlag.None || Header.LockRomMode != null - || Header.BiosMode != MergingFlag.None || Header.LockBiosMode != null - || Header.SampleMode != MergingFlag.None || Header.LockSampleMode != null) - { - xtw.WriteStartElement("romcenter"); - - xtw.WriteOptionalAttributeString("plugin", Header.System); - if (Header.RomMode != MergingFlag.None) - xtw.WriteOptionalAttributeString("rommode", Header.RomMode.FromMergingFlag(true)); - if (Header.BiosMode != MergingFlag.None) - xtw.WriteOptionalAttributeString("biosmode", Header.BiosMode.FromMergingFlag(true)); - if (Header.SampleMode != MergingFlag.None) - xtw.WriteOptionalAttributeString("samplemode", Header.SampleMode.FromMergingFlag(true)); - xtw.WriteOptionalAttributeString("lockrommode", Header.LockRomMode.FromYesNo()); - xtw.WriteOptionalAttributeString("lockbiosmode", Header.LockBiosMode.FromYesNo()); - xtw.WriteOptionalAttributeString("locksamplemode", Header.LockSampleMode.FromYesNo()); - - // End romcenter - xtw.WriteEndElement(); - } - - // End header - xtw.WriteEndElement(); - - xtw.Flush(); - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// XmlTextWriter to output to - /// DatItem object to be output - private void WriteStartGame(XmlTextWriter xtw, DatItem datItem) - { - // No game should start with a path separator - datItem.Machine.Name = datItem.Machine.Name.TrimStart(Path.DirectorySeparatorChar); - - // Build the state - xtw.WriteStartElement(_deprecated ? "game" : "machine"); - xtw.WriteRequiredAttributeString("name", datItem.Machine.Name); - - if (datItem.Machine.MachineType.HasFlag(MachineType.Bios)) - xtw.WriteAttributeString("isbios", "yes"); - if (datItem.Machine.MachineType.HasFlag(MachineType.Device)) - xtw.WriteAttributeString("isdevice", "yes"); - if (datItem.Machine.MachineType.HasFlag(MachineType.Mechanical)) - xtw.WriteAttributeString("ismechanical", "yes"); - - xtw.WriteOptionalAttributeString("runnable", datItem.Machine.Runnable.FromRunnable()); - xtw.WriteOptionalAttributeString("id", datItem.Machine.NoIntroId); - xtw.WriteOptionalAttributeString("cloneofid", datItem.Machine.NoIntroCloneOfId); - - if (!string.Equals(datItem.Machine.Name, datItem.Machine.CloneOf, StringComparison.OrdinalIgnoreCase)) - xtw.WriteOptionalAttributeString("cloneof", datItem.Machine.CloneOf); - if (!string.Equals(datItem.Machine.Name, datItem.Machine.RomOf, StringComparison.OrdinalIgnoreCase)) - xtw.WriteOptionalAttributeString("romof", datItem.Machine.RomOf); - if (!string.Equals(datItem.Machine.Name, datItem.Machine.SampleOf, StringComparison.OrdinalIgnoreCase)) - xtw.WriteOptionalAttributeString("sampleof", datItem.Machine.SampleOf); - - xtw.WriteOptionalElementString("comment", datItem.Machine.Comment); - xtw.WriteOptionalElementString("description", datItem.Machine.Description); - xtw.WriteOptionalElementString("year", datItem.Machine.Year); - xtw.WriteOptionalElementString("publisher", datItem.Machine.Publisher); - xtw.WriteOptionalElementString("manufacturer", datItem.Machine.Manufacturer); - xtw.WriteOptionalElementString("category", datItem.Machine.Category); - - if (datItem.Machine.TitleID != null - || datItem.Machine.Developer != null - || datItem.Machine.Genre != null - || datItem.Machine.Subgenre != null - || datItem.Machine.Ratings != null - || datItem.Machine.Score != null - || datItem.Machine.Enabled != null - || datItem.Machine.Crc != null - || datItem.Machine.RelatedTo != null) - { - xtw.WriteStartElement("trurip"); - - xtw.WriteOptionalElementString("titleid", datItem.Machine.TitleID); - xtw.WriteOptionalElementString("publisher", datItem.Machine.Publisher); - xtw.WriteOptionalElementString("developer", datItem.Machine.Developer); - xtw.WriteOptionalElementString("year", datItem.Machine.Year); - xtw.WriteOptionalElementString("genre", datItem.Machine.Genre); - xtw.WriteOptionalElementString("subgenre", datItem.Machine.Subgenre); - xtw.WriteOptionalElementString("ratings", datItem.Machine.Ratings); - xtw.WriteOptionalElementString("score", datItem.Machine.Score); - xtw.WriteOptionalElementString("players", datItem.Machine.Players); - xtw.WriteOptionalElementString("enabled", datItem.Machine.Enabled); - xtw.WriteOptionalElementString("titleid", datItem.Machine.TitleID); - xtw.WriteOptionalElementString("crc", datItem.Machine.Crc.FromYesNo()); - xtw.WriteOptionalElementString("source", datItem.Machine.SourceFile); - xtw.WriteOptionalElementString("cloneof", datItem.Machine.CloneOf); - xtw.WriteOptionalElementString("relatedto", datItem.Machine.RelatedTo); - - // End trurip - xtw.WriteEndElement(); - } - - xtw.Flush(); - } - - /// - /// Write out Game end using the supplied StreamWriter - /// - /// XmlTextWriter to output to - private void WriteEndGame(XmlTextWriter xtw) - { - // End machine - xtw.WriteEndElement(); - - xtw.Flush(); - } - - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// XmlTextWriter to output to - /// DatItem object to be output - private void WriteDatItem(XmlTextWriter xtw, DatItem datItem) - { - // Pre-process the item name - ProcessItemName(datItem, true); - - // Build the state - switch (datItem.ItemType) - { - case ItemType.Archive: - var archive = datItem as Archive; - xtw.WriteStartElement("archive"); - xtw.WriteRequiredAttributeString("name", archive.Name); - xtw.WriteEndElement(); - break; - - case ItemType.BiosSet: - var biosSet = datItem as BiosSet; - xtw.WriteStartElement("biosset"); - xtw.WriteRequiredAttributeString("name", biosSet.Name); - xtw.WriteOptionalAttributeString("description", biosSet.Description); - xtw.WriteOptionalAttributeString("default", biosSet.Default.FromYesNo()); - xtw.WriteEndElement(); - break; - - case ItemType.Disk: - var disk = datItem as Disk; - xtw.WriteStartElement("disk"); - xtw.WriteRequiredAttributeString("name", disk.Name); - xtw.WriteOptionalAttributeString("md5", disk.MD5?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha1", disk.SHA1?.ToLowerInvariant()); - if (disk.ItemStatus != ItemStatus.None) - xtw.WriteOptionalAttributeString("status", disk.ItemStatus.FromItemStatus(false)); - xtw.WriteEndElement(); - break; - - case ItemType.Media: - var media = datItem as Media; - xtw.WriteStartElement("media"); - xtw.WriteRequiredAttributeString("name", media.Name); - xtw.WriteOptionalAttributeString("md5", media.MD5?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha1", media.SHA1?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha256", media.SHA256?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("spamsum", media.SpamSum?.ToLowerInvariant()); - xtw.WriteEndElement(); - break; - - case ItemType.Release: - var release = datItem as Release; - xtw.WriteStartElement("release"); - xtw.WriteRequiredAttributeString("name", release.Name); - xtw.WriteOptionalAttributeString("region", release.Region); - xtw.WriteOptionalAttributeString("language", release.Language); - xtw.WriteOptionalAttributeString("date", release.Date); - xtw.WriteOptionalAttributeString("default", release.Default.FromYesNo()); - xtw.WriteEndElement(); - break; - - case ItemType.Rom: - var rom = datItem as Rom; - xtw.WriteStartElement("rom"); - xtw.WriteRequiredAttributeString("name", rom.Name); - xtw.WriteAttributeString("size", rom.Size?.ToString()); - xtw.WriteOptionalAttributeString("crc", rom.CRC?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("md5", rom.MD5?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha1", rom.SHA1?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha256", rom.SHA256?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha384", rom.SHA384?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha512", rom.SHA512?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("spamsum", rom.SpamSum?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("date", rom.Date); - if (rom.ItemStatus != ItemStatus.None) - xtw.WriteOptionalAttributeString("status", rom.ItemStatus.FromItemStatus(false)); - xtw.WriteOptionalAttributeString("inverted", rom.Inverted.FromYesNo()); - xtw.WriteOptionalAttributeString("mia", rom.MIA.FromYesNo()); - xtw.WriteEndElement(); - break; - - case ItemType.Sample: - var sample = datItem as Sample; - xtw.WriteStartElement("sample"); - xtw.WriteRequiredAttributeString("name", sample.Name); - xtw.WriteEndElement(); - break; - } - - xtw.Flush(); - } - - /// - /// Write out DAT footer using the supplied StreamWriter - /// - /// XmlTextWriter to output to - private void WriteFooter(XmlTextWriter xtw) - { - // End machine - xtw.WriteEndElement(); - - // End datafile - xtw.WriteEndElement(); - - xtw.Flush(); - } } }