diff --git a/SabreTools.DatFiles/Formats/AttractMode.Writer.cs b/SabreTools.DatFiles/Formats/AttractMode.Writer.cs
index 7b89fa16..fad1070a 100644
--- a/SabreTools.DatFiles/Formats/AttractMode.Writer.cs
+++ b/SabreTools.DatFiles/Formats/AttractMode.Writer.cs
@@ -104,8 +104,6 @@ namespace SabreTools.DatFiles.Formats
}
}
- // TODO: Populate the games
-
return rows.ToArray();
}
diff --git a/SabreTools.DatFiles/Formats/ClrMamePro.Writer.cs b/SabreTools.DatFiles/Formats/ClrMamePro.Writer.cs
index 50e85bda..292ab6a3 100644
--- a/SabreTools.DatFiles/Formats/ClrMamePro.Writer.cs
+++ b/SabreTools.DatFiles/Formats/ClrMamePro.Writer.cs
@@ -317,8 +317,6 @@ namespace SabreTools.DatFiles.Formats
games.Add(game);
}
- // TODO: Populate the games
-
return games.ToArray();
}
diff --git a/SabreTools.DatFiles/Formats/EverdriveSMDB.Reader.cs b/SabreTools.DatFiles/Formats/EverdriveSMDB.Reader.cs
new file mode 100644
index 00000000..34b6c52e
--- /dev/null
+++ b/SabreTools.DatFiles/Formats/EverdriveSMDB.Reader.cs
@@ -0,0 +1,127 @@
+using System;
+using System.IO;
+using System.Linq;
+using SabreTools.Core;
+using SabreTools.Core.Tools;
+using SabreTools.DatItems;
+using SabreTools.DatItems.Formats;
+
+namespace SabreTools.DatFiles.Formats
+{
+ ///
+ /// Represents parsing and writing of an Everdrive SMDB file
+ ///
+ internal partial class EverdriveSMDB : 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.EverdriveSMDB.Deserialize(filename);
+
+ // Convert the row data to the internal format
+ ConvertRows(metadataFile?.Row, filename, indexId, statsOnly);
+ }
+ catch (Exception ex) when (!throwOnError)
+ {
+ string message = $"'{filename}' - An error occurred during parsing";
+ logger.Error(ex, message);
+ }
+ }
+
+ #region Converters
+
+ ///
+ /// Create a machine from the filename
+ ///
+ /// Filename to derive from
+ /// Filled machine and new filename on success, null on error
+ private static (Machine?, string?) DeriveMachine(string filename)
+ {
+ // If the filename is missing, we can't do anything
+ if (string.IsNullOrWhiteSpace(filename))
+ return (null, null);
+
+ string machineName = Path.GetFileNameWithoutExtension(filename);
+ if (filename.Contains('/'))
+ {
+ string[] split = filename.Split('/');
+ machineName = split[0];
+ filename = filename[(machineName.Length + 1)..];
+ }
+ else if (filename.Contains('\\'))
+ {
+ string[] split = filename.Split('\\');
+ machineName = split[0];
+ filename = filename[(machineName.Length + 1)..];
+ }
+
+ var machine = new Machine { Name = machineName };
+ return (machine, filename);
+ }
+
+ ///
+ /// Convert rows information
+ ///
+ /// Array of deserialized models 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 ConvertRows(Models.EverdriveSMDB.Row[]? rows, string filename, int indexId, bool statsOnly)
+ {
+ // If the rows array is missing, we can't do anything
+ if (rows == null || !rows.Any())
+ return;
+
+ // Loop through the rows and add
+ foreach (var row in rows)
+ {
+ ConvertRow(row, filename, indexId, statsOnly);
+ }
+ }
+
+ ///
+ /// Convert rows 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 ConvertRow(Models.EverdriveSMDB.Row? row, string filename, int indexId, bool statsOnly)
+ {
+ // If the row is missing, we can't do anything
+ if (row == null)
+ return;
+
+ (var machine, string name) = DeriveMachine(row.Name);
+ if (machine == null)
+ machine = new Machine { Name = Path.GetFileNameWithoutExtension(row.Name) };
+
+ var rom = new Rom()
+ {
+ Name = name,
+ Size = Utilities.CleanLong(row.Size),
+ CRC = row.CRC32,
+ MD5 = row.MD5,
+ SHA1 = row.SHA1,
+ SHA256 = row.SHA256,
+ ItemStatus = ItemStatus.None,
+
+ Machine = machine,
+
+ Source = new Source
+ {
+ Index = indexId,
+ Name = filename,
+ },
+ };
+
+ // Now process and add the rom
+ ParseAddHelper(rom, statsOnly);
+ }
+
+ #endregion
+ }
+}
diff --git a/SabreTools.DatFiles/Formats/EverdriveSMDB.Writer.cs b/SabreTools.DatFiles/Formats/EverdriveSMDB.Writer.cs
new file mode 100644
index 00000000..d3cdf667
--- /dev/null
+++ b/SabreTools.DatFiles/Formats/EverdriveSMDB.Writer.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using SabreTools.Core;
+using SabreTools.DatItems;
+using SabreTools.DatItems.Formats;
+using SabreTools.IO.Writers;
+
+namespace SabreTools.DatFiles.Formats
+{
+ ///
+ /// Represents parsing and writing of an Everdrive SMDB file
+ ///
+ internal partial class EverdriveSMDB : 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.SHA256))
+ missingFields.Add(DatItemField.SHA256);
+ if (string.IsNullOrWhiteSpace(rom.SHA1))
+ missingFields.Add(DatItemField.SHA1);
+ if (string.IsNullOrWhiteSpace(rom.MD5))
+ missingFields.Add(DatItemField.MD5);
+ if (string.IsNullOrWhiteSpace(rom.CRC))
+ missingFields.Add(DatItemField.CRC);
+ 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.EverdriveSMDB.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.EverdriveSMDB.MetadataFile CreateMetadataFile(bool ignoreblanks)
+ {
+ var metadataFile = new Models.EverdriveSMDB.MetadataFile
+ {
+ Row = CreateRows(ignoreblanks)
+ };
+ return metadataFile;
+ }
+
+ ///
+ /// Create an array of Row from the current internal information
+ ///
+ /// True if blank roms should be skipped on output, false otherwise
+ private Models.EverdriveSMDB.Row[]? CreateRows(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 rows
+ var rows = 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:
+ rows.Add(CreateRow(rom));
+ break;
+ }
+ }
+ }
+
+ return rows.ToArray();
+ }
+
+ ///
+ /// Create a Row from the current Rom DatItem
+ ///
+ private static Models.EverdriveSMDB.Row CreateRow(Rom rom)
+ {
+ var row = new Models.EverdriveSMDB.Row
+ {
+ SHA256 = rom.SHA256,
+ Name = $"{rom.Machine.Name}/{rom.Name}",
+ SHA1 = rom.SHA1,
+ MD5 = rom.MD5,
+ CRC32 = rom.CRC,
+ Size = rom.Size?.ToString(),
+ };
+ return row;
+ }
+
+ #endregion
+ }
+}
diff --git a/SabreTools.DatFiles/Formats/EverdriveSmdb.cs b/SabreTools.DatFiles/Formats/EverdriveSmdb.cs
index f20ec5bf..64613e80 100644
--- a/SabreTools.DatFiles/Formats/EverdriveSmdb.cs
+++ b/SabreTools.DatFiles/Formats/EverdriveSmdb.cs
@@ -1,21 +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;
-using SabreTools.IO.Readers;
-using SabreTools.IO.Writers;
-
-namespace SabreTools.DatFiles.Formats
+namespace SabreTools.DatFiles.Formats
{
///
/// Represents parsing and writing of an Everdrive SMDB file
///
- internal class EverdriveSMDB : DatFile
+ internal partial class EverdriveSMDB : DatFile
{
///
/// Constructor designed for casting a base DatFile
@@ -25,192 +13,5 @@ namespace SabreTools.DatFiles.Formats
: base(datFile)
{
}
-
- ///
- public override void ParseFile(string filename, int indexId, bool keep, bool statsOnly = false, bool throwOnError = false)
- {
- // Open a file reader
- Encoding enc = filename.GetEncoding();
- SeparatedValueReader svr = new(System.IO.File.OpenRead(filename), enc)
- {
- Header = false,
- Quotes = false,
- Separator = '\t',
- VerifyFieldCount = false,
- };
-
- while (!svr.EndOfStream)
- {
- try
- {
- // If we can't read the next line, break
- if (!svr.ReadNextLine())
- break;
-
- // If the line returns null somehow, skip
- if (svr.Line == null)
- continue;
-
- /*
- The gameinfo order is as follows
- 0 - SHA-256
- 1 - Machine Name/Filename
- 2 - SHA-1
- 3 - MD5
- 4 - CRC32
- 5 - Size (Optional)
- */
-
- string[] fullname = svr.Line[1].Split('/');
-
- Rom rom = new()
- {
- Name = svr.Line[1][(fullname[0].Length + 1)..],
- Size = null, // No size provided, but we don't want the size being 0
- CRC = svr.Line[4],
- MD5 = svr.Line[3],
- SHA1 = svr.Line[2],
- SHA256 = svr.Line[0],
- ItemStatus = ItemStatus.None,
-
- Machine = new Machine
- {
- Name = fullname[0],
- Description = fullname[0],
- },
-
- Source = new Source
- {
- Index = indexId,
- Name = filename,
- },
- };
-
- // Size in SMDB files is optional
- if (svr.Line.Count > 5)
- rom.Size = Utilities.CleanLong(svr.Line[5]);
-
- // Now process and add the rom
- ParseAddHelper(rom, statsOnly);
- }
- catch (Exception ex) when (!throwOnError)
- {
- string message = $"'{filename}' - There was an error parsing line {svr.LineNumber} '{svr.CurrentLine}'";
- logger.Error(ex, message);
- }
- }
-
- svr.Dispose();
- }
-
- ///
- 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;
- }
-
- SeparatedValueWriter svw = new(fs, new UTF8Encoding(false))
- {
- Quotes = false,
- Separator = '\t',
- VerifyFieldCount = true
- };
-
- // 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(svw, datItem);
- }
- }
-
- logger.User($"'{outfile}' written!{Environment.NewLine}");
- svw.Dispose();
- fs.Dispose();
- }
- catch (Exception ex) when (!throwOnError)
- {
- logger.Error(ex);
- return false;
- }
-
- return true;
- }
-
- ///
- /// Write out Game start using the supplied StreamWriter
- ///
- /// SeparatedValueWriter to output to
- /// DatItem object to be output
- private void WriteDatItem(SeparatedValueWriter svw, DatItem datItem)
- {
- // No game should start with a path separator
- datItem.Machine.Name = datItem.Machine.Name.TrimStart(Path.DirectorySeparatorChar);
-
- // Pre-process the item name
- ProcessItemName(datItem, true);
-
- // Build the state
- switch (datItem.ItemType)
- {
- case ItemType.Rom:
- var rom = datItem as Rom;
-
- string[] fields = new string[]
- {
- rom.SHA256 ?? string.Empty,
- $"{rom.Machine.Name ?? string.Empty}/{rom.Name ?? string.Empty}",
- rom.SHA1 ?? string.Empty,
- rom.MD5 ?? string.Empty,
- rom.CRC ?? string.Empty,
- rom.Size.ToString() ?? string.Empty,
- };
-
- svw.WriteValues(fields);
-
- break;
- }
-
- svw.Flush();
- }
}
}
diff --git a/SabreTools.Serialization/EverdriveSMDB.cs b/SabreTools.Serialization/EverdriveSMDB.Deserializer.cs
similarity index 96%
rename from SabreTools.Serialization/EverdriveSMDB.cs
rename to SabreTools.Serialization/EverdriveSMDB.Deserializer.cs
index 27e5756a..f736d9f2 100644
--- a/SabreTools.Serialization/EverdriveSMDB.cs
+++ b/SabreTools.Serialization/EverdriveSMDB.Deserializer.cs
@@ -8,9 +8,9 @@ using SabreTools.Models.EverdriveSMDB;
namespace SabreTools.Serialization
{
///
- /// Separated value serializer for Everdrive SMDBs
+ /// Separated value deserializer for Everdrive SMDBs
///
- public class EverdriveSMDB
+ public partial class EverdriveSMDB
{
///
/// Deserializes an Everdrive SMDB to the defined type
diff --git a/SabreTools.Serialization/EverdriveSMDB.Serializer.cs b/SabreTools.Serialization/EverdriveSMDB.Serializer.cs
new file mode 100644
index 00000000..ed18821f
--- /dev/null
+++ b/SabreTools.Serialization/EverdriveSMDB.Serializer.cs
@@ -0,0 +1,102 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using SabreTools.IO.Writers;
+using SabreTools.Models.EverdriveSMDB;
+
+namespace SabreTools.Serialization
+{
+ ///
+ /// Separated value serializer for Everdrive SMDBs
+ ///
+ public partial class EverdriveSMDB
+ {
+ ///
+ /// Serializes the defined type to an Everdrive SMDB 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)
+ {
+ try
+ {
+ 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;
+ }
+ catch
+ {
+ // TODO: Handle logging the exception
+ return false;
+ }
+ }
+
+ ///
+ /// Serializes the defined type to a stream
+ ///
+ /// Data to serialize
+ /// Stream containing serialized data on success, null otherwise
+ public static Stream? SerializeToStream(MetadataFile? metadataFile)
+ {
+ try
+ {
+ // If the metadata file is null
+ if (metadataFile == null)
+ return null;
+
+ // Setup the writer and output
+ var stream = new MemoryStream();
+ var writer = new SeparatedValueWriter(stream, Encoding.UTF8) { Separator = '\t', Quotes = false };
+
+ // Write out the rows, if they exist
+ WriteRows(metadataFile.Row, writer);
+
+ // Return the stream
+ return stream;
+ }
+ catch
+ {
+ // TODO: Handle logging the exception
+ return null;
+ }
+ }
+
+ ///
+ /// Write rows information to the current writer
+ ///
+ /// Array of Row objects representing the rows information
+ /// SeparatedValueWriter representing the output
+ private static void WriteRows(Row[]? rows, SeparatedValueWriter writer)
+ {
+ // If the games information is missing, we can't do anything
+ if (rows == null || !rows.Any())
+ return;
+
+ // Loop through and write out the rows
+ foreach (var row in rows)
+ {
+ var rowArray = new List
+ {
+ row.SHA256,
+ row.Name,
+ row.SHA1,
+ row.MD5,
+ row.CRC32,
+ };
+
+ if (row.Size != null)
+ rowArray.Add(row.Size);
+
+ writer.WriteValues(rowArray.ToArray());
+ writer.Flush();
+ }
+ }
+ }
+}
\ No newline at end of file