diff --git a/SabreTools.IO/Readers/ClrMameProReader.cs b/SabreTools.IO/Readers/ClrMameProReader.cs index 020ed3f0..5b6f864d 100644 --- a/SabreTools.IO/Readers/ClrMameProReader.cs +++ b/SabreTools.IO/Readers/ClrMameProReader.cs @@ -175,7 +175,7 @@ namespace SabreTools.IO.Readers { while (++i < linegc.Length && linegc[i] != "size" - && linegc[i] != "date" + && !(linegc[i] == "date" && char.IsDigit(linegc[i + 1][0])) && linegc[i] != "crc") { value += $" {linegc[i]}"; @@ -235,7 +235,7 @@ namespace SabreTools.IO.Readers Internal[key] = value; RowType = CmpRowType.Internal; Standalone = null; - } + } InternalName = normalizedValue; } diff --git a/SabreTools.Models/AttractMode/Row.cs b/SabreTools.Models/AttractMode/Row.cs index 8d702fc3..278eede2 100644 --- a/SabreTools.Models/AttractMode/Row.cs +++ b/SabreTools.Models/AttractMode/Row.cs @@ -50,7 +50,7 @@ namespace SabreTools.Models.AttractMode #region DO NOT USE IN PRODUCTION /// Should be empty - public string[] ADDITIONAL_ELEMENTS { get; set; } + public string[]? ADDITIONAL_ELEMENTS { get; set; } #endregion } diff --git a/SabreTools.Models/DosCenter/DatFile.cs b/SabreTools.Models/DosCenter/DatFile.cs index df721086..571b37ee 100644 --- a/SabreTools.Models/DosCenter/DatFile.cs +++ b/SabreTools.Models/DosCenter/DatFile.cs @@ -7,5 +7,12 @@ namespace SabreTools.Models.DosCenter /// game public Game[]? Game { get; set; } + + #region DO NOT USE IN PRODUCTION + + /// Should be empty + public string[]? ADDITIONAL_ELEMENTS { get; set; } + + #endregion } } \ No newline at end of file diff --git a/SabreTools.Models/DosCenter/DosCenter.cs b/SabreTools.Models/DosCenter/DosCenter.cs index b391ceb6..9fb3fda2 100644 --- a/SabreTools.Models/DosCenter/DosCenter.cs +++ b/SabreTools.Models/DosCenter/DosCenter.cs @@ -23,5 +23,12 @@ namespace SabreTools.Models.DosCenter /// comment public string? Comment { get; set; } + + #region DO NOT USE IN PRODUCTION + + /// Should be empty + public string[] ADDITIONAL_ELEMENTS { get; set; } + + #endregion } } \ No newline at end of file diff --git a/SabreTools.Models/DosCenter/File.cs b/SabreTools.Models/DosCenter/File.cs index edb049dc..5003a7c9 100644 --- a/SabreTools.Models/DosCenter/File.cs +++ b/SabreTools.Models/DosCenter/File.cs @@ -6,13 +6,20 @@ namespace SabreTools.Models.DosCenter /// name, attribute public string? Name { get; set; } - /// size, attribute - public long? Size { get; set; } + /// size, attribute, numeric + public string? Size { get; set; } /// crc, attribute public string? CRC { get; set; } /// date, attribute public string? Date { get; set; } + + #region DO NOT USE IN PRODUCTION + + /// Should be empty + public string[]? ADDITIONAL_ELEMENTS { get; set; } + + #endregion } } \ No newline at end of file diff --git a/SabreTools.Models/DosCenter/Game.cs b/SabreTools.Models/DosCenter/Game.cs index 165b5645..3f5b159a 100644 --- a/SabreTools.Models/DosCenter/Game.cs +++ b/SabreTools.Models/DosCenter/Game.cs @@ -8,5 +8,12 @@ namespace SabreTools.Models.DosCenter /// file public File[]? File { get; set; } + + #region DO NOT USE IN PRODUCTION + + /// Should be empty + public string[]? ADDITIONAL_ELEMENTS { get; set; } + + #endregion } } \ No newline at end of file diff --git a/SabreTools.Serialization/DosCenter.cs b/SabreTools.Serialization/DosCenter.cs new file mode 100644 index 00000000..fab060a9 --- /dev/null +++ b/SabreTools.Serialization/DosCenter.cs @@ -0,0 +1,218 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using SabreTools.IO.Readers; +using SabreTools.Models.DosCenter; + +namespace SabreTools.Serialization +{ + /// + /// Serializer for DosCenter metadata files + /// + public class DosCenter + { + /// + /// Deserializes a DosCenter metadata file to the defined type + /// + /// Path to the file to deserialize + /// Deserialized data on success, null on failure + public static DatFile? Deserialize(string path) + { + try + { + using var stream = PathProcessor.OpenStream(path); + return Deserialize(stream); + } + catch + { + // TODO: Handle logging the exception + return default; + } + } + + /// + /// Deserializes a DosCenter metadata file in a stream to the defined type + /// + /// Stream to deserialize + /// Deserialized data on success, null on failure + public static DatFile? Deserialize(Stream? stream) + { + try + { + // If the stream is null + if (stream == null) + return default; + + // Setup the reader and output + var reader = new ClrMameProReader(stream, Encoding.UTF8) { DosCenter = true }; + var dat = new DatFile(); + + // Loop through and parse out the values + string lastTopLevel = reader.TopLevel; + + Game? game = null; + var games = new List(); + var files = new List(); + + var additional = new List(); + var headerAdditional = new List(); + var gameAdditional = new List(); + var fileAdditional = new List(); + while (!reader.EndOfStream) + { + // If we have no next line + if (!reader.ReadNextLine()) + break; + + // Ignore certain row types + switch (reader.RowType) + { + case CmpRowType.None: + case CmpRowType.Comment: + continue; + case CmpRowType.EndTopLevel: + switch (lastTopLevel) + { + case "doscenter": + dat.DosCenter.ADDITIONAL_ELEMENTS = headerAdditional.ToArray(); + headerAdditional.Clear(); + break; + case "game": + game.File = files.ToArray(); + game.ADDITIONAL_ELEMENTS = gameAdditional.ToArray(); + games.Add(game); + + game = null; + files.Clear(); + gameAdditional.Clear(); + break; + default: + // No-op + break; + } + continue; + } + + // If we're at the root + if (reader.RowType == CmpRowType.TopLevel) + { + lastTopLevel = reader.TopLevel; + switch (reader.TopLevel) + { + case "doscenter": + dat.DosCenter = new Models.DosCenter.DosCenter(); + break; + case "game": + game = new Game(); + break; + default: + additional.Add(reader.CurrentLine); + break; + } + } + + // If we're in the doscenter block + else if (reader.TopLevel == "doscenter" && reader.RowType == CmpRowType.Standalone) + { + // Create the block if we haven't already + dat.DosCenter ??= new Models.DosCenter.DosCenter(); + + switch (reader.Standalone?.Key?.ToLowerInvariant()) + { + case "name:": + dat.DosCenter.Name = reader.Standalone?.Value; + break; + case "description:": + dat.DosCenter.Description = reader.Standalone?.Value; + break; + case "version:": + dat.DosCenter.Version = reader.Standalone?.Value; + break; + case "date:": + dat.DosCenter.Date = reader.Standalone?.Value; + break; + case "author:": + dat.DosCenter.Author = reader.Standalone?.Value; + break; + case "homepage:": + dat.DosCenter.Homepage = reader.Standalone?.Value; + break; + case "comment:": + dat.DosCenter.Comment = reader.Standalone?.Value; + break; + default: + headerAdditional.Add(item: reader.CurrentLine); + break; + } + } + + // If we're in a game block + else if (reader.TopLevel == "game" && reader.RowType == CmpRowType.Standalone) + { + // Create the block if we haven't already + game ??= new Game(); + + switch (reader.Standalone?.Key?.ToLowerInvariant()) + { + case "name": + game.Name = reader.Standalone?.Value; + break; + default: + gameAdditional.Add(item: reader.CurrentLine); + break; + } + } + + // If we're in a file block + else if (reader.TopLevel == "game" && reader.RowType == CmpRowType.Internal) + { + // Create the block + var file = new Models.DosCenter.File(); + + foreach (var kvp in reader.Internal) + { + switch (kvp.Key?.ToLowerInvariant()) + { + case "name": + file.Name = kvp.Value; + break; + case "size": + file.Size = kvp.Value; + break; + case "crc": + file.CRC = kvp.Value; + break; + case "date": + file.Date = kvp.Value; + break; + default: + fileAdditional.Add(item: reader.CurrentLine); + break; + } + } + + // Add the file to the list + file.ADDITIONAL_ELEMENTS = fileAdditional.ToArray(); + files.Add(file); + fileAdditional.Clear(); + } + + else + { + additional.Add(item: reader.CurrentLine); + } + } + + // Add extra pieces and return + dat.Game = games.ToArray(); + dat.ADDITIONAL_ELEMENTS = additional.ToArray(); + return dat; + } + catch + { + // TODO: Handle logging the exception + return default; + } + } + } +} \ No newline at end of file diff --git a/SabreTools.Test/Parser/SerializationTests.cs b/SabreTools.Test/Parser/SerializationTests.cs index 30214788..d7f108eb 100644 --- a/SabreTools.Test/Parser/SerializationTests.cs +++ b/SabreTools.Test/Parser/SerializationTests.cs @@ -48,6 +48,32 @@ namespace SabreTools.Test.Parser } } + [Fact] + public void DosCenterDeserializeTest() + { + // Open the file for reading + string filename = System.IO.Path.Combine(Environment.CurrentDirectory, "TestData", "test-doscenter-files.dat.gz"); + + // Deserialize the file + var dat = Serialization.DosCenter.Deserialize(filename); + + // Validate the values + Assert.NotNull(dat?.DosCenter); + Assert.Equal(34965, dat.Game.Length); + + // Validate we're not missing any attributes or elements + Assert.Empty(dat.ADDITIONAL_ELEMENTS); + Assert.Empty(dat.DosCenter.ADDITIONAL_ELEMENTS); + foreach (var game in dat.Game) + { + Assert.Empty(game.ADDITIONAL_ELEMENTS); + foreach (var file in game.File) + { + Assert.Empty(file.ADDITIONAL_ELEMENTS); + } + } + } + [Fact] public void ListxmlDeserializeTest() { diff --git a/SabreTools.Test/TestData/test-doscenter-files.dat.gz b/SabreTools.Test/TestData/test-doscenter-files.dat.gz new file mode 100644 index 00000000..d2f2d147 Binary files /dev/null and b/SabreTools.Test/TestData/test-doscenter-files.dat.gz differ