diff --git a/SabreTools.DatFiles/Formats/OfflineList.Reader.cs b/SabreTools.DatFiles/Formats/OfflineList.Reader.cs
new file mode 100644
index 00000000..08955927
--- /dev/null
+++ b/SabreTools.DatFiles/Formats/OfflineList.Reader.cs
@@ -0,0 +1,646 @@
+using System;
+using System.Collections.Generic;
+using System.Xml;
+using System.Xml.Schema;
+using SabreTools.Core;
+using SabreTools.Core.Tools;
+using SabreTools.DatItems;
+using SabreTools.DatItems.Formats;
+
+namespace SabreTools.DatFiles.Formats
+{
+ ///
+ /// Represents parsing an OfflineList XML DAT
+ ///
+ internal partial class OfflineList : DatFile
+ {
+ ///
+ 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;
+ }
+
+ switch (xtr.Name)
+ {
+ case "configuration":
+ ReadConfiguration(xtr.ReadSubtree(), keep);
+
+ // Skip the configuration node now that we've processed it
+ xtr.Skip();
+ break;
+
+ case "games":
+ ReadGames(xtr.ReadSubtree(), statsOnly, filename, indexId);
+
+ // Skip the games node now that we've processed it
+ xtr.Skip();
+ break;
+
+ default:
+ xtr.Read();
+ break;
+ }
+ }
+ }
+ catch (Exception ex) when (!throwOnError)
+ {
+ logger.Warning(ex, $"Exception found while parsing '{filename}'");
+
+ // For XML errors, just skip the affected node
+ xtr?.Read();
+ }
+
+ 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;
+
+ // If there's no subtree to the configuration, skip it
+ if (reader == 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;
+ }
+ }
+ }
+
+ ///
+ /// Read infos information
+ ///
+ /// XmlReader to use to parse the header
+ private void ReadInfos(XmlReader reader)
+ {
+ // If there's no subtree to the configuration, skip it
+ if (reader == null)
+ return;
+
+ // Setup the infos object
+ Header.Infos = new List();
+
+ // 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;
+ }
+ }
+ }
+
+ ///
+ /// Read canopen information
+ ///
+ /// XmlReader to use to parse the header
+ private void ReadCanOpen(XmlReader reader)
+ {
+ // Prepare all internal variables
+ Header.CanOpen = new List();
+
+ // If there's no subtree to the configuration, skip it
+ if (reader == 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 canopen items
+ switch (reader.Name.ToLowerInvariant())
+ {
+ case "extension":
+ Header.CanOpen.Add(reader.ReadElementContentAsString());
+ break;
+
+ default:
+ reader.Read();
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Read newdat information
+ ///
+ /// XmlReader to use to parse the header
+ private void ReadNewDat(XmlReader reader)
+ {
+ // If there's no subtree to the configuration, skip it
+ if (reader == 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;
+ }
+ }
+ }
+
+ ///
+ /// Read search information
+ ///
+ /// XmlReader to use to parse the header
+ private void ReadSearch(XmlReader reader)
+ {
+ // If there's no subtree to the configuration, skip it
+ if (reader == 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;
+ }
+ }
+ }
+
+ ///
+ /// Read to information
+ ///
+ /// XmlReader to use to parse the header
+ private void ReadTo(XmlReader reader)
+ {
+ // If there's no subtree to the configuration, skip it
+ if (reader == 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;
+ }
+ }
+ }
+
+ ///
+ /// Read games information
+ ///
+ /// XmlReader to use to parse the header
+ /// 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)
+ {
+ // If there's no subtree to the configuration, skip it
+ if (reader == 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 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;
+ }
+ }
+ }
+
+ ///
+ /// Read game information
+ ///
+ /// XmlReader to use to parse the header
+ /// 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)
+ {
+ // 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)
+ 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 games items
+ switch (reader.Name.ToLowerInvariant())
+ {
+ case "imagenumber":
+ // TODO: Read this into a field
+ reader.ReadElementContentAsString();
+ break;
+
+ case "releasenumber":
+ // TODO: Read this into a field
+ releaseNumber = reader.ReadElementContentAsString();
+ break;
+
+ case "title":
+ machine.Name = reader.ReadElementContentAsString();
+ break;
+
+ 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++)
+ {
+ datItems[i].Size = size;
+ datItems[i].CopyMachineInformation(machine);
+
+ // Now process and add the rom
+ ParseAddHelper(datItems[i], statsOnly);
+ }
+ }
+
+ ///
+ /// Read files information
+ ///
+ /// XmlReader to use to parse the header
+ /// Release number from the parent game
+ /// Name of the parent game 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)
+ {
+ // Prepare all internal variables
+ var extensionToCrc = new List>();
+ var roms = new List();
+
+ // 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)
+ {
+ // We only want elements
+ if (reader.NodeType != XmlNodeType.Element)
+ {
+ 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,
+
+ ItemStatus = ItemStatus.None,
+
+ Source = new Source
+ {
+ Index = indexId,
+ Name = filename,
+ },
+ });
+ }
+
+ return roms;
+ }
+ }
+}
diff --git a/SabreTools.DatFiles/Formats/OfflineList.Writer.cs b/SabreTools.DatFiles/Formats/OfflineList.Writer.cs
new file mode 100644
index 00000000..c01eb401
--- /dev/null
+++ b/SabreTools.DatFiles/Formats/OfflineList.Writer.cs
@@ -0,0 +1,319 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Xml;
+using SabreTools.Core;
+using SabreTools.DatItems;
+using SabreTools.DatItems.Formats;
+using SabreTools.IO;
+
+namespace SabreTools.DatFiles.Formats
+{
+ ///
+ /// Represents writing an OfflineList XML DAT
+ ///
+ internal partial class OfflineList : DatFile
+ {
+ ///
+ protected override ItemType[] GetSupportedTypes()
+ {
+ return new ItemType[]
+ {
+ ItemType.Rom
+ };
+ }
+
+ ///
+ protected override List GetMissingRequiredFields(DatItem datItem)
+ {
+ // TODO: Check required fields
+ return null;
+ }
+
+ ///
+ public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false)
+ {
+ try
+ {
+ logger.User($"Writing to '{outfile}'...");
+ FileStream fs = System.IO.File.Create(outfile);
+
+ // If we get back null for some reason, just log and return
+ if (fs == null)
+ {
+ logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable");
+ return false;
+ }
+
+ XmlTextWriter xtw = new(fs, new UTF8Encoding(false))
+ {
+ Formatting = Formatting.Indented,
+ IndentChar = '\t',
+ Indentation = 1
+ };
+
+ // Write out the header
+ WriteHeader(xtw);
+
+ // Write out each of the machines and roms
+ string lastgame = null;
+
+ // Use a sorted list of games to output
+ foreach (string key in Items.SortedKeys)
+ {
+ ConcurrentList datItems = Items.FilteredItems(key);
+
+ // If this machine doesn't contain any writable items, skip
+ if (!ContainsWritable(datItems))
+ continue;
+
+ // Resolve the names in the block
+ datItems = DatItem.ResolveNames(datItems);
+
+ for (int index = 0; index < datItems.Count; index++)
+ {
+ DatItem datItem = datItems[index];
+
+ // Check for a "null" item
+ datItem = ProcessNullifiedItem(datItem);
+
+ // Write out the item if we're not ignoring
+ if (!ShouldIgnore(datItem, ignoreblanks))
+ WriteDatItem(xtw, datItem);
+
+ // Set the new data to compare against
+ lastgame = datItem.Machine.Name;
+ }
+ }
+
+ // Write the file footer out
+ WriteFooter(xtw);
+
+ logger.User($"'{outfile}' written!{Environment.NewLine}");
+ xtw.Dispose();
+ fs.Dispose();
+ }
+ catch (Exception ex) when (!throwOnError)
+ {
+ logger.Error(ex);
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Write out DAT header using the supplied StreamWriter
+ ///
+ /// XmlTextWriter to output to
+ private void WriteHeader(XmlTextWriter xtw)
+ {
+ xtw.WriteStartDocument(false);
+
+ xtw.WriteStartElement("dat");
+ xtw.WriteAttributeString("xsi", "xmlns", "http://www.w3.org/2001/XMLSchema-instance");
+ xtw.WriteAttributeString("noNamespaceSchemaLocation", "xsi", "datas.xsd");
+
+ xtw.WriteStartElement("configuration");
+ xtw.WriteRequiredElementString("datName", Header.Name);
+ xtw.WriteElementString("datVersion", Items.TotalCount.ToString());
+ xtw.WriteRequiredElementString("system", Header.System);
+ xtw.WriteRequiredElementString("screenshotsWidth", Header.ScreenshotsWidth);
+ xtw.WriteRequiredElementString("screenshotsHeight", Header.ScreenshotsHeight);
+
+ if (Header.Infos != null)
+ {
+ xtw.WriteStartElement("infos");
+
+ foreach (var info in Header.Infos)
+ {
+ xtw.WriteStartElement(info.Name);
+ xtw.WriteAttributeString("visible", info.Visible?.ToString());
+ xtw.WriteAttributeString("inNamingOption", info.InNamingOption?.ToString());
+ xtw.WriteAttributeString("default", info.Default?.ToString());
+ xtw.WriteEndElement();
+ }
+
+ // End infos
+ xtw.WriteEndElement();
+ }
+
+ if (Header.CanOpen != null)
+ {
+ xtw.WriteStartElement("canOpen");
+
+ foreach (string extension in Header.CanOpen)
+ {
+ xtw.WriteElementString("extension", extension);
+ }
+
+ // End canOpen
+ xtw.WriteEndElement();
+ }
+
+ xtw.WriteStartElement("newDat");
+ xtw.WriteRequiredElementString("datVersionURL", Header.Url);
+
+ xtw.WriteStartElement("datUrl");
+ xtw.WriteAttributeString("fileName", $"{Header.FileName ?? string.Empty}.zip");
+ xtw.WriteString(Header.Url);
+ xtw.WriteEndElement();
+
+ xtw.WriteRequiredElementString("imURL", Header.Url);
+
+ // End newDat
+ xtw.WriteEndElement();
+
+ xtw.WriteStartElement("search");
+
+ xtw.WriteStartElement("to");
+ xtw.WriteAttributeString("value", "location");
+ xtw.WriteAttributeString("default", "true");
+ xtw.WriteAttributeString("auto", "true");
+ xtw.WriteEndElement();
+
+ xtw.WriteStartElement("to");
+ xtw.WriteAttributeString("value", "romSize");
+ xtw.WriteAttributeString("default", "true");
+ xtw.WriteAttributeString("auto", "false");
+ xtw.WriteEndElement();
+
+ xtw.WriteStartElement("to");
+ xtw.WriteAttributeString("value", "languages");
+ xtw.WriteAttributeString("default", "true");
+ xtw.WriteAttributeString("auto", "true");
+ xtw.WriteEndElement();
+
+ xtw.WriteStartElement("to");
+ xtw.WriteAttributeString("value", "saveType");
+ xtw.WriteAttributeString("default", "false");
+ xtw.WriteAttributeString("auto", "false");
+ xtw.WriteEndElement();
+
+ xtw.WriteStartElement("to");
+ xtw.WriteAttributeString("value", "publisher");
+ xtw.WriteAttributeString("default", "false");
+ xtw.WriteAttributeString("auto", "true");
+ xtw.WriteEndElement();
+
+ xtw.WriteStartElement("to");
+ xtw.WriteAttributeString("value", "sourceRom");
+ xtw.WriteAttributeString("default", "false");
+ xtw.WriteAttributeString("auto", "true");
+ xtw.WriteEndElement();
+
+ // End search
+ xtw.WriteEndElement();
+
+ xtw.WriteRequiredElementString("romTitle", Header.RomTitle ?? "%u - %n");
+
+ // End configuration
+ xtw.WriteEndElement();
+
+ xtw.WriteStartElement("games");
+
+ xtw.Flush();
+ }
+
+ ///
+ /// Write out DatItem using the supplied StreamWriter
+ ///
+ /// XmlTextWriter to output to
+ /// DatItem object to be output
+ /// True if the data was written, false on error
+ private void WriteDatItem(XmlTextWriter xtw, DatItem datItem)
+ {
+ // Pre-process the item name
+ ProcessItemName(datItem, true);
+
+ // Build the state
+ xtw.WriteStartElement("game");
+ xtw.WriteElementString("imageNumber", "1");
+ xtw.WriteElementString("releaseNumber", "1");
+ xtw.WriteRequiredElementString("title", datItem.GetName() ?? string.Empty);
+ xtw.WriteElementString("saveType", "None");
+
+ if (datItem.ItemType == ItemType.Rom)
+ {
+ var rom = datItem as Rom;
+ xtw.WriteRequiredElementString("romSize", rom.Size?.ToString());
+ }
+
+ xtw.WriteRequiredElementString("publisher", datItem.Machine.Publisher);
+ xtw.WriteElementString("location", "0");
+ xtw.WriteElementString("sourceRom", "None");
+ xtw.WriteElementString("language", "0");
+
+ if (datItem.ItemType == ItemType.Rom)
+ {
+ var rom = datItem as Rom;
+ string tempext = "." + rom.Name.GetNormalizedExtension();
+
+ xtw.WriteStartElement("files");
+ if (!string.IsNullOrWhiteSpace(rom.CRC))
+ {
+ xtw.WriteStartElement("romCRC");
+ xtw.WriteRequiredAttributeString("extension", tempext);
+ xtw.WriteString(rom.CRC?.ToUpperInvariant());
+ xtw.WriteEndElement();
+ }
+
+ // End files
+ xtw.WriteEndElement();
+ }
+
+ xtw.WriteElementString("im1CRC", "00000000");
+ xtw.WriteElementString("im2CRC", "00000000");
+ xtw.WriteRequiredElementString("comment", datItem.Machine.Comment);
+ xtw.WriteRequiredElementString("duplicateID", datItem.Machine.CloneOf);
+
+ // End game
+ xtw.WriteEndElement();
+
+ xtw.Flush();
+ }
+
+ ///
+ /// Write out DAT footer using the supplied StreamWriter
+ ///
+ /// XmlTextWriter to output to
+ /// True if the data was written, false on error
+ private void WriteFooter(XmlTextWriter xtw)
+ {
+ // End games
+ xtw.WriteEndElement();
+
+ xtw.WriteStartElement("gui");
+
+ xtw.WriteStartElement("images");
+ xtw.WriteAttributeString("width", "487");
+ xtw.WriteAttributeString("height", "162");
+
+ xtw.WriteStartElement("image");
+ xtw.WriteAttributeString("x", "0");
+ xtw.WriteAttributeString("y", "0");
+ xtw.WriteAttributeString("width", "240");
+ xtw.WriteAttributeString("height", "160");
+ xtw.WriteEndElement();
+
+ xtw.WriteStartElement("image");
+ xtw.WriteAttributeString("x", "245");
+ xtw.WriteAttributeString("y", "0");
+ xtw.WriteAttributeString("width", "240");
+ xtw.WriteAttributeString("height", "160");
+ xtw.WriteEndElement();
+
+ // End images
+ xtw.WriteEndElement();
+
+ // End gui
+ xtw.WriteEndElement();
+
+ // End dat
+ xtw.WriteEndElement();
+
+ xtw.Flush();
+ }
+ }
+}
diff --git a/SabreTools.DatFiles/Formats/OfflineList.cs b/SabreTools.DatFiles/Formats/OfflineList.cs
index 13bc4751..03d113c8 100644
--- a/SabreTools.DatFiles/Formats/OfflineList.cs
+++ b/SabreTools.DatFiles/Formats/OfflineList.cs
@@ -1,21 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using System.Xml;
-using System.Xml.Schema;
-using SabreTools.Core;
-using SabreTools.Core.Tools;
-using SabreTools.DatItems;
-using SabreTools.DatItems.Formats;
-using SabreTools.IO;
-
-namespace SabreTools.DatFiles.Formats
+namespace SabreTools.DatFiles.Formats
{
///
- /// Represents parsing and writing of an OfflineList XML DAT
+ /// Represents an OfflineList XML DAT
///
- internal class OfflineList : DatFile
+ internal partial class OfflineList : DatFile
{
///
/// Constructor designed for casting a base DatFile
@@ -25,932 +13,5 @@ namespace SabreTools.DatFiles.Formats
: base(datFile)
{
}
-
- ///
- 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;
- }
-
- switch (xtr.Name)
- {
- case "configuration":
- ReadConfiguration(xtr.ReadSubtree(), keep);
-
- // Skip the configuration node now that we've processed it
- xtr.Skip();
- break;
-
- case "games":
- ReadGames(xtr.ReadSubtree(), statsOnly, filename, indexId);
-
- // Skip the games node now that we've processed it
- xtr.Skip();
- break;
-
- default:
- xtr.Read();
- break;
- }
- }
- }
- catch (Exception ex) when (!throwOnError)
- {
- logger.Warning(ex, $"Exception found while parsing '{filename}'");
-
- // For XML errors, just skip the affected node
- xtr?.Read();
- }
-
- 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;
-
- // If there's no subtree to the configuration, skip it
- if (reader == 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;
- }
- }
- }
-
- ///
- /// Read infos information
- ///
- /// XmlReader to use to parse the header
- private void ReadInfos(XmlReader reader)
- {
- // If there's no subtree to the configuration, skip it
- if (reader == null)
- return;
-
- // Setup the infos object
- Header.Infos = new List();
-
- // 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;
- }
- }
- }
-
- ///
- /// Read canopen information
- ///
- /// XmlReader to use to parse the header
- private void ReadCanOpen(XmlReader reader)
- {
- // Prepare all internal variables
- Header.CanOpen = new List();
-
- // If there's no subtree to the configuration, skip it
- if (reader == 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 canopen items
- switch (reader.Name.ToLowerInvariant())
- {
- case "extension":
- Header.CanOpen.Add(reader.ReadElementContentAsString());
- break;
-
- default:
- reader.Read();
- break;
- }
- }
- }
-
- ///
- /// Read newdat information
- ///
- /// XmlReader to use to parse the header
- private void ReadNewDat(XmlReader reader)
- {
- // If there's no subtree to the configuration, skip it
- if (reader == 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;
- }
- }
- }
-
- ///
- /// Read search information
- ///
- /// XmlReader to use to parse the header
- private void ReadSearch(XmlReader reader)
- {
- // If there's no subtree to the configuration, skip it
- if (reader == 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;
- }
- }
- }
-
- ///
- /// Read to information
- ///
- /// XmlReader to use to parse the header
- private void ReadTo(XmlReader reader)
- {
- // If there's no subtree to the configuration, skip it
- if (reader == 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;
- }
- }
- }
-
- ///
- /// Read games information
- ///
- /// XmlReader to use to parse the header
- /// 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)
- {
- // If there's no subtree to the configuration, skip it
- if (reader == 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 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;
- }
- }
- }
-
- ///
- /// Read game information
- ///
- /// XmlReader to use to parse the header
- /// 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)
- {
- // 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)
- 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 games items
- switch (reader.Name.ToLowerInvariant())
- {
- case "imagenumber":
- // TODO: Read this into a field
- reader.ReadElementContentAsString();
- break;
-
- case "releasenumber":
- // TODO: Read this into a field
- releaseNumber = reader.ReadElementContentAsString();
- break;
-
- case "title":
- machine.Name = reader.ReadElementContentAsString();
- break;
-
- 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++)
- {
- datItems[i].Size = size;
- datItems[i].CopyMachineInformation(machine);
-
- // Now process and add the rom
- ParseAddHelper(datItems[i], statsOnly);
- }
- }
-
- ///
- /// Read files information
- ///
- /// XmlReader to use to parse the header
- /// Release number from the parent game
- /// Name of the parent game 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)
- {
- // Prepare all internal variables
- var extensionToCrc = new List>();
- var roms = new List();
-
- // 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)
- {
- // We only want elements
- if (reader.NodeType != XmlNodeType.Element)
- {
- 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,
-
- ItemStatus = ItemStatus.None,
-
- Source = new Source
- {
- Index = indexId,
- Name = filename,
- },
- });
- }
-
- return roms;
- }
-
- ///
- protected override ItemType[] GetSupportedTypes()
- {
- return new ItemType[] { ItemType.Rom };
- }
-
- ///
- protected override List GetMissingRequiredFields(DatItem datItem)
- {
- // TODO: Check required fields
- return null;
- }
-
- ///
- public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false)
- {
- try
- {
- logger.User($"Writing to '{outfile}'...");
- FileStream fs = System.IO.File.Create(outfile);
-
- // If we get back null for some reason, just log and return
- if (fs == null)
- {
- logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable");
- return false;
- }
-
- XmlTextWriter xtw = new(fs, new UTF8Encoding(false))
- {
- Formatting = Formatting.Indented,
- IndentChar = '\t',
- Indentation = 1
- };
-
- // Write out the header
- WriteHeader(xtw);
-
- // Write out each of the machines and roms
- string lastgame = null;
-
- // Use a sorted list of games to output
- foreach (string key in Items.SortedKeys)
- {
- ConcurrentList datItems = Items.FilteredItems(key);
-
- // If this machine doesn't contain any writable items, skip
- if (!ContainsWritable(datItems))
- continue;
-
- // Resolve the names in the block
- datItems = DatItem.ResolveNames(datItems);
-
- for (int index = 0; index < datItems.Count; index++)
- {
- DatItem datItem = datItems[index];
-
- // Check for a "null" item
- datItem = ProcessNullifiedItem(datItem);
-
- // Write out the item if we're not ignoring
- if (!ShouldIgnore(datItem, ignoreblanks))
- WriteDatItem(xtw, datItem);
-
- // Set the new data to compare against
- lastgame = datItem.Machine.Name;
- }
- }
-
- // Write the file footer out
- WriteFooter(xtw);
-
- logger.User($"'{outfile}' written!{Environment.NewLine}");
- xtw.Dispose();
- fs.Dispose();
- }
- catch (Exception ex) when (!throwOnError)
- {
- logger.Error(ex);
- return false;
- }
-
- return true;
- }
-
- ///
- /// Write out DAT header using the supplied StreamWriter
- ///
- /// XmlTextWriter to output to
- private void WriteHeader(XmlTextWriter xtw)
- {
- xtw.WriteStartDocument(false);
-
- xtw.WriteStartElement("dat");
- xtw.WriteAttributeString("xsi", "xmlns", "http://www.w3.org/2001/XMLSchema-instance");
- xtw.WriteAttributeString("noNamespaceSchemaLocation", "xsi", "datas.xsd");
-
- xtw.WriteStartElement("configuration");
- xtw.WriteRequiredElementString("datName", Header.Name);
- xtw.WriteElementString("datVersion", Items.TotalCount.ToString());
- xtw.WriteRequiredElementString("system", Header.System);
- xtw.WriteRequiredElementString("screenshotsWidth", Header.ScreenshotsWidth);
- xtw.WriteRequiredElementString("screenshotsHeight", Header.ScreenshotsHeight);
-
- if (Header.Infos != null)
- {
- xtw.WriteStartElement("infos");
-
- foreach (var info in Header.Infos)
- {
- xtw.WriteStartElement(info.Name);
- xtw.WriteAttributeString("visible", info.Visible?.ToString());
- xtw.WriteAttributeString("inNamingOption", info.InNamingOption?.ToString());
- xtw.WriteAttributeString("default", info.Default?.ToString());
- xtw.WriteEndElement();
- }
-
- // End infos
- xtw.WriteEndElement();
- }
-
- if (Header.CanOpen != null)
- {
- xtw.WriteStartElement("canOpen");
-
- foreach (string extension in Header.CanOpen)
- {
- xtw.WriteElementString("extension", extension);
- }
-
- // End canOpen
- xtw.WriteEndElement();
- }
-
- xtw.WriteStartElement("newDat");
- xtw.WriteRequiredElementString("datVersionURL", Header.Url);
-
- xtw.WriteStartElement("datUrl");
- xtw.WriteAttributeString("fileName", $"{Header.FileName ?? string.Empty}.zip");
- xtw.WriteString(Header.Url);
- xtw.WriteEndElement();
-
- xtw.WriteRequiredElementString("imURL", Header.Url);
-
- // End newDat
- xtw.WriteEndElement();
-
- xtw.WriteStartElement("search");
-
- xtw.WriteStartElement("to");
- xtw.WriteAttributeString("value", "location");
- xtw.WriteAttributeString("default", "true");
- xtw.WriteAttributeString("auto", "true");
- xtw.WriteEndElement();
-
- xtw.WriteStartElement("to");
- xtw.WriteAttributeString("value", "romSize");
- xtw.WriteAttributeString("default", "true");
- xtw.WriteAttributeString("auto", "false");
- xtw.WriteEndElement();
-
- xtw.WriteStartElement("to");
- xtw.WriteAttributeString("value", "languages");
- xtw.WriteAttributeString("default", "true");
- xtw.WriteAttributeString("auto", "true");
- xtw.WriteEndElement();
-
- xtw.WriteStartElement("to");
- xtw.WriteAttributeString("value", "saveType");
- xtw.WriteAttributeString("default", "false");
- xtw.WriteAttributeString("auto", "false");
- xtw.WriteEndElement();
-
- xtw.WriteStartElement("to");
- xtw.WriteAttributeString("value", "publisher");
- xtw.WriteAttributeString("default", "false");
- xtw.WriteAttributeString("auto", "true");
- xtw.WriteEndElement();
-
- xtw.WriteStartElement("to");
- xtw.WriteAttributeString("value", "sourceRom");
- xtw.WriteAttributeString("default", "false");
- xtw.WriteAttributeString("auto", "true");
- xtw.WriteEndElement();
-
- // End search
- xtw.WriteEndElement();
-
- xtw.WriteRequiredElementString("romTitle", Header.RomTitle ?? "%u - %n");
-
- // End configuration
- xtw.WriteEndElement();
-
- xtw.WriteStartElement("games");
-
- xtw.Flush();
- }
-
- ///
- /// Write out DatItem using the supplied StreamWriter
- ///
- /// XmlTextWriter to output to
- /// DatItem object to be output
- /// True if the data was written, false on error
- private void WriteDatItem(XmlTextWriter xtw, DatItem datItem)
- {
- // Pre-process the item name
- ProcessItemName(datItem, true);
-
- // Build the state
- xtw.WriteStartElement("game");
- xtw.WriteElementString("imageNumber", "1");
- xtw.WriteElementString("releaseNumber", "1");
- xtw.WriteRequiredElementString("title", datItem.GetName() ?? string.Empty);
- xtw.WriteElementString("saveType", "None");
-
- if (datItem.ItemType == ItemType.Rom)
- {
- var rom = datItem as Rom;
- xtw.WriteRequiredElementString("romSize", rom.Size?.ToString());
- }
-
- xtw.WriteRequiredElementString("publisher", datItem.Machine.Publisher);
- xtw.WriteElementString("location", "0");
- xtw.WriteElementString("sourceRom", "None");
- xtw.WriteElementString("language", "0");
-
- if (datItem.ItemType == ItemType.Rom)
- {
- var rom = datItem as Rom;
- string tempext = "." + rom.Name.GetNormalizedExtension();
-
- xtw.WriteStartElement("files");
- if (!string.IsNullOrWhiteSpace(rom.CRC))
- {
- xtw.WriteStartElement("romCRC");
- xtw.WriteRequiredAttributeString("extension", tempext);
- xtw.WriteString(rom.CRC?.ToUpperInvariant());
- xtw.WriteEndElement();
- }
-
- // End files
- xtw.WriteEndElement();
- }
-
- xtw.WriteElementString("im1CRC", "00000000");
- xtw.WriteElementString("im2CRC", "00000000");
- xtw.WriteRequiredElementString("comment", datItem.Machine.Comment);
- xtw.WriteRequiredElementString("duplicateID", datItem.Machine.CloneOf);
-
- // End game
- xtw.WriteEndElement();
-
- xtw.Flush();
- }
-
- ///
- /// Write out DAT footer using the supplied StreamWriter
- ///
- /// XmlTextWriter to output to
- /// True if the data was written, false on error
- private void WriteFooter(XmlTextWriter xtw)
- {
- // End games
- xtw.WriteEndElement();
-
- xtw.WriteStartElement("gui");
-
- xtw.WriteStartElement("images");
- xtw.WriteAttributeString("width", "487");
- xtw.WriteAttributeString("height", "162");
-
- xtw.WriteStartElement("image");
- xtw.WriteAttributeString("x", "0");
- xtw.WriteAttributeString("y", "0");
- xtw.WriteAttributeString("width", "240");
- xtw.WriteAttributeString("height", "160");
- xtw.WriteEndElement();
-
- xtw.WriteStartElement("image");
- xtw.WriteAttributeString("x", "245");
- xtw.WriteAttributeString("y", "0");
- xtw.WriteAttributeString("width", "240");
- xtw.WriteAttributeString("height", "160");
- xtw.WriteEndElement();
-
- // End images
- xtw.WriteEndElement();
-
- // End gui
- xtw.WriteEndElement();
-
- // End dat
- xtw.WriteEndElement();
-
- xtw.Flush();
- }
}
}