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();
- }
}
}