diff --git a/SabreTools.DatFiles/Formats/Listxml.Reader.cs b/SabreTools.DatFiles/Formats/Listxml.Reader.cs new file mode 100644 index 00000000..3552c656 --- /dev/null +++ b/SabreTools.DatFiles/Formats/Listxml.Reader.cs @@ -0,0 +1,1145 @@ +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 MAME XML DAT + /// + internal partial class Listxml : 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 "mame": + Header.Name ??= xtr.GetAttribute("build"); + Header.Description ??= Header.Name; + Header.Debug ??= xtr.GetAttribute("debug").AsYesNo(); + Header.MameConfig ??= xtr.GetAttribute("mameconfig"); + xtr.Read(); + break; + + // Handle M1 DATs since they're 99% the same as a SL DAT + case "m1": + Header.Name ??= "M1"; + Header.Description ??= "M1"; + Header.Version ??= xtr.GetAttribute("version") ?? string.Empty; + xtr.Read(); + break; + + // We want to process the entire subtree of the machine + case "game": // Some older DATs still use "game" + case "machine": + ReadMachine(xtr.ReadSubtree(), statsOnly, filename, indexId); + + // 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 machine 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 ReadMachine(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(); + + // Create a new machine + MachineType machineType = 0x0; + if (reader.GetAttribute("isbios").AsYesNo() == true) + machineType |= MachineType.Bios; + + if (reader.GetAttribute("isdevice").AsYesNo() == true) + machineType |= MachineType.Device; + + if (reader.GetAttribute("ismechanical").AsYesNo() == true) + machineType |= MachineType.Mechanical; + + Machine machine = new() + { + Name = reader.GetAttribute("name"), + Description = reader.GetAttribute("name"), + CloneOf = reader.GetAttribute("cloneof"), + RomOf = reader.GetAttribute("romof"), + SampleOf = reader.GetAttribute("sampleof"), + MachineType = (machineType == 0x0 ? MachineType.None : machineType), + + SourceFile = reader.GetAttribute("sourcefile"), + Runnable = reader.GetAttribute("runnable").AsRunnable(), + }; + + // Get list for new DatItems + List datItems = 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 "description": + machine.Description = reader.ReadElementContentAsString(); + break; + + case "year": + machine.Year = reader.ReadElementContentAsString(); + break; + + case "manufacturer": + machine.Manufacturer = reader.ReadElementContentAsString(); + break; + + case "history": + machine.History = reader.ReadElementContentAsString(); + break; + + case "adjuster": + var adjuster = new Adjuster + { + Name = reader.GetAttribute("name"), + Default = reader.GetAttribute("default").AsYesNo(), + Conditions = new List(), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + // Now read the internal tags + ReadAdjuster(reader.ReadSubtree(), adjuster); + + datItems.Add(adjuster); + + // Skip the adjuster now that we've processed it + reader.Skip(); + break; + + case "biosset": + datItems.Add(new BiosSet + { + Name = reader.GetAttribute("name"), + Description = reader.GetAttribute("description"), + Default = reader.GetAttribute("default").AsYesNo(), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }); + + reader.Read(); + break; + + case "chip": + var chip = new Chip + { + Name = reader.GetAttribute("name"), + Tag = reader.GetAttribute("tag"), + ChipType = reader.GetAttribute("type").AsChipType(), + Clock = Utilities.CleanLong(reader.GetAttribute("clock")), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + datItems.Add(chip); + + reader.Read(); + break; + + case "condition": + datItems.Add(new Condition + { + Tag = reader.GetAttribute("tag"), + Mask = reader.GetAttribute("mask"), + Relation = reader.GetAttribute("relation").AsRelation(), + Value = reader.GetAttribute("value"), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }); + + reader.Read(); + break; + + case "configuration": + var configuration = new Configuration + { + Name = reader.GetAttribute("name"), + Tag = reader.GetAttribute("tag"), + Mask = reader.GetAttribute("mask"), + Conditions = new List(), + Locations = new List(), + Settings = new List(), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + // Now read the internal tags + ReadConfiguration(reader.ReadSubtree(), configuration); + + datItems.Add(configuration); + + // Skip the configuration now that we've processed it + reader.Skip(); + break; + + case "device": + var device = new Device + { + DeviceType = reader.GetAttribute("type").AsDeviceType(), + Tag = reader.GetAttribute("tag"), + FixedImage = reader.GetAttribute("fixed_image"), + Mandatory = Utilities.CleanLong(reader.GetAttribute("mandatory")), + Interface = reader.GetAttribute("interface"), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + // Now read the internal tags + ReadDevice(reader.ReadSubtree(), device); + + datItems.Add(device); + + // Skip the device now that we've processed it + reader.Skip(); + break; + + case "device_ref": + datItems.Add(new DeviceReference + { + Name = reader.GetAttribute("name"), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }); + + reader.Read(); + break; + + case "dipswitch": + var dipSwitch = new DipSwitch + { + Name = reader.GetAttribute("name"), + Tag = reader.GetAttribute("tag"), + Mask = reader.GetAttribute("mask"), + Conditions = new List(), + Locations = new List(), + Values = new List(), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + // Now read the internal tags + ReadDipSwitch(reader.ReadSubtree(), dipSwitch); + + datItems.Add(dipSwitch); + + // Skip the dipswitch now that we've processed it + reader.Skip(); + break; + + case "disk": + datItems.Add(new Disk + { + Name = reader.GetAttribute("name"), + SHA1 = reader.GetAttribute("sha1"), + MergeTag = reader.GetAttribute("merge"), + Region = reader.GetAttribute("region"), + Index = reader.GetAttribute("index"), + Writable = reader.GetAttribute("writable").AsYesNo(), + ItemStatus = reader.GetAttribute("status").AsItemStatus(), + Optional = reader.GetAttribute("optional").AsYesNo(), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }); + + reader.Read(); + break; + + case "display": + var display = new Display + { + Tag = reader.GetAttribute("tag"), + DisplayType = reader.GetAttribute("type").AsDisplayType(), + Rotate = Utilities.CleanLong(reader.GetAttribute("rotate")), + FlipX = reader.GetAttribute("flipx").AsYesNo(), + Width = Utilities.CleanLong(reader.GetAttribute("width")), + Height = Utilities.CleanLong(reader.GetAttribute("height")), + Refresh = Utilities.CleanDouble(reader.GetAttribute("refresh")), + PixClock = Utilities.CleanLong(reader.GetAttribute("pixclock")), + HTotal = Utilities.CleanLong(reader.GetAttribute("htotal")), + HBEnd = Utilities.CleanLong(reader.GetAttribute("hbend")), + HBStart = Utilities.CleanLong(reader.GetAttribute("hbstart")), + VTotal = Utilities.CleanLong(reader.GetAttribute("vtotal")), + VBEnd = Utilities.CleanLong(reader.GetAttribute("vbend")), + VBStart = Utilities.CleanLong(reader.GetAttribute("vbstart")), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + datItems.Add(display); + + reader.Read(); + break; + + case "driver": + datItems.Add(new Driver + { + Status = reader.GetAttribute("status").AsSupportStatus(), + Emulation = reader.GetAttribute("emulation").AsSupportStatus(), + Cocktail = reader.GetAttribute("cocktail").AsSupportStatus(), + SaveState = reader.GetAttribute("savestate").AsSupported(), + RequiresArtwork = reader.GetAttribute("requiresartwork").AsYesNo(), + Unofficial = reader.GetAttribute("unofficial").AsYesNo(), + NoSoundHardware = reader.GetAttribute("nosoundhardware").AsYesNo(), + Incomplete = reader.GetAttribute("incomplete").AsYesNo(), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }); + + reader.Read(); + break; + + case "feature": + datItems.Add(new Feature + { + Type = reader.GetAttribute("type").AsFeatureType(), + Status = reader.GetAttribute("status").AsFeatureStatus(), + Overall = reader.GetAttribute("overall").AsFeatureStatus(), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }); + + reader.Read(); + break; + + case "input": + var input = new Input + { + Service = reader.GetAttribute("service").AsYesNo(), + Tilt = reader.GetAttribute("tilt").AsYesNo(), + Players = Utilities.CleanLong(reader.GetAttribute("players")), + Coins = Utilities.CleanLong(reader.GetAttribute("coins")), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + // Now read the internal tags + ReadInput(reader.ReadSubtree(), input); + + datItems.Add(input); + + // Skip the input now that we've processed it + reader.Skip(); + break; + + case "port": + var port = new Port + { + Tag = reader.GetAttribute("tag"), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + // Now read the internal tags + ReadPort(reader.ReadSubtree(), port); + + datItems.Add(port); + + // Skip the port now that we've processed it + reader.Skip(); + break; + + case "ramoption": + datItems.Add(new RamOption + { + Name = reader.GetAttribute("name"), + Default = reader.GetAttribute("default").AsYesNo(), + Content = reader.ReadElementContentAsString(), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }); + + break; + + case "rom": + datItems.Add(new Rom + { + Name = reader.GetAttribute("name"), + Bios = reader.GetAttribute("bios"), + Size = Utilities.CleanLong(reader.GetAttribute("size")), + CRC = reader.GetAttribute("crc"), + SHA1 = reader.GetAttribute("sha1"), + MergeTag = reader.GetAttribute("merge"), + Region = reader.GetAttribute("region"), + Offset = reader.GetAttribute("offset"), + ItemStatus = reader.GetAttribute("status").AsItemStatus(), + Optional = reader.GetAttribute("optional").AsYesNo(), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }); + + reader.Read(); + break; + + case "sample": + datItems.Add(new Sample + { + Name = reader.GetAttribute("name"), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }); + + reader.Read(); + break; + + case "slot": + var slot = new Slot + { + Name = reader.GetAttribute("name"), + SlotOptions = new List(), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + // Now read the internal tags + ReadSlot(reader.ReadSubtree(), slot); + + datItems.Add(slot); + + // Skip the slot now that we've processed it + reader.Skip(); + break; + + case "softwarelist": + datItems.Add(new DatItems.Formats.SoftwareList + { + Tag = reader.GetAttribute("tag"), + Name = reader.GetAttribute("name"), + Status = reader.GetAttribute("status").AsSoftwareListStatus(), + Filter = reader.GetAttribute("filter"), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }); + + reader.Read(); + break; + + case "sound": + var sound = new Sound + { + Channels = Utilities.CleanLong(reader.GetAttribute("channels")), + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + datItems.Add(sound); + + reader.Read(); + break; + + default: + reader.Read(); + break; + } + } + + // If we found items, copy the machine info and add them + if (datItems.Any()) + { + foreach (DatItem datItem in datItems) + { + datItem.CopyMachineInformation(machine); + ParseAddHelper(datItem, statsOnly); + } + } + + // If no items were found for this machine, add a Blank placeholder + else + { + Blank blank = new() + { + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + blank.CopyMachineInformation(machine); + + // Now process and add the rom + ParseAddHelper(blank, statsOnly); + } + } + + /// + /// Read slot information + /// + /// XmlReader representing a machine block + /// ListXmlSlot to populate + private void ReadSlot(XmlReader reader, Slot slot) + { + // If we have an empty machine, skip it + if (reader == null) + return; + + // Get list ready + slot.SlotOptions = 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 roms from the machine + switch (reader.Name) + { + case "slotoption": + var slotOption = new SlotOption + { + Name = reader.GetAttribute("name"), + DeviceName = reader.GetAttribute("devname"), + Default = reader.GetAttribute("default").AsYesNo() + }; + + slot.SlotOptions.Add(slotOption); + + reader.Read(); + break; + + default: + reader.Read(); + break; + } + } + } + + /// + /// Read Input information + /// + /// XmlReader representing a diskarea block + /// ListXmlInput to populate + private void ReadInput(XmlReader reader, Input input) + { + // If we have an empty input, skip it + if (reader == null) + return; + + // Get list ready + input.Controls = 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 "control": + var control = new Control + { + ControlType = reader.GetAttribute("type").AsControlType(), + Player = Utilities.CleanLong(reader.GetAttribute("player")), + Buttons = Utilities.CleanLong(reader.GetAttribute("buttons")), + RequiredButtons = Utilities.CleanLong(reader.GetAttribute("reqbuttons")), + Minimum = Utilities.CleanLong(reader.GetAttribute("minimum")), + Maximum = Utilities.CleanLong(reader.GetAttribute("maximum")), + Sensitivity = Utilities.CleanLong(reader.GetAttribute("sensitivity")), + KeyDelta = Utilities.CleanLong(reader.GetAttribute("keydelta")), + Reverse = reader.GetAttribute("reverse").AsYesNo(), + Ways = reader.GetAttribute("ways"), + Ways2 = reader.GetAttribute("ways2"), + Ways3 = reader.GetAttribute("ways3"), + }; + + input.Controls.Add(control); + + reader.Read(); + break; + + default: + reader.Read(); + break; + } + } + } + + /// + /// Read DipSwitch 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 lists ready + dipSwitch.Conditions = new List(); + dipSwitch.Locations = new List(); + 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 "condition": + var condition = new Condition + { + Tag = reader.GetAttribute("tag"), + Mask = reader.GetAttribute("mask"), + Relation = reader.GetAttribute("relation").AsRelation(), + Value = reader.GetAttribute("value") + }; + + dipSwitch.Conditions.Add(condition); + + reader.Read(); + break; + + case "diplocation": + var dipLocation = new Location + { + Name = reader.GetAttribute("name"), + Number = Utilities.CleanLong(reader.GetAttribute("number")), + Inverted = reader.GetAttribute("inverted").AsYesNo() + }; + + dipSwitch.Locations.Add(dipLocation); + + reader.Read(); + break; + + case "dipvalue": + var dipValue = new Setting + { + Name = reader.GetAttribute("name"), + Value = reader.GetAttribute("value"), + Default = reader.GetAttribute("default").AsYesNo() + }; + + // Now read the internal tags + ReadDipValue(reader, dipValue); + + dipSwitch.Values.Add(dipValue); + + // Skip the dipvalue now that we've processed it + reader.Read(); + break; + + default: + reader.Read(); + break; + } + } + } + + /// + /// Read DipValue information + /// + /// XmlReader representing a diskarea block + /// Setting to populate + private void ReadDipValue(XmlReader reader, Setting dipValue) + { + // If we have an empty dipvalue, skip it + if (reader == null) + return; + + // Get list ready + dipValue.Conditions = 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 dipvalue + switch (reader.Name) + { + case "condition": + var condition = new Condition + { + Tag = reader.GetAttribute("tag"), + Mask = reader.GetAttribute("mask"), + Relation = reader.GetAttribute("relation").AsRelation(), + Value = reader.GetAttribute("value") + }; + + dipValue.Conditions.Add(condition); + + reader.Read(); + break; + + default: + reader.Read(); + break; + } + } + } + + /// + /// Read Configuration information + /// + /// XmlReader representing a diskarea block + /// Configuration to populate + private void ReadConfiguration(XmlReader reader, Configuration configuration) + { + // If we have an empty configuration, skip it + if (reader == null) + return; + + // Get lists ready + configuration.Conditions = new List(); + configuration.Locations = new List(); + configuration.Settings = 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 "condition": + var condition = new Condition + { + Tag = reader.GetAttribute("tag"), + Mask = reader.GetAttribute("mask"), + Relation = reader.GetAttribute("relation").AsRelation(), + Value = reader.GetAttribute("value") + }; + + configuration.Conditions.Add(condition); + + reader.Read(); + break; + + case "conflocation": + var confLocation = new Location + { + Name = reader.GetAttribute("name"), + Number = Utilities.CleanLong(reader.GetAttribute("number")), + Inverted = reader.GetAttribute("inverted").AsYesNo() + }; + + configuration.Locations.Add(confLocation); + + reader.Read(); + break; + + case "confsetting": + var confSetting = new Setting + { + Name = reader.GetAttribute("name"), + Value = reader.GetAttribute("value"), + Default = reader.GetAttribute("default").AsYesNo() + }; + + // Now read the internal tags + ReadConfSetting(reader, confSetting); + + configuration.Settings.Add(confSetting); + + // Skip the dipvalue now that we've processed it + reader.Read(); + break; + + default: + reader.Read(); + break; + } + } + } + + /// + /// Read ConfSetting information + /// + /// XmlReader representing a diskarea block + /// Setting to populate + private void ReadConfSetting(XmlReader reader, Setting confSetting) + { + // If we have an empty confsetting, skip it + if (reader == null) + return; + + // Get list ready + confSetting.Conditions = 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 confsetting + switch (reader.Name) + { + case "condition": + var condition = new Condition + { + Tag = reader.GetAttribute("tag"), + Mask = reader.GetAttribute("mask"), + Relation = reader.GetAttribute("relation").AsRelation(), + Value = reader.GetAttribute("value") + }; + + confSetting.Conditions.Add(condition); + + reader.Read(); + break; + + default: + reader.Read(); + break; + } + } + } + + /// + /// Read Port information + /// + /// XmlReader representing a diskarea block + /// ListXmlPort to populate + private void ReadPort(XmlReader reader, Port port) + { + // If we have an empty port, skip it + if (reader == null) + return; + + // Get list ready + port.Analogs = 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 port + switch (reader.Name) + { + case "analog": + var analog = new Analog + { + Mask = reader.GetAttribute("mask") + }; + + port.Analogs.Add(analog); + + reader.Read(); + break; + + default: + reader.Read(); + break; + } + } + } + + /// + /// Read Adjuster information + /// + /// XmlReader representing a diskarea block + /// Adjuster to populate + private void ReadAdjuster(XmlReader reader, Adjuster adjuster) + { + // If we have an empty port, skip it + if (reader == null) + return; + + // Get list ready + adjuster.Conditions = 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 adjuster + switch (reader.Name) + { + case "condition": + var condition = new Condition + { + Tag = reader.GetAttribute("tag"), + Mask = reader.GetAttribute("mask"), + Relation = reader.GetAttribute("relation").AsRelation(), + Value = reader.GetAttribute("value") + }; + + adjuster.Conditions.Add(condition); + + reader.Read(); + break; + + default: + reader.Read(); + break; + } + } + } + + /// + /// Read Device information + /// + /// XmlReader representing a diskarea block + /// ListXmlDevice to populate + private void ReadDevice(XmlReader reader, Device device) + { + // If we have an empty port, skip it + if (reader == null) + return; + + // Get lists ready + device.Instances = new List(); + device.Extensions = 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 adjuster + switch (reader.Name) + { + case "instance": + var instance = new Instance + { + Name = reader.GetAttribute("name"), + BriefName = reader.GetAttribute("briefname"), + }; + + device.Instances.Add(instance); + + reader.Read(); + break; + + case "extension": + var extension = new Extension + { + Name = reader.GetAttribute("name"), + }; + + device.Extensions.Add(extension); + + reader.Read(); + break; + + default: + reader.Read(); + break; + } + } + } + } +} diff --git a/SabreTools.DatFiles/Formats/Listxml.Writer.cs b/SabreTools.DatFiles/Formats/Listxml.Writer.cs new file mode 100644 index 00000000..fd987665 --- /dev/null +++ b/SabreTools.DatFiles/Formats/Listxml.Writer.cs @@ -0,0 +1,604 @@ +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 a MAME XML DAT + /// + internal partial class Listxml : DatFile + { + /// + protected override ItemType[] GetSupportedTypes() + { + return new ItemType[] + { + ItemType.Adjuster, + ItemType.BiosSet, + ItemType.Chip, + ItemType.Condition, + ItemType.Configuration, + ItemType.Device, + ItemType.DeviceReference, + ItemType.DipSwitch, + ItemType.Disk, + ItemType.Display, + ItemType.Driver, + ItemType.Feature, + ItemType.Input, + ItemType.Port, + ItemType.RamOption, + ItemType.Rom, + ItemType.Sample, + ItemType.Slot, + ItemType.SoftwareList, + ItemType.Sound, + }; + } + + /// + 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.WriteStartElement("mame"); + xtw.WriteRequiredAttributeString("build", Header.Name); + xtw.WriteOptionalAttributeString("debug", Header.Debug.FromYesNo()); + xtw.WriteOptionalAttributeString("mameconfig", Header.MameConfig); + + xtw.Flush(); + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// XmlTextWriter to output to + /// DatItem object to be output + /// True if the data was written, false on error + 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("machine"); + xtw.WriteRequiredAttributeString("name", datItem.Machine.Name); + xtw.WriteOptionalAttributeString("sourcefile", datItem.Machine.SourceFile); + + 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()); + + 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("description", datItem.Machine.Description); + xtw.WriteOptionalElementString("year", datItem.Machine.Year); + xtw.WriteOptionalElementString("manufacturer", datItem.Machine.Manufacturer); + xtw.WriteOptionalElementString("history", datItem.Machine.History); + + xtw.Flush(); + } + + /// + /// Write out Game start 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.Adjuster: + var adjuster = datItem as Adjuster; + xtw.WriteStartElement("adjuster"); + xtw.WriteRequiredAttributeString("name", adjuster.Name); + xtw.WriteRequiredAttributeString("default", adjuster.Default.FromYesNo()); + if (adjuster.ConditionsSpecified) + { + foreach (var adjusterCondition in adjuster.Conditions) + { + xtw.WriteStartElement("condition"); + xtw.WriteRequiredAttributeString("tag", adjusterCondition.Tag); + xtw.WriteRequiredAttributeString("mask", adjusterCondition.Mask); + xtw.WriteRequiredAttributeString("relation", adjusterCondition.Relation.FromRelation()); + xtw.WriteRequiredAttributeString("value", adjusterCondition.Value); + xtw.WriteEndElement(); + } + } + xtw.WriteEndElement(); + break; + + case ItemType.BiosSet: + var biosSet = datItem as BiosSet; + xtw.WriteStartElement("biosset"); + xtw.WriteRequiredAttributeString("name", biosSet.Name); + xtw.WriteRequiredAttributeString("description", biosSet.Description); + xtw.WriteOptionalAttributeString("default", biosSet.Default?.ToString().ToLowerInvariant()); + xtw.WriteEndElement(); + break; + + case ItemType.Chip: + var chip = datItem as Chip; + xtw.WriteStartElement("chip"); + xtw.WriteRequiredAttributeString("name", chip.Name); + xtw.WriteOptionalAttributeString("tag", chip.Tag); + xtw.WriteRequiredAttributeString("type", chip.ChipType.FromChipType()); + xtw.WriteOptionalAttributeString("clock", chip.Clock?.ToString()); + xtw.WriteEndElement(); + break; + + case ItemType.Condition: + var condition = datItem as Condition; + xtw.WriteStartElement("condition"); + xtw.WriteRequiredAttributeString("tag", condition.Tag); + xtw.WriteRequiredAttributeString("mask", condition.Mask); + xtw.WriteRequiredAttributeString("relation", condition.Relation.FromRelation()); + xtw.WriteRequiredAttributeString("value", condition.Value); + xtw.WriteEndElement(); + break; + + case ItemType.Configuration: + var configuration = datItem as Configuration; + xtw.WriteStartElement("configuration"); + xtw.WriteRequiredAttributeString("name", configuration.Name); + xtw.WriteRequiredAttributeString("tag", configuration.Tag); + xtw.WriteRequiredAttributeString("mask", configuration.Mask); + + if (configuration.ConditionsSpecified) + { + foreach (var configurationCondition in configuration.Conditions) + { + xtw.WriteStartElement("condition"); + xtw.WriteRequiredAttributeString("tag", configurationCondition.Tag); + xtw.WriteRequiredAttributeString("mask", configurationCondition.Mask); + xtw.WriteRequiredAttributeString("relation", configurationCondition.Relation.FromRelation()); + xtw.WriteRequiredAttributeString("value", configurationCondition.Value); + xtw.WriteEndElement(); + } + } + if (configuration.LocationsSpecified) + { + foreach (var location in configuration.Locations) + { + xtw.WriteStartElement("conflocation"); + xtw.WriteRequiredAttributeString("name", location.Name); + xtw.WriteRequiredAttributeString("number", location.Number?.ToString()); + xtw.WriteOptionalAttributeString("inverted", location.Inverted.FromYesNo()); + xtw.WriteEndElement(); + } + } + if (configuration.SettingsSpecified) + { + foreach (var setting in configuration.Settings) + { + xtw.WriteStartElement("confsetting"); + xtw.WriteRequiredAttributeString("name", setting.Name); + xtw.WriteRequiredAttributeString("value", setting.Value); + xtw.WriteOptionalAttributeString("default", setting.Default.FromYesNo()); + if (setting.ConditionsSpecified) + { + foreach (var confsettingCondition in setting.Conditions) + { + xtw.WriteStartElement("condition"); + xtw.WriteRequiredAttributeString("tag", confsettingCondition.Tag); + xtw.WriteRequiredAttributeString("mask", confsettingCondition.Mask); + xtw.WriteRequiredAttributeString("relation", confsettingCondition.Relation.FromRelation()); + xtw.WriteRequiredAttributeString("value", confsettingCondition.Value); + xtw.WriteEndElement(); + } + } + xtw.WriteEndElement(); + } + } + xtw.WriteEndElement(); + break; + + case ItemType.Device: + var device = datItem as Device; + xtw.WriteStartElement("device"); + xtw.WriteRequiredAttributeString("type", device.DeviceType.FromDeviceType()); + xtw.WriteOptionalAttributeString("tag", device.Tag); + xtw.WriteOptionalAttributeString("fixed_image", device.FixedImage); + xtw.WriteOptionalAttributeString("mandatory", device.Mandatory?.ToString()); + xtw.WriteOptionalAttributeString("interface", device.Interface); + if (device.InstancesSpecified) + { + foreach (var instance in device.Instances) + { + xtw.WriteStartElement("instance"); + xtw.WriteRequiredAttributeString("name", instance.Name); + xtw.WriteRequiredAttributeString("briefname", instance.BriefName); + xtw.WriteEndElement(); + } + } + if (device.ExtensionsSpecified) + { + foreach (var extension in device.Extensions) + { + xtw.WriteStartElement("extension"); + xtw.WriteRequiredAttributeString("name", extension.Name); + xtw.WriteEndElement(); + } + } + xtw.WriteEndElement(); + break; + + case ItemType.DeviceReference: + var deviceRef = datItem as DeviceReference; + xtw.WriteStartElement("device_ref"); + xtw.WriteRequiredAttributeString("name", deviceRef.Name); + xtw.WriteEndElement(); + break; + + 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.ConditionsSpecified) + { + foreach (var dipSwitchCondition in dipSwitch.Conditions) + { + xtw.WriteStartElement("condition"); + xtw.WriteRequiredAttributeString("tag", dipSwitchCondition.Tag); + xtw.WriteRequiredAttributeString("mask", dipSwitchCondition.Mask); + xtw.WriteRequiredAttributeString("relation", dipSwitchCondition.Relation.FromRelation()); + xtw.WriteRequiredAttributeString("value", dipSwitchCondition.Value); + xtw.WriteEndElement(); + } + } + if (dipSwitch.LocationsSpecified) + { + foreach (var location in dipSwitch.Locations) + { + xtw.WriteStartElement("diplocation"); + xtw.WriteRequiredAttributeString("name", location.Name); + xtw.WriteRequiredAttributeString("number", location.Number?.ToString()); + xtw.WriteOptionalAttributeString("inverted", location.Inverted.FromYesNo()); + xtw.WriteEndElement(); + } + } + if (dipSwitch.ValuesSpecified) + { + foreach (var value in dipSwitch.Values) + { + xtw.WriteStartElement("dipvalue"); + xtw.WriteRequiredAttributeString("name", value.Name); + xtw.WriteRequiredAttributeString("value", value.Value); + xtw.WriteOptionalAttributeString("default", value.Default.FromYesNo()); + if (value.ConditionsSpecified) + { + foreach (var dipValueCondition in value.Conditions) + { + xtw.WriteStartElement("condition"); + xtw.WriteRequiredAttributeString("tag", dipValueCondition.Tag); + xtw.WriteRequiredAttributeString("mask", dipValueCondition.Mask); + xtw.WriteRequiredAttributeString("relation", dipValueCondition.Relation.FromRelation()); + xtw.WriteRequiredAttributeString("value", dipValueCondition.Value); + xtw.WriteEndElement(); + } + } + xtw.WriteEndElement(); + } + } + xtw.WriteEndElement(); + break; + + case ItemType.Disk: + var disk = datItem as Disk; + xtw.WriteStartElement("disk"); + xtw.WriteRequiredAttributeString("name", disk.Name); + xtw.WriteOptionalAttributeString("sha1", disk.SHA1?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("merge", disk.MergeTag); + xtw.WriteOptionalAttributeString("region", disk.Region); + xtw.WriteOptionalAttributeString("index", disk.Index); + xtw.WriteOptionalAttributeString("writable", disk.Writable.FromYesNo()); + if (disk.ItemStatus != ItemStatus.None) + xtw.WriteOptionalAttributeString("status", disk.ItemStatus.FromItemStatus(false)); + xtw.WriteOptionalAttributeString("optional", disk.Optional.FromYesNo()); + xtw.WriteEndElement(); + break; + + case ItemType.Display: + var display = datItem as Display; + xtw.WriteStartElement("display"); + xtw.WriteOptionalAttributeString("tag", display.Tag); + xtw.WriteRequiredAttributeString("type", display.DisplayType.FromDisplayType()); + xtw.WriteOptionalAttributeString("rotate", display.Rotate?.ToString()); + xtw.WriteOptionalAttributeString("flipx", display.FlipX.FromYesNo()); + xtw.WriteOptionalAttributeString("width", display.Width?.ToString()); + xtw.WriteOptionalAttributeString("height", display.Height?.ToString()); + xtw.WriteRequiredAttributeString("refresh", display.Refresh?.ToString("N6")); + xtw.WriteOptionalAttributeString("pixclock", display.PixClock?.ToString()); + xtw.WriteOptionalAttributeString("htotal", display.HTotal?.ToString()); + xtw.WriteOptionalAttributeString("hbend", display.HBEnd?.ToString()); + xtw.WriteOptionalAttributeString("hstart", display.HBStart?.ToString()); + xtw.WriteOptionalAttributeString("vtotal", display.VTotal?.ToString()); + xtw.WriteOptionalAttributeString("vbend", display.VBEnd?.ToString()); + xtw.WriteOptionalAttributeString("vbstart", display.VBStart?.ToString()); + xtw.WriteEndElement(); + break; + + case ItemType.Driver: + var driver = datItem as Driver; + xtw.WriteStartElement("driver"); + xtw.WriteRequiredAttributeString("status", driver.Status.FromSupportStatus()); + xtw.WriteRequiredAttributeString("emulation", driver.Emulation.FromSupportStatus()); + xtw.WriteOptionalAttributeString("cocktail", driver.Cocktail.FromSupportStatus()); + xtw.WriteRequiredAttributeString("savestate", driver.SaveState.FromSupported(true)); + xtw.WriteOptionalAttributeString("requiresartwork", driver.RequiresArtwork.FromYesNo()); + xtw.WriteOptionalAttributeString("unofficial", driver.Unofficial.FromYesNo()); + xtw.WriteOptionalAttributeString("nosoundhardware", driver.NoSoundHardware.FromYesNo()); + xtw.WriteOptionalAttributeString("incomplete", driver.Incomplete.FromYesNo()); + xtw.WriteEndElement(); + break; + + case ItemType.Feature: + var feature = datItem as Feature; + xtw.WriteStartElement("feature"); + xtw.WriteRequiredAttributeString("type", feature.Type.FromFeatureType()); + xtw.WriteOptionalAttributeString("status", feature.Status.FromFeatureStatus()); + xtw.WriteOptionalAttributeString("overall", feature.Overall.FromFeatureStatus()); + xtw.WriteEndElement(); + break; + + case ItemType.Input: + var input = datItem as Input; + xtw.WriteStartElement("input"); + xtw.WriteOptionalAttributeString("service", input.Service.FromYesNo()); + xtw.WriteOptionalAttributeString("tilt", input.Tilt.FromYesNo()); + xtw.WriteRequiredAttributeString("players", input.Players?.ToString()); + xtw.WriteOptionalAttributeString("coins", input.Coins?.ToString()); + if (input.ControlsSpecified) + { + foreach (var control in input.Controls) + { + xtw.WriteStartElement("control"); + xtw.WriteRequiredAttributeString("type", control.ControlType.FromControlType()); + xtw.WriteOptionalAttributeString("player", control.Player?.ToString()); + xtw.WriteOptionalAttributeString("buttons", control.Buttons?.ToString()); + xtw.WriteOptionalAttributeString("reqbuttons", control.RequiredButtons?.ToString()); + xtw.WriteOptionalAttributeString("minimum", control.Minimum?.ToString()); + xtw.WriteOptionalAttributeString("maximum", control.Maximum?.ToString()); + xtw.WriteOptionalAttributeString("sensitivity", control.Sensitivity?.ToString()); + xtw.WriteOptionalAttributeString("keydelta", control.KeyDelta?.ToString()); + xtw.WriteOptionalAttributeString("reverse", control.Reverse.FromYesNo()); + xtw.WriteOptionalAttributeString("ways", control.Ways); + xtw.WriteOptionalAttributeString("ways2", control.Ways2); + xtw.WriteOptionalAttributeString("ways3", control.Ways3); + xtw.WriteEndElement(); + } + } + xtw.WriteEndElement(); + break; + + case ItemType.Port: + var port = datItem as Port; + xtw.WriteStartElement("port"); + xtw.WriteRequiredAttributeString("tag", port.Tag); + if (port.AnalogsSpecified) + { + foreach (var analog in port.Analogs) + { + xtw.WriteStartElement("analog"); + xtw.WriteRequiredAttributeString("mask", analog.Mask); + xtw.WriteEndElement(); + } + } + xtw.WriteEndElement(); + break; + + case ItemType.RamOption: + var ramOption = datItem as RamOption; + xtw.WriteStartElement("ramoption"); + xtw.WriteRequiredAttributeString("name", ramOption.Name); + xtw.WriteOptionalAttributeString("default", ramOption.Default.FromYesNo()); + xtw.WriteRaw(ramOption.Content ?? string.Empty); + xtw.WriteFullEndElement(); + break; + + case ItemType.Rom: + var rom = datItem as Rom; + xtw.WriteStartElement("rom"); + xtw.WriteRequiredAttributeString("name", rom.Name); + xtw.WriteOptionalAttributeString("bios", rom.Bios); + xtw.WriteRequiredAttributeString("size", rom.Size?.ToString()); + xtw.WriteOptionalAttributeString("crc", rom.CRC?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("sha1", rom.SHA1?.ToLowerInvariant()); + xtw.WriteOptionalAttributeString("merge", rom.MergeTag); + xtw.WriteOptionalAttributeString("region", rom.Region); + xtw.WriteOptionalAttributeString("offset", rom.Offset); + if (rom.ItemStatus != ItemStatus.None) + xtw.WriteOptionalAttributeString("status", rom.ItemStatus.FromItemStatus(false)); + xtw.WriteOptionalAttributeString("optional", rom.Optional.FromYesNo()); + xtw.WriteEndElement(); + break; + + case ItemType.Sample: + var sample = datItem as Sample; + xtw.WriteStartElement("sample"); + xtw.WriteRequiredAttributeString("name", sample.Name); + xtw.WriteEndElement(); + break; + + case ItemType.Slot: + var slot = datItem as Slot; + xtw.WriteStartElement("slot"); + xtw.WriteRequiredAttributeString("name", slot.Name); + if (slot.SlotOptionsSpecified) + { + foreach (var slotOption in slot.SlotOptions) + { + xtw.WriteStartElement("slotoption"); + xtw.WriteRequiredAttributeString("name", slotOption.Name); + xtw.WriteRequiredAttributeString("devname", slotOption.DeviceName); + xtw.WriteOptionalAttributeString("default", slotOption.Default.FromYesNo()); + xtw.WriteEndElement(); + } + } + xtw.WriteEndElement(); + break; + + case ItemType.SoftwareList: + var softwareList = datItem as DatItems.Formats.SoftwareList; + xtw.WriteStartElement("softwarelist"); + xtw.WriteRequiredAttributeString("tag", softwareList.Tag); + xtw.WriteRequiredAttributeString("name", softwareList.Name); + if (softwareList.Status != SoftwareListStatus.None) + xtw.WriteRequiredAttributeString("status", softwareList.Status.FromSoftwareListStatus()); + xtw.WriteOptionalAttributeString("filter", softwareList.Filter); + xtw.WriteEndElement(); + break; + + case ItemType.Sound: + var sound = datItem as Sound; + xtw.WriteStartElement("sound"); + xtw.WriteRequiredAttributeString("channels", sound.Channels?.ToString()); + 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 mame + xtw.WriteEndElement(); + + xtw.Flush(); + } + } +} diff --git a/SabreTools.DatFiles/Formats/Listxml.cs b/SabreTools.DatFiles/Formats/Listxml.cs index 8211d928..75f4094d 100644 --- a/SabreTools.DatFiles/Formats/Listxml.cs +++ b/SabreTools.DatFiles/Formats/Listxml.cs @@ -1,22 +1,9 @@ -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; - -namespace SabreTools.DatFiles.Formats +namespace SabreTools.DatFiles.Formats { /// - /// Represents parsing and writing of a MAME XML DAT + /// Represents a MAME XML DAT /// - internal class Listxml : DatFile + internal partial class Listxml : DatFile { /// /// DTD for original MAME XML DATs @@ -192,1717 +179,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 "mame": - Header.Name ??= xtr.GetAttribute("build"); - Header.Description ??= Header.Name; - Header.Debug ??= xtr.GetAttribute("debug").AsYesNo(); - Header.MameConfig ??= xtr.GetAttribute("mameconfig"); - xtr.Read(); - break; - - // Handle M1 DATs since they're 99% the same as a SL DAT - case "m1": - Header.Name ??= "M1"; - Header.Description ??= "M1"; - Header.Version ??= xtr.GetAttribute("version") ?? string.Empty; - xtr.Read(); - break; - - // We want to process the entire subtree of the machine - case "game": // Some older DATs still use "game" - case "machine": - ReadMachine(xtr.ReadSubtree(), statsOnly, filename, indexId); - - // 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 machine 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 ReadMachine(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(); - - // Create a new machine - MachineType machineType = 0x0; - if (reader.GetAttribute("isbios").AsYesNo() == true) - machineType |= MachineType.Bios; - - if (reader.GetAttribute("isdevice").AsYesNo() == true) - machineType |= MachineType.Device; - - if (reader.GetAttribute("ismechanical").AsYesNo() == true) - machineType |= MachineType.Mechanical; - - Machine machine = new() - { - Name = reader.GetAttribute("name"), - Description = reader.GetAttribute("name"), - CloneOf = reader.GetAttribute("cloneof"), - RomOf = reader.GetAttribute("romof"), - SampleOf = reader.GetAttribute("sampleof"), - MachineType = (machineType == 0x0 ? MachineType.None : machineType), - - SourceFile = reader.GetAttribute("sourcefile"), - Runnable = reader.GetAttribute("runnable").AsRunnable(), - }; - - // Get list for new DatItems - List datItems = 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 "description": - machine.Description = reader.ReadElementContentAsString(); - break; - - case "year": - machine.Year = reader.ReadElementContentAsString(); - break; - - case "manufacturer": - machine.Manufacturer = reader.ReadElementContentAsString(); - break; - - case "history": - machine.History = reader.ReadElementContentAsString(); - break; - - case "adjuster": - var adjuster = new Adjuster - { - Name = reader.GetAttribute("name"), - Default = reader.GetAttribute("default").AsYesNo(), - Conditions = new List(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - // Now read the internal tags - ReadAdjuster(reader.ReadSubtree(), adjuster); - - datItems.Add(adjuster); - - // Skip the adjuster now that we've processed it - reader.Skip(); - break; - - case "biosset": - datItems.Add(new BiosSet - { - Name = reader.GetAttribute("name"), - Description = reader.GetAttribute("description"), - Default = reader.GetAttribute("default").AsYesNo(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }); - - reader.Read(); - break; - - case "chip": - var chip = new Chip - { - Name = reader.GetAttribute("name"), - Tag = reader.GetAttribute("tag"), - ChipType = reader.GetAttribute("type").AsChipType(), - Clock = Utilities.CleanLong(reader.GetAttribute("clock")), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - datItems.Add(chip); - - reader.Read(); - break; - - case "condition": - datItems.Add(new Condition - { - Tag = reader.GetAttribute("tag"), - Mask = reader.GetAttribute("mask"), - Relation = reader.GetAttribute("relation").AsRelation(), - Value = reader.GetAttribute("value"), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }); - - reader.Read(); - break; - - case "configuration": - var configuration = new Configuration - { - Name = reader.GetAttribute("name"), - Tag = reader.GetAttribute("tag"), - Mask = reader.GetAttribute("mask"), - Conditions = new List(), - Locations = new List(), - Settings = new List(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - // Now read the internal tags - ReadConfiguration(reader.ReadSubtree(), configuration); - - datItems.Add(configuration); - - // Skip the configuration now that we've processed it - reader.Skip(); - break; - - case "device": - var device = new Device - { - DeviceType = reader.GetAttribute("type").AsDeviceType(), - Tag = reader.GetAttribute("tag"), - FixedImage = reader.GetAttribute("fixed_image"), - Mandatory = Utilities.CleanLong(reader.GetAttribute("mandatory")), - Interface = reader.GetAttribute("interface"), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - // Now read the internal tags - ReadDevice(reader.ReadSubtree(), device); - - datItems.Add(device); - - // Skip the device now that we've processed it - reader.Skip(); - break; - - case "device_ref": - datItems.Add(new DeviceReference - { - Name = reader.GetAttribute("name"), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }); - - reader.Read(); - break; - - case "dipswitch": - var dipSwitch = new DipSwitch - { - Name = reader.GetAttribute("name"), - Tag = reader.GetAttribute("tag"), - Mask = reader.GetAttribute("mask"), - Conditions = new List(), - Locations = new List(), - Values = new List(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - // Now read the internal tags - ReadDipSwitch(reader.ReadSubtree(), dipSwitch); - - datItems.Add(dipSwitch); - - // Skip the dipswitch now that we've processed it - reader.Skip(); - break; - - case "disk": - datItems.Add(new Disk - { - Name = reader.GetAttribute("name"), - SHA1 = reader.GetAttribute("sha1"), - MergeTag = reader.GetAttribute("merge"), - Region = reader.GetAttribute("region"), - Index = reader.GetAttribute("index"), - Writable = reader.GetAttribute("writable").AsYesNo(), - ItemStatus = reader.GetAttribute("status").AsItemStatus(), - Optional = reader.GetAttribute("optional").AsYesNo(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }); - - reader.Read(); - break; - - case "display": - var display = new Display - { - Tag = reader.GetAttribute("tag"), - DisplayType = reader.GetAttribute("type").AsDisplayType(), - Rotate = Utilities.CleanLong(reader.GetAttribute("rotate")), - FlipX = reader.GetAttribute("flipx").AsYesNo(), - Width = Utilities.CleanLong(reader.GetAttribute("width")), - Height = Utilities.CleanLong(reader.GetAttribute("height")), - Refresh = Utilities.CleanDouble(reader.GetAttribute("refresh")), - PixClock = Utilities.CleanLong(reader.GetAttribute("pixclock")), - HTotal = Utilities.CleanLong(reader.GetAttribute("htotal")), - HBEnd = Utilities.CleanLong(reader.GetAttribute("hbend")), - HBStart = Utilities.CleanLong(reader.GetAttribute("hbstart")), - VTotal = Utilities.CleanLong(reader.GetAttribute("vtotal")), - VBEnd = Utilities.CleanLong(reader.GetAttribute("vbend")), - VBStart = Utilities.CleanLong(reader.GetAttribute("vbstart")), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - datItems.Add(display); - - reader.Read(); - break; - - case "driver": - datItems.Add(new Driver - { - Status = reader.GetAttribute("status").AsSupportStatus(), - Emulation = reader.GetAttribute("emulation").AsSupportStatus(), - Cocktail = reader.GetAttribute("cocktail").AsSupportStatus(), - SaveState = reader.GetAttribute("savestate").AsSupported(), - RequiresArtwork = reader.GetAttribute("requiresartwork").AsYesNo(), - Unofficial = reader.GetAttribute("unofficial").AsYesNo(), - NoSoundHardware = reader.GetAttribute("nosoundhardware").AsYesNo(), - Incomplete = reader.GetAttribute("incomplete").AsYesNo(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }); - - reader.Read(); - break; - - case "feature": - datItems.Add(new Feature - { - Type = reader.GetAttribute("type").AsFeatureType(), - Status = reader.GetAttribute("status").AsFeatureStatus(), - Overall = reader.GetAttribute("overall").AsFeatureStatus(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }); - - reader.Read(); - break; - - case "input": - var input = new Input - { - Service = reader.GetAttribute("service").AsYesNo(), - Tilt = reader.GetAttribute("tilt").AsYesNo(), - Players = Utilities.CleanLong(reader.GetAttribute("players")), - Coins = Utilities.CleanLong(reader.GetAttribute("coins")), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - // Now read the internal tags - ReadInput(reader.ReadSubtree(), input); - - datItems.Add(input); - - // Skip the input now that we've processed it - reader.Skip(); - break; - - case "port": - var port = new Port - { - Tag = reader.GetAttribute("tag"), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - // Now read the internal tags - ReadPort(reader.ReadSubtree(), port); - - datItems.Add(port); - - // Skip the port now that we've processed it - reader.Skip(); - break; - - case "ramoption": - datItems.Add(new RamOption - { - Name = reader.GetAttribute("name"), - Default = reader.GetAttribute("default").AsYesNo(), - Content = reader.ReadElementContentAsString(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }); - - break; - - case "rom": - datItems.Add(new Rom - { - Name = reader.GetAttribute("name"), - Bios = reader.GetAttribute("bios"), - Size = Utilities.CleanLong(reader.GetAttribute("size")), - CRC = reader.GetAttribute("crc"), - SHA1 = reader.GetAttribute("sha1"), - MergeTag = reader.GetAttribute("merge"), - Region = reader.GetAttribute("region"), - Offset = reader.GetAttribute("offset"), - ItemStatus = reader.GetAttribute("status").AsItemStatus(), - Optional = reader.GetAttribute("optional").AsYesNo(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }); - - reader.Read(); - break; - - case "sample": - datItems.Add(new Sample - { - Name = reader.GetAttribute("name"), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }); - - reader.Read(); - break; - - case "slot": - var slot = new Slot - { - Name = reader.GetAttribute("name"), - SlotOptions = new List(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - // Now read the internal tags - ReadSlot(reader.ReadSubtree(), slot); - - datItems.Add(slot); - - // Skip the slot now that we've processed it - reader.Skip(); - break; - - case "softwarelist": - datItems.Add(new DatItems.Formats.SoftwareList - { - Tag = reader.GetAttribute("tag"), - Name = reader.GetAttribute("name"), - Status = reader.GetAttribute("status").AsSoftwareListStatus(), - Filter = reader.GetAttribute("filter"), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }); - - reader.Read(); - break; - - case "sound": - var sound = new Sound - { - Channels = Utilities.CleanLong(reader.GetAttribute("channels")), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - datItems.Add(sound); - - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - - // If we found items, copy the machine info and add them - if (datItems.Any()) - { - foreach (DatItem datItem in datItems) - { - datItem.CopyMachineInformation(machine); - ParseAddHelper(datItem, statsOnly); - } - } - - // If no items were found for this machine, add a Blank placeholder - else - { - Blank blank = new() - { - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - blank.CopyMachineInformation(machine); - - // Now process and add the rom - ParseAddHelper(blank, statsOnly); - } - } - - /// - /// Read slot information - /// - /// XmlReader representing a machine block - /// ListXmlSlot to populate - private void ReadSlot(XmlReader reader, Slot slot) - { - // If we have an empty machine, skip it - if (reader == null) - return; - - // Get list ready - slot.SlotOptions = 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 roms from the machine - switch (reader.Name) - { - case "slotoption": - var slotOption = new SlotOption - { - Name = reader.GetAttribute("name"), - DeviceName = reader.GetAttribute("devname"), - Default = reader.GetAttribute("default").AsYesNo() - }; - - slot.SlotOptions.Add(slotOption); - - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - } - - /// - /// Read Input information - /// - /// XmlReader representing a diskarea block - /// ListXmlInput to populate - private void ReadInput(XmlReader reader, Input input) - { - // If we have an empty input, skip it - if (reader == null) - return; - - // Get list ready - input.Controls = 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 "control": - var control = new Control - { - ControlType = reader.GetAttribute("type").AsControlType(), - Player = Utilities.CleanLong(reader.GetAttribute("player")), - Buttons = Utilities.CleanLong(reader.GetAttribute("buttons")), - RequiredButtons = Utilities.CleanLong(reader.GetAttribute("reqbuttons")), - Minimum = Utilities.CleanLong(reader.GetAttribute("minimum")), - Maximum = Utilities.CleanLong(reader.GetAttribute("maximum")), - Sensitivity = Utilities.CleanLong(reader.GetAttribute("sensitivity")), - KeyDelta = Utilities.CleanLong(reader.GetAttribute("keydelta")), - Reverse = reader.GetAttribute("reverse").AsYesNo(), - Ways = reader.GetAttribute("ways"), - Ways2 = reader.GetAttribute("ways2"), - Ways3 = reader.GetAttribute("ways3"), - }; - - input.Controls.Add(control); - - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - } - - /// - /// Read DipSwitch 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 lists ready - dipSwitch.Conditions = new List(); - dipSwitch.Locations = new List(); - 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 "condition": - var condition = new Condition - { - Tag = reader.GetAttribute("tag"), - Mask = reader.GetAttribute("mask"), - Relation = reader.GetAttribute("relation").AsRelation(), - Value = reader.GetAttribute("value") - }; - - dipSwitch.Conditions.Add(condition); - - reader.Read(); - break; - - case "diplocation": - var dipLocation = new Location - { - Name = reader.GetAttribute("name"), - Number = Utilities.CleanLong(reader.GetAttribute("number")), - Inverted = reader.GetAttribute("inverted").AsYesNo() - }; - - dipSwitch.Locations.Add(dipLocation); - - reader.Read(); - break; - - case "dipvalue": - var dipValue = new Setting - { - Name = reader.GetAttribute("name"), - Value = reader.GetAttribute("value"), - Default = reader.GetAttribute("default").AsYesNo() - }; - - // Now read the internal tags - ReadDipValue(reader, dipValue); - - dipSwitch.Values.Add(dipValue); - - // Skip the dipvalue now that we've processed it - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - } - - /// - /// Read DipValue information - /// - /// XmlReader representing a diskarea block - /// Setting to populate - private void ReadDipValue(XmlReader reader, Setting dipValue) - { - // If we have an empty dipvalue, skip it - if (reader == null) - return; - - // Get list ready - dipValue.Conditions = 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 dipvalue - switch (reader.Name) - { - case "condition": - var condition = new Condition - { - Tag = reader.GetAttribute("tag"), - Mask = reader.GetAttribute("mask"), - Relation = reader.GetAttribute("relation").AsRelation(), - Value = reader.GetAttribute("value") - }; - - dipValue.Conditions.Add(condition); - - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - } - - /// - /// Read Configuration information - /// - /// XmlReader representing a diskarea block - /// Configuration to populate - private void ReadConfiguration(XmlReader reader, Configuration configuration) - { - // If we have an empty configuration, skip it - if (reader == null) - return; - - // Get lists ready - configuration.Conditions = new List(); - configuration.Locations = new List(); - configuration.Settings = 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 "condition": - var condition = new Condition - { - Tag = reader.GetAttribute("tag"), - Mask = reader.GetAttribute("mask"), - Relation = reader.GetAttribute("relation").AsRelation(), - Value = reader.GetAttribute("value") - }; - - configuration.Conditions.Add(condition); - - reader.Read(); - break; - - case "conflocation": - var confLocation = new Location - { - Name = reader.GetAttribute("name"), - Number = Utilities.CleanLong(reader.GetAttribute("number")), - Inverted = reader.GetAttribute("inverted").AsYesNo() - }; - - configuration.Locations.Add(confLocation); - - reader.Read(); - break; - - case "confsetting": - var confSetting = new Setting - { - Name = reader.GetAttribute("name"), - Value = reader.GetAttribute("value"), - Default = reader.GetAttribute("default").AsYesNo() - }; - - // Now read the internal tags - ReadConfSetting(reader, confSetting); - - configuration.Settings.Add(confSetting); - - // Skip the dipvalue now that we've processed it - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - } - - /// - /// Read ConfSetting information - /// - /// XmlReader representing a diskarea block - /// Setting to populate - private void ReadConfSetting(XmlReader reader, Setting confSetting) - { - // If we have an empty confsetting, skip it - if (reader == null) - return; - - // Get list ready - confSetting.Conditions = 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 confsetting - switch (reader.Name) - { - case "condition": - var condition = new Condition - { - Tag = reader.GetAttribute("tag"), - Mask = reader.GetAttribute("mask"), - Relation = reader.GetAttribute("relation").AsRelation(), - Value = reader.GetAttribute("value") - }; - - confSetting.Conditions.Add(condition); - - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - } - - /// - /// Read Port information - /// - /// XmlReader representing a diskarea block - /// ListXmlPort to populate - private void ReadPort(XmlReader reader, Port port) - { - // If we have an empty port, skip it - if (reader == null) - return; - - // Get list ready - port.Analogs = 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 port - switch (reader.Name) - { - case "analog": - var analog = new Analog - { - Mask = reader.GetAttribute("mask") - }; - - port.Analogs.Add(analog); - - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - } - - /// - /// Read Adjuster information - /// - /// XmlReader representing a diskarea block - /// Adjuster to populate - private void ReadAdjuster(XmlReader reader, Adjuster adjuster) - { - // If we have an empty port, skip it - if (reader == null) - return; - - // Get list ready - adjuster.Conditions = 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 adjuster - switch (reader.Name) - { - case "condition": - var condition = new Condition - { - Tag = reader.GetAttribute("tag"), - Mask = reader.GetAttribute("mask"), - Relation = reader.GetAttribute("relation").AsRelation(), - Value = reader.GetAttribute("value") - }; - - adjuster.Conditions.Add(condition); - - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - } - - /// - /// Read Device information - /// - /// XmlReader representing a diskarea block - /// ListXmlDevice to populate - private void ReadDevice(XmlReader reader, Device device) - { - // If we have an empty port, skip it - if (reader == null) - return; - - // Get lists ready - device.Instances = new List(); - device.Extensions = 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 adjuster - switch (reader.Name) - { - case "instance": - var instance = new Instance - { - Name = reader.GetAttribute("name"), - BriefName = reader.GetAttribute("briefname"), - }; - - device.Instances.Add(instance); - - reader.Read(); - break; - - case "extension": - var extension = new Extension - { - Name = reader.GetAttribute("name"), - }; - - device.Extensions.Add(extension); - - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - } - - /// - protected override ItemType[] GetSupportedTypes() - { - return new ItemType[] - { - ItemType.Adjuster, - ItemType.BiosSet, - ItemType.Chip, - ItemType.Condition, - ItemType.Configuration, - ItemType.Device, - ItemType.DeviceReference, - ItemType.DipSwitch, - ItemType.Disk, - ItemType.Display, - ItemType.Driver, - ItemType.Feature, - ItemType.Input, - ItemType.Port, - ItemType.RamOption, - ItemType.Rom, - ItemType.Sample, - ItemType.Slot, - ItemType.SoftwareList, - ItemType.Sound, - }; - } - - /// - 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.WriteStartElement("mame"); - xtw.WriteRequiredAttributeString("build", Header.Name); - xtw.WriteOptionalAttributeString("debug", Header.Debug.FromYesNo()); - xtw.WriteOptionalAttributeString("mameconfig", Header.MameConfig); - - xtw.Flush(); - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// XmlTextWriter to output to - /// DatItem object to be output - /// True if the data was written, false on error - 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("machine"); - xtw.WriteRequiredAttributeString("name", datItem.Machine.Name); - xtw.WriteOptionalAttributeString("sourcefile", datItem.Machine.SourceFile); - - 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()); - - 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("description", datItem.Machine.Description); - xtw.WriteOptionalElementString("year", datItem.Machine.Year); - xtw.WriteOptionalElementString("manufacturer", datItem.Machine.Manufacturer); - xtw.WriteOptionalElementString("history", datItem.Machine.History); - - xtw.Flush(); - } - - /// - /// Write out Game start 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.Adjuster: - var adjuster = datItem as Adjuster; - xtw.WriteStartElement("adjuster"); - xtw.WriteRequiredAttributeString("name", adjuster.Name); - xtw.WriteRequiredAttributeString("default", adjuster.Default.FromYesNo()); - if (adjuster.ConditionsSpecified) - { - foreach (var adjusterCondition in adjuster.Conditions) - { - xtw.WriteStartElement("condition"); - xtw.WriteRequiredAttributeString("tag", adjusterCondition.Tag); - xtw.WriteRequiredAttributeString("mask", adjusterCondition.Mask); - xtw.WriteRequiredAttributeString("relation", adjusterCondition.Relation.FromRelation()); - xtw.WriteRequiredAttributeString("value", adjusterCondition.Value); - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - break; - - case ItemType.BiosSet: - var biosSet = datItem as BiosSet; - xtw.WriteStartElement("biosset"); - xtw.WriteRequiredAttributeString("name", biosSet.Name); - xtw.WriteRequiredAttributeString("description", biosSet.Description); - xtw.WriteOptionalAttributeString("default", biosSet.Default?.ToString().ToLowerInvariant()); - xtw.WriteEndElement(); - break; - - case ItemType.Chip: - var chip = datItem as Chip; - xtw.WriteStartElement("chip"); - xtw.WriteRequiredAttributeString("name", chip.Name); - xtw.WriteOptionalAttributeString("tag", chip.Tag); - xtw.WriteRequiredAttributeString("type", chip.ChipType.FromChipType()); - xtw.WriteOptionalAttributeString("clock", chip.Clock?.ToString()); - xtw.WriteEndElement(); - break; - - case ItemType.Condition: - var condition = datItem as Condition; - xtw.WriteStartElement("condition"); - xtw.WriteRequiredAttributeString("tag", condition.Tag); - xtw.WriteRequiredAttributeString("mask", condition.Mask); - xtw.WriteRequiredAttributeString("relation", condition.Relation.FromRelation()); - xtw.WriteRequiredAttributeString("value", condition.Value); - xtw.WriteEndElement(); - break; - - case ItemType.Configuration: - var configuration = datItem as Configuration; - xtw.WriteStartElement("configuration"); - xtw.WriteRequiredAttributeString("name", configuration.Name); - xtw.WriteRequiredAttributeString("tag", configuration.Tag); - xtw.WriteRequiredAttributeString("mask", configuration.Mask); - - if (configuration.ConditionsSpecified) - { - foreach (var configurationCondition in configuration.Conditions) - { - xtw.WriteStartElement("condition"); - xtw.WriteRequiredAttributeString("tag", configurationCondition.Tag); - xtw.WriteRequiredAttributeString("mask", configurationCondition.Mask); - xtw.WriteRequiredAttributeString("relation", configurationCondition.Relation.FromRelation()); - xtw.WriteRequiredAttributeString("value", configurationCondition.Value); - xtw.WriteEndElement(); - } - } - if (configuration.LocationsSpecified) - { - foreach (var location in configuration.Locations) - { - xtw.WriteStartElement("conflocation"); - xtw.WriteRequiredAttributeString("name", location.Name); - xtw.WriteRequiredAttributeString("number", location.Number?.ToString()); - xtw.WriteOptionalAttributeString("inverted", location.Inverted.FromYesNo()); - xtw.WriteEndElement(); - } - } - if (configuration.SettingsSpecified) - { - foreach (var setting in configuration.Settings) - { - xtw.WriteStartElement("confsetting"); - xtw.WriteRequiredAttributeString("name", setting.Name); - xtw.WriteRequiredAttributeString("value", setting.Value); - xtw.WriteOptionalAttributeString("default", setting.Default.FromYesNo()); - if (setting.ConditionsSpecified) - { - foreach (var confsettingCondition in setting.Conditions) - { - xtw.WriteStartElement("condition"); - xtw.WriteRequiredAttributeString("tag", confsettingCondition.Tag); - xtw.WriteRequiredAttributeString("mask", confsettingCondition.Mask); - xtw.WriteRequiredAttributeString("relation", confsettingCondition.Relation.FromRelation()); - xtw.WriteRequiredAttributeString("value", confsettingCondition.Value); - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - break; - - case ItemType.Device: - var device = datItem as Device; - xtw.WriteStartElement("device"); - xtw.WriteRequiredAttributeString("type", device.DeviceType.FromDeviceType()); - xtw.WriteOptionalAttributeString("tag", device.Tag); - xtw.WriteOptionalAttributeString("fixed_image", device.FixedImage); - xtw.WriteOptionalAttributeString("mandatory", device.Mandatory?.ToString()); - xtw.WriteOptionalAttributeString("interface", device.Interface); - if (device.InstancesSpecified) - { - foreach (var instance in device.Instances) - { - xtw.WriteStartElement("instance"); - xtw.WriteRequiredAttributeString("name", instance.Name); - xtw.WriteRequiredAttributeString("briefname", instance.BriefName); - xtw.WriteEndElement(); - } - } - if (device.ExtensionsSpecified) - { - foreach (var extension in device.Extensions) - { - xtw.WriteStartElement("extension"); - xtw.WriteRequiredAttributeString("name", extension.Name); - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - break; - - case ItemType.DeviceReference: - var deviceRef = datItem as DeviceReference; - xtw.WriteStartElement("device_ref"); - xtw.WriteRequiredAttributeString("name", deviceRef.Name); - xtw.WriteEndElement(); - break; - - 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.ConditionsSpecified) - { - foreach (var dipSwitchCondition in dipSwitch.Conditions) - { - xtw.WriteStartElement("condition"); - xtw.WriteRequiredAttributeString("tag", dipSwitchCondition.Tag); - xtw.WriteRequiredAttributeString("mask", dipSwitchCondition.Mask); - xtw.WriteRequiredAttributeString("relation", dipSwitchCondition.Relation.FromRelation()); - xtw.WriteRequiredAttributeString("value", dipSwitchCondition.Value); - xtw.WriteEndElement(); - } - } - if (dipSwitch.LocationsSpecified) - { - foreach (var location in dipSwitch.Locations) - { - xtw.WriteStartElement("diplocation"); - xtw.WriteRequiredAttributeString("name", location.Name); - xtw.WriteRequiredAttributeString("number", location.Number?.ToString()); - xtw.WriteOptionalAttributeString("inverted", location.Inverted.FromYesNo()); - xtw.WriteEndElement(); - } - } - if (dipSwitch.ValuesSpecified) - { - foreach (var value in dipSwitch.Values) - { - xtw.WriteStartElement("dipvalue"); - xtw.WriteRequiredAttributeString("name", value.Name); - xtw.WriteRequiredAttributeString("value", value.Value); - xtw.WriteOptionalAttributeString("default", value.Default.FromYesNo()); - if (value.ConditionsSpecified) - { - foreach (var dipValueCondition in value.Conditions) - { - xtw.WriteStartElement("condition"); - xtw.WriteRequiredAttributeString("tag", dipValueCondition.Tag); - xtw.WriteRequiredAttributeString("mask", dipValueCondition.Mask); - xtw.WriteRequiredAttributeString("relation", dipValueCondition.Relation.FromRelation()); - xtw.WriteRequiredAttributeString("value", dipValueCondition.Value); - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - break; - - case ItemType.Disk: - var disk = datItem as Disk; - xtw.WriteStartElement("disk"); - xtw.WriteRequiredAttributeString("name", disk.Name); - xtw.WriteOptionalAttributeString("sha1", disk.SHA1?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("merge", disk.MergeTag); - xtw.WriteOptionalAttributeString("region", disk.Region); - xtw.WriteOptionalAttributeString("index", disk.Index); - xtw.WriteOptionalAttributeString("writable", disk.Writable.FromYesNo()); - if (disk.ItemStatus != ItemStatus.None) - xtw.WriteOptionalAttributeString("status", disk.ItemStatus.FromItemStatus(false)); - xtw.WriteOptionalAttributeString("optional", disk.Optional.FromYesNo()); - xtw.WriteEndElement(); - break; - - case ItemType.Display: - var display = datItem as Display; - xtw.WriteStartElement("display"); - xtw.WriteOptionalAttributeString("tag", display.Tag); - xtw.WriteRequiredAttributeString("type", display.DisplayType.FromDisplayType()); - xtw.WriteOptionalAttributeString("rotate", display.Rotate?.ToString()); - xtw.WriteOptionalAttributeString("flipx", display.FlipX.FromYesNo()); - xtw.WriteOptionalAttributeString("width", display.Width?.ToString()); - xtw.WriteOptionalAttributeString("height", display.Height?.ToString()); - xtw.WriteRequiredAttributeString("refresh", display.Refresh?.ToString("N6")); - xtw.WriteOptionalAttributeString("pixclock", display.PixClock?.ToString()); - xtw.WriteOptionalAttributeString("htotal", display.HTotal?.ToString()); - xtw.WriteOptionalAttributeString("hbend", display.HBEnd?.ToString()); - xtw.WriteOptionalAttributeString("hstart", display.HBStart?.ToString()); - xtw.WriteOptionalAttributeString("vtotal", display.VTotal?.ToString()); - xtw.WriteOptionalAttributeString("vbend", display.VBEnd?.ToString()); - xtw.WriteOptionalAttributeString("vbstart", display.VBStart?.ToString()); - xtw.WriteEndElement(); - break; - - case ItemType.Driver: - var driver = datItem as Driver; - xtw.WriteStartElement("driver"); - xtw.WriteRequiredAttributeString("status", driver.Status.FromSupportStatus()); - xtw.WriteRequiredAttributeString("emulation", driver.Emulation.FromSupportStatus()); - xtw.WriteOptionalAttributeString("cocktail", driver.Cocktail.FromSupportStatus()); - xtw.WriteRequiredAttributeString("savestate", driver.SaveState.FromSupported(true)); - xtw.WriteOptionalAttributeString("requiresartwork", driver.RequiresArtwork.FromYesNo()); - xtw.WriteOptionalAttributeString("unofficial", driver.Unofficial.FromYesNo()); - xtw.WriteOptionalAttributeString("nosoundhardware", driver.NoSoundHardware.FromYesNo()); - xtw.WriteOptionalAttributeString("incomplete", driver.Incomplete.FromYesNo()); - xtw.WriteEndElement(); - break; - - case ItemType.Feature: - var feature = datItem as Feature; - xtw.WriteStartElement("feature"); - xtw.WriteRequiredAttributeString("type", feature.Type.FromFeatureType()); - xtw.WriteOptionalAttributeString("status", feature.Status.FromFeatureStatus()); - xtw.WriteOptionalAttributeString("overall", feature.Overall.FromFeatureStatus()); - xtw.WriteEndElement(); - break; - - case ItemType.Input: - var input = datItem as Input; - xtw.WriteStartElement("input"); - xtw.WriteOptionalAttributeString("service", input.Service.FromYesNo()); - xtw.WriteOptionalAttributeString("tilt", input.Tilt.FromYesNo()); - xtw.WriteRequiredAttributeString("players", input.Players?.ToString()); - xtw.WriteOptionalAttributeString("coins", input.Coins?.ToString()); - if (input.ControlsSpecified) - { - foreach (var control in input.Controls) - { - xtw.WriteStartElement("control"); - xtw.WriteRequiredAttributeString("type", control.ControlType.FromControlType()); - xtw.WriteOptionalAttributeString("player", control.Player?.ToString()); - xtw.WriteOptionalAttributeString("buttons", control.Buttons?.ToString()); - xtw.WriteOptionalAttributeString("reqbuttons", control.RequiredButtons?.ToString()); - xtw.WriteOptionalAttributeString("minimum", control.Minimum?.ToString()); - xtw.WriteOptionalAttributeString("maximum", control.Maximum?.ToString()); - xtw.WriteOptionalAttributeString("sensitivity", control.Sensitivity?.ToString()); - xtw.WriteOptionalAttributeString("keydelta", control.KeyDelta?.ToString()); - xtw.WriteOptionalAttributeString("reverse", control.Reverse.FromYesNo()); - xtw.WriteOptionalAttributeString("ways", control.Ways); - xtw.WriteOptionalAttributeString("ways2", control.Ways2); - xtw.WriteOptionalAttributeString("ways3", control.Ways3); - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - break; - - case ItemType.Port: - var port = datItem as Port; - xtw.WriteStartElement("port"); - xtw.WriteRequiredAttributeString("tag", port.Tag); - if (port.AnalogsSpecified) - { - foreach (var analog in port.Analogs) - { - xtw.WriteStartElement("analog"); - xtw.WriteRequiredAttributeString("mask", analog.Mask); - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - break; - - case ItemType.RamOption: - var ramOption = datItem as RamOption; - xtw.WriteStartElement("ramoption"); - xtw.WriteRequiredAttributeString("name", ramOption.Name); - xtw.WriteOptionalAttributeString("default", ramOption.Default.FromYesNo()); - xtw.WriteRaw(ramOption.Content ?? string.Empty); - xtw.WriteFullEndElement(); - break; - - case ItemType.Rom: - var rom = datItem as Rom; - xtw.WriteStartElement("rom"); - xtw.WriteRequiredAttributeString("name", rom.Name); - xtw.WriteOptionalAttributeString("bios", rom.Bios); - xtw.WriteRequiredAttributeString("size", rom.Size?.ToString()); - xtw.WriteOptionalAttributeString("crc", rom.CRC?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha1", rom.SHA1?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("merge", rom.MergeTag); - xtw.WriteOptionalAttributeString("region", rom.Region); - xtw.WriteOptionalAttributeString("offset", rom.Offset); - if (rom.ItemStatus != ItemStatus.None) - xtw.WriteOptionalAttributeString("status", rom.ItemStatus.FromItemStatus(false)); - xtw.WriteOptionalAttributeString("optional", rom.Optional.FromYesNo()); - xtw.WriteEndElement(); - break; - - case ItemType.Sample: - var sample = datItem as Sample; - xtw.WriteStartElement("sample"); - xtw.WriteRequiredAttributeString("name", sample.Name); - xtw.WriteEndElement(); - break; - - case ItemType.Slot: - var slot = datItem as Slot; - xtw.WriteStartElement("slot"); - xtw.WriteRequiredAttributeString("name", slot.Name); - if (slot.SlotOptionsSpecified) - { - foreach (var slotOption in slot.SlotOptions) - { - xtw.WriteStartElement("slotoption"); - xtw.WriteRequiredAttributeString("name", slotOption.Name); - xtw.WriteRequiredAttributeString("devname", slotOption.DeviceName); - xtw.WriteOptionalAttributeString("default", slotOption.Default.FromYesNo()); - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - break; - - case ItemType.SoftwareList: - var softwareList = datItem as DatItems.Formats.SoftwareList; - xtw.WriteStartElement("softwarelist"); - xtw.WriteRequiredAttributeString("tag", softwareList.Tag); - xtw.WriteRequiredAttributeString("name", softwareList.Name); - if (softwareList.Status != SoftwareListStatus.None) - xtw.WriteRequiredAttributeString("status", softwareList.Status.FromSoftwareListStatus()); - xtw.WriteOptionalAttributeString("filter", softwareList.Filter); - xtw.WriteEndElement(); - break; - - case ItemType.Sound: - var sound = datItem as Sound; - xtw.WriteStartElement("sound"); - xtw.WriteRequiredAttributeString("channels", sound.Channels?.ToString()); - 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 mame - xtw.WriteEndElement(); - - xtw.Flush(); - } } }