diff --git a/SabreTools.DatFiles/Formats/Logiqx.Writer.cs b/SabreTools.DatFiles/Formats/Logiqx.Writer.cs index ff1eae11..ce38f774 100644 --- a/SabreTools.DatFiles/Formats/Logiqx.Writer.cs +++ b/SabreTools.DatFiles/Formats/Logiqx.Writer.cs @@ -385,7 +385,7 @@ namespace SabreTools.DatFiles.Formats } /// - /// Create a Trurip from the current internal information + /// Create a GameBase from the current internal information /// private Models.Logiqx.GameBase? CreateGame(Machine machine) { diff --git a/SabreTools.DatFiles/Formats/SoftwareList.Writer.cs b/SabreTools.DatFiles/Formats/SoftwareList.Writer.cs index 212c185d..24d4f99c 100644 --- a/SabreTools.DatFiles/Formats/SoftwareList.Writer.cs +++ b/SabreTools.DatFiles/Formats/SoftwareList.Writer.cs @@ -1,14 +1,10 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; -using System.Xml; using SabreTools.Core; using SabreTools.Core.Tools; using SabreTools.DatItems; using SabreTools.DatItems.Formats; -using SabreTools.IO; namespace SabreTools.DatFiles.Formats { @@ -147,70 +143,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 softwarelist = CreateSoftwareList(ignoreblanks); + if (!Serialization.SoftawreList.SerializeToFileWithDocType(softwarelist, 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) { @@ -218,217 +157,352 @@ namespace SabreTools.DatFiles.Formats return false; } + logger.User($"'{outfile}' written!{Environment.NewLine}"); return true; } - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// XmlTextWriter to output to - private void WriteHeader(XmlTextWriter xtw) - { - xtw.WriteStartDocument(); - xtw.WriteDocType("softwarelist", null, "softwarelist.dtd", null); - - xtw.WriteStartElement("softwarelist"); - xtw.WriteRequiredAttributeString("name", Header.Name); - xtw.WriteRequiredAttributeString("description", Header.Description); - - xtw.Flush(); - } + #region Converters /// - /// Write out Game start using the supplied StreamWriter - /// - /// XmlTextWriter to output to - /// DatItem object to be output - private void WriteStartGame(XmlTextWriter xtw, DatItem datItem) - { - // No game should start with a path separator - datItem.Machine.Name = datItem.Machine.Name.TrimStart(Path.DirectorySeparatorChar); - - // Build the state - xtw.WriteStartElement("software"); - xtw.WriteRequiredAttributeString("name", datItem.Machine.Name); - if (!string.Equals(datItem.Machine.Name, datItem.Machine.CloneOf, StringComparison.OrdinalIgnoreCase)) - xtw.WriteOptionalAttributeString("cloneof", datItem.Machine.CloneOf); - xtw.WriteOptionalAttributeString("supported", datItem.Machine.Supported.FromSupported(false)); - - xtw.WriteOptionalElementString("description", datItem.Machine.Description); - xtw.WriteOptionalElementString("year", datItem.Machine.Year); - xtw.WriteOptionalElementString("publisher", datItem.Machine.Publisher); - - xtw.Flush(); - } - + /// Create a SoftwareList from the current internal information /// - /// Write out Game start using the supplied StreamWriter - /// - /// XmlTextWriter to output to - private void WriteEndGame(XmlTextWriter xtw) + /// True if blank roms should be skipped on output, false otherwise + private Models.SoftwareList.SoftwareList CreateSoftwareList(bool ignoreblanks) { - // End software - xtw.WriteEndElement(); - - xtw.Flush(); - } - - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// XmlTextWriter to output to - /// DatItem object to be output - private void WriteDatItem(XmlTextWriter xtw, DatItem datItem) - { - // Pre-process the item name - ProcessItemName(datItem, true); - - // Build the state - switch (datItem.ItemType) + var softwarelist = new Models.SoftwareList.SoftwareList { - case ItemType.DipSwitch: - var dipSwitch = datItem as DipSwitch; - xtw.WriteStartElement("dipswitch"); - xtw.WriteRequiredAttributeString("name", dipSwitch.Name); - xtw.WriteRequiredAttributeString("tag", dipSwitch.Tag); - xtw.WriteRequiredAttributeString("mask", dipSwitch.Mask); - if (dipSwitch.ValuesSpecified) + Name = Header.Name, + Description = Header.Description, + Notes = Header.Comment, + Software = CreateSoftware(ignoreblanks), + }; + + return softwarelist; + } + + /// + /// Create an array of Software from the current internal information + /// + /// True if blank roms should be skipped on output, false otherwise + private Models.SoftwareList.Software[]? CreateSoftware(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 software = 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 sw = CreateSoftware(machine); + + // Create holders for all item types + var infos = new List(); + var sharedfeats = new List(); + var parts = 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) { - foreach (Setting dipValue in dipSwitch.Values) - { - xtw.WriteStartElement("dipvalue"); - xtw.WriteRequiredAttributeString("name", dipValue.Name); - xtw.WriteRequiredAttributeString("value", dipValue.Value); - xtw.WriteOptionalAttributeString("default", dipValue.Default.FromYesNo()); - xtw.WriteEndElement(); - } + case Info info: + infos.Add(CreateInfo(info)); + break; + case SharedFeature sharedFeature: + sharedfeats.Add(CreateSharedFeat(sharedFeature)); + break; + case Rom rom: + parts.Add(CreatePart(rom)); + break; + case Disk disk: + parts.Add(CreatePart(disk)); + break; + case DipSwitch dipswitch: + parts.Add(CreatePart(dipswitch)); + break; } - xtw.WriteEndElement(); - break; + } - case ItemType.Disk: - var disk = datItem as Disk; - string diskAreaName = disk.DiskArea?.Name; - if (string.IsNullOrWhiteSpace(diskAreaName)) - diskAreaName = "cdrom"; - - xtw.WriteStartElement("part"); - xtw.WriteRequiredAttributeString("name", disk.Part?.Name); - xtw.WriteRequiredAttributeString("interface", disk.Part?.Interface); - - if (disk.Part?.FeaturesSpecified == true) + // Process the parts to ensure we don't have duplicates + if (parts.Count > 0) + { + var grouped = parts.GroupBy(p => p.Name); + + var tempParts = new List(); + foreach (var grouping in grouped) { - foreach (PartFeature partFeature in disk.Part.Features) + var tempPart = new Models.SoftwareList.Part(); + + var tempFeatures = new List(); + var tempDataAreas = new List(); + var tempDiskAreas = new List(); + var tempDipSwitches = new List(); + + foreach (var part in grouping) { - xtw.WriteStartElement("feature"); - xtw.WriteRequiredAttributeString("name", partFeature.Name); - xtw.WriteOptionalAttributeString("value", partFeature.Value); - xtw.WriteEndElement(); + tempPart.Name ??= part.Name; + tempPart.Interface ??= part.Interface; + + if (part.Feature != null) + tempFeatures.AddRange(part.Feature); + if (part.DataArea != null) + tempDataAreas.AddRange(part.DataArea); + if (part.DiskArea != null) + tempDiskAreas.AddRange(part.DiskArea); + if (part.DipSwitch != null) + tempDipSwitches.AddRange(part.DipSwitch); } + + if (tempFeatures.Count > 0) + tempPart.Feature = tempFeatures.ToArray(); + if (tempDataAreas.Count > 0) + tempPart.DataArea = tempDataAreas.ToArray(); + if (tempDiskAreas.Count > 0) + tempPart.DiskArea = tempDiskAreas.ToArray(); + if (tempDipSwitches.Count > 0) + tempPart.DipSwitch = tempDipSwitches.ToArray(); + + tempParts.Add(tempPart); } - xtw.WriteStartElement("diskarea"); - xtw.WriteRequiredAttributeString("name", diskAreaName); + parts = tempParts; + } - xtw.WriteStartElement("disk"); - xtw.WriteRequiredAttributeString("name", disk.Name); - xtw.WriteOptionalAttributeString("md5", disk.MD5?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha1", disk.SHA1?.ToLowerInvariant()); - if (disk.ItemStatus != ItemStatus.None) - xtw.WriteOptionalAttributeString("status", disk.ItemStatus.FromItemStatus(false)); - xtw.WriteOptionalAttributeString("writable", disk.Writable.FromYesNo()); - xtw.WriteEndElement(); + // Assign the values to the game + sw.Info = infos.ToArray(); + sw.SharedFeat = sharedfeats.ToArray(); + sw.Part = parts.ToArray(); - // End diskarea - xtw.WriteEndElement(); - - // End part - xtw.WriteEndElement(); - break; - - case ItemType.Info: - var info = datItem as Info; - xtw.WriteStartElement("info"); - xtw.WriteRequiredAttributeString("name", info.Name); - xtw.WriteOptionalAttributeString("value", info.Value); - xtw.WriteEndElement(); - break; - - case ItemType.Rom: - var rom = datItem as Rom; - string dataAreaName = rom.DataArea?.Name; - if (string.IsNullOrWhiteSpace(dataAreaName)) - dataAreaName = "rom"; - - xtw.WriteStartElement("part"); - xtw.WriteRequiredAttributeString("name", rom.Part?.Name); - xtw.WriteRequiredAttributeString("interface", rom.Part?.Interface); - - if (rom.Part?.FeaturesSpecified == true) - { - foreach (PartFeature kvp in rom.Part.Features) - { - xtw.WriteStartElement("feature"); - xtw.WriteRequiredAttributeString("name", kvp.Name); - xtw.WriteOptionalAttributeString("value", kvp.Value); - xtw.WriteEndElement(); - } - } - - xtw.WriteStartElement("dataarea"); - xtw.WriteOptionalAttributeString("name", dataAreaName); - xtw.WriteOptionalAttributeString("size", rom.DataArea?.Size.ToString()); - xtw.WriteOptionalAttributeString("width", rom.DataArea?.Width?.ToString()); - xtw.WriteOptionalAttributeString("endianness", rom.DataArea?.Endianness.FromEndianness()); - - xtw.WriteStartElement("rom"); - xtw.WriteRequiredAttributeString("name", rom.Name); - xtw.WriteOptionalAttributeString("size", rom.Size?.ToString()); - xtw.WriteOptionalAttributeString("crc", rom.CRC?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("md5", rom.MD5?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha1", rom.SHA1?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha256", rom.SHA256?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha384", rom.SHA384?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha512", rom.SHA512?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("offset", rom.Offset); - xtw.WriteOptionalAttributeString("value", rom.Value); - if (rom.ItemStatus != ItemStatus.None) - xtw.WriteOptionalAttributeString("status", rom.ItemStatus.FromItemStatus(false)); - xtw.WriteOptionalAttributeString("loadflag", rom.LoadFlag.FromLoadFlag()); - xtw.WriteEndElement(); - - // End dataarea - xtw.WriteEndElement(); - - // End part - xtw.WriteEndElement(); - break; - - case ItemType.SharedFeature: - var sharedFeature = datItem as SharedFeature; - xtw.WriteStartElement("sharedfeat"); - xtw.WriteRequiredAttributeString("name", sharedFeature.Name); - xtw.WriteOptionalAttributeString("value", sharedFeature.Value); - xtw.WriteEndElement(); - break; + // Add the game to the list + software.Add(sw); } - xtw.Flush(); + return software.ToArray(); } /// - /// Write out DAT footer using the supplied StreamWriter - /// - /// XmlTextWriter to output to - private void WriteFooter(XmlTextWriter xtw) + /// Create a Software from the current internal information + /// + private Models.SoftwareList.Software? CreateSoftware(Machine machine) { - // End software - xtw.WriteEndElement(); + var software = new Models.SoftwareList.Software + { + Name = machine.Name, + CloneOf = machine.CloneOf, + Supported = machine.Supported.FromSupported(verbose: true), + Description = machine.Description, + Year = machine.Year, + Publisher = machine.Publisher, + Notes = machine.Comment, + }; - // End softwarelist - xtw.WriteEndElement(); - - xtw.Flush(); + return software; } + + /// + /// Create a Info from the current Info DatItem + /// + private static Models.SoftwareList.Info CreateInfo(Info item) + { + var info = new Models.SoftwareList.Info + { + Name = item.Name, + Value = item.Value, + }; + return info; + } + + /// + /// Create a SharedFeat from the current SharedFeature DatItem + /// + private static Models.SoftwareList.SharedFeat CreateSharedFeat(SharedFeature item) + { + var sharedfeat = new Models.SoftwareList.SharedFeat + { + Name = item.Name, + Value = item.Value, + }; + return sharedfeat; + } + + /// + /// Create a Part from the current Rom DatItem + /// + private static Models.SoftwareList.Part CreatePart(Rom item) + { + var part = new Models.SoftwareList.Part + { + Name = item.Part.Name, + Interface = item.Part.Interface, + Feature = CreateFeatures(item.Part.Features), + DataArea = CreateDataAreas(item), + DiskArea = null, + DipSwitch = null, + }; + return part; + } + + /// + /// Create a Part from the current Disk DatItem + /// + private static Models.SoftwareList.Part CreatePart(Disk item) + { + var part = new Models.SoftwareList.Part + { + Name = item.Part.Name, + Interface = item.Part.Interface, + Feature = CreateFeatures(item.Part.Features), + DataArea = null, + DiskArea = CreateDiskAreas(item), + DipSwitch = null, + }; + return part; + } + + /// + /// Create a Part from the current DipSwitch DatItem + /// + private static Models.SoftwareList.Part CreatePart(DipSwitch item) + { + var part = new Models.SoftwareList.Part + { + Name = item.Part.Name, + Interface = item.Part.Interface, + Feature = CreateFeatures(item.Part.Features), + DataArea = null, + DiskArea = null, + DipSwitch = CreateDipSwitches(item), + }; + return part; + } + + /// + /// Create a Feature array from the current list of PartFeature DatItems + /// + private static Models.SoftwareList.Feature[]? CreateFeatures(List items) + { + // If we don't have features, we can't do anything + if (items == null || !items.Any()) + return null; + + var features = new List(); + foreach (var item in items) + { + var feature = new Models.SoftwareList.Feature + { + Name = item.Name, + Value = item.Value, + }; + features.Add(feature); + } + + return features.ToArray(); + } + + /// + /// Create a DataArea array from the current Rom DatItem + /// + private static Models.SoftwareList.DataArea[]? CreateDataAreas(Rom item) + { + var dataArea = new Models.SoftwareList.DataArea + { + Name = item.DataArea.Name, + Size = item.DataArea.Size?.ToString(), + Width = item.DataArea.Width?.ToString(), + Endianness = item.DataArea.Endianness.FromEndianness(), + Rom = CreateRom(item), + }; + return new Models.SoftwareList.DataArea[] { dataArea }; + } + + /// + /// Create a Rom array from the current Rom DatItem + /// + private static Models.SoftwareList.Rom[]? CreateRom(Rom item) + { + var rom = new Models.SoftwareList.Rom + { + Name = item.Name, + Size = item.Size?.ToString(), + Length = null, + CRC = item.CRC, + SHA1 = item.SHA1, + Offset = item.Offset, + Value = item.Value, + Status = item.ItemStatus.FromItemStatus(yesno: false), + LoadFlag = item.LoadFlag.FromLoadFlag(), + }; + return new Models.SoftwareList.Rom[] { rom }; + } + + /// + /// Create a DiskArea array from the current Disk DatItem + /// + private static Models.SoftwareList.DiskArea[]? CreateDiskAreas(Disk item) + { + var diskArea = new Models.SoftwareList.DiskArea + { + Disk = CreateDisk(item), + }; + return new Models.SoftwareList.DiskArea[] { diskArea }; + } + + /// + /// Create a Disk array from the current Disk DatItem + /// + private static Models.SoftwareList.Disk[]? CreateDisk(Disk item) + { + var disk = new Models.SoftwareList.Disk + { + Name = item.Name, + MD5 = item.MD5, + SHA1 = item.SHA1, + Status = item.ItemStatus.FromItemStatus(yesno: false), + Writeable = item.Writable?.ToString(), + }; + return new Models.SoftwareList.Disk[] { disk }; + } + + /// + /// Create a DipSwitch array from the current DipSwitch DatItem + /// + private static Models.SoftwareList.DipSwitch[]? CreateDipSwitches(DipSwitch item) + { + var dipValues = new List(); + foreach (var setting in item.Values ?? new List()) + { + var dipValue = new Models.SoftwareList.DipValue + { + Name = setting.Name, + Value = setting.Value, + Default = setting.Default?.ToString(), + }; + dipValues.Add(dipValue); + } + + var dipSwitch = new Models.SoftwareList.DipSwitch { DipValue = dipValues.ToArray() }; + return new Models.SoftwareList.DipSwitch[] { dipSwitch }; + } + + #endregion } } diff --git a/SabreTools.Models/SoftwareList/Rom.cs b/SabreTools.Models/SoftwareList/Rom.cs index 0215016e..16fef098 100644 --- a/SabreTools.Models/SoftwareList/Rom.cs +++ b/SabreTools.Models/SoftwareList/Rom.cs @@ -10,10 +10,10 @@ namespace SabreTools.Models.SoftwareList public string Name { get; set; } [XmlAttribute("size")] - public string Size { get; set; } + public string? Size { get; set; } [XmlAttribute("length")] - public string Length { get; set; } + public string? Length { get; set; } [XmlAttribute("crc")] public string? CRC { get; set; }