diff --git a/SabreTools.DatFiles/Formats/SoftwareList.Reader.cs b/SabreTools.DatFiles/Formats/SoftwareList.Reader.cs
new file mode 100644
index 00000000..51683867
--- /dev/null
+++ b/SabreTools.DatFiles/Formats/SoftwareList.Reader.cs
@@ -0,0 +1,496 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+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 SoftwareList
+ ///
+ internal partial class SoftwareList : 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 "softwarelist":
+ Header.Name ??= xtr.GetAttribute("name") ?? string.Empty;
+ Header.Description ??= xtr.GetAttribute("description") ?? string.Empty;
+
+ xtr.Read();
+ break;
+
+ // We want to process the entire subtree of the machine
+ 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 software 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 software, skip it
+ if (reader == null)
+ return;
+
+ // Otherwise, add what is possible
+ reader.MoveToContent();
+
+ bool containsItems = false;
+
+ // Create a new machine
+ Machine machine = new()
+ {
+ Name = reader.GetAttribute("name"),
+ CloneOf = reader.GetAttribute("cloneof"),
+ Supported = reader.GetAttribute("supported").AsSupported(),
+ };
+
+ while (!reader.EOF)
+ {
+ // We only want elements
+ if (reader.NodeType != XmlNodeType.Element)
+ {
+ reader.Read();
+ continue;
+ }
+
+ // Get the elements from the software
+ switch (reader.Name)
+ {
+ case "description":
+ machine.Description = reader.ReadElementContentAsString();
+ break;
+
+ case "year":
+ machine.Year = reader.ReadElementContentAsString();
+ break;
+
+ case "publisher":
+ machine.Publisher = reader.ReadElementContentAsString();
+ break;
+
+ case "info":
+ ParseAddHelper(new Info
+ {
+ Name = reader.GetAttribute("name"),
+ Value = reader.GetAttribute("value"),
+
+ Source = new Source
+ {
+ Index = indexId,
+ Name = filename,
+ },
+ }, statsOnly);
+
+ reader.Read();
+ break;
+
+ case "sharedfeat":
+ ParseAddHelper(new SharedFeature
+ {
+ Name = reader.GetAttribute("name"),
+ Value = reader.GetAttribute("value"),
+
+ Source = new Source
+ {
+ Index = indexId,
+ Name = filename,
+ },
+ }, statsOnly);
+
+ reader.Read();
+ break;
+
+ case "part": // Contains all rom and disk information
+ var part = new Part()
+ {
+ Name = reader.GetAttribute("name"),
+ Interface = reader.GetAttribute("interface"),
+ };
+
+ // Now read the internal tags
+ containsItems = ReadPart(reader.ReadSubtree(), machine, part, statsOnly, filename, indexId);
+
+ // Skip the part 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 part information
+ ///
+ /// XmlReader representing a part block
+ /// Machine information to pass to contained items
+ /// Part information to pass to contained items
+ /// True to only add item statistics while parsing, false otherwise
+ /// Name of the file to be parsed
+ /// Index ID for the DAT
+ private bool ReadPart(XmlReader reader, Machine machine, Part part, bool statsOnly, string filename, int indexId)
+ {
+ // If we have an empty port, skip it
+ if (reader == null)
+ return false;
+
+ // Get lists ready
+ part.Features = new List();
+ List items = new();
+
+ while (!reader.EOF)
+ {
+ // We only want elements
+ if (reader.NodeType != XmlNodeType.Element)
+ {
+ reader.Read();
+ continue;
+ }
+
+ // Get the elements from the software
+ switch (reader.Name)
+ {
+ case "feature":
+ var feature = new PartFeature()
+ {
+ Name = reader.GetAttribute("name"),
+ Value = reader.GetAttribute("value"),
+ };
+
+ part.Features.Add(feature);
+
+ reader.Read();
+ break;
+
+ case "dataarea":
+ var dataArea = new DataArea
+ {
+ Name = reader.GetAttribute("name"),
+ Size = Utilities.CleanLong(reader.GetAttribute("size")),
+ Width = Utilities.CleanLong(reader.GetAttribute("width")),
+ Endianness = reader.GetAttribute("endianness").AsEndianness(),
+ };
+
+ List roms = ReadDataArea(reader.ReadSubtree(), dataArea);
+
+ // If we got valid roms, add them to the list
+ if (roms != null)
+ items.AddRange(roms);
+
+ // Skip the dataarea now that we've processed it
+ reader.Skip();
+ break;
+
+ case "diskarea":
+ var diskArea = new DiskArea
+ {
+ Name = reader.GetAttribute("name"),
+ };
+
+ List disks = ReadDiskArea(reader.ReadSubtree(), diskArea);
+
+ // If we got valid disks, add them to the list
+ if (disks != null)
+ items.AddRange(disks);
+
+ // Skip the diskarea now that we've processed it
+ reader.Skip();
+ break;
+
+ case "dipswitch":
+ var dipSwitch = new DipSwitch
+ {
+ Name = reader.GetAttribute("name"),
+ Tag = reader.GetAttribute("tag"),
+ Mask = reader.GetAttribute("mask"),
+ };
+
+ // Now read the internal tags
+ ReadDipSwitch(reader.ReadSubtree(), dipSwitch);
+
+ items.Add(dipSwitch);
+
+ // Skip the dipswitch now that we've processed it
+ reader.Skip();
+ break;
+
+ default:
+ reader.Read();
+ break;
+ }
+ }
+
+ // Loop over all of the items, if they exist
+ string key = string.Empty;
+ foreach (DatItem item in items)
+ {
+ // Add all missing information
+ switch (item.ItemType)
+ {
+ case ItemType.DipSwitch:
+ (item as DipSwitch).Part = part;
+ break;
+ case ItemType.Disk:
+ (item as Disk).Part = part;
+ break;
+ case ItemType.Rom:
+ (item as Rom).Part = part;
+
+ // If the rom is continue or ignore, add the size to the previous rom
+ // TODO: Can this be done on write? We technically lose information this way.
+ // Order is not guaranteed, and since these don't tend to have any way
+ // of determining what the "previous" item was after this, that info would
+ // have to be stored *with* the item somehow
+ if ((item as Rom).LoadFlag == LoadFlag.Continue || (item as Rom).LoadFlag == LoadFlag.Ignore)
+ {
+ int index = Items[key].Count - 1;
+ DatItem lastrom = Items[key][index];
+ if (lastrom.ItemType == ItemType.Rom)
+ {
+ (lastrom as Rom).Size += (item as Rom).Size;
+ Items[key].RemoveAt(index);
+ Items[key].Add(lastrom);
+ }
+
+ continue;
+ }
+
+ break;
+ }
+
+ item.Source = new Source(indexId, filename);
+ item.CopyMachineInformation(machine);
+
+ // Finally add each item
+ key = ParseAddHelper(item, statsOnly);
+ }
+
+ return items.Any();
+ }
+
+ ///
+ /// Read dataarea information
+ ///
+ /// XmlReader representing a dataarea block
+ /// DataArea representing the enclosing area
+ private List ReadDataArea(XmlReader reader, DataArea dataArea)
+ {
+ List items = new();
+
+ while (!reader.EOF)
+ {
+ // We only want elements
+ if (reader.NodeType != XmlNodeType.Element)
+ {
+ reader.Read();
+ continue;
+ }
+
+ // Get the elements from the software
+ switch (reader.Name)
+ {
+ case "rom":
+ var rom = new Rom
+ {
+ Name = reader.GetAttribute("name"),
+ Size = Utilities.CleanLong(reader.GetAttribute("size")),
+ CRC = reader.GetAttribute("crc"),
+ SHA1 = reader.GetAttribute("sha1"),
+ Offset = reader.GetAttribute("offset"),
+ Value = reader.GetAttribute("value"),
+ ItemStatus = reader.GetAttribute("status").AsItemStatus(),
+ LoadFlag = reader.GetAttribute("loadflag").AsLoadFlag(),
+
+ DataArea = dataArea,
+ };
+
+ items.Add(rom);
+ reader.Read();
+ break;
+
+ default:
+ reader.Read();
+ break;
+ }
+ }
+
+ return items;
+ }
+
+ ///
+ /// Read diskarea information
+ ///
+ /// XmlReader representing a diskarea block
+ /// DiskArea representing the enclosing area
+ private List ReadDiskArea(XmlReader reader, DiskArea diskArea)
+ {
+ List items = new();
+
+ while (!reader.EOF)
+ {
+ // We only want elements
+ if (reader.NodeType != XmlNodeType.Element)
+ {
+ reader.Read();
+ continue;
+ }
+
+ // Get the elements from the software
+ switch (reader.Name)
+ {
+ case "disk":
+ DatItem disk = new Disk
+ {
+ Name = reader.GetAttribute("name"),
+ SHA1 = reader.GetAttribute("sha1"),
+ ItemStatus = reader.GetAttribute("status").AsItemStatus(),
+ Writable = reader.GetAttribute("writable").AsYesNo(),
+
+ DiskArea = diskArea,
+ };
+
+ items.Add(disk);
+ reader.Read();
+ break;
+
+ default:
+ reader.Read();
+ break;
+ }
+ }
+
+ return items;
+ }
+
+ ///
+ /// Read DipSwitch DipValues information
+ ///
+ /// XmlReader representing a diskarea block
+ /// DipSwitch to populate
+ private void ReadDipSwitch(XmlReader reader, DipSwitch dipSwitch)
+ {
+ // If we have an empty dipswitch, skip it
+ if (reader == null)
+ return;
+
+ // Get list ready
+ dipSwitch.Values = new List();
+
+ // 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 dipswitch
+ switch (reader.Name)
+ {
+ case "dipvalue":
+ var dipValue = new Setting
+ {
+ Name = reader.GetAttribute("name"),
+ Value = reader.GetAttribute("value"),
+ Default = reader.GetAttribute("default").AsYesNo(),
+ };
+
+ dipSwitch.Values.Add(dipValue);
+
+ reader.Read();
+ break;
+
+ default:
+ reader.Read();
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/SabreTools.DatFiles/Formats/SoftwareList.Writer.cs b/SabreTools.DatFiles/Formats/SoftwareList.Writer.cs
new file mode 100644
index 00000000..44500bed
--- /dev/null
+++ b/SabreTools.DatFiles/Formats/SoftwareList.Writer.cs
@@ -0,0 +1,430 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+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 a SoftwareList
+ ///
+ internal partial class SoftwareList : DatFile
+ {
+ ///
+ protected override ItemType[] GetSupportedTypes()
+ {
+ return new ItemType[]
+ {
+ ItemType.DipSwitch,
+ ItemType.Disk,
+ ItemType.Info,
+ ItemType.Rom,
+ ItemType.SharedFeature,
+ };
+ }
+
+ ///
+ protected override List GetMissingRequiredFields(DatItem datItem)
+ {
+ List missingFields = new();
+
+ switch (datItem.ItemType)
+ {
+ case ItemType.DipSwitch:
+ DipSwitch dipSwitch = datItem as DipSwitch;
+ if (!dipSwitch.PartSpecified)
+ {
+ missingFields.Add(DatItemField.Part_Name);
+ missingFields.Add(DatItemField.Part_Interface);
+ }
+ else
+ {
+ if (string.IsNullOrWhiteSpace(dipSwitch.Part.Name))
+ missingFields.Add(DatItemField.Part_Name);
+ if (string.IsNullOrWhiteSpace(dipSwitch.Part.Interface))
+ missingFields.Add(DatItemField.Part_Interface);
+ }
+ if (string.IsNullOrWhiteSpace(dipSwitch.Name))
+ missingFields.Add(DatItemField.Name);
+ if (string.IsNullOrWhiteSpace(dipSwitch.Tag))
+ missingFields.Add(DatItemField.Tag);
+ if (string.IsNullOrWhiteSpace(dipSwitch.Mask))
+ missingFields.Add(DatItemField.Mask);
+ if (dipSwitch.ValuesSpecified)
+ {
+ if (dipSwitch.Values.Any(dv => string.IsNullOrWhiteSpace(dv.Name)))
+ missingFields.Add(DatItemField.Part_Feature_Name);
+ if (dipSwitch.Values.Any(dv => string.IsNullOrWhiteSpace(dv.Value)))
+ missingFields.Add(DatItemField.Part_Feature_Value);
+ }
+
+ break;
+ case ItemType.Disk:
+ Disk disk = datItem as Disk;
+ if (!disk.PartSpecified)
+ {
+ missingFields.Add(DatItemField.Part_Name);
+ missingFields.Add(DatItemField.Part_Interface);
+ }
+ else
+ {
+ if (string.IsNullOrWhiteSpace(disk.Part.Name))
+ missingFields.Add(DatItemField.Part_Name);
+ if (string.IsNullOrWhiteSpace(disk.Part.Interface))
+ missingFields.Add(DatItemField.Part_Interface);
+ }
+ if (!disk.DiskAreaSpecified)
+ {
+ missingFields.Add(DatItemField.AreaName);
+ }
+ else
+ {
+ if (string.IsNullOrWhiteSpace(disk.DiskArea.Name))
+ missingFields.Add(DatItemField.AreaName);
+ }
+ if (string.IsNullOrWhiteSpace(disk.Name))
+ missingFields.Add(DatItemField.Name);
+ break;
+ case ItemType.Info:
+ Info info = datItem as Info;
+ if (string.IsNullOrWhiteSpace(info.Name))
+ missingFields.Add(DatItemField.Name);
+ break;
+ case ItemType.Rom:
+ Rom rom = datItem as Rom;
+ if (!rom.PartSpecified)
+ {
+ missingFields.Add(DatItemField.Part_Name);
+ missingFields.Add(DatItemField.Part_Interface);
+ }
+ else
+ {
+ if (string.IsNullOrWhiteSpace(rom.Part.Name))
+ missingFields.Add(DatItemField.Part_Name);
+ if (string.IsNullOrWhiteSpace(rom.Part.Interface))
+ missingFields.Add(DatItemField.Part_Interface);
+ }
+ if (!rom.DataAreaSpecified)
+ {
+ missingFields.Add(DatItemField.AreaName);
+ missingFields.Add(DatItemField.AreaSize);
+ }
+ else
+ {
+ if (string.IsNullOrWhiteSpace(rom.DataArea.Name))
+ missingFields.Add(DatItemField.AreaName);
+ if (!rom.DataArea.SizeSpecified)
+ missingFields.Add(DatItemField.AreaSize);
+ }
+ break;
+ case ItemType.SharedFeature:
+ SharedFeature sharedFeature = datItem as SharedFeature;
+ if (string.IsNullOrWhiteSpace(sharedFeature.Name))
+ missingFields.Add(DatItemField.Name);
+ break;
+ default:
+ // Unsupported ItemTypes should be caught already
+ return null;
+ }
+
+ return missingFields;
+ }
+
+ ///
+ 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("softwarelist", null, "softwarelist.dtd", null);
+
+ xtw.WriteStartElement("softwarelist");
+ xtw.WriteRequiredAttributeString("name", Header.Name);
+ xtw.WriteRequiredAttributeString("description", Header.Description);
+
+ 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.WriteRequiredAttributeString("name", datItem.Machine.Name);
+ if (!string.Equals(datItem.Machine.Name, datItem.Machine.CloneOf, StringComparison.OrdinalIgnoreCase))
+ xtw.WriteOptionalAttributeString("cloneof", datItem.Machine.CloneOf);
+ xtw.WriteOptionalAttributeString("supported", datItem.Machine.Supported.FromSupported(false));
+
+ xtw.WriteOptionalElementString("description", datItem.Machine.Description);
+ xtw.WriteOptionalElementString("year", datItem.Machine.Year);
+ xtw.WriteOptionalElementString("publisher", datItem.Machine.Publisher);
+
+ 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.DipSwitch:
+ var dipSwitch = datItem as DipSwitch;
+ xtw.WriteStartElement("dipswitch");
+ xtw.WriteRequiredAttributeString("name", dipSwitch.Name);
+ xtw.WriteRequiredAttributeString("tag", dipSwitch.Tag);
+ xtw.WriteRequiredAttributeString("mask", dipSwitch.Mask);
+ if (dipSwitch.ValuesSpecified)
+ {
+ foreach (Setting dipValue in dipSwitch.Values)
+ {
+ xtw.WriteStartElement("dipvalue");
+ xtw.WriteRequiredAttributeString("name", dipValue.Name);
+ xtw.WriteRequiredAttributeString("value", dipValue.Value);
+ xtw.WriteOptionalAttributeString("default", dipValue.Default.FromYesNo());
+ xtw.WriteEndElement();
+ }
+ }
+ xtw.WriteEndElement();
+ break;
+
+ case ItemType.Disk:
+ var disk = datItem as Disk;
+ string diskAreaName = disk.DiskArea?.Name;
+ if (string.IsNullOrWhiteSpace(diskAreaName))
+ diskAreaName = "cdrom";
+
+ xtw.WriteStartElement("part");
+ xtw.WriteRequiredAttributeString("name", disk.Part?.Name);
+ xtw.WriteRequiredAttributeString("interface", disk.Part?.Interface);
+
+ if (disk.Part?.FeaturesSpecified == true)
+ {
+ foreach (PartFeature partFeature in disk.Part.Features)
+ {
+ xtw.WriteStartElement("feature");
+ xtw.WriteRequiredAttributeString("name", partFeature.Name);
+ xtw.WriteOptionalAttributeString("value", partFeature.Value);
+ xtw.WriteEndElement();
+ }
+ }
+
+ xtw.WriteStartElement("diskarea");
+ xtw.WriteRequiredAttributeString("name", diskAreaName);
+
+ 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.WriteOptionalAttributeString("writable", disk.Writable.FromYesNo());
+ xtw.WriteEndElement();
+
+ // End diskarea
+ xtw.WriteEndElement();
+
+ // End part
+ xtw.WriteEndElement();
+ break;
+
+ case ItemType.Info:
+ var info = datItem as Info;
+ xtw.WriteStartElement("info");
+ xtw.WriteRequiredAttributeString("name", info.Name);
+ xtw.WriteOptionalAttributeString("value", info.Value);
+ xtw.WriteEndElement();
+ break;
+
+ case ItemType.Rom:
+ var rom = datItem as Rom;
+ string dataAreaName = rom.DataArea?.Name;
+ if (string.IsNullOrWhiteSpace(dataAreaName))
+ dataAreaName = "rom";
+
+ xtw.WriteStartElement("part");
+ xtw.WriteRequiredAttributeString("name", rom.Part?.Name);
+ xtw.WriteRequiredAttributeString("interface", rom.Part?.Interface);
+
+ if (rom.Part?.FeaturesSpecified == true)
+ {
+ foreach (PartFeature kvp in rom.Part.Features)
+ {
+ xtw.WriteStartElement("feature");
+ xtw.WriteRequiredAttributeString("name", kvp.Name);
+ xtw.WriteOptionalAttributeString("value", kvp.Value);
+ xtw.WriteEndElement();
+ }
+ }
+
+ xtw.WriteStartElement("dataarea");
+ xtw.WriteOptionalAttributeString("name", dataAreaName);
+ xtw.WriteOptionalAttributeString("size", rom.DataArea?.Size.ToString());
+ xtw.WriteOptionalAttributeString("width", rom.DataArea?.Width?.ToString());
+ xtw.WriteOptionalAttributeString("endianness", rom.DataArea?.Endianness.FromEndianness());
+
+ xtw.WriteStartElement("rom");
+ xtw.WriteRequiredAttributeString("name", rom.Name);
+ xtw.WriteOptionalAttributeString("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("offset", rom.Offset);
+ xtw.WriteOptionalAttributeString("value", rom.Value);
+ if (rom.ItemStatus != ItemStatus.None)
+ xtw.WriteOptionalAttributeString("status", rom.ItemStatus.FromItemStatus(false));
+ xtw.WriteOptionalAttributeString("loadflag", rom.LoadFlag.FromLoadFlag());
+ xtw.WriteEndElement();
+
+ // End dataarea
+ xtw.WriteEndElement();
+
+ // End part
+ xtw.WriteEndElement();
+ break;
+
+ case ItemType.SharedFeature:
+ var sharedFeature = datItem as SharedFeature;
+ xtw.WriteStartElement("sharedfeat");
+ xtw.WriteRequiredAttributeString("name", sharedFeature.Name);
+ xtw.WriteOptionalAttributeString("value", sharedFeature.Value);
+ 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 softwarelist
+ xtw.WriteEndElement();
+
+ xtw.Flush();
+ }
+ }
+}
diff --git a/SabreTools.DatFiles/Formats/SoftwareList.cs b/SabreTools.DatFiles/Formats/SoftwareList.cs
index 949663a0..a984bae4 100644
--- a/SabreTools.DatFiles/Formats/SoftwareList.cs
+++ b/SabreTools.DatFiles/Formats/SoftwareList.cs
@@ -1,17 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-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;
-
-// TODO: Use softwarelist.dtd and *try* to make this write more correctly
+// TODO: Use softwarelist.dtd and *try* to make this write more correctly
namespace SabreTools.DatFiles.Formats
{
///
@@ -20,7 +7,7 @@ namespace SabreTools.DatFiles.Formats
///
/// TODO: Check and enforce required fields in output
///
- internal class SoftwareList : DatFile
+ internal partial class SoftwareList : DatFile
{
///
/// DTD for original MAME Software List DATs
@@ -91,893 +78,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 "softwarelist":
- Header.Name ??= xtr.GetAttribute("name") ?? string.Empty;
- Header.Description ??= xtr.GetAttribute("description") ?? string.Empty;
-
- xtr.Read();
- break;
-
- // We want to process the entire subtree of the machine
- 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 software 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 software, skip it
- if (reader == null)
- return;
-
- // Otherwise, add what is possible
- reader.MoveToContent();
-
- bool containsItems = false;
-
- // Create a new machine
- Machine machine = new()
- {
- Name = reader.GetAttribute("name"),
- CloneOf = reader.GetAttribute("cloneof"),
- Supported = reader.GetAttribute("supported").AsSupported(),
- };
-
- while (!reader.EOF)
- {
- // We only want elements
- if (reader.NodeType != XmlNodeType.Element)
- {
- reader.Read();
- continue;
- }
-
- // Get the elements from the software
- switch (reader.Name)
- {
- case "description":
- machine.Description = reader.ReadElementContentAsString();
- break;
-
- case "year":
- machine.Year = reader.ReadElementContentAsString();
- break;
-
- case "publisher":
- machine.Publisher = reader.ReadElementContentAsString();
- break;
-
- case "info":
- ParseAddHelper(new Info
- {
- Name = reader.GetAttribute("name"),
- Value = reader.GetAttribute("value"),
-
- Source = new Source
- {
- Index = indexId,
- Name = filename,
- },
- }, statsOnly);
-
- reader.Read();
- break;
-
- case "sharedfeat":
- ParseAddHelper(new SharedFeature
- {
- Name = reader.GetAttribute("name"),
- Value = reader.GetAttribute("value"),
-
- Source = new Source
- {
- Index = indexId,
- Name = filename,
- },
- }, statsOnly);
-
- reader.Read();
- break;
-
- case "part": // Contains all rom and disk information
- var part = new Part()
- {
- Name = reader.GetAttribute("name"),
- Interface = reader.GetAttribute("interface"),
- };
-
- // Now read the internal tags
- containsItems = ReadPart(reader.ReadSubtree(), machine, part, statsOnly, filename, indexId);
-
- // Skip the part 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 part information
- ///
- /// XmlReader representing a part block
- /// Machine information to pass to contained items
- /// Part information to pass to contained items
- /// True to only add item statistics while parsing, false otherwise
- /// Name of the file to be parsed
- /// Index ID for the DAT
- private bool ReadPart(XmlReader reader, Machine machine, Part part, bool statsOnly, string filename, int indexId)
- {
- // If we have an empty port, skip it
- if (reader == null)
- return false;
-
- // Get lists ready
- part.Features = new List();
- List items = new();
-
- while (!reader.EOF)
- {
- // We only want elements
- if (reader.NodeType != XmlNodeType.Element)
- {
- reader.Read();
- continue;
- }
-
- // Get the elements from the software
- switch (reader.Name)
- {
- case "feature":
- var feature = new PartFeature()
- {
- Name = reader.GetAttribute("name"),
- Value = reader.GetAttribute("value"),
- };
-
- part.Features.Add(feature);
-
- reader.Read();
- break;
-
- case "dataarea":
- var dataArea = new DataArea
- {
- Name = reader.GetAttribute("name"),
- Size = Utilities.CleanLong(reader.GetAttribute("size")),
- Width = Utilities.CleanLong(reader.GetAttribute("width")),
- Endianness = reader.GetAttribute("endianness").AsEndianness(),
- };
-
- List roms = ReadDataArea(reader.ReadSubtree(), dataArea);
-
- // If we got valid roms, add them to the list
- if (roms != null)
- items.AddRange(roms);
-
- // Skip the dataarea now that we've processed it
- reader.Skip();
- break;
-
- case "diskarea":
- var diskArea = new DiskArea
- {
- Name = reader.GetAttribute("name"),
- };
-
- List disks = ReadDiskArea(reader.ReadSubtree(), diskArea);
-
- // If we got valid disks, add them to the list
- if (disks != null)
- items.AddRange(disks);
-
- // Skip the diskarea now that we've processed it
- reader.Skip();
- break;
-
- case "dipswitch":
- var dipSwitch = new DipSwitch
- {
- Name = reader.GetAttribute("name"),
- Tag = reader.GetAttribute("tag"),
- Mask = reader.GetAttribute("mask"),
- };
-
- // Now read the internal tags
- ReadDipSwitch(reader.ReadSubtree(), dipSwitch);
-
- items.Add(dipSwitch);
-
- // Skip the dipswitch now that we've processed it
- reader.Skip();
- break;
-
- default:
- reader.Read();
- break;
- }
- }
-
- // Loop over all of the items, if they exist
- string key = string.Empty;
- foreach (DatItem item in items)
- {
- // Add all missing information
- switch (item.ItemType)
- {
- case ItemType.DipSwitch:
- (item as DipSwitch).Part = part;
- break;
- case ItemType.Disk:
- (item as Disk).Part = part;
- break;
- case ItemType.Rom:
- (item as Rom).Part = part;
-
- // If the rom is continue or ignore, add the size to the previous rom
- // TODO: Can this be done on write? We technically lose information this way.
- // Order is not guaranteed, and since these don't tend to have any way
- // of determining what the "previous" item was after this, that info would
- // have to be stored *with* the item somehow
- if ((item as Rom).LoadFlag == LoadFlag.Continue || (item as Rom).LoadFlag == LoadFlag.Ignore)
- {
- int index = Items[key].Count - 1;
- DatItem lastrom = Items[key][index];
- if (lastrom.ItemType == ItemType.Rom)
- {
- (lastrom as Rom).Size += (item as Rom).Size;
- Items[key].RemoveAt(index);
- Items[key].Add(lastrom);
- }
-
- continue;
- }
-
- break;
- }
-
- item.Source = new Source(indexId, filename);
- item.CopyMachineInformation(machine);
-
- // Finally add each item
- key = ParseAddHelper(item, statsOnly);
- }
-
- return items.Any();
- }
-
- ///
- /// Read dataarea information
- ///
- /// XmlReader representing a dataarea block
- /// DataArea representing the enclosing area
- private List ReadDataArea(XmlReader reader, DataArea dataArea)
- {
- List items = new();
-
- while (!reader.EOF)
- {
- // We only want elements
- if (reader.NodeType != XmlNodeType.Element)
- {
- reader.Read();
- continue;
- }
-
- // Get the elements from the software
- switch (reader.Name)
- {
- case "rom":
- var rom = new Rom
- {
- Name = reader.GetAttribute("name"),
- Size = Utilities.CleanLong(reader.GetAttribute("size")),
- CRC = reader.GetAttribute("crc"),
- SHA1 = reader.GetAttribute("sha1"),
- Offset = reader.GetAttribute("offset"),
- Value = reader.GetAttribute("value"),
- ItemStatus = reader.GetAttribute("status").AsItemStatus(),
- LoadFlag = reader.GetAttribute("loadflag").AsLoadFlag(),
-
- DataArea = dataArea,
- };
-
- items.Add(rom);
- reader.Read();
- break;
-
- default:
- reader.Read();
- break;
- }
- }
-
- return items;
- }
-
- ///
- /// Read diskarea information
- ///
- /// XmlReader representing a diskarea block
- /// DiskArea representing the enclosing area
- private List ReadDiskArea(XmlReader reader, DiskArea diskArea)
- {
- List items = new();
-
- while (!reader.EOF)
- {
- // We only want elements
- if (reader.NodeType != XmlNodeType.Element)
- {
- reader.Read();
- continue;
- }
-
- // Get the elements from the software
- switch (reader.Name)
- {
- case "disk":
- DatItem disk = new Disk
- {
- Name = reader.GetAttribute("name"),
- SHA1 = reader.GetAttribute("sha1"),
- ItemStatus = reader.GetAttribute("status").AsItemStatus(),
- Writable = reader.GetAttribute("writable").AsYesNo(),
-
- DiskArea = diskArea,
- };
-
- items.Add(disk);
- reader.Read();
- break;
-
- default:
- reader.Read();
- break;
- }
- }
-
- return items;
- }
-
- ///
- /// Read DipSwitch DipValues information
- ///
- /// XmlReader representing a diskarea block
- /// DipSwitch to populate
- private void ReadDipSwitch(XmlReader reader, DipSwitch dipSwitch)
- {
- // If we have an empty dipswitch, skip it
- if (reader == null)
- return;
-
- // Get list ready
- dipSwitch.Values = new List();
-
- // 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 dipswitch
- switch (reader.Name)
- {
- case "dipvalue":
- var dipValue = new Setting
- {
- Name = reader.GetAttribute("name"),
- Value = reader.GetAttribute("value"),
- Default = reader.GetAttribute("default").AsYesNo(),
- };
-
- dipSwitch.Values.Add(dipValue);
-
- reader.Read();
- break;
-
- default:
- reader.Read();
- break;
- }
- }
- }
-
- ///
- protected override ItemType[] GetSupportedTypes()
- {
- return new ItemType[]
- {
- ItemType.DipSwitch,
- ItemType.Disk,
- ItemType.Info,
- ItemType.Rom,
- ItemType.SharedFeature,
- };
- }
-
- ///
- protected override List GetMissingRequiredFields(DatItem datItem)
- {
- List missingFields = new();
-
- switch (datItem.ItemType)
- {
- case ItemType.DipSwitch:
- DipSwitch dipSwitch = datItem as DipSwitch;
- if (!dipSwitch.PartSpecified)
- {
- missingFields.Add(DatItemField.Part_Name);
- missingFields.Add(DatItemField.Part_Interface);
- }
- else
- {
- if (string.IsNullOrWhiteSpace(dipSwitch.Part.Name))
- missingFields.Add(DatItemField.Part_Name);
- if (string.IsNullOrWhiteSpace(dipSwitch.Part.Interface))
- missingFields.Add(DatItemField.Part_Interface);
- }
- if (string.IsNullOrWhiteSpace(dipSwitch.Name))
- missingFields.Add(DatItemField.Name);
- if (string.IsNullOrWhiteSpace(dipSwitch.Tag))
- missingFields.Add(DatItemField.Tag);
- if (string.IsNullOrWhiteSpace(dipSwitch.Mask))
- missingFields.Add(DatItemField.Mask);
- if (dipSwitch.ValuesSpecified)
- {
- if (dipSwitch.Values.Any(dv => string.IsNullOrWhiteSpace(dv.Name)))
- missingFields.Add(DatItemField.Part_Feature_Name);
- if (dipSwitch.Values.Any(dv => string.IsNullOrWhiteSpace(dv.Value)))
- missingFields.Add(DatItemField.Part_Feature_Value);
- }
-
- break;
- case ItemType.Disk:
- Disk disk = datItem as Disk;
- if (!disk.PartSpecified)
- {
- missingFields.Add(DatItemField.Part_Name);
- missingFields.Add(DatItemField.Part_Interface);
- }
- else
- {
- if (string.IsNullOrWhiteSpace(disk.Part.Name))
- missingFields.Add(DatItemField.Part_Name);
- if (string.IsNullOrWhiteSpace(disk.Part.Interface))
- missingFields.Add(DatItemField.Part_Interface);
- }
- if (!disk.DiskAreaSpecified)
- {
- missingFields.Add(DatItemField.AreaName);
- }
- else
- {
- if (string.IsNullOrWhiteSpace(disk.DiskArea.Name))
- missingFields.Add(DatItemField.AreaName);
- }
- if (string.IsNullOrWhiteSpace(disk.Name))
- missingFields.Add(DatItemField.Name);
- break;
- case ItemType.Info:
- Info info = datItem as Info;
- if (string.IsNullOrWhiteSpace(info.Name))
- missingFields.Add(DatItemField.Name);
- break;
- case ItemType.Rom:
- Rom rom = datItem as Rom;
- if (!rom.PartSpecified)
- {
- missingFields.Add(DatItemField.Part_Name);
- missingFields.Add(DatItemField.Part_Interface);
- }
- else
- {
- if (string.IsNullOrWhiteSpace(rom.Part.Name))
- missingFields.Add(DatItemField.Part_Name);
- if (string.IsNullOrWhiteSpace(rom.Part.Interface))
- missingFields.Add(DatItemField.Part_Interface);
- }
- if (!rom.DataAreaSpecified)
- {
- missingFields.Add(DatItemField.AreaName);
- missingFields.Add(DatItemField.AreaSize);
- }
- else
- {
- if (string.IsNullOrWhiteSpace(rom.DataArea.Name))
- missingFields.Add(DatItemField.AreaName);
- if (!rom.DataArea.SizeSpecified)
- missingFields.Add(DatItemField.AreaSize);
- }
- break;
- case ItemType.SharedFeature:
- SharedFeature sharedFeature = datItem as SharedFeature;
- if (string.IsNullOrWhiteSpace(sharedFeature.Name))
- missingFields.Add(DatItemField.Name);
- break;
- default:
- // Unsupported ItemTypes should be caught already
- return null;
- }
-
- return missingFields;
- }
-
- ///
- 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("softwarelist", null, "softwarelist.dtd", null);
-
- xtw.WriteStartElement("softwarelist");
- xtw.WriteRequiredAttributeString("name", Header.Name);
- xtw.WriteRequiredAttributeString("description", Header.Description);
-
- 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.WriteRequiredAttributeString("name", datItem.Machine.Name);
- if (!string.Equals(datItem.Machine.Name, datItem.Machine.CloneOf, StringComparison.OrdinalIgnoreCase))
- xtw.WriteOptionalAttributeString("cloneof", datItem.Machine.CloneOf);
- xtw.WriteOptionalAttributeString("supported", datItem.Machine.Supported.FromSupported(false));
-
- xtw.WriteOptionalElementString("description", datItem.Machine.Description);
- xtw.WriteOptionalElementString("year", datItem.Machine.Year);
- xtw.WriteOptionalElementString("publisher", datItem.Machine.Publisher);
-
- 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.DipSwitch:
- var dipSwitch = datItem as DipSwitch;
- xtw.WriteStartElement("dipswitch");
- xtw.WriteRequiredAttributeString("name", dipSwitch.Name);
- xtw.WriteRequiredAttributeString("tag", dipSwitch.Tag);
- xtw.WriteRequiredAttributeString("mask", dipSwitch.Mask);
- if (dipSwitch.ValuesSpecified)
- {
- foreach (Setting dipValue in dipSwitch.Values)
- {
- xtw.WriteStartElement("dipvalue");
- xtw.WriteRequiredAttributeString("name", dipValue.Name);
- xtw.WriteRequiredAttributeString("value", dipValue.Value);
- xtw.WriteOptionalAttributeString("default", dipValue.Default.FromYesNo());
- xtw.WriteEndElement();
- }
- }
- xtw.WriteEndElement();
- break;
-
- case ItemType.Disk:
- var disk = datItem as Disk;
- string diskAreaName = disk.DiskArea?.Name;
- if (string.IsNullOrWhiteSpace(diskAreaName))
- diskAreaName = "cdrom";
-
- xtw.WriteStartElement("part");
- xtw.WriteRequiredAttributeString("name", disk.Part?.Name);
- xtw.WriteRequiredAttributeString("interface", disk.Part?.Interface);
-
- if (disk.Part?.FeaturesSpecified == true)
- {
- foreach (PartFeature partFeature in disk.Part.Features)
- {
- xtw.WriteStartElement("feature");
- xtw.WriteRequiredAttributeString("name", partFeature.Name);
- xtw.WriteOptionalAttributeString("value", partFeature.Value);
- xtw.WriteEndElement();
- }
- }
-
- xtw.WriteStartElement("diskarea");
- xtw.WriteRequiredAttributeString("name", diskAreaName);
-
- 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.WriteOptionalAttributeString("writable", disk.Writable.FromYesNo());
- xtw.WriteEndElement();
-
- // End diskarea
- xtw.WriteEndElement();
-
- // End part
- xtw.WriteEndElement();
- break;
-
- case ItemType.Info:
- var info = datItem as Info;
- xtw.WriteStartElement("info");
- xtw.WriteRequiredAttributeString("name", info.Name);
- xtw.WriteOptionalAttributeString("value", info.Value);
- xtw.WriteEndElement();
- break;
-
- case ItemType.Rom:
- var rom = datItem as Rom;
- string dataAreaName = rom.DataArea?.Name;
- if (string.IsNullOrWhiteSpace(dataAreaName))
- dataAreaName = "rom";
-
- xtw.WriteStartElement("part");
- xtw.WriteRequiredAttributeString("name", rom.Part?.Name);
- xtw.WriteRequiredAttributeString("interface", rom.Part?.Interface);
-
- if (rom.Part?.FeaturesSpecified == true)
- {
- foreach (PartFeature kvp in rom.Part.Features)
- {
- xtw.WriteStartElement("feature");
- xtw.WriteRequiredAttributeString("name", kvp.Name);
- xtw.WriteOptionalAttributeString("value", kvp.Value);
- xtw.WriteEndElement();
- }
- }
-
- xtw.WriteStartElement("dataarea");
- xtw.WriteOptionalAttributeString("name", dataAreaName);
- xtw.WriteOptionalAttributeString("size", rom.DataArea?.Size.ToString());
- xtw.WriteOptionalAttributeString("width", rom.DataArea?.Width?.ToString());
- xtw.WriteOptionalAttributeString("endianness", rom.DataArea?.Endianness.FromEndianness());
-
- xtw.WriteStartElement("rom");
- xtw.WriteRequiredAttributeString("name", rom.Name);
- xtw.WriteOptionalAttributeString("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("offset", rom.Offset);
- xtw.WriteOptionalAttributeString("value", rom.Value);
- if (rom.ItemStatus != ItemStatus.None)
- xtw.WriteOptionalAttributeString("status", rom.ItemStatus.FromItemStatus(false));
- xtw.WriteOptionalAttributeString("loadflag", rom.LoadFlag.FromLoadFlag());
- xtw.WriteEndElement();
-
- // End dataarea
- xtw.WriteEndElement();
-
- // End part
- xtw.WriteEndElement();
- break;
-
- case ItemType.SharedFeature:
- var sharedFeature = datItem as SharedFeature;
- xtw.WriteStartElement("sharedfeat");
- xtw.WriteRequiredAttributeString("name", sharedFeature.Name);
- xtw.WriteOptionalAttributeString("value", sharedFeature.Value);
- 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 softwarelist
- xtw.WriteEndElement();
-
- xtw.Flush();
- }
}
}