Create and use Everdrive SMDB serializer

This commit is contained in:
Matt Nadareski
2023-07-29 20:46:05 -04:00
parent db7dd3d353
commit 41594b7f2d
7 changed files with 378 additions and 207 deletions

View File

@@ -104,8 +104,6 @@ namespace SabreTools.DatFiles.Formats
}
}
// TODO: Populate the games
return rows.ToArray();
}

View File

@@ -317,8 +317,6 @@ namespace SabreTools.DatFiles.Formats
games.Add(game);
}
// TODO: Populate the games
return games.ToArray();
}

View File

@@ -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
{
/// <summary>
/// Represents parsing and writing of an Everdrive SMDB file
/// </summary>
internal partial class EverdriveSMDB : DatFile
{
/// <inheritdoc/>
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
/// <summary>
/// Create a machine from the filename
/// </summary>
/// <param name="filename">Filename to derive from</param>
/// <returns>Filled machine and new filename on success, null on error</returns>
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);
}
/// <summary>
/// Convert rows information
/// </summary>
/// <param name="rows">Array of deserialized models to convert</param>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
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);
}
}
/// <summary>
/// Convert rows information
/// </summary>
/// <param name="row">Deserialized model to convert</param>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
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
}
}

View File

@@ -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
{
/// <summary>
/// Represents parsing and writing of an Everdrive SMDB file
/// </summary>
internal partial class EverdriveSMDB : DatFile
{
/// <inheritdoc/>
protected override ItemType[] GetSupportedTypes()
{
return new ItemType[] { ItemType.Rom };
}
/// <inheritdoc/>
protected override List<DatItemField> GetMissingRequiredFields(DatItem datItem)
{
List<DatItemField> 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;
}
/// <inheritdoc/>
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
/// <summary>
/// Create a MetadataFile from the current internal information
/// <summary>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise</param>
private Models.EverdriveSMDB.MetadataFile CreateMetadataFile(bool ignoreblanks)
{
var metadataFile = new Models.EverdriveSMDB.MetadataFile
{
Row = CreateRows(ignoreblanks)
};
return metadataFile;
}
/// <summary>
/// Create an array of Row from the current internal information
/// <summary>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise</param>
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<Models.EverdriveSMDB.Row>();
// 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();
}
/// <summary>
/// Create a Row from the current Rom DatItem
/// <summary>
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
}
}

View File

@@ -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
{
/// <summary>
/// Represents parsing and writing of an Everdrive SMDB file
/// </summary>
internal class EverdriveSMDB : DatFile
internal partial class EverdriveSMDB : DatFile
{
/// <summary>
/// Constructor designed for casting a base DatFile
@@ -25,192 +13,5 @@ namespace SabreTools.DatFiles.Formats
: base(datFile)
{
}
/// <inheritdoc/>
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();
}
/// <inheritdoc/>
protected override ItemType[] GetSupportedTypes()
{
return new ItemType[] { ItemType.Rom };
}
/// <inheritdoc/>
protected override List<DatItemField> GetMissingRequiredFields(DatItem datItem)
{
// TODO: Check required fields
return null;
}
/// <inheritdoc/>
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<DatItem> 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;
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="svw">SeparatedValueWriter to output to</param>
/// <param name="datItem">DatItem object to be output</param>
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();
}
}
}

View File

@@ -8,9 +8,9 @@ using SabreTools.Models.EverdriveSMDB;
namespace SabreTools.Serialization
{
/// <summary>
/// Separated value serializer for Everdrive SMDBs
/// Separated value deserializer for Everdrive SMDBs
/// </summary>
public class EverdriveSMDB
public partial class EverdriveSMDB
{
/// <summary>
/// Deserializes an Everdrive SMDB to the defined type

View File

@@ -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
{
/// <summary>
/// Separated value serializer for Everdrive SMDBs
/// </summary>
public partial class EverdriveSMDB
{
/// <summary>
/// Serializes the defined type to an Everdrive SMDB file
/// </summary>
/// <param name="metadataFile">Data to serialize</param>
/// <param name="path">Path to the file to serialize to</param>
/// <returns>True on successful serialization, false otherwise</returns>
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;
}
}
/// <summary>
/// Serializes the defined type to a stream
/// </summary>
/// <param name="metadataFile">Data to serialize</param>
/// <returns>Stream containing serialized data on success, null otherwise</returns>
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;
}
}
/// <summary>
/// Write rows information to the current writer
/// </summary>
/// <param name="rows">Array of Row objects representing the rows information</param>
/// <param name="writer">SeparatedValueWriter representing the output</param>
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<string>
{
row.SHA256,
row.Name,
row.SHA1,
row.MD5,
row.CRC32,
};
if (row.Size != null)
rowArray.Add(row.Size);
writer.WriteValues(rowArray.ToArray());
writer.Flush();
}
}
}
}