diff --git a/SabreTools.DatFiles/Formats/RomCenter.Reader.cs b/SabreTools.DatFiles/Formats/RomCenter.Reader.cs
new file mode 100644
index 00000000..0e566a00
--- /dev/null
+++ b/SabreTools.DatFiles/Formats/RomCenter.Reader.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Linq;
+using SabreTools.Core;
+using SabreTools.Core.Tools;
+using SabreTools.DatItems;
+using SabreTools.DatItems.Formats;
+
+namespace SabreTools.DatFiles.Formats
+{
+ ///
+ /// Represents parsing a RomCenter INI file
+ ///
+ internal partial class RomCenter : DatFile
+ {
+ ///
+ public override void ParseFile(string filename, int indexId, bool keep, bool statsOnly = false, bool throwOnError = false)
+ {
+ try
+ {
+ // Deserialize the input file
+ var metadataFile = Serialization.RomCenter.Deserialize(filename);
+
+ // Convert the credits data to the internal format
+ ConvertCredits(metadataFile?.Credits);
+
+ // Convert the dat data to the internal format
+ ConvertDat(metadataFile?.Dat);
+
+ // Convert the emulator data to the internal format
+ ConvertEmulator(metadataFile?.Emulator);
+
+ // Convert the games data to the internal format
+ ConvertGames(metadataFile?.Games, filename, indexId, statsOnly);
+ }
+ catch (Exception ex) when (!throwOnError)
+ {
+ string message = $"'{filename}' - An error occurred during parsing";
+ logger.Error(ex, message);
+ }
+ }
+
+ #region Converters
+
+ ///
+ /// Convert credits information
+ ///
+ /// Deserialized model to convert
+ private void ConvertCredits(Models.RomCenter.Credits? credits)
+ {
+ // If the credits is missing, we can't do anything
+ if (credits == null)
+ return;
+
+ Header.Author ??= credits.Author;
+ Header.Version ??= credits.Version;
+ Header.Email ??= credits.Email;
+ Header.Homepage ??= credits.Homepage;
+ Header.Url ??= credits.Url;
+ Header.Date ??= credits.Date;
+ Header.Comment ??= credits.Comment;
+ }
+
+ ///
+ /// Convert dat information
+ ///
+ /// Deserialized model to convert
+ private void ConvertDat(Models.RomCenter.Dat? dat)
+ {
+ // If the dat is missing, we can't do anything
+ if (dat == null)
+ return;
+
+ Header.RomCenterVersion ??= dat.Version;
+ Header.System ??= dat.Plugin;
+ if (Header.ForceMerging == MergingFlag.None && dat.Split == "1")
+ Header.ForceMerging = MergingFlag.Split;
+ if (Header.ForceMerging == MergingFlag.None && dat.Merge == "1")
+ Header.ForceMerging = MergingFlag.Merged;
+ }
+
+ ///
+ /// Convert emulator information
+ ///
+ /// Deserialized model to convert
+ private void ConvertEmulator(Models.RomCenter.Emulator? emulator)
+ {
+ // If the emulator is missing, we can't do anything
+ if (emulator == null)
+ return;
+
+ Header.Name ??= emulator.RefName;
+ Header.Description ??= emulator.Version;
+ }
+
+ ///
+ /// Convert games information
+ ///
+ /// 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
+ private void ConvertGames(Models.RomCenter.Games? games, string filename, int indexId, bool statsOnly)
+ {
+ // If the games is missing, we can't do anything
+ if (games?.Rom == null || !games.Rom.Any())
+ return;
+
+ foreach (var rom in games.Rom)
+ {
+ var item = new Rom
+ {
+ Name = rom.RomName,
+ Size = Utilities.CleanLong(rom.RomSize),
+ CRC = rom.RomCRC,
+ MergeTag = rom.MergeName,
+ ItemStatus = ItemStatus.None,
+
+ Machine = new Machine
+ {
+ Name = rom.GameName,
+ Description = rom.GameDescription,
+ CloneOf = rom.ParentName,
+ //CloneOfDescription = rom.ParentDescription, // TODO: Add to internal model or find mapping
+ RomOf = rom.RomOf,
+ },
+
+ Source = new Source
+ {
+ Index = indexId,
+ Name = filename,
+ },
+ };
+
+ // Now process and add the item
+ ParseAddHelper(item, statsOnly);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/SabreTools.DatFiles/Formats/RomCenter.Writer.cs b/SabreTools.DatFiles/Formats/RomCenter.Writer.cs
new file mode 100644
index 00000000..3b589760
--- /dev/null
+++ b/SabreTools.DatFiles/Formats/RomCenter.Writer.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using SabreTools.Core;
+using SabreTools.DatItems;
+using SabreTools.DatItems.Formats;
+
+namespace SabreTools.DatFiles.Formats
+{
+ ///
+ /// Represents writing a RomCenter INI file
+ ///
+ internal partial class RomCenter : DatFile
+ {
+ ///
+ protected override ItemType[] GetSupportedTypes()
+ {
+ return new ItemType[]
+ {
+ ItemType.Rom
+ };
+ }
+
+ ///
+ protected override List GetMissingRequiredFields(DatItem datItem)
+ {
+ List missingFields = new();
+
+ // Check item name
+ if (string.IsNullOrWhiteSpace(datItem.GetName()))
+ missingFields.Add(DatItemField.Name);
+
+ switch (datItem)
+ {
+ case Rom rom:
+ if (string.IsNullOrWhiteSpace(rom.CRC))
+ missingFields.Add(DatItemField.CRC);
+ if (!rom.SizeSpecified)
+ missingFields.Add(DatItemField.Size);
+ break;
+ }
+
+ return missingFields;
+ }
+
+ ///
+ public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false)
+ {
+ try
+ {
+ logger.User($"Writing to '{outfile}'...");
+
+ var metadataFile = CreateMetadataFile(ignoreblanks);
+ if (!Serialization.RomCenter.SerializeToFile(metadataFile, outfile))
+ {
+ logger.Warning($"File '{outfile}' could not be written! See the log for more details.");
+ return false;
+ }
+ }
+ catch (Exception ex) when (!throwOnError)
+ {
+ logger.Error(ex);
+ return false;
+ }
+
+ return true;
+ }
+
+ #region Converters
+
+ ///
+ /// Create a MetadataFile from the current internal information
+ ///
+ /// True if blank roms should be skipped on output, false otherwise
+ private Models.RomCenter.MetadataFile CreateMetadataFile(bool ignoreblanks)
+ {
+ var metadataFile = new Models.RomCenter.MetadataFile
+ {
+ Credits = CreateCredits(),
+ Dat = CreateDat(),
+ Emulator = CreateEmulator(),
+ Games = CreateGames(ignoreblanks),
+ };
+ return metadataFile;
+ }
+
+ ///
+ /// Create a Credits from the current internal information
+ ///
+ private Models.RomCenter.Credits CreateCredits()
+ {
+ var credits = new Models.RomCenter.Credits
+ {
+ Author = Header.Author,
+ Version = Header.Version,
+ Email = Header.Email,
+ Homepage = Header.Homepage,
+ Url = Header.Url,
+ Date = Header.Date,
+ Comment = Header.Comment,
+ };
+ return credits;
+ }
+
+ ///
+ /// Create a Dat from the current internal information
+ ///
+ private Models.RomCenter.Dat CreateDat()
+ {
+ var dat = new Models.RomCenter.Dat
+ {
+ Version = Header.RomCenterVersion,
+ Plugin = Header.System,
+ Split = (Header.ForceMerging == MergingFlag.Split ? "1" : "0"),
+ Merge = (Header.ForceMerging == MergingFlag.Merged || Header.ForceMerging == MergingFlag.FullMerged ? "1" : "0"),
+ };
+ return dat;
+ }
+
+ ///
+ /// Create a Emulator from the current internal information
+ ///
+ private Models.RomCenter.Emulator CreateEmulator()
+ {
+ var emulator = new Models.RomCenter.Emulator
+ {
+ RefName = Header.Name,
+ Version = Header.Description,
+ };
+ return emulator;
+ }
+
+ ///
+ /// Create a Games from the current internal information
+ ///
+ /// True if blank roms should be skipped on output, false otherwise
+ private Models.RomCenter.Games CreateGames(bool ignoreblanks)
+ {
+ // If we don't have items, we can't do anything
+ if (this.Items == null || !this.Items.Any())
+ return null;
+
+ // Create a list of hold the roms
+ var roms = new List();
+
+ // Loop through the sorted items and create games for them
+ foreach (string key in Items.SortedKeys)
+ {
+ var items = Items.FilteredItems(key);
+ if (items == null || !items.Any())
+ continue;
+
+ // Loop through and convert the items to respective lists
+ foreach (var item in items)
+ {
+ // Skip if we're ignoring the item
+ if (ShouldIgnore(item, ignoreblanks))
+ continue;
+
+ switch (item)
+ {
+ case Rom rom:
+ roms.Add(CreateRom(rom));
+ break;
+ }
+ }
+ }
+
+ return new Models.RomCenter.Games { Rom = roms.ToArray() };
+ }
+
+ ///
+ /// Create a Rom from the current Rom DatItem
+ ///
+ private static Models.RomCenter.Rom CreateRom(Rom item)
+ {
+ var rom = new Models.RomCenter.Rom
+ {
+ ParentName = item.Machine.CloneOf,
+ //ParentDescription = item.Machine.CloneOfDescription, // TODO: Add to internal model or find mapping
+ GameName = item.Machine.Name,
+ GameDescription = item.Machine.Description,
+ RomName = item.Name,
+ RomCRC = item.CRC,
+ RomSize = item.Size?.ToString(),
+ RomOf = item.Machine.RomOf,
+ MergeName = item.MergeTag,
+ };
+ return rom;
+ }
+
+ #endregion
+ }
+}
diff --git a/SabreTools.DatFiles/Formats/RomCenter.cs b/SabreTools.DatFiles/Formats/RomCenter.cs
index d9056303..5985319c 100644
--- a/SabreTools.DatFiles/Formats/RomCenter.cs
+++ b/SabreTools.DatFiles/Formats/RomCenter.cs
@@ -1,20 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using SabreTools.Core;
-using SabreTools.Core.Tools;
-using SabreTools.DatItems;
-using SabreTools.DatItems.Formats;
-using SabreTools.IO.Readers;
-using SabreTools.IO.Writers;
-
-namespace SabreTools.DatFiles.Formats
+namespace SabreTools.DatFiles.Formats
{
///
- /// Represents parsing and writing of a RomCenter DAT
+ /// Represents a RomCenter INI file
///
- internal class RomCenter : DatFile
+ internal partial class RomCenter : DatFile
{
///
/// Constructor designed for casting a base DatFile
@@ -24,491 +13,5 @@ namespace SabreTools.DatFiles.Formats
: base(datFile)
{
}
-
- ///
- public override void ParseFile(string filename, int indexId, bool keep, bool statsOnly = false, bool throwOnError = false)
- {
- // Prepare all intenral variables
- IniReader ir = new(filename) { ValidateRows = false };
-
- // If we got a null reader, just return
- if (ir == null)
- return;
-
- // Otherwise, read the file to the end
- try
- {
- ir.ReadNextLine();
- while (!ir.EndOfStream)
- {
- // We don't care about whitespace or comments
- if (ir.RowType == IniRowType.None || ir.RowType == IniRowType.Comment)
- {
- ir.ReadNextLine();
- continue;
- }
-
- // If we have a section
- if (ir.RowType == IniRowType.SectionHeader)
- {
- switch (ir.Section.ToLowerInvariant())
- {
- case "credits":
- ReadCreditsSection(ir);
- break;
-
- case "dat":
- ReadDatSection(ir);
- break;
-
- case "emulator":
- ReadEmulatorSection(ir);
- break;
-
- case "games":
- ReadGamesSection(ir, statsOnly, filename, indexId);
- break;
-
- // Unknown section so we ignore it
- default:
- ir.ReadNextLine();
- break;
- }
- }
- }
- }
- catch (Exception ex) when (!throwOnError)
- {
- string message = $"'{filename}' - There was an error parsing line {ir.LineNumber} '{ir.CurrentLine}'";
- logger.Error(ex, message);
- }
-
- ir.Dispose();
- }
-
- ///
- /// Read credits information
- ///
- /// IniReader to use to parse the credits
- private void ReadCreditsSection(IniReader reader)
- {
- // If the reader is somehow null, skip it
- if (reader == null)
- return;
-
- reader.ReadNextLine();
- do
- {
- // We don't care about whitespace, comments, or invalid
- if (reader.RowType != IniRowType.KeyValue)
- {
- reader.ReadNextLine();
- continue;
- }
-
- var kvp = reader.KeyValuePair;
-
- // If the KeyValuePair is invalid, skip it
- if (kvp == null)
- {
- reader.ReadNextLine();
- continue;
- }
-
- // Get all credits items
- switch (kvp?.Key.ToLowerInvariant())
- {
- case "author":
- Header.Author ??= kvp?.Value;
- reader.ReadNextLine();
- break;
-
- case "version":
- Header.Version ??= kvp?.Value;
- reader.ReadNextLine();
- break;
-
- case "email":
- Header.Email ??= kvp?.Value;
- reader.ReadNextLine();
- break;
-
- case "homepage":
- Header.Homepage ??= kvp?.Value;
- reader.ReadNextLine();
- break;
-
- case "url":
- Header.Url ??= kvp?.Value;
- reader.ReadNextLine();
- break;
-
- case "date":
- Header.Date ??= kvp?.Value;
- reader.ReadNextLine();
- break;
-
- case "comment":
- Header.Comment ??= kvp?.Value;
- reader.ReadNextLine();
- break;
-
- // Unknown value, just skip
- default:
- reader.ReadNextLine();
- break;
- }
- } while (!reader.EndOfStream && reader.Section.ToLowerInvariant() == "credits");
- }
-
- ///
- /// Read dat information
- ///
- /// IniReader to use to parse the credits
- private void ReadDatSection(IniReader reader)
- {
- // If the reader is somehow null, skip it
- if (reader == null)
- return;
-
- reader.ReadNextLine();
- do
- {
- // We don't care about whitespace, comments, or invalid
- if (reader.RowType != IniRowType.KeyValue)
- {
- reader.ReadNextLine();
- continue;
- }
-
- var kvp = reader.KeyValuePair;
-
- // If the KeyValuePair is invalid, skip it
- if (kvp == null)
- {
- reader.ReadNextLine();
- continue;
- }
-
- // Get all dat items
- switch (kvp?.Key.ToLowerInvariant())
- {
- case "version":
- Header.RomCenterVersion ??= kvp?.Value;
- reader.ReadNextLine();
- break;
-
- case "plugin":
- Header.System ??= kvp?.Value;
- reader.ReadNextLine();
- break;
-
- case "split":
- if (Header.ForceMerging == MergingFlag.None && kvp?.Value == "1")
- Header.ForceMerging = MergingFlag.Split;
-
- reader.ReadNextLine();
- break;
-
- case "merge":
- if (Header.ForceMerging == MergingFlag.None && kvp?.Value == "1")
- Header.ForceMerging = MergingFlag.Merged;
-
- reader.ReadNextLine();
- break;
-
- // Unknown value, just skip
- default:
- reader.ReadNextLine();
- break;
- }
- } while (!reader.EndOfStream && reader.Section.ToLowerInvariant() == "dat");
- }
-
- ///
- /// Read emulator information
- ///
- /// IniReader to use to parse the credits
- private void ReadEmulatorSection(IniReader reader)
- {
- // If the reader is somehow null, skip it
- if (reader == null)
- return;
-
- reader.ReadNextLine();
- do
- {
- // We don't care about whitespace, comments, or invalid
- if (reader.RowType != IniRowType.KeyValue)
- {
- reader.ReadNextLine();
- continue;
- }
-
- var kvp = reader.KeyValuePair;
-
- // If the KeyValuePair is invalid, skip it
- if (kvp == null)
- {
- reader.ReadNextLine();
- continue;
- }
-
- // Get all emulator items (ONLY OVERWRITE IF THERE'S NO DATA)
- switch (kvp?.Key.ToLowerInvariant())
- {
- case "refname":
- Header.Name ??= kvp?.Value;
- reader.ReadNextLine();
- break;
-
- case "version":
- Header.Description ??= kvp?.Value;
- reader.ReadNextLine();
- break;
-
- // Unknown value, just skip
- default:
- reader.ReadNextLine();
- break;
- }
- } while (!reader.EndOfStream && reader.Section.ToLowerInvariant() == "emulator");
- }
-
- ///
- /// Read games information
- ///
- /// IniReader to use to parse the credits
- /// True to only add item statistics while parsing, false otherwise
- /// Name of the file to be parsed
- /// Index ID for the DAT
- private void ReadGamesSection(IniReader reader, bool statsOnly, string filename, int indexId)
- {
- // If the reader is somehow null, skip it
- if (reader == null)
- return;
-
- reader.ReadNextLine();
- do
- {
- // We don't care about whitespace or comments
- // We're keeping keyvalue in case a file has '=' in the row
- if (reader.RowType != IniRowType.Invalid && reader.RowType != IniRowType.KeyValue)
- {
- reader.ReadNextLine();
- continue;
- }
-
- // Roms are not valid row formats, usually
- string line = reader.CurrentLine;
-
- // If we don't have a valid game, keep reading
- if (!line.StartsWith("¬"))
- {
- reader.ReadNextLine();
- continue;
- }
-
- // Some old RC DATs have this behavior
- if (line.Contains("¬N¬O"))
- line = line.Replace("¬N¬O", string.Empty) + "¬¬";
-
- /*
- The rominfo order is as follows:
- 1 - parent name
- 2 - parent description
- 3 - game name
- 4 - game description
- 5 - rom name
- 6 - rom crc
- 7 - rom size
- 8 - romof name
- 9 - merge name
- */
- string[] rominfo = line.Split('¬');
- Rom rom = new()
- {
- Name = rominfo[5],
- Size = Utilities.CleanLong(rominfo[7]),
- CRC = rominfo[6],
- ItemStatus = ItemStatus.None,
-
- Machine = new Machine
- {
- Name = rominfo[3],
- Description = rominfo[4],
- CloneOf = rominfo[1],
- RomOf = rominfo[8],
- },
-
- MergeTag = rominfo[9],
-
- Source = new Source
- {
- Index = indexId,
- Name = filename,
- },
- };
-
- // Now process and add the rom
- ParseAddHelper(rom, statsOnly);
-
- reader.ReadNextLine();
- } while (!reader.EndOfStream && reader.Section.ToLowerInvariant() == "games");
- }
-
- ///
- 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;
- }
-
- IniWriter iw = new(fs, new UTF8Encoding(false));
-
- // Write out the header
- WriteHeader(iw);
-
- // Write out each of the machines and roms
- string lastgame = null;
- List splitpath = new();
-
- // 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(iw, datItem);
-
- // Set the new data to compare against
- lastgame = datItem.Machine.Name;
- }
- }
-
- logger.User($"'{outfile}' written!{Environment.NewLine}");
- iw.Dispose();
- fs.Dispose();
- }
- catch (Exception ex) when (!throwOnError)
- {
- logger.Error(ex);
- return false;
- }
-
- return true;
- }
-
- ///
- /// Write out DAT header using the supplied StreamWriter
- ///
- /// IniWriter to output to
- private void WriteHeader(IniWriter iw)
- {
- iw.WriteSection("CREDITS");
- iw.WriteKeyValuePair("author", Header.Author);
- iw.WriteKeyValuePair("version", Header.Version);
- iw.WriteKeyValuePair("comment", Header.Comment);
-
- iw.WriteSection("DAT");
- iw.WriteKeyValuePair("version", Header.RomCenterVersion ?? "2.50");
- iw.WriteKeyValuePair("plugin", Header.System);
- iw.WriteKeyValuePair("split", Header.ForceMerging == MergingFlag.Split ? "1" : "0");
- iw.WriteKeyValuePair("merge", Header.ForceMerging == MergingFlag.FullNonMerged || Header.ForceMerging == MergingFlag.Merged ? "1" : "0");
-
- iw.WriteSection("EMULATOR");
- iw.WriteKeyValuePair("refname", Header.Name);
- iw.WriteKeyValuePair("version", Header.Description);
-
- iw.WriteSection("GAMES");
-
- iw.Flush();
- }
-
- ///
- /// Write out DatItem using the supplied StreamWriter
- ///
- /// IniWriter to output to
- /// DatItem object to be output
- private void WriteDatItem(IniWriter iw, DatItem datItem)
- {
- /*
- The rominfo order is as follows:
- 1 - parent name
- 2 - parent description
- 3 - game name
- 4 - game description
- 5 - rom name
- 6 - rom crc
- 7 - rom size
- 8 - romof name
- 9 - merge name
- */
-
- // Pre-process the item name
- ProcessItemName(datItem, true);
-
- // Build the state
- switch (datItem.ItemType)
- {
- case ItemType.Rom:
- var rom = datItem as Rom;
-
- iw.WriteString($"¬{rom.Machine.CloneOf ?? string.Empty}");
- iw.WriteString($"¬{rom.Machine.CloneOf ?? string.Empty}");
- iw.WriteString($"¬{rom.Machine.Name ?? string.Empty}");
- if (string.IsNullOrWhiteSpace(rom.Machine.Description ?? string.Empty))
- iw.WriteString($"¬{rom.Machine.Name ?? string.Empty}");
- else
- iw.WriteString($"¬{rom.Machine.Description ?? string.Empty}");
- iw.WriteString($"¬{rom.Name ?? string.Empty}");
- iw.WriteString($"¬{rom.CRC ?? string.Empty}");
- iw.WriteString($"¬{rom.Size?.ToString() ?? string.Empty}");
- iw.WriteString($"¬{rom.Machine.RomOf ?? string.Empty}");
- iw.WriteString($"¬{rom.MergeTag ?? string.Empty}");
- iw.WriteString("¬");
- iw.WriteLine();
-
- break;
- }
-
- iw.Flush();
- }
}
}
diff --git a/SabreTools.Serialization/RomCenter.cs b/SabreTools.Serialization/RomCenter.Deserializer.cs
similarity index 99%
rename from SabreTools.Serialization/RomCenter.cs
rename to SabreTools.Serialization/RomCenter.Deserializer.cs
index 7cce3521..0eb32b9e 100644
--- a/SabreTools.Serialization/RomCenter.cs
+++ b/SabreTools.Serialization/RomCenter.Deserializer.cs
@@ -8,9 +8,9 @@ using SabreTools.Models.RomCenter;
namespace SabreTools.Serialization
{
///
- /// Serializer for RomCenter INI files
+ /// Deserializer for RomCenter INI files
///
- public class RomCenter
+ public partial class RomCenter
{
///
/// Deserializes a RomCenter INI file to the defined type
diff --git a/SabreTools.Serialization/RomCenter.Serializer.cs b/SabreTools.Serialization/RomCenter.Serializer.cs
new file mode 100644
index 00000000..b0ebc6a3
--- /dev/null
+++ b/SabreTools.Serialization/RomCenter.Serializer.cs
@@ -0,0 +1,176 @@
+using System.IO;
+using System.Linq;
+using System.Text;
+using SabreTools.IO.Writers;
+using SabreTools.Models.RomCenter;
+
+namespace SabreTools.Serialization
+{
+ ///
+ /// Serializer for RomCenter INI files
+ ///
+ public partial class RomCenter
+ {
+ ///
+ /// Serializes the defined type to a RomCenter INI file
+ ///
+ /// Data to serialize
+ /// Path to the file to serialize to
+ /// True on successful serialization, false otherwise
+ public static bool SerializeToFile(MetadataFile? metadataFile, string path)
+ {
+ using var stream = SerializeToStream(metadataFile);
+ if (stream == null)
+ return false;
+
+ using var fs = File.OpenWrite(path);
+ stream.Seek(0, SeekOrigin.Begin);
+ stream.CopyTo(fs);
+ return true;
+ }
+
+ ///
+ /// Serializes the defined type to a stream
+ ///
+ /// Data to serialize
+ /// Stream containing serialized data on success, null otherwise
+ public static Stream? SerializeToStream(MetadataFile? metadataFile)
+ {
+ // If the metadata file is null
+ if (metadataFile == null)
+ return null;
+
+ // Setup the writer and output
+ var stream = new MemoryStream();
+ var writer = new IniWriter(stream, Encoding.UTF8);
+
+ // Write out the credits section
+ WriteCredits(metadataFile.Credits, writer);
+
+ // Write out the dat section
+ WriteDat(metadataFile.Dat, writer);
+
+ // Write out the emulator section
+ WriteEmulator(metadataFile.Emulator, writer);
+
+ // Write out the games
+ WriteGames(metadataFile.Games, writer);
+
+ // Return the stream
+ return stream;
+ }
+
+ ///
+ /// Write credits information to the current writer
+ ///
+ /// Credits object representing the credits information
+ /// IniWriter representing the output
+ private static void WriteCredits(Credits? credits, IniWriter writer)
+ {
+ // If the credits information is missing, we can't do anything
+ if (credits == null)
+ return;
+
+ writer.WriteSection("credits");
+
+ writer.WriteKeyValuePair("author", credits.Author);
+ writer.WriteKeyValuePair("version", credits.Version);
+ writer.WriteKeyValuePair("email", credits.Email);
+ writer.WriteKeyValuePair("homepage", credits.Homepage);
+ writer.WriteKeyValuePair("url", credits.Url);
+ writer.WriteKeyValuePair("date", credits.Date);
+ writer.WriteKeyValuePair("comment", credits.Comment);
+ writer.WriteLine();
+
+ writer.Flush();
+ }
+
+ ///
+ /// Write dat information to the current writer
+ ///
+ /// Dat object representing the dat information
+ /// IniWriter representing the output
+ private static void WriteDat(Dat? dat, IniWriter writer)
+ {
+ // If the dat information is missing, we can't do anything
+ if (dat == null)
+ return;
+
+ writer.WriteSection("dat");
+
+ writer.WriteKeyValuePair("version", dat.Version);
+ writer.WriteKeyValuePair("plugin", dat.Plugin);
+ writer.WriteKeyValuePair("split", dat.Split);
+ writer.WriteKeyValuePair("merge", dat.Merge);
+ writer.WriteLine();
+
+ writer.Flush();
+ }
+
+ ///
+ /// Write emulator information to the current writer
+ ///
+ /// Emulator object representing the emulator information
+ /// IniWriter representing the output
+ private static void WriteEmulator(Emulator? emulator, IniWriter writer)
+ {
+ // If the emulator information is missing, we can't do anything
+ if (emulator == null)
+ return;
+
+ writer.WriteSection("emulator");
+
+ writer.WriteKeyValuePair("refname", emulator.RefName);
+ writer.WriteKeyValuePair("version", emulator.Version);
+ writer.WriteLine();
+
+ writer.Flush();
+ }
+
+ ///
+ /// Write games information to the current writer
+ ///
+ /// Games object representing the games information
+ /// IniWriter representing the output
+ private static void WriteGames(Games? games, IniWriter writer)
+ {
+ // If the games information is missing, we can't do anything
+ if (games?.Rom == null || !games.Rom.Any())
+ return;
+
+ writer.WriteSection("games");
+
+ foreach (var rom in games.Rom)
+ {
+ var romBuilder = new StringBuilder();
+
+ romBuilder.Append('¬');
+ romBuilder.Append(rom.ParentName);
+ romBuilder.Append('¬');
+ romBuilder.Append(rom.ParentDescription);
+ romBuilder.Append('¬');
+ romBuilder.Append(rom.GameName);
+ romBuilder.Append('¬');
+ romBuilder.Append(rom.GameDescription);
+ romBuilder.Append('¬');
+ romBuilder.Append(rom.RomName);
+ romBuilder.Append('¬');
+ romBuilder.Append(rom.RomCRC);
+ romBuilder.Append('¬');
+ romBuilder.Append(rom.RomSize);
+ romBuilder.Append('¬');
+ romBuilder.Append(rom.RomOf);
+ romBuilder.Append('¬');
+ romBuilder.Append(rom.MergeName);
+ romBuilder.Append('¬');
+ romBuilder.Append('\n');
+
+ writer.WriteString(romBuilder.ToString());
+ writer.Flush();
+ }
+
+ writer.WriteLine();
+ writer.Flush();
+ }
+ }
+}
\ No newline at end of file