Add No-Intro database export reader and writer

This commit is contained in:
Matt Nadareski
2026-03-31 22:09:32 -04:00
parent b82d464b12
commit 1cec6fa918
4 changed files with 1345 additions and 0 deletions

View File

@@ -0,0 +1,480 @@
using System.IO;
using System.Linq;
using Xunit;
namespace SabreTools.Serialization.Readers.Test
{
public class NoIntroDatabaseTests
{
[Fact]
public void NullArray_Null()
{
byte[]? data = null;
int offset = 0;
var deserializer = new NoIntroDatabase();
var actual = deserializer.Deserialize(data, offset);
Assert.Null(actual);
}
[Fact]
public void EmptyArray_Null()
{
byte[]? data = [];
int offset = 0;
var deserializer = new NoIntroDatabase();
var actual = deserializer.Deserialize(data, offset);
Assert.Null(actual);
}
[Fact]
public void InvalidArray_Null()
{
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
int offset = 0;
var deserializer = new NoIntroDatabase();
var actual = deserializer.Deserialize(data, offset);
Assert.Null(actual);
}
[Fact]
public void NullStream_Null()
{
Stream? data = null;
var deserializer = new NoIntroDatabase();
var actual = deserializer.Deserialize(data);
Assert.Null(actual);
}
[Fact]
public void EmptyStream_Null()
{
Stream? data = new MemoryStream([]);
var deserializer = new NoIntroDatabase();
var actual = deserializer.Deserialize(data);
Assert.Null(actual);
}
[Fact]
public void InvalidStream_Null()
{
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
var deserializer = new NoIntroDatabase();
var actual = deserializer.Deserialize(data);
Assert.Null(actual);
}
[Fact]
public void RoundTripTest()
{
// Get the serializer and deserializer
var deserializer = new NoIntroDatabase();
var serializer = new Writers.NoIntroDatabase();
// Build the data
Data.Models.NoIntroDatabase.Datafile df = Build();
// Serialize to stream
Stream? metadata = serializer.SerializeStream(df);
Assert.NotNull(metadata);
// Serialize back to original model
Data.Models.NoIntroDatabase.Datafile? newDf = deserializer.Deserialize(metadata);
// Validate the data
Assert.NotNull(newDf);
Assert.NotNull(newDf.Game);
var newGame = Assert.Single(newDf.Game);
Validate(newGame);
}
/// <summary>
/// Build model for serialization and deserialization
/// </summary>
private static Data.Models.NoIntroDatabase.Datafile Build()
{
var archive = new Data.Models.NoIntroDatabase.Archive
{
Number = "XXXXXX",
Clone = "XXXXXX",
RegParent = "XXXXXX",
MergeOf = "XXXXXX",
MergeName = "XXXXXX",
Name = "XXXXXX",
NameAlt = "XXXXXX",
Region = "XXXXXX",
Languages = "XXXXXX",
ShowLang = "XXXXXX",
LangChecked = "XXXXXX",
Version1 = "XXXXXX",
Version2 = "XXXXXX",
DevStatus = "XXXXXX",
Additional = "XXXXXX",
Special1 = "XXXXXX",
Special2 = "XXXXXX",
Alt = "XXXXXX",
GameId1 = "XXXXXX",
GameId2 = "XXXXXX",
Description = "XXXXXX",
Bios = "XXXXXX",
Licensed = "XXXXXX",
Pirate = "XXXXXX",
Physical = "XXXXXX",
Complete = "XXXXXX",
Adult = "XXXXXX",
Dat = "XXXXXX",
Listed = "XXXXXX",
Private = "XXXXXX",
StickyNote = "XXXXXX",
DatterNote = "XXXXXX",
Categories = "XXXXXX",
};
var media = new Data.Models.NoIntroDatabase.Media();
var sourceDetails = new Data.Models.NoIntroDatabase.SourceDetails
{
Id = "XXXXXX",
AppendToNumber = "XXXXXX",
Section = "XXXXXX",
RomInfo = "XXXXXX",
DumpDate = "XXXXXX",
DumpDateInfo = "XXXXXX",
ReleaseDate = "XXXXXX",
ReleaseDateInfo = "XXXXXX",
Dumper = "XXXXXX",
Project = "XXXXXX",
OriginalFormat = "XXXXXX",
Nodump = "XXXXXX",
Tool = "XXXXXX",
Origin = "XXXXXX",
Comment1 = "XXXXXX",
Comment2 = "XXXXXX",
Link1 = "XXXXXX",
Link1Public = "XXXXXX",
Link2 = "XXXXXX",
Link2Public = "XXXXXX",
Link3 = "XXXXXX",
Link3Public = "XXXXXX",
Region = "XXXXXX",
MediaTitle = "XXXXXX",
};
var serials = new Data.Models.NoIntroDatabase.Serials
{
MediaSerial1 = "XXXXXX",
MediaSerial2 = "XXXXXX",
MediaSerial3 = "XXXXXX",
PCBSerial = "XXXXXX",
RomChipSerial1 = "XXXXXX",
RomChipSerial2 = "XXXXXX",
LockoutSerial = "XXXXXX",
SaveChipSerial = "XXXXXX",
ChipSerial = "XXXXXX",
BoxSerial = "XXXXXX",
MediaStamp = "XXXXXX",
BoxBarcode = "XXXXXX",
DigitalSerial1 = "XXXXXX",
DigitalSerial2 = "XXXXXX",
};
var file = new Data.Models.NoIntroDatabase.File
{
Id = "XXXXXX",
AppendToSourceId = "XXXXXX",
ForceName = "XXXXXX",
ForceSceneName = "XXXXXX",
EmptyDir = "XXXXXX",
Extension = "XXXXXX",
Item = "XXXXXX",
Date = "XXXXXX",
Format = "XXXXXX",
Note = "XXXXXX",
Filter = "XXXXXX",
Version = "XXXXXX",
UpdateType = "XXXXXX",
Size = "XXXXXX",
CRC32 = "XXXXXX",
MD5 = "XXXXXX",
SHA1 = "XXXXXX",
SHA256 = "XXXXXX",
Serial = "XXXXXX",
Header = "XXXXXX",
Bad = "XXXXXX",
MIA = "XXXXXX",
Unique = "XXXXXX",
MergeName = "XXXXXX",
UniqueAttachment = "XXXXXX",
};
var source = new Data.Models.NoIntroDatabase.Source
{
Details = sourceDetails,
Serials = serials,
File = [file],
};
var releaseDetails = new Data.Models.NoIntroDatabase.ReleaseDetails
{
Id = "XXXXXX",
AppendToNumber = "XXXXXX",
Date = "XXXXXX",
OriginalFormat = "XXXXXX",
Group = "XXXXXX",
DirName = "XXXXXX",
NfoName = "XXXXXX",
NfoSize = "XXXXXX",
NfoCRC = "XXXXXX",
ArchiveName = "XXXXXX",
RomInfo = "XXXXXX",
Category = "XXXXXX",
Comment = "XXXXXX",
Tool = "XXXXXX",
Region = "XXXXXX",
Origin = "XXXXXX",
};
var release = new Data.Models.NoIntroDatabase.Release
{
Details = releaseDetails,
Serials = serials,
File = [file],
};
var game = new Data.Models.NoIntroDatabase.Game
{
Name = "XXXXXX",
Archive = archive,
Media = [media],
Source = [source],
Release = [release],
};
return new Data.Models.NoIntroDatabase.Datafile
{
Game = [game],
};
}
/// <summary>
/// Validate a Game
/// </summary>
private static void Validate(Data.Models.NoIntroDatabase.Game? game)
{
Assert.NotNull(game);
Assert.Equal("XXXXXX", game.Name);
Validate(game.Archive);
Assert.NotNull(game.Media);
Data.Models.NoIntroDatabase.Media media = Assert.Single(game.Media);
Validate(media);
Assert.NotNull(game.Source);
Data.Models.NoIntroDatabase.Source source = Assert.Single(game.Source);
Validate(source);
Assert.NotNull(game.Release);
Data.Models.NoIntroDatabase.Release release = Assert.Single(game.Release);
Validate(release);
}
/// <summary>
/// Validate a Archive
/// </summary>
private static void Validate(Data.Models.NoIntroDatabase.Archive? archive)
{
Assert.NotNull(archive);
Assert.Equal("XXXXXX", archive.Number);
Assert.Equal("XXXXXX", archive.Clone);
Assert.Equal("XXXXXX", archive.RegParent);
Assert.Equal("XXXXXX", archive.MergeOf);
Assert.Equal("XXXXXX", archive.MergeName);
Assert.Equal("XXXXXX", archive.Name);
Assert.Equal("XXXXXX", archive.NameAlt);
Assert.Equal("XXXXXX", archive.Region);
Assert.Equal("XXXXXX", archive.Languages);
Assert.Equal("XXXXXX", archive.ShowLang);
Assert.Equal("XXXXXX", archive.LangChecked);
Assert.Equal("XXXXXX", archive.Version1);
Assert.Equal("XXXXXX", archive.Version2);
Assert.Equal("XXXXXX", archive.DevStatus);
Assert.Equal("XXXXXX", archive.Additional);
Assert.Equal("XXXXXX", archive.Special1);
Assert.Equal("XXXXXX", archive.Special2);
Assert.Equal("XXXXXX", archive.Alt);
Assert.Equal("XXXXXX", archive.GameId1);
Assert.Equal("XXXXXX", archive.GameId2);
Assert.Equal("XXXXXX", archive.Description);
Assert.Equal("XXXXXX", archive.Bios);
Assert.Equal("XXXXXX", archive.Licensed);
Assert.Equal("XXXXXX", archive.Pirate);
Assert.Equal("XXXXXX", archive.Physical);
Assert.Equal("XXXXXX", archive.Complete);
Assert.Equal("XXXXXX", archive.Adult);
Assert.Equal("XXXXXX", archive.Dat);
Assert.Equal("XXXXXX", archive.Listed);
Assert.Equal("XXXXXX", archive.Private);
Assert.Equal("XXXXXX", archive.StickyNote);
Assert.Equal("XXXXXX", archive.DatterNote);
Assert.Equal("XXXXXX", archive.Categories);
}
/// <summary>
/// Validate a Media
/// </summary>
private static void Validate(Data.Models.NoIntroDatabase.Media? media)
{
Assert.NotNull(media);
}
/// <summary>
/// Validate a Source
/// </summary>
private static void Validate(Data.Models.NoIntroDatabase.Source? source)
{
Assert.NotNull(source);
Validate(source.Details);
Validate(source.Serials);
Assert.NotNull(source.File);
Data.Models.NoIntroDatabase.File file = Assert.Single(source.File);
Validate(file);
}
/// <summary>
/// Validate a Rom
/// </summary>
private static void Validate(Data.Models.NoIntroDatabase.SourceDetails? sourceDetails)
{
Assert.NotNull(sourceDetails);
Assert.Equal("XXXXXX", sourceDetails.Id);
Assert.Equal("XXXXXX", sourceDetails.AppendToNumber);
Assert.Equal("XXXXXX", sourceDetails.Section);
Assert.Equal("XXXXXX", sourceDetails.RomInfo);
Assert.Equal("XXXXXX", sourceDetails.DumpDate);
Assert.Equal("XXXXXX", sourceDetails.DumpDateInfo);
Assert.Equal("XXXXXX", sourceDetails.ReleaseDate);
Assert.Equal("XXXXXX", sourceDetails.ReleaseDateInfo);
Assert.Equal("XXXXXX", sourceDetails.Dumper);
Assert.Equal("XXXXXX", sourceDetails.Project);
Assert.Equal("XXXXXX", sourceDetails.OriginalFormat);
Assert.Equal("XXXXXX", sourceDetails.Nodump);
Assert.Equal("XXXXXX", sourceDetails.Tool);
Assert.Equal("XXXXXX", sourceDetails.Origin);
Assert.Equal("XXXXXX", sourceDetails.Comment1);
Assert.Equal("XXXXXX", sourceDetails.Comment2);
Assert.Equal("XXXXXX", sourceDetails.Link1);
Assert.Equal("XXXXXX", sourceDetails.Link1Public);
Assert.Equal("XXXXXX", sourceDetails.Link2);
Assert.Equal("XXXXXX", sourceDetails.Link2Public);
Assert.Equal("XXXXXX", sourceDetails.Link3);
Assert.Equal("XXXXXX", sourceDetails.Link3Public);
Assert.Equal("XXXXXX", sourceDetails.Region);
Assert.Equal("XXXXXX", sourceDetails.MediaTitle);
}
/// <summary>
/// Validate a Serials
/// </summary>
private static void Validate(Data.Models.NoIntroDatabase.Serials? serials)
{
Assert.NotNull(serials);
Assert.Equal("XXXXXX", serials.MediaSerial1);
Assert.Equal("XXXXXX", serials.MediaSerial2);
Assert.Equal("XXXXXX", serials.MediaSerial3);
Assert.Equal("XXXXXX", serials.PCBSerial);
Assert.Equal("XXXXXX", serials.RomChipSerial1);
Assert.Equal("XXXXXX", serials.RomChipSerial2);
Assert.Equal("XXXXXX", serials.LockoutSerial);
Assert.Equal("XXXXXX", serials.SaveChipSerial);
Assert.Equal("XXXXXX", serials.ChipSerial);
Assert.Equal("XXXXXX", serials.BoxSerial);
Assert.Equal("XXXXXX", serials.MediaStamp);
Assert.Equal("XXXXXX", serials.BoxBarcode);
Assert.Equal("XXXXXX", serials.DigitalSerial1);
Assert.Equal("XXXXXX", serials.DigitalSerial2);
}
/// <summary>
/// Validate a File
/// </summary>
private static void Validate(Data.Models.NoIntroDatabase.File? file)
{
Assert.NotNull(file);
Assert.Equal("XXXXXX", file.Id);
Assert.Equal("XXXXXX", file.AppendToSourceId);
Assert.Equal("XXXXXX", file.ForceName);
Assert.Equal("XXXXXX", file.ForceSceneName);
Assert.Equal("XXXXXX", file.EmptyDir);
Assert.Equal("XXXXXX", file.Extension);
Assert.Equal("XXXXXX", file.Item);
Assert.Equal("XXXXXX", file.Date);
Assert.Equal("XXXXXX", file.Format);
Assert.Equal("XXXXXX", file.Note);
Assert.Equal("XXXXXX", file.Filter);
Assert.Equal("XXXXXX", file.Version);
Assert.Equal("XXXXXX", file.UpdateType);
Assert.Equal("XXXXXX", file.Size);
Assert.Equal("XXXXXX", file.CRC32);
Assert.Equal("XXXXXX", file.MD5);
Assert.Equal("XXXXXX", file.SHA1);
Assert.Equal("XXXXXX", file.SHA256);
Assert.Equal("XXXXXX", file.Serial);
Assert.Equal("XXXXXX", file.Header);
Assert.Equal("XXXXXX", file.Bad);
Assert.Equal("XXXXXX", file.MIA);
Assert.Equal("XXXXXX", file.Unique);
Assert.Equal("XXXXXX", file.MergeName);
Assert.Equal("XXXXXX", file.UniqueAttachment);
}
/// <summary>
/// Validate a Release
/// </summary>
private static void Validate(Data.Models.NoIntroDatabase.Release? release)
{
Assert.NotNull(release);
Validate(release.Details);
Validate(release.Serials);
Assert.NotNull(release.File);
Data.Models.NoIntroDatabase.File file = Assert.Single(release.File);
Validate(file);
}
/// <summary>
/// Validate a ReleaseDetails
/// </summary>
private static void Validate(Data.Models.NoIntroDatabase.ReleaseDetails? releaseDetails)
{
Assert.NotNull(releaseDetails);
Assert.Equal("XXXXXX", releaseDetails.Id);
Assert.Equal("XXXXXX", releaseDetails.AppendToNumber);
Assert.Equal("XXXXXX", releaseDetails.Date);
Assert.Equal("XXXXXX", releaseDetails.OriginalFormat);
Assert.Equal("XXXXXX", releaseDetails.Group);
Assert.Equal("XXXXXX", releaseDetails.DirName);
Assert.Equal("XXXXXX", releaseDetails.NfoName);
Assert.Equal("XXXXXX", releaseDetails.NfoSize);
Assert.Equal("XXXXXX", releaseDetails.NfoCRC);
Assert.Equal("XXXXXX", releaseDetails.ArchiveName);
Assert.Equal("XXXXXX", releaseDetails.RomInfo);
Assert.Equal("XXXXXX", releaseDetails.Category);
Assert.Equal("XXXXXX", releaseDetails.Comment);
Assert.Equal("XXXXXX", releaseDetails.Tool);
Assert.Equal("XXXXXX", releaseDetails.Region);
Assert.Equal("XXXXXX", releaseDetails.Origin);
}
}
}

View File

@@ -0,0 +1,491 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using SabreTools.Data.Models.NoIntroDatabase;
namespace SabreTools.Serialization.Readers
{
public class NoIntroDatabase : BaseBinaryReader<Datafile>
{
/// <inheritdoc/>
public override Datafile? Deserialize(Stream? data)
{
// If the data is invalid
if (data is null || !data.CanRead)
return null;
try
{
// Cache the current offset
long initialOffset = data.Position;
// Create the XmlTextReader
var reader = new XmlTextReader(data);
reader.WhitespaceHandling = WhitespaceHandling.None;
// Parse the XML, if possible
Datafile? datafile = null;
while (reader.Read())
{
// An ending element means exit
if (reader.NodeType == XmlNodeType.EndElement)
break;
// Only process starting elements
if (!reader.IsStartElement())
continue;
switch (reader.Name)
{
case "datafile":
if (datafile is not null && Debug)
Console.WriteLine($"'{reader.Name}' element already found, overwriting");
datafile = ParseDatafile(reader);
break;
default:
if (Debug) Console.Error.WriteLine($"Element '{reader.Name}' is not recognized");
break;
}
}
return datafile;
}
catch
{
// Ignore the actual error
return null;
}
}
/// <summary>
/// Parse from an XmlTextReader into a Datafile
/// </summary>
/// <param name="reader">XmlTextReader to read from</param>
/// <returns>Filled Datafile on success, null on error</returns>
public Datafile ParseDatafile(XmlTextReader reader)
{
var obj = new Datafile();
List<Game> games = [];
while (reader.Read())
{
// An ending element means exit
if (reader.NodeType == XmlNodeType.EndElement)
break;
// Only process starting elements
if (!reader.IsStartElement())
continue;
switch (reader.Name)
{
case "game":
var game = ParseGame(reader);
if (game is not null)
games.Add(game);
break;
default:
if (Debug) Console.Error.WriteLine($"Element '{reader.Name}' is not recognized");
break;
}
}
if (games.Count > 0)
obj.Game = [.. games];
return obj;
}
#region Items
/// <summary>
/// Parse from an XmlTextReader into a Archive
/// </summary>
/// <param name="reader">XmlTextReader to read from</param>
/// <returns>Filled Archive on success, null on error</returns>
public Archive ParseArchive(XmlTextReader reader)
{
var obj = new Archive();
obj.Number = reader.GetAttribute("number");
obj.Clone = reader.GetAttribute("clone");
obj.RegParent = reader.GetAttribute("regparent");
obj.MergeOf = reader.GetAttribute("mergeof");
obj.MergeName = reader.GetAttribute("mergename");
obj.Name = reader.GetAttribute("name");
obj.NameAlt = reader.GetAttribute("name_alt");
obj.Region = reader.GetAttribute("region");
obj.Languages = reader.GetAttribute("languages");
obj.ShowLang = reader.GetAttribute("showlang");
obj.LangChecked = reader.GetAttribute("langchecked");
obj.Version1 = reader.GetAttribute("version1");
obj.Version2 = reader.GetAttribute("version2");
obj.DevStatus = reader.GetAttribute("devstatus");
obj.Additional = reader.GetAttribute("additional");
obj.Special1 = reader.GetAttribute("special1");
obj.Special2 = reader.GetAttribute("special2");
obj.Alt = reader.GetAttribute("alt");
obj.GameId1 = reader.GetAttribute("gameid1");
obj.GameId2 = reader.GetAttribute("gameid2");
obj.Description = reader.GetAttribute("description");
obj.Bios = reader.GetAttribute("bios");
obj.Licensed = reader.GetAttribute("licensed");
obj.Pirate = reader.GetAttribute("pirate");
obj.Physical = reader.GetAttribute("physical");
obj.Complete = reader.GetAttribute("complete");
obj.Adult = reader.GetAttribute("adult");
obj.Dat = reader.GetAttribute("dat");
obj.Listed = reader.GetAttribute("listed");
obj.Private = reader.GetAttribute("private");
obj.StickyNote = reader.GetAttribute("sticky_note");
obj.DatterNote = reader.GetAttribute("datter_note");
obj.Categories = reader.GetAttribute("categories");
return obj;
}
/// <summary>
/// Parse from an XmlTextReader into a File
/// </summary>
/// <param name="reader">XmlTextReader to read from</param>
/// <returns>Filled File on success, null on error</returns>
public Data.Models.NoIntroDatabase.File ParseFile(XmlTextReader reader)
{
var obj = new Data.Models.NoIntroDatabase.File();
obj.Id = reader.GetAttribute("id");
obj.AppendToSourceId = reader.GetAttribute("append_to_source_id");
obj.ForceName = reader.GetAttribute("forcename");
obj.ForceSceneName = reader.GetAttribute("forcescenename");
obj.EmptyDir = reader.GetAttribute("emptydir");
obj.Extension = reader.GetAttribute("extension");
obj.Item = reader.GetAttribute("item");
obj.Date = reader.GetAttribute("date");
obj.Format = reader.GetAttribute("format");
obj.Note = reader.GetAttribute("note");
obj.Filter = reader.GetAttribute("filter");
obj.Version = reader.GetAttribute("version");
obj.UpdateType = reader.GetAttribute("update_type");
obj.Size = reader.GetAttribute("size");
obj.CRC32 = reader.GetAttribute("crc32");
obj.MD5 = reader.GetAttribute("md5");
obj.SHA1 = reader.GetAttribute("sha1");
obj.SHA256 = reader.GetAttribute("sha256");
obj.Serial = reader.GetAttribute("serial");
obj.Header = reader.GetAttribute("header");
obj.Bad = reader.GetAttribute("bad");
obj.MIA = reader.GetAttribute("mia");
obj.Unique = reader.GetAttribute("unique");
obj.MergeName = reader.GetAttribute("mergename");
obj.UniqueAttachment = reader.GetAttribute("unique_attachment");
return obj;
}
/// <summary>
/// Parse from an XmlTextReader into a Game
/// </summary>
/// <param name="reader">XmlTextReader to read from</param>
/// <returns>Filled Game on success, null on error</returns>
public Game ParseGame(XmlTextReader reader)
{
var obj = new Game();
obj.Name = reader.GetAttribute("name");
List<Media> medias = [];
List<Source> sources = [];
List<Release> releases = [];
reader.Read();
while (!reader.EOF)
{
// An ending element means exit
if (reader.NodeType == XmlNodeType.EndElement)
break;
// Only process starting elements
if (!reader.IsStartElement())
continue;
switch (reader.Name)
{
case "archive":
if (obj.Archive is not null && Debug)
Console.WriteLine($"'{reader.Name}' element already found, overwriting");
obj.Archive = ParseArchive(reader);
reader.Skip();
break;
case "media":
var media = ParseMedia(reader);
if (media is not null)
medias.Add(media);
reader.Skip();
break;
case "source":
var source = ParseSource(reader);
if (source is not null)
sources.Add(source);
reader.Skip();
break;
case "release":
var release = ParseRelease(reader);
if (release is not null)
releases.Add(release);
reader.Skip();
break;
default:
if (Debug) Console.Error.WriteLine($"Element '{reader.Name}' is not recognized");
reader.Skip();
break;
}
}
if (medias.Count > 0)
obj.Media = [.. medias];
if (sources.Count > 0)
obj.Source = [.. sources];
if (releases.Count > 0)
obj.Release = [.. releases];
return obj;
}
/// <summary>
/// Parse from an XmlTextReader into a Media
/// </summary>
/// <param name="reader">XmlTextReader to read from</param>
/// <returns>Filled Media on success, null on error</returns>
public Media ParseMedia(XmlTextReader reader)
{
var obj = new Media();
// This item is empty
return obj;
}
/// <summary>
/// Parse from an XmlTextReader into a Release
/// </summary>
/// <param name="reader">XmlTextReader to read from</param>
/// <returns>Filled Release on success, null on error</returns>
public Release ParseRelease(XmlTextReader reader)
{
var obj = new Release();
List<Data.Models.NoIntroDatabase.File> files = [];
reader.Read();
while (!reader.EOF)
{
// An ending element means exit
if (reader.NodeType == XmlNodeType.EndElement)
break;
// Only process starting elements
if (!reader.IsStartElement())
continue;
switch (reader.Name)
{
case "details":
if (obj.Details is not null && Debug)
Console.WriteLine($"'{reader.Name}' element already found, overwriting");
obj.Details = ParseReleaseDetails(reader);
reader.Skip();
break;
case "serials":
if (obj.Serials is not null && Debug)
Console.WriteLine($"'{reader.Name}' element already found, overwriting");
obj.Serials = ParseSerials(reader);
reader.Skip();
break;
case "file":
var file = ParseFile(reader);
if (file is not null)
files.Add(file);
reader.Skip();
break;
default:
if (Debug) Console.Error.WriteLine($"Element '{reader.Name}' is not recognized");
reader.Skip();
break;
}
}
if (files.Count > 0)
obj.File = [.. files];
return obj;
}
/// <summary>
/// Parse from an XmlTextReader into a ReleaseDetails
/// </summary>
/// <param name="reader">XmlTextReader to read from</param>
/// <returns>Filled ReleaseDetails on success, null on error</returns>
public ReleaseDetails ParseReleaseDetails(XmlTextReader reader)
{
var obj = new ReleaseDetails();
obj.Id = reader.GetAttribute("id");
obj.AppendToNumber = reader.GetAttribute("append_to_number");
obj.Date = reader.GetAttribute("date");
obj.OriginalFormat = reader.GetAttribute("originalformat");
obj.Group = reader.GetAttribute("group");
obj.DirName = reader.GetAttribute("dirname");
obj.NfoName = reader.GetAttribute("nfoname");
obj.NfoSize = reader.GetAttribute("nfosize");
obj.NfoCRC = reader.GetAttribute("nfocrc");
obj.ArchiveName = reader.GetAttribute("archivename");
obj.RomInfo = reader.GetAttribute("rominfo");
obj.Category = reader.GetAttribute("category");
obj.Comment = reader.GetAttribute("comment");
obj.Tool = reader.GetAttribute("tool");
obj.Region = reader.GetAttribute("region");
obj.Origin = reader.GetAttribute("origin");
return obj;
}
/// <summary>
/// Parse from an XmlTextReader into a Serials
/// </summary>
/// <param name="reader">XmlTextReader to read from</param>
/// <returns>Filled Serials on success, null on error</returns>
public Serials ParseSerials(XmlTextReader reader)
{
var obj = new Serials();
obj.MediaSerial1 = reader.GetAttribute("media_serial1");
obj.MediaSerial2 = reader.GetAttribute("media_serial2");
obj.MediaSerial3 = reader.GetAttribute("media_serial3");
obj.PCBSerial = reader.GetAttribute("pcb_serial");
obj.RomChipSerial1 = reader.GetAttribute("romchip_serial1");
obj.RomChipSerial2 = reader.GetAttribute("romchip_serial2");
obj.LockoutSerial = reader.GetAttribute("lockout_serial");
obj.SaveChipSerial = reader.GetAttribute("savechip_serial");
obj.ChipSerial = reader.GetAttribute("chip_serial");
obj.BoxSerial = reader.GetAttribute("box_serial");
obj.MediaStamp = reader.GetAttribute("mediastamp");
obj.BoxBarcode = reader.GetAttribute("box_barcode");
obj.DigitalSerial1 = reader.GetAttribute("digital_serial1");
obj.DigitalSerial2 = reader.GetAttribute("digital_serial2");
return obj;
}
/// <summary>
/// Parse from an XmlTextReader into a Source
/// </summary>
/// <param name="reader">XmlTextReader to read from</param>
/// <returns>Filled Source on success, null on error</returns>
public Source ParseSource(XmlTextReader reader)
{
var obj = new Source();
List<Data.Models.NoIntroDatabase.File> files = [];
reader.Read();
while (!reader.EOF)
{
// An ending element means exit
if (reader.NodeType == XmlNodeType.EndElement)
break;
// Only process starting elements
if (!reader.IsStartElement())
continue;
switch (reader.Name)
{
case "details":
if (obj.Details is not null && Debug)
Console.WriteLine($"'{reader.Name}' element already found, overwriting");
obj.Details = ParseSourceDetails(reader);
reader.Skip();
break;
case "serials":
if (obj.Serials is not null && Debug)
Console.WriteLine($"'{reader.Name}' element already found, overwriting");
obj.Serials = ParseSerials(reader);
reader.Skip();
break;
case "file":
var file = ParseFile(reader);
if (file is not null)
files.Add(file);
reader.Skip();
break;
default:
if (Debug) Console.Error.WriteLine($"Element '{reader.Name}' is not recognized");
reader.Skip();
break;
}
}
if (files.Count > 0)
obj.File = [.. files];
return obj;
}
/// <summary>
/// Parse from an XmlTextReader into a SourceDetails
/// </summary>
/// <param name="reader">XmlTextReader to read from</param>
/// <returns>Filled SourceDetails on success, null on error</returns>
public SourceDetails ParseSourceDetails(XmlTextReader reader)
{
var obj = new SourceDetails();
obj.Id = reader.GetAttribute("id");
obj.AppendToNumber = reader.GetAttribute("append_to_number");
obj.Section = reader.GetAttribute("section");
obj.RomInfo = reader.GetAttribute("rominfo");
obj.DumpDate = reader.GetAttribute("d_date");
obj.DumpDateInfo = reader.GetAttribute("d_date_info");
obj.ReleaseDate = reader.GetAttribute("r_date");
obj.ReleaseDateInfo = reader.GetAttribute("r_date_info");
obj.Dumper = reader.GetAttribute("dumper");
obj.Project = reader.GetAttribute("project");
obj.OriginalFormat = reader.GetAttribute("originalformat");
obj.Nodump = reader.GetAttribute("nodump");
obj.Tool = reader.GetAttribute("tool");
obj.Origin = reader.GetAttribute("origin");
obj.Comment1 = reader.GetAttribute("comment1");
obj.Comment2 = reader.GetAttribute("comment2");
obj.Link1 = reader.GetAttribute("link1");
obj.Link1Public = reader.GetAttribute("link1_public");
obj.Link2 = reader.GetAttribute("link2");
obj.Link2Public = reader.GetAttribute("link2_public");
obj.Link3 = reader.GetAttribute("link3");
obj.Link3Public = reader.GetAttribute("link3_public");
obj.Region = reader.GetAttribute("region");
obj.MediaTitle = reader.GetAttribute("media_title");
return obj;
}
#endregion
}
}

View File

@@ -0,0 +1,24 @@
using System.IO;
using Xunit;
namespace SabreTools.Serialization.Writers.Test
{
public class NoIntroDatabaseTests
{
[Fact]
public void SerializeArray_Null_Null()
{
var serializer = new NoIntroDatabase();
byte[]? actual = serializer.SerializeArray(null);
Assert.Null(actual);
}
[Fact]
public void SerializeStream_Null_Null()
{
var serializer = new NoIntroDatabase();
Stream? actual = serializer.SerializeStream(null);
Assert.Null(actual);
}
}
}

View File

@@ -0,0 +1,350 @@
using System.IO;
using System.Text;
using System.Xml;
using SabreTools.Data.Models.NoIntroDatabase;
using SabreTools.IO.Extensions;
using SabreTools.Text.Extensions;
namespace SabreTools.Serialization.Writers
{
public class NoIntroDatabase : BaseBinaryWriter<Datafile>
{
/// <inheritdoc/>
public override Stream? SerializeStream(Datafile? 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 XmlTextWriter(stream, Encoding.UTF8)
{
Formatting = Formatting.Indented,
IndentChar = '\t',
Indentation = 1
};
writer.Settings?.CheckCharacters = false;
writer.Settings?.NewLineChars = "\n";
// Write document start
writer.WriteStartDocument();
// Write the SoftwareDb, if it exists
WriteDatafile(obj, writer);
writer.Flush();
// Return the stream
stream.SeekIfPossible(0, SeekOrigin.Begin);
return stream;
}
/// <summary>
/// Write a Datafile to an XmlTextWriter
/// </summary>
/// <param name="obj">Datafile to write</param>
/// <param name="writer">XmlTextReader to write to</param>
private static void WriteDatafile(Datafile obj, XmlTextWriter writer)
{
writer.WriteStartElement("datafile");
if (obj.Game is not null && obj.Game.Length > 0)
{
foreach (var game in obj.Game)
{
WriteGame(game, writer);
}
}
writer.WriteEndElement();
}
#region Items
/// <summary>
/// Write a Archive to an XmlTextWriter
/// </summary>
/// <param name="obj">Archive to write</param>
/// <param name="writer">XmlTextReader to write to</param>
private static void WriteArchive(Archive obj, XmlTextWriter writer)
{
writer.WriteStartElement("archive");
writer.WriteOptionalAttributeString("number", obj.Number);
writer.WriteOptionalAttributeString("clone", obj.Clone);
writer.WriteOptionalAttributeString("regparent", obj.RegParent);
writer.WriteOptionalAttributeString("mergeof", obj.MergeOf);
writer.WriteOptionalAttributeString("mergename", obj.MergeName);
writer.WriteOptionalAttributeString("name", obj.Name);
writer.WriteOptionalAttributeString("name_alt", obj.NameAlt);
writer.WriteOptionalAttributeString("region", obj.Region);
writer.WriteOptionalAttributeString("languages", obj.Languages);
writer.WriteOptionalAttributeString("showlang", obj.ShowLang);
writer.WriteOptionalAttributeString("langchecked", obj.LangChecked);
writer.WriteOptionalAttributeString("version1", obj.Version1);
writer.WriteOptionalAttributeString("version2", obj.Version2);
writer.WriteOptionalAttributeString("devstatus", obj.DevStatus);
writer.WriteOptionalAttributeString("additional", obj.Additional);
writer.WriteOptionalAttributeString("special1", obj.Special1);
writer.WriteOptionalAttributeString("special2", obj.Special2);
writer.WriteOptionalAttributeString("alt", obj.Alt);
writer.WriteOptionalAttributeString("gameid1", obj.GameId1);
writer.WriteOptionalAttributeString("gameid2", obj.GameId2);
writer.WriteOptionalAttributeString("description", obj.Description);
writer.WriteOptionalAttributeString("bios", obj.Bios);
writer.WriteOptionalAttributeString("licensed", obj.Licensed);
writer.WriteOptionalAttributeString("pirate", obj.Pirate);
writer.WriteOptionalAttributeString("physical", obj.Physical);
writer.WriteOptionalAttributeString("complete", obj.Complete);
writer.WriteOptionalAttributeString("adult", obj.Adult);
writer.WriteOptionalAttributeString("dat", obj.Dat);
writer.WriteOptionalAttributeString("listed", obj.Listed);
writer.WriteOptionalAttributeString("private", obj.Private);
writer.WriteOptionalAttributeString("sticky_note", obj.StickyNote);
writer.WriteOptionalAttributeString("datter_note", obj.DatterNote);
writer.WriteOptionalAttributeString("categories", obj.Categories);
writer.WriteEndElement();
}
/// <summary>
/// Write a File to an XmlTextWriter
/// </summary>
/// <param name="obj">File to write</param>
/// <param name="writer">XmlTextReader to write to</param>
private static void WriteFile(Data.Models.NoIntroDatabase.File obj, XmlTextWriter writer)
{
writer.WriteStartElement("file");
writer.WriteOptionalAttributeString("id", obj.Id);
writer.WriteOptionalAttributeString("append_to_source_id", obj.AppendToSourceId);
writer.WriteOptionalAttributeString("forcename", obj.ForceName);
writer.WriteOptionalAttributeString("forcescenename", obj.ForceSceneName);
writer.WriteOptionalAttributeString("emptydir", obj.EmptyDir);
writer.WriteOptionalAttributeString("extension", obj.Extension);
writer.WriteOptionalAttributeString("item", obj.Item);
writer.WriteOptionalAttributeString("date", obj.Date);
writer.WriteOptionalAttributeString("format", obj.Format);
writer.WriteOptionalAttributeString("note", obj.Note);
writer.WriteOptionalAttributeString("filter", obj.Filter);
writer.WriteOptionalAttributeString("version", obj.Version);
writer.WriteOptionalAttributeString("update_type", obj.UpdateType);
writer.WriteOptionalAttributeString("size", obj.Size);
writer.WriteOptionalAttributeString("crc32", obj.CRC32);
writer.WriteOptionalAttributeString("md5", obj.MD5);
writer.WriteOptionalAttributeString("sha1", obj.SHA1);
writer.WriteOptionalAttributeString("sha256", obj.SHA256);
writer.WriteOptionalAttributeString("serial", obj.Serial);
writer.WriteOptionalAttributeString("header", obj.Header);
writer.WriteOptionalAttributeString("bad", obj.Bad);
writer.WriteOptionalAttributeString("mia", obj.MIA);
writer.WriteOptionalAttributeString("unique", obj.Unique);
writer.WriteOptionalAttributeString("mergename", obj.MergeName);
writer.WriteOptionalAttributeString("unique_attachment", obj.UniqueAttachment);
writer.WriteEndElement();
}
/// <summary>
/// Write a Game to an XmlTextWriter
/// </summary>
/// <param name="obj">Game to write</param>
/// <param name="writer">XmlTextReader to write to</param>
private static void WriteGame(Game obj, XmlTextWriter writer)
{
writer.WriteStartElement("game");
writer.WriteOptionalAttributeString("name", obj.Name);
if (obj.Archive is not null)
WriteArchive(obj.Archive, writer);
if (obj.Media is not null && obj.Media.Length > 0)
{
foreach (var media in obj.Media)
{
WriteMedia(media, writer);
}
}
if (obj.Source is not null && obj.Source.Length > 0)
{
foreach (var source in obj.Source)
{
WriteSource(source, writer);
}
}
if (obj.Release is not null && obj.Release.Length > 0)
{
foreach (var release in obj.Release)
{
WriteRelease(release, writer);
}
}
writer.WriteEndElement();
}
/// <summary>
/// Write a Media to an XmlTextWriter
/// </summary>
/// <param name="obj">Media to write</param>
/// <param name="writer">XmlTextReader to write to</param>
private static void WriteMedia(Media obj, XmlTextWriter writer)
{
writer.WriteStartElement("media");
// This item is empty
writer.WriteEndElement();
}
/// <summary>
/// Write a Release to an XmlTextWriter
/// </summary>
/// <param name="obj">Release to write</param>
/// <param name="writer">XmlTextReader to write to</param>
private static void WriteRelease(Release obj, XmlTextWriter writer)
{
writer.WriteStartElement("release");
if (obj.Details is not null)
WriteReleaseDetails(obj.Details, writer);
if (obj.Serials is not null)
WriteSerials(obj.Serials, writer);
if (obj.File is not null && obj.File.Length > 0)
{
foreach (var file in obj.File)
{
WriteFile(file, writer);
}
}
writer.WriteEndElement();
}
/// <summary>
/// Write a ReleaseDetails to an XmlTextWriter
/// </summary>
/// <param name="obj">ReleaseDetails to write</param>
/// <param name="writer">XmlTextReader to write to</param>
private static void WriteReleaseDetails(ReleaseDetails obj, XmlTextWriter writer)
{
writer.WriteStartElement("details");
writer.WriteOptionalAttributeString("id", obj.Id);
writer.WriteOptionalAttributeString("append_to_number", obj.AppendToNumber);
writer.WriteOptionalAttributeString("date", obj.Date);
writer.WriteOptionalAttributeString("originalformat", obj.OriginalFormat);
writer.WriteOptionalAttributeString("group", obj.Group);
writer.WriteOptionalAttributeString("dirname", obj.DirName);
writer.WriteOptionalAttributeString("nfoname", obj.NfoName);
writer.WriteOptionalAttributeString("nfosize", obj.NfoSize);
writer.WriteOptionalAttributeString("nfocrc", obj.NfoCRC);
writer.WriteOptionalAttributeString("archivename", obj.ArchiveName);
writer.WriteOptionalAttributeString("rominfo", obj.RomInfo);
writer.WriteOptionalAttributeString("category", obj.Category);
writer.WriteOptionalAttributeString("comment", obj.Comment);
writer.WriteOptionalAttributeString("tool", obj.Tool);
writer.WriteOptionalAttributeString("region", obj.Region);
writer.WriteOptionalAttributeString("origin", obj.Origin);
writer.WriteEndElement();
}
/// <summary>
/// Write a Serials to an XmlTextWriter
/// </summary>
/// <param name="obj">Serials to write</param>
/// <param name="writer">XmlTextReader to write to</param>
private static void WriteSerials(Serials obj, XmlTextWriter writer)
{
writer.WriteStartElement("serials");
writer.WriteOptionalAttributeString("media_serial1", obj.MediaSerial1);
writer.WriteOptionalAttributeString("media_serial2", obj.MediaSerial2);
writer.WriteOptionalAttributeString("media_serial3", obj.MediaSerial3);
writer.WriteOptionalAttributeString("pcb_serial", obj.PCBSerial);
writer.WriteOptionalAttributeString("romchip_serial1", obj.RomChipSerial1);
writer.WriteOptionalAttributeString("romchip_serial2", obj.RomChipSerial2);
writer.WriteOptionalAttributeString("lockout_serial", obj.LockoutSerial);
writer.WriteOptionalAttributeString("savechip_serial", obj.SaveChipSerial);
writer.WriteOptionalAttributeString("chip_serial", obj.ChipSerial);
writer.WriteOptionalAttributeString("box_serial", obj.BoxSerial);
writer.WriteOptionalAttributeString("mediastamp", obj.MediaStamp);
writer.WriteOptionalAttributeString("box_barcode", obj.BoxBarcode);
writer.WriteOptionalAttributeString("digital_serial1", obj.DigitalSerial1);
writer.WriteOptionalAttributeString("digital_serial2", obj.DigitalSerial2);
writer.WriteEndElement();
}
/// <summary>
/// Write a Source to an XmlTextWriter
/// </summary>
/// <param name="obj">Source to write</param>
/// <param name="writer">XmlTextReader to write to</param>
private static void WriteSource(Source obj, XmlTextWriter writer)
{
writer.WriteStartElement("source");
if (obj.Details is not null)
WriteSourceDetails(obj.Details, writer);
if (obj.Serials is not null)
WriteSerials(obj.Serials, writer);
if (obj.File is not null && obj.File.Length > 0)
{
foreach (var file in obj.File)
{
WriteFile(file, writer);
}
}
writer.WriteEndElement();
}
/// <summary>
/// Write a SourceDetails to an XmlTextWriter
/// </summary>
/// <param name="obj">SourceDetails to write</param>
/// <param name="writer">XmlTextReader to write to</param>
private static void WriteSourceDetails(SourceDetails obj, XmlTextWriter writer)
{
writer.WriteStartElement("details");
writer.WriteOptionalAttributeString("id", obj.Id);
writer.WriteOptionalAttributeString("append_to_number", obj.AppendToNumber);
writer.WriteOptionalAttributeString("section", obj.Section);
writer.WriteOptionalAttributeString("rominfo", obj.RomInfo);
writer.WriteOptionalAttributeString("d_date", obj.DumpDate);
writer.WriteOptionalAttributeString("d_date_info", obj.DumpDateInfo);
writer.WriteOptionalAttributeString("r_date", obj.ReleaseDate);
writer.WriteOptionalAttributeString("r_date_info", obj.ReleaseDateInfo);
writer.WriteOptionalAttributeString("dumper", obj.Dumper);
writer.WriteOptionalAttributeString("project", obj.Project);
writer.WriteOptionalAttributeString("originalformat", obj.OriginalFormat);
writer.WriteOptionalAttributeString("nodump", obj.Nodump);
writer.WriteOptionalAttributeString("tool", obj.Tool);
writer.WriteOptionalAttributeString("origin", obj.Origin);
writer.WriteOptionalAttributeString("comment1", obj.Comment1);
writer.WriteOptionalAttributeString("comment2", obj.Comment2);
writer.WriteOptionalAttributeString("link1", obj.Link1);
writer.WriteOptionalAttributeString("link1_public", obj.Link1Public);
writer.WriteOptionalAttributeString("link2", obj.Link2);
writer.WriteOptionalAttributeString("link2_public", obj.Link2Public);
writer.WriteOptionalAttributeString("link3", obj.Link3);
writer.WriteOptionalAttributeString("link3_public", obj.Link3Public);
writer.WriteOptionalAttributeString("region", obj.Region);
writer.WriteOptionalAttributeString("media_title", obj.MediaTitle);
writer.WriteEndElement();
}
#endregion
}
}