From 219392e3a09bce7ff2c968cc22147c4af92de6f6 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Sun, 22 Mar 2026 20:22:04 -0400 Subject: [PATCH] Port DictionaryBase extensions from ST Notably, this is missing the equality checking because ST introduces a new type called "ModelBackedItem", which is specifically a type backed by a DictionaryBase item, with extensions. It is very similar to a Wrapper, but has different common utility. If there is a way to migrate that here, then the equality methods can come here as well. --- .../MetadataExtensions.cs | 1337 +++++++++++++++++ .../MetadataExtensions.cs | 842 +++++++++++ 2 files changed, 2179 insertions(+) create mode 100644 SabreTools.Data.Extensions.Test/MetadataExtensions.cs create mode 100644 SabreTools.Data.Extensions/MetadataExtensions.cs diff --git a/SabreTools.Data.Extensions.Test/MetadataExtensions.cs b/SabreTools.Data.Extensions.Test/MetadataExtensions.cs new file mode 100644 index 00000000..f4e0483c --- /dev/null +++ b/SabreTools.Data.Extensions.Test/MetadataExtensions.cs @@ -0,0 +1,1337 @@ +using SabreTools.Data.Models.Metadata; +using SabreTools.Hashing; +using Xunit; + +namespace SabreTools.Data.Extensions.Test +{ + public class MetadataExtensionsTests + { + #region ConvertToRom + + [Fact] + public void ConvertToRom_Null_Null() + { + DictionaryBase? self = null; + Rom? actual = self.ConvertToRom(); + Assert.Null(actual); + } + + [Fact] + public void ConvertToRom_EmptyDisk_EmptyRom() + { + DictionaryBase? self = new Disk(); + Rom? actual = self.ConvertToRom(); + + Assert.NotNull(actual); + Assert.Equal(8, actual.Count); + Assert.Equal(ItemType.Rom, actual["_type"]); + Assert.Null(actual[Rom.NameKey]); + Assert.Null(actual[Rom.MergeKey]); + Assert.Null(actual[Rom.RegionKey]); + Assert.Null(actual[Rom.StatusKey]); + Assert.Null(actual[Rom.OptionalKey]); + Assert.Null(actual[Rom.MD5Key]); + Assert.Null(actual[Rom.SHA1Key]); + } + + [Fact] + public void ConvertToRom_FilledDisk_FilledRom() + { + DictionaryBase? self = new Disk + { + [Disk.NameKey] = "XXXXXX", + [Disk.MergeKey] = "XXXXXX", + [Disk.RegionKey] = "XXXXXX", + [Disk.StatusKey] = "XXXXXX", + [Disk.OptionalKey] = "XXXXXX", + [Disk.MD5Key] = "XXXXXX", + [Disk.SHA1Key] = "XXXXXX", + }; + + Rom? actual = self.ConvertToRom(); + + Assert.NotNull(actual); + Assert.Equal(8, actual.Count); + Assert.Equal(ItemType.Rom, actual["_type"]); + Assert.Equal("XXXXXX.chd", actual[Rom.NameKey]); + Assert.Equal("XXXXXX", actual[Rom.MergeKey]); + Assert.Equal("XXXXXX", actual[Rom.RegionKey]); + Assert.Equal("XXXXXX", actual[Rom.StatusKey]); + Assert.Equal("XXXXXX", actual[Rom.OptionalKey]); + Assert.Equal("XXXXXX", actual[Rom.MD5Key]); + Assert.Equal("XXXXXX", actual[Rom.SHA1Key]); + } + + [Fact] + public void ConvertToRom_EmptyMedia_EmptyRom() + { + DictionaryBase? self = new Media(); + Rom? actual = self.ConvertToRom(); + + Assert.NotNull(actual); + Assert.Equal(6, actual.Count); + Assert.Equal(ItemType.Rom, actual["_type"]); + Assert.Null(actual[Rom.NameKey]); + Assert.Null(actual[Rom.MD5Key]); + Assert.Null(actual[Rom.SHA1Key]); + Assert.Null(actual[Rom.SHA256Key]); + Assert.Null(actual[Rom.SpamSumKey]); + } + + [Fact] + public void ConvertToRom_FilledMedia_FilledRom() + { + DictionaryBase? self = new Media + { + [Media.NameKey] = "XXXXXX", + [Media.MD5Key] = "XXXXXX", + [Media.SHA1Key] = "XXXXXX", + [Media.SHA256Key] = "XXXXXX", + [Media.SpamSumKey] = "XXXXXX", + }; + + Rom? actual = self.ConvertToRom(); + + Assert.NotNull(actual); + Assert.Equal(6, actual.Count); + Assert.Equal(ItemType.Rom, actual["_type"]); + Assert.Equal("XXXXXX.aaruf", actual[Rom.NameKey]); + Assert.Equal("XXXXXX", actual[Rom.MD5Key]); + Assert.Equal("XXXXXX", actual[Rom.SHA1Key]); + Assert.Equal("XXXXXX", actual[Rom.SHA256Key]); + Assert.Equal("XXXXXX", actual[Rom.SpamSumKey]); + } + + [Fact] + public void ConvertToRom_Other_Null() + { + DictionaryBase? self = new Sample(); + Rom? actual = self.ConvertToRom(); + Assert.Null(actual); + } + + #endregion + + #region HashMatch + + [Fact] + public void HashMatch_Disk_Mismatch_False() + { + Disk self = new Disk + { + [Disk.MD5Key] = "XXXXXX", + [Disk.SHA1Key] = string.Empty, + }; + Disk other = new Disk + { + [Disk.MD5Key] = string.Empty, + [Disk.SHA1Key] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.False(actual); + } + + [Fact] + public void HashMatch_Disk_PartialMD5_True() + { + Disk self = new Disk + { + [Disk.MD5Key] = "XXXXXX", + [Disk.SHA1Key] = string.Empty, + }; + Disk other = new Disk + { + [Disk.MD5Key] = "XXXXXX", + [Disk.SHA1Key] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Disk_PartialSHA1_True() + { + Disk self = new Disk + { + [Disk.MD5Key] = string.Empty, + [Disk.SHA1Key] = "XXXXXX", + }; + Disk other = new Disk + { + [Disk.MD5Key] = "XXXXXX", + [Disk.SHA1Key] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Disk_FullMatch_True() + { + Disk self = new Disk + { + [Disk.MD5Key] = "XXXXXX", + [Disk.SHA1Key] = "XXXXXX", + }; + Disk other = new Disk + { + [Disk.MD5Key] = "XXXXXX", + [Disk.SHA1Key] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Media_Mismatch_False() + { + Media self = new Media + { + [Media.MD5Key] = "XXXXXX", + [Media.SHA1Key] = string.Empty, + [Media.SHA256Key] = "XXXXXX", + [Media.SpamSumKey] = string.Empty, + }; + Media other = new Media + { + [Media.MD5Key] = string.Empty, + [Media.SHA1Key] = "XXXXXX", + [Media.SHA256Key] = string.Empty, + [Media.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.False(actual); + } + + [Fact] + public void HashMatch_Media_PartialMD5_True() + { + Media self = new Media + { + [Media.MD5Key] = "XXXXXX", + [Media.SHA1Key] = string.Empty, + [Media.SHA256Key] = string.Empty, + [Media.SpamSumKey] = string.Empty, + }; + Media other = new Media + { + [Media.MD5Key] = "XXXXXX", + [Media.SHA1Key] = "XXXXXX", + [Media.SHA256Key] = "XXXXXX", + [Media.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Media_PartialSHA1_True() + { + Media self = new Media + { + [Media.MD5Key] = string.Empty, + [Media.SHA1Key] = "XXXXXX", + [Media.SHA256Key] = string.Empty, + [Media.SpamSumKey] = string.Empty, + }; + Media other = new Media + { + [Media.MD5Key] = "XXXXXX", + [Media.SHA1Key] = "XXXXXX", + [Media.SHA256Key] = "XXXXXX", + [Media.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Media_PartialSHA256_True() + { + Media self = new Media + { + [Media.MD5Key] = string.Empty, + [Media.SHA1Key] = string.Empty, + [Media.SHA256Key] = "XXXXXX", + [Media.SpamSumKey] = string.Empty, + }; + Media other = new Media + { + [Media.MD5Key] = "XXXXXX", + [Media.SHA1Key] = "XXXXXX", + [Media.SHA256Key] = "XXXXXX", + [Media.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Media_PartialSpamSum_True() + { + Media self = new Media + { + [Media.MD5Key] = string.Empty, + [Media.SHA1Key] = string.Empty, + [Media.SHA256Key] = string.Empty, + [Media.SpamSumKey] = "XXXXXX", + }; + Media other = new Media + { + [Media.MD5Key] = "XXXXXX", + [Media.SHA1Key] = "XXXXXX", + [Media.SHA256Key] = "XXXXXX", + [Media.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Media_FullMatch_True() + { + Media self = new Media + { + [Media.MD5Key] = "XXXXXX", + [Media.SHA1Key] = "XXXXXX", + [Media.SHA256Key] = "XXXXXX", + [Media.SpamSumKey] = "XXXXXX", + }; + Media other = new Media + { + [Media.MD5Key] = "XXXXXX", + [Media.SHA1Key] = "XXXXXX", + [Media.SHA256Key] = "XXXXXX", + [Media.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Rom_Mismatch_False() + { + Rom self = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = "XXXXXX", + }; + Rom other = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = string.Empty, + }; + + bool actual = self.HashMatch(other); + Assert.False(actual); + } + + [Fact] + public void HashMatch_Rom_PartialCRC_True() + { + Rom self = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + Rom other = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Rom_PartialMD2_True() + { + Rom self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + Rom other = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Rom_PartialMD4_True() + { + Rom self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + Rom other = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Rom_PartialMD5_True() + { + Rom self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + Rom other = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Rom_PartialRIPEMD128_True() + { + Rom self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + Rom other = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Rom_PartialRIPEMD160_True() + { + Rom self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + Rom other = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Rom_PartialSHA1_True() + { + Rom self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + Rom other = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Rom_PartialSHA256_True() + { + Rom self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + Rom other = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Rom_PartialSHA384_True() + { + Rom self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + Rom other = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Rom_PartialSHA512_True() + { + Rom self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = string.Empty, + }; + Rom other = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Rom_PartialSpamSum_True() + { + Rom self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = "XXXXXX", + }; + Rom other = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + [Fact] + public void HashMatch_Rom_FullMatch_True() + { + Rom self = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + Rom other = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HashMatch(other); + Assert.True(actual); + } + + #endregion + + #region HasZeroHash + + [Fact] + public void HasZeroHash_Disk_NoHash_True() + { + DictionaryBase self = new Disk(); + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Disk_NonZeroHash_False() + { + DictionaryBase self = new Disk + { + [Disk.MD5Key] = "XXXXXX", + [Disk.SHA1Key] = "XXXXXX", + }; + + bool actual = self.HasZeroHash(); + Assert.False(actual); + } + + [Fact] + public void HasZeroHash_Disk_ZeroMD5_True() + { + DictionaryBase self = new Disk + { + [Disk.MD5Key] = ZeroHash.MD5Str, + [Disk.SHA1Key] = string.Empty, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Disk_ZeroSHA1_True() + { + DictionaryBase self = new Disk + { + [Disk.MD5Key] = string.Empty, + [Disk.SHA1Key] = ZeroHash.SHA1Str, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Disk_ZeroAll_True() + { + DictionaryBase self = new Disk + { + [Disk.MD5Key] = ZeroHash.MD5Str, + [Disk.SHA1Key] = ZeroHash.SHA1Str, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Media_NoHash_True() + { + DictionaryBase self = new Media(); + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Media_NonZeroHash_False() + { + DictionaryBase self = new Media + { + [Media.MD5Key] = "XXXXXX", + [Media.SHA1Key] = "XXXXXX", + [Media.SHA256Key] = "XXXXXX", + [Media.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HasZeroHash(); + Assert.False(actual); + } + + [Fact] + public void HasZeroHash_Media_ZeroMD5_True() + { + DictionaryBase self = new Media + { + [Media.MD5Key] = ZeroHash.MD5Str, + [Media.SHA1Key] = string.Empty, + [Media.SHA256Key] = string.Empty, + [Media.SpamSumKey] = string.Empty, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Media_ZeroSHA1_True() + { + DictionaryBase self = new Media + { + [Media.MD5Key] = string.Empty, + [Media.SHA1Key] = ZeroHash.SHA1Str, + [Media.SHA256Key] = string.Empty, + [Media.SpamSumKey] = string.Empty, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Media_ZeroSHA256_True() + { + DictionaryBase self = new Media + { + [Media.MD5Key] = string.Empty, + [Media.SHA1Key] = string.Empty, + [Media.SHA256Key] = ZeroHash.SHA256Str, + [Media.SpamSumKey] = string.Empty, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Media_ZeroSpamSum_True() + { + DictionaryBase self = new Media + { + [Media.MD5Key] = string.Empty, + [Media.SHA1Key] = string.Empty, + [Media.SHA256Key] = string.Empty, + [Media.SpamSumKey] = ZeroHash.SpamSumStr, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Media_ZeroAll_True() + { + DictionaryBase self = new Media + { + [Media.MD5Key] = ZeroHash.MD5Str, + [Media.SHA1Key] = ZeroHash.SHA1Str, + [Media.SHA256Key] = ZeroHash.SHA256Str, + [Media.SpamSumKey] = ZeroHash.SpamSumStr, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Rom_NoHash_True() + { + DictionaryBase self = new Rom(); + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Rom_NonZeroHash_False() + { + DictionaryBase self = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + + bool actual = self.HasZeroHash(); + Assert.False(actual); + } + + [Fact] + public void HasZeroHash_Rom_ZeroCRC_True() + { + DictionaryBase self = new Rom + { + [Rom.CRCKey] = ZeroHash.CRC32Str, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Rom_ZeroMD2_True() + { + DictionaryBase self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = ZeroHash.GetString(HashType.MD2), + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Rom_ZeroMD4_True() + { + DictionaryBase self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = ZeroHash.GetString(HashType.MD4), + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Rom_ZeroMD5_True() + { + DictionaryBase self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = ZeroHash.MD5Str, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Rom_ZeroRIPEMD128_True() + { + DictionaryBase self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = ZeroHash.GetString(HashType.RIPEMD128), + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Rom_ZeroRIPEMD160_True() + { + DictionaryBase self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = ZeroHash.GetString(HashType.RIPEMD160), + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Rom_ZeroSHA1_True() + { + DictionaryBase self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = ZeroHash.SHA1Str, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Rom_ZeroSHA256_True() + { + DictionaryBase self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = ZeroHash.SHA256Str, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Rom_ZeroSHA384_True() + { + DictionaryBase self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = ZeroHash.SHA384Str, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = string.Empty, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Rom_ZeroSHA512_True() + { + DictionaryBase self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = ZeroHash.SHA512Str, + [Rom.SpamSumKey] = string.Empty, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Rom_ZeroSpamSum_True() + { + DictionaryBase self = new Rom + { + [Rom.CRCKey] = string.Empty, + [Rom.MD2Key] = string.Empty, + [Rom.MD4Key] = string.Empty, + [Rom.MD5Key] = string.Empty, + [Rom.RIPEMD128Key] = string.Empty, + [Rom.RIPEMD160Key] = string.Empty, + [Rom.SHA1Key] = string.Empty, + [Rom.SHA256Key] = string.Empty, + [Rom.SHA384Key] = string.Empty, + [Rom.SHA512Key] = string.Empty, + [Rom.SpamSumKey] = ZeroHash.SpamSumStr, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Rom_ZeroAll_True() + { + DictionaryBase self = new Rom + { + [Rom.CRCKey] = ZeroHash.CRC32Str, + [Rom.MD2Key] = ZeroHash.GetString(HashType.MD2), + [Rom.MD4Key] = ZeroHash.GetString(HashType.MD4), + [Rom.MD5Key] = ZeroHash.MD5Str, + [Rom.RIPEMD128Key] = ZeroHash.GetString(HashType.RIPEMD128), + [Rom.RIPEMD160Key] = ZeroHash.GetString(HashType.RIPEMD160), + [Rom.SHA1Key] = ZeroHash.SHA1Str, + [Rom.SHA256Key] = ZeroHash.SHA256Str, + [Rom.SHA384Key] = ZeroHash.SHA384Str, + [Rom.SHA512Key] = ZeroHash.SHA512Str, + [Rom.SpamSumKey] = ZeroHash.SpamSumStr, + }; + + bool actual = self.HasZeroHash(); + Assert.True(actual); + } + + [Fact] + public void HasZeroHash_Other_False() + { + DictionaryBase self = new Sample(); + bool actual = self.HasZeroHash(); + Assert.False(actual); + } + + #endregion + + #region FillMissingHashes + + [Fact] + public void FillMissingHashes_Disk_BothEmpty() + { + DictionaryBase self = new Disk(); + DictionaryBase other = new Disk(); + + self.FillMissingHashes(other); + Assert.Single(self); + } + + [Fact] + public void FillMissingHashes_Disk_AllMissing() + { + DictionaryBase self = new Disk(); + DictionaryBase other = new Disk + { + [Disk.MD5Key] = "XXXXXX", + [Disk.SHA1Key] = "XXXXXX", + }; + + self.FillMissingHashes(other); + } + + [Fact] + public void FillMissingHashes_Media_BothEmpty() + { + DictionaryBase self = new Media(); + DictionaryBase other = new Media(); + self.FillMissingHashes(other); + Assert.Single(self); + } + + [Fact] + public void FillMissingHashes_Media_AllMissing() + { + DictionaryBase self = new Media(); + DictionaryBase other = new Media + { + [Media.MD5Key] = "XXXXXX", + [Media.SHA1Key] = "XXXXXX", + [Media.SHA256Key] = "XXXXXX", + [Media.SpamSumKey] = "XXXXXX", + }; + + self.FillMissingHashes(other); + } + + [Fact] + public void FillMissingHashes_Rom_BothEmpty() + { + DictionaryBase self = new Rom(); + DictionaryBase other = new Rom(); + self.FillMissingHashes(other); + Assert.Single(self); + } + + [Fact] + public void FillMissingHashes_Rom_AllMissing() + { + DictionaryBase self = new Rom(); + DictionaryBase other = new Rom + { + [Rom.CRCKey] = "XXXXXX", + [Rom.MD2Key] = "XXXXXX", + [Rom.MD4Key] = "XXXXXX", + [Rom.MD5Key] = "XXXXXX", + [Rom.RIPEMD128Key] = "XXXXXX", + [Rom.RIPEMD160Key] = "XXXXXX", + [Rom.SHA1Key] = "XXXXXX", + [Rom.SHA256Key] = "XXXXXX", + [Rom.SHA384Key] = "XXXXXX", + [Rom.SHA512Key] = "XXXXXX", + [Rom.SpamSumKey] = "XXXXXX", + }; + + self.FillMissingHashes(other); + } + + #endregion + } +} diff --git a/SabreTools.Data.Extensions/MetadataExtensions.cs b/SabreTools.Data.Extensions/MetadataExtensions.cs new file mode 100644 index 00000000..10f30948 --- /dev/null +++ b/SabreTools.Data.Extensions/MetadataExtensions.cs @@ -0,0 +1,842 @@ +using System; +using SabreTools.Data.Models.Metadata; +using SabreTools.Hashing; +using SabreTools.IO.Extensions; + +namespace SabreTools.Data.Extensions +{ + public static class MetadataExtensions + { + #region Accessors + + /// + /// Gets the name to use for a DictionaryBase or null + /// + public static string? GetName(this DictionaryBase self) + { + if (self is null) + return null; + + return self switch + { + Machine => self.ReadString(Machine.NameKey), + + Adjuster => self.ReadString(Adjuster.NameKey), + Analog => null, + Archive => self.ReadString(Archive.NameKey), + BiosSet => self.ReadString(BiosSet.NameKey), + Chip => self.ReadString(Chip.NameKey), + Condition => null, + ConfLocation => self.ReadString(ConfLocation.NameKey), + ConfSetting => self.ReadString(ConfSetting.NameKey), + Configuration => self.ReadString(Configuration.NameKey), + Control => null, + DataArea => self.ReadString(DataArea.NameKey), + Device => null, + DeviceRef => self.ReadString(DeviceRef.NameKey), + DipLocation => self.ReadString(DipLocation.NameKey), + DipSwitch => self.ReadString(DipSwitch.NameKey), + DipValue => self.ReadString(DipValue.NameKey), + Disk => self.ReadString(Disk.NameKey), + DiskArea => self.ReadString(DiskArea.NameKey), + Display => null, + Driver => null, + Extension => self.ReadString(Extension.NameKey), + Feature => self.ReadString(Feature.NameKey), + Info => self.ReadString(Info.NameKey), + Input => null, + Instance => self.ReadString(Instance.NameKey), + Media => self.ReadString(Media.NameKey), + Part => self.ReadString(Part.NameKey), + Port => null, + RamOption => self.ReadString(RamOption.NameKey), + Release => self.ReadString(Release.NameKey), + Rom => self.ReadString(Rom.NameKey), + Sample => self.ReadString(Sample.NameKey), + SharedFeat => self.ReadString(SharedFeat.NameKey), + Slot => self.ReadString(Slot.NameKey), + SlotOption => self.ReadString(SlotOption.NameKey), + SoftwareList => self.ReadString(SoftwareList.NameKey), + Sound => null, + + _ => null, + }; + } + + /// + /// Gets the name to use for a DictionaryBase or null + /// + public static void SetName(this DictionaryBase self, string? name) + { + if (self is null || string.IsNullOrEmpty(name)) + return; + + switch (self) + { + case Machine: self[Machine.NameKey] = name; break; + + case Adjuster: self[Adjuster.NameKey] = name; break; + case Analog: break; + case Archive: self[Archive.NameKey] = name; break; + case BiosSet: self[BiosSet.NameKey] = name; break; + case Chip: self[Chip.NameKey] = name; break; + case Condition: break; + case ConfLocation: self[ConfLocation.NameKey] = name; break; + case ConfSetting: self[ConfSetting.NameKey] = name; break; + case Configuration: self[Configuration.NameKey] = name; break; + case Control: break; + case DataArea: self[DataArea.NameKey] = name; break; + case Device: break; + case DeviceRef: self[DeviceRef.NameKey] = name; break; + case DipLocation: self[DipLocation.NameKey] = name; break; + case DipSwitch: self[DipSwitch.NameKey] = name; break; + case DipValue: self[DipValue.NameKey] = name; break; + case Disk: self[Disk.NameKey] = name; break; + case DiskArea: self[DiskArea.NameKey] = name; break; + case Display: break; + case Driver: break; + case Extension: self[Extension.NameKey] = name; break; + case Feature: self[Feature.NameKey] = name; break; + case Info: self[Info.NameKey] = name; break; + case Input: break; + case Instance: self[Instance.NameKey] = name; break; + case Media: self[Media.NameKey] = name; break; + case Part: self[Part.NameKey] = name; break; + case Port: break; + case RamOption: self[RamOption.NameKey] = name; break; + case Release: self[Release.NameKey] = name; break; + case Rom: self[Rom.NameKey] = name; break; + case Sample: self[Sample.NameKey] = name; break; + case SharedFeat: self[SharedFeat.NameKey] = name; break; + case Slot: self[Slot.NameKey] = name; break; + case SlotOption: self[SlotOption.NameKey] = name; break; + case SoftwareList: self[SoftwareList.NameKey] = name; break; + case Sound: break; + + default: break; + } + } + + #endregion + + #region Cloning + + /// + /// Deep clone a DictionaryBase object + /// + public static DictionaryBase? Clone(this DictionaryBase self) + { + // If construction failed, we can't do anything + if (Activator.CreateInstance(self.GetType()) is not DictionaryBase clone) + return null; + + // Loop through and clone per type + foreach (string key in self.Keys) + { + object? value = self[key]; + clone[key] = value switch + { + // Primative types + bool or long or double or string => value, + + // DictionaryBase types + DictionaryBase db => db.Clone(), + + // Enumerable types + byte[] bytArr => bytArr.Clone(), + string[] strArr => strArr.Clone(), + DictionaryBase[] dbArr => Array.ConvertAll(dbArr, Clone), + ICloneable[] clArr => Array.ConvertAll(clArr, cl => cl.Clone()), + + // Everything else just copies + _ => value, + }; + } + + return clone; + } + + #endregion + + #region Conversion + + /// + /// Convert a DictionaryBase to a Rom + /// + public static Rom? ConvertToRom(this DictionaryBase? self) + { + // If the DatItem is missing, we can't do anything + if (self is null) + return null; + + return self switch + { + Disk diskSelf => ConvertToRom(diskSelf), + Media mediaSelf => ConvertToRom(mediaSelf), + _ => null, + }; + } + + /// + /// Convert a Disk to a Rom + /// + private static Rom? ConvertToRom(this Disk? disk) + { + // If the Disk is missing, we can't do anything + if (disk is null) + return null; + + // Append a suffix to the name + string? name = disk.ReadString(Disk.NameKey); + if (name is not null) + name += ".chd"; + + return new Rom + { + [Rom.NameKey] = name, + [Rom.MergeKey] = disk.ReadString(Disk.MergeKey), + [Rom.RegionKey] = disk.ReadString(Disk.RegionKey), + [Rom.StatusKey] = disk.ReadString(Disk.StatusKey), + [Rom.OptionalKey] = disk.ReadString(Disk.OptionalKey), + [Rom.MD5Key] = disk.ReadString(Disk.MD5Key), + [Rom.SHA1Key] = disk.ReadString(Disk.SHA1Key), + }; + } + + /// + /// Convert a Media to a Rom + /// + private static Rom? ConvertToRom(this Media? media) + { + // If the Media is missing, we can't do anything + if (media is null) + return null; + + // Append a suffix to the name + string? name = media.ReadString(Media.NameKey); + if (name is not null) + name += ".aaruf"; + + return new Rom + { + [Rom.NameKey] = name, + [Rom.MD5Key] = media.ReadString(Media.MD5Key), + [Rom.SHA1Key] = media.ReadString(Media.SHA1Key), + [Rom.SHA256Key] = media.ReadString(Media.SHA256Key), + [Rom.SpamSumKey] = media.ReadString(Media.SpamSumKey), + }; + } + + #endregion + + #region Hash Checking + + /// + /// Determine if two hashes are equal for the purposes of merging + /// + public static bool ConditionalHashEquals(byte[]? firstHash, byte[]? secondHash) + { + // If either hash is empty, we say they're equal for merging + if (firstHash.IsNullOrEmpty() || secondHash.IsNullOrEmpty()) + return true; + + // If they're different sizes, they can't match + if (firstHash!.Length != secondHash!.Length) + return false; + + // Otherwise, they need to match exactly + return firstHash.EqualsExactly(secondHash); + } + + /// + /// Determine if two hashes are equal for the purposes of merging + /// + public static bool ConditionalHashEquals(string? firstHash, string? secondHash) + { + // If either hash is empty, we say they're equal for merging + if (string.IsNullOrEmpty(firstHash) || string.IsNullOrEmpty(secondHash)) + return true; + + // If they're different sizes, they can't match + if (firstHash!.Length != secondHash!.Length) + return false; + + // Otherwise, they need to match exactly + return string.Equals(firstHash, secondHash, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Returns if any hashes are common + /// + public static bool HashMatch(this Disk self, Disk other) + { + // If either have no hashes, we return false, otherwise this would be a false positive + if (!self.HasHashes() || !other.HasHashes()) + return false; + + // If neither have hashes in common, we return false, otherwise this would be a false positive + if (!self.HasCommonHash(other)) + return false; + + // Return if all hashes match according to merge rules + string? selfMd5 = self.ReadString(Disk.MD5Key); + string? otherMd5 = other.ReadString(Disk.MD5Key); + bool conditionalMd5 = ConditionalHashEquals(selfMd5, otherMd5); + + string? selfSha1 = self.ReadString(Disk.SHA1Key); + string? otherSha1 = other.ReadString(Disk.SHA1Key); + bool conditionalSha1 = ConditionalHashEquals(selfSha1, otherSha1); + + return conditionalMd5 + && conditionalSha1; + } + + /// + /// Returns if any hashes are common + /// + public static bool HashMatch(this Media self, Media other) + { + // If either have no hashes, we return false, otherwise this would be a false positive + if (!self.HasHashes() || !other.HasHashes()) + return false; + + // If neither have hashes in common, we return false, otherwise this would be a false positive + if (!self.HasCommonHash(other)) + return false; + + // Return if all hashes match according to merge rules + string? selfMd5 = self.ReadString(Media.MD5Key); + string? otherMd5 = other.ReadString(Media.MD5Key); + bool conditionalMd5 = ConditionalHashEquals(selfMd5, otherMd5); + + string? selfSha1 = self.ReadString(Media.SHA1Key); + string? otherSha1 = other.ReadString(Media.SHA1Key); + bool conditionalSha1 = ConditionalHashEquals(selfSha1, otherSha1); + + string? selfSha256 = self.ReadString(Media.SHA256Key); + string? otherSha256 = other.ReadString(Media.SHA256Key); + bool conditionalSha256 = ConditionalHashEquals(selfSha256, otherSha256); + + string? selfSpamSum = self.ReadString(Media.SpamSumKey); + string? otherSpamSum = other.ReadString(Media.SpamSumKey); + bool conditionalSpamSum = ConditionalHashEquals(selfSpamSum, otherSpamSum); + + return conditionalMd5 + && conditionalSha1 + && conditionalSha256 + && conditionalSpamSum; + } + + /// + /// Returns if any hashes are common + /// + public static bool HashMatch(this Rom self, Rom other) + { + // If either have no hashes, we return false, otherwise this would be a false positive + if (!self.HasHashes() || !other.HasHashes()) + return false; + + // If neither have hashes in common, we return false, otherwise this would be a false positive + if (!self.HasCommonHash(other)) + return false; + + // Return if all hashes match according to merge rules + string? selfCrc = self.ReadString(Rom.CRCKey); + string? otherCrc = other.ReadString(Rom.CRCKey); + bool conditionalCrc = ConditionalHashEquals(selfCrc, otherCrc); + + string? selfMd2 = self.ReadString(Rom.MD2Key); + string? otherMd2 = other.ReadString(Rom.MD2Key); + bool conditionalMd2 = ConditionalHashEquals(selfMd2, otherMd2); + + string? selfMd4 = self.ReadString(Rom.MD4Key); + string? otherMd4 = other.ReadString(Rom.MD4Key); + bool conditionalMd4 = ConditionalHashEquals(selfMd4, otherMd4); + + string? selfMd5 = self.ReadString(Rom.MD5Key); + string? otherMd5 = other.ReadString(Rom.MD5Key); + bool conditionalMd5 = ConditionalHashEquals(selfMd5, otherMd5); + + string? selfRipeMD128 = self.ReadString(Rom.RIPEMD128Key); + string? otherRipeMD128 = other.ReadString(Rom.RIPEMD128Key); + bool conditionaRipeMD128 = ConditionalHashEquals(selfRipeMD128, otherRipeMD128); + + string? selfRipeMD160 = self.ReadString(Rom.RIPEMD160Key); + string? otherRipeMD160 = other.ReadString(Rom.RIPEMD160Key); + bool conditionaRipeMD160 = ConditionalHashEquals(selfRipeMD160, otherRipeMD160); + + string? selfSha1 = self.ReadString(Rom.SHA1Key); + string? otherSha1 = other.ReadString(Rom.SHA1Key); + bool conditionalSha1 = ConditionalHashEquals(selfSha1, otherSha1); + + string? selfSha256 = self.ReadString(Rom.SHA256Key); + string? otherSha256 = other.ReadString(Rom.SHA256Key); + bool conditionalSha256 = ConditionalHashEquals(selfSha256, otherSha256); + + string? selfSha384 = self.ReadString(Rom.SHA384Key); + string? otherSha384 = other.ReadString(Rom.SHA384Key); + bool conditionalSha384 = ConditionalHashEquals(selfSha384, otherSha384); + + string? selfSha512 = self.ReadString(Rom.SHA512Key); + string? otherSha512 = other.ReadString(Rom.SHA512Key); + bool conditionalSha512 = ConditionalHashEquals(selfSha512, otherSha512); + + string? selfSpamSum = self.ReadString(Rom.SpamSumKey); + string? otherSpamSum = other.ReadString(Rom.SpamSumKey); + bool conditionalSpamSum = ConditionalHashEquals(selfSpamSum, otherSpamSum); + + return conditionalCrc + && conditionalMd2 + && conditionalMd4 + && conditionalMd5 + && conditionaRipeMD128 + && conditionaRipeMD160 + && conditionalSha1 + && conditionalSha256 + && conditionalSha384 + && conditionalSha512 + && conditionalSpamSum; + } + + /// + /// Returns if any hashes exist + /// + public static bool HasHashes(this DictionaryBase self) + { + return self switch + { + Disk diskSelf => diskSelf.HasHashes(), + Media mediaSelf => mediaSelf.HasHashes(), + Rom romSelf => romSelf.HasHashes(), + _ => false, + }; + } + + /// + /// Returns if all of the hashes are set to their 0-byte values or null + /// + public static bool HasZeroHash(this DictionaryBase self) + { + return self switch + { + Disk diskSelf => diskSelf.HasZeroHash(), + Media mediaSelf => mediaSelf.HasZeroHash(), + Rom romSelf => romSelf.HasZeroHash(), + _ => false, + }; + } + + /// + /// Returns if there are no, non-empty hashes in common + /// + private static bool HasCommonHash(this Disk self, Disk other) + { + bool md5Null = string.IsNullOrEmpty(self.ReadString(Disk.MD5Key)); + md5Null ^= string.IsNullOrEmpty(other.ReadString(Disk.MD5Key)); + + bool sha1Null = string.IsNullOrEmpty(self.ReadString(Disk.SHA1Key)); + sha1Null ^= string.IsNullOrEmpty(other.ReadString(Disk.SHA1Key)); + + return !md5Null + || !sha1Null; + } + + /// + /// Returns if there are no, non-empty hashes in common + /// + private static bool HasCommonHash(this Media self, Media other) + { + bool md5Null = string.IsNullOrEmpty(self.ReadString(Media.MD5Key)); + md5Null ^= string.IsNullOrEmpty(other.ReadString(Media.MD5Key)); + + bool sha1Null = string.IsNullOrEmpty(self.ReadString(Media.SHA1Key)); + sha1Null ^= string.IsNullOrEmpty(other.ReadString(Media.SHA1Key)); + + bool sha256Null = string.IsNullOrEmpty(self.ReadString(Media.SHA256Key)); + sha256Null ^= string.IsNullOrEmpty(other.ReadString(Media.SHA256Key)); + + bool spamsumNull = string.IsNullOrEmpty(self.ReadString(Media.SpamSumKey)); + spamsumNull ^= string.IsNullOrEmpty(other.ReadString(Media.SpamSumKey)); + + return !md5Null + || !sha1Null + || !sha256Null + || !spamsumNull; + } + + /// + /// Returns if there are no, non-empty hashes in common + /// + private static bool HasCommonHash(this Rom self, Rom other) + { + bool crcNull = string.IsNullOrEmpty(self.ReadString(Rom.CRCKey)); + crcNull ^= string.IsNullOrEmpty(other.ReadString(Rom.CRCKey)); + + bool md2Null = string.IsNullOrEmpty(self.ReadString(Rom.MD2Key)); + md2Null ^= string.IsNullOrEmpty(other.ReadString(Rom.MD2Key)); + + bool md4Null = string.IsNullOrEmpty(self.ReadString(Rom.MD4Key)); + md4Null ^= string.IsNullOrEmpty(other.ReadString(Rom.MD4Key)); + + bool md5Null = string.IsNullOrEmpty(self.ReadString(Rom.MD5Key)); + md5Null ^= string.IsNullOrEmpty(other.ReadString(Rom.MD5Key)); + + bool ripeMD128Null = string.IsNullOrEmpty(self.ReadString(Rom.RIPEMD128Key)); + ripeMD128Null ^= string.IsNullOrEmpty(other.ReadString(Rom.RIPEMD128Key)); + + bool ripeMD160Null = string.IsNullOrEmpty(self.ReadString(Rom.RIPEMD160Key)); + ripeMD160Null ^= string.IsNullOrEmpty(other.ReadString(Rom.RIPEMD160Key)); + + bool sha1Null = string.IsNullOrEmpty(self.ReadString(Rom.SHA1Key)); + sha1Null ^= string.IsNullOrEmpty(other.ReadString(Rom.SHA1Key)); + + bool sha256Null = string.IsNullOrEmpty(self.ReadString(Rom.SHA256Key)); + sha256Null ^= string.IsNullOrEmpty(other.ReadString(Rom.SHA256Key)); + + bool sha384Null = string.IsNullOrEmpty(self.ReadString(Rom.SHA384Key)); + sha384Null ^= string.IsNullOrEmpty(other.ReadString(Rom.SHA384Key)); + + bool sha512Null = string.IsNullOrEmpty(self.ReadString(Rom.SHA512Key)); + sha512Null ^= string.IsNullOrEmpty(other.ReadString(Rom.SHA512Key)); + + bool spamsumNull = string.IsNullOrEmpty(self.ReadString(Rom.SpamSumKey)); + spamsumNull ^= string.IsNullOrEmpty(other.ReadString(Rom.SpamSumKey)); + + return !crcNull + || !md2Null + || !md4Null + || !md5Null + || !ripeMD128Null + || !ripeMD160Null + || !sha1Null + || !sha256Null + || !sha384Null + || !sha512Null + || !spamsumNull; + } + + /// + /// Returns if any hashes exist + /// + private static bool HasHashes(this Disk disk) + { + bool md5Null = string.IsNullOrEmpty(disk.ReadString(Disk.MD5Key)); + bool sha1Null = string.IsNullOrEmpty(disk.ReadString(Disk.SHA1Key)); + + return !md5Null + || !sha1Null; + } + + /// + /// Returns if any hashes exist + /// + private static bool HasHashes(this Media media) + { + bool md5Null = string.IsNullOrEmpty(media.ReadString(Media.MD5Key)); + bool sha1Null = string.IsNullOrEmpty(media.ReadString(Media.SHA1Key)); + bool sha256Null = string.IsNullOrEmpty(media.ReadString(Media.SHA256Key)); + bool spamsumNull = string.IsNullOrEmpty(media.ReadString(Media.SpamSumKey)); + + return !md5Null + || !sha1Null + || !sha256Null + || !spamsumNull; + } + + /// + /// Returns if any hashes exist + /// + private static bool HasHashes(this Rom rom) + { + bool crcNull = string.IsNullOrEmpty(rom.ReadString(Rom.CRCKey)); + bool md2Null = string.IsNullOrEmpty(rom.ReadString(Rom.MD2Key)); + bool md4Null = string.IsNullOrEmpty(rom.ReadString(Rom.MD4Key)); + bool md5Null = string.IsNullOrEmpty(rom.ReadString(Rom.MD5Key)); + bool ripeMD128Null = string.IsNullOrEmpty(rom.ReadString(Rom.RIPEMD128Key)); + bool ripeMD160Null = string.IsNullOrEmpty(rom.ReadString(Rom.RIPEMD160Key)); + bool sha1Null = string.IsNullOrEmpty(rom.ReadString(Rom.SHA1Key)); + bool sha256Null = string.IsNullOrEmpty(rom.ReadString(Rom.SHA256Key)); + bool sha384Null = string.IsNullOrEmpty(rom.ReadString(Rom.SHA384Key)); + bool sha512Null = string.IsNullOrEmpty(rom.ReadString(Rom.SHA512Key)); + bool spamsumNull = string.IsNullOrEmpty(rom.ReadString(Rom.SpamSumKey)); + + return !crcNull + || !md2Null + || !md4Null + || !md5Null + || !ripeMD128Null + || !ripeMD160Null + || !sha1Null + || !sha256Null + || !sha384Null + || !sha512Null + || !spamsumNull; + } + + /// + /// Returns if all of the hashes are set to their 0-byte values or null + /// + private static bool HasZeroHash(this Disk disk) + { + string? md5 = disk.ReadString(Disk.MD5Key); + bool md5Null = string.IsNullOrEmpty(md5) || string.Equals(md5, ZeroHash.MD5Str, StringComparison.OrdinalIgnoreCase); + + string? sha1 = disk.ReadString(Disk.SHA1Key); + bool sha1Null = string.IsNullOrEmpty(sha1) || string.Equals(sha1, ZeroHash.SHA1Str, StringComparison.OrdinalIgnoreCase); + + return md5Null + && sha1Null; + } + + /// + /// Returns if all of the hashes are set to their 0-byte values or null + /// + private static bool HasZeroHash(this Media media) + { + string? md5 = media.ReadString(Media.MD5Key); + bool md5Null = string.IsNullOrEmpty(md5) || string.Equals(md5, ZeroHash.MD5Str, StringComparison.OrdinalIgnoreCase); + + string? sha1 = media.ReadString(Media.SHA1Key); + bool sha1Null = string.IsNullOrEmpty(sha1) || string.Equals(sha1, ZeroHash.SHA1Str, StringComparison.OrdinalIgnoreCase); + + string? sha256 = media.ReadString(Media.SHA256Key); + bool sha256Null = string.IsNullOrEmpty(sha256) || string.Equals(sha256, ZeroHash.SHA256Str, StringComparison.OrdinalIgnoreCase); + + string? spamsum = media.ReadString(Media.SpamSumKey); + bool spamsumNull = string.IsNullOrEmpty(spamsum) || string.Equals(spamsum, ZeroHash.SpamSumStr, StringComparison.OrdinalIgnoreCase); + + return md5Null + && sha1Null + && sha256Null + && spamsumNull; + } + + /// + /// Returns if all of the hashes are set to their 0-byte values or null + /// + private static bool HasZeroHash(this Rom rom) + { + string? crc = rom.ReadString(Rom.CRCKey); + bool crcNull = string.IsNullOrEmpty(crc) || string.Equals(crc, ZeroHash.CRC32Str, StringComparison.OrdinalIgnoreCase); + + string? md2 = rom.ReadString(Rom.MD2Key); + bool md2Null = string.IsNullOrEmpty(md2) || string.Equals(md2, ZeroHash.GetString(HashType.MD2), StringComparison.OrdinalIgnoreCase); + + string? md4 = rom.ReadString(Rom.MD4Key); + bool md4Null = string.IsNullOrEmpty(md4) || string.Equals(md4, ZeroHash.GetString(HashType.MD4), StringComparison.OrdinalIgnoreCase); + + string? md5 = rom.ReadString(Rom.MD5Key); + bool md5Null = string.IsNullOrEmpty(md5) || string.Equals(md5, ZeroHash.MD5Str, StringComparison.OrdinalIgnoreCase); + + string? ripeMD128 = rom.ReadString(Rom.RIPEMD128Key); + bool ripeMD128Null = string.IsNullOrEmpty(value: ripeMD128) || string.Equals(ripeMD128, ZeroHash.GetString(HashType.RIPEMD128), StringComparison.OrdinalIgnoreCase); + + string? ripeMD160 = rom.ReadString(Rom.RIPEMD160Key); + bool ripeMD160Null = string.IsNullOrEmpty(ripeMD160) || string.Equals(ripeMD160, ZeroHash.GetString(HashType.RIPEMD160), StringComparison.OrdinalIgnoreCase); + + string? sha1 = rom.ReadString(Rom.SHA1Key); + bool sha1Null = string.IsNullOrEmpty(sha1) || string.Equals(sha1, ZeroHash.SHA1Str, StringComparison.OrdinalIgnoreCase); + + string? sha256 = rom.ReadString(Rom.SHA256Key); + bool sha256Null = string.IsNullOrEmpty(sha256) || string.Equals(sha256, ZeroHash.SHA256Str, StringComparison.OrdinalIgnoreCase); + + string? sha384 = rom.ReadString(Rom.SHA384Key); + bool sha384Null = string.IsNullOrEmpty(sha384) || string.Equals(sha384, ZeroHash.SHA384Str, StringComparison.OrdinalIgnoreCase); + + string? sha512 = rom.ReadString(Rom.SHA512Key); + bool sha512Null = string.IsNullOrEmpty(sha512) || string.Equals(sha512, ZeroHash.SHA512Str, StringComparison.OrdinalIgnoreCase); + + string? spamsum = rom.ReadString(Rom.SpamSumKey); + bool spamsumNull = string.IsNullOrEmpty(spamsum) || string.Equals(spamsum, ZeroHash.SpamSumStr, StringComparison.OrdinalIgnoreCase); + + return crcNull + && md2Null + && md4Null + && md5Null + && ripeMD128Null + && ripeMD160Null + && sha1Null + && sha256Null + && sha384Null + && sha512Null + && spamsumNull; + } + + #endregion + + #region Information Filling + + /// + /// Fill any missing size and hash information from another DictionaryBase + /// + public static void FillMissingHashes(this DictionaryBase? self, DictionaryBase? other) + { + if (self is null || other is null) + return; + +#if NETCOREAPP || NETSTANDARD2_0_OR_GREATER + switch (self, other) + { + case (Disk diskSelf, Disk diskOther): + diskSelf.FillMissingHashes(diskOther); + break; + case (Media mediaSelf, Media mediaOther): + mediaSelf.FillMissingHashes(mediaOther); + break; + case (Rom romSelf, Rom romOther): + romSelf.FillMissingHashes(romOther); + break; + + default: + break; + } +#else + if (self is Disk diskSelf && other is Disk diskOther) + diskSelf.FillMissingHashes(diskOther); + else if (self is Media mediaSelf && other is Media mediaOther) + mediaSelf.FillMissingHashes(mediaOther); + else if (self is Rom romSelf && other is Rom romOther) + romSelf.FillMissingHashes(romOther); +#endif + } + + /// + /// Fill any missing size and hash information from another Disk + /// + private static void FillMissingHashes(this Disk? self, Disk? other) + { + if (self is null || other is null) + return; + + string? selfMd5 = self.ReadString(Disk.MD5Key); + string? otherMd5 = other.ReadString(Disk.MD5Key); + if (string.IsNullOrEmpty(selfMd5) && !string.IsNullOrEmpty(otherMd5)) + self[Disk.MD5Key] = otherMd5; + + string? selfSha1 = self.ReadString(Disk.SHA1Key); + string? otherSha1 = other.ReadString(Disk.SHA1Key); + if (string.IsNullOrEmpty(selfSha1) && !string.IsNullOrEmpty(otherSha1)) + self[Disk.SHA1Key] = otherSha1; + } + + /// + /// Fill any missing size and hash information from another Media + /// + private static void FillMissingHashes(this Media? self, Media? other) + { + if (self is null || other is null) + return; + + string? selfMd5 = self.ReadString(Media.MD5Key); + string? otherMd5 = other.ReadString(Media.MD5Key); + if (string.IsNullOrEmpty(selfMd5) && !string.IsNullOrEmpty(otherMd5)) + self[Media.MD5Key] = otherMd5; + + string? selfSha1 = self.ReadString(Media.SHA1Key); + string? otherSha1 = other.ReadString(Media.SHA1Key); + if (string.IsNullOrEmpty(selfSha1) && !string.IsNullOrEmpty(otherSha1)) + self[Media.SHA1Key] = otherSha1; + + string? selfSha256 = self.ReadString(Media.SHA256Key); + string? otherSha256 = other.ReadString(Media.SHA256Key); + if (string.IsNullOrEmpty(selfSha256) && !string.IsNullOrEmpty(otherSha256)) + self[Media.SHA256Key] = otherSha256; + + string? selfSpamSum = self.ReadString(Media.SpamSumKey); + string? otherSpamSum = other.ReadString(Media.SpamSumKey); + if (string.IsNullOrEmpty(selfSpamSum) && !string.IsNullOrEmpty(otherSpamSum)) + self[Media.SpamSumKey] = otherSpamSum; + } + + /// + /// Fill any missing size and hash information from another Rom + /// + private static void FillMissingHashes(this Rom? self, Rom? other) + { + if (self is null || other is null) + return; + + long? selfSize = self.ReadLong(Rom.SizeKey); + long? otherSize = other.ReadLong(Rom.SizeKey); + if (selfSize is null && otherSize is not null) + self[Rom.SizeKey] = otherSize; + + string? selfCrc = self.ReadString(Rom.CRCKey); + string? otherCrc = other.ReadString(Rom.CRCKey); + if (string.IsNullOrEmpty(selfCrc) && !string.IsNullOrEmpty(otherCrc)) + self[Rom.CRCKey] = otherCrc; + + string? selfMd2 = self.ReadString(Rom.MD2Key); + string? otherMd2 = other.ReadString(Rom.MD2Key); + if (string.IsNullOrEmpty(selfMd2) && !string.IsNullOrEmpty(otherMd2)) + self[Rom.MD2Key] = otherMd2; + + string? selfMd4 = self.ReadString(Rom.MD4Key); + string? otherMd4 = other.ReadString(Rom.MD4Key); + if (string.IsNullOrEmpty(selfMd4) && !string.IsNullOrEmpty(otherMd4)) + self[Rom.MD4Key] = otherMd4; + + string? selfMd5 = self.ReadString(Rom.MD5Key); + string? otherMd5 = other.ReadString(Rom.MD5Key); + if (string.IsNullOrEmpty(selfMd5) && !string.IsNullOrEmpty(otherMd5)) + self[Rom.MD5Key] = otherMd5; + + string? selfRipeMD128 = self.ReadString(Rom.RIPEMD128Key); + string? otherRipeMD128 = other.ReadString(Rom.RIPEMD128Key); + if (string.IsNullOrEmpty(selfRipeMD128) && !string.IsNullOrEmpty(otherRipeMD128)) + self[Rom.RIPEMD128Key] = otherRipeMD128; + + string? selfRipeMD160 = self.ReadString(Rom.RIPEMD160Key); + string? otherRipeMD160 = other.ReadString(Rom.RIPEMD160Key); + if (string.IsNullOrEmpty(selfRipeMD160) && !string.IsNullOrEmpty(otherRipeMD160)) + self[Rom.RIPEMD160Key] = otherRipeMD160; + + string? selfSha1 = self.ReadString(Rom.SHA1Key); + string? otherSha1 = other.ReadString(Rom.SHA1Key); + if (string.IsNullOrEmpty(selfSha1) && !string.IsNullOrEmpty(otherSha1)) + self[Rom.SHA1Key] = otherSha1; + + string? selfSha256 = self.ReadString(Rom.SHA256Key); + string? otherSha256 = other.ReadString(Rom.SHA256Key); + if (string.IsNullOrEmpty(selfSha256) && !string.IsNullOrEmpty(otherSha256)) + self[Rom.SHA256Key] = otherSha256; + + string? selfSha384 = self.ReadString(Rom.SHA384Key); + string? otherSha384 = other.ReadString(Rom.SHA384Key); + if (string.IsNullOrEmpty(selfSha384) && !string.IsNullOrEmpty(otherSha384)) + self[Rom.SHA384Key] = otherSha384; + + string? selfSha512 = self.ReadString(Rom.SHA512Key); + string? otherSha512 = other.ReadString(Rom.SHA512Key); + if (string.IsNullOrEmpty(selfSha512) && !string.IsNullOrEmpty(otherSha512)) + self[Rom.SHA512Key] = otherSha512; + + string? selfSpamSum = self.ReadString(Rom.SpamSumKey); + string? otherSpamSum = other.ReadString(Rom.SpamSumKey); + if (string.IsNullOrEmpty(selfSpamSum) && !string.IsNullOrEmpty(otherSpamSum)) + self[Rom.SpamSumKey] = otherSpamSum; + } + + #endregion + + #region Reading + + /// + /// Read an item array from a given key, if possible + /// + public static T[]? ReadItemArray(this DictionaryBase self, string key) where T : DictionaryBase + { + var items = self.Read(key); + if (items == default) + { + var single = self.Read(key); + if (single != default) + items = [single]; + } + + return items; + } + + #endregion + } +}