diff --git a/SabreTools.Models/DosCenter/DosCenter.cs b/SabreTools.Models/DosCenter/DosCenter.cs index 9fb3fda2..8368d8a7 100644 --- a/SabreTools.Models/DosCenter/DosCenter.cs +++ b/SabreTools.Models/DosCenter/DosCenter.cs @@ -27,7 +27,7 @@ namespace SabreTools.Models.DosCenter #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/Hashfile/Hashfile.cs b/SabreTools.Models/Hashfile/Hashfile.cs index 3dd5f4e2..ad694120 100644 --- a/SabreTools.Models/Hashfile/Hashfile.cs +++ b/SabreTools.Models/Hashfile/Hashfile.cs @@ -16,5 +16,12 @@ namespace SabreTools.Models.Hashfile public SHA512[]? SHA512 { get; set; } public SpamSum[]? SpamSum { 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/Hashfile.cs b/SabreTools.Serialization/Hashfile.cs new file mode 100644 index 00000000..a26cad38 --- /dev/null +++ b/SabreTools.Serialization/Hashfile.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using SabreTools.Core; + +namespace SabreTools.Serialization +{ + /// + /// Serializer for hashfile variants + /// + public class Hashfile + { + /// + /// Deserializes a hashfile variant to the defined type + /// + /// Path to the file to deserialize + /// Hash corresponding to the hashfile variant + /// Deserialized data on success, null on failure + public static Models.Hashfile.Hashfile? Deserialize(string path, Hash hash) + { + try + { + using var stream = PathProcessor.OpenStream(path); + return Deserialize(stream, hash); + } + catch + { + // TODO: Handle logging the exception + return default; + } + } + + /// + /// Deserializes a hashfile variant in a stream to the defined type + /// + /// Stream to deserialize + /// Hash corresponding to the hashfile variant + /// Deserialized data on success, null on failure + public static Models.Hashfile.Hashfile? Deserialize(Stream? stream, Hash hash) + { + try + { + // If the stream is null + if (stream == null) + return default; + + // Setup the reader and output + var reader = new StreamReader(stream); + var dat = new Models.Hashfile.Hashfile(); + var additional = new List(); + + // Loop through the rows and parse out values + var hashes = new List(); + while (!reader.EndOfStream) + { + // Read and split the line + string? line = reader.ReadLine(); + string[]? lineParts = line?.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (lineParts == null || lineParts.Length != 2) + { + additional.Add(line); + continue; + } + + // Parse the line into a hash + switch (hash) + { + case Hash.CRC: + var sfv = new Models.Hashfile.SFV + { + File = lineParts[0], + Hash = lineParts[1], + }; + hashes.Add(sfv); + break; + case Hash.MD5: + var md5 = new Models.Hashfile.MD5 + { + Hash = lineParts[0], + File = lineParts[1], + }; + hashes.Add(md5); + break; + case Hash.SHA1: + var sha1 = new Models.Hashfile.SHA1 + { + Hash = lineParts[0], + File = lineParts[1], + }; + hashes.Add(sha1); + break; + case Hash.SHA256: + var sha256 = new Models.Hashfile.SHA256 + { + Hash = lineParts[0], + File = lineParts[1], + }; + hashes.Add(sha256); + break; + case Hash.SHA384: + var sha384 = new Models.Hashfile.SHA384 + { + Hash = lineParts[0], + File = lineParts[1], + }; + hashes.Add(sha384); + break; + case Hash.SHA512: + var sha512 = new Models.Hashfile.SHA512 + { + Hash = lineParts[0], + File = lineParts[1], + }; + hashes.Add(sha512); + break; + case Hash.SpamSum: + var spamSum = new Models.Hashfile.SpamSum + { + Hash = lineParts[0], + File = lineParts[1], + }; + hashes.Add(spamSum); + break; + } + } + + // Assign the hashes to the hashfile and return + switch (hash) + { + case Hash.CRC: + dat.SFV = hashes.Cast().ToArray(); + break; + case Hash.MD5: + dat.MD5 = hashes.Cast().ToArray(); + break; + case Hash.SHA1: + dat.SHA1 = hashes.Cast().ToArray(); + break; + case Hash.SHA256: + dat.SHA256 = hashes.Cast().ToArray(); + break; + case Hash.SHA384: + dat.SHA384 = hashes.Cast().ToArray(); + break; + case Hash.SHA512: + dat.SHA512 = hashes.Cast().ToArray(); + break; + case Hash.SpamSum: + dat.SpamSum = hashes.Cast().ToArray(); + break; + } + 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.Serialization/SabreTools.Serialization.csproj b/SabreTools.Serialization/SabreTools.Serialization.csproj index c1595d16..cc4f6027 100644 --- a/SabreTools.Serialization/SabreTools.Serialization.csproj +++ b/SabreTools.Serialization/SabreTools.Serialization.csproj @@ -6,6 +6,7 @@ + diff --git a/SabreTools.Test/Parser/SerializationTests.cs b/SabreTools.Test/Parser/SerializationTests.cs index d7f108eb..ad3a4d6c 100644 --- a/SabreTools.Test/Parser/SerializationTests.cs +++ b/SabreTools.Test/Parser/SerializationTests.cs @@ -1,4 +1,5 @@ using System; +using SabreTools.Core; using Xunit; namespace SabreTools.Test.Parser @@ -74,6 +75,53 @@ namespace SabreTools.Test.Parser } } + [Theory] + [InlineData("test-sfv.sfv", Hash.CRC)] + [InlineData("test-md5.md5", Hash.MD5)] + [InlineData("test-sha1.sha1", Hash.SHA1)] + [InlineData("test-sha256.sha256", Hash.SHA256)] + [InlineData("test-sha384.sha384", Hash.SHA384)] + [InlineData("test-sha512.sha512", Hash.SHA512)] + [InlineData("test-spamsum.spamsum", Hash.SpamSum)] + public void HashfileDeserializeTest(string file, Hash hash) + { + // Open the file for reading + string filename = System.IO.Path.Combine(Environment.CurrentDirectory, "TestData", file); + + // Deserialize the file + var dat = Serialization.Hashfile.Deserialize(filename, hash); + + // Validate the values + Assert.NotNull(dat); + + switch (hash) + { + case Hash.CRC: + Assert.Single(dat.SFV); + break; + case Hash.MD5: + Assert.Single(dat.MD5); + break; + case Hash.SHA1: + Assert.Single(dat.SHA1); + break; + case Hash.SHA256: + Assert.Single(dat.SHA256); + break; + case Hash.SHA384: + Assert.Single(dat.SHA384); + break; + case Hash.SHA512: + Assert.Single(dat.SHA512); + break; + case Hash.SpamSum: + Assert.Single(dat.SpamSum); + break; + default: + throw new ArgumentOutOfRangeException(nameof(hash)); + } + } + [Fact] public void ListxmlDeserializeTest() {