diff --git a/SabreTools.DatFiles/Formats/Listxml.Reader.cs b/SabreTools.DatFiles/Formats/Listxml.Reader.cs index 619cc8d5..d1b13d13 100644 --- a/SabreTools.DatFiles/Formats/Listxml.Reader.cs +++ b/SabreTools.DatFiles/Formats/Listxml.Reader.cs @@ -873,7 +873,7 @@ namespace SabreTools.DatFiles.Formats Emulation = driver.Emulation.AsSupportStatus(), Cocktail = driver.Cocktail.AsSupportStatus(), SaveState = driver.SaveState.AsSupported(), - RequiresArtwork = driver.SaveState.AsYesNo(), + RequiresArtwork = driver.RequiresArtwork.AsYesNo(), Unofficial = driver.Unofficial.AsYesNo(), NoSoundHardware = driver.NoSoundHardware.AsYesNo(), Incomplete = driver.Incomplete.AsYesNo(), @@ -1015,7 +1015,7 @@ namespace SabreTools.DatFiles.Formats }, }; - var extensions = new List(); + var slotoptions = new List(); foreach (var slotoption in slot.SlotOption ?? Array.Empty()) { var slotoptionItem = new SlotOption @@ -1024,11 +1024,11 @@ namespace SabreTools.DatFiles.Formats DeviceName = slotoption.DevName, Default = slotoption.Default.AsYesNo(), }; - extensions.Add(slotoptionItem); + slotoptions.Add(slotoptionItem); } - if (extensions.Any()) - item.SlotOptions = extensions; + if (slotoptions.Any()) + item.SlotOptions = slotoptions; item.CopyMachineInformation(machine); ParseAddHelper(item, statsOnly); diff --git a/SabreTools.DatFiles/Formats/Listxml.Writer.cs b/SabreTools.DatFiles/Formats/Listxml.Writer.cs index 77b18471..28530a34 100644 --- a/SabreTools.DatFiles/Formats/Listxml.Writer.cs +++ b/SabreTools.DatFiles/Formats/Listxml.Writer.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Text; +using System.Linq; using System.Xml; using SabreTools.Core; using SabreTools.Core.Tools; @@ -187,70 +187,13 @@ namespace SabreTools.DatFiles.Formats 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) + var mame = CreateMame(ignoreblanks); + if (!Serialization.Listxml.SerializeToFile(mame, outfile)) { - logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable"); + logger.Warning($"File '{outfile}' could not be written! See the log for more details."); 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) { @@ -258,6 +201,7 @@ namespace SabreTools.DatFiles.Formats return false; } + logger.User($"'{outfile}' written!{Environment.NewLine}"); return true; } @@ -730,5 +674,754 @@ namespace SabreTools.DatFiles.Formats xtw.Flush(); } + + #region Converters + + /// + /// Create a Mame from the current internal information + /// + /// True if blank roms should be skipped on output, false otherwise + private Models.Listxml.Mame CreateMame(bool ignoreblanks) + { + var datafile = new Models.Listxml.Mame + { + Build = Header.Name ?? Header.Description ?? Header.Build, + Debug = Header.Debug.FromYesNo(), + MameConfig = Header.MameConfig, + + Game = CreateGames(ignoreblanks) + }; + + return datafile; + } + + /// + /// Create an array of GameBase from the current internal information + /// + /// True if blank roms should be skipped on output, false otherwise + private Models.Listxml.GameBase[]? CreateGames(bool ignoreblanks) + { + // If we don't have items, we can't do anything + if (this.Items == null || !this.Items.Any()) + return null; + + // Create a list of hold the games + var games = new List(); + + // Loop through the sorted items and create games for them + foreach (string key in Items.SortedKeys) + { + var items = Items.FilteredItems(key); + if (items == null || !items.Any()) + continue; + + // Get the first item for game information + var machine = items[0].Machine; + var game = CreateGame(machine); + + // Create holders for all item types + var biosSets = new List(); + var roms = new List(); + var disks = new List(); + var deviceRefs = new List(); + var samples = new List(); + var chips = new List(); + var displays = new List(); + var dipSwitches = new List(); + var configurations = new List(); + var ports = new List(); + var adjusters = new List(); + var features = new List(); + var devices = new List(); + var slots = new List(); + var softwareLists = new List(); + var ramOptions = new List(); + + // Loop through and convert the items to respective lists + for (int index = 0; index < items.Count; index++) + { + // Get the item + var item = items[index]; + + // Check for a "null" item + item = ProcessNullifiedItem(item); + + // Skip if we're ignoring the item + if (ShouldIgnore(item, ignoreblanks)) + continue; + + switch (item) + { + case BiosSet biosset: + biosSets.Add(CreateBiosSet(biosset)); + break; + case Rom rom: + roms.Add(CreateRom(rom)); + break; + case Disk disk: + disks.Add(CreateDisk(disk)); + break; + case DeviceReference deviceref: + deviceRefs.Add(CreateDeviceRef(deviceref)); + break; + case Sample sample: + samples.Add(CreateSample(sample)); + break; + case Chip chip: + chips.Add(CreateChip(chip)); + break; + case Display display: + displays.Add(CreateDisplay(display)); + break; + case Sound sound: + game.Sound = CreateSound(sound); + break; + case Input input: + game.Input = CreateInput(input); + break; + case DipSwitch dipswitch: + dipSwitches.Add(CreateDipSwitch(dipswitch)); + break; + case Configuration configuration: + configurations.Add(CreateConfiguration(configuration)); + break; + case Port port: + ports.Add(CreatePort(port)); + break; + case Adjuster adjuster: + adjusters.Add(CreateAdjuster(adjuster)); + break; + case Driver driver: + game.Driver = CreateDriver(driver); + break; + case Feature feature: + features.Add(CreateFeature(feature)); + break; + case Device device: + devices.Add(CreateDevice(device)); + break; + case Slot slot: + slots.Add(CreateSlot(slot)); + break; + case DatItems.Formats.SoftwareList softwarelist: + softwareLists.Add(CreateSoftwareList(softwarelist)); + break; + case RamOption ramoption: + ramOptions.Add(CreateRamOption(ramoption)); + break; + } + } + + // Assign the values to the game + game.BiosSet = biosSets.ToArray(); + game.Rom = roms.ToArray(); + game.Disk = disks.ToArray(); + game.DeviceRef = deviceRefs.ToArray(); + game.Sample = samples.ToArray(); + game.Chip = chips.ToArray(); + game.Display = displays.ToArray(); + game.Video = null; + game.DipSwitch = dipSwitches.ToArray(); + game.Configuration = configurations.ToArray(); + game.Port = ports.ToArray(); + game.Adjuster = adjusters.ToArray(); + game.Feature = features.ToArray(); + game.Device = devices.ToArray(); + game.Slot = slots.ToArray(); + game.SoftwareList = softwareLists.ToArray(); + game.RamOption = ramOptions.ToArray(); + + // Add the game to the list + games.Add(game); + } + + return games.ToArray(); + } + + /// + /// Create a GameBase from the current internal information + /// + private Models.Listxml.GameBase? CreateGame(Machine machine) + { + var game = new Models.Listxml.Machine + { + Name = machine.Name, + SourceFile = machine.SourceFile, + Runnable = machine.Runnable.FromRunnable(), + CloneOf = machine.CloneOf, + RomOf = machine.RomOf, + SampleOf = machine.SampleOf, + Description = machine.Description, + Year = machine.Year, + Manufacturer = machine.Manufacturer, + History = machine.History, + }; + + if (machine.MachineType.HasFlag(MachineType.Bios)) + game.IsBios = "yes"; + if (machine.MachineType.HasFlag(MachineType.Device)) + game.IsDevice = "yes"; + if (machine.MachineType.HasFlag(MachineType.Mechanical)) + game.IsMechanical = "yes"; + + return game; + } + + /// + /// Create a BiosSet from the current BiosSet DatItem + /// + private static Models.Listxml.BiosSet CreateBiosSet(BiosSet item) + { + var biosset = new Models.Listxml.BiosSet + { + Name = item.Name, + Description = item.Description, + }; + + if (item.DefaultSpecified) + biosset.Default = item.Default.FromYesNo(); + + return biosset; + } + + /// + /// Create a Rom from the current Rom DatItem + /// + private static Models.Listxml.Rom CreateRom(Rom item) + { + var rom = new Models.Listxml.Rom + { + Name = item.Name, + Bios = item.Bios, + Size = item.Size?.ToString(), + CRC = item.CRC, + SHA1 = item.SHA1, + Merge = item.MergeTag, + Region = item.Region, + Offset = item.Offset, + Status = item.ItemStatus.FromItemStatus(yesno: false), + Optional = item.Optional.FromYesNo(), + //Dispose = item.Dispose.FromYesNo(), // TODO: Add to internal model + //SoundOnly = item.SoundOnly.FromYesNo(), // TODO: Add to internal model + }; + + return rom; + } + + /// + /// Create a Disk from the current Disk DatItem + /// + private static Models.Listxml.Disk CreateDisk(Disk item) + { + var disk = new Models.Listxml.Disk + { + Name = item.Name, + MD5 = item.MD5, + SHA1 = item.SHA1, + Merge = item.MergeTag, + Region = item.Region, + Index = item.Index, + Writable = item.Writable.FromYesNo(), + Status = item.ItemStatus.FromItemStatus(yesno: false), + Optional = item.Optional.FromYesNo(), + }; + + return disk; + } + + /// + /// Create a DeviceRef from the current DeviceReference DatItem + /// + private static Models.Listxml.DeviceRef CreateDeviceRef(DeviceReference item) + { + var deviceref = new Models.Listxml.DeviceRef + { + Name = item.Name, + }; + + return deviceref; + } + + /// + /// Create a Sample from the current Sample DatItem + /// + private static Models.Listxml.Sample CreateSample(Sample item) + { + var sample = new Models.Listxml.Sample + { + Name = item.Name, + }; + + return sample; + } + + /// + /// Create a Chip from the current Chip DatItem + /// + private static Models.Listxml.Chip CreateChip(Chip item) + { + var chip = new Models.Listxml.Chip + { + Name = item.Name, + Tag = item.Tag, + Type = item.ChipType.FromChipType(), + //SoundOnly = item.SoundOnly, // TODO: Add to internal model + Clock = item.Clock?.ToString(), + }; + + return chip; + } + + /// + /// Create a Display from the current Display DatItem + /// + private static Models.Listxml.Display CreateDisplay(Display item) + { + var display = new Models.Listxml.Display + { + Tag = item.Tag, + Type = item.DisplayType.FromDisplayType(), + Rotate = item.Rotate?.ToString(), + FlipX = item.FlipX.FromYesNo(), + Width = item.Width?.ToString(), + Height = item.Height?.ToString(), + Refresh = item.Refresh?.ToString(), + PixClock = item.PixClock?.ToString(), + HTotal = item.HTotal?.ToString(), + HBEnd = item.HBEnd?.ToString(), + HBStart = item.HBStart?.ToString(), + VTotal = item.VTotal?.ToString(), + VBEnd = item.VBEnd?.ToString(), + VBStart = item.VBStart?.ToString(), + }; + + return display; + } + + /// + /// Create a Sound from the current Sound DatItem + /// + private static Models.Listxml.Sound CreateSound(Sound item) + { + var sound = new Models.Listxml.Sound + { + Channels = item.Channels?.ToString(), + }; + + return sound; + } + + /// + /// Create an Input from the current Input DatItem + /// + private static Models.Listxml.Input CreateInput(Input item) + { + var input = new Models.Listxml.Input + { + Service = item.Service.FromYesNo(), + Tilt = item.Tilt.FromYesNo(), + Players = item.Players?.ToString(), + //ControlAttr = item.ControlAttr, // TODO: Add to internal model + //Buttons = item.Buttons, // TODO: Add to internal model + Coins = item.Coins?.ToString(), + }; + + var controls = new List(); + foreach (var controlItem in item.Controls ?? new List()) + { + var control = CreateControl(controlItem); + controls.Add(control); + } + + if (controls.Any()) + input.Control = controls.ToArray(); + + return input; + } + + /// + /// Create an Control from the current Input DatItem + /// + private static Models.Listxml.Control CreateControl(Control item) + { + var control = new Models.Listxml.Control + { + Type = item.ControlType.FromControlType(), + Player = item.Player?.ToString(), + Buttons = item.Buttons?.ToString(), + ReqButtons = item.RequiredButtons?.ToString(), + Minimum = item.Minimum?.ToString(), + Maximum = item.Maximum?.ToString(), + Sensitivity = item.Sensitivity?.ToString(), + KeyDelta = item.KeyDelta?.ToString(), + Reverse = item.Reverse.FromYesNo(), + Ways = item.Ways, + Ways2 = item.Ways2, + Ways3 = item.Ways3, + }; + + return control; + } + + /// + /// Create an DipSwitch from the current DipSwitch DatItem + /// + private static Models.Listxml.DipSwitch CreateDipSwitch(DipSwitch item) + { + var dipswitch = new Models.Listxml.DipSwitch + { + Name = item.Name, + Tag = item.Tag, + Mask = item.Mask, + }; + + if (item.ConditionsSpecified) + { + var conditionItem = item.Conditions[0]; + var condition = new Models.Listxml.Condition + { + Tag = conditionItem.Tag, + Mask = conditionItem.Mask, + Relation = conditionItem.Relation.FromRelation(), + Value = conditionItem.Value, + }; + dipswitch.Condition = condition; + } + + var diplocations = new List(); + foreach (var locationItem in item.Locations ?? new List()) + { + var control = CreateDipLocation(locationItem); + diplocations.Add(control); + } + + if (diplocations.Any()) + dipswitch.DipLocation = diplocations.ToArray(); + + var dipvalues = new List(); + foreach (var settingItem in item.Values ?? new List()) + { + var dipvalue = CreateDipValue(settingItem); + dipvalues.Add(dipvalue); + } + + if (dipvalues.Any()) + dipswitch.DipValue = dipvalues.ToArray(); + + return dipswitch; + } + + /// + /// Create a DipLocation from the current Location DatItem + /// + private static Models.Listxml.DipLocation CreateDipLocation(Location item) + { + var diplocation = new Models.Listxml.DipLocation + { + Name = item.Name, + Number = item.Number?.ToString(), + Inverted = item.Inverted.FromYesNo(), + }; + + return diplocation; + } + + /// + /// Create a DipValue from the current Setting DatItem + /// + private static Models.Listxml.DipValue CreateDipValue(Setting item) + { + var dipvalue = new Models.Listxml.DipValue + { + Name = item.Name, + Value = item.Value, + Default = item.Default.FromYesNo(), + }; + + if (item.ConditionsSpecified) + { + var conditionItem = item.Conditions[0]; + var condition = new Models.Listxml.Condition + { + Tag = conditionItem.Tag, + Mask = conditionItem.Mask, + Relation = conditionItem.Relation.FromRelation(), + Value = conditionItem.Value, + }; + dipvalue.Condition = condition; + } + + return dipvalue; + } + + /// + /// Create an Configuration from the current Configuration DatItem + /// + private static Models.Listxml.Configuration CreateConfiguration(Configuration item) + { + var configuration = new Models.Listxml.Configuration + { + Name = item.Name, + Tag = item.Tag, + Mask = item.Mask, + }; + + if (item.ConditionsSpecified) + { + var conditionItem = item.Conditions[0]; + var condition = new Models.Listxml.Condition + { + Tag = conditionItem.Tag, + Mask = conditionItem.Mask, + Relation = conditionItem.Relation.FromRelation(), + Value = conditionItem.Value, + }; + configuration.Condition = condition; + } + + var confLocations = new List(); + foreach (var location in item.Locations ?? new List()) + { + var control = CreateConfLocation(location); + confLocations.Add(control); + } + + if (confLocations.Any()) + configuration.ConfLocation = confLocations.ToArray(); + + var confsettings = new List(); + foreach (var settingItem in item.Settings ?? new List()) + { + var dipvalue = CreateConfSetting(settingItem); + confsettings.Add(dipvalue); + } + + if (confsettings.Any()) + configuration.ConfSetting = confsettings.ToArray(); + + return configuration; + } + + /// + /// Create a ConfLocation from the current Location DatItem + /// + private static Models.Listxml.ConfLocation CreateConfLocation(Location item) + { + var conflocation = new Models.Listxml.ConfLocation + { + Name = item.Name, + Number = item.Number?.ToString(), + Inverted = item.Inverted.FromYesNo(), + }; + + return conflocation; + } + + /// + /// Create a ConfSetting from the current Setting DatItem + /// + private static Models.Listxml.ConfSetting CreateConfSetting(Setting item) + { + var confsetting = new Models.Listxml.ConfSetting + { + Name = item.Name, + Value = item.Value, + Default = item.Default.FromYesNo(), + }; + + if (item.ConditionsSpecified) + { + var conditionItem = item.Conditions[0]; + var condition = new Models.Listxml.Condition + { + Tag = conditionItem.Tag, + Mask = conditionItem.Mask, + Relation = conditionItem.Relation.FromRelation(), + Value = conditionItem.Value, + }; + confsetting.Condition = condition; + } + + return confsetting; + } + + /// + /// Create a Port from the current Port DatItem + /// + private static Models.Listxml.Port CreatePort(Port item) + { + var port = new Models.Listxml.Port + { + Tag = item.Tag, + }; + + return port; + } + + /// + /// Create a Adjuster from the current Adjuster DatItem + /// + private static Models.Listxml.Adjuster CreateAdjuster(Adjuster item) + { + var adjuster = new Models.Listxml.Adjuster + { + Name = item.Name, + Default = item.Default.FromYesNo(), + }; + + if (item.ConditionsSpecified) + { + var conditionItem = item.Conditions[0]; + var condition = new Models.Listxml.Condition + { + Tag = conditionItem.Tag, + Mask = conditionItem.Mask, + Relation = conditionItem.Relation.FromRelation(), + Value = conditionItem.Value, + }; + adjuster.Condition = condition; + } + + return adjuster; + } + + /// + /// Create a Driver from the current Driver DatItem + /// + private static Models.Listxml.Driver CreateDriver(Driver item) + { + var driver = new Models.Listxml.Driver + { + Status = item.Status.FromSupportStatus(), + //Color = item.Color.FromSupportStatus(), // TODO: Add to internal model + //Sound = item.Sound.FromSupportStatus(), // TODO: Add to internal model + //PaletteSize = driver.PaletteSize?.ToString(), // TODO: Add to internal model + Emulation = item.Emulation.FromSupportStatus(), + Cocktail = item.Cocktail.FromSupportStatus(), + SaveState = item.SaveState.FromSupported(verbose: true), + RequiresArtwork = item.RequiresArtwork.FromYesNo(), + Unofficial = item.Unofficial.FromYesNo(), + NoSoundHardware = item.NoSoundHardware.FromYesNo(), + Incomplete = item.Incomplete.FromYesNo(), + }; + + return driver; + } + + /// + /// Create a Feature from the current Feature DatItem + /// + private static Models.Listxml.Feature CreateFeature(Feature item) + { + var feature = new Models.Listxml.Feature + { + Type = item.Type.FromFeatureType(), + Status = item.Status.FromFeatureStatus(), + Overall = item.Overall.FromFeatureStatus(), + }; + + return feature; + } + + /// + /// Create a Device from the current Device DatItem + /// + private static Models.Listxml.Device CreateDevice(Device item) + { + var device = new Models.Listxml.Device + { + Type = item.DeviceType.FromDeviceType(), + Tag = item.Tag, + FixedImage = item.FixedImage, + Mandatory = item.Mandatory?.ToString(), + Interface = item.Interface, + }; + + if (item.InstancesSpecified) + { + var instanceItem = item.Instances[0]; + var instance = new Models.Listxml.Instance + { + Name = instanceItem.Name, + BriefName = instanceItem.BriefName, + }; + device.Instance = instance; + } + + var extensions = new List(); + foreach (var extensionItem in item.Extensions ?? new List()) + { + var extension = new Models.Listxml.Extension + { + Name = extensionItem.Name, + }; + extensions.Add(extension); + } + + if (extensions.Any()) + device.Extension = extensions.ToArray(); + + return device; + } + + /// + /// Create a Slot from the current Slot DatItem + /// + private static Models.Listxml.Slot CreateSlot(Slot item) + { + var slot = new Models.Listxml.Slot + { + Name = item.Name, + }; + + var slotoptions = new List(); + foreach (var slotoptionItem in item.SlotOptions ?? new List()) + { + var slotoption = new Models.Listxml.SlotOption + { + Name = slotoptionItem.Name, + DevName = slotoptionItem.DeviceName, + Default = slotoptionItem.Default.FromYesNo(), + }; + slotoptions.Add(slotoption); + } + + if (slotoptions.Any()) + slot.SlotOption = slotoptions.ToArray(); + + return slot; + } + + /// + /// Create a SoftwareList from the current SoftwareList DatItem + /// + private static Models.Listxml.SoftwareList CreateSoftwareList(DatItems.Formats.SoftwareList item) + { + var softwarelist = new Models.Listxml.SoftwareList + { + Tag = item.Tag, + Name = item.Name, + Status = item.Status.FromSoftwareListStatus(), + Filter = item.Filter, + }; + + return softwarelist; + } + + /// + /// Create a RamOption from the current RamOption DatItem + /// + private static Models.Listxml.RamOption CreateRamOption(RamOption item) + { + var softwarelist = new Models.Listxml.RamOption + { + Name = item.Name, + Default = item.Default.FromYesNo(), + }; + + return softwarelist; + } + + #endregion } }