diff --git a/SabreTools.DatFiles/Formats/OfflineList.Reader.cs b/SabreTools.DatFiles/Formats/OfflineList.Reader.cs index 08955927..897e02a8 100644 --- a/SabreTools.DatFiles/Formats/OfflineList.Reader.cs +++ b/SabreTools.DatFiles/Formats/OfflineList.Reader.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; -using System.Xml; -using System.Xml.Schema; +using System.Linq; using SabreTools.Core; using SabreTools.Core.Tools; using SabreTools.DatItems; @@ -17,619 +16,362 @@ namespace SabreTools.DatFiles.Formats /// public override void ParseFile(string filename, int indexId, bool keep, bool statsOnly = false, bool throwOnError = false) { - XmlReader xtr = XmlReader.Create(filename, new XmlReaderSettings - { - CheckCharacters = false, - DtdProcessing = DtdProcessing.Ignore, - IgnoreComments = true, - IgnoreWhitespace = true, - ValidationFlags = XmlSchemaValidationFlags.None, - ValidationType = ValidationType.None, - }); - - // If we got a null reader, just return - if (xtr == null) - return; - - // Otherwise, read the file to the end try { - xtr.MoveToContent(); - while (!xtr.EOF) - { - // We only want elements - if (xtr.NodeType != XmlNodeType.Element) - { - xtr.Read(); - continue; - } + // Deserialize the input file + var dat = Serialization.OfflineList.Deserialize(filename); - switch (xtr.Name) - { - case "configuration": - ReadConfiguration(xtr.ReadSubtree(), keep); + // Convert the header to the internal format + ConvertHeader(dat); - // Skip the configuration node now that we've processed it - xtr.Skip(); - break; + // Convert the configuration to the internal format + ConvertConfiguration(dat.Configuration, keep); - case "games": - ReadGames(xtr.ReadSubtree(), statsOnly, filename, indexId); + // Convert the games to the internal format + ConvertGames(dat?.Games, filename, indexId, statsOnly); - // Skip the games node now that we've processed it - xtr.Skip(); - break; - - default: - xtr.Read(); - break; - } - } + // Convert the GUI to the internal format + ConvertGUI(dat?.GUI); } catch (Exception ex) when (!throwOnError) { - logger.Warning(ex, $"Exception found while parsing '{filename}'"); - - // For XML errors, just skip the affected node - xtr?.Read(); + string message = $"'{filename}' - An error occurred during parsing"; + logger.Error(ex, message); } - - xtr.Dispose(); } - /// - /// Read configuration information - /// - /// XmlReader to use to parse the header - /// True if full pathnames are to be kept, false otherwise (default) - private void ReadConfiguration(XmlReader reader, bool keep) - { - bool superdat = false; + #region Converters - // If there's no subtree to the configuration, skip it - if (reader == null) + /// + /// Convert header information + /// + /// Deserialized model to convert + private void ConvertHeader(Models.OfflineList.Dat? dat) + { + // If the datafile is missing, we can't do anything + if (dat == null) return; - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all configuration items (ONLY OVERWRITE IF THERE'S NO DATA) - string content; - switch (reader.Name.ToLowerInvariant()) - { - case "datname": - content = reader.ReadElementContentAsString(); - Header.Name ??= content; - superdat |= content.Contains(" - SuperDAT"); - if (keep && superdat) - { - Header.Type ??= "SuperDAT"; - } - break; - - case "datversion": - content = reader.ReadElementContentAsString(); - Header.Version ??= content; - break; - - case "system": - content = reader.ReadElementContentAsString(); - Header.System ??= content; - break; - - // TODO: Int32? - case "screenshotswidth": - content = reader.ReadElementContentAsString(); - Header.ScreenshotsWidth ??= content; - break; - - // TODO: Int32? - case "screenshotsheight": - content = reader.ReadElementContentAsString(); - Header.ScreenshotsHeight ??= content; - break; - - case "infos": - ReadInfos(reader.ReadSubtree()); - - // Skip the infos node now that we've processed it - reader.Skip(); - break; - - case "canopen": - ReadCanOpen(reader.ReadSubtree()); - - // Skip the canopen node now that we've processed it - reader.Skip(); - break; - - // TODO: Use all header values - case "newdat": - ReadNewDat(reader.ReadSubtree()); - - // Skip the newdat node now that we've processed it - reader.Skip(); - break; - - // TODO: Use header values - case "search": - ReadSearch(reader.ReadSubtree()); - - // Skip the search node now that we've processed it - reader.Skip(); - break; - - case "romtitle": - content = reader.ReadElementContentAsString(); - Header.RomTitle ??= content; - break; - - default: - reader.Read(); - break; - } - } + //Header.NoNamespaceSchemaLocation = dat.NoNamespaceSchemaLocation; // TODO: Add to internal model } /// - /// Read infos information + /// Convert configuration information /// - /// XmlReader to use to parse the header - private void ReadInfos(XmlReader reader) + /// Deserialized model to convert + private void ConvertConfiguration(Models.OfflineList.Configuration? config, bool keep) { - // If there's no subtree to the configuration, skip it - if (reader == null) + // If the config is missing, we can't do anything + if (config == null) return; - // Setup the infos object - Header.Infos = new List(); + Header.Name ??= config.DatName; + //Header.ImFolder ??= config.ImFolder; // TODO: Add to internal model + Header.Version = config.DatVersion; + Header.System = config.System; + Header.ScreenshotsWidth = config.ScreenshotsWidth; + Header.ScreenshotsHeight = config.ScreenshotsHeight; + ConvertInfos(config.Infos); + ConvertCanOpen(config.CanOpen); + ConvertNewDat(config.NewDat); + ConvertSearch(config.Search); + Header.RomTitle = config.RomTitle; - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Add all infos to the info list - switch (reader.Name.ToLowerInvariant()) - { - default: - var info = new OfflineListInfo - { - Name = reader.Name.ToLowerInvariant(), - Visible = reader.GetAttribute("visible").AsYesNo(), - InNamingOption = reader.GetAttribute("inNamingOption").AsYesNo(), - Default = reader.GetAttribute("default").AsYesNo() - }; - - Header.Infos.Add(info); - - reader.Read(); - break; - } - } + // Handle implied SuperDAT + if (config.DatName.Contains(" - SuperDAT") && keep) + Header.Type ??= "SuperDAT"; } /// - /// Read canopen information + /// Convert infos information /// - /// XmlReader to use to parse the header - private void ReadCanOpen(XmlReader reader) + /// Deserialized model to convert + private void ConvertInfos(Models.OfflineList.Infos? infos) { - // Prepare all internal variables - Header.CanOpen = new List(); - - // If there's no subtree to the configuration, skip it - if (reader == null) + // If the infos is missing, we can't do anything + if (infos == null) return; - // Otherwise, add what is possible - reader.MoveToContent(); + var offlineListInfos = new List(); - // Otherwise, read what we can from the header - while (!reader.EOF) + if (infos.Title != null) { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) + offlineListInfos.Add(new OfflineListInfo { - reader.Read(); - continue; - } - - // Get all canopen items - switch (reader.Name.ToLowerInvariant()) - { - case "extension": - Header.CanOpen.Add(reader.ReadElementContentAsString()); - break; - - default: - reader.Read(); - break; - } + Name = "title", + Visible = infos.Title.Visible.AsYesNo(), + InNamingOption = infos.Title.InNamingOption.AsYesNo(), + Default = infos.Title.Default.AsYesNo(), + }); } + if (infos.Location != null) + { + offlineListInfos.Add(new OfflineListInfo + { + Name = "location", + Visible = infos.Location.Visible.AsYesNo(), + InNamingOption = infos.Location.InNamingOption.AsYesNo(), + Default = infos.Location.Default.AsYesNo(), + }); + } + if (infos.Publisher != null) + { + offlineListInfos.Add(new OfflineListInfo + { + Name = "publisher", + Visible = infos.Publisher.Visible.AsYesNo(), + InNamingOption = infos.Publisher.InNamingOption.AsYesNo(), + Default = infos.Publisher.Default.AsYesNo(), + }); + } + if (infos.SourceRom != null) + { + offlineListInfos.Add(new OfflineListInfo + { + Name = "sourceRom", + Visible = infos.SourceRom.Visible.AsYesNo(), + InNamingOption = infos.SourceRom.InNamingOption.AsYesNo(), + Default = infos.SourceRom.Default.AsYesNo(), + }); + } + if (infos.SaveType != null) + { + offlineListInfos.Add(new OfflineListInfo + { + Name = "saveType", + Visible = infos.SaveType.Visible.AsYesNo(), + InNamingOption = infos.SaveType.InNamingOption.AsYesNo(), + Default = infos.SaveType.Default.AsYesNo(), + }); + } + if (infos.RomSize != null) + { + offlineListInfos.Add(new OfflineListInfo + { + Name = "romSize", + Visible = infos.RomSize.Visible.AsYesNo(), + InNamingOption = infos.RomSize.InNamingOption.AsYesNo(), + Default = infos.RomSize.Default.AsYesNo(), + }); + } + if (infos.ReleaseNumber != null) + { + offlineListInfos.Add(new OfflineListInfo + { + Name = "releaseNumber", + Visible = infos.ReleaseNumber.Visible.AsYesNo(), + InNamingOption = infos.ReleaseNumber.InNamingOption.AsYesNo(), + Default = infos.ReleaseNumber.Default.AsYesNo(), + }); + } + if (infos.LanguageNumber != null) + { + offlineListInfos.Add(new OfflineListInfo + { + Name = "languageNumber", + Visible = infos.LanguageNumber.Visible.AsYesNo(), + InNamingOption = infos.LanguageNumber.InNamingOption.AsYesNo(), + Default = infos.LanguageNumber.Default.AsYesNo(), + }); + } + if (infos.Comment != null) + { + offlineListInfos.Add(new OfflineListInfo + { + Name = "comment", + Visible = infos.Comment.Visible.AsYesNo(), + InNamingOption = infos.Comment.InNamingOption.AsYesNo(), + Default = infos.Comment.Default.AsYesNo(), + }); + } + if (infos.RomCRC != null) + { + offlineListInfos.Add(new OfflineListInfo + { + Name = "romCRC", + Visible = infos.RomCRC.Visible.AsYesNo(), + InNamingOption = infos.RomCRC.InNamingOption.AsYesNo(), + Default = infos.RomCRC.Default.AsYesNo(), + }); + } + if (infos.Im1CRC != null) + { + offlineListInfos.Add(new OfflineListInfo + { + Name = "im1CRC", + Visible = infos.Im1CRC.Visible.AsYesNo(), + InNamingOption = infos.Im1CRC.InNamingOption.AsYesNo(), + Default = infos.Im1CRC.Default.AsYesNo(), + }); + } + if (infos.Im2CRC != null) + { + offlineListInfos.Add(new OfflineListInfo + { + Name = "im2CRC", + Visible = infos.Im2CRC.Visible.AsYesNo(), + InNamingOption = infos.Im2CRC.InNamingOption.AsYesNo(), + Default = infos.Im2CRC.Default.AsYesNo(), + }); + } + if (infos.Languages != null) + { + offlineListInfos.Add(new OfflineListInfo + { + Name = "languages", + Visible = infos.Languages.Visible.AsYesNo(), + InNamingOption = infos.Languages.InNamingOption.AsYesNo(), + Default = infos.Languages.Default.AsYesNo(), + }); + } + + Header.Infos = offlineListInfos; } /// - /// Read newdat information + /// Convert canopen information /// - /// XmlReader to use to parse the header - private void ReadNewDat(XmlReader reader) + /// Deserialized model to convert + private void ConvertCanOpen(Models.OfflineList.CanOpen? canOpen) { - // If there's no subtree to the configuration, skip it - if (reader == null) + // If the canOpen is missing, we can't do anything + if (canOpen == null) return; - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all newdat items - string content; - switch (reader.Name.ToLowerInvariant()) - { - case "datversionurl": - // TODO: Read this into an appropriate field - content = reader.ReadElementContentAsString(); - Header.Url ??= content; - break; - - case "daturl": - // TODO: Read this into an appropriate structure - reader.GetAttribute("fileName"); - reader.ReadElementContentAsString(); - break; - - case "imurl": - // TODO: Read this into an appropriate field - reader.ReadElementContentAsString(); - break; - - default: - reader.Read(); - break; - } - } + Header.CanOpen = new List(canOpen.Extension); } /// - /// Read search information + /// Convert newdat information /// - /// XmlReader to use to parse the header - private void ReadSearch(XmlReader reader) + /// Deserialized model to convert + private void ConvertNewDat(Models.OfflineList.NewDat? newDat) { - // If there's no subtree to the configuration, skip it - if (reader == null) + // If the canOpen is missing, we can't do anything + if (newDat == null) return; - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all search items - switch (reader.Name.ToLowerInvariant()) - { - case "to": - // TODO: Read this into an appropriate structure - reader.GetAttribute("value"); - reader.GetAttribute("default"); // (true|false) - reader.GetAttribute("auto"); // (true|false) - - ReadTo(reader.ReadSubtree()); - - // Skip the to node now that we've processed it - reader.Skip(); - break; - - default: - reader.Read(); - break; - } - } + Header.Url = newDat.DatVersionUrl; + //Header.DatUrl = newDat.DatUrl; // TODO: Add to internal model + //Header.ImUrl = newDat.ImUrl; // TODO: Add to internal model } /// - /// Read to information + /// Convert search information /// - /// XmlReader to use to parse the header - private void ReadTo(XmlReader reader) + /// Deserialized model to convert + private void ConvertSearch(Models.OfflineList.Search? search) { - // If there's no subtree to the configuration, skip it - if (reader == null) + // If the search or to array is missing, we can't do anything + if (search?.To == null) return; - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all search items - switch (reader.Name.ToLowerInvariant()) - { - case "find": - // TODO: Read this into an appropriate structure - reader.GetAttribute("operation"); - reader.GetAttribute("value"); // Int32? - reader.ReadElementContentAsString(); - break; - - default: - reader.Read(); - break; - } - } + // TODO: Add to internal model } /// - /// Read games information + /// Convert games information /// - /// XmlReader to use to parse the header + /// Deserialized model to convert + /// Name of the file to be parsed + /// Index ID for the DAT /// True to only add item statistics while parsing, false otherwise - /// Name of the file to be parsed - /// Index ID for the DAT - private void ReadGames(XmlReader reader, bool statsOnly, string filename, int indexId) + private void ConvertGames(Models.OfflineList.Games? games, string filename, int indexId, bool statsOnly) { - // If there's no subtree to the configuration, skip it - if (reader == null) + // If the games array is missing, we can't do anything + if (games?.Game == null || !games.Game.Any()) return; - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) + foreach (var game in games.Game) { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all games items (ONLY OVERWRITE IF THERE'S NO DATA) - switch (reader.Name.ToLowerInvariant()) - { - case "game": - ReadGame(reader.ReadSubtree(), statsOnly, filename, indexId); - - // Skip the game node now that we've processed it - reader.Skip(); - break; - - default: - reader.Read(); - break; - } + ConvertGame(game, filename, indexId, statsOnly); } } /// - /// Read game information + /// Convert game information /// - /// XmlReader to use to parse the header + /// Deserialized model to convert + /// Name of the file to be parsed + /// Index ID for the DAT /// True to only add item statistics while parsing, false otherwise - /// Name of the file to be parsed - /// Index ID for the DAT - private void ReadGame(XmlReader reader, bool statsOnly, string filename, int indexId) + private void ConvertGame(Models.OfflineList.Game? game, string filename, int indexId, bool statsOnly) { - // Prepare all internal variables - string releaseNumber = string.Empty, duplicateid; - long? size = null; - List datItems = new(); - Machine machine = new(); - - // If there's no subtree to the configuration, skip it - if (reader == null) + // If the game is missing, we can't do anything + if (game == null) return; - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) + var machine = new Machine { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } + //ImageNumber = game.ImageNumber, // TODO: Add to internal model + //ReleaseNumber = game.ReleaseNumber, // TODO: Add to internal model + Name = game.Title, + //SaveType = game.SaveType, // TODO: Add to internal model + Publisher = game.Publisher, + //Location = game.Location, // TODO: Add to internal model + //SourceRom = game.SourceRom, // TODO: Add to internal model + //Language = game.Language, // TODO: Add to internal model + //Im1CRC = game.Im1CRC, // TODO: Add to internal model + //Im2CRC = game.Im2CRC, // TODO: Add to internal model + Comment = game.Comment, + }; - // Get all games items - switch (reader.Name.ToLowerInvariant()) - { - case "imagenumber": - // TODO: Read this into a field - reader.ReadElementContentAsString(); - break; + long? size = Utilities.CleanLong(game.RomSize); + if (game.DuplicateID != "0") + machine.CloneOf = game.DuplicateID; - case "releasenumber": - // TODO: Read this into a field - releaseNumber = reader.ReadElementContentAsString(); - break; + // Check if there are any items + bool containsItems = false; - case "title": - machine.Name = reader.ReadElementContentAsString(); - break; + // Loop through each file + ConvertFiles(game.Files, machine, size, game.ReleaseNumber, filename, indexId, statsOnly, ref containsItems); - case "savetype": - // TODO: Read this into a field - reader.ReadElementContentAsString(); - break; - - case "romsize": - size = Utilities.CleanLong(reader.ReadElementContentAsString()); - break; - - case "publisher": - machine.Publisher = reader.ReadElementContentAsString(); - break; - - case "location": - // TODO: Read this into a field - reader.ReadElementContentAsString(); - break; - - case "sourcerom": - // TODO: Read this into a field - reader.ReadElementContentAsString(); - break; - - case "language": - // TODO: Read this into a field - reader.ReadElementContentAsString(); - break; - - case "files": - datItems = ReadFiles(reader.ReadSubtree(), releaseNumber, machine.Name, filename, indexId); - - // Skip the files node now that we've processed it - reader.Skip(); - break; - - case "im1crc": - // TODO: Read this into a field - reader.ReadElementContentAsString(); - break; - - case "im2crc": - // TODO: Read this into a field - reader.ReadElementContentAsString(); - break; - - case "comment": - machine.Comment = reader.ReadElementContentAsString(); - break; - - case "duplicateid": - duplicateid = reader.ReadElementContentAsString(); - if (duplicateid != "0") - machine.CloneOf = duplicateid; - - break; - - default: - reader.Read(); - break; - } - } - - // Add information accordingly for each rom - for (int i = 0; i < datItems.Count; i++) + // If we had no items, create a Blank placeholder + if (!containsItems) { - datItems[i].Size = size; - datItems[i].CopyMachineInformation(machine); + var blank = new Blank + { + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; - // Now process and add the rom - ParseAddHelper(datItems[i], statsOnly); + blank.CopyMachineInformation(machine); + ParseAddHelper(blank, statsOnly); } } /// - /// Read files information + /// Convert Files information /// - /// XmlReader to use to parse the header - /// Release number from the parent game - /// Name of the parent game to use + /// Array of deserialized models to convert + /// Prefilled machine to use + /// Item size to use + /// Release number to use /// Name of the file to be parsed /// Index ID for the DAT - private List ReadFiles( - XmlReader reader, - string releaseNumber, - string machineName, - - // Standard Dat parsing - string filename, - int indexId) + /// True to only add item statistics while parsing, false otherwise + /// True if there were any items in the array, false otherwise + private void ConvertFiles(Models.OfflineList.Files? files, Machine machine, long? size, string releaseNumber, string filename, int indexId, bool statsOnly, ref bool containsItems) { - // Prepare all internal variables - var extensionToCrc = new List>(); - var roms = new List(); + // If the files array is missing, we can't do anything + if (files?.RomCRC == null || !files.RomCRC.Any()) + return; - // If there's no subtree to the configuration, skip it - if (reader == null) - return roms; - - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) + containsItems = true; + foreach (var crc in files.RomCRC) { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) + string name = string.Empty; + if (!string.IsNullOrWhiteSpace(releaseNumber) && releaseNumber != "0") + name += $"{releaseNumber} - "; + name += $"{machine.Name}{crc.Extension}"; + + var item = new Rom { - reader.Read(); - continue; - } - - // Get all romCRC items - switch (reader.Name.ToLowerInvariant()) - { - case "romcrc": - extensionToCrc.Add( - new KeyValuePair( - reader.GetAttribute("extension") ?? string.Empty, - reader.ReadElementContentAsString().ToLowerInvariant())); - break; - - default: - reader.Read(); - break; - } - } - - // Now process the roms with the proper information - foreach (KeyValuePair pair in extensionToCrc) - { - roms.Add(new Rom() - { - Name = (releaseNumber != "0" ? releaseNumber + " - " : string.Empty) + machineName + pair.Key, - CRC = pair.Value, - + Name = name, + CRC = crc.Content, ItemStatus = ItemStatus.None, Source = new Source @@ -637,10 +379,27 @@ namespace SabreTools.DatFiles.Formats Index = indexId, Name = filename, }, - }); - } + }; - return roms; + item.CopyMachineInformation(machine); + ParseAddHelper(item, statsOnly); + } } + + /// + /// Convert GUI information + /// + /// Deserialized model to convert + private void ConvertGUI(Models.OfflineList.GUI? gui) + { + // If the gui or Images are missing, we can't do anything + if (gui?.Images?.Image == null || !gui.Images.Image.Any()) + return; + + // TODO: Add to internal model + } + + #endregion + } } diff --git a/SabreTools.DatFiles/Formats/OfflineList.Writer.cs b/SabreTools.DatFiles/Formats/OfflineList.Writer.cs index c01eb401..c844c807 100644 --- a/SabreTools.DatFiles/Formats/OfflineList.Writer.cs +++ b/SabreTools.DatFiles/Formats/OfflineList.Writer.cs @@ -27,8 +27,24 @@ namespace SabreTools.DatFiles.Formats /// protected override List GetMissingRequiredFields(DatItem datItem) { - // TODO: Check required fields - return null; + var missingFields = new List(); + + if (string.IsNullOrWhiteSpace(datItem.GetName())) + missingFields.Add(DatItemField.Name); + + switch (datItem) + { + case Rom rom: + if (rom.Size == null || rom.Size < 0) + missingFields.Add(DatItemField.Size); + if (string.IsNullOrWhiteSpace(rom.CRC)) + { + missingFields.Add(DatItemField.SHA1); + } + break; + } + + return missingFields; } /// diff --git a/SabreTools.DatFiles/Formats/OpenMSX.Reader.cs b/SabreTools.DatFiles/Formats/OpenMSX.Reader.cs index 86d217e4..8807a151 100644 --- a/SabreTools.DatFiles/Formats/OpenMSX.Reader.cs +++ b/SabreTools.DatFiles/Formats/OpenMSX.Reader.cs @@ -38,16 +38,16 @@ namespace SabreTools.DatFiles.Formats /// /// Convert header information /// - /// Deserialized model to convert - private void ConvertHeader(Models.OpenMSX.SoftwareDb? datafile) + /// Deserialized model to convert + private void ConvertHeader(Models.OpenMSX.SoftwareDb? softwaredb) { // If the datafile is missing, we can't do anything - if (datafile == null) + if (softwaredb == null) return; Header.Name ??= "openMSX Software List"; Header.Description ??= Header.Name; - Header.Date ??= datafile.Timestamp; + Header.Date ??= softwaredb.Timestamp; } /// diff --git a/SabreTools.Models/OfflineList/Game.cs b/SabreTools.Models/OfflineList/Game.cs index 1d106745..26e34366 100644 --- a/SabreTools.Models/OfflineList/Game.cs +++ b/SabreTools.Models/OfflineList/Game.cs @@ -18,8 +18,9 @@ namespace SabreTools.Models.OfflineList [XmlElement("saveType")] public string? SaveType { get; set; } + /// Numeric [XmlElement("romSize")] - public long? RomSize { get; set; } + public string? RomSize { get; set; } [XmlElement("publisher")] public string? Publisher { get; set; }