diff --git a/SabreTools.Library/DatFiles/DatHeader.cs b/SabreTools.Library/DatFiles/DatHeader.cs index 068ff81a..23b69bc8 100644 --- a/SabreTools.Library/DatFiles/DatHeader.cs +++ b/SabreTools.Library/DatFiles/DatHeader.cs @@ -20,7 +20,7 @@ namespace SabreTools.Library.DatFiles { #region Fields - #region Common Fields + #region Common /// /// External name of the DAT @@ -127,6 +127,9 @@ namespace SabreTools.Library.DatFiles [XmlElement("forcemerging")] public MergingFlag ForceMerging { get; set; } + [JsonIgnore] + public bool ForceMergingSpecified { get { return ForceMerging != MergingFlag.None; } } + /// /// Force nodump handling when loaded /// @@ -134,6 +137,9 @@ namespace SabreTools.Library.DatFiles [XmlElement("forcenodump")] public NodumpFlag ForceNodump { get; set; } + [JsonIgnore] + public bool ForceNodumpSpecified { get { return ForceNodump != NodumpFlag.None; } } + /// /// Force output packing when loaded /// @@ -141,6 +147,9 @@ namespace SabreTools.Library.DatFiles [XmlElement("forcepacking")] public PackingFlag ForcePacking { get; set; } + [JsonIgnore] + public bool ForcePackingSpecified { get { return ForcePacking != PackingFlag.None; } } + /// /// Read or write format /// @@ -150,7 +159,7 @@ namespace SabreTools.Library.DatFiles #endregion - #region ListXML Fields + #region ListXML /// /// Debug build flag @@ -160,6 +169,9 @@ namespace SabreTools.Library.DatFiles [XmlElement("debug")] public bool? Debug { get; set; } = null; + [JsonIgnore] + public bool DebugSpecified { get { return Debug != null; } } + /// /// MAME configuration name /// @@ -193,6 +205,9 @@ namespace SabreTools.Library.DatFiles [XmlElement("rommode")] public MergingFlag RomMode { get; set; } + [JsonIgnore] + public bool RomModeSpecified { get { return RomMode != MergingFlag.None; } } + /// /// RomCenter bios mode /// @@ -201,6 +216,9 @@ namespace SabreTools.Library.DatFiles [XmlElement("biosmode")] public MergingFlag BiosMode { get; set; } + [JsonIgnore] + public bool BiosModeSpecified { get { return BiosMode != MergingFlag.None; } } + /// /// RomCenter sample mode /// @@ -209,6 +227,9 @@ namespace SabreTools.Library.DatFiles [XmlElement("samplemode")] public MergingFlag SampleMode { get; set; } + [JsonIgnore] + public bool SampleModeSpecified { get { return SampleMode != MergingFlag.None; } } + /// /// RomCenter lock rom mode /// @@ -216,6 +237,9 @@ namespace SabreTools.Library.DatFiles [XmlElement("lockrommode")] public bool? LockRomMode { get; set; } + [JsonIgnore] + public bool LockRomModeSpecified { get { return LockRomMode != null; } } + /// /// RomCenter lock bios mode /// @@ -223,6 +247,9 @@ namespace SabreTools.Library.DatFiles [XmlElement("lockbiosmode")] public bool? LockBiosMode { get; set; } + [JsonIgnore] + public bool LockBiosModeSpecified { get { return LockBiosMode != null; } } + /// /// RomCenter lock sample mode /// @@ -230,9 +257,12 @@ namespace SabreTools.Library.DatFiles [XmlElement("locksamplemode")] public bool? LockSampleMode { get; set; } + [JsonIgnore] + public bool LockSampleModeSpecified { get { return LockSampleMode != null; } } + #endregion - #region Missfile Fields + #region Missfile /// /// Output the item name @@ -243,7 +273,7 @@ namespace SabreTools.Library.DatFiles #endregion - #region OfflineList Fields + #region OfflineList /// /// Screenshots width @@ -266,6 +296,9 @@ namespace SabreTools.Library.DatFiles [XmlElement("infos")] public List Infos { get; set; } + [JsonIgnore] + public bool InfosSpecified { get { return Infos != null && Infos.Count > 0; } } + /// /// OfflineList can-open extensions /// @@ -273,6 +306,9 @@ namespace SabreTools.Library.DatFiles [XmlElement("canopen")] public List CanOpen { get; set; } + [JsonIgnore] + public bool CanOpenSpecified { get { return CanOpen != null && CanOpen.Count > 0; } } + // TODO: Implement the following header values: // - newdat.datversionurl (currently reads and writes to Header.Url, not strictly correct) // - newdat.daturl (currently writes to Header.Url, not strictly correct) @@ -354,62 +390,6 @@ namespace SabreTools.Library.DatFiles #endregion - #region XML Serialization Nullable Specifications - - #region Common - - [JsonIgnore] - public bool ForceMergingSpecified { get { return ForceMerging != MergingFlag.None; } } - - [JsonIgnore] - public bool ForceNodumpSpecified { get { return ForceNodump != NodumpFlag.None; } } - - [JsonIgnore] - public bool ForcePackingSpecified { get { return ForcePacking != PackingFlag.None; } } - - #endregion - - #region ListXML - - [JsonIgnore] - public bool DebugSpecified { get { return Debug != null; } } - - #endregion - - #region Logiqx - - [JsonIgnore] - public bool RomModeSpecified { get { return RomMode != MergingFlag.None; } } - - [JsonIgnore] - public bool BiosModeSpecified { get { return BiosMode != MergingFlag.None; } } - - [JsonIgnore] - public bool SampleModeSpecified { get { return SampleMode != MergingFlag.None; } } - - [JsonIgnore] - public bool LockRomModeSpecified { get { return LockRomMode != null; } } - - [JsonIgnore] - public bool LockBiosModeSpecified { get { return LockBiosMode != null; } } - - [JsonIgnore] - public bool LockSampleModeSpecified { get { return LockSampleMode != null; } } - - #endregion - - #region OfflineList - - [JsonIgnore] - public bool InfosSpecified { get { return Infos != null; } } - - [JsonIgnore] - public bool CanOpenSpecified { get { return CanOpen != null; } } - - #endregion - - #endregion // XML Serialization Nullable Specifications - #region Depot Information /// diff --git a/SabreTools.Library/DatFiles/Json.cs b/SabreTools.Library/DatFiles/Json.cs index 8ec1c536..1143ad94 100644 --- a/SabreTools.Library/DatFiles/Json.cs +++ b/SabreTools.Library/DatFiles/Json.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; using SabreTools.Library.Data; @@ -138,7 +137,6 @@ namespace SabreTools.Library.DatFiles return; // Prepare internal variables - JsonSerializer js = new JsonSerializer(); Machine machine = null; // Read the machine info, if possible @@ -454,7 +452,7 @@ namespace SabreTools.Library.DatFiles try { // No game should start with a path separator - datItem.Machine.Name = datItem.Machine.Name.TrimStart(Path.DirectorySeparatorChar); + datItem.Machine.Name = datItem.Machine.Name.TrimStart(Path.DirectorySeparatorChar) ?? string.Empty; // Build the state jtw.WriteStartObject(); diff --git a/SabreTools.Library/DatFiles/SabreDat.cs b/SabreTools.Library/DatFiles/SabreDat.cs index e06e5c5b..7298b1e1 100644 --- a/SabreTools.Library/DatFiles/SabreDat.cs +++ b/SabreTools.Library/DatFiles/SabreDat.cs @@ -1,19 +1,18 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; using System.Xml; +using System.Xml.Serialization; using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.IO; -using SabreTools.Library.Tools; namespace SabreTools.Library.DatFiles { /// - /// Represents parsing and writing of an SabreDat XML DAT + /// Represents parsing and writing of a SabreDat XML DAT /// internal class SabreDat : DatFile { @@ -35,10 +34,6 @@ namespace SabreTools.Library.DatFiles protected override void ParseFile(string filename, int indexId, bool keep) { // Prepare all internal variables - bool empty = true; - string key; - List parent = new List(); - XmlReader xtr = filename.GetXmlTextReader(); // If we got a null reader, just return @@ -51,36 +46,6 @@ namespace SabreTools.Library.DatFiles xtr.MoveToContent(); while (!xtr.EOF) { - // If we're ending a folder or game, take care of possibly empty games and removing from the parent - if (xtr.NodeType == XmlNodeType.EndElement && (xtr.Name == "directory" || xtr.Name == "dir")) - { - // If we didn't find any items in the folder, make sure to add the blank rom - if (empty) - { - string tempgame = string.Join("\\", parent); - Rom rom = new Rom("null", tempgame); - - // Now process and add the rom - key = ParseAddHelper(rom); - } - - // Regardless, end the current folder - int parentcount = parent.Count; - if (parentcount == 0) - { - Globals.Logger.Verbose($"Empty parent '{string.Join("\\", parent)}' found in '{filename}'"); - empty = true; - } - - // If we have an end folder element, remove one item from the parent, if possible - if (parentcount > 0) - { - parent.RemoveAt(parent.Count - 1); - if (keep && parentcount > 1) - Header.Type = (string.IsNullOrWhiteSpace(Header.Type) ? "SuperDAT" : Header.Type); - } - } - // We only want elements if (xtr.NodeType != XmlNodeType.Element) { @@ -90,17 +55,15 @@ namespace SabreTools.Library.DatFiles switch (xtr.Name) { - // We want to process the entire subtree of the header case "header": - ReadHeader(xtr.ReadSubtree(), keep); - - // Skip the header node now that we've processed it + XmlSerializer xs = new XmlSerializer(typeof(DatHeader)); + DatHeader header = xs.Deserialize(xtr.ReadSubtree()) as DatHeader; + Header.ConditionalCopy(header); xtr.Skip(); break; - case "dir": case "directory": - empty = ReadDirectory(xtr.ReadSubtree(), parent, filename, indexId, keep); + ReadDirectory(xtr.ReadSubtree(), filename, indexId); // Skip the directory node now that we've processed it xtr.Read(); @@ -122,819 +85,111 @@ namespace SabreTools.Library.DatFiles xtr.Dispose(); } - /// - /// Read header information - /// - /// XmlReader to use to parse the header - /// True if full pathnames are to be kept, false otherwise (default) - private void ReadHeader(XmlReader reader, bool keep) - { - bool superdat = false; - - // If there's no subtree to the header, skip it - if (reader == null) - return; - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element || reader.Name == "header") - { - reader.Read(); - continue; - } - - // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) - string content; - switch (reader.Name) - { - case "name": - content = reader.ReadElementContentAsString(); ; - Header.Name = (Header.Name== null ? content : Header.Name); - superdat = superdat || content.Contains(" - SuperDAT"); - if (keep && superdat) - { - Header.Type = (Header.Type == null ? "SuperDAT" : Header.Type); - } - break; - - case "description": - content = reader.ReadElementContentAsString(); - Header.Description = (Header.Description== null ? content : Header.Description); - break; - - case "rootdir": - content = reader.ReadElementContentAsString(); - Header.RootDir = (Header.RootDir== null ? content : Header.RootDir); - break; - - case "category": - content = reader.ReadElementContentAsString(); - Header.Category = (Header.Category== null ? content : Header.Category); - break; - - case "version": - content = reader.ReadElementContentAsString(); - Header.Version = (Header.Version== null ? content : Header.Version); - break; - - case "date": - content = reader.ReadElementContentAsString(); - Header.Date = (Header.Date== null ? content.Replace(".", "/") : Header.Date); - break; - - case "author": - content = reader.ReadElementContentAsString(); - Header.Author = (Header.Author== null ? content : Header.Author); - Header.Email = (Header.Email == null ? reader.GetAttribute("email") : Header.Email); - Header.Homepage = (Header.Homepage == null ? reader.GetAttribute("homepage") : Header.Homepage); - Header.Url = (Header.Url == null ? reader.GetAttribute("url") : Header.Url); - break; - - case "comment": - content = reader.ReadElementContentAsString(); - Header.Comment = (Header.Comment== null ? content : Header.Comment); - break; - - case "flags": - ReadFlags(reader.ReadSubtree(), superdat); - - // Skip the flags node now that we've processed it - reader.Skip(); - break; - - default: - reader.Read(); - break; - } - } - } - /// /// Read directory information /// - /// XmlReader to use to parse the header + /// XmlReader to use to parse the header /// Name of the file to be parsed /// Index ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// TODO: Convert this to a direct serializer like JSON is - private bool ReadDirectory( - XmlReader reader, - List parent, - - // Standard Dat parsing - string filename, - int indexId, - - // Miscellaneous - bool keep) + private void ReadDirectory(XmlReader xtr, string filename, int indexId) { - // Prepare all internal variables - XmlReader flagreader; - bool empty = true; - string key = string.Empty, date = string.Empty; - long? size = null; - ItemStatus its = ItemStatus.None; + // If the reader is invalid, skip + if (xtr == null) + return; - // If there's no subtree to the header, skip it - if (reader == null) - return empty; + // Prepare internal variables + Machine machine = null; - string foldername = (reader.GetAttribute("name") ?? string.Empty); - if (!string.IsNullOrWhiteSpace(foldername)) - parent.Add(foldername); - - // Otherwise, read what we can from the directory - while (!reader.EOF) + // Otherwise, read the directory + try { - // If we're ending a folder or game, take care of possibly empty games and removing from the parent - if (reader.NodeType == XmlNodeType.EndElement && (reader.Name == "directory" || reader.Name == "dir")) + xtr.MoveToContent(); + while (!xtr.EOF) { - // If we didn't find any items in the folder, make sure to add the blank rom - if (empty) + // We only want elements + if (xtr.NodeType != XmlNodeType.Element) { - string tempgame = string.Join("\\", parent); - Rom rom = new Rom("null", tempgame); - - // Now process and add the rom - key = ParseAddHelper(rom); + xtr.Read(); + continue; } - // Regardless, end the current folder - int parentcount = parent.Count; - if (parentcount == 0) + switch (xtr.Name) { - Globals.Logger.Verbose($"Empty parent '{string.Join("\\", parent)}' found in '{filename}'"); - empty = true; - } + case "machine": + XmlSerializer xs = new XmlSerializer(typeof(Machine)); + machine = xs.Deserialize(xtr.ReadSubtree()) as Machine; + xtr.Skip(); + break; - // If we have an end folder element, remove one item from the parent, if possible - if (parentcount > 0) - { - parent.RemoveAt(parent.Count - 1); - if (keep && parentcount > 1) - Header.Type = (string.IsNullOrWhiteSpace(Header.Type) ? "SuperDAT" : Header.Type); + case "files": + ReadFiles(xtr.ReadSubtree(), machine, filename, indexId); + + // Skip the directory node now that we've processed it + xtr.Read(); + break; + default: + xtr.Read(); + break; } } - - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all directory items - string content = string.Empty; - switch (reader.Name) - { - // Directories can contain directories - case "dir": - case "directory": - ReadDirectory(reader.ReadSubtree(), parent, filename, indexId, keep); - - // Skip the directory node now that we've processed it - reader.Read(); - break; - - case "file": - empty = false; - - // If the rom is itemStatus, flag it - its = ItemStatus.None; - flagreader = reader.ReadSubtree(); - - // If the subtree is empty, skip it - if (flagreader == null) - { - reader.Skip(); - continue; - } - - while (!flagreader.EOF) - { - // We only want elements - if (flagreader.NodeType != XmlNodeType.Element || flagreader.Name == "flags") - { - flagreader.Read(); - continue; - } - - switch (flagreader.Name) - { - case "flag": - if (flagreader.GetAttribute("name") != null && flagreader.GetAttribute("value") != null) - { - content = flagreader.GetAttribute("value"); - its = flagreader.GetAttribute("name").AsItemStatus(); - } - break; - } - - flagreader.Read(); - } - - // If the rom has a Date attached, read it in and then sanitize it - date = Sanitizer.CleanDate(reader.GetAttribute("date")); - - // Take care of hex-sized files - size = Sanitizer.CleanLong(reader.GetAttribute("size")); - - Machine dir = new Machine - { - // Get the name of the game from the parent - Name = string.Join("\\", parent), - Description = string.Join("\\", parent), - }; - - DatItem datItem; - switch (reader.GetAttribute("type").ToLowerInvariant()) - { - case "adjuster": - datItem = new Adjuster - { - Name = reader.GetAttribute("name"), - Default = reader.GetAttribute("default").AsYesNo(), - Conditions = new List(), - }; - - // Now read the internal tags - ReadAdjuster(reader.ReadSubtree(), datItem); - - // Skip the adjuster now that we've processed it - reader.Skip(); - break; - - case "archive": - datItem = new Archive - { - Name = reader.GetAttribute("name"), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - break; - - case "biosset": - datItem = new BiosSet - { - Name = reader.GetAttribute("name"), - Description = reader.GetAttribute("description"), - Default = reader.GetAttribute("default").AsYesNo(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - break; - - case "chip": - datItem = new Chip - { - Name = reader.GetAttribute("name"), - Tag = reader.GetAttribute("tag"), - ChipType = reader.GetAttribute("chiptype").AsChipType(), - Clock = Sanitizer.CleanLong(reader.GetAttribute("clock")), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - break; - - case "configuration": - datItem = new Configuration - { - Name = reader.GetAttribute("name"), - Tag = reader.GetAttribute("tag"), - Mask = reader.GetAttribute("mask"), - Conditions = new List(), - Locations = new List(), - Settings = new List(), - }; - - // Now read the internal tags - ReadConfiguration(reader.ReadSubtree(), datItem); - - // Skip the configuration now that we've processed it - reader.Skip(); - break; - - case "device_ref": - datItem = new DeviceReference - { - Name = reader.GetAttribute("name"), - }; - - reader.Read(); - break; - - case "dipswitch": - datItem = new DipSwitch - { - Name = reader.GetAttribute("name"), - Tag = reader.GetAttribute("tag"), - Mask = reader.GetAttribute("mask"), - Conditions = new List(), - Locations = new List(), - Values = new List(), - }; - - // Now read the internal tags - ReadDipSwitch(reader.ReadSubtree(), datItem); - - // Skip the dipswitch now that we've processed it - reader.Skip(); - break; - - case "disk": - datItem = new Disk - { - Name = reader.GetAttribute("name"), - MD5 = reader.GetAttribute("md5"), - SHA1 = reader.GetAttribute("sha1"), - ItemStatus = its, - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - break; - - case "media": - datItem = new Media - { - Name = reader.GetAttribute("name"), - MD5 = reader.GetAttribute("md5"), - SHA1 = reader.GetAttribute("sha1"), - SHA256 = reader.GetAttribute("sha256"), - SpamSum = reader.GetAttribute("spamsum"), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - break; - - case "release": - datItem = new Release - { - Name = reader.GetAttribute("name"), - Region = reader.GetAttribute("region"), - Language = reader.GetAttribute("language"), - Date = reader.GetAttribute("date"), - Default = reader.GetAttribute("default").AsYesNo(), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - break; - - case "rom": - datItem = new Rom - { - Name = reader.GetAttribute("name"), - Size = size, - CRC = reader.GetAttribute("crc"), - MD5 = reader.GetAttribute("md5"), -#if NET_FRAMEWORK - RIPEMD160 = reader.GetAttribute("ripemd160"), -#endif - SHA1 = reader.GetAttribute("sha1"), - SHA256 = reader.GetAttribute("sha256"), - SHA384 = reader.GetAttribute("sha384"), - SHA512 = reader.GetAttribute("sha512"), - SpamSum = reader.GetAttribute("spamsum"), - ItemStatus = its, - Date = date, - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - break; - - case "sample": - datItem = new Sample - { - Name = reader.GetAttribute("name"), - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - break; - - default: - // By default, create a new Blank, just in case - datItem = new Blank(); - break; - } - - datItem?.CopyMachineInformation(dir); - - // Now process and add the rom - key = ParseAddHelper(datItem); - - reader.Read(); - break; - } } - - return empty; - } - - /// - /// Read flags information - /// - /// XmlReader to use to parse the header - /// True if superdat has already been set externally, false otherwise - private void ReadFlags(XmlReader reader, bool superdat) - { - // Prepare all internal variables - string content; - - // If we somehow have a null flag section, skip it - if (reader == null) - return; - - while (!reader.EOF) + catch (Exception ex) { - // We only want elements - if (reader.NodeType != XmlNodeType.Element || reader.Name == "flags") - { - reader.Read(); - continue; - } + Globals.Logger.Warning($"Exception found while parsing '{filename}': {ex}"); - switch (reader.Name) - { - case "flag": - if (reader.GetAttribute("name") != null && reader.GetAttribute("value") != null) - { - content = reader.GetAttribute("value"); - switch (reader.GetAttribute("name").ToLowerInvariant()) - { - case "type": - Header.Type = (Header.Type == null ? content : Header.Type); - superdat = superdat || content.Contains("SuperDAT"); - break; - - case "forcemerging": - if (Header.ForceMerging == MergingFlag.None) - Header.ForceMerging = content.AsMergingFlag(); - - break; - - case "forcenodump": - if (Header.ForceNodump == NodumpFlag.None) - Header.ForceNodump = content.AsNodumpFlag(); - - break; - - case "forcepacking": - if (Header.ForcePacking == PackingFlag.None) - Header.ForcePacking = content.AsPackingFlag(); - - break; - } - } - - reader.Read(); - break; - - default: - reader.Read(); - break; - } + // For XML errors, just skip the affected node + xtr?.Read(); } } /// - /// Read Adjuster information + /// Read Files information /// - /// XmlReader representing a diskarea block - /// Adjuster to populate - private void ReadAdjuster(XmlReader reader, DatItem adjuster) + /// XmlReader to use to parse the header + /// Machine to copy information from + /// Name of the file to be parsed + /// Index ID for the DAT + private void ReadFiles(XmlReader xtr, Machine machine, string filename, int indexId) { - // If we have an empty port, skip it - if (reader == null) + // If the reader is invalid, skip + if (xtr == null) return; - // If the DatItem isn't an Adjuster, skip it - if (adjuster.ItemType != ItemType.Adjuster) - return; - - // Get list ready - (adjuster as Adjuster).Conditions = new List(); - - // Otherwise, add what is possible - reader.MoveToContent(); - - while (!reader.EOF) + // Otherwise, read the items + try { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) + xtr.MoveToContent(); + while (!xtr.EOF) { - reader.Read(); - continue; - } + // We only want elements + if (xtr.NodeType != XmlNodeType.Element) + { + xtr.Read(); + continue; + } - // Get the information from the adjuster - switch (reader.Name) - { - case "condition": - var condition = new Condition(); - condition.Tag = reader.GetAttribute("tag"); - condition.Mask = reader.GetAttribute("mask"); - condition.Relation = reader.GetAttribute("relation").AsRelation(); - condition.Value = reader.GetAttribute("value"); - - (adjuster as Adjuster).Conditions.Add(condition); - - reader.Read(); - break; - - default: - reader.Read(); - break; + switch (xtr.Name) + { + case "datitem": + XmlSerializer xs = new XmlSerializer(typeof(DatItem)); + DatItem item = xs.Deserialize(xtr.ReadSubtree()) as DatItem; + item.CopyMachineInformation(machine); + item.Source = new Source { Name = filename, Index = indexId }; + ParseAddHelper(item); + xtr.Skip(); + break; + default: + xtr.Read(); + break; + } } } - } - - /// - /// Read Configuration information - /// - /// XmlReader representing a diskarea block - /// Configuration to populate - private void ReadConfiguration(XmlReader reader, DatItem configuration) - { - // If we have an empty configuration, skip it - if (reader == null) - return; - - // If the DatItem isn't an Configuration, skip it - if (configuration.ItemType != ItemType.Configuration) - return; - - // Get lists ready - (configuration as Configuration).Conditions = new List(); - (configuration as Configuration).Locations = new List(); - (configuration as Configuration).Settings = new List(); - - // Otherwise, add what is possible - reader.MoveToContent(); - - while (!reader.EOF) + catch (Exception ex) { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } + Globals.Logger.Warning($"Exception found while parsing '{filename}': {ex}"); - // Get the information from the dipswitch - switch (reader.Name) - { - case "condition": - var condition = new Condition(); - condition.Tag = reader.GetAttribute("tag"); - condition.Mask = reader.GetAttribute("mask"); - condition.Relation = reader.GetAttribute("relation").AsRelation(); - condition.Value = reader.GetAttribute("value"); - - (configuration as Configuration).Conditions.Add(condition); - - reader.Read(); - break; - - case "conflocation": - var confLocation = new Location(); - confLocation.Name = reader.GetAttribute("name"); - confLocation.Number = Sanitizer.CleanLong(reader.GetAttribute("number")); - confLocation.Inverted = reader.GetAttribute("inverted").AsYesNo(); - - (configuration as Configuration).Locations.Add(confLocation); - - reader.Read(); - break; - - case "confsetting": - var confSetting = new Setting(); - confSetting.Name = reader.GetAttribute("name"); - confSetting.Value = reader.GetAttribute("value"); - confSetting.Default = reader.GetAttribute("default").AsYesNo(); - - // Now read the internal tags - ReadConfSetting(reader, confSetting); - - (configuration as 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 - /// ListXmlConfSetting 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(); - condition.Tag = reader.GetAttribute("tag"); - condition.Mask = reader.GetAttribute("mask"); - condition.Relation = reader.GetAttribute("relation").AsRelation(); - condition.Value = reader.GetAttribute("value"); - - confSetting.Conditions.Add(condition); - - reader.Read(); - break; - - default: - reader.Read(); - break; - } - } - } - - /// - /// Read DipSwitch information - /// - /// XmlReader representing a diskarea block - /// DipSwitch to populate - private void ReadDipSwitch(XmlReader reader, DatItem dipSwitch) - { - // If we have an empty dipswitch, skip it - if (reader == null) - return; - - // If the DatItem isn't an DipSwitch, skip it - if (dipSwitch.ItemType != ItemType.DipSwitch) - return; - - // Get lists ready - (dipSwitch as DipSwitch).Conditions = new List(); - (dipSwitch as DipSwitch).Locations = new List(); - (dipSwitch as 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(); - condition.Tag = reader.GetAttribute("tag"); - condition.Mask = reader.GetAttribute("mask"); - condition.Relation = reader.GetAttribute("relation").AsRelation(); - condition.Value = reader.GetAttribute("value"); - - (dipSwitch as DipSwitch).Conditions.Add(condition); - - reader.Read(); - break; - - case "diplocation": - var dipLocation = new Location(); - dipLocation.Name = reader.GetAttribute("name"); - dipLocation.Number = Sanitizer.CleanLong(reader.GetAttribute("number")); - dipLocation.Inverted = reader.GetAttribute("inverted").AsYesNo(); - - (dipSwitch as DipSwitch).Locations.Add(dipLocation); - - reader.Read(); - break; - - case "dipvalue": - var dipValue = new Setting(); - dipValue.Name = reader.GetAttribute("name"); - dipValue.Value = reader.GetAttribute("value"); - dipValue.Default = reader.GetAttribute("default").AsYesNo(); - - // Now read the internal tags - ReadDipValue(reader, dipValue); - - (dipSwitch as 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(); - condition.Tag = reader.GetAttribute("tag"); - condition.Mask = reader.GetAttribute("mask"); - condition.Relation = reader.GetAttribute("relation").AsRelation(); - condition.Value = reader.GetAttribute("value"); - - dipValue.Conditions.Add(condition); - - reader.Read(); - break; - - default: - reader.Read(); - break; - } + // For XML errors, just skip the affected node + xtr?.Read(); } } @@ -963,16 +218,14 @@ namespace SabreTools.Library.DatFiles { Formatting = Formatting.Indented, IndentChar = '\t', - Indentation = 1 + Indentation = 1, }; // Write out the header WriteHeader(xtw); // Write out each of the machines and roms - int depth = 2, last = -1; string lastgame = null; - List splitpath = new List(); // Use a sorted list of games to output foreach (string key in Items.SortedKeys) @@ -986,31 +239,28 @@ namespace SabreTools.Library.DatFiles { DatItem datItem = datItems[index]; - List newsplit = datItem.Machine.Name.Split('\\').ToList(); - // 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()) - depth = WriteEndGame(xtw, splitpath, newsplit, depth, out last); + WriteEndGame(xtw); // If we have a new game, output the beginning of the new item if (lastgame == null || lastgame.ToLowerInvariant() != datItem.Machine.Name.ToLowerInvariant()) - depth = WriteStartGame(xtw, datItem, newsplit, depth, last); + 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, depth); + WriteDatItem(xtw, datItem); // Set the new data to compare against - splitpath = newsplit; lastgame = datItem.Machine.Name; } } // Write the file footer out - WriteFooter(xtw, depth); + WriteFooter(xtw); Globals.Logger.Verbose("File written!" + Environment.NewLine); xtw.Dispose(); @@ -1035,65 +285,13 @@ namespace SabreTools.Library.DatFiles try { xtw.WriteStartDocument(); - xtw.WriteDocType("sabredat", null, "newdat.xsd", null); xtw.WriteStartElement("datafile"); - xtw.WriteStartElement("header"); - - xtw.WriteRequiredElementString("name", Header.Name); - xtw.WriteRequiredElementString("description", Header.Description); - xtw.WriteOptionalElementString("rootdir", Header.RootDir); - xtw.WriteOptionalElementString("category", Header.Category); - xtw.WriteRequiredElementString("version", Header.Version); - xtw.WriteOptionalElementString("date", Header.Date); - xtw.WriteRequiredElementString("author", Header.Author); - xtw.WriteOptionalElementString("comment", Header.Comment); - if (!string.IsNullOrWhiteSpace(Header.Type) - || Header.ForcePacking != PackingFlag.None - || Header.ForceMerging != MergingFlag.None - || Header.ForceNodump != NodumpFlag.None) - { - xtw.WriteStartElement("flags"); - - if (!string.IsNullOrWhiteSpace(Header.Type)) - { - xtw.WriteStartElement("flag"); - xtw.WriteAttributeString("name", "type"); - xtw.WriteRequiredAttributeString("value", Header.Type); - xtw.WriteEndElement(); - } - - if (Header.ForcePacking != PackingFlag.None) - { - xtw.WriteStartElement("flag"); - xtw.WriteAttributeString("name", "forcepacking"); - xtw.WriteOptionalAttributeString("value", Header.ForcePacking.FromPackingFlag(false)); - xtw.WriteEndElement(); - } - - if (Header.ForceMerging != MergingFlag.None) - { - xtw.WriteStartElement("flag"); - xtw.WriteAttributeString("name", "forcemerging"); - xtw.WriteAttributeString("value", Header.ForceMerging.FromMergingFlag(false)); - xtw.WriteEndElement(); - } - - if (Header.ForceNodump != NodumpFlag.None) - { - xtw.WriteStartElement("flag"); - xtw.WriteAttributeString("name", "forcenodump"); - xtw.WriteAttributeString("value", Header.ForceNodump.FromNodumpFlag()); - xtw.WriteEndElement(); - } - - // End flags - xtw.WriteEndElement(); - } - - // End header - xtw.WriteEndElement(); + XmlSerializer xs = new XmlSerializer(typeof(DatHeader)); + XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); + ns.Add("", ""); + xs.Serialize(xtw, Header, ns); xtw.WriteStartElement("data"); @@ -1113,85 +311,57 @@ namespace SabreTools.Library.DatFiles /// /// XmlTextWriter to output to /// DatItem object to be output - /// Split path representing the parent game (SabreDAT only) - /// Current depth to output file at (SabreDAT only) - /// Last known depth to cycle back from (SabreDAT only) - /// The new depth of the tag - private int WriteStartGame(XmlTextWriter xtw, DatItem datItem, List newsplit, int depth, int last) + /// True if the data was written, false on error + private bool WriteStartGame(XmlTextWriter xtw, DatItem datItem) { try { // No game should start with a path separator datItem.Machine.Name = datItem.Machine.Name?.TrimStart(Path.DirectorySeparatorChar) ?? string.Empty; - // Build the state - for (int i = (last == -1 ? 0 : last); i < newsplit.Count; i++) - { - xtw.WriteStartElement("directory"); - xtw.WriteRequiredAttributeString("name", newsplit[i]); - xtw.WriteRequiredAttributeString("description", newsplit[i]); - } + // Write the machine + xtw.WriteStartElement("directory"); + XmlSerializer xs = new XmlSerializer(typeof(Machine)); + XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); + ns.Add("", ""); + xs.Serialize(xtw, datItem.Machine, ns); - depth = depth - (last == -1 ? 0 : last) + newsplit.Count; + xtw.WriteStartElement("files"); xtw.Flush(); } catch (Exception ex) { Globals.Logger.Error(ex.ToString()); - return depth; + return false; } - return depth; + return true; } /// /// Write out Game start using the supplied StreamWriter /// /// XmlTextWriter to output to - /// Split path representing last kwown parent game (SabreDAT only) - /// Split path representing the parent game (SabreDAT only) - /// Current depth to output file at (SabreDAT only) - /// Last known depth to cycle back from (SabreDAT only) - /// The new depth of the tag - private int WriteEndGame(XmlTextWriter xtw, List splitpath, List newsplit, int depth, out int last) + private bool WriteEndGame(XmlTextWriter xtw) { - last = 0; - try { - if (splitpath != null) - { - for (int i = 0; i < newsplit.Count && i < splitpath.Count; i++) - { - // Always keep track of the last seen item - last = i; + // End files + xtw.WriteEndElement(); - // If we find a difference, break - if (newsplit[i] != splitpath[i]) - break; - } - - // Now that we have the last known position, take down all open folders - for (int i = depth - 1; i > last + 1; i--) - { - // End directory - xtw.WriteEndElement(); - } - - // Reset the current depth - depth = 2 + last; - } + // End directory + xtw.WriteEndElement(); xtw.Flush(); } catch (Exception ex) { Globals.Logger.Error(ex.ToString()); - return depth; + return false; } - return depth; + return true; } /// @@ -1199,460 +369,19 @@ namespace SabreTools.Library.DatFiles /// /// XmlTextWriter to output to /// DatItem object to be output - /// Current depth to output file at (SabreDAT only) /// True if the data was written, false on error - private bool WriteDatItem(XmlTextWriter xtw, DatItem datItem, int depth) + private bool WriteDatItem(XmlTextWriter xtw, DatItem datItem) { try { // Pre-process the item name ProcessItemName(datItem, true); - // Build the state - switch (datItem.ItemType) - { - case ItemType.Adjuster: - var adjuster = datItem as Adjuster; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "adjuster"); - xtw.WriteRequiredAttributeString("name", adjuster.Name); - xtw.WriteOptionalAttributeString("default", adjuster.Default.FromYesNo()); - if (adjuster.Conditions != null) - { - foreach (var adjusterCondition in adjuster.Conditions) - { - xtw.WriteStartElement("condition"); - xtw.WriteOptionalAttributeString("tag", adjusterCondition.Tag); - xtw.WriteOptionalAttributeString("mask", adjusterCondition.Mask); - xtw.WriteOptionalAttributeString("relation", adjusterCondition.Relation.FromRelation()); - xtw.WriteOptionalAttributeString("value", adjusterCondition.Value); - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - break; - - case ItemType.Archive: - var archive = datItem as Archive; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "archive"); - xtw.WriteRequiredAttributeString("name", archive.Name); - xtw.WriteEndElement(); - break; - - case ItemType.BiosSet: - var biosSet = datItem as BiosSet; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "biosset"); - xtw.WriteRequiredAttributeString("name", biosSet.Name); - xtw.WriteOptionalAttributeString("description", biosSet.Description); - xtw.WriteOptionalAttributeString("default", biosSet.Default.FromYesNo()); - xtw.WriteEndElement(); - break; - - case ItemType.Chip: - var chip = datItem as Chip; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "chip"); - xtw.WriteRequiredAttributeString("name", chip.Name); - xtw.WriteOptionalAttributeString("tag", chip.Tag); - xtw.WriteOptionalAttributeString("chiptype", chip.ChipType.FromChipType()); - xtw.WriteOptionalAttributeString("clock", chip.Clock.ToString()); - xtw.WriteEndElement(); - break; - - case ItemType.Condition: - var condition = datItem as Condition; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "condition"); - xtw.WriteOptionalAttributeString("tag", condition.Tag); - xtw.WriteOptionalAttributeString("mask", condition.Mask); - xtw.WriteOptionalAttributeString("relation", condition.Relation.FromRelation()); - xtw.WriteOptionalAttributeString("value", condition.Value); - xtw.WriteEndElement(); - break; - - case ItemType.Configuration: - var configuration = datItem as Configuration; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "configuration"); - xtw.WriteOptionalAttributeString("name", configuration.Name); - xtw.WriteOptionalAttributeString("tag", configuration.Tag); - xtw.WriteOptionalAttributeString("mask", configuration.Mask); - - if (configuration.Conditions != null) - { - foreach (var configurationCondition in configuration.Conditions) - { - xtw.WriteStartElement("condition"); - xtw.WriteOptionalAttributeString("tag", configurationCondition.Tag); - xtw.WriteOptionalAttributeString("mask", configurationCondition.Mask); - xtw.WriteOptionalAttributeString("relation", configurationCondition.Relation.FromRelation()); - xtw.WriteOptionalAttributeString("value", configurationCondition.Value); - xtw.WriteEndElement(); - } - } - if (configuration.Locations != null) - { - foreach (var location in configuration.Locations) - { - xtw.WriteStartElement("conflocation"); - xtw.WriteOptionalAttributeString("name", location.Name); - xtw.WriteOptionalAttributeString("number", location.Number?.ToString()); - xtw.WriteOptionalAttributeString("inverted", location.Inverted.FromYesNo()); - xtw.WriteEndElement(); - } - } - if (configuration.Settings != null) - { - foreach (var setting in configuration.Settings) - { - xtw.WriteStartElement("confsetting"); - xtw.WriteOptionalAttributeString("name", setting.Name); - xtw.WriteOptionalAttributeString("value", setting.Value); - xtw.WriteOptionalAttributeString("default", setting.Default.FromYesNo()); - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - break; - - case ItemType.Device: - var device = datItem as Device; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "device"); - xtw.WriteOptionalAttributeString("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.Instances != null) - { - foreach (var instance in device.Instances) - { - xtw.WriteStartElement("instance"); - xtw.WriteOptionalAttributeString("name", instance.Name); - xtw.WriteOptionalAttributeString("briefname", instance.BriefName); - xtw.WriteEndElement(); - } - } - if (device.Extensions != null) - { - foreach (var extension in device.Extensions) - { - xtw.WriteStartElement("extension"); - xtw.WriteOptionalAttributeString("name", extension.Name); - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - break; - - case ItemType.DeviceReference: - var deviceRef = datItem as DeviceReference; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "device_ref"); - xtw.WriteRequiredAttributeString("name", deviceRef.Name); - xtw.WriteEndElement(); - break; - - case ItemType.DipSwitch: - var dipSwitch = datItem as DipSwitch; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "dipswitch"); - xtw.WriteOptionalAttributeString("name", dipSwitch.Name); - xtw.WriteOptionalAttributeString("tag", dipSwitch.Tag); - xtw.WriteOptionalAttributeString("mask", dipSwitch.Mask); - if (dipSwitch.Conditions != null) - { - foreach (var dipSwitchCondition in dipSwitch.Conditions) - { - xtw.WriteStartElement("condition"); - xtw.WriteOptionalAttributeString("tag", dipSwitchCondition.Tag); - xtw.WriteOptionalAttributeString("mask", dipSwitchCondition.Mask); - xtw.WriteOptionalAttributeString("relation", dipSwitchCondition.Relation.FromRelation()); - xtw.WriteOptionalAttributeString("value", dipSwitchCondition.Value); - xtw.WriteEndElement(); - } - } - if (dipSwitch.Locations != null) - { - foreach (var location in dipSwitch.Locations) - { - xtw.WriteStartElement("diplocation"); - xtw.WriteOptionalAttributeString("name", location.Name); - xtw.WriteOptionalAttributeString("number", location.Number?.ToString()); - xtw.WriteOptionalAttributeString("inverted", location.Inverted.FromYesNo()); - xtw.WriteEndElement(); - } - } - if (dipSwitch.Values != null) - { - foreach (var value in dipSwitch.Values) - { - xtw.WriteStartElement("dipvalue"); - xtw.WriteOptionalAttributeString("name", value.Name); - xtw.WriteOptionalAttributeString("value", value.Value); - xtw.WriteOptionalAttributeString("default", value.Default.FromYesNo()); - if (value.Conditions != null) - { - foreach (var dipValueCondition in value.Conditions) - { - xtw.WriteStartElement("condition"); - xtw.WriteOptionalAttributeString("tag", dipValueCondition.Tag); - xtw.WriteOptionalAttributeString("mask", dipValueCondition.Mask); - xtw.WriteOptionalAttributeString("relation", dipValueCondition.Relation.FromRelation()); - xtw.WriteOptionalAttributeString("value", dipValueCondition.Value); - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - break; - - case ItemType.Disk: - var disk = datItem as Disk; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "disk"); - xtw.WriteRequiredAttributeString("name", disk.Name); - xtw.WriteOptionalAttributeString("md5", disk.MD5?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha1", disk.SHA1?.ToLowerInvariant()); - if (disk.ItemStatus != ItemStatus.None) - { - xtw.WriteStartElement("flags"); - - xtw.WriteStartElement("flag"); - xtw.WriteAttributeString("name", "status"); - xtw.WriteAttributeString("value", disk.ItemStatus.FromItemStatus(false)); - xtw.WriteEndElement(); - - // End flags - xtw.WriteEndElement(); - } - xtw.WriteEndElement(); - break; - - case ItemType.Display: - var display = datItem as Display; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "display"); - xtw.WriteOptionalAttributeString("tag", display.Tag); - xtw.WriteOptionalAttributeString("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.WriteOptionalAttributeString("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("file"); - xtw.WriteAttributeString("type", "driver"); - xtw.WriteOptionalAttributeString("status", driver.Status.FromSupportStatus()); - xtw.WriteOptionalAttributeString("emulation", driver.Emulation.FromSupportStatus()); - xtw.WriteOptionalAttributeString("cocktail", driver.Cocktail.FromSupportStatus()); - xtw.WriteOptionalAttributeString("savestate", driver.SaveState.FromSupported(true)); - xtw.WriteEndElement(); - break; - - case ItemType.Feature: - var feature = datItem as Feature; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "feature"); - xtw.WriteOptionalAttributeString("type", feature.Type.FromFeatureType()); - xtw.WriteOptionalAttributeString("status", feature.Status.FromFeatureStatus()); - xtw.WriteOptionalAttributeString("overall", feature.Overall.FromFeatureStatus()); - xtw.WriteEndElement(); - break; - - case ItemType.Info: - var info = datItem as Info; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "info"); - xtw.WriteRequiredAttributeString("name", info.Name); - xtw.WriteRequiredAttributeString("value", info.Value); - xtw.WriteEndElement(); - break; - - case ItemType.Input: - var input = datItem as Input; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "input"); - xtw.WriteOptionalAttributeString("service", input.Service.FromYesNo()); - xtw.WriteOptionalAttributeString("tilt", input.Tilt.FromYesNo()); - xtw.WriteOptionalAttributeString("players", input.Players?.ToString()); - xtw.WriteOptionalAttributeString("coins", input.Coins?.ToString()); - if (input.Controls != null) - { - foreach (var control in input.Controls) - { - xtw.WriteStartElement("control"); - xtw.WriteOptionalAttributeString("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.Media: - var media = datItem as Media; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "media"); - xtw.WriteRequiredAttributeString("name", media.Name); - xtw.WriteOptionalAttributeString("md5", media.MD5?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha1", media.SHA1?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("sha256", media.SHA256?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("spamsum", media.SpamSum?.ToLowerInvariant()); - xtw.WriteEndElement(); - break; - - case ItemType.Port: - var port = datItem as Port; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "port"); - xtw.WriteOptionalAttributeString("tag", port.Tag); - if (port.Analogs != null) - { - foreach (var analog in port.Analogs) - { - xtw.WriteStartElement("analog"); - xtw.WriteOptionalAttributeString("mask", analog.Mask); - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - break; - - case ItemType.RamOption: - var ramOption = datItem as RamOption; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "ramoption"); - xtw.WriteRequiredAttributeString("name", ramOption.Name); - xtw.WriteOptionalAttributeString("default", ramOption.Default.FromYesNo()); - xtw.WriteRaw(ramOption.Content ?? string.Empty); - xtw.WriteFullEndElement(); - break; - - case ItemType.Release: - var release = datItem as Release; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "release"); - xtw.WriteRequiredAttributeString("name", release.Name); - xtw.WriteOptionalAttributeString("region", release.Region); - xtw.WriteOptionalAttributeString("language", release.Language); - xtw.WriteOptionalAttributeString("date", release.Date); - xtw.WriteOptionalAttributeString("default", release.Default.FromYesNo()); - xtw.WriteEndElement(); - break; - - case ItemType.Rom: - var rom = datItem as Rom; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "rom"); - xtw.WriteRequiredAttributeString("name", rom.Name); - xtw.WriteOptionalAttributeString("size", rom.Size?.ToString()); - xtw.WriteOptionalAttributeString("crc", rom.CRC?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("md5", rom.MD5?.ToLowerInvariant()); -#if NET_FRAMEWORK - xtw.WriteOptionalAttributeString("ripemd160", rom.RIPEMD160?.ToLowerInvariant()); -#endif - 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("spamsum", rom.SpamSum?.ToLowerInvariant()); - xtw.WriteOptionalAttributeString("date", rom.Date); - if (rom.ItemStatus != ItemStatus.None) - { - xtw.WriteStartElement("flags"); - - xtw.WriteStartElement("flag"); - xtw.WriteAttributeString("name", "status"); - xtw.WriteAttributeString("value", rom.ItemStatus.FromItemStatus(false)); - xtw.WriteEndElement(); - - // End flags - xtw.WriteEndElement(); - } - xtw.WriteEndElement(); - break; - - case ItemType.Sample: - var sample = datItem as Sample; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "sample"); - xtw.WriteRequiredAttributeString("name", sample.Name); - xtw.WriteEndElement(); - break; - - case ItemType.SharedFeature: - var sharedFeature = datItem as SharedFeature; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "sharedfeat"); - xtw.WriteRequiredAttributeString("name", sharedFeature.Name); - xtw.WriteRequiredAttributeString("value", sharedFeature.Value); - xtw.WriteEndElement(); - break; - - case ItemType.Slot: - var slot = datItem as Slot; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "slot"); - xtw.WriteOptionalAttributeString("name", slot.Name); - if (slot.SlotOptions != null) - { - foreach (var slotOption in slot.SlotOptions) - { - xtw.WriteStartElement("slotoption"); - xtw.WriteOptionalAttributeString("name", slotOption.Name); - xtw.WriteOptionalAttributeString("devname", slotOption.DeviceName); - xtw.WriteOptionalAttributeString("default", slotOption.Default.FromYesNo()); - xtw.WriteEndElement(); - } - } - xtw.WriteEndElement(); - break; - - case ItemType.SoftwareList: - var softwareList = datItem as DatItems.SoftwareList; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "softwarelist"); - xtw.WriteRequiredAttributeString("name", softwareList.Name); - xtw.WriteOptionalAttributeString("status", softwareList.Status.FromSoftwareListStatus()); - xtw.WriteOptionalAttributeString("sha512", softwareList.Filter); - xtw.WriteEndElement(); - break; - - case ItemType.Sound: - var sound = datItem as Sound; - xtw.WriteStartElement("file"); - xtw.WriteAttributeString("type", "sound"); - xtw.WriteOptionalAttributeString("channels", sound.Channels?.ToString()); - xtw.WriteEndElement(); - break; - } + // Write the DatItem + XmlSerializer xs = new XmlSerializer(typeof(DatItem)); + XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); + ns.Add("", ""); + xs.Serialize(xtw, datItem, ns); xtw.Flush(); } @@ -1669,17 +398,16 @@ namespace SabreTools.Library.DatFiles /// Write out DAT footer using the supplied StreamWriter /// /// XmlTextWriter to output to - /// Current depth to output file at (SabreDAT only) /// True if the data was written, false on error - private bool WriteFooter(XmlTextWriter xtw, int depth) + private bool WriteFooter(XmlTextWriter xtw) { try { - for (int i = depth - 1; i >= 2; i--) - { - // End directory - xtw.WriteEndElement(); - } + // End files + xtw.WriteEndElement(); + + // End directory + xtw.WriteEndElement(); // End data xtw.WriteEndElement();