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