Files
SabreTools.Serialization/SabreTools.Metadata.DatItems/Formats/Rom.cs
2026-04-09 23:56:36 -04:00

1009 lines
27 KiB
C#

using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Data.Extensions;
using SabreTools.Data.Models.Metadata;
using SabreTools.Text.Extensions;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents a generic file within a set
/// </summary>
[JsonObject("rom"), XmlRoot("rom")]
public sealed class Rom : DatItem<Data.Models.Metadata.Rom>
{
#region Properties
public string? Album
{
get => _internal.Album;
set => _internal.Album = value;
}
public string? AltRomname
{
get => _internal.AltRomname;
set => _internal.AltRomname = value;
}
public string? AltTitle
{
get => _internal.AltTitle;
set => _internal.AltTitle = value;
}
public string? Artist
{
get => _internal.Artist;
set => _internal.Artist = value;
}
public string? ASRDetectedLang
{
get => _internal.ASRDetectedLang;
set => _internal.ASRDetectedLang = value;
}
public string? ASRDetectedLangConf
{
get => _internal.ASRDetectedLangConf;
set => _internal.ASRDetectedLangConf = value;
}
public string? ASRTranscribedLang
{
get => _internal.ASRTranscribedLang;
set => _internal.ASRTranscribedLang = value;
}
public string? Bios
{
get => _internal.Bios;
set => _internal.Bios = value;
}
public string? Bitrate
{
get => _internal.Bitrate;
set => _internal.Bitrate = value;
}
public string? BitTorrentMagnetHash
{
get => _internal.BitTorrentMagnetHash;
set => _internal.BitTorrentMagnetHash = value;
}
public string? ClothCoverDetectionModuleVersion
{
get => _internal.ClothCoverDetectionModuleVersion;
set => _internal.ClothCoverDetectionModuleVersion = value;
}
public string? CollectionCatalogNumber
{
get => _internal.CollectionCatalogNumber;
set => _internal.CollectionCatalogNumber = value;
}
public string? Comment
{
get => _internal.Comment;
set => _internal.Comment = value;
}
public string? CRC16
{
get => _internal.CRC16;
set => _internal.CRC16 = value;
}
public string? CRC32
{
get => _internal.CRC32;
set => _internal.CRC32 = value;
}
public string? CRC64
{
get => _internal.CRC64;
set => _internal.CRC64 = value;
}
public string? Creator
{
get => _internal.Creator;
set => _internal.Creator = value;
}
public Endianness? DataAreaEndianness { get; set; }
public string? DataAreaName { get; set; }
public long? DataAreaSize { get; set; }
public Width? DataAreaWidth { get; set; }
public string? Date
{
get => _internal.Date;
set => _internal.Date = value;
}
public bool? Dispose
{
get => _internal.Dispose;
set => _internal.Dispose = value;
}
public string? Extension
{
get => _internal.Extension;
set => _internal.Extension = value;
}
public long? FileCount
{
get => _internal.FileCount;
set => _internal.FileCount = value;
}
public bool? FileIsAvailable
{
get => _internal.FileIsAvailable;
set => _internal.FileIsAvailable = value;
}
public string? Flags
{
get => _internal.Flags;
set => _internal.Flags = value;
}
public string? Format
{
get => _internal.Format;
set => _internal.Format = value;
}
public string? Header
{
get => _internal.Header;
set => _internal.Header = value;
}
public string? Height
{
get => _internal.Height;
set => _internal.Height = value;
}
public string? hOCRCharToWordhOCRVersion
{
get => _internal.hOCRCharToWordhOCRVersion;
set => _internal.hOCRCharToWordhOCRVersion = value;
}
public string? hOCRCharToWordModuleVersion
{
get => _internal.hOCRCharToWordModuleVersion;
set => _internal.hOCRCharToWordModuleVersion = value;
}
public string? hOCRFtsTexthOCRVersion
{
get => _internal.hOCRFtsTexthOCRVersion;
set => _internal.hOCRFtsTexthOCRVersion = value;
}
public string? hOCRFtsTextModuleVersion
{
get => _internal.hOCRFtsTextModuleVersion;
set => _internal.hOCRFtsTextModuleVersion = value;
}
public string? hOCRPageIndexhOCRVersion
{
get => _internal.hOCRPageIndexhOCRVersion;
set => _internal.hOCRPageIndexhOCRVersion = value;
}
public string? hOCRPageIndexModuleVersion
{
get => _internal.hOCRPageIndexModuleVersion;
set => _internal.hOCRPageIndexModuleVersion = value;
}
public bool? Inverted
{
get => _internal.Inverted;
set => _internal.Inverted = value;
}
public string? LastModifiedTime
{
get => _internal.LastModifiedTime;
set => _internal.LastModifiedTime = value;
}
/// <inheritdoc>/>
public override ItemType ItemType => ItemType.Rom;
public string? Length
{
get => _internal.Length;
set => _internal.Length = value;
}
public LoadFlag? LoadFlag
{
get => _internal.LoadFlag;
set => _internal.LoadFlag = value;
}
public string? MatrixNumber
{
get => _internal.MatrixNumber;
set => _internal.MatrixNumber = value;
}
public string? MD2
{
get => _internal.MD2;
set => _internal.MD2 = value;
}
public string? MD4
{
get => _internal.MD4;
set => _internal.MD4 = value;
}
public string? MD5
{
get => _internal.MD5;
set => _internal.MD5 = value;
}
public string? Merge
{
get => _internal.Merge;
set => _internal.Merge = value;
}
public bool? MIA
{
get => _internal.MIA;
set => _internal.MIA = value;
}
public string? Name
{
get => _internal.Name;
set => _internal.Name = value;
}
public string? Offset
{
get => _internal.Offset;
set => _internal.Offset = value;
}
public string? OpenMSXType
{
get => _internal.OpenMSXType;
set => _internal.OpenMSXType = value;
}
public OpenMSXSubType? OpenMSXMediaType
{
get => _internal.OpenMSXMediaType;
set => _internal.OpenMSXMediaType = value;
}
public Original? Original { get; set; }
public string? OriginalProperty
{
get => _internal.Original;
set => _internal.Original = value;
}
[JsonIgnore]
public bool OriginalSpecified => Original is not null;
public bool? Optional
{
get => _internal.Optional;
set => _internal.Optional = value;
}
public string? PartInterface { get; set; }
public string? PartName { get; set; }
public string? PDFModuleVersion
{
get => _internal.PDFModuleVersion;
set => _internal.PDFModuleVersion = value;
}
public string? PreviewImage
{
get => _internal.PreviewImage;
set => _internal.PreviewImage = value;
}
public string? Publisher
{
get => _internal.Publisher;
set => _internal.Publisher = value;
}
public string? Region
{
get => _internal.Region;
set => _internal.Region = value;
}
public string? Remark
{
get => _internal.Remark;
set => _internal.Remark = value;
}
public string? RIPEMD128
{
get => _internal.RIPEMD128;
set => _internal.RIPEMD128 = value;
}
public string? RIPEMD160
{
get => _internal.RIPEMD160;
set => _internal.RIPEMD160 = value;
}
public string? Rotation
{
get => _internal.Rotation;
set => _internal.Rotation = value;
}
public string? Serial
{
get => _internal.Serial;
set => _internal.Serial = value;
}
public string? SHA1
{
get => _internal.SHA1;
set => _internal.SHA1 = value;
}
public string? SHA256
{
get => _internal.SHA256;
set => _internal.SHA256 = value;
}
public string? SHA384
{
get => _internal.SHA384;
set => _internal.SHA384 = value;
}
public string? SHA512
{
get => _internal.SHA512;
set => _internal.SHA512 = value;
}
public long? Size
{
get => _internal.Size;
set => _internal.Size = value;
}
public bool? SoundOnly
{
get => _internal.SoundOnly;
set => _internal.SoundOnly = value;
}
public string? SourceProperty
{
get => _internal.Source;
set => _internal.Source = value;
}
public string? SpamSum
{
get => _internal.SpamSum;
set => _internal.SpamSum = value;
}
public string? Start
{
get => _internal.Start;
set => _internal.Start = value;
}
public ItemStatus? Status
{
get => _internal.Status;
set => _internal.Status = value;
}
public string? Summation
{
get => _internal.Summation;
set => _internal.Summation = value;
}
public string? TesseractOCR
{
get => _internal.TesseractOCR;
set => _internal.TesseractOCR = value;
}
public string? TesseractOCRConverted
{
get => _internal.TesseractOCRConverted;
set => _internal.TesseractOCRConverted = value;
}
public string? TesseractOCRDetectedLang
{
get => _internal.TesseractOCRDetectedLang;
set => _internal.TesseractOCRDetectedLang = value;
}
public string? TesseractOCRDetectedLangConf
{
get => _internal.TesseractOCRDetectedLangConf;
set => _internal.TesseractOCRDetectedLangConf = value;
}
public string? TesseractOCRDetectedScript
{
get => _internal.TesseractOCRDetectedScript;
set => _internal.TesseractOCRDetectedScript = value;
}
public string? TesseractOCRDetectedScriptConf
{
get => _internal.TesseractOCRDetectedScriptConf;
set => _internal.TesseractOCRDetectedScriptConf = value;
}
public string? TesseractOCRModuleVersion
{
get => _internal.TesseractOCRModuleVersion;
set => _internal.TesseractOCRModuleVersion = value;
}
public string? TesseractOCRParameters
{
get => _internal.TesseractOCRParameters;
set => _internal.TesseractOCRParameters = value;
}
public string? Title
{
get => _internal.Title;
set => _internal.Title = value;
}
public string? Track
{
get => _internal.Track;
set => _internal.Track = value;
}
public string? Value
{
get => _internal.Value;
set => _internal.Value = value;
}
public string? WhisperASRModuleVersion
{
get => _internal.WhisperASRModuleVersion;
set => _internal.WhisperASRModuleVersion = value;
}
public string? WhisperModelHash
{
get => _internal.WhisperModelHash;
set => _internal.WhisperModelHash = value;
}
public string? WhisperModelName
{
get => _internal.WhisperModelName;
set => _internal.WhisperModelName = value;
}
public string? WhisperVersion
{
get => _internal.WhisperVersion;
set => _internal.WhisperVersion = value;
}
public string? Width
{
get => _internal.Width;
set => _internal.Width = value;
}
public string? WordConfidenceInterval0To10
{
get => _internal.WordConfidenceInterval0To10;
set => _internal.WordConfidenceInterval0To10 = value;
}
public string? WordConfidenceInterval11To20
{
get => _internal.WordConfidenceInterval11To20;
set => _internal.WordConfidenceInterval11To20 = value;
}
public string? WordConfidenceInterval21To30
{
get => _internal.WordConfidenceInterval21To30;
set => _internal.WordConfidenceInterval21To30 = value;
}
public string? WordConfidenceInterval31To40
{
get => _internal.WordConfidenceInterval31To40;
set => _internal.WordConfidenceInterval31To40 = value;
}
public string? WordConfidenceInterval41To50
{
get => _internal.WordConfidenceInterval41To50;
set => _internal.WordConfidenceInterval41To50 = value;
}
public string? WordConfidenceInterval51To60
{
get => _internal.WordConfidenceInterval51To60;
set => _internal.WordConfidenceInterval51To60 = value;
}
public string? WordConfidenceInterval61To70
{
get => _internal.WordConfidenceInterval61To70;
set => _internal.WordConfidenceInterval61To70 = value;
}
public string? WordConfidenceInterval71To80
{
get => _internal.WordConfidenceInterval71To80;
set => _internal.WordConfidenceInterval71To80 = value;
}
public string? WordConfidenceInterval81To90
{
get => _internal.WordConfidenceInterval81To90;
set => _internal.WordConfidenceInterval81To90 = value;
}
public string? WordConfidenceInterval91To100
{
get => _internal.WordConfidenceInterval91To100;
set => _internal.WordConfidenceInterval91To100 = value;
}
public string? xxHash364
{
get => _internal.xxHash364;
set => _internal.xxHash364 = value;
}
public string? xxHash3128
{
get => _internal.xxHash3128;
set => _internal.xxHash3128 = value;
}
#endregion
#region Constructors
public Rom() : base()
{
DupeType = 0x00;
Status = ItemStatus.None;
}
public Rom(Dump item, Machine machine, Source source, int index)
{
// If we don't have rom data, we can't do anything
Data.Models.Metadata.Rom? rom = null;
OpenMSXSubType? subType = null;
if (item.Rom is not null)
{
rom = item.Rom;
subType = OpenMSXSubType.Rom;
}
else if (item.MegaRom is not null)
{
rom = item.MegaRom;
subType = OpenMSXSubType.MegaRom;
}
else if (item.SCCPlusCart is not null)
{
rom = item.SCCPlusCart;
subType = OpenMSXSubType.SCCPlusCart;
}
// Just return if nothing valid was found
if (rom is null)
return;
string name = $"{machine.Name}_{index++}{(!string.IsNullOrEmpty(rom!.Remark) ? $" {rom.Remark}" : string.Empty)}";
Name = name;
Offset = rom.Start;
OpenMSXMediaType = subType;
OpenMSXType = rom.ItemType.ToString();
Remark = rom.Remark;
SHA1 = rom.SHA1;
Start = rom.Start;
Source = source;
var original = item.Original;
if (original is not null)
{
Original = new Original
{
Value = original.Value,
Content = original.Content,
};
}
CopyMachineInformation(machine);
// Process hash values
// TODO: This should be normalized to CRC-16
string? crc16 = CRC16;
if (crc16 is not null)
CRC16 = NormalizeHashData(crc16, 4);
string? crc32 = CRC32;
if (crc32 is not null)
CRC32 = TextHelper.NormalizeCRC32(crc32);
// TODO: This should be normalized to CRC-64
string? crc64 = CRC64;
if (crc64 is not null)
CRC64 = NormalizeHashData(crc64, 16);
string? md2 = MD2;
if (md2 is not null)
MD2 = TextHelper.NormalizeMD2(md2);
string? md4 = MD4;
if (md4 is not null)
MD4 = TextHelper.NormalizeMD5(md4);
string? md5 = MD5;
if (md5 is not null)
MD5 = TextHelper.NormalizeMD5(md5);
string? ripemd128 = RIPEMD128;
if (ripemd128 is not null)
RIPEMD128 = TextHelper.NormalizeRIPEMD128(ripemd128);
string? ripemd160 = RIPEMD160;
if (ripemd160 is not null)
RIPEMD160 = TextHelper.NormalizeRIPEMD160(ripemd160);
string? sha1 = SHA1;
if (sha1 is not null)
SHA1 = TextHelper.NormalizeSHA1(sha1);
string? sha256 = SHA256;
if (sha256 is not null)
SHA256 = TextHelper.NormalizeSHA256(sha256);
string? sha384 = SHA384;
if (sha384 is not null)
SHA384 = TextHelper.NormalizeSHA384(sha384);
string? sha512 = SHA512;
if (sha512 is not null)
SHA512 = TextHelper.NormalizeSHA512(sha512);
}
public Rom(Data.Models.Metadata.Rom item) : base(item)
{
DupeType = 0x00;
// Process hash values
// TODO: This should be normalized to CRC-16
string? crc16 = CRC16;
if (crc16 is not null)
CRC16 = NormalizeHashData(crc16, 4);
string? crc32 = CRC32;
if (crc32 is not null)
CRC32 = TextHelper.NormalizeCRC32(crc32);
// TODO: This should be normalized to CRC-64
string? crc64 = CRC64;
if (crc64 is not null)
CRC64 = NormalizeHashData(crc64, 16);
string? md2 = MD2;
if (md2 is not null)
MD2 = TextHelper.NormalizeMD2(md2);
string? md4 = MD4;
if (md4 is not null)
MD4 = TextHelper.NormalizeMD5(md4);
string? md5 = MD5;
if (md5 is not null)
MD5 = TextHelper.NormalizeMD5(md5);
string? ripemd128 = RIPEMD128;
if (ripemd128 is not null)
RIPEMD128 = TextHelper.NormalizeRIPEMD128(ripemd128);
string? ripemd160 = RIPEMD160;
if (ripemd160 is not null)
RIPEMD160 = TextHelper.NormalizeRIPEMD160(ripemd160);
string? sha1 = SHA1;
if (sha1 is not null)
SHA1 = TextHelper.NormalizeSHA1(sha1);
string? sha256 = SHA256;
if (sha256 is not null)
SHA256 = TextHelper.NormalizeSHA256(sha256);
string? sha384 = SHA384;
if (sha384 is not null)
SHA384 = TextHelper.NormalizeSHA384(sha384);
string? sha512 = SHA512;
if (sha512 is not null)
SHA512 = TextHelper.NormalizeSHA512(sha512);
}
public Rom(Data.Models.Metadata.Rom item, Machine machine, Source source) : this(item)
{
Source = source;
CopyMachineInformation(machine);
}
/// <summary>
/// Normalize a hash string and pad to the correct size
/// </summary>
/// TODO: Remove when IO is updated
private static string? NormalizeHashData(string? hash, int expectedLength)
{
// If we have a known blank hash, return blank
if (hash is null)
return null;
else if (hash == string.Empty || hash == "-" || hash == "_")
return string.Empty;
// Check to see if it's a "hex" hash
hash = hash!.Trim().Replace("0x", string.Empty);
// If we have a blank hash now, return blank
if (string.IsNullOrEmpty(hash))
return string.Empty;
// If the hash shorter than the required length, pad it
if (hash.Length < expectedLength)
hash = hash.PadLeft(expectedLength, '0');
// If the hash is longer than the required length, it's invalid
else if (hash.Length > expectedLength)
return string.Empty;
// Now normalize the hash
hash = hash.ToLowerInvariant();
// Otherwise, make sure that every character is a proper match
for (int i = 0; i < hash.Length; i++)
{
char c = hash[i];
#if NET7_0_OR_GREATER
if (!char.IsAsciiHexDigit(c))
#else
if (!IsAsciiHexDigit(c))
#endif
{
hash = string.Empty;
break;
}
}
return hash;
}
#if NETFRAMEWORK || NETCOREAPP3_1 || NET5_0 || NET6_0 || NETSTANDARD2_0_OR_GREATER
/// <summary>
/// Indicates whether a character is categorized as an ASCII hexademical digit.
/// </summary>
/// <param name="c">The character to evaluate.</param>
/// <returns>true if c is a hexademical digit; otherwise, false.</returns>
/// <remarks>This method determines whether the character is in the range '0' through '9', inclusive, 'A' through 'F', inclusive, or 'a' through 'f', inclusive.</remarks>
/// TODO: Remove when IO is updated
internal static bool IsAsciiHexDigit(char c)
{
return char.ToLowerInvariant(c) switch
{
'0' => true,
'1' => true,
'2' => true,
'3' => true,
'4' => true,
'5' => true,
'6' => true,
'7' => true,
'8' => true,
'9' => true,
'a' => true,
'b' => true,
'c' => true,
'd' => true,
'e' => true,
'f' => true,
_ => false,
};
}
#endif
#endregion
#region Accessors
/// <inheritdoc/>
public override string? GetName() => Name;
/// <inheritdoc/>
public override void SetName(string? name) => Name = name;
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override object Clone() => new Rom(GetInternalClone());
/// <inheritdoc/>
public override Data.Models.Metadata.Rom GetInternalClone()
=> _internal.Clone() as Data.Models.Metadata.Rom ?? new();
#endregion
#region Comparision Methods
/// <inheritdoc/>
public override bool Equals(DatItem? other)
{
// If the other item is null
if (other is null)
return false;
// If the type matches
if (other is Rom otherRom)
return _internal.PartialEquals(otherRom._internal);
// Everything else fails
return false;
}
/// <summary>
/// Fill any missing size and hash information from another Rom
/// </summary>
/// <param name="other">Rom to fill information from</param>
public void FillMissingInformation(Rom other)
=> _internal.FillMissingHashes(other._internal);
/// <summary>
/// Returns if the Rom contains any hashes
/// </summary>
/// <returns>True if any hash exists, false otherwise</returns>
public bool HasHashes() => _internal.HasHashes();
/// <summary>
/// Returns if all of the hashes are set to their 0-byte values
/// </summary>
/// <returns>True if any hash matches the 0-byte value, false otherwise</returns>
public bool HasZeroHash() => _internal.HasZeroHash();
#endregion
#region Sorting and Merging
/// <inheritdoc/>
public override string GetKey(ItemKey bucketedBy, Machine? machine, Source? source, bool lower = true, bool norename = true)
{
// Set the output key as the default blank string
string? key;
#pragma warning disable IDE0010
// Now determine what the key should be based on the bucketedBy value
switch (bucketedBy)
{
case ItemKey.CRC16:
key = CRC16;
break;
case ItemKey.CRC32:
key = CRC32;
break;
case ItemKey.CRC64:
key = CRC64;
break;
case ItemKey.MD2:
key = MD2;
break;
case ItemKey.MD4:
key = MD4;
break;
case ItemKey.MD5:
key = MD5;
break;
case ItemKey.RIPEMD128:
key = RIPEMD128;
break;
case ItemKey.RIPEMD160:
key = RIPEMD160;
break;
case ItemKey.SHA1:
key = SHA1;
break;
case ItemKey.SHA256:
key = SHA256;
break;
case ItemKey.SHA384:
key = SHA384;
break;
case ItemKey.SHA512:
key = SHA512;
break;
case ItemKey.SpamSum:
key = SpamSum;
break;
// Let the base handle generic stuff
default:
return base.GetKey(bucketedBy, machine, source, lower, norename);
}
#pragma warning restore IDE0010
// Double and triple check the key for corner cases
key ??= string.Empty;
if (lower)
key = key.ToLowerInvariant();
return key;
}
#endregion
}
}