diff --git a/SabreTools.DatFiles/Formats/OpenMSX.Reader.cs b/SabreTools.DatFiles/Formats/OpenMSX.Reader.cs
new file mode 100644
index 00000000..c781d187
--- /dev/null
+++ b/SabreTools.DatFiles/Formats/OpenMSX.Reader.cs
@@ -0,0 +1,505 @@
+using System;
+using System.Collections.Generic;
+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 an openMSX softawre list XML DAT
+ ///
+ internal partial class OpenMSX : 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,
+ });
+
+ // 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)
+ {
+ xtr.Read();
+ continue;
+ }
+
+ switch (xtr.Name)
+ {
+ case "softwaredb":
+ Header.Name ??= "openMSX Software List";
+ Header.Description ??= Header.Name;
+ Header.Date ??= xtr.GetAttribute("timestamp");
+ xtr.Read();
+ break;
+
+ // We want to process the entire subtree of the software
+ case "software":
+ ReadSoftware(xtr.ReadSubtree(), statsOnly, filename, indexId);
+
+ // Skip the software 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 software information
+ ///
+ /// XmlReader representing a machine block
+ /// True to only add item statistics while parsing, false otherwise
+ /// Name of the file to be parsed
+ /// Index ID for the DAT
+ private void ReadSoftware(XmlReader reader, bool statsOnly, string filename, int indexId)
+ {
+ // If we have an empty machine, skip it
+ if (reader == null)
+ return;
+
+ // Otherwise, add what is possible
+ reader.MoveToContent();
+
+ int diskno = 0;
+ bool containsItems = false;
+
+ // Create a new machine
+ Machine machine = new();
+
+ 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 "title":
+ machine.Name = reader.ReadElementContentAsString();
+ break;
+
+ case "genmsxid":
+ machine.GenMSXID = reader.ReadElementContentAsString();
+ break;
+
+ case "system":
+ machine.System = reader.ReadElementContentAsString();
+ break;
+
+ case "company":
+ machine.Manufacturer = reader.ReadElementContentAsString();
+ break;
+
+ case "year":
+ machine.Year = reader.ReadElementContentAsString();
+ break;
+
+ case "country":
+ machine.Country = reader.ReadElementContentAsString();
+ break;
+
+ case "dump":
+ containsItems = ReadDump(reader.ReadSubtree(), machine, diskno, statsOnly, filename, indexId);
+ diskno++;
+
+ // Skip the dump now that we've processed it
+ reader.Skip();
+ 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 dump information
+ ///
+ /// XmlReader representing a part block
+ /// Machine information to pass to contained items
+ /// Disk number to use when outputting to other DAT formats
+ /// True to only add item statistics while parsing, false otherwise
+ /// Name of the file to be parsed
+ /// Index ID for the DAT
+ private bool ReadDump(
+ XmlReader reader,
+ Machine machine,
+ int diskno,
+ bool statsOnly,
+
+ // Standard Dat parsing
+ string filename,
+ int indexId)
+ {
+ List items = new();
+ Original original = null;
+
+ while (!reader.EOF)
+ {
+ // We only want elements
+ if (reader.NodeType != XmlNodeType.Element)
+ {
+ reader.Read();
+ continue;
+ }
+
+ // Get the elements from the dump
+ switch (reader.Name)
+ {
+ case "rom":
+ DatItem rom = ReadRom(reader.ReadSubtree(), machine, diskno, filename, indexId);
+ if (rom != null)
+ items.Add(rom);
+
+ // Skip the rom now that we've processed it
+ reader.Skip();
+ break;
+
+ case "megarom":
+ DatItem megarom = ReadMegaRom(reader.ReadSubtree(), machine, diskno, filename, indexId);
+ if (megarom != null)
+ items.Add(megarom);
+
+ // Skip the megarom now that we've processed it
+ reader.Skip();
+ break;
+
+ case "sccpluscart":
+ DatItem sccpluscart = ReadSccPlusCart(reader.ReadSubtree(), machine, diskno, filename, indexId);
+ if (sccpluscart != null)
+ items.Add(sccpluscart);
+
+ // Skip the sccpluscart now that we've processed it
+ reader.Skip();
+ break;
+
+ case "original":
+ original = new Original
+ {
+ Value = reader.GetAttribute("value").AsYesNo(),
+ Content = reader.ReadElementContentAsString()
+ };
+ break;
+
+ default:
+ reader.Read();
+ break;
+ }
+ }
+
+ // If we have any items, loop through and add them
+ foreach (DatItem item in items)
+ {
+ switch (item.ItemType)
+ {
+ case ItemType.Rom:
+ (item as Rom).Original = original;
+ break;
+ }
+
+ item.CopyMachineInformation(machine);
+ ParseAddHelper(item, statsOnly);
+ }
+
+ return items.Count > 0;
+ }
+
+ ///
+ /// Read rom information
+ ///
+ /// XmlReader representing a rom block
+ /// Machine information to pass to contained items
+ /// Disk number to use when outputting to other DAT formats
+ /// Name of the file to be parsed
+ /// Index ID for the DAT
+ private DatItem ReadRom(
+ XmlReader reader,
+ Machine machine,
+ int diskno,
+
+ // Standard Dat parsing
+ string filename,
+ int indexId)
+ {
+ string hash = string.Empty,
+ offset = string.Empty,
+ type = string.Empty,
+ remark = string.Empty;
+
+ while (!reader.EOF)
+ {
+ // We only want elements
+ if (reader.NodeType != XmlNodeType.Element)
+ {
+ reader.Read();
+ continue;
+ }
+
+ // Get the elements from the rom
+ switch (reader.Name)
+ {
+ case "hash":
+ hash = reader.ReadElementContentAsString();
+ break;
+
+ case "start":
+ offset = reader.ReadElementContentAsString();
+ break;
+
+ case "type":
+ type = reader.ReadElementContentAsString();
+ break;
+
+ case "remark":
+ remark = reader.ReadElementContentAsString();
+ break;
+
+ default:
+ reader.Read();
+ break;
+ }
+ }
+
+ // If we got a hash, then create and return the item
+ if (!string.IsNullOrWhiteSpace(hash))
+ {
+ return new Rom
+ {
+ Name = machine.Name + "_" + diskno + (!string.IsNullOrWhiteSpace(remark) ? " " + remark : string.Empty),
+ Offset = offset,
+ Size = null,
+ SHA1 = hash,
+
+ Source = new Source
+ {
+ Index = indexId,
+ Name = filename,
+ },
+
+ OpenMSXSubType = OpenMSXSubType.Rom,
+ OpenMSXType = type,
+ Remark = remark,
+ };
+ }
+
+ // No valid item means returning null
+ return null;
+ }
+
+ ///
+ /// Read megarom information
+ ///
+ /// XmlReader representing a megarom block
+ /// Machine information to pass to contained items
+ /// Disk number to use when outputting to other DAT formats
+ /// Name of the file to be parsed
+ /// Index ID for the DAT
+ private DatItem ReadMegaRom(
+ XmlReader reader,
+ Machine machine,
+ int diskno,
+
+ // Standard Dat parsing
+ string filename,
+ int indexId)
+ {
+ string hash = string.Empty,
+ offset = string.Empty,
+ type = string.Empty,
+ remark = string.Empty;
+
+ while (!reader.EOF)
+ {
+ // We only want elements
+ if (reader.NodeType != XmlNodeType.Element)
+ {
+ reader.Read();
+ continue;
+ }
+
+ // Get the elements from the dump
+ switch (reader.Name)
+ {
+ case "hash":
+ hash = reader.ReadElementContentAsString();
+ break;
+
+ case "start":
+ offset = reader.ReadElementContentAsString();
+ break;
+
+ case "type":
+ reader.ReadElementContentAsString();
+ break;
+
+ case "remark":
+ remark = reader.ReadElementContentAsString();
+ break;
+
+ default:
+ reader.Read();
+ break;
+ }
+ }
+
+ // If we got a hash, then create and return the item
+ if (!string.IsNullOrWhiteSpace(hash))
+ {
+ return new Rom
+ {
+ Name = machine.Name + "_" + diskno + (!string.IsNullOrWhiteSpace(remark) ? " " + remark : string.Empty),
+ Offset = offset,
+ Size = null,
+ SHA1 = hash,
+
+ Source = new Source
+ {
+ Index = indexId,
+ Name = filename,
+ },
+
+ OpenMSXSubType = OpenMSXSubType.MegaRom,
+ OpenMSXType = type,
+ Remark = remark,
+ };
+ }
+
+ // No valid item means returning null
+ return null;
+ }
+
+ ///
+ /// Read sccpluscart information
+ ///
+ /// XmlReader representing a sccpluscart block
+ /// Machine information to pass to contained items
+ /// Disk number to use when outputting to other DAT formats
+ /// Name of the file to be parsed
+ /// Index ID for the DAT
+ private DatItem ReadSccPlusCart(
+ XmlReader reader,
+ Machine machine,
+ int diskno,
+
+ // Standard Dat parsing
+ string filename,
+ int indexId)
+ {
+ string boot = string.Empty,
+ hash = string.Empty,
+ remark = string.Empty;
+
+ while (!reader.EOF)
+ {
+ // We only want elements
+ if (reader.NodeType != XmlNodeType.Element)
+ {
+ reader.Read();
+ continue;
+ }
+
+ // Get the elements from the dump
+ switch (reader.Name)
+ {
+ case "boot":
+ boot = reader.ReadElementContentAsString();
+ break;
+
+ case "hash":
+ hash = reader.ReadElementContentAsString();
+ break;
+
+ case "remark":
+ remark = reader.ReadElementContentAsString();
+ break;
+
+ default:
+ reader.Read();
+ break;
+ }
+ }
+
+ // If we got a hash, then create and return the item
+ if (!string.IsNullOrWhiteSpace(hash))
+ {
+ return new Rom
+ {
+ Name = machine.Name + "_" + diskno + (!string.IsNullOrWhiteSpace(remark) ? " " + remark : string.Empty),
+ Size = null,
+ SHA1 = hash,
+
+ Source = new Source
+ {
+ Index = indexId,
+ Name = filename,
+ },
+
+ OpenMSXSubType = OpenMSXSubType.SCCPlusCart,
+ Boot = boot,
+ Remark = remark,
+ };
+ }
+
+ // No valid item means returning null
+ return null;
+ }
+ }
+}
diff --git a/SabreTools.DatFiles/Formats/OpenMSX.Writer.cs b/SabreTools.DatFiles/Formats/OpenMSX.Writer.cs
new file mode 100644
index 00000000..f5d5a6d9
--- /dev/null
+++ b/SabreTools.DatFiles/Formats/OpenMSX.Writer.cs
@@ -0,0 +1,257 @@
+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 writing an openMSX softawre list XML DAT
+ ///
+ internal partial class OpenMSX : DatFile
+ {
+ ///
+ protected override ItemType[] GetSupportedTypes()
+ {
+ return new ItemType[]
+ {
+ ItemType.Rom
+ };
+ }
+
+ ///
+ 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();
+ xtw.WriteDocType("softwaredb", null, "softwaredb1.dtd", null);
+
+ xtw.WriteStartElement("softwaredb");
+ xtw.WriteRequiredAttributeString("timestamp", Header.Date);
+
+ //TODO: Figure out how to fix the issue with removed formatting after this point
+// xtw.WriteComment("Credits");
+// xtw.WriteCData(@"The softwaredb.xml file contains information about rom mapper types
+
+//-Copyright 2003 Nicolas Beyaert(Initial Database)
+//-Copyright 2004 - 2013 BlueMSX Team
+//-Copyright 2005 - 2020 openMSX Team
+//-Generation MSXIDs by www.generation - msx.nl
+
+//- Thanks go out to:
+//-Generation MSX / Sylvester for the incredible source of information
+//- p_gimeno and diedel for their help adding and valdiating ROM additions
+//- GDX for additional ROM info and validations and corrections");
+
+ 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("software");
+ xtw.WriteRequiredElementString("title", datItem.Machine.Name);
+ xtw.WriteRequiredElementString("genmsxid", datItem.Machine.GenMSXID);
+ xtw.WriteRequiredElementString("system", datItem.Machine.System);
+ xtw.WriteRequiredElementString("company", datItem.Machine.Manufacturer);
+ xtw.WriteRequiredElementString("year", datItem.Machine.Year);
+ xtw.WriteRequiredElementString("country", datItem.Machine.Country);
+
+ xtw.Flush();
+ }
+
+ ///
+ /// Write out Game start using the supplied StreamWriter
+ ///
+ /// XmlTextWriter to output to
+ private void WriteEndGame(XmlTextWriter xtw)
+ {
+ // End software
+ 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.Rom:
+ var rom = datItem as Rom;
+ xtw.WriteStartElement("dump");
+
+ if (rom.Original != null)
+ {
+ xtw.WriteStartElement("original");
+ xtw.WriteAttributeString("value", rom.Original.Value == true ? "true" : "false");
+ xtw.WriteString(rom.Original.Content);
+ xtw.WriteEndElement();
+ }
+
+ switch (rom.OpenMSXSubType)
+ {
+ // Default to Rom for converting from other formats
+ case OpenMSXSubType.Rom:
+ case OpenMSXSubType.NULL:
+ xtw.WriteStartElement(rom.OpenMSXSubType.FromOpenMSXSubType());
+ xtw.WriteRequiredElementString("hash", rom.SHA1?.ToLowerInvariant());
+ xtw.WriteOptionalElementString("start", rom.Offset);
+ xtw.WriteOptionalElementString("type", rom.OpenMSXType);
+ xtw.WriteOptionalElementString("remark", rom.Remark);
+ xtw.WriteEndElement();
+ break;
+
+ case OpenMSXSubType.MegaRom:
+ xtw.WriteStartElement(rom.OpenMSXSubType.FromOpenMSXSubType());
+ xtw.WriteRequiredElementString("hash", rom.SHA1?.ToLowerInvariant());
+ xtw.WriteOptionalElementString("start", rom.Offset);
+ xtw.WriteOptionalElementString("type", rom.OpenMSXType);
+ xtw.WriteOptionalElementString("remark", rom.Remark);
+ xtw.WriteEndElement();
+ break;
+
+ case OpenMSXSubType.SCCPlusCart:
+ xtw.WriteStartElement(rom.OpenMSXSubType.FromOpenMSXSubType());
+ xtw.WriteOptionalElementString("boot", rom.Boot);
+ xtw.WriteRequiredElementString("hash", rom.SHA1?.ToLowerInvariant());
+ xtw.WriteOptionalElementString("remark", rom.Remark);
+ xtw.WriteEndElement();
+ break;
+ }
+
+ // End dump
+ xtw.WriteEndElement();
+ break;
+ }
+
+ xtw.Flush();
+ }
+
+ ///
+ /// Write out DAT footer using the supplied StreamWriter
+ ///
+ /// XmlTextWriter to output to
+ private void WriteFooter(XmlTextWriter xtw)
+ {
+ // End software
+ xtw.WriteEndElement();
+
+ // End softwaredb
+ xtw.WriteEndElement();
+
+ xtw.Flush();
+ }
+ }
+}
diff --git a/SabreTools.DatFiles/Formats/OpenMSX.cs b/SabreTools.DatFiles/Formats/OpenMSX.cs
index f0027e93..96983125 100644
--- a/SabreTools.DatFiles/Formats/OpenMSX.cs
+++ b/SabreTools.DatFiles/Formats/OpenMSX.cs
@@ -1,21 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-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 openMSX softawre list XML DAT
+ /// Represents an openMSX softawre list XML DAT
///
- internal class OpenMSX : DatFile
+ internal partial class OpenMSX : DatFile
{
///
/// DTD for original openMSX DATs
@@ -39,728 +27,5 @@ namespace SabreTools.DatFiles.Formats
: base(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,
- });
-
- // 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)
- {
- xtr.Read();
- continue;
- }
-
- switch (xtr.Name)
- {
- case "softwaredb":
- Header.Name ??= "openMSX Software List";
- Header.Description ??= Header.Name;
- Header.Date ??= xtr.GetAttribute("timestamp");
- xtr.Read();
- break;
-
- // We want to process the entire subtree of the software
- case "software":
- ReadSoftware(xtr.ReadSubtree(), statsOnly, filename, indexId);
-
- // Skip the software 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 software information
- ///
- /// XmlReader representing a machine block
- /// True to only add item statistics while parsing, false otherwise
- /// Name of the file to be parsed
- /// Index ID for the DAT
- private void ReadSoftware(XmlReader reader, bool statsOnly, string filename, int indexId)
- {
- // If we have an empty machine, skip it
- if (reader == null)
- return;
-
- // Otherwise, add what is possible
- reader.MoveToContent();
-
- int diskno = 0;
- bool containsItems = false;
-
- // Create a new machine
- Machine machine = new();
-
- 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 "title":
- machine.Name = reader.ReadElementContentAsString();
- break;
-
- case "genmsxid":
- machine.GenMSXID = reader.ReadElementContentAsString();
- break;
-
- case "system":
- machine.System = reader.ReadElementContentAsString();
- break;
-
- case "company":
- machine.Manufacturer = reader.ReadElementContentAsString();
- break;
-
- case "year":
- machine.Year = reader.ReadElementContentAsString();
- break;
-
- case "country":
- machine.Country = reader.ReadElementContentAsString();
- break;
-
- case "dump":
- containsItems = ReadDump(reader.ReadSubtree(), machine, diskno, statsOnly, filename, indexId);
- diskno++;
-
- // Skip the dump now that we've processed it
- reader.Skip();
- 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 dump information
- ///
- /// XmlReader representing a part block
- /// Machine information to pass to contained items
- /// Disk number to use when outputting to other DAT formats
- /// True to only add item statistics while parsing, false otherwise
- /// Name of the file to be parsed
- /// Index ID for the DAT
- private bool ReadDump(
- XmlReader reader,
- Machine machine,
- int diskno,
- bool statsOnly,
-
- // Standard Dat parsing
- string filename,
- int indexId)
- {
- List items = new();
- Original original = null;
-
- while (!reader.EOF)
- {
- // We only want elements
- if (reader.NodeType != XmlNodeType.Element)
- {
- reader.Read();
- continue;
- }
-
- // Get the elements from the dump
- switch (reader.Name)
- {
- case "rom":
- DatItem rom = ReadRom(reader.ReadSubtree(), machine, diskno, filename, indexId);
- if (rom != null)
- items.Add(rom);
-
- // Skip the rom now that we've processed it
- reader.Skip();
- break;
-
- case "megarom":
- DatItem megarom = ReadMegaRom(reader.ReadSubtree(), machine, diskno, filename, indexId);
- if (megarom != null)
- items.Add(megarom);
-
- // Skip the megarom now that we've processed it
- reader.Skip();
- break;
-
- case "sccpluscart":
- DatItem sccpluscart = ReadSccPlusCart(reader.ReadSubtree(), machine, diskno, filename, indexId);
- if (sccpluscart != null)
- items.Add(sccpluscart);
-
- // Skip the sccpluscart now that we've processed it
- reader.Skip();
- break;
-
- case "original":
- original = new Original
- {
- Value = reader.GetAttribute("value").AsYesNo(),
- Content = reader.ReadElementContentAsString()
- };
- break;
-
- default:
- reader.Read();
- break;
- }
- }
-
- // If we have any items, loop through and add them
- foreach (DatItem item in items)
- {
- switch (item.ItemType)
- {
- case ItemType.Rom:
- (item as Rom).Original = original;
- break;
- }
-
- item.CopyMachineInformation(machine);
- ParseAddHelper(item, statsOnly);
- }
-
- return items.Count > 0;
- }
-
- ///
- /// Read rom information
- ///
- /// XmlReader representing a rom block
- /// Machine information to pass to contained items
- /// Disk number to use when outputting to other DAT formats
- /// Name of the file to be parsed
- /// Index ID for the DAT
- private DatItem ReadRom(
- XmlReader reader,
- Machine machine,
- int diskno,
-
- // Standard Dat parsing
- string filename,
- int indexId)
- {
- string hash = string.Empty,
- offset = string.Empty,
- type = string.Empty,
- remark = string.Empty;
-
- while (!reader.EOF)
- {
- // We only want elements
- if (reader.NodeType != XmlNodeType.Element)
- {
- reader.Read();
- continue;
- }
-
- // Get the elements from the rom
- switch (reader.Name)
- {
- case "hash":
- hash = reader.ReadElementContentAsString();
- break;
-
- case "start":
- offset = reader.ReadElementContentAsString();
- break;
-
- case "type":
- type = reader.ReadElementContentAsString();
- break;
-
- case "remark":
- remark = reader.ReadElementContentAsString();
- break;
-
- default:
- reader.Read();
- break;
- }
- }
-
- // If we got a hash, then create and return the item
- if (!string.IsNullOrWhiteSpace(hash))
- {
- return new Rom
- {
- Name = machine.Name + "_" + diskno + (!string.IsNullOrWhiteSpace(remark) ? " " + remark : string.Empty),
- Offset = offset,
- Size = null,
- SHA1 = hash,
-
- Source = new Source
- {
- Index = indexId,
- Name = filename,
- },
-
- OpenMSXSubType = OpenMSXSubType.Rom,
- OpenMSXType = type,
- Remark = remark,
- };
- }
-
- // No valid item means returning null
- return null;
- }
-
- ///
- /// Read megarom information
- ///
- /// XmlReader representing a megarom block
- /// Machine information to pass to contained items
- /// Disk number to use when outputting to other DAT formats
- /// Name of the file to be parsed
- /// Index ID for the DAT
- private DatItem ReadMegaRom(
- XmlReader reader,
- Machine machine,
- int diskno,
-
- // Standard Dat parsing
- string filename,
- int indexId)
- {
- string hash = string.Empty,
- offset = string.Empty,
- type = string.Empty,
- remark = string.Empty;
-
- while (!reader.EOF)
- {
- // We only want elements
- if (reader.NodeType != XmlNodeType.Element)
- {
- reader.Read();
- continue;
- }
-
- // Get the elements from the dump
- switch (reader.Name)
- {
- case "hash":
- hash = reader.ReadElementContentAsString();
- break;
-
- case "start":
- offset = reader.ReadElementContentAsString();
- break;
-
- case "type":
- reader.ReadElementContentAsString();
- break;
-
- case "remark":
- remark = reader.ReadElementContentAsString();
- break;
-
- default:
- reader.Read();
- break;
- }
- }
-
- // If we got a hash, then create and return the item
- if (!string.IsNullOrWhiteSpace(hash))
- {
- return new Rom
- {
- Name = machine.Name + "_" + diskno + (!string.IsNullOrWhiteSpace(remark) ? " " + remark : string.Empty),
- Offset = offset,
- Size = null,
- SHA1 = hash,
-
- Source = new Source
- {
- Index = indexId,
- Name = filename,
- },
-
- OpenMSXSubType = OpenMSXSubType.MegaRom,
- OpenMSXType = type,
- Remark = remark,
- };
- }
-
- // No valid item means returning null
- return null;
- }
-
- ///
- /// Read sccpluscart information
- ///
- /// XmlReader representing a sccpluscart block
- /// Machine information to pass to contained items
- /// Disk number to use when outputting to other DAT formats
- /// Name of the file to be parsed
- /// Index ID for the DAT
- private DatItem ReadSccPlusCart(
- XmlReader reader,
- Machine machine,
- int diskno,
-
- // Standard Dat parsing
- string filename,
- int indexId)
- {
- string boot = string.Empty,
- hash = string.Empty,
- remark = string.Empty;
-
- while (!reader.EOF)
- {
- // We only want elements
- if (reader.NodeType != XmlNodeType.Element)
- {
- reader.Read();
- continue;
- }
-
- // Get the elements from the dump
- switch (reader.Name)
- {
- case "boot":
- boot = reader.ReadElementContentAsString();
- break;
-
- case "hash":
- hash = reader.ReadElementContentAsString();
- break;
-
- case "remark":
- remark = reader.ReadElementContentAsString();
- break;
-
- default:
- reader.Read();
- break;
- }
- }
-
- // If we got a hash, then create and return the item
- if (!string.IsNullOrWhiteSpace(hash))
- {
- return new Rom
- {
- Name = machine.Name + "_" + diskno + (!string.IsNullOrWhiteSpace(remark) ? " " + remark : string.Empty),
- Size = null,
- SHA1 = hash,
-
- Source = new Source
- {
- Index = indexId,
- Name = filename,
- },
-
- OpenMSXSubType = OpenMSXSubType.SCCPlusCart,
- Boot = boot,
- Remark = remark,
- };
- }
-
- // No valid item means returning null
- return null;
- }
-
- ///
- protected override ItemType[] GetSupportedTypes()
- {
- return new ItemType[] { ItemType.Rom };
- }
-
- ///
- 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();
- xtw.WriteDocType("softwaredb", null, "softwaredb1.dtd", null);
-
- xtw.WriteStartElement("softwaredb");
- xtw.WriteRequiredAttributeString("timestamp", Header.Date);
-
- //TODO: Figure out how to fix the issue with removed formatting after this point
-// xtw.WriteComment("Credits");
-// xtw.WriteCData(@"The softwaredb.xml file contains information about rom mapper types
-
-//-Copyright 2003 Nicolas Beyaert(Initial Database)
-//-Copyright 2004 - 2013 BlueMSX Team
-//-Copyright 2005 - 2020 openMSX Team
-//-Generation MSXIDs by www.generation - msx.nl
-
-//- Thanks go out to:
-//-Generation MSX / Sylvester for the incredible source of information
-//- p_gimeno and diedel for their help adding and valdiating ROM additions
-//- GDX for additional ROM info and validations and corrections");
-
- 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("software");
- xtw.WriteRequiredElementString("title", datItem.Machine.Name);
- xtw.WriteRequiredElementString("genmsxid", datItem.Machine.GenMSXID);
- xtw.WriteRequiredElementString("system", datItem.Machine.System);
- xtw.WriteRequiredElementString("company", datItem.Machine.Manufacturer);
- xtw.WriteRequiredElementString("year", datItem.Machine.Year);
- xtw.WriteRequiredElementString("country", datItem.Machine.Country);
-
- xtw.Flush();
- }
-
- ///
- /// Write out Game start using the supplied StreamWriter
- ///
- /// XmlTextWriter to output to
- private void WriteEndGame(XmlTextWriter xtw)
- {
- // End software
- 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.Rom:
- var rom = datItem as Rom;
- xtw.WriteStartElement("dump");
-
- if (rom.Original != null)
- {
- xtw.WriteStartElement("original");
- xtw.WriteAttributeString("value", rom.Original.Value == true ? "true" : "false");
- xtw.WriteString(rom.Original.Content);
- xtw.WriteEndElement();
- }
-
- switch (rom.OpenMSXSubType)
- {
- // Default to Rom for converting from other formats
- case OpenMSXSubType.Rom:
- case OpenMSXSubType.NULL:
- xtw.WriteStartElement(rom.OpenMSXSubType.FromOpenMSXSubType());
- xtw.WriteRequiredElementString("hash", rom.SHA1?.ToLowerInvariant());
- xtw.WriteOptionalElementString("start", rom.Offset);
- xtw.WriteOptionalElementString("type", rom.OpenMSXType);
- xtw.WriteOptionalElementString("remark", rom.Remark);
- xtw.WriteEndElement();
- break;
-
- case OpenMSXSubType.MegaRom:
- xtw.WriteStartElement(rom.OpenMSXSubType.FromOpenMSXSubType());
- xtw.WriteRequiredElementString("hash", rom.SHA1?.ToLowerInvariant());
- xtw.WriteOptionalElementString("start", rom.Offset);
- xtw.WriteOptionalElementString("type", rom.OpenMSXType);
- xtw.WriteOptionalElementString("remark", rom.Remark);
- xtw.WriteEndElement();
- break;
-
- case OpenMSXSubType.SCCPlusCart:
- xtw.WriteStartElement(rom.OpenMSXSubType.FromOpenMSXSubType());
- xtw.WriteOptionalElementString("boot", rom.Boot);
- xtw.WriteRequiredElementString("hash", rom.SHA1?.ToLowerInvariant());
- xtw.WriteOptionalElementString("remark", rom.Remark);
- xtw.WriteEndElement();
- break;
- }
-
- // End dump
- xtw.WriteEndElement();
- break;
- }
-
- xtw.Flush();
- }
-
- ///
- /// Write out DAT footer using the supplied StreamWriter
- ///
- /// XmlTextWriter to output to
- private void WriteFooter(XmlTextWriter xtw)
- {
- // End software
- xtw.WriteEndElement();
-
- // End softwaredb
- xtw.WriteEndElement();
-
- xtw.Flush();
- }
}
}