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