Libraries

This change looks dramatic, but it's just separating out the already-split namespaces into separate top-level folders. In theory, every single one could be built into their own Nuget package. `SabreTools.Serialization` still builds the normal Nuget package that is used by all other projects and includes all namespaces.
This commit is contained in:
Matt Nadareski
2026-03-21 16:26:56 -04:00
parent bec0aeb04c
commit 7689c6dd07
1495 changed files with 841 additions and 338 deletions

View File

@@ -0,0 +1,9 @@
using SabreTools.Data.Models.ArchiveDotOrg;
namespace SabreTools.Serialization.Writers
{
public class ArchiveDotOrg : XmlFile<Files>
{
// All logic taken care of in the base class
}
}

View File

@@ -0,0 +1,223 @@
using System.IO;
using System.Text;
using SabreTools.Data.Models.AttractMode;
using SabreTools.IO.Extensions;
using SabreTools.IO.Writers;
#pragma warning disable CA1822 // Mark members as static
namespace SabreTools.Serialization.Writers
{
public class AttractMode : BaseBinaryWriter<MetadataFile>
{
#region Constants
public const string HeaderWithoutRomname = "#Name;Title;Emulator;CloneOf;Year;Manufacturer;Category;Players;Rotation;Control;Status;DisplayCount;DisplayType;AltRomname;AltTitle;Extra;Buttons";
public static readonly string[] HeaderArrayWithoutRomname =
[
"#Name",
"Title",
"Emulator",
"CloneOf",
"Year",
"Manufacturer",
"Category",
"Players",
"Rotation",
"Control",
"Status",
"DisplayCount",
"DisplayType",
"AltRomname",
"AltTitle",
"Extra",
"Buttons"
];
public const string HeaderWithRomname = "#Romname;Title;Emulator;Cloneof;Year;Manufacturer;Category;Players;Rotation;Control;Status;DisplayCount;DisplayType;AltRomname;AltTitle;Extra;Buttons;Favourite;Tags;PlayedCount;PlayedTime;FileIsAvailable";
public static readonly string[] HeaderArrayWithRomname =
[
"#Romname",
"Title",
"Emulator",
"Cloneof",
"Year",
"Manufacturer",
"Category",
"Players",
"Rotation",
"Control",
"Status",
"DisplayCount",
"DisplayType",
"AltRomname",
"AltTitle",
"Extra",
"Buttons",
"Favourite",
"Tags",
"PlayedCount",
"PlayedTime",
"FileIsAvailable"
];
#endregion
#region IByteWriter
/// <inheritdoc/>
public override byte[]? SerializeArray(MetadataFile? obj)
=> SerializeArray(obj, false);
/// <inheritdoc/>
public byte[]? SerializeArray(MetadataFile? obj, bool longHeader)
{
using var stream = SerializeStream(obj, longHeader);
if (stream is null)
return null;
byte[] bytes = new byte[stream.Length];
int read = stream.Read(bytes, 0, bytes.Length);
return bytes;
}
#endregion
#region IFileWriter
/// <inheritdoc/>
public override bool SerializeFile(MetadataFile? obj, string? path)
=> SerializeFile(obj, path, false);
/// <inheritdoc/>
public bool SerializeFile(MetadataFile? obj, string? path, bool longHeader)
{
if (string.IsNullOrEmpty(path))
return false;
using var stream = SerializeStream(obj, longHeader);
if (stream is null)
return false;
using var fs = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None);
stream.CopyTo(fs);
fs.Flush();
return true;
}
#endregion
#region IStreamWriter
/// <inheritdoc/>
public override Stream? SerializeStream(MetadataFile? obj)
=> SerializeStream(obj, false);
/// <inheritdoc/>
public Stream? SerializeStream(MetadataFile? obj, bool longHeader)
{
// If the metadata file is null
if (obj is null)
return null;
// Setup the writer and output
var stream = new MemoryStream();
var writer = new SeparatedValueWriter(stream, Encoding.UTF8)
{
Separator = ';',
Quotes = false,
VerifyFieldCount = false,
};
// Write the header
writer.WriteValues(longHeader ? HeaderArrayWithRomname : HeaderArrayWithoutRomname);
// Write out the rows, if they exist
WriteRows(obj.Row, writer, longHeader);
// Return the stream
stream.SeekIfPossible(0, SeekOrigin.Begin);
return stream;
}
/// <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>
/// <param name="longHeader">True if the long variant of the row should be written, false otherwise</param>
private static void WriteRows(Row[]? rows, SeparatedValueWriter writer, bool longHeader)
{
// If the games information is missing, we can't do anything
if (rows is null || rows.Length == 0)
return;
// Loop through and write out the rows
foreach (var row in rows)
{
if (row is null)
continue;
string?[] rowArray;
if (longHeader)
{
rowArray =
[
row.Name,
row.Title,
row.Emulator,
row.CloneOf,
row.Year,
row.Manufacturer,
row.Category,
row.Players,
row.Rotation,
row.Control,
row.Status,
row.DisplayCount,
row.DisplayType,
row.AltRomname,
row.AltTitle,
row.Extra,
row.Buttons,
row.Favorite,
row.Tags,
row.PlayedCount,
row.PlayedTime,
row.FileIsAvailable,
];
}
else
{
rowArray =
[
row.Name,
row.Title,
row.Emulator,
row.CloneOf,
row.Year,
row.Manufacturer,
row.Category,
row.Players,
row.Rotation,
row.Control,
row.Status,
row.DisplayCount,
row.DisplayType,
row.AltRomname,
row.AltTitle,
row.Extra,
row.Buttons,
];
}
writer.WriteValues(rowArray);
writer.Flush();
}
}
#endregion
}
}

View File

@@ -0,0 +1,65 @@
using System.IO;
namespace SabreTools.Serialization.Writers
{
/// <summary>
/// Base class for all binary serializers
/// </summary>
/// <typeparam name="TModel">Type of the model to serialize</typeparam>
/// <remarks>
/// This class allows all inheriting types to only implement <see cref="IStreamWriter<>"/>
/// and still implicitly implement <see cref="IByteWriter<>"/> and <see cref="IFileWriter<>"/>
/// </remarks>
public abstract class BaseBinaryWriter<TModel> :
IByteWriter<TModel>,
IFileWriter<TModel>,
IStreamWriter<TModel>
{
/// <inheritdoc/>
public bool Debug { get; set; } = false;
#region IByteWriter
/// <inheritdoc/>
public virtual byte[]? SerializeArray(TModel? obj)
{
using var stream = SerializeStream(obj);
if (stream is null)
return null;
byte[] bytes = new byte[stream.Length];
int read = stream.Read(bytes, 0, bytes.Length);
return bytes;
}
#endregion
#region IFileWriter
/// <inheritdoc/>
public virtual bool SerializeFile(TModel? obj, string? path)
{
if (string.IsNullOrEmpty(path))
return false;
using var stream = SerializeStream(obj);
if (stream is null)
return false;
using var fs = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None);
stream.CopyTo(fs);
fs.Flush();
return true;
}
#endregion
#region IStreamWriter
/// <inheritdoc/>
public abstract Stream? SerializeStream(TModel? obj);
#endregion
}
}

View File

@@ -0,0 +1,12 @@
using System.IO;
using System.Text;
namespace SabreTools.Serialization.Writers
{
public class Catalog : JsonFile<Data.Models.Xbox.Catalog>
{
/// <remarks>Catalog.js file is encoded as UTF-16 LE</remarks>
public override Stream? SerializeStream(Data.Models.Xbox.Catalog? obj)
=> Serialize(obj, new UnicodeEncoding());
}
}

View File

@@ -0,0 +1,529 @@
using System.IO;
using System.Text;
using SabreTools.Data.Models.ClrMamePro;
using SabreTools.IO.Extensions;
using SabreTools.IO.Writers;
#pragma warning disable CA1822 // Mark members as static
namespace SabreTools.Serialization.Writers
{
public class ClrMamePro : BaseBinaryWriter<MetadataFile>
{
#region IByteWriter
/// <inheritdoc/>
public override byte[]? SerializeArray(MetadataFile? obj)
=> SerializeArray(obj, false);
/// <inheritdoc/>
public byte[]? SerializeArray(MetadataFile? obj, bool quotes)
{
using var stream = SerializeStream(obj, quotes);
if (stream is null)
return null;
byte[] bytes = new byte[stream.Length];
int read = stream.Read(bytes, 0, bytes.Length);
return bytes;
}
#endregion
#region IFileWriter
/// <inheritdoc/>
public override bool SerializeFile(MetadataFile? obj, string? path)
=> SerializeFile(obj, path, true);
/// <inheritdoc cref="SerializeFile(MetadataFile, string)"/>
public bool SerializeFile(MetadataFile? obj, string? path, bool quotes)
{
if (string.IsNullOrEmpty(path))
return false;
using var stream = SerializeStream(obj, quotes);
if (stream is null)
return false;
using var fs = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None);
stream.CopyTo(fs);
fs.Flush();
return true;
}
#endregion
#region IStreamWriter
/// <inheritdoc/>
public override Stream? SerializeStream(MetadataFile? obj)
=> SerializeStream(obj, true);
/// <inheritdoc cref="SerializeStream(MetadataFile)"/>
public Stream? SerializeStream(MetadataFile? obj, bool quotes)
{
// If the metadata file is null
if (obj is null)
return null;
// Setup the writer and output
var stream = new MemoryStream();
var writer = new ClrMameProWriter(stream, Encoding.UTF8) { Quotes = quotes };
// Write the header, if it exists
WriteHeader(obj.ClrMamePro, writer);
// Write out the games, if they exist
WriteGames(obj.Game, writer);
// Write out the info, if it exists
WriteInfo(obj.Info, writer);
// Return the stream
stream.SeekIfPossible(0, SeekOrigin.Begin);
return stream;
}
/// <summary>
/// Write header information to the current writer
/// </summary>
/// <param name="header">ClrMamePro representing the header information</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteHeader(Data.Models.ClrMamePro.ClrMamePro? header, ClrMameProWriter writer)
{
// If the header information is missing, we can't do anything
if (header is null)
return;
writer.WriteStartElement("clrmamepro");
writer.WriteOptionalStandalone("name", header.Name);
writer.WriteOptionalStandalone("description", header.Description);
writer.WriteOptionalStandalone("rootdir", header.RootDir);
writer.WriteOptionalStandalone("category", header.Category);
writer.WriteOptionalStandalone("version", header.Version);
writer.WriteOptionalStandalone("date", header.Date);
writer.WriteOptionalStandalone("author", header.Author);
writer.WriteOptionalStandalone("homepage", header.Homepage);
writer.WriteOptionalStandalone("url", header.Url);
writer.WriteOptionalStandalone("comment", header.Comment);
writer.WriteOptionalStandalone("header", header.Header);
writer.WriteOptionalStandalone("type", header.Type);
writer.WriteOptionalStandalone("forcemerging", header.ForceMerging);
writer.WriteOptionalStandalone("forcezipping", header.ForceZipping);
writer.WriteOptionalStandalone("forcepacking", header.ForcePacking);
writer.WriteEndElement(); // clrmamepro
writer.Flush();
}
/// <summary>
/// Write games information to the current writer
/// </summary>
/// <param name="games">Array of GameBase objects representing the games information</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteGames(GameBase[]? games, ClrMameProWriter writer)
{
// If the games information is missing, we can't do anything
if (games is null || games.Length == 0)
return;
// Loop through and write out the games
foreach (var game in games)
{
if (game is null)
continue;
WriteGame(game, writer);
writer.Flush();
}
}
/// <summary>
/// Write game information to the current writer
/// </summary>
/// <param name="game">GameBase object representing the game information</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteGame(GameBase game, ClrMameProWriter writer)
{
// If the game information is missing, we can't do anything
if (game is null)
return;
switch (game)
{
case Game:
writer.WriteStartElement("game");
break;
case Machine:
writer.WriteStartElement("machine");
break;
case Resource:
writer.WriteStartElement("resource");
break;
case Set:
writer.WriteStartElement(name: "set");
break;
default:
// TODO: Log invalid values
break;
}
// Write the standalone values
writer.WriteRequiredStandalone("name", game.Name, throwOnError: true);
writer.WriteOptionalStandalone("description", game.Description);
writer.WriteOptionalStandalone("driverstatus", game.DriverStatus);
writer.WriteOptionalStandalone("year", game.Year);
writer.WriteOptionalStandalone("manufacturer", game.Manufacturer);
writer.WriteOptionalStandalone("category", game.Category);
writer.WriteOptionalStandalone("cloneof", game.CloneOf);
writer.WriteOptionalStandalone("romof", game.RomOf);
writer.WriteOptionalStandalone("sampleof", game.SampleOf);
// Write the item values
WriteReleases(game.Release, writer);
WriteBiosSets(game.BiosSet, writer);
WriteRoms(game.Rom, writer);
WriteDisks(game.Disk, writer);
WriteMedia(game.Media, writer);
WriteSamples(game.Sample, writer);
WriteArchives(game.Archive, writer);
WriteChips(game.Chip, writer);
WriteVideos(game.Video, writer);
WriteSound(game.Sound, writer);
WriteInput(game.Input, writer);
WriteDipSwitches(game.DipSwitch, writer);
WriteDriver(game.Driver, writer);
writer.WriteEndElement(); // game, machine, resource, set
}
/// <summary>
/// Write releases information to the current writer
/// </summary>
/// <param name="releases">Array of Release objects to write</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteReleases(Release[]? releases, ClrMameProWriter writer)
{
// If the array is missing, we can't do anything
if (releases is null)
return;
foreach (var release in releases)
{
writer.WriteStartElement("release");
writer.WriteRequiredAttributeString("name", release.Name, throwOnError: true);
writer.WriteRequiredAttributeString("region", release.Region, throwOnError: true);
writer.WriteOptionalAttributeString("language", release.Language);
writer.WriteOptionalAttributeString("date", release.Date);
writer.WriteOptionalAttributeString("default", release.Default);
writer.WriteEndElement(); // release
}
}
/// <summary>
/// Write biossets information to the current writer
/// </summary>
/// <param name="biossets">Array of BiosSet objects to write</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteBiosSets(BiosSet[]? biossets, ClrMameProWriter writer)
{
// If the array is missing, we can't do anything
if (biossets is null)
return;
foreach (var biosset in biossets)
{
writer.WriteStartElement("biosset");
writer.WriteRequiredAttributeString("name", biosset.Name, throwOnError: true);
writer.WriteRequiredAttributeString("description", biosset.Description, throwOnError: true);
writer.WriteOptionalAttributeString("default", biosset.Default);
writer.WriteEndElement(); // release
}
}
/// <summary>
/// Write roms information to the current writer
/// </summary>
/// <param name="roms">Array of Rom objects to write</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteRoms(Rom[]? roms, ClrMameProWriter writer)
{
// If the array is missing, we can't do anything
if (roms is null)
return;
foreach (var rom in roms)
{
writer.WriteStartElement("rom");
writer.WriteRequiredAttributeString("name", rom.Name, throwOnError: true);
writer.WriteRequiredAttributeString("size", rom.Size, throwOnError: true);
writer.WriteOptionalAttributeString("crc16", rom.CRC16);
writer.WriteOptionalAttributeString("crc", rom.CRC);
writer.WriteOptionalAttributeString("crc64", rom.CRC64);
writer.WriteOptionalAttributeString("md2", rom.MD2);
writer.WriteOptionalAttributeString("md4", rom.MD4);
writer.WriteOptionalAttributeString("md5", rom.MD5);
writer.WriteOptionalAttributeString("ripemd128", rom.RIPEMD128);
writer.WriteOptionalAttributeString("ripemd160", rom.RIPEMD160);
writer.WriteOptionalAttributeString("sha1", rom.SHA1);
writer.WriteOptionalAttributeString("sha256", rom.SHA256);
writer.WriteOptionalAttributeString("sha384", rom.SHA384);
writer.WriteOptionalAttributeString("sha512", rom.SHA512);
writer.WriteOptionalAttributeString("spamsum", rom.SpamSum);
writer.WriteOptionalAttributeString("xxh3_64", rom.xxHash364);
writer.WriteOptionalAttributeString("xxh3_128", rom.xxHash3128);
writer.WriteOptionalAttributeString("merge", rom.Merge);
writer.WriteOptionalAttributeString("status", rom.Status);
writer.WriteOptionalAttributeString("region", rom.Region);
writer.WriteOptionalAttributeString("flags", rom.Flags);
writer.WriteOptionalAttributeString("offs", rom.Offs);
writer.WriteOptionalAttributeString("serial", rom.Serial);
writer.WriteOptionalAttributeString("header", rom.Header);
writer.WriteOptionalAttributeString("date", rom.Date);
writer.WriteOptionalAttributeString("inverted", rom.Inverted);
writer.WriteOptionalAttributeString("mia", rom.MIA);
writer.WriteEndElement(); // rom
}
}
/// <summary>
/// Write disks information to the current writer
/// </summary>
/// <param name="disks">Array of Disk objects to write</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteDisks(Disk[]? disks, ClrMameProWriter writer)
{
// If the array is missing, we can't do anything
if (disks is null)
return;
foreach (var disk in disks)
{
writer.WriteStartElement("disk");
writer.WriteRequiredAttributeString("name", disk.Name, throwOnError: true);
writer.WriteOptionalAttributeString("md5", disk.MD5);
writer.WriteOptionalAttributeString("sha1", disk.SHA1);
writer.WriteOptionalAttributeString("merge", disk.Merge);
writer.WriteOptionalAttributeString("status", disk.Status);
writer.WriteOptionalAttributeString("flags", disk.Flags);
writer.WriteEndElement(); // disk
}
}
/// <summary>
/// Write medias information to the current writer
/// </summary>
/// <param name="medias">Array of Media objects to write</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteMedia(Media[]? medias, ClrMameProWriter writer)
{
// If the array is missing, we can't do anything
if (medias is null)
return;
foreach (var media in medias)
{
writer.WriteStartElement("media");
writer.WriteRequiredAttributeString("name", media.Name, throwOnError: true);
writer.WriteOptionalAttributeString("md5", media.MD5);
writer.WriteOptionalAttributeString("sha1", media.SHA1);
writer.WriteOptionalAttributeString("sha256", media.SHA256);
writer.WriteOptionalAttributeString("spamsum", media.SpamSum);
writer.WriteEndElement(); // media
}
}
/// <summary>
/// Write samples information to the current writer
/// </summary>
/// <param name="samples">Array of Sample objects to write</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteSamples(Sample[]? samples, ClrMameProWriter writer)
{
// If the array is missing, we can't do anything
if (samples is null)
return;
foreach (var sample in samples)
{
writer.WriteStartElement("sample");
writer.WriteRequiredAttributeString("name", sample.Name, throwOnError: true);
writer.WriteEndElement(); // sample
}
}
/// <summary>
/// Write archives information to the current writer
/// </summary>
/// <param name="archives">Array of Archive objects to write</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteArchives(Archive[]? archives, ClrMameProWriter writer)
{
// If the array is missing, we can't do anything
if (archives is null)
return;
foreach (var archive in archives)
{
writer.WriteStartElement("archive");
writer.WriteRequiredAttributeString("name", archive.Name, throwOnError: true);
writer.WriteEndElement(); // archive
}
}
/// <summary>
/// Write chips information to the current writer
/// </summary>
/// <param name="chips">Array of Chip objects to write</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteChips(Chip[]? chips, ClrMameProWriter writer)
{
// If the array is missing, we can't do anything
if (chips is null)
return;
foreach (var chip in chips)
{
writer.WriteStartElement("chip");
writer.WriteRequiredAttributeString("type", chip.Type, throwOnError: true);
writer.WriteRequiredAttributeString("name", chip.Name, throwOnError: true);
writer.WriteOptionalAttributeString("flags", chip.Flags);
writer.WriteOptionalAttributeString("clock", chip.Clock);
writer.WriteEndElement(); // chip
}
}
/// <summary>
/// Write video information to the current writer
/// </summary>
/// <param name="videos">Array of Video objects to write</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteVideos(Video[]? videos, ClrMameProWriter writer)
{
// If the item is missing, we can't do anything
if (videos is null)
return;
foreach (var video in videos)
{
writer.WriteStartElement("video");
writer.WriteRequiredAttributeString("screen", video.Screen, throwOnError: true);
writer.WriteRequiredAttributeString("orientation", video.Orientation, throwOnError: true);
writer.WriteOptionalAttributeString("x", video.X);
writer.WriteOptionalAttributeString("y", video.Y);
writer.WriteOptionalAttributeString("aspectx", video.AspectX);
writer.WriteOptionalAttributeString("aspecty", video.AspectY);
writer.WriteOptionalAttributeString("freq", video.Freq);
writer.WriteEndElement(); // video
}
}
/// <summary>
/// Write sound information to the current writer
/// </summary>
/// <param name="sound">Sound object to write</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteSound(Sound? sound, ClrMameProWriter writer)
{
// If the item is missing, we can't do anything
if (sound is null)
return;
writer.WriteStartElement("sound");
writer.WriteRequiredAttributeString("channels", sound.Channels, throwOnError: true);
writer.WriteEndElement(); // sound
}
/// <summary>
/// Write input information to the current writer
/// </summary>
/// <param name="input">Input object to write</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteInput(Input? input, ClrMameProWriter writer)
{
// If the item is missing, we can't do anything
if (input is null)
return;
writer.WriteStartElement("input");
writer.WriteRequiredAttributeString("players", input.Players, throwOnError: true);
writer.WriteOptionalAttributeString("control", input.Control);
writer.WriteRequiredAttributeString("buttons", input.Buttons, throwOnError: true);
writer.WriteOptionalAttributeString("coins", input.Coins);
writer.WriteOptionalAttributeString("tilt", input.Tilt);
writer.WriteOptionalAttributeString("service", input.Service);
writer.WriteEndElement(); // input
}
/// <summary>
/// Write dipswitches information to the current writer
/// </summary>
/// <param name="dipswitches">Array of DipSwitch objects to write</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteDipSwitches(DipSwitch[]? dipswitches, ClrMameProWriter writer)
{
// If the array is missing, we can't do anything
if (dipswitches is null)
return;
foreach (var dipswitch in dipswitches)
{
writer.WriteStartElement("dipswitch");
writer.WriteRequiredAttributeString("name", dipswitch.Name, throwOnError: true);
foreach (var entry in dipswitch.Entry ?? [])
{
writer.WriteRequiredAttributeString("entry", entry);
}
writer.WriteOptionalAttributeString("default", dipswitch.Default);
writer.WriteEndElement(); // dipswitch
}
}
/// <summary>
/// Write driver information to the current writer
/// </summary>
/// <param name="driver">Driver object to write</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteDriver(Driver? driver, ClrMameProWriter writer)
{
// If the item is missing, we can't do anything
if (driver is null)
return;
writer.WriteStartElement("driver");
writer.WriteRequiredAttributeString("status", driver.Status, throwOnError: true);
writer.WriteOptionalAttributeString("color", driver.Color); // TODO: Probably actually required
writer.WriteOptionalAttributeString("sound", driver.Sound); // TODO: Probably actually required
writer.WriteOptionalAttributeString("palettesize", driver.PaletteSize);
writer.WriteOptionalAttributeString("blit", driver.Blit);
writer.WriteEndElement(); // driver
}
/// <summary>
/// Write info information to the current writer
/// </summary>
/// <param name="info">ClrMamePro representing the info information</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteInfo(Info? info, ClrMameProWriter writer)
{
// If the info information is missing, we can't do anything
if (info?.Source is null)
return;
writer.WriteStartElement("info");
foreach (var source in info.Source)
{
writer.WriteOptionalStandalone("source", source);
}
writer.WriteEndElement(); // info
writer.Flush();
}
#endregion
}
}

View File

@@ -0,0 +1,261 @@
using System;
using System.IO;
using System.Text;
using SabreTools.Data.Models.CueSheets;
using SabreTools.IO.Extensions;
#pragma warning disable CA1510 // Use ArgumentNullException throw helper
namespace SabreTools.Serialization.Writers
{
public class CueSheet : BaseBinaryWriter<Data.Models.CueSheets.CueSheet>
{
/// <inheritdoc/>
public override Stream? SerializeStream(Data.Models.CueSheets.CueSheet? obj)
{
// If the cuesheet is null
if (obj is null)
return null;
// If we don't have any files, it's invalid
if (obj?.Files is null)
throw new InvalidDataException(nameof(obj.Files));
else if (obj.Files.Length == 0)
throw new ArgumentException("No files provided to write");
// Setup the writer and output
var stream = new MemoryStream();
#if NET20 || NET35 || NET40
var writer = new StreamWriter(stream, Encoding.ASCII, 1024);
#else
var writer = new StreamWriter(stream, Encoding.ASCII, 1024, true);
#endif
// Write the file
WriteCueSheet(obj, writer);
// Return the stream
stream.SeekIfPossible(0, SeekOrigin.Begin);
return stream;
}
/// <summary>
/// Write the cuesheet out to a stream
/// </summary>
/// <param name="cueSheet">CueSheet to write</param>
/// <param name="sw">StreamWriter to write to</param>
private static void WriteCueSheet(Data.Models.CueSheets.CueSheet cueSheet, StreamWriter sw)
{
// If we don't have any files, it's invalid
if (cueSheet.Files is null)
throw new InvalidDataException(nameof(cueSheet.Files));
else if (cueSheet.Files.Length == 0)
throw new ArgumentException("No files provided to write");
if (!string.IsNullOrEmpty(cueSheet.Catalog))
sw.WriteLine($"CATALOG {cueSheet.Catalog}");
if (!string.IsNullOrEmpty(cueSheet.CdTextFile))
sw.WriteLine($"CDTEXTFILE {cueSheet.CdTextFile}");
if (!string.IsNullOrEmpty(cueSheet.Performer))
sw.WriteLine($"PERFORMER {cueSheet.Performer}");
if (!string.IsNullOrEmpty(cueSheet.Songwriter))
sw.WriteLine($"SONGWRITER {cueSheet.Songwriter}");
if (!string.IsNullOrEmpty(cueSheet.Title))
sw.WriteLine($"TITLE {cueSheet.Title}");
foreach (var cueFile in cueSheet.Files)
{
WriteCueFile(cueFile, sw);
}
}
/// <summary>
/// Write the FILE out to a stream
/// </summary>
/// <param name="cueFile">CueFile to write</param>
/// <param name="sw">StreamWriter to write to</param>
private static void WriteCueFile(CueFile? cueFile, StreamWriter sw)
{
// If we don't have any tracks, it's invalid
if (cueFile?.Tracks is null)
throw new InvalidDataException(nameof(cueFile.Tracks));
else if (cueFile.Tracks.Length == 0)
throw new ArgumentException("No tracks provided to write");
sw.WriteLine($"FILE \"{cueFile.FileName}\" {FromFileType(cueFile.FileType)}");
foreach (var track in cueFile.Tracks)
{
WriteCueTrack(track, sw);
}
}
/// <summary>
/// Write the TRACK out to a stream
/// </summary>
/// <param name="cueFile">CueFile to write</param>
/// <param name="sw">StreamWriter to write to</param>
private static void WriteCueTrack(CueTrack? cueTrack, StreamWriter sw)
{
// If we don't have any indices, it's invalid
if (cueTrack?.Indices is null)
throw new InvalidDataException(nameof(cueTrack.Indices));
else if (cueTrack.Indices.Length == 0)
throw new ArgumentException("No indices provided to write");
sw.WriteLine($" TRACK {cueTrack.Number:D2} {FromDataType(cueTrack.DataType)}");
if (cueTrack.Flags != 0)
sw.WriteLine($" FLAGS {FromFlags(cueTrack.Flags)}");
if (!string.IsNullOrEmpty(cueTrack.ISRC))
sw.WriteLine($" ISRC {cueTrack.ISRC}");
if (!string.IsNullOrEmpty(cueTrack.Performer))
sw.WriteLine($" PERFORMER {cueTrack.Performer}");
if (!string.IsNullOrEmpty(cueTrack.Songwriter))
sw.WriteLine($" SONGWRITER {cueTrack.Songwriter}");
if (!string.IsNullOrEmpty(cueTrack.Title))
sw.WriteLine($" TITLE {cueTrack.Title}");
if (cueTrack.PreGap is not null)
WritePreGap(cueTrack.PreGap, sw);
foreach (var index in cueTrack.Indices)
{
WriteCueIndex(index, sw);
}
if (cueTrack.PostGap is not null)
WritePostGap(cueTrack.PostGap, sw);
}
/// <summary>
/// Write the PREGAP out to a stream
/// </summary>
/// <param name="preGap">PreGap to write</param>
/// <param name="sw">StreamWriter to write to</param>
private static void WritePreGap(PreGap preGap, StreamWriter sw)
{
sw.WriteLine($" PREGAP {preGap.Minutes:D2}:{preGap.Seconds:D2}:{preGap.Frames:D2}");
}
/// <summary>
/// Write the INDEX out to a stream
/// </summary>
/// <param name="cueIndex">CueIndex to write</param>
/// <param name="sw">StreamWriter to write to</param>
private static void WriteCueIndex(CueIndex? cueIndex, StreamWriter sw)
{
if (cueIndex is null)
throw new ArgumentNullException(nameof(cueIndex));
sw.WriteLine($" INDEX {cueIndex.Index:D2} {cueIndex.Minutes:D2}:{cueIndex.Seconds:D2}:{cueIndex.Frames:D2}");
}
/// <summary>
/// Write the POSTGAP out to a stream
/// </summary>
/// <param name="postGap">PostGap to write</param>
/// <param name="sw">StreamWriter to write to</param>
private static void WritePostGap(PostGap postGap, StreamWriter sw)
{
sw.WriteLine($" POSTGAP {postGap.Minutes:D2}:{postGap.Seconds:D2}:{postGap.Frames:D2}");
}
#region Helpers
/// <summary>
/// Get the string from a given file type
/// </summary>
/// <param name="fileType">CueFileType to get value from</param>
/// <returns>String, if possible (default BINARY)</returns>
private static string FromFileType(CueFileType fileType)
{
return fileType switch
{
CueFileType.BINARY => "BINARY",
CueFileType.MOTOROLA => "MOTOROLA",
CueFileType.AIFF => "AIFF",
CueFileType.WAVE => "WAVE",
CueFileType.MP3 => "MP3",
_ => string.Empty,
};
}
/// <summary>
/// Get the string from a given data type
/// </summary>
/// <param name="dataType">CueTrackDataType to get value from</param>
/// <returns>string, if possible</returns>
private static string FromDataType(CueTrackDataType dataType)
{
return dataType switch
{
CueTrackDataType.AUDIO => "AUDIO",
CueTrackDataType.CDG => "CDG",
CueTrackDataType.MODE1_2048 => "MODE1/2048",
CueTrackDataType.MODE1_2352 => "MODE1/2352",
CueTrackDataType.MODE2_2336 => "MODE2/2336",
CueTrackDataType.MODE2_2352 => "MODE2/2352",
CueTrackDataType.CDI_2336 => "CDI/2336",
CueTrackDataType.CDI_2352 => "CDI/2352",
_ => string.Empty,
};
}
/// <summary>
/// Get the string value for a set of track flags
/// </summary>
/// <param name="flags">CueTrackFlag to get value from</param>
/// <returns>String value representing the CueTrackFlag, if possible</returns>
private static string FromFlags(CueTrackFlag flags)
{
string outputFlagString = string.Empty;
#if NET20 || NET35
if ((flags & CueTrackFlag.DCP) != 0)
#else
if (flags.HasFlag(CueTrackFlag.DCP))
#endif
outputFlagString += "DCP ";
#if NET20 || NET35
if ((flags & CueTrackFlag.FourCH) != 0)
#else
if (flags.HasFlag(CueTrackFlag.FourCH))
#endif
outputFlagString += "4CH ";
#if NET20 || NET35
if ((flags & CueTrackFlag.PRE) != 0)
#else
if (flags.HasFlag(CueTrackFlag.PRE))
#endif
outputFlagString += "PRE ";
#if NET20 || NET35
if ((flags & CueTrackFlag.SCMS) != 0)
#else
if (flags.HasFlag(CueTrackFlag.SCMS))
#endif
outputFlagString += "SCMS ";
#if NET20 || NET35
if ((flags & CueTrackFlag.DATA) != 0)
#else
if (flags.HasFlag(CueTrackFlag.DATA))
#endif
outputFlagString += "DATA ";
return outputFlagString.Trim();
}
#endregion
}
}

View File

@@ -0,0 +1,127 @@
using System.IO;
using System.Text;
using SabreTools.Data.Models.DosCenter;
using SabreTools.IO.Extensions;
using SabreTools.IO.Writers;
namespace SabreTools.Serialization.Writers
{
public class DosCenter : BaseBinaryWriter<MetadataFile>
{
/// <inheritdoc/>
public override Stream? SerializeStream(MetadataFile? obj)
{
// If the metadata file is null
if (obj is null)
return null;
// Setup the writer and output
var stream = new MemoryStream();
var writer = new ClrMameProWriter(stream, Encoding.UTF8)
{
Quotes = false,
};
// Write the header, if it exists
WriteHeader(obj.DosCenter, writer);
// Write out the games, if they exist
WriteGames(obj.Game, writer);
// Return the stream
stream.SeekIfPossible(0, SeekOrigin.Begin);
return stream;
}
/// <summary>
/// Write header information to the current writer
/// </summary>
/// <param name="header">DosCenter representing the header information</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteHeader(Data.Models.DosCenter.DosCenter? header, ClrMameProWriter writer)
{
// If the header information is missing, we can't do anything
if (header is null)
return;
writer.WriteStartElement("DOSCenter");
writer.WriteOptionalStandalone("Name:", header.Name);
writer.WriteOptionalStandalone("Description:", header.Description);
writer.WriteOptionalStandalone("Version:", header.Version);
writer.WriteOptionalStandalone("Date:", header.Date);
writer.WriteOptionalStandalone("Author:", header.Author);
writer.WriteOptionalStandalone("Homepage:", header.Homepage);
writer.WriteOptionalStandalone("Comment:", header.Comment);
writer.WriteEndElement(); // doscenter
writer.Flush();
}
/// <summary>
/// Write games information to the current writer
/// </summary>
/// <param name="games">Array of Game objects representing the games information</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteGames(Game[]? games, ClrMameProWriter writer)
{
// If the games information is missing, we can't do anything
if (games is null || games.Length == 0)
return;
// Loop through and write out the games
foreach (var game in games)
{
WriteGame(game, writer);
writer.Flush();
}
}
/// <summary>
/// Write game information to the current writer
/// </summary>
/// <param name="game">Game object representing the game information</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteGame(Game game, ClrMameProWriter writer)
{
// If the game information is missing, we can't do anything
if (game is null)
return;
writer.WriteStartElement("game");
// Write the standalone values
writer.WriteRequiredStandalone("name", game.Name, throwOnError: true);
// Write the item values
WriteFiles(game.File, writer);
writer.WriteEndElement(); // game
}
/// <summary>
/// Write files information to the current writer
/// </summary>
/// <param name="files">Array of File objects to write</param>
/// <param name="writer">ClrMameProWriter representing the output</param>
private static void WriteFiles(Data.Models.DosCenter.File[]? files, ClrMameProWriter writer)
{
// If the array is missing, we can't do anything
if (files is null)
return;
foreach (var file in files)
{
writer.WriteStartElement("file");
writer.WriteRequiredAttributeString("name", file.Name, throwOnError: true);
writer.WriteRequiredAttributeString("size", file.Size, throwOnError: true);
writer.WriteOptionalAttributeString("date", file.Date);
writer.WriteRequiredAttributeString("crc", file.CRC?.ToUpperInvariant(), throwOnError: true);
writer.WriteRequiredAttributeString("sha1", file.SHA1?.ToUpperInvariant());
writer.WriteEndElement(); // file
}
}
}
}

View File

@@ -0,0 +1,65 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using SabreTools.Data.Models.EverdriveSMDB;
using SabreTools.IO.Extensions;
using SabreTools.IO.Writers;
namespace SabreTools.Serialization.Writers
{
public class EverdriveSMDB : BaseBinaryWriter<MetadataFile>
{
/// <inheritdoc/>
public override Stream? SerializeStream(MetadataFile? obj)
{
// If the metadata file is null
if (obj is 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(obj.Row, writer);
// Return the stream
stream.SeekIfPossible(0, SeekOrigin.Begin);
return stream;
}
/// <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 is null || rows.Length == 0)
return;
// Loop through and write out the rows
foreach (var row in rows)
{
if (row is null)
continue;
var rowArray = new List<string?>
{
row.SHA256,
row.Name,
row.SHA1,
row.MD5,
row.CRC32,
};
if (row.Size is not null)
rowArray.Add(row.Size);
writer.WriteValues([.. rowArray]);
writer.Flush();
}
}
}
}

View File

@@ -0,0 +1,9 @@
#if NET20
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
internal sealed class ExtensionAttribute : Attribute {}
}
#endif

View File

@@ -0,0 +1,372 @@
using System;
using System.IO;
using System.Text;
using SabreTools.Data.Models.Hashfile;
using SabreTools.Hashing;
using SabreTools.IO.Extensions;
using SabreTools.IO.Writers;
#pragma warning disable CA1822 // Mark members as static
namespace SabreTools.Serialization.Writers
{
public class Hashfile : BaseBinaryWriter<Data.Models.Hashfile.Hashfile>
{
#region IByteWriter
/// <inheritdoc/>
public override byte[]? SerializeArray(Data.Models.Hashfile.Hashfile? obj)
=> SerializeArray(obj, HashType.CRC32);
/// <inheritdoc/>
public byte[]? SerializeArray(Data.Models.Hashfile.Hashfile? obj, HashType hash)
{
using var stream = SerializeStream(obj, hash);
if (stream is null)
return null;
byte[] bytes = new byte[stream.Length];
int read = stream.Read(bytes, 0, bytes.Length);
return bytes;
}
#endregion
#region IFileWriter
/// <inheritdoc/>
public override bool SerializeFile(Data.Models.Hashfile.Hashfile? obj, string? path)
=> SerializeFile(obj, path, HashType.CRC32);
/// <inheritdoc/>
public bool SerializeFile(Data.Models.Hashfile.Hashfile? obj, string? path, HashType hash)
{
if (string.IsNullOrEmpty(path))
return false;
using var stream = SerializeStream(obj, hash);
if (stream is null)
return false;
using var fs = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None);
stream.CopyTo(fs);
fs.Flush();
return true;
}
#endregion
#region IStreamWriter
/// <inheritdoc/>
public override Stream? SerializeStream(Data.Models.Hashfile.Hashfile? obj)
=> SerializeStream(obj, HashType.CRC32);
/// <inheritdoc/>
public Stream? SerializeStream(Data.Models.Hashfile.Hashfile? obj, HashType hash)
{
// If the metadata file is null
if (obj is null)
return null;
// Setup the writer and output
var stream = new MemoryStream();
var writer = new SeparatedValueWriter(stream, Encoding.UTF8)
{
Separator = ' ',
Quotes = false,
VerifyFieldCount = false,
};
// Write out the items, if they exist
#pragma warning disable IDE0010
switch (hash)
{
case HashType.CRC32:
WriteSFV(obj.SFV, writer);
break;
case HashType.MD2:
WriteMD2(obj.MD2, writer);
break;
case HashType.MD4:
WriteMD4(obj.MD4, writer);
break;
case HashType.MD5:
WriteMD5(obj.MD5, writer);
break;
case HashType.RIPEMD128:
WriteRIPEMD128(obj.RIPEMD128, writer);
break;
case HashType.RIPEMD160:
WriteRIPEMD160(obj.RIPEMD160, writer);
break;
case HashType.SHA1:
WriteSHA1(obj.SHA1, writer);
break;
case HashType.SHA256:
WriteSHA256(obj.SHA256, writer);
break;
case HashType.SHA384:
WriteSHA384(obj.SHA384, writer);
break;
case HashType.SHA512:
WriteSHA512(obj.SHA512, writer);
break;
case HashType.SpamSum:
WriteSpamSum(obj.SpamSum, writer);
break;
default:
throw new ArgumentOutOfRangeException(nameof(hash));
}
#pragma warning restore IDE0010
// Return the stream
stream.SeekIfPossible(0, SeekOrigin.Begin);
return stream;
}
/// <summary>
/// Write SFV information to the current writer
/// </summary>
/// <param name="sfvs">Array of SFV objects representing the files</param>
/// <param name="writer">SeparatedValueWriter representing the output</param>
private static void WriteSFV(SFV[]? sfvs, SeparatedValueWriter writer)
{
// If the item information is missing, we can't do anything
if (sfvs is null || sfvs.Length == 0)
return;
// Loop through and write out the items
foreach (var sfv in sfvs)
{
if (string.IsNullOrEmpty(sfv.File) || string.IsNullOrEmpty(sfv.Hash))
continue;
writer.WriteValues([sfv.File!, sfv.Hash!]);
writer.Flush();
}
}
/// <summary>
/// Write MD2 information to the current writer
/// </summary>
/// <param name="md2s">Array of MD2 objects representing the files</param>
/// <param name="writer">SeparatedValueWriter representing the output</param>
private static void WriteMD2(MD2[]? md2s, SeparatedValueWriter writer)
{
// If the item information is missing, we can't do anything
if (md2s is null || md2s.Length == 0)
return;
// Loop through and write out the items
foreach (var md2 in md2s)
{
if (string.IsNullOrEmpty(md2.Hash) || string.IsNullOrEmpty(md2.File))
continue;
writer.WriteValues([md2.Hash!, md2.File!]);
writer.Flush();
}
}
/// <summary>
/// Write MD4 information to the current writer
/// </summary>
/// <param name="md4s">Array of MD4 objects representing the files</param>
/// <param name="writer">SeparatedValueWriter representing the output</param>
private static void WriteMD4(MD4[]? md4s, SeparatedValueWriter writer)
{
// If the item information is missing, we can't do anything
if (md4s is null || md4s.Length == 0)
return;
// Loop through and write out the items
foreach (var md4 in md4s)
{
if (string.IsNullOrEmpty(md4.Hash) || string.IsNullOrEmpty(md4.File))
continue;
writer.WriteValues([md4.Hash!, md4.File!]);
writer.Flush();
}
}
/// <summary>
/// Write MD5 information to the current writer
/// </summary>
/// <param name="md5s">Array of MD5 objects representing the files</param>
/// <param name="writer">SeparatedValueWriter representing the output</param>
private static void WriteMD5(MD5[]? md5s, SeparatedValueWriter writer)
{
// If the item information is missing, we can't do anything
if (md5s is null || md5s.Length == 0)
return;
// Loop through and write out the items
foreach (var md5 in md5s)
{
if (string.IsNullOrEmpty(md5.Hash) || string.IsNullOrEmpty(md5.File))
continue;
writer.WriteValues([md5.Hash!, md5.File!]);
writer.Flush();
}
}
/// <summary>
/// Write RIPEMD128 information to the current writer
/// </summary>
/// <param name="ripemd128s">Array of RIPEMD128 objects representing the files</param>
/// <param name="writer">SeparatedValueWriter representing the output</param>
private static void WriteRIPEMD128(RIPEMD128[]? ripemd128s, SeparatedValueWriter writer)
{
// If the item information is missing, we can't do anything
if (ripemd128s is null || ripemd128s.Length == 0)
return;
// Loop through and write out the items
foreach (var ripemd128 in ripemd128s)
{
if (string.IsNullOrEmpty(ripemd128.Hash) || string.IsNullOrEmpty(ripemd128.File))
continue;
writer.WriteValues([ripemd128.Hash!, ripemd128.File!]);
writer.Flush();
}
}
/// <summary>
/// Write RIPEMD160 information to the current writer
/// </summary>
/// <param name="ripemd160s">Array of RIPEMD160 objects representing the files</param>
/// <param name="writer">SeparatedValueWriter representing the output</param>
private static void WriteRIPEMD160(RIPEMD160[]? ripemd160s, SeparatedValueWriter writer)
{
// If the item information is missing, we can't do anything
if (ripemd160s is null || ripemd160s.Length == 0)
return;
// Loop through and write out the items
foreach (var ripemd160 in ripemd160s)
{
if (string.IsNullOrEmpty(ripemd160.Hash) || string.IsNullOrEmpty(ripemd160.File))
continue;
writer.WriteValues([ripemd160.Hash!, ripemd160.File!]);
writer.Flush();
}
}
/// <summary>
/// Write SHA1 information to the current writer
/// </summary>
/// <param name="sha1s">Array of SHA1 objects representing the files</param>
/// <param name="writer">SeparatedValueWriter representing the output</param>
private static void WriteSHA1(SHA1[]? sha1s, SeparatedValueWriter writer)
{
// If the item information is missing, we can't do anything
if (sha1s is null || sha1s.Length == 0)
return;
// Loop through and write out the items
foreach (var sha1 in sha1s)
{
if (string.IsNullOrEmpty(sha1.Hash) || string.IsNullOrEmpty(sha1.File))
continue;
writer.WriteValues([sha1.Hash!, sha1.File!]);
writer.Flush();
}
}
/// <summary>
/// Write SHA256 information to the current writer
/// </summary>
/// <param name="sha256s">Array of SHA256 objects representing the files</param>
/// <param name="writer">SeparatedValueWriter representing the output</param>
private static void WriteSHA256(SHA256[]? sha256s, SeparatedValueWriter writer)
{
// If the item information is missing, we can't do anything
if (sha256s is null || sha256s.Length == 0)
return;
// Loop through and write out the items
foreach (var sha256 in sha256s)
{
if (string.IsNullOrEmpty(sha256.Hash) || string.IsNullOrEmpty(sha256.File))
continue;
writer.WriteValues([sha256.Hash!, sha256.File!]);
writer.Flush();
}
}
/// <summary>
/// Write SHA384 information to the current writer
/// </summary>
/// <param name="sha384s">Array of SHA384 objects representing the files</param>
/// <param name="writer">SeparatedValueWriter representing the output</param>
private static void WriteSHA384(SHA384[]? sha384s, SeparatedValueWriter writer)
{
// If the item information is missing, we can't do anything
if (sha384s is null || sha384s.Length == 0)
return;
// Loop through and write out the items
foreach (var sha384 in sha384s)
{
if (string.IsNullOrEmpty(sha384.Hash) || string.IsNullOrEmpty(sha384.File))
continue;
writer.WriteValues([sha384.Hash!, sha384.File!]);
writer.Flush();
}
}
/// <summary>
/// Write SHA512 information to the current writer
/// </summary>
/// <param name="sha512s">Array of SHA512 objects representing the files</param>
/// <param name="writer">SeparatedValueWriter representing the output</param>
private static void WriteSHA512(SHA512[]? sha512s, SeparatedValueWriter writer)
{
// If the item information is missing, we can't do anything
if (sha512s is null || sha512s.Length == 0)
return;
// Loop through and write out the items
foreach (var sha512 in sha512s)
{
if (string.IsNullOrEmpty(sha512.Hash) || string.IsNullOrEmpty(sha512.File))
continue;
writer.WriteValues([sha512.Hash!, sha512.File!]);
writer.Flush();
}
}
/// <summary>
/// Write SpamSum information to the current writer
/// </summary>
/// <param name="spamsums">Array of SpamSum objects representing the files</param>
/// <param name="writer">SeparatedValueWriter representing the output</param>
private static void WriteSpamSum(SpamSum[]? spamsums, SeparatedValueWriter writer)
{
// If the item information is missing, we can't do anything
if (spamsums is null || spamsums.Length == 0)
return;
// Loop through and write out the items
foreach (var spamsum in spamsums)
{
if (string.IsNullOrEmpty(spamsum.Hash) || string.IsNullOrEmpty(spamsum.File))
continue;
writer.WriteValues([spamsum.Hash!, spamsum.File!]);
writer.Flush();
}
}
#endregion
}
}

View File

@@ -0,0 +1,21 @@
namespace SabreTools.Serialization.Writers
{
/// <summary>
/// Defines how to write to byte arrays
/// </summary>
public interface IByteWriter<TModel>
{
/// <summary>
/// Enable outputting debug information
/// </summary>
public bool Debug { get; set; }
/// <summary>
/// Serialize a <typeparamref name="TModel"/> into a byte array
/// </summary>
/// <typeparam name="T">Type of object to serialize from</typeparam>
/// <param name="obj">Data to serialize</param>
/// <returns>Filled object on success, null on error</returns>
public byte[]? SerializeArray(TModel? obj);
}
}

View File

@@ -0,0 +1,22 @@
namespace SabreTools.Serialization.Writers
{
/// <summary>
/// Defines how to write to files
/// </summary>
public interface IFileWriter<TModel>
{
/// <summary>
/// Enable outputting debug information
/// </summary>
public bool Debug { get; set; }
/// <summary>
/// Serialize a <typeparamref name="TModel"/> into a file
/// </summary>
/// <typeparam name="T">Type of object to serialize from</typeparam>
/// <param name="obj">Data to serialize</param>
/// <param name="path">Path to the file to serialize to</param>
/// <returns>True on successful serialization, false otherwise</returns>
public bool SerializeFile(TModel? obj, string? path);
}
}

View File

@@ -0,0 +1,130 @@
using System;
using System.IO;
using System.Text;
namespace SabreTools.Serialization.Writers
{
public class IRD : BaseBinaryWriter<Data.Models.IRD.File>
{
/// <inheritdoc/>
public override Stream? SerializeStream(Data.Models.IRD.File? obj)
{
// If the data is invalid
if (obj?.Magic is null)
return null;
// If the magic doesn't match
string magic = Encoding.ASCII.GetString(obj.Magic);
if (magic != "3IRD")
return null;
// If the version is less than the supported
if (obj.Version < 6)
return null;
// If any static-length fields aren't the correct length
if (obj.TitleID.Length != 9)
return null;
if (obj.Title.Length != obj.TitleLength)
return null;
if (obj.SystemVersion.Length != 4)
return null;
if (obj.GameVersion.Length != 5)
return null;
if (obj.AppVersion.Length != 5)
return null;
if (obj.Header.Length != obj.HeaderLength)
return null;
if (obj.Footer.Length != obj.FooterLength)
return null;
if (obj.RegionHashes.Length != obj.RegionCount || !Array.TrueForAll(obj.RegionHashes, h => h is null || h.Length != 16))
return null;
if (obj.FileKeys.Length != obj.FileCount)
return null;
if (obj.FileHashes.Length != obj.FileCount || !Array.TrueForAll(obj.FileHashes, h => h is null || h.Length != 16))
return null;
if (obj.PIC.Length != 115)
return null;
if (obj.Data1Key.Length != 16)
return null;
if (obj.Data2Key.Length != 16)
return null;
// Create the output stream
var stream = new MemoryStream();
stream.Write(obj.Magic, 0, obj.Magic.Length);
stream.WriteByte(obj.Version);
byte[] titleId = Encoding.ASCII.GetBytes(obj.TitleID);
stream.Write(titleId, 0, titleId.Length);
stream.WriteByte(obj.TitleLength);
byte[] title = Encoding.ASCII.GetBytes(obj.Title);
stream.Write(title, 0, title.Length);
byte[] systemVersion = Encoding.ASCII.GetBytes(obj.SystemVersion);
stream.Write(systemVersion, 0, systemVersion.Length);
byte[] gameVersion = Encoding.ASCII.GetBytes(obj.GameVersion);
stream.Write(gameVersion, 0, gameVersion.Length);
byte[] appVersion = Encoding.ASCII.GetBytes(obj.AppVersion);
stream.Write(appVersion, 0, appVersion.Length);
if (obj.Version == 7)
{
byte[] uid = BitConverter.GetBytes(obj.UID);
stream.Write(uid, 0, uid.Length);
}
byte[] headerLength = BitConverter.GetBytes(obj.HeaderLength);
stream.Write(headerLength, 0, headerLength.Length);
stream.Write(obj.Header, 0, obj.Header.Length);
byte[] footerLength = BitConverter.GetBytes(obj.FooterLength);
stream.Write(footerLength, 0, footerLength.Length);
stream.Write(obj.Footer, 0, obj.Footer.Length);
stream.WriteByte(obj.RegionCount);
for (int i = 0; i < obj.RegionCount; i++)
{
stream.Write(obj.RegionHashes[i], 0, obj.RegionHashes[i].Length);
}
byte[] fileCount = BitConverter.GetBytes(obj.FileCount);
stream.Write(fileCount, 0, fileCount.Length);
for (int i = 0; i < obj.FileCount; i++)
{
byte[] fileKey = BitConverter.GetBytes(obj.FileKeys[i]);
stream.Write(fileKey, 0, fileKey.Length);
stream.Write(obj.FileHashes[i], 0, obj.FileHashes[i].Length);
}
byte[] extraConfig = BitConverter.GetBytes(obj.ExtraConfig);
stream.Write(extraConfig, 0, extraConfig.Length);
byte[] attachments = BitConverter.GetBytes(obj.Attachments);
stream.Write(attachments, 0, attachments.Length);
if (obj.Version >= 9)
stream.Write(obj.PIC, 0, obj.PIC.Length);
stream.Write(obj.Data1Key, 0, obj.Data1Key.Length);
stream.Write(obj.Data2Key, 0, obj.Data2Key.Length);
if (obj.Version < 9)
stream.Write(obj.PIC, 0, obj.PIC.Length);
if (obj.Version > 7)
{
byte[] uid = BitConverter.GetBytes(obj.UID);
stream.Write(uid, 0, uid.Length);
}
byte[] crc = BitConverter.GetBytes(obj.CRC);
stream.Write(crc, 0, crc.Length);
return stream;
}
}
}

View File

@@ -0,0 +1,21 @@
namespace SabreTools.Serialization.Writers
{
/// <summary>
/// Defines how to write to Streams
/// </summary>
public interface IStreamWriter<TModel>
{
/// <summary>
/// Enable outputting debug information
/// </summary>
public bool Debug { get; set; }
/// <summary>
/// Serialize a <typeparamref name="TModel"/> into a Stream
/// </summary>
/// <typeparam name="T">Type of object to serialize from</typeparam>
/// <param name="obj">Data to serialize</param>
/// <returns>Filled object on success, null on error</returns>
public System.IO.Stream? SerializeStream(TModel? obj);
}
}

View File

@@ -0,0 +1,21 @@
namespace SabreTools.Serialization.Writers
{
/// <summary>
/// Defines how to write to strings
/// </summary>
public interface IStringWriter<TModel>
{
/// <summary>
/// Enable outputting debug information
/// </summary>
public bool Debug { get; set; }
/// <summary>
/// Serialize a <typeparamref name="TModel"/> into a string
/// </summary>
/// <typeparam name="T">Type of object to serialize from</typeparam>
/// <param name="obj">Data to serialize</param>
/// <returns>Filled string on successful serialization, null otherwise</returns>
public string? Serialize(TModel? obj);
}
}

View File

@@ -0,0 +1,105 @@
using System.IO;
using System.Text;
using Newtonsoft.Json;
using SabreTools.IO.Extensions;
namespace SabreTools.Serialization.Writers
{
/// <summary>
/// Base class for other JSON serializers
/// </summary>
/// <typeparam name="T"></typeparam>
public class JsonFile<T> : BaseBinaryWriter<T>
{
#region IByteWriter
/// <inheritdoc/>
public override byte[]? SerializeArray(T? obj)
=> SerializeArray(obj, new UTF8Encoding(false));
/// <summary>
/// Serialize a <typeparamref name="T"/> into a byte array
/// </summary>
/// <typeparam name="T">Type of object to serialize from</typeparam>
/// <param name="obj">Data to serialize</param>
/// <param name="encoding">Encoding to parse text as</param>
/// <returns>Filled object on success, null on error</returns>
public byte[]? SerializeArray(T? obj, Encoding encoding)
{
using var stream = Serialize(obj, encoding);
if (stream is null)
return null;
byte[] bytes = new byte[stream.Length];
int read = stream.Read(bytes, 0, bytes.Length);
return bytes;
}
#endregion
#region IFileWriter
/// <inheritdoc/>
public override bool SerializeFile(T? obj, string? path)
=> Serialize(obj, path, new UTF8Encoding(false));
/// <summary>
/// Serialize a <typeparamref name="T"/> into a file
/// </summary>
/// <typeparam name="T">Type of object to serialize from</typeparam>
/// <param name="obj">Data to serialize</param>
/// <param name="path">Path to the file to serialize to</param>
/// <param name="encoding">Encoding to parse text as</param>
/// <returns>True on successful serialization, false otherwise</returns>
public bool Serialize(T? obj, string? path, Encoding encoding)
{
if (string.IsNullOrEmpty(path))
return false;
using var stream = Serialize(obj, encoding);
if (stream is null)
return false;
using var fs = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None);
stream.CopyTo(fs);
fs.Flush();
return true;
}
#endregion
#region IStreamWriter
/// <inheritdoc/>
public override Stream? SerializeStream(T? obj)
=> Serialize(obj, new UTF8Encoding(false));
/// <summary>
/// Serialize a <typeparamref name="T"/> into a Stream
/// </summary>
/// <typeparam name="T">Type of object to serialize from</typeparam>
/// <param name="obj">Data to serialize</param>
/// <param name="encoding"></param>
/// <returns>Filled object on success, null on error</returns>
public Stream? Serialize(T? obj, Encoding encoding)
{
// If the object is null
if (obj is null)
return null;
// Setup the serializer and the writer
var serializer = JsonSerializer.Create();
var stream = new MemoryStream();
var streamWriter = new StreamWriter(stream, encoding);
var jsonWriter = new JsonTextWriter(streamWriter);
// Perform the deserialization and return
serializer.Serialize(jsonWriter, obj);
stream.SeekIfPossible(0, SeekOrigin.Begin);
return stream;
}
#endregion
}
}

View File

@@ -0,0 +1,158 @@
using System.IO;
using System.Text;
using SabreTools.Data.Models.Listrom;
using SabreTools.IO.Extensions;
namespace SabreTools.Serialization.Writers
{
public class Listrom : BaseBinaryWriter<MetadataFile>
{
/// <inheritdoc/>
public override Stream? SerializeStream(MetadataFile? obj)
{
// If the metadata file is null
if (obj is null)
return null;
// Setup the writer and output
var stream = new MemoryStream();
var writer = new StreamWriter(stream, Encoding.UTF8);
// Write out the sets, if they exist
WriteSets(obj.Set, writer);
// Return the stream
stream.SeekIfPossible(0, SeekOrigin.Begin);
return stream;
}
/// <summary>
/// Write sets information to the current writer
/// </summary>
/// <param name="sets">Array of Set objects representing the sets information</param>
/// <param name="writer">StreamWriter representing the output</param>
private static void WriteSets(Set[]? sets, StreamWriter writer)
{
// If the games information is missing, we can't do anything
if (sets is null || sets.Length == 0)
return;
// Loop through and write out the games
foreach (var set in sets)
{
WriteSet(set, writer);
writer.Flush();
}
}
/// <summary>
/// Write set information to the current writer
/// </summary>
/// <param name="set">Set object representing the set information</param>
/// <param name="writer">StreamWriter representing the output</param>
private static void WriteSet(Set set, StreamWriter writer)
{
// If the set information is missing, we can't do anything
if (set is null)
return;
if (!string.IsNullOrEmpty(set.Driver))
{
if (set.Row is not null && set.Row.Length > 0)
{
writer.WriteLine($"ROMs required for driver \"{set.Driver}\".");
writer.WriteLine("Name Size Checksum");
writer.Flush();
WriteRows(set.Row, writer);
writer.WriteLine();
writer.Flush();
}
else
{
writer.WriteLine($"No ROMs required for driver \"{set.Driver}\".");
writer.WriteLine();
writer.Flush();
}
}
else if (!string.IsNullOrEmpty(set.Device))
{
if (set.Row is not null && set.Row.Length > 0)
{
writer.WriteLine($"ROMs required for device \"{set.Device}\".");
writer.WriteLine("Name Size Checksum");
writer.Flush();
WriteRows(set.Row, writer);
writer.WriteLine();
writer.Flush();
}
else
{
writer.WriteLine($"No ROMs required for device \"{set.Device}\".");
writer.WriteLine();
writer.Flush();
}
}
}
/// <summary>
/// Write rows information to the current writer
/// </summary>
/// <param name="rows">Array of Row objects to write</param>
/// <param name="writer">StreamWriter representing the output</param>
private static void WriteRows(Row[]? rows, StreamWriter writer)
{
// If the array is missing, we can't do anything
if (rows is null)
return;
foreach (var row in rows)
{
if (string.IsNullOrEmpty(row.Name))
continue;
var rowBuilder = new StringBuilder();
int padding = 40 - (row.Size?.Length ?? 0);
if (padding < row.Name!.Length)
padding = row.Name.Length + 2;
rowBuilder.Append($"{row.Name.PadRight(padding, ' ')}");
if (row.Size is not null)
rowBuilder.Append($"{row.Size} ");
if (row.NoGoodDumpKnown)
{
rowBuilder.Append("NO GOOD DUMP KNOWN");
}
else
{
if (row.Bad)
rowBuilder.Append("BAD ");
if (row.Size is not null)
{
rowBuilder.Append($"CRC({row.CRC}) ");
rowBuilder.Append($"SHA1({row.SHA1}) ");
}
else
{
if (!string.IsNullOrEmpty(row.MD5))
rowBuilder.Append($"MD5({row.MD5}) ");
else
rowBuilder.Append($"SHA1({row.SHA1}) ");
}
if (row.Bad)
rowBuilder.Append("BAD_DUMP");
}
writer.WriteLine(rowBuilder.ToString().TrimEnd());
writer.Flush();
}
}
}
}

View File

@@ -0,0 +1,9 @@
using SabreTools.Data.Models.Listxml;
namespace SabreTools.Serialization.Writers
{
public class Listxml : XmlFile<Mame>
{
// All logic taken care of in the base class
}
}

View File

@@ -0,0 +1,56 @@
using System.IO;
using SabreTools.Data.Models.Logiqx;
namespace SabreTools.Serialization.Writers
{
public class Logiqx : XmlFile<Datafile>
{
#region Constants
/// <summary>
/// name field for DOCTYPE
/// </summary>
public const string DocTypeName = "datafile";
/// <summary>
/// pubid field for DOCTYPE
/// </summary>
public const string DocTypePubId = "-//Logiqx//DTD ROM Management Datafile//EN";
/// <summary>
/// sysid field for DOCTYPE
/// </summary>
public const string DocTypeSysId = "http://www.logiqx.com/Dats/datafile.dtd";
/// <summary>
/// subset field for DOCTYPE
/// </summary>
public const string? DocTypeSubset = null;
#endregion
#region IByteWriter
/// <inheritdoc cref="XmlFile.SerializeArray(T?, string?, string?, string?, string?)" />
public override byte[]? SerializeArray(Datafile? obj)
=> SerializeArray(obj, DocTypeName, DocTypePubId, DocTypeSysId, DocTypeSysId);
#endregion
#region IFileWriter
/// <inheritdoc cref="XmlFile.Serialize(T?, string?, string?, string?, string?, string?)" />
public override bool SerializeFile(Datafile? obj, string? path)
=> Serialize(obj, path, DocTypeName, DocTypePubId, DocTypeSysId, DocTypeSysId);
#endregion
#region IStreamWriter
/// <inheritdoc cref="XmlFile.Serialize(T?, string?, string?, string?, string?)" />
public override Stream? SerializeStream(Datafile? obj)
=> Serialize(obj, DocTypeName, DocTypePubId, DocTypeSysId, DocTypeSysId);
#endregion
}
}

View File

@@ -0,0 +1,7 @@
namespace SabreTools.Serialization.Writers
{
public class M1 : XmlFile<Data.Models.Listxml.M1>
{
// All logic taken care of in the base class
}
}

View File

@@ -0,0 +1,7 @@
namespace SabreTools.Serialization.Writers
{
public class Mess : XmlFile<Data.Models.Listxml.Mess>
{
// All logic taken care of in the base class
}
}

View File

@@ -0,0 +1,9 @@
using SabreTools.Data.Models.OfflineList;
namespace SabreTools.Serialization.Writers
{
public class OfflineList : XmlFile<Dat>
{
// All logic taken care of in the base class
}
}

View File

@@ -0,0 +1,50 @@
#if NET20 || NET35
using System;
using System.IO;
namespace SabreTools.Serialization.Writers
{
/// <summary>
/// Derived from the mscorlib code from .NET Framework 4.0
/// </summary>
internal static class OldDotNet
{
public static void CopyTo(this Stream source, Stream destination)
{
if (destination is null)
{
throw new ArgumentNullException("destination");
}
if (!source.CanRead && !source.CanWrite)
{
throw new ObjectDisposedException(null);
}
if (!destination.CanRead && !destination.CanWrite)
{
throw new ObjectDisposedException("destination");
}
if (!source.CanRead)
{
throw new NotSupportedException();
}
if (!destination.CanWrite)
{
throw new NotSupportedException();
}
byte[] array = new byte[81920];
int count;
while ((count = source.Read(array, 0, array.Length)) != 0)
{
destination.Write(array, 0, count);
}
}
}
}
#endif

View File

@@ -0,0 +1,56 @@
using System.IO;
using SabreTools.Data.Models.OpenMSX;
namespace SabreTools.Serialization.Writers
{
public class OpenMSX : XmlFile<SoftwareDb>
{
#region Constants
/// <summary>
/// name field for DOCTYPE
/// </summary>
public const string DocTypeName = "softwaredb";
/// <summary>
/// pubid field for DOCTYPE
/// </summary>
public const string? DocTypePubId = null;
/// <summary>
/// sysid field for DOCTYPE
/// </summary>
public const string DocTypeSysId = "softwaredb1.dtd";
/// <summary>
/// subset field for DOCTYPE
/// </summary>
public const string? DocTypeSubset = null;
#endregion
#region IByteWriter
/// <inheritdoc cref="XmlFile.SerializeArray(T?, string?, string?, string?, string?)" />
public override byte[]? SerializeArray(SoftwareDb? obj)
=> SerializeArray(obj, DocTypeName, DocTypePubId, DocTypeSysId, DocTypeSysId);
#endregion
#region IFileWriter
/// <inheritdoc cref="XmlFile.Serialize(T?, string?, string?, string?, string?, string?)" />
public override bool SerializeFile(SoftwareDb? obj, string? path)
=> Serialize(obj, path, DocTypeName, DocTypePubId, DocTypeSysId, DocTypeSysId);
#endregion
#region IStreamWriter
/// <inheritdoc cref="XmlFile.Serialize(T?, string?, string?, string?, string?)" />
public override Stream? SerializeStream(SoftwareDb? obj)
=> Serialize(obj, DocTypeName, DocTypePubId, DocTypeSysId, DocTypeSysId);
#endregion
}
}

View File

@@ -0,0 +1,12 @@
# SabreTools.Serialization.Writers
This library contains methods for writing out data from models that are defined in `SabreTools.Data.Models` into various output sources into.
Writer classes can inherit from one or more interfaces, as seen in the table below:
| Interface Name | Source Type | Destination Type |
| --- | --- | --- |
| `SabreTools.Serialization.Writers.IByteWriter<TModel>` | `TModel` | `byte[]?` |
| `SabreTools.Serialization.Writers.IFileWriter<TModel>` | `TModel` | `string?` path |
| `SabreTools.Serialization.Writers.IStreamWriter<TModel>` | `TModel` | `Stream?` |
| `SabreTools.Serialization.Writers.IStringWriter<TModel>` | `TModel` | `string?` representation |

View File

@@ -0,0 +1,165 @@
using System.IO;
using System.Text;
using SabreTools.Data.Models.RomCenter;
using SabreTools.IO.Extensions;
using SabreTools.IO.Writers;
namespace SabreTools.Serialization.Writers
{
public class RomCenter : BaseBinaryWriter<MetadataFile>
{
/// <inheritdoc/>
public override Stream? SerializeStream(MetadataFile? obj)
{
// If the metadata file is null
if (obj is 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(obj.Credits, writer);
// Write out the dat section
WriteDat(obj.Dat, writer);
// Write out the emulator section
WriteEmulator(obj.Emulator, writer);
// Write out the games
WriteGames(obj.Games, writer);
// Return the stream
stream.SeekIfPossible(0, SeekOrigin.Begin);
return stream;
}
/// <summary>
/// Write credits information to the current writer
/// </summary>
/// <param name="credits">Credits object representing the credits information</param>
/// <param name="writer">IniWriter representing the output</param>
private static void WriteCredits(Credits? credits, IniWriter writer)
{
// If the credits information is missing, we can't do anything
if (credits is null)
return;
writer.WriteSection("CREDITS");
if (credits.Author is not null)
writer.WriteKeyValuePair("Author", credits.Author);
if (credits.Version is not null)
writer.WriteKeyValuePair("Version", credits.Version);
if (credits.Email is not null)
writer.WriteKeyValuePair("Email", credits.Email);
if (credits.Homepage is not null)
writer.WriteKeyValuePair("Homepage", credits.Homepage);
if (credits.Url is not null)
writer.WriteKeyValuePair("Url", credits.Url);
if (credits.Date is not null)
writer.WriteKeyValuePair("Date", credits.Date);
if (credits.Comment is not null)
writer.WriteKeyValuePair("Comment", credits.Comment);
writer.WriteLine();
writer.Flush();
}
/// <summary>
/// Write dat information to the current writer
/// </summary>
/// <param name="dat">Dat object representing the dat information</param>
/// <param name="writer">IniWriter representing the output</param>
private static void WriteDat(Dat? dat, IniWriter writer)
{
// If the dat information is missing, we can't do anything
if (dat is null)
return;
writer.WriteSection("DAT");
if (dat.Version is not null)
writer.WriteKeyValuePair("Version", dat.Version);
if (dat.Plugin is not null)
writer.WriteKeyValuePair("Plugin", dat.Plugin);
if (dat.Split is not null)
writer.WriteKeyValuePair("Split", dat.Split);
if (dat.Merge is not null)
writer.WriteKeyValuePair("Merge", dat.Merge);
writer.WriteLine();
writer.Flush();
}
/// <summary>
/// Write emulator information to the current writer
/// </summary>
/// <param name="emulator">Emulator object representing the emulator information</param>
/// <param name="writer">IniWriter representing the output</param>
private static void WriteEmulator(Emulator? emulator, IniWriter writer)
{
// If the emulator information is missing, we can't do anything
if (emulator is null)
return;
writer.WriteSection("EMULATOR");
if (emulator.RefName is not null)
writer.WriteKeyValuePair("refname", emulator.RefName);
if (emulator.Version is not null)
writer.WriteKeyValuePair("version", emulator.Version);
writer.WriteLine();
writer.Flush();
}
/// <summary>
/// Write games information to the current writer
/// </summary>
/// <param name="games">Games object representing the games information</param>
/// <param name="writer">IniWriter representing the output</param>
private static void WriteGames(Games? games, IniWriter writer)
{
// If the games information is missing, we can't do anything
if (games?.Rom is null || games.Rom.Length == 0)
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();
}
}
}

View File

@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<IncludeSymbols>true</IncludeSymbols>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>2.3.0</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Description>Convert from models to external sources</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2026</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/SabreTools/SabreTools.Serialization</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>serialization writer conversion models</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="SabreTools.Hashing" Version="[1.6.1]" />
<PackageReference Include="SabreTools.IO" Version="[1.9.1]" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SabreTools.Data.Models\SabreTools.Data.Models.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,211 @@
using System.IO;
using System.Text;
using SabreTools.Data.Models.SeparatedValue;
using SabreTools.IO.Extensions;
using SabreTools.IO.Writers;
#pragma warning disable CA1822 // Mark members as static
namespace SabreTools.Serialization.Writers
{
public class SeparatedValue : BaseBinaryWriter<MetadataFile>
{
#region Constants
public static readonly string[] HeaderArrayStandard =
[
"File Name",
"Internal Name",
"Description",
"Game Name",
"Game Description",
"Type",
"Rom Name",
"Disk Name",
"Size",
"CRC",
"MD5",
"SHA1",
"SHA256",
"Status",
];
public static readonly string[] HeaderArrayExtended =
[
"File Name",
"Internal Name",
"Description",
"Game Name",
"Game Description",
"Type",
"Rom Name",
"Disk Name",
"Size",
"CRC",
"MD5",
"SHA1",
"SHA256",
"SHA384",
"SHA512",
"SpamSum",
"Status",
];
#endregion
#region IByteWriter
/// <inheritdoc/>
public override byte[]? SerializeArray(MetadataFile? obj)
=> SerializeArray(obj, ',', false);
/// <inheritdoc/>
public byte[]? SerializeArray(MetadataFile? obj, char delim, bool longHeader)
{
using var stream = SerializeStream(obj, delim, longHeader);
if (stream is null)
return null;
byte[] bytes = new byte[stream.Length];
int read = stream.Read(bytes, 0, bytes.Length);
return bytes;
}
#endregion
#region IFileWriter
/// <inheritdoc/>
public override bool SerializeFile(MetadataFile? obj, string? path)
=> SerializeFile(obj, path, ',', false);
/// <inheritdoc/>
public bool SerializeFile(MetadataFile? obj, string? path, char delim, bool longHeader)
{
if (string.IsNullOrEmpty(path))
return false;
using var stream = SerializeStream(obj, delim, longHeader);
if (stream is null)
return false;
using var fs = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None);
stream.CopyTo(fs);
fs.Flush();
return true;
}
#endregion
#region IStreamWriter
/// <inheritdoc/>
public override Stream? SerializeStream(MetadataFile? obj)
=> SerializeStream(obj, ',', false);
/// <inheritdoc cref="SerializeStream(MetadataFile)"/>
public Stream? SerializeStream(MetadataFile? obj, char delim, bool longHeader)
{
// If the metadata file is null
if (obj is null)
return null;
// Setup the writer and output
var stream = new MemoryStream();
var writer = new SeparatedValueWriter(stream, Encoding.UTF8)
{
Separator = delim,
Quotes = true
};
// Write the header
WriteHeader(writer, longHeader);
// Write out the rows, if they exist
WriteRows(obj.Row, writer, longHeader);
// Return the stream
stream.SeekIfPossible(0, SeekOrigin.Begin);
return stream;
}
/// <summary>
/// Write header information to the current writer
/// </summary>
/// <param name="writer">SeparatedValueWriter representing the output</param>
/// <param name="longHeader">True if the long variant of the row should be written, false otherwise</param>
private static void WriteHeader(SeparatedValueWriter writer, bool longHeader)
{
string[] headerArray = longHeader ? HeaderArrayExtended : HeaderArrayStandard;
writer.WriteHeader(headerArray);
writer.Flush();
}
/// <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>
/// <param name="longHeader">True if the long variant of the row should be written, false otherwise</param>
private static void WriteRows(Row[]? rows, SeparatedValueWriter writer, bool longHeader)
{
// If the games information is missing, we can't do anything
if (rows is null || rows.Length == 0)
return;
// Loop through and write out the rows
foreach (var row in rows)
{
string?[] rowArray;
if (longHeader)
{
rowArray =
[
row.FileName,
row.InternalName,
row.Description,
row.GameName,
row.GameDescription,
row.Type,
row.RomName,
row.DiskName,
row.Size,
row.CRC,
row.MD5,
row.SHA1,
row.SHA256,
row.SHA384,
row.SHA512,
row.SpamSum,
row.Status,
];
}
else
{
rowArray =
[
row.FileName,
row.InternalName,
row.Description,
row.GameName,
row.GameDescription,
row.Type,
row.RomName,
row.DiskName,
row.Size,
row.CRC,
row.MD5,
row.SHA1,
row.SHA256,
row.Status,
];
}
writer.WriteValues(rowArray);
writer.Flush();
}
}
#endregion
}
}

View File

@@ -0,0 +1,47 @@
using System.IO;
namespace SabreTools.Serialization.Writers
{
public class SoftwareList : XmlFile<Data.Models.SoftwareList.SoftwareList>
{
#region Constants
/// <summary>
/// name field for DOCTYPE
/// </summary>
public const string DocTypeName = "softwarelist";
/// <summary>
/// pubid field for DOCTYPE
/// </summary>
public const string? DocTypePubId = null;
/// <summary>
/// sysid field for DOCTYPE
/// </summary>
public const string DocTypeSysId = "softwarelist.dtd";
/// <summary>
/// subset field for DOCTYPE
/// </summary>
public const string? DocTypeSubset = null;
#endregion
#region IFileWriter
/// <inheritdoc cref="XmlFile.Serialize(T?, string?, string?, string?, string?, string?)" />
public override bool SerializeFile(Data.Models.SoftwareList.SoftwareList? obj, string? path)
=> Serialize(obj, path, DocTypeName, DocTypePubId, DocTypeSysId, DocTypeSysId);
#endregion
#region IStreamWriter
/// <inheritdoc cref="XmlFile.Serialize(T?, string?, string?, string?, string?)" />
public override Stream? SerializeStream(Data.Models.SoftwareList.SoftwareList? obj)
=> Serialize(obj, DocTypeName, DocTypePubId, DocTypeSysId, DocTypeSysId);
#endregion
}
}

View File

@@ -0,0 +1,33 @@
using System.Text;
namespace SabreTools.Serialization.Writers
{
public partial class XMID : IStringWriter<Data.Models.Xbox.XMID>
{
/// <inheritdoc/>
public bool Debug { get; set; } = false;
/// <inheritdoc cref="IStringWriter.Serialize(T?)"/>
public static string? SerializeString(Data.Models.Xbox.XMID? obj)
{
var deserializer = new XMID();
return deserializer.Serialize(obj);
}
/// <inheritdoc/>
public string? Serialize(Data.Models.Xbox.XMID? obj)
{
if (obj is null)
return null;
var sb = new StringBuilder();
sb.Append(obj.PublisherIdentifier);
sb.Append(obj.GameID);
sb.Append(obj.VersionNumber);
sb.Append(obj.RegionIdentifier);
return sb.ToString();
}
}
}

View File

@@ -0,0 +1,39 @@
using System.Text;
namespace SabreTools.Serialization.Writers
{
public partial class XeMID : IStringWriter<Data.Models.Xbox.XeMID>
{
/// <inheritdoc/>
public bool Debug { get; set; } = false;
/// <inheritdoc cref="IStringWriter.Serialize(T?)"/>
public static string? SerializeString(Data.Models.Xbox.XeMID? obj)
{
var deserializer = new XeMID();
return deserializer.Serialize(obj);
}
/// <inheritdoc/>
public string? Serialize(Data.Models.Xbox.XeMID? obj)
{
if (obj is null)
return null;
var sb = new StringBuilder();
sb.Append(obj.PublisherIdentifier);
sb.Append(obj.PlatformIdentifier);
sb.Append(obj.GameID);
sb.Append(obj.SKU);
sb.Append(obj.RegionIdentifier);
sb.Append(obj.BaseVersion);
sb.Append(obj.MediaSubtypeIdentifier);
sb.Append(obj.DiscNumberIdentifier);
if (!string.IsNullOrEmpty(obj.CertificationSubmissionIdentifier))
sb.Append(obj.CertificationSubmissionIdentifier);
return sb.ToString();
}
}
}

View File

@@ -0,0 +1,130 @@
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using SabreTools.IO.Extensions;
namespace SabreTools.Serialization.Writers
{
/// <summary>
/// Base class for other XML serializers
/// </summary>
/// <typeparam name="T"></typeparam>
public class XmlFile<T> : BaseBinaryWriter<T>
{
#region IByteWriter
/// <inheritdoc/>
public override byte[]? SerializeArray(T? obj)
=> SerializeArray(obj, null, null, null, null);
/// <summary>
/// Serializes the defined type to a byte array
/// </summary>
/// <param name="obj">Data to serialize</param>
/// <param name="name">Optional DOCTYPE name</param>
/// <param name="pubid">Optional DOCTYPE pubid</param>
/// <param name="sysid">Optional DOCTYPE sysid</param>
/// <param name="subset">Optional DOCTYPE name</param>
/// <returns>Byte array containing serialized data on success, null otherwise</returns>
public byte[]? SerializeArray(T? obj, string? name = null, string? pubid = null, string? sysid = null, string? subset = null)
{
using var stream = Serialize(obj, name, pubid, sysid, subset);
if (stream is null)
return null;
byte[] bytes = new byte[stream.Length];
int read = stream.Read(bytes, 0, bytes.Length);
return bytes;
}
#endregion
#region IFileWriter
/// <inheritdoc/>
public override bool SerializeFile(T? obj, string? path)
=> Serialize(obj, path, null, null, null, null);
/// <summary>
/// Serializes the defined type to an XML file
/// </summary>
/// <param name="obj">Data to serialize</param>
/// <param name="path">Path to the file to serialize to</param>
/// <param name="name">Optional DOCTYPE name</param>
/// <param name="pubid">Optional DOCTYPE pubid</param>
/// <param name="sysid">Optional DOCTYPE sysid</param>
/// <param name="subset">Optional DOCTYPE name</param>
/// <returns>True on successful serialization, false otherwise</returns>
public bool Serialize(T? obj, string? path, string? name = null, string? pubid = null, string? sysid = null, string? subset = null)
{
if (string.IsNullOrEmpty(path))
return false;
using var stream = Serialize(obj, name, pubid, sysid, subset);
if (stream is null)
return false;
using var fs = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None);
stream.CopyTo(fs);
fs.Flush();
return true;
}
#endregion
#region IStreamWriter
/// <inheritdoc/>
public override Stream? SerializeStream(T? obj)
=> Serialize(obj, null, null, null, null);
/// <summary>
/// Serializes the defined type to a stream
/// </summary>
/// <param name="obj">Data to serialize</param>
/// <param name="name">Optional DOCTYPE name</param>
/// <param name="pubid">Optional DOCTYPE pubid</param>
/// <param name="sysid">Optional DOCTYPE sysid</param>
/// <param name="subset">Optional DOCTYPE name</param>
/// <returns>Stream containing serialized data on success, null otherwise</returns>
public Stream? Serialize(T? obj, string? name = null, string? pubid = null, string? sysid = null, string? subset = null)
{
// If the object is null
if (obj is null)
return null;
// Setup the serializer and the writer
var serializer = new XmlSerializer(typeof(T));
var namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "");
var settings = new XmlWriterSettings
{
CheckCharacters = false,
Encoding = Encoding.UTF8,
Indent = true,
IndentChars = "\t",
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
NamespaceHandling = NamespaceHandling.OmitDuplicates,
#endif
NewLineChars = "\n",
};
var stream = new MemoryStream();
var streamWriter = new StreamWriter(stream);
var xmlWriter = XmlWriter.Create(streamWriter, settings);
// Write the doctype if provided
if (!string.IsNullOrEmpty(name))
xmlWriter.WriteDocType(name, pubid, sysid, subset);
// Perform the deserialization and return
serializer.Serialize(xmlWriter, obj, namespaces);
stream.SeekIfPossible(0, SeekOrigin.Begin);
return stream;
}
#endregion
}
}