Port metadata functionality from ST

This commit is contained in:
Matt Nadareski
2026-03-24 18:03:01 -04:00
parent 5f0fdcfd8d
commit e11a08b587
138 changed files with 37585 additions and 0 deletions

View File

@@ -0,0 +1,346 @@
using System;
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Data.Extensions;
using SabreTools.Hashing;
using SabreTools.IO.Logging;
using SabreTools.Metadata.DatItems.Formats;
using SabreTools.Metadata.Filter;
namespace SabreTools.Metadata.DatItems
{
/// <summary>
/// Base class for all items included in a set
/// </summary>
[JsonObject("datitem"), XmlRoot("datitem")]
[XmlInclude(typeof(Adjuster))]
[XmlInclude(typeof(Analog))]
[XmlInclude(typeof(Archive))]
[XmlInclude(typeof(BiosSet))]
[XmlInclude(typeof(Blank))]
[XmlInclude(typeof(Chip))]
[XmlInclude(typeof(Condition))]
[XmlInclude(typeof(Configuration))]
[XmlInclude(typeof(ConfLocation))]
[XmlInclude(typeof(ConfSetting))]
[XmlInclude(typeof(Control))]
[XmlInclude(typeof(DataArea))]
[XmlInclude(typeof(Device))]
[XmlInclude(typeof(DeviceRef))]
[XmlInclude(typeof(DipLocation))]
[XmlInclude(typeof(DipSwitch))]
[XmlInclude(typeof(DipValue))]
[XmlInclude(typeof(Disk))]
[XmlInclude(typeof(DiskArea))]
[XmlInclude(typeof(Display))]
[XmlInclude(typeof(Driver))]
[XmlInclude(typeof(Extension))]
[XmlInclude(typeof(Feature))]
[XmlInclude(typeof(Info))]
[XmlInclude(typeof(Input))]
[XmlInclude(typeof(Instance))]
[XmlInclude(typeof(Media))]
[XmlInclude(typeof(Part))]
[XmlInclude(typeof(PartFeature))]
[XmlInclude(typeof(Port))]
[XmlInclude(typeof(RamOption))]
[XmlInclude(typeof(Release))]
[XmlInclude(typeof(Rom))]
[XmlInclude(typeof(Sample))]
[XmlInclude(typeof(SharedFeat))]
[XmlInclude(typeof(Slot))]
[XmlInclude(typeof(SlotOption))]
[XmlInclude(typeof(SoftwareList))]
[XmlInclude(typeof(Sound))]
public abstract class DatItem : ModelBackedItem<Data.Models.Metadata.DatItem>, IEquatable<DatItem>, IComparable<DatItem>, ICloneable
{
#region Constants
/// <summary>
/// Duplicate type when compared to another item
/// </summary>
public const string DupeTypeKey = "DUPETYPE";
/// <summary>
/// Machine associated with the item
/// </summary>
public const string MachineKey = "MACHINE";
/// <summary>
/// Flag if item should be removed
/// </summary>
public const string RemoveKey = "REMOVE";
/// <summary>
/// Source information
/// </summary>
public const string SourceKey = "SOURCE";
#endregion
#region Fields
/// <summary>
/// Item type for the object
/// </summary>
protected abstract ItemType ItemType { get; }
#endregion
#region Logging
/// <summary>
/// Static logger for static methods
/// </summary>
[JsonIgnore, XmlIgnore]
protected static readonly Logger _staticLogger = new();
#endregion
#region Accessors
/// <summary>
/// Get the machine for a DatItem
/// </summary>
/// <returns>Machine if available, null otherwise</returns>
/// <remarks>Relies on <see cref="MachineKey"/></remarks>
public Machine? GetMachine() => _internal.Read<Machine>(MachineKey);
/// <summary>
/// Gets the name to use for a DatItem
/// </summary>
/// <returns>Name if available, null otherwise</returns>
public virtual string? GetName() => _internal.GetName();
/// <summary>
/// Sets the name to use for a DatItem
/// </summary>
/// <param name="name">Name to set for the item</param>
public virtual void SetName(string? name) => _internal.SetName(name);
#endregion
#region Cloning Methods
/// <summary>
/// Clone the DatItem
/// </summary>
/// <returns>Clone of the DatItem</returns>
public abstract object Clone();
/// <summary>
/// Copy all machine information over in one shot
/// </summary>
/// <param name="item">Existing item to copy information from</param>
public void CopyMachineInformation(DatItem item)
{
// If there is no machine
if (!item._internal.ContainsKey(MachineKey))
return;
var machine = item.GetMachine();
CopyMachineInformation(machine);
}
/// <summary>
/// Copy all machine information over in one shot
/// </summary>
/// <param name="machine">Existing machine to copy information from</param>
public void CopyMachineInformation(Machine? machine)
{
if (machine is null)
return;
if (machine.Clone() is Machine cloned)
SetFieldValue(MachineKey, cloned);
}
#endregion
#region Comparision Methods
/// <inheritdoc/>
public int CompareTo(DatItem? other)
{
// If the other item doesn't exist
if (other is null)
return 1;
// Get the names to avoid changing values
string? selfName = GetName();
string? otherName = other.GetName();
// If the names are equal
if (selfName == otherName)
return Equals(other) ? 0 : 1;
// If `otherName` is null, Compare will return > 0
// If `selfName` is null, Compare will return < 0
return string.Compare(selfName, otherName, StringComparison.Ordinal);
}
/// <inheritdoc/>
public override bool Equals(ModelBackedItem? other)
{
// If other is null
if (other is null)
return false;
// If the type is mismatched
if (other is not DatItem otherItem)
return false;
// Compare internal models
return _internal.Equals(otherItem);
}
/// <inheritdoc/>
public override bool Equals(ModelBackedItem<Data.Models.Metadata.DatItem>? other)
{
// If other is null
if (other is null)
return false;
// If the type is mismatched
if (other is not DatItem otherItem)
return false;
// Compare internal models
return _internal.Equals(otherItem);
}
/// <summary>
/// Determine if an item is a duplicate using partial matching logic
/// </summary>
/// <param name="other">DatItem to use as a baseline</param>
/// <returns>True if the items are duplicates, false otherwise</returns>
public virtual bool Equals(DatItem? other)
{
// If the other item is null
if (other is null)
return false;
// Get the types for comparison
ItemType selfType = GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey).AsItemType();
ItemType otherType = other.GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey).AsItemType();
// If we don't have a matched type, return false
if (selfType != otherType)
return false;
// Compare the internal models
return _internal.EqualTo(other._internal);
}
#endregion
#region Manipulation
/// <summary>
/// Runs a filter and determines if it passes or not
/// </summary>
/// <param name="filterRunner">Filter runner to use for checking</param>
/// <returns>True if the item and its machine passes the filter, false otherwise</returns>
public bool PassesFilter(FilterRunner filterRunner)
{
var machine = GetMachine();
if (machine is not null && !machine.PassesFilter(filterRunner))
return false;
return filterRunner.Run(_internal);
}
/// <summary>
/// Runs a filter and determines if it passes or not
/// </summary>
/// <param name="filterRunner">Filter runner to use for checking</param>
/// <returns>True if the item passes the filter, false otherwise</returns>
public bool PassesFilterDB(FilterRunner filterRunner)
=> filterRunner.Run(_internal);
#endregion
#region Sorting and Merging
/// <summary>
/// Get the dictionary key that should be used for a given item and bucketing type
/// </summary>
/// <param name="bucketedBy">ItemKey value representing what key to get</param>
/// <param name="machine">Machine associated with the item for renaming</param>
/// <param name="source">Source associated with the item for renaming</param>
/// <param name="lower">True if the key should be lowercased (default), false otherwise</param>
/// <param name="norename">True if games should only be compared on game and file name, false if system and source are counted</param>
/// <returns>String representing the key to be used for the DatItem</returns>
public virtual 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 = string.Empty;
string sourceKeyPadded = source?.Index.ToString().PadLeft(10, '0') + '-';
string machineName = machine?.GetName() ?? "Default";
#pragma warning disable IDE0010
// Now determine what the key should be based on the bucketedBy value
switch (bucketedBy)
{
case ItemKey.CRC:
key = HashType.CRC32.ZeroString;
break;
case ItemKey.Machine:
key = (norename ? string.Empty : sourceKeyPadded) + machineName;
break;
case ItemKey.MD2:
key = HashType.MD2.ZeroString;
break;
case ItemKey.MD4:
key = HashType.MD4.ZeroString;
break;
case ItemKey.MD5:
key = HashType.MD5.ZeroString;
break;
case ItemKey.RIPEMD128:
key = HashType.RIPEMD128.ZeroString;
break;
case ItemKey.RIPEMD160:
key = HashType.RIPEMD160.ZeroString;
break;
case ItemKey.SHA1:
key = HashType.SHA1.ZeroString;
break;
case ItemKey.SHA256:
key = HashType.SHA256.ZeroString;
break;
case ItemKey.SHA384:
key = HashType.SHA384.ZeroString;
break;
case ItemKey.SHA512:
key = HashType.SHA512.ZeroString;
break;
case ItemKey.SpamSum:
key = HashType.SpamSum.ZeroString;
break;
}
#pragma warning restore IDE0010
// Double and triple check the key for corner cases
key ??= string.Empty;
if (lower)
key = key.ToLowerInvariant();
return key;
}
#endregion
}
}

View File

@@ -0,0 +1,112 @@
using System;
using System.Reflection;
namespace SabreTools.Metadata.DatItems
{
/// <summary>
/// Base class for all items included in a set that are backed by an internal model
/// </summary>
public abstract class DatItem<T> : DatItem, IEquatable<DatItem<T>>, IComparable<DatItem<T>>, ICloneable where T : Data.Models.Metadata.DatItem
{
#region Constructors
/// <summary>
/// Create a default, empty object
/// </summary>
public DatItem()
{
_internal = Activator.CreateInstance<T>();
SetName(string.Empty);
SetFieldValue(Data.Models.Metadata.DatItem.TypeKey, ItemType);
SetFieldValue(MachineKey, new Machine());
}
/// <summary>
/// Create an object from the internal model
/// </summary>
public DatItem(T item)
{
_internal = item;
SetFieldValue(Data.Models.Metadata.DatItem.TypeKey, ItemType);
SetFieldValue(MachineKey, new Machine());
}
#endregion
#region Cloning Methods
/// <summary>
/// Clone the DatItem
/// </summary>
/// <returns>Clone of the DatItem</returns>
/// <remarks>
/// Throws an exception if there is a DatItem implementation
/// that is not a part of this library.
/// </remarks>
public override object Clone()
{
var concrete = Array.Find(Assembly.GetExecutingAssembly().GetTypes(),
t => !t.IsAbstract && t.IsClass && t.BaseType == typeof(DatItem<T>));
var clone = Activator.CreateInstance(concrete!);
(clone as DatItem<T>)!._internal = _internal?.Clone() as T ?? Activator.CreateInstance<T>();
return clone;
}
/// <summary>
/// Get a clone of the current internal model
/// </summary>
public virtual T GetInternalClone() => (_internal.Clone() as T)!;
#endregion
#region Comparision Methods
/// <inheritdoc/>
public int CompareTo(DatItem<T>? other)
{
// If the other item doesn't exist
if (other is null)
return 1;
// Get the names to avoid changing values
string? selfName = GetName();
string? otherName = other.GetName();
// If the names are equal
if (selfName == otherName)
return Equals(other) ? 0 : 1;
// If `otherName` is null, Compare will return > 0
// If `selfName` is null, Compare will return < 0
return string.Compare(selfName, otherName, StringComparison.Ordinal);
}
/// <summary>
/// Determine if an item is a duplicate using partial matching logic
/// </summary>
/// <param name="other">DatItem to use as a baseline</param>
/// <returns>True if the items are duplicates, false otherwise</returns>
public virtual bool Equals(DatItem<T>? other)
{
// If the other value is null
if (other is null)
return false;
// Get the types for comparison
ItemType selfType = GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey).AsItemType();
ItemType otherType = other.GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey).AsItemType();
// If we don't have a matched type, return false
if (selfType != otherType)
return false;
// Compare the internal models
return _internal.EqualTo(other._internal);
}
#endregion
}
}

View File

@@ -0,0 +1,685 @@
using System;
namespace SabreTools.Metadata.DatItems
{
/// <summary>
/// Determine the chip type
/// </summary>
[Flags]
public enum ChipType
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
[Mapping("cpu")]
CPU = 1 << 0,
[Mapping("audio")]
Audio = 1 << 1,
}
/// <summary>
/// Determine the control type
/// </summary>
[Flags]
public enum ControlType
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
[Mapping("joy")]
Joy = 1 << 0,
[Mapping("stick")]
Stick = 1 << 1,
[Mapping("paddle")]
Paddle = 1 << 2,
[Mapping("pedal")]
Pedal = 1 << 3,
[Mapping("lightgun")]
Lightgun = 1 << 4,
[Mapping("positional")]
Positional = 1 << 5,
[Mapping("dial")]
Dial = 1 << 6,
[Mapping("trackball")]
Trackball = 1 << 7,
[Mapping("mouse")]
Mouse = 1 << 8,
[Mapping("only_buttons")]
OnlyButtons = 1 << 9,
[Mapping("keypad")]
Keypad = 1 << 10,
[Mapping("keyboard")]
Keyboard = 1 << 11,
[Mapping("mahjong")]
Mahjong = 1 << 12,
[Mapping("hanafuda")]
Hanafuda = 1 << 13,
[Mapping("gambling")]
Gambling = 1 << 14,
}
/// <summary>
/// Determine the device type
/// </summary>
[Flags]
public enum DeviceType
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
[Mapping("unknown")]
Unknown = 1 << 0,
[Mapping("cartridge")]
Cartridge = 1 << 1,
[Mapping("floppydisk")]
FloppyDisk = 1 << 2,
[Mapping("harddisk")]
HardDisk = 1 << 3,
[Mapping("cylinder")]
Cylinder = 1 << 4,
[Mapping("cassette")]
Cassette = 1 << 5,
[Mapping("punchcard")]
PunchCard = 1 << 6,
[Mapping("punchtape")]
PunchTape = 1 << 7,
[Mapping("printout")]
Printout = 1 << 8,
[Mapping("serial")]
Serial = 1 << 9,
[Mapping("parallel")]
Parallel = 1 << 10,
[Mapping("snapshot")]
Snapshot = 1 << 11,
[Mapping("quickload")]
QuickLoad = 1 << 12,
[Mapping("memcard")]
MemCard = 1 << 13,
[Mapping("cdrom")]
CDROM = 1 << 14,
[Mapping("magtape")]
MagTape = 1 << 15,
[Mapping("romimage")]
ROMImage = 1 << 16,
[Mapping("midiin")]
MIDIIn = 1 << 17,
[Mapping("midiout")]
MIDIOut = 1 << 18,
[Mapping("picture")]
Picture = 1 << 19,
[Mapping("vidfile")]
VidFile = 1 << 20,
}
/// <summary>
/// Determine the display type
/// </summary>
[Flags]
public enum DisplayType
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
[Mapping("raster")]
Raster = 1 << 0,
[Mapping("vector")]
Vector = 1 << 1,
[Mapping("lcd")]
LCD = 1 << 2,
[Mapping("svg")]
SVG = 1 << 3,
[Mapping("unknown")]
Unknown = 1 << 4,
}
/// <summary>
/// Determines which type of duplicate a file is
/// </summary>
[Flags]
public enum DupeType
{
// Type of match
Hash = 1 << 0,
All = 1 << 1,
// Location of match
Internal = 1 << 2,
External = 1 << 3,
}
/// <summary>
/// Determine the endianness
/// </summary>
[Flags]
public enum Endianness
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
[Mapping("big")]
Big = 1 << 0,
[Mapping("little")]
Little = 1 << 1,
}
/// <summary>
/// Determine the emulation status
/// </summary>
[Flags]
public enum FeatureStatus
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
[Mapping("unemulated")]
Unemulated = 1 << 0,
[Mapping("imperfect")]
Imperfect = 1 << 1,
}
/// <summary>
/// Determine the feature type
/// </summary>
[Flags]
public enum FeatureType
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
[Mapping("protection")]
Protection = 1 << 0,
[Mapping("palette")]
Palette = 1 << 1,
[Mapping("graphics")]
Graphics = 1 << 2,
[Mapping("sound")]
Sound = 1 << 3,
[Mapping("controls")]
Controls = 1 << 4,
[Mapping("keyboard")]
Keyboard = 1 << 5,
[Mapping("mouse")]
Mouse = 1 << 6,
[Mapping("microphone")]
Microphone = 1 << 7,
[Mapping("camera")]
Camera = 1 << 8,
[Mapping("disk")]
Disk = 1 << 9,
[Mapping("printer")]
Printer = 1 << 10,
[Mapping("lan")]
Lan = 1 << 11,
[Mapping("wan")]
Wan = 1 << 12,
[Mapping("timing")]
Timing = 1 << 13,
}
/// <summary>
/// A subset of fields that can be used as keys
/// </summary>
public enum ItemKey
{
NULL = 0,
Machine,
CRC,
MD2,
MD4,
MD5,
RIPEMD128,
RIPEMD160,
SHA1,
SHA256,
SHA384,
SHA512,
SpamSum,
}
/// <summary>
/// Determine the status of the item
/// </summary>
[Flags]
public enum ItemStatus
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
[Mapping("none", "no")]
None = 1 << 0,
[Mapping("good")]
Good = 1 << 1,
[Mapping("baddump")]
BadDump = 1 << 2,
[Mapping("nodump", "yes")]
Nodump = 1 << 3,
[Mapping("verified")]
Verified = 1 << 4,
}
/// <summary>
/// Determine what type of file an item is
/// </summary>
public enum ItemType
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
// "Actionable" item types
[Mapping("rom")]
Rom,
[Mapping("disk")]
Disk,
[Mapping("file")]
File,
[Mapping("media")]
Media,
// "Auxiliary" item types
[Mapping("adjuster")]
Adjuster,
[Mapping("analog")]
Analog,
[Mapping("archive")]
Archive,
[Mapping("biosset")]
BiosSet,
[Mapping("chip")]
Chip,
[Mapping("condition")]
Condition,
[Mapping("configuration")]
Configuration,
[Mapping("conflocation")]
ConfLocation,
[Mapping("confsetting")]
ConfSetting,
[Mapping("control")]
Control,
[Mapping("dataarea")]
DataArea,
[Mapping("device")]
Device,
[Mapping("device_ref", "deviceref")]
DeviceRef,
[Mapping("diplocation")]
DipLocation,
[Mapping("dipswitch")]
DipSwitch,
[Mapping("dipvalue")]
DipValue,
[Mapping("diskarea")]
DiskArea,
[Mapping("display")]
Display,
[Mapping("driver")]
Driver,
[Mapping("extension")]
Extension,
[Mapping("feature")]
Feature,
[Mapping("info")]
Info,
[Mapping("input")]
Input,
[Mapping("instance")]
Instance,
[Mapping("original")]
Original,
[Mapping("part")]
Part,
[Mapping("part_feature", "partfeature")]
PartFeature,
[Mapping("port")]
Port,
[Mapping("ramoption", "ram_option")]
RamOption,
[Mapping("release")]
Release,
[Mapping("release_details", "releasedetails")]
ReleaseDetails,
[Mapping("sample")]
Sample,
[Mapping("serials")]
Serials,
[Mapping("sharedfeat", "shared_feat", "sharedfeature", "shared_feature")]
SharedFeat,
[Mapping("slot")]
Slot,
[Mapping("slotoption", "slot_option")]
SlotOption,
[Mapping("softwarelist", "software_list")]
SoftwareList,
[Mapping("sound")]
Sound,
[Mapping("source_details", "sourcedetails")]
SourceDetails,
[Mapping("blank")]
Blank = 99, // This is not a real type, only used internally
}
/// <summary>
/// Determine the loadflag value
/// </summary>
[Flags]
public enum LoadFlag
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
[Mapping("load16_byte")]
Load16Byte = 1 << 0,
[Mapping("load16_word")]
Load16Word = 1 << 1,
[Mapping("load16_word_swap")]
Load16WordSwap = 1 << 2,
[Mapping("load32_byte")]
Load32Byte = 1 << 3,
[Mapping("load32_word")]
Load32Word = 1 << 4,
[Mapping("load32_word_swap")]
Load32WordSwap = 1 << 5,
[Mapping("load32_dword")]
Load32DWord = 1 << 6,
[Mapping("load64_word")]
Load64Word = 1 << 7,
[Mapping("load64_word_swap")]
Load64WordSwap = 1 << 8,
[Mapping("reload")]
Reload = 1 << 9,
[Mapping("fill")]
Fill = 1 << 10,
[Mapping("continue")]
Continue = 1 << 11,
[Mapping("reload_plain")]
ReloadPlain = 1 << 12,
[Mapping("ignore")]
Ignore = 1 << 13,
}
/// <summary>
/// Determine what type of machine it is
/// </summary>
[Flags]
public enum MachineType
{
[Mapping("none")]
None = 0,
[Mapping("bios")]
Bios = 1 << 0,
[Mapping("device", "dev")]
Device = 1 << 1,
[Mapping("mechanical", "mech")]
Mechanical = 1 << 2,
}
/// <summary>
/// Determine which OpenMSX subtype an item is
/// </summary>
[Flags]
public enum OpenMSXSubType
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
[Mapping("rom")]
Rom = 1 << 0,
[Mapping("megarom")]
MegaRom = 1 << 1,
[Mapping("sccpluscart")]
SCCPlusCart = 1 << 2,
}
/// <summary>
/// Determine relation of value to condition
/// </summary>
[Flags]
public enum Relation
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
[Mapping("eq")]
Equal = 1 << 0,
[Mapping("ne")]
NotEqual = 1 << 1,
[Mapping("gt")]
GreaterThan = 1 << 2,
[Mapping("le")]
LessThanOrEqual = 1 << 3,
[Mapping("lt")]
LessThan = 1 << 4,
[Mapping("ge")]
GreaterThanOrEqual = 1 << 5,
}
/// <summary>
/// Determine machine runnable status
/// </summary>
[Flags]
public enum Runnable
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
[Mapping("no")]
No = 1 << 0,
[Mapping("partial")]
Partial = 1 << 1,
[Mapping("yes")]
Yes = 1 << 2,
}
/// <summary>
/// Determine software list status
/// </summary>
[Flags]
public enum SoftwareListStatus
{
[Mapping("none")]
None = 0,
[Mapping("original")]
Original = 1 << 0,
[Mapping("compatible")]
Compatible = 1 << 1,
}
/// <summary>
/// Determine machine support status
/// </summary>
[Flags]
public enum Supported
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
[Mapping("no", "unsupported")]
No = 1 << 0,
[Mapping("partial")]
Partial = 1 << 1,
[Mapping("yes", "supported")]
Yes = 1 << 2,
}
/// <summary>
/// Determine driver support statuses
/// </summary>
[Flags]
public enum SupportStatus
{
/// <summary>
/// This is a fake flag that is used for filter only
/// </summary>
NULL = 0,
[Mapping("good")]
Good = 1 << 0,
[Mapping("imperfect")]
Imperfect = 1 << 1,
[Mapping("preliminary")]
Preliminary = 1 << 2,
}
}

View File

@@ -0,0 +1,788 @@
using System.Collections.Generic;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems
{
public static class Extensions
{
#region Private Maps
/// <summary>
/// Set of enum to string mappings for ChipType
/// </summary>
private static readonly Dictionary<string, ChipType> _toChipTypeMap = Converters.GenerateToEnum<ChipType>();
/// <summary>
/// Set of string to enum mappings for ChipType
/// </summary>
private static readonly Dictionary<ChipType, string> _fromChipTypeMap = Converters.GenerateToString<ChipType>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for ControlType
/// </summary>
private static readonly Dictionary<string, ControlType> _toControlTypeMap = Converters.GenerateToEnum<ControlType>();
/// <summary>
/// Set of string to enum mappings for ControlType
/// </summary>
private static readonly Dictionary<ControlType, string> _fromControlTypeMap = Converters.GenerateToString<ControlType>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for DeviceType
/// </summary>
private static readonly Dictionary<string, DeviceType> _toDeviceTypeMap = Converters.GenerateToEnum<DeviceType>();
/// <summary>
/// Set of string to enum mappings for DeviceType
/// </summary>
private static readonly Dictionary<DeviceType, string> _fromDeviceTypeMap = Converters.GenerateToString<DeviceType>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for DisplayType
/// </summary>
private static readonly Dictionary<string, DisplayType> _toDisplayTypeMap = Converters.GenerateToEnum<DisplayType>();
/// <summary>
/// Set of string to enum mappings for DisplayType
/// </summary>
private static readonly Dictionary<DisplayType, string> _fromDisplayTypeMap = Converters.GenerateToString<DisplayType>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for Endianness
/// </summary>
private static readonly Dictionary<string, Endianness> _toEndiannessMap = Converters.GenerateToEnum<Endianness>();
/// <summary>
/// Set of string to enum mappings for Endianness
/// </summary>
private static readonly Dictionary<Endianness, string> _fromEndiannessMap = Converters.GenerateToString<Endianness>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for FeatureStatus
/// </summary>
private static readonly Dictionary<string, FeatureStatus> _toFeatureStatusMap = Converters.GenerateToEnum<FeatureStatus>();
/// <summary>
/// Set of string to enum mappings for FeatureStatus
/// </summary>
private static readonly Dictionary<FeatureStatus, string> _fromFeatureStatusMap = Converters.GenerateToString<FeatureStatus>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for FeatureType
/// </summary>
private static readonly Dictionary<string, FeatureType> _toFeatureTypeMap = Converters.GenerateToEnum<FeatureType>();
/// <summary>
/// Set of string to enum mappings for FeatureType
/// </summary>
private static readonly Dictionary<FeatureType, string> _fromFeatureTypeMap = Converters.GenerateToString<FeatureType>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for ItemStatus
/// </summary>
private static readonly Dictionary<string, ItemStatus> _toItemStatusMap = Converters.GenerateToEnum<ItemStatus>();
/// <summary>
/// Set of string to enum mappings for ItemStatus
/// </summary>
private static readonly Dictionary<ItemStatus, string> _fromItemStatusMap = Converters.GenerateToString<ItemStatus>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for ItemType
/// </summary>
private static readonly Dictionary<string, ItemType> _toItemTypeMap = Converters.GenerateToEnum<ItemType>();
/// <summary>
/// Set of string to enum mappings for ItemType
/// </summary>
private static readonly Dictionary<ItemType, string> _fromItemTypeMap = Converters.GenerateToString<ItemType>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for LoadFlag
/// </summary>
private static readonly Dictionary<string, LoadFlag> _toLoadFlagMap = Converters.GenerateToEnum<LoadFlag>();
/// <summary>
/// Set of string to enum mappings for LoadFlag
/// </summary>
private static readonly Dictionary<LoadFlag, string> _fromLoadFlagMap = Converters.GenerateToString<LoadFlag>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for MachineType
/// </summary>
private static readonly Dictionary<string, MachineType> _toMachineTypeMap = Converters.GenerateToEnum<MachineType>();
/// <summary>
/// Set of enum to string mappings for OpenMSXSubType
/// </summary>
private static readonly Dictionary<string, OpenMSXSubType> _toOpenMSXSubTypeMap = Converters.GenerateToEnum<OpenMSXSubType>();
/// <summary>
/// Set of string to enum mappings for OpenMSXSubType
/// </summary>
private static readonly Dictionary<OpenMSXSubType, string> _fromOpenMSXSubTypeMap = Converters.GenerateToString<OpenMSXSubType>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for Relation
/// </summary>
private static readonly Dictionary<string, Relation> _toRelationMap = Converters.GenerateToEnum<Relation>();
/// <summary>
/// Set of string to enum mappings for Relation
/// </summary>
private static readonly Dictionary<Relation, string> _fromRelationMap = Converters.GenerateToString<Relation>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for Runnable
/// </summary>
private static readonly Dictionary<string, Runnable> _toRunnableMap = Converters.GenerateToEnum<Runnable>();
/// <summary>
/// Set of string to enum mappings for Runnable
/// </summary>
private static readonly Dictionary<Runnable, string> _fromRunnableMap = Converters.GenerateToString<Runnable>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for SoftwareListStatus
/// </summary>
private static readonly Dictionary<string, SoftwareListStatus> _toSoftwareListStatusMap = Converters.GenerateToEnum<SoftwareListStatus>();
/// <summary>
/// Set of string to enum mappings for SoftwareListStatus
/// </summary>
private static readonly Dictionary<SoftwareListStatus, string> _fromSoftwareListStatusMap = Converters.GenerateToString<SoftwareListStatus>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for Supported
/// </summary>
private static readonly Dictionary<string, Supported> _toSupportedMap = Converters.GenerateToEnum<Supported>();
/// <summary>
/// Set of string to enum mappings for Supported
/// </summary>
private static readonly Dictionary<Supported, string> _fromSupportedMap = Converters.GenerateToString<Supported>(useSecond: false);
/// <summary>
/// Set of string to enum mappings for Supported (secondary)
/// </summary>
private static readonly Dictionary<Supported, string> _fromSupportedSecondaryMap = Converters.GenerateToString<Supported>(useSecond: true);
/// <summary>
/// Set of enum to string mappings for SupportStatus
/// </summary>
private static readonly Dictionary<string, SupportStatus> _toSupportStatusMap = Converters.GenerateToEnum<SupportStatus>();
/// <summary>
/// Set of string to enum mappings for SupportStatus
/// </summary>
private static readonly Dictionary<SupportStatus, string> _fromSupportStatusMap = Converters.GenerateToString<SupportStatus>(useSecond: false);
#endregion
#region String to Enum
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static ChipType AsChipType(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toChipTypeMap.ContainsKey(value))
return _toChipTypeMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static ControlType AsControlType(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toControlTypeMap.ContainsKey(value))
return _toControlTypeMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static DeviceType AsDeviceType(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toDeviceTypeMap.ContainsKey(value))
return _toDeviceTypeMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static DisplayType AsDisplayType(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toDisplayTypeMap.ContainsKey(value))
return _toDisplayTypeMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static Endianness AsEndianness(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toEndiannessMap.ContainsKey(value))
return _toEndiannessMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static FeatureStatus AsFeatureStatus(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toFeatureStatusMap.ContainsKey(value))
return _toFeatureStatusMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static FeatureType AsFeatureType(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toFeatureTypeMap.ContainsKey(value))
return _toFeatureTypeMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static ItemStatus AsItemStatus(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toItemStatusMap.ContainsKey(value))
return _toItemStatusMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static ItemType AsItemType(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toItemTypeMap.ContainsKey(value))
return _toItemTypeMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static LoadFlag AsLoadFlag(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toLoadFlagMap.ContainsKey(value))
return _toLoadFlagMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static MachineType AsMachineType(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toMachineTypeMap.ContainsKey(value))
return _toMachineTypeMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static OpenMSXSubType AsOpenMSXSubType(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toOpenMSXSubTypeMap.ContainsKey(value))
return _toOpenMSXSubTypeMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static Relation AsRelation(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toRelationMap.ContainsKey(value))
return _toRelationMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static Runnable AsRunnable(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toRunnableMap.ContainsKey(value))
return _toRunnableMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static SoftwareListStatus AsSoftwareListStatus(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toSoftwareListStatusMap.ContainsKey(value))
return _toSoftwareListStatusMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static Supported AsSupported(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toSupportedMap.ContainsKey(value))
return _toSupportedMap[value];
// Otherwise, return the default value for the enum
return default;
}
/// <summary>
/// Get the enum value for an input string, if possible
/// </summary>
/// <param name="value">String value to parse/param>
/// <returns>Enum value representing the input, default on error</returns>
public static SupportStatus AsSupportStatus(this string? value)
{
// Normalize the input value
value = value?.ToLowerInvariant();
if (value is null)
return default;
// Try to get the value from the mappings
if (_toSupportStatusMap.ContainsKey(value))
return _toSupportStatusMap[value];
// Otherwise, return the default value for the enum
return default;
}
#endregion
#region Enum to String
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this ChipType value)
{
// Try to get the value from the mappings
if (_fromChipTypeMap.ContainsKey(value))
return _fromChipTypeMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this ControlType value)
{
// Try to get the value from the mappings
if (_fromControlTypeMap.ContainsKey(value))
return _fromControlTypeMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this DeviceType value)
{
// Try to get the value from the mappings
if (_fromDeviceTypeMap.ContainsKey(value))
return _fromDeviceTypeMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this DisplayType value)
{
// Try to get the value from the mappings
if (_fromDisplayTypeMap.ContainsKey(value))
return _fromDisplayTypeMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this Endianness value)
{
// Try to get the value from the mappings
if (_fromEndiannessMap.ContainsKey(value))
return _fromEndiannessMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this FeatureStatus value)
{
// Try to get the value from the mappings
if (_fromFeatureStatusMap.ContainsKey(value))
return _fromFeatureStatusMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this FeatureType value)
{
// Try to get the value from the mappings
if (_fromFeatureTypeMap.ContainsKey(value))
return _fromFeatureTypeMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this ItemStatus value)
{
// Try to get the value from the mappings
if (_fromItemStatusMap.ContainsKey(value))
return _fromItemStatusMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this ItemType value)
{
// Try to get the value from the mappings
if (_fromItemTypeMap.ContainsKey(value))
return _fromItemTypeMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this LoadFlag value)
{
// Try to get the value from the mappings
if (_fromLoadFlagMap.ContainsKey(value))
return _fromLoadFlagMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this OpenMSXSubType value)
{
// Try to get the value from the mappings
if (_fromOpenMSXSubTypeMap.ContainsKey(value))
return _fromOpenMSXSubTypeMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this Relation value)
{
// Try to get the value from the mappings
if (_fromRelationMap.ContainsKey(value))
return _fromRelationMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this Runnable value)
{
// Try to get the value from the mappings
if (_fromRunnableMap.ContainsKey(value))
return _fromRunnableMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this SoftwareListStatus value)
{
// Try to get the value from the mappings
if (_fromSoftwareListStatusMap.ContainsKey(value))
return _fromSoftwareListStatusMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this Supported value, bool useSecond = false)
{
// Try to get the value from the mappings
if (!useSecond && _fromSupportedMap.ContainsKey(value))
return _fromSupportedMap[value];
else if (useSecond && _fromSupportedSecondaryMap.ContainsKey(value))
return _fromSupportedSecondaryMap[value];
// Otherwise, return null
return null;
}
/// <summary>
/// Get the string value for an input enum, if possible
/// </summary>
/// <param name="value">Enum value to parse/param>
/// <param name="useSecond">True to use the second mapping option, if it exists</param>
/// <returns>String value representing the input, default on error</returns>
public static string? AsStringValue(this SupportStatus value)
{
// Try to get the value from the mappings
if (_fromSupportStatusMap.ContainsKey(value))
return _fromSupportStatusMap[value];
// Otherwise, return null
return null;
}
#endregion
}
}

View File

@@ -0,0 +1,70 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents which Adjuster(s) is associated with a set
/// </summary>
[JsonObject("adjuster"), XmlRoot("adjuster")]
public sealed class Adjuster : DatItem<Data.Models.Metadata.Adjuster>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Adjuster;
[JsonIgnore]
public bool ConditionsSpecified
{
get
{
var conditions = GetFieldValue<Condition[]?>(Data.Models.Metadata.Adjuster.ConditionKey);
return conditions is not null && conditions.Length > 0;
}
}
#endregion
#region Constructors
public Adjuster() : base() { }
public Adjuster(Data.Models.Metadata.Adjuster item) : base(item)
{
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.Adjuster.DefaultKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Adjuster.DefaultKey, GetBoolFieldValue(Data.Models.Metadata.Adjuster.DefaultKey).FromYesNo());
// Handle subitems
var condition = item.Read<Data.Models.Metadata.Condition>(Data.Models.Metadata.Adjuster.ConditionKey);
if (condition is not null)
SetFieldValue(Data.Models.Metadata.Adjuster.ConditionKey, new Condition(condition));
}
public Adjuster(Data.Models.Metadata.Adjuster item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override Data.Models.Metadata.Adjuster GetInternalClone()
{
var adjusterItem = base.GetInternalClone();
var condition = GetFieldValue<Condition?>(Data.Models.Metadata.Adjuster.ConditionKey);
if (condition is not null)
adjusterItem[Data.Models.Metadata.Adjuster.ConditionKey] = condition.GetInternalClone();
return adjusterItem;
}
#endregion
}
}

View File

@@ -0,0 +1,33 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents a single analog item
/// </summary>
[JsonObject("analog"), XmlRoot("analog")]
public sealed class Analog : DatItem<Data.Models.Metadata.Analog>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Analog;
#endregion
#region Constructors
public Analog() : base() { }
public Analog(Data.Models.Metadata.Analog item) : base(item) { }
public Analog(Data.Models.Metadata.Analog item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,100 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents generic archive files to be included in a set
/// </summary>
[JsonObject("archive"), XmlRoot("archive")]
public sealed class Archive : DatItem<Data.Models.Metadata.Archive>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Archive;
// TODO: None of the following are used or checked
/// <summary>
/// Archive ID number
/// </summary>
/// <remarks>TODO: No-Intro database export only</remarks>
[JsonProperty("number"), XmlElement("number")]
public string? Number { get; set; }
/// <summary>
/// Clone value
/// </summary>
/// <remarks>TODO: No-Intro database export only</remarks>
[JsonProperty("clone"), XmlElement("clone")]
public string? CloneValue { get; set; }
/// <summary>
/// Regional parent value
/// </summary>
/// <remarks>TODO: No-Intro database export only</remarks>
[JsonProperty("regparent"), XmlElement("regparent")]
public string? RegParent { get; set; }
/// <summary>
/// Region value
/// </summary>
/// <remarks>TODO: No-Intro database export only</remarks>
[JsonProperty("region"), XmlElement("region")]
public string? Region { get; set; }
/// <summary>
/// Languages value
/// </summary>
/// <remarks>TODO: No-Intro database export only</remarks>
[JsonProperty("languages"), XmlElement("languages")]
public string? Languages { get; set; }
/// <summary>
/// Development status value
/// </summary>
/// <remarks>TODO: No-Intro database export only</remarks>
[JsonProperty("devstatus"), XmlElement("devstatus")]
public string? DevStatus { get; set; }
/// <summary>
/// Physical value
/// </summary>
/// <remarks>TODO: No-Intro database export only</remarks>
/// <remarks>TODO: Is this numeric or a flag?</remarks>
[JsonProperty("physical"), XmlElement("physical")]
public string? Physical { get; set; }
/// <summary>
/// Complete value
/// </summary>
/// <remarks>TODO: No-Intro database export only</remarks>
/// <remarks>TODO: Is this numeric or a flag?</remarks>
[JsonProperty("complete"), XmlElement("complete")]
public string? Complete { get; set; }
/// <summary>
/// Categories value
/// </summary>
/// <remarks>TODO: No-Intro database export only</remarks>
[JsonProperty("categories"), XmlElement("categories")]
public string? Categories { get; set; }
#endregion
#region Constructors
public Archive() : base() { }
public Archive(Data.Models.Metadata.Archive item) : base(item) { }
public Archive(Data.Models.Metadata.Archive item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,39 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents which BIOS(es) is associated with a set
/// </summary>
[JsonObject("biosset"), XmlRoot("biosset")]
public sealed class BiosSet : DatItem<Data.Models.Metadata.BiosSet>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.BiosSet;
#endregion
#region Constructors
public BiosSet() : base() { }
public BiosSet(Data.Models.Metadata.BiosSet item) : base(item)
{
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.BiosSet.DefaultKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.BiosSet.DefaultKey, GetBoolFieldValue(Data.Models.Metadata.BiosSet.DefaultKey).FromYesNo());
}
public BiosSet(Data.Models.Metadata.BiosSet item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,95 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents a blank set from an input DAT
/// </summary>
[JsonObject("blank"), XmlRoot("blank")]
public sealed class Blank : DatItem
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Blank;
#endregion
#region Constructors
/// <summary>
/// Create a default, empty Blank object
/// </summary>
public Blank()
{
SetFieldValue(Data.Models.Metadata.DatItem.TypeKey, ItemType);
}
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override object Clone()
{
var blank = new Blank();
blank.SetFieldValue(MachineKey, GetMachine());
blank.SetFieldValue(RemoveKey, GetBoolFieldValue(RemoveKey));
blank.SetFieldValue<Source?>(SourceKey, GetFieldValue<Source?>(SourceKey));
blank.SetFieldValue<string?>(Data.Models.Metadata.DatItem.TypeKey, GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey).AsItemType().AsStringValue());
return blank;
}
#endregion
#region Comparision Methods
/// <inheritdoc/>
public override bool Equals(ModelBackedItem? other)
{
// If other is null
if (other is null)
return false;
// If the type is mismatched
if (other is not DatItem otherItem)
return false;
// Compare internal models
return Equals(otherItem);
}
/// <inheritdoc/>
public override bool Equals(ModelBackedItem<Data.Models.Metadata.DatItem>? other)
{
// If other is null
if (other is null)
return false;
// If the type is mismatched
if (other is not DatItem otherItem)
return false;
// Compare internal models
return Equals(otherItem);
}
/// <inheritdoc/>
public override bool Equals(DatItem? other)
{
// If we don't have a blank, return false
if (GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey) != other?.GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey))
return false;
// Otherwise, treat it as a Blank
Blank? newOther = other as Blank;
// If the machine information matches
return GetMachine() == newOther!.GetMachine();
}
#endregion
}
}

View File

@@ -0,0 +1,41 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents which Chip(s) is associated with a set
/// </summary>
[JsonObject("chip"), XmlRoot("chip")]
public sealed class Chip : DatItem<Data.Models.Metadata.Chip>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Chip;
#endregion
#region Constructors
public Chip() : base() { }
public Chip(Data.Models.Metadata.Chip item) : base(item)
{
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.Chip.SoundOnlyKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Chip.SoundOnlyKey, GetBoolFieldValue(Data.Models.Metadata.Chip.SoundOnlyKey).FromYesNo());
if (GetStringFieldValue(Data.Models.Metadata.Chip.ChipTypeKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Chip.ChipTypeKey, GetStringFieldValue(Data.Models.Metadata.Chip.ChipTypeKey).AsChipType().AsStringValue());
}
public Chip(Data.Models.Metadata.Chip item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,38 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents a condition on a machine or other item
/// </summary>
[JsonObject("condition"), XmlRoot("condition")]
public sealed class Condition : DatItem<Data.Models.Metadata.Condition>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Condition;
#endregion
#region Constructors
public Condition() : base() { }
public Condition(Data.Models.Metadata.Condition item) : base(item)
{
// Process flag values
if (GetStringFieldValue(Data.Models.Metadata.Condition.RelationKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Condition.RelationKey, GetStringFieldValue(Data.Models.Metadata.Condition.RelationKey).AsRelation().AsStringValue());
}
public Condition(Data.Models.Metadata.Condition item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,39 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents one conflocation
/// </summary>
[JsonObject("conflocation"), XmlRoot("conflocation")]
public sealed class ConfLocation : DatItem<Data.Models.Metadata.ConfLocation>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.ConfLocation;
#endregion
#region Constructors
public ConfLocation() : base() { }
public ConfLocation(Data.Models.Metadata.ConfLocation item) : base(item)
{
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.ConfLocation.InvertedKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.ConfLocation.InvertedKey, GetBoolFieldValue(Data.Models.Metadata.ConfLocation.InvertedKey).FromYesNo());
}
public ConfLocation(Data.Models.Metadata.ConfLocation item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,71 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents one ListXML confsetting
/// </summary>
[JsonObject("confsetting"), XmlRoot("confsetting")]
public sealed class ConfSetting : DatItem<Data.Models.Metadata.ConfSetting>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.ConfSetting;
[JsonIgnore]
public bool ConditionsSpecified
{
get
{
var conditions = GetFieldValue<Condition[]?>(Data.Models.Metadata.ConfSetting.ConditionKey);
return conditions is not null && conditions.Length > 0;
}
}
#endregion
#region Constructors
public ConfSetting() : base() { }
public ConfSetting(Data.Models.Metadata.ConfSetting item) : base(item)
{
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.ConfSetting.DefaultKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.ConfSetting.DefaultKey, GetBoolFieldValue(Data.Models.Metadata.ConfSetting.DefaultKey).FromYesNo());
// Handle subitems
var condition = GetFieldValue<Data.Models.Metadata.Condition>(Data.Models.Metadata.ConfSetting.ConditionKey);
if (condition is not null)
SetFieldValue<Condition?>(Data.Models.Metadata.ConfSetting.ConditionKey, new Condition(condition));
}
public ConfSetting(Data.Models.Metadata.ConfSetting item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override Data.Models.Metadata.ConfSetting GetInternalClone()
{
var confSettingItem = base.GetInternalClone();
// Handle subitems
var condition = GetFieldValue<Condition>(Data.Models.Metadata.ConfSetting.ConditionKey);
if (condition is not null)
confSettingItem[Data.Models.Metadata.ConfSetting.ConditionKey] = condition.GetInternalClone();
return confSettingItem;
}
#endregion
}
}

View File

@@ -0,0 +1,115 @@
using System;
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Data.Extensions;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents which Configuration(s) is associated with a set
/// </summary>
[JsonObject("configuration"), XmlRoot("configuration")]
public sealed class Configuration : DatItem<Data.Models.Metadata.Configuration>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Configuration;
[JsonIgnore]
public bool ConditionsSpecified
{
get
{
var conditions = GetFieldValue<Condition[]?>(Data.Models.Metadata.Configuration.ConditionKey);
return conditions is not null && conditions.Length > 0;
}
}
[JsonIgnore]
public bool LocationsSpecified
{
get
{
var locations = GetFieldValue<ConfLocation[]?>(Data.Models.Metadata.Configuration.ConfLocationKey);
return locations is not null && locations.Length > 0;
}
}
[JsonIgnore]
public bool SettingsSpecified
{
get
{
var settings = GetFieldValue<ConfSetting[]?>(Data.Models.Metadata.Configuration.ConfSettingKey);
return settings is not null && settings.Length > 0;
}
}
#endregion
#region Constructors
public Configuration() : base() { }
public Configuration(Data.Models.Metadata.Configuration item) : base(item)
{
// Handle subitems
var condition = item.Read<Data.Models.Metadata.Condition>(Data.Models.Metadata.Configuration.ConditionKey);
if (condition is not null)
SetFieldValue<Condition?>(Data.Models.Metadata.Configuration.ConditionKey, new Condition(condition));
var confLocations = item.ReadItemArray<Data.Models.Metadata.ConfLocation>(Data.Models.Metadata.Configuration.ConfLocationKey);
if (confLocations is not null)
{
ConfLocation[] confLocationItems = Array.ConvertAll(confLocations, confLocation => new ConfLocation(confLocation));
SetFieldValue<ConfLocation[]?>(Data.Models.Metadata.Configuration.ConfLocationKey, confLocationItems);
}
var confSettings = item.ReadItemArray<Data.Models.Metadata.ConfSetting>(Data.Models.Metadata.Configuration.ConfSettingKey);
if (confSettings is not null)
{
ConfSetting[] confSettingItems = Array.ConvertAll(confSettings, confSetting => new ConfSetting(confSetting));
SetFieldValue<ConfSetting[]?>(Data.Models.Metadata.Configuration.ConfSettingKey, confSettingItems);
}
}
public Configuration(Data.Models.Metadata.Configuration item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override Data.Models.Metadata.Configuration GetInternalClone()
{
var configurationItem = base.GetInternalClone();
var condition = GetFieldValue<Condition?>(Data.Models.Metadata.Configuration.ConditionKey);
if (condition is not null)
configurationItem[Data.Models.Metadata.Configuration.ConditionKey] = condition.GetInternalClone();
var confLocations = GetFieldValue<ConfLocation[]?>(Data.Models.Metadata.Configuration.ConfLocationKey);
if (confLocations is not null)
{
Data.Models.Metadata.ConfLocation[] confLocationItems = Array.ConvertAll(confLocations, confLocation => confLocation.GetInternalClone());
configurationItem[Data.Models.Metadata.Configuration.ConfLocationKey] = confLocationItems;
}
var confSettings = GetFieldValue<ConfSetting[]?>(Data.Models.Metadata.Configuration.ConfSettingKey);
if (confSettings is not null)
{
Data.Models.Metadata.ConfSetting[] confSettingItems = Array.ConvertAll(confSettings, confSetting => confSetting.GetInternalClone());
configurationItem[Data.Models.Metadata.Configuration.ConfSettingKey] = confSettingItems;
}
return configurationItem;
}
#endregion
}
}

View File

@@ -0,0 +1,55 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents control for an input
/// </summary>
[JsonObject("control"), XmlRoot("control")]
public sealed class Control : DatItem<Data.Models.Metadata.Control>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Control;
#endregion
#region Constructors
public Control() : base() { }
public Control(Data.Models.Metadata.Control item) : base(item)
{
// Process flag values
if (GetInt64FieldValue(Data.Models.Metadata.Control.ButtonsKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Control.ButtonsKey, GetInt64FieldValue(Data.Models.Metadata.Control.ButtonsKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Control.KeyDeltaKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Control.KeyDeltaKey, GetInt64FieldValue(Data.Models.Metadata.Control.KeyDeltaKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Control.MaximumKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Control.MaximumKey, GetInt64FieldValue(Data.Models.Metadata.Control.MaximumKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Control.MinimumKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Control.MinimumKey, GetInt64FieldValue(Data.Models.Metadata.Control.MinimumKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Control.PlayerKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Control.PlayerKey, GetInt64FieldValue(Data.Models.Metadata.Control.PlayerKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Control.ReqButtonsKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Control.ReqButtonsKey, GetInt64FieldValue(Data.Models.Metadata.Control.ReqButtonsKey).ToString());
if (GetBoolFieldValue(Data.Models.Metadata.Control.ReverseKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Control.ReverseKey, GetBoolFieldValue(Data.Models.Metadata.Control.ReverseKey).FromYesNo());
if (GetInt64FieldValue(Data.Models.Metadata.Control.SensitivityKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Control.SensitivityKey, GetInt64FieldValue(Data.Models.Metadata.Control.SensitivityKey).ToString());
if (GetStringFieldValue(Data.Models.Metadata.Control.ControlTypeKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Control.ControlTypeKey, GetStringFieldValue(Data.Models.Metadata.Control.ControlTypeKey).AsControlType().AsStringValue());
}
public Control(Data.Models.Metadata.Control item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,43 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// SoftwareList dataarea information
/// </summary>
/// <remarks>One DataArea can contain multiple Rom items</remarks>
[JsonObject("dataarea"), XmlRoot("dataarea")]
public sealed class DataArea : DatItem<Data.Models.Metadata.DataArea>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.DataArea;
#endregion
#region Constructors
public DataArea() : base() { }
public DataArea(Data.Models.Metadata.DataArea item) : base(item)
{
// Process flag values
if (GetStringFieldValue(Data.Models.Metadata.DataArea.EndiannessKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.DataArea.EndiannessKey, GetStringFieldValue(Data.Models.Metadata.DataArea.EndiannessKey).AsEndianness().AsStringValue());
if (GetInt64FieldValue(Data.Models.Metadata.DataArea.SizeKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.DataArea.SizeKey, GetInt64FieldValue(Data.Models.Metadata.DataArea.SizeKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.DataArea.WidthKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.DataArea.WidthKey, GetInt64FieldValue(Data.Models.Metadata.DataArea.WidthKey).ToString());
}
public DataArea(Data.Models.Metadata.DataArea item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,98 @@
using System;
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Data.Extensions;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents a single device on the machine
/// </summary>
[JsonObject("device"), XmlRoot("device")]
public sealed class Device : DatItem<Data.Models.Metadata.Device>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Device;
[JsonIgnore]
public bool InstancesSpecified
{
get
{
var instances = GetFieldValue<Instance[]?>(Data.Models.Metadata.Device.InstanceKey);
return instances is not null && instances.Length > 0;
}
}
[JsonIgnore]
public bool ExtensionsSpecified
{
get
{
var extensions = GetFieldValue<Extension[]?>(Data.Models.Metadata.Device.ExtensionKey);
return extensions is not null && extensions.Length > 0;
}
}
#endregion
#region Constructors
public Device() : base() { }
public Device(Data.Models.Metadata.Device item) : base(item)
{
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.Device.MandatoryKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Device.MandatoryKey, GetBoolFieldValue(Data.Models.Metadata.Device.MandatoryKey).FromYesNo());
if (GetStringFieldValue(Data.Models.Metadata.Device.DeviceTypeKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Device.DeviceTypeKey, GetStringFieldValue(Data.Models.Metadata.Device.DeviceTypeKey).AsDeviceType().AsStringValue());
// Handle subitems
var instance = item.Read<Data.Models.Metadata.Instance>(Data.Models.Metadata.Device.InstanceKey);
if (instance is not null)
SetFieldValue<Instance?>(Data.Models.Metadata.Device.InstanceKey, new Instance(instance));
var extensions = item.ReadItemArray<Data.Models.Metadata.Extension>(Data.Models.Metadata.Device.ExtensionKey);
if (extensions is not null)
{
Extension[] extensionItems = Array.ConvertAll(extensions, extension => new Extension(extension));
SetFieldValue<Extension[]?>(Data.Models.Metadata.Device.ExtensionKey, extensionItems);
}
}
public Device(Data.Models.Metadata.Device item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override Data.Models.Metadata.Device GetInternalClone()
{
var deviceItem = base.GetInternalClone();
var instance = GetFieldValue<Instance?>(Data.Models.Metadata.Device.InstanceKey);
if (instance is not null)
deviceItem[Data.Models.Metadata.Device.InstanceKey] = instance.GetInternalClone();
var extensions = GetFieldValue<Extension[]?>(Data.Models.Metadata.Device.ExtensionKey);
if (extensions is not null)
{
Data.Models.Metadata.Extension[] extensionItems = Array.ConvertAll(extensions, extension => extension.GetInternalClone());
deviceItem[Data.Models.Metadata.Device.ExtensionKey] = extensionItems;
}
return deviceItem;
}
#endregion
}
}

View File

@@ -0,0 +1,33 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents which Device Reference(s) is associated with a set
/// </summary>
[JsonObject("device_ref"), XmlRoot("device_ref")]
public sealed class DeviceRef : DatItem<Data.Models.Metadata.DeviceRef>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.DeviceRef;
#endregion
#region Constructors
public DeviceRef() : base() { }
public DeviceRef(Data.Models.Metadata.DeviceRef item) : base(item) { }
public DeviceRef(Data.Models.Metadata.DeviceRef item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,39 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents one diplocation
/// </summary>
[JsonObject("diplocation"), XmlRoot("diplocation")]
public sealed class DipLocation : DatItem<Data.Models.Metadata.DipLocation>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.DipLocation;
#endregion
#region Constructors
public DipLocation() : base() { }
public DipLocation(Data.Models.Metadata.DipLocation item) : base(item)
{
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.DipLocation.InvertedKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.DipLocation.InvertedKey, GetBoolFieldValue(Data.Models.Metadata.DipLocation.InvertedKey).FromYesNo());
}
public DipLocation(Data.Models.Metadata.DipLocation item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,141 @@
using System;
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Data.Extensions;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents which DIP Switch(es) is associated with a set
/// </summary>
[JsonObject("dipswitch"), XmlRoot("dipswitch")]
public sealed class DipSwitch : DatItem<Data.Models.Metadata.DipSwitch>
{
#region Constants
/// <summary>
/// Non-standard key for inverted logic
/// </summary>
public const string PartKey = "PART";
#endregion
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.DipSwitch;
[JsonIgnore]
public bool ConditionsSpecified
{
get
{
var conditions = GetFieldValue<Condition[]?>(Data.Models.Metadata.DipSwitch.ConditionKey);
return conditions is not null && conditions.Length > 0;
}
}
[JsonIgnore]
public bool LocationsSpecified
{
get
{
var locations = GetFieldValue<DipLocation[]?>(Data.Models.Metadata.DipSwitch.DipLocationKey);
return locations is not null && locations.Length > 0;
}
}
[JsonIgnore]
public bool ValuesSpecified
{
get
{
var values = GetFieldValue<DipValue[]?>(Data.Models.Metadata.DipSwitch.DipValueKey);
return values is not null && values.Length > 0;
}
}
[JsonIgnore]
public bool PartSpecified
{
get
{
var part = GetFieldValue<Part?>(PartKey);
return part is not null
&& (!string.IsNullOrEmpty(part.GetName())
|| !string.IsNullOrEmpty(part.GetStringFieldValue(Data.Models.Metadata.Part.InterfaceKey)));
}
}
#endregion
#region Constructors
public DipSwitch() : base() { }
public DipSwitch(Data.Models.Metadata.DipSwitch item) : base(item)
{
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.DipSwitch.DefaultKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.DipSwitch.DefaultKey, GetBoolFieldValue(Data.Models.Metadata.DipSwitch.DefaultKey).FromYesNo());
// Handle subitems
var condition = item.Read<Data.Models.Metadata.Condition>(Data.Models.Metadata.DipSwitch.ConditionKey);
if (condition is not null)
SetFieldValue<Condition?>(Data.Models.Metadata.DipSwitch.ConditionKey, new Condition(condition));
var dipLocations = item.ReadItemArray<Data.Models.Metadata.DipLocation>(Data.Models.Metadata.DipSwitch.DipLocationKey);
if (dipLocations is not null)
{
DipLocation[] dipLocationItems = Array.ConvertAll(dipLocations, dipLocation => new DipLocation(dipLocation));
SetFieldValue<DipLocation[]?>(Data.Models.Metadata.DipSwitch.DipLocationKey, dipLocationItems);
}
var dipValues = item.ReadItemArray<Data.Models.Metadata.DipValue>(Data.Models.Metadata.DipSwitch.DipValueKey);
if (dipValues is not null)
{
DipValue[] dipValueItems = Array.ConvertAll(dipValues, dipValue => new DipValue(dipValue));
SetFieldValue<DipValue[]?>(Data.Models.Metadata.DipSwitch.DipValueKey, dipValueItems);
}
}
public DipSwitch(Data.Models.Metadata.DipSwitch item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override Data.Models.Metadata.DipSwitch GetInternalClone()
{
var dipSwitchItem = base.GetInternalClone();
var condition = GetFieldValue<Condition?>(Data.Models.Metadata.DipSwitch.ConditionKey);
if (condition is not null)
dipSwitchItem[Data.Models.Metadata.DipSwitch.ConditionKey] = condition.GetInternalClone();
var dipLocations = GetFieldValue<DipLocation[]?>(Data.Models.Metadata.DipSwitch.DipLocationKey);
if (dipLocations is not null)
{
Data.Models.Metadata.DipLocation[] dipLocationItems = Array.ConvertAll(dipLocations, dipLocation => dipLocation.GetInternalClone());
dipSwitchItem[Data.Models.Metadata.DipSwitch.DipLocationKey] = dipLocationItems;
}
var dipValues = GetFieldValue<DipValue[]?>(Data.Models.Metadata.DipSwitch.DipValueKey);
if (dipValues is not null)
{
Data.Models.Metadata.DipValue[] dipValueItems = Array.ConvertAll(dipValues, dipValue => dipValue.GetInternalClone());
dipSwitchItem[Data.Models.Metadata.DipSwitch.DipValueKey] = dipValueItems;
}
return dipSwitchItem;
}
#endregion
}
}

View File

@@ -0,0 +1,71 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents one ListXML dipvalue
/// </summary>
[JsonObject("dipvalue"), XmlRoot("dipvalue")]
public sealed class DipValue : DatItem<Data.Models.Metadata.DipValue>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.DipValue;
[JsonIgnore]
public bool ConditionsSpecified
{
get
{
var conditions = GetFieldValue<Condition[]?>(Data.Models.Metadata.DipValue.ConditionKey);
return conditions is not null && conditions.Length > 0;
}
}
#endregion
#region Constructors
public DipValue() : base() { }
public DipValue(Data.Models.Metadata.DipValue item) : base(item)
{
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.DipValue.DefaultKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.DipValue.DefaultKey, GetBoolFieldValue(Data.Models.Metadata.DipValue.DefaultKey).FromYesNo());
// Handle subitems
var condition = GetFieldValue<Data.Models.Metadata.Condition>(Data.Models.Metadata.DipValue.ConditionKey);
if (condition is not null)
SetFieldValue<Condition?>(Data.Models.Metadata.DipValue.ConditionKey, new Condition(condition));
}
public DipValue(Data.Models.Metadata.DipValue item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override Data.Models.Metadata.DipValue GetInternalClone()
{
var dipValueItem = base.GetInternalClone();
// Handle subitems
var subCondition = GetFieldValue<Condition>(Data.Models.Metadata.DipValue.ConditionKey);
if (subCondition is not null)
dipValueItem[Data.Models.Metadata.DipValue.ConditionKey] = subCondition.GetInternalClone();
return dipValueItem;
}
#endregion
}
}

View File

@@ -0,0 +1,184 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Data.Extensions;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents Compressed Hunks of Data (CHD) formatted disks which use internal hashes
/// </summary>
[JsonObject("disk"), XmlRoot("disk")]
public sealed class Disk : DatItem<Data.Models.Metadata.Disk>
{
#region Constants
/// <summary>
/// Non-standard key for inverted logic
/// </summary>
public const string DiskAreaKey = "DISKAREA";
/// <summary>
/// Non-standard key for inverted logic
/// </summary>
public const string PartKey = "PART";
#endregion
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Disk;
[JsonIgnore]
public bool DiskAreaSpecified
{
get
{
var diskArea = GetFieldValue<DiskArea?>(DiskAreaKey);
return diskArea is not null && !string.IsNullOrEmpty(diskArea.GetName());
}
}
[JsonIgnore]
public bool PartSpecified
{
get
{
var part = GetFieldValue<Part?>(PartKey);
return part is not null
&& (!string.IsNullOrEmpty(part.GetName())
|| !string.IsNullOrEmpty(part.GetStringFieldValue(Data.Models.Metadata.Part.InterfaceKey)));
}
}
#endregion
#region Constructors
public Disk() : base()
{
SetFieldValue<DupeType>(DupeTypeKey, 0x00);
SetFieldValue<string?>(Data.Models.Metadata.Disk.StatusKey, ItemStatus.None.AsStringValue());
}
public Disk(Data.Models.Metadata.Disk item) : base(item)
{
SetFieldValue<DupeType>(DupeTypeKey, 0x00);
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.Disk.OptionalKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Disk.OptionalKey, GetBoolFieldValue(Data.Models.Metadata.Disk.OptionalKey).FromYesNo());
if (GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Disk.StatusKey, GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey).AsItemStatus().AsStringValue());
if (GetBoolFieldValue(Data.Models.Metadata.Disk.WritableKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Disk.WritableKey, GetBoolFieldValue(Data.Models.Metadata.Disk.WritableKey).FromYesNo());
// Process hash values
if (GetStringFieldValue(Data.Models.Metadata.Disk.MD5Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Disk.MD5Key, TextHelper.NormalizeMD5(GetStringFieldValue(Data.Models.Metadata.Disk.MD5Key)));
if (GetStringFieldValue(Data.Models.Metadata.Disk.SHA1Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Disk.SHA1Key, TextHelper.NormalizeSHA1(GetStringFieldValue(Data.Models.Metadata.Disk.SHA1Key)));
}
public Disk(Data.Models.Metadata.Disk item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
#region Cloning Methods
/// <summary>
/// Convert a disk to the closest Rom approximation
/// </summary>
/// <returns></returns>
public Rom ConvertToRom()
{
var rom = new Rom(_internal.ConvertToRom()!);
// Create a DataArea if there was an existing DiskArea
var diskArea = GetFieldValue<DiskArea?>(DiskAreaKey);
if (diskArea is not null)
{
var dataArea = new DataArea();
string? diskAreaName = diskArea.GetStringFieldValue(Data.Models.Metadata.DiskArea.NameKey);
dataArea.SetFieldValue(Data.Models.Metadata.DataArea.NameKey, diskAreaName);
rom.SetFieldValue<DataArea?>(Rom.DataAreaKey, dataArea);
}
rom.SetFieldValue(DupeTypeKey, GetFieldValue<DupeType>(DupeTypeKey));
rom.SetFieldValue(MachineKey, GetMachine()?.Clone() as Machine);
rom.SetFieldValue(Rom.PartKey, GetFieldValue<Part>(PartKey)?.Clone() as Part);
rom.SetFieldValue(RemoveKey, GetBoolFieldValue(RemoveKey));
rom.SetFieldValue<Source?>(SourceKey, GetFieldValue<Source?>(SourceKey)?.Clone() as Source);
return rom;
}
#endregion
#region Comparision Methods
/// <summary>
/// Fill any missing size and hash information from another Disk
/// </summary>
/// <param name="other">Disk to fill information from</param>
public void FillMissingInformation(Disk 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.MD5:
key = GetStringFieldValue(Data.Models.Metadata.Disk.MD5Key);
break;
case ItemKey.SHA1:
key = GetStringFieldValue(Data.Models.Metadata.Disk.SHA1Key);
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
}
}

View File

@@ -0,0 +1,34 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// SoftwareList diskarea information
/// </summary>
/// <remarks>One DiskArea can contain multiple Disk items</remarks>
[JsonObject("diskarea"), XmlRoot("diskarea")]
public sealed class DiskArea : DatItem<Data.Models.Metadata.DiskArea>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.DiskArea;
#endregion
#region Constructors
public DiskArea() : base() { }
public DiskArea(Data.Models.Metadata.DiskArea item) : base(item) { }
public DiskArea(Data.Models.Metadata.DiskArea item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,106 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents one machine display
/// </summary>
[JsonObject("display"), XmlRoot("display")]
public sealed class Display : DatItem<Data.Models.Metadata.Display>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Display;
#endregion
#region Constructors
public Display() : base() { }
public Display(Data.Models.Metadata.Display item) : base(item)
{
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.Display.FlipXKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.FlipXKey, GetBoolFieldValue(Data.Models.Metadata.Display.FlipXKey).FromYesNo());
if (GetInt64FieldValue(Data.Models.Metadata.Display.HBEndKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.HBEndKey, GetInt64FieldValue(Data.Models.Metadata.Display.HBEndKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Display.HBStartKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.HBStartKey, GetInt64FieldValue(Data.Models.Metadata.Display.HBStartKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Display.HeightKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.HeightKey, GetInt64FieldValue(Data.Models.Metadata.Display.HeightKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Display.HTotalKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.HTotalKey, GetInt64FieldValue(Data.Models.Metadata.Display.HTotalKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Display.PixClockKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.PixClockKey, GetInt64FieldValue(Data.Models.Metadata.Display.PixClockKey).ToString());
if (GetDoubleFieldValue(Data.Models.Metadata.Display.RefreshKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.RefreshKey, GetDoubleFieldValue(Data.Models.Metadata.Display.RefreshKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Display.RotateKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.RotateKey, GetInt64FieldValue(Data.Models.Metadata.Display.RotateKey).ToString());
if (GetStringFieldValue(Data.Models.Metadata.Display.DisplayTypeKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.DisplayTypeKey, GetStringFieldValue(Data.Models.Metadata.Display.DisplayTypeKey).AsDisplayType().AsStringValue());
if (GetInt64FieldValue(Data.Models.Metadata.Display.VBEndKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.VBEndKey, GetInt64FieldValue(Data.Models.Metadata.Display.VBEndKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Display.VBStartKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.VBStartKey, GetInt64FieldValue(Data.Models.Metadata.Display.VBStartKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Display.VTotalKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.VTotalKey, GetInt64FieldValue(Data.Models.Metadata.Display.VTotalKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Display.WidthKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.WidthKey, GetInt64FieldValue(Data.Models.Metadata.Display.WidthKey).ToString());
}
public Display(Data.Models.Metadata.Display item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
public Display(Data.Models.Metadata.Video item) : base()
{
SetFieldValue(Data.Models.Metadata.Video.AspectXKey, NumberHelper.ConvertToInt64(item.ReadString(Data.Models.Metadata.Video.AspectXKey)));
SetFieldValue(Data.Models.Metadata.Video.AspectYKey, NumberHelper.ConvertToInt64(item.ReadString(Data.Models.Metadata.Video.AspectYKey)));
SetFieldValue<string?>(Data.Models.Metadata.Display.DisplayTypeKey, item.ReadString(Data.Models.Metadata.Video.ScreenKey).AsDisplayType().AsStringValue());
SetFieldValue(Data.Models.Metadata.Display.HeightKey, NumberHelper.ConvertToInt64(item.ReadString(Data.Models.Metadata.Video.HeightKey)));
SetFieldValue(Data.Models.Metadata.Display.RefreshKey, NumberHelper.ConvertToDouble(item.ReadString(Data.Models.Metadata.Video.RefreshKey)));
SetFieldValue(Data.Models.Metadata.Display.WidthKey, NumberHelper.ConvertToInt64(item.ReadString(Data.Models.Metadata.Video.WidthKey)));
switch (item.ReadString(Data.Models.Metadata.Video.OrientationKey))
{
case "horizontal":
SetFieldValue<long?>(Data.Models.Metadata.Display.RotateKey, 0);
break;
case "vertical":
SetFieldValue<long?>(Data.Models.Metadata.Display.RotateKey, 90);
break;
default:
// TODO: Log invalid values
break;
}
// Process flag values
if (GetInt64FieldValue(Data.Models.Metadata.Video.AspectXKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Video.AspectXKey, GetInt64FieldValue(Data.Models.Metadata.Video.AspectXKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Video.AspectYKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Video.AspectYKey, GetInt64FieldValue(Data.Models.Metadata.Video.AspectYKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Video.HeightKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.HeightKey, GetInt64FieldValue(Data.Models.Metadata.Video.HeightKey).ToString());
if (GetDoubleFieldValue(Data.Models.Metadata.Video.RefreshKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.RefreshKey, GetDoubleFieldValue(Data.Models.Metadata.Video.RefreshKey).ToString());
if (GetStringFieldValue(Data.Models.Metadata.Video.ScreenKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.DisplayTypeKey, GetStringFieldValue(Data.Models.Metadata.Video.ScreenKey).AsDisplayType().AsStringValue());
if (GetInt64FieldValue(Data.Models.Metadata.Video.WidthKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Display.WidthKey, GetInt64FieldValue(Data.Models.Metadata.Video.WidthKey).ToString());
}
public Display(Data.Models.Metadata.Video item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,59 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents the a driver of the machine
/// </summary>
[JsonObject("driver"), XmlRoot("driver")]
public sealed class Driver : DatItem<Data.Models.Metadata.Driver>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Driver;
#endregion
#region Constructors
public Driver() : base() { }
public Driver(Data.Models.Metadata.Driver item) : base(item)
{
// Process flag values
if (GetStringFieldValue(Data.Models.Metadata.Driver.CocktailKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Driver.CocktailKey, GetStringFieldValue(Data.Models.Metadata.Driver.CocktailKey).AsSupportStatus().AsStringValue());
if (GetStringFieldValue(Data.Models.Metadata.Driver.ColorKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Driver.ColorKey, GetStringFieldValue(Data.Models.Metadata.Driver.ColorKey).AsSupportStatus().AsStringValue());
if (GetStringFieldValue(Data.Models.Metadata.Driver.EmulationKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Driver.EmulationKey, GetStringFieldValue(Data.Models.Metadata.Driver.EmulationKey).AsSupportStatus().AsStringValue());
if (GetBoolFieldValue(Data.Models.Metadata.Driver.IncompleteKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Driver.IncompleteKey, GetBoolFieldValue(Data.Models.Metadata.Driver.IncompleteKey).FromYesNo());
if (GetBoolFieldValue(Data.Models.Metadata.Driver.NoSoundHardwareKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Driver.NoSoundHardwareKey, GetBoolFieldValue(Data.Models.Metadata.Driver.NoSoundHardwareKey).FromYesNo());
if (GetInt64FieldValue(Data.Models.Metadata.Driver.PaletteSizeKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Driver.PaletteSizeKey, GetInt64FieldValue(Data.Models.Metadata.Driver.PaletteSizeKey).ToString());
if (GetBoolFieldValue(Data.Models.Metadata.Driver.RequiresArtworkKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Driver.RequiresArtworkKey, GetBoolFieldValue(Data.Models.Metadata.Driver.RequiresArtworkKey).FromYesNo());
if (GetStringFieldValue(Data.Models.Metadata.Driver.SaveStateKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Driver.SaveStateKey, GetStringFieldValue(Data.Models.Metadata.Driver.SaveStateKey).AsSupported().AsStringValue(useSecond: true));
if (GetStringFieldValue(Data.Models.Metadata.Driver.SoundKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Driver.SoundKey, GetStringFieldValue(Data.Models.Metadata.Driver.SoundKey).AsSupportStatus().AsStringValue());
if (GetStringFieldValue(Data.Models.Metadata.Driver.StatusKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Driver.StatusKey, GetStringFieldValue(Data.Models.Metadata.Driver.StatusKey).AsSupportStatus().AsStringValue());
if (GetBoolFieldValue(Data.Models.Metadata.Driver.UnofficialKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Driver.UnofficialKey, GetBoolFieldValue(Data.Models.Metadata.Driver.UnofficialKey).FromYesNo());
}
public Driver(Data.Models.Metadata.Driver item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,33 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents a matchable extension
/// </summary>
[JsonObject("extension"), XmlRoot("extension")]
public sealed class Extension : DatItem<Data.Models.Metadata.Extension>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Extension;
#endregion
#region Constructors
public Extension() : base() { }
public Extension(Data.Models.Metadata.Extension item) : base(item) { }
public Extension(Data.Models.Metadata.Extension item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,42 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents the a feature of the machine
/// </summary>
[JsonObject("feature"), XmlRoot("feature")]
public sealed class Feature : DatItem<Data.Models.Metadata.Feature>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Feature;
#endregion
#region Constructors
public Feature() : base() { }
public Feature(Data.Models.Metadata.Feature item) : base(item)
{
// Process flag values
if (GetStringFieldValue(Data.Models.Metadata.Feature.OverallKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Feature.OverallKey, GetStringFieldValue(Data.Models.Metadata.Feature.OverallKey).AsFeatureStatus().AsStringValue());
if (GetStringFieldValue(Data.Models.Metadata.Feature.StatusKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Feature.StatusKey, GetStringFieldValue(Data.Models.Metadata.Feature.StatusKey).AsFeatureStatus().AsStringValue());
if (GetStringFieldValue(Data.Models.Metadata.Feature.FeatureTypeKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Feature.FeatureTypeKey, GetStringFieldValue(Data.Models.Metadata.Feature.FeatureTypeKey).AsFeatureType().AsStringValue());
}
public Feature(Data.Models.Metadata.Feature item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,323 @@
using System;
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Data.Extensions;
using SabreTools.Hashing;
using SabreTools.IO.Extensions;
using SabreTools.Metadata.Tools;
// TODO: Add item mappings for all fields
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents a single file item
/// </summary>
[JsonObject("file"), XmlRoot("file")]
public sealed class File : DatItem
{
#region Private instance variables
private byte[]? _crc; // 8 bytes
private byte[]? _md5; // 16 bytes
private byte[]? _sha1; // 20 bytes
private byte[]? _sha256; // 32 bytes
#endregion
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.File;
/// <summary>
/// ID value
/// </summary>
[JsonProperty("id", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("id")]
public string? Id { get; set; }
/// <summary>
/// Extension value
/// </summary>
[JsonProperty("extension", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("extension")]
public string? Extension { get; set; }
/// <summary>
/// Byte size of the rom
/// </summary>
[JsonProperty("size", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("size")]
public long? Size { get; set; } = null;
/// <summary>
/// File CRC32 hash
/// </summary>
[JsonProperty("crc", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("crc")]
public string? CRC
{
get { return _crc.ToHexString(); }
set { _crc = value == "null" ? HashType.CRC32.ZeroBytes : TextHelper.NormalizeCRC32(value).FromHexString(); }
}
/// <summary>
/// File MD5 hash
/// </summary>
[JsonProperty("md5", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("md5")]
public string? MD5
{
get { return _md5.ToHexString(); }
set { _md5 = TextHelper.NormalizeMD5(value).FromHexString(); }
}
/// <summary>
/// File SHA-1 hash
/// </summary>
[JsonProperty("sha1", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("sha1")]
public string? SHA1
{
get { return _sha1.ToHexString(); }
set { _sha1 = TextHelper.NormalizeSHA1(value).FromHexString(); }
}
/// <summary>
/// File SHA-256 hash
/// </summary>
[JsonProperty("sha256", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("sha256")]
public string? SHA256
{
get { return _sha256.ToHexString(); }
set { _sha256 = TextHelper.NormalizeSHA256(value).FromHexString(); }
}
/// <summary>
/// Format value
/// </summary>
[JsonProperty("format", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("format")]
public string? Format { get; set; }
#endregion
#region Constructors
/// <summary>
/// Create a default, empty File object
/// </summary>
public File()
{
SetFieldValue(Data.Models.Metadata.DatItem.TypeKey, ItemType);
}
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override object Clone()
{
var file = new File()
{
Id = this.Id,
Extension = this.Extension,
Size = this.Size,
_crc = this._crc,
_md5 = this._md5,
_sha1 = this._sha1,
_sha256 = this._sha256,
Format = this.Format,
};
file.SetFieldValue(DupeTypeKey, GetFieldValue<DupeType>(DupeTypeKey));
file.SetFieldValue(MachineKey, GetMachine()!.Clone() as Machine ?? new Machine());
file.SetFieldValue(RemoveKey, GetBoolFieldValue(RemoveKey));
file.SetFieldValue<Source?>(SourceKey, GetFieldValue<Source?>(SourceKey));
return file;
}
/// <summary>
/// Convert a disk to the closest Rom approximation
/// </summary>
/// <returns></returns>
public Rom ConvertToRom()
{
var rom = new Rom();
rom.SetName($"{Id}.{Extension}");
rom.SetFieldValue(Data.Models.Metadata.Rom.SizeKey, Size);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, CRC);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.MD5Key, MD5);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, SHA1);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA256Key, SHA256);
rom.SetFieldValue(DupeTypeKey, GetFieldValue<DupeType>(DupeTypeKey));
rom.SetFieldValue(MachineKey, GetMachine()?.Clone() as Machine);
rom.SetFieldValue(RemoveKey, GetBoolFieldValue(RemoveKey));
rom.SetFieldValue<Source?>(SourceKey, GetFieldValue<Source?>(SourceKey));
return rom;
}
#endregion
#region Comparision Methods
/// <inheritdoc/>
public override bool Equals(DatItem? other)
{
bool dupefound = false;
// If we don't have a file, return false
if (GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey) != other?.GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey))
return dupefound;
// Otherwise, treat it as a File
File? newOther = other as File;
// If all hashes are empty, then they're dupes
if (!HasHashes() && !newOther!.HasHashes())
{
dupefound = true;
}
// If we have a file that has no known size, rely on the hashes only
else if (Size is null && HashMatch(newOther!))
{
dupefound = true;
}
// Otherwise if we get a partial match
else if (Size == newOther!.Size && HashMatch(newOther))
{
dupefound = true;
}
return dupefound;
}
/// <summary>
/// Fill any missing size and hash information from another Rom
/// </summary>
/// <param name="other">File to fill information from</param>
public void FillMissingInformation(File other)
{
if (Size is null && other.Size is not null)
Size = other.Size;
if (_crc.IsNullOrEmpty() && !other._crc.IsNullOrEmpty())
_crc = other._crc;
if (_md5.IsNullOrEmpty() && !other._md5.IsNullOrEmpty())
_md5 = other._md5;
if (_sha1.IsNullOrEmpty() && !other._sha1.IsNullOrEmpty())
_sha1 = other._sha1;
if (_sha256.IsNullOrEmpty() && !other._sha256.IsNullOrEmpty())
_sha256 = other._sha256;
}
/// <summary>
/// Returns if the File contains any hashes
/// </summary>
/// <returns>True if any hash exists, false otherwise</returns>
public bool HasHashes()
{
return !_crc.IsNullOrEmpty()
|| !_md5.IsNullOrEmpty()
|| !_sha1.IsNullOrEmpty()
|| !_sha256.IsNullOrEmpty();
}
/// <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()
{
bool crcNull = string.IsNullOrEmpty(CRC) || string.Equals(CRC, HashType.CRC32.ZeroString, StringComparison.OrdinalIgnoreCase);
bool md5Null = string.IsNullOrEmpty(MD5) || string.Equals(MD5, HashType.MD5.ZeroString, StringComparison.OrdinalIgnoreCase);
bool sha1Null = string.IsNullOrEmpty(SHA1) || string.Equals(SHA1, HashType.SHA1.ZeroString, StringComparison.OrdinalIgnoreCase);
bool sha256Null = string.IsNullOrEmpty(SHA256) || string.Equals(SHA256, HashType.SHA256.ZeroString, StringComparison.OrdinalIgnoreCase);
return crcNull && md5Null && sha1Null && sha256Null;
}
/// <summary>
/// Returns if there are no, non-empty hashes in common with another File
/// </summary>
/// <param name="other">File to compare against</param>
/// <returns>True if at least one hash is not mutually exclusive, false otherwise</returns>
private bool HasCommonHash(File other)
{
return !(_crc.IsNullOrEmpty() ^ other._crc.IsNullOrEmpty())
|| !(_md5.IsNullOrEmpty() ^ other._md5.IsNullOrEmpty())
|| !(_sha1.IsNullOrEmpty() ^ other._sha1.IsNullOrEmpty())
|| !(_sha256.IsNullOrEmpty() ^ other._sha256.IsNullOrEmpty());
}
/// <summary>
/// Returns if any hashes are common with another File
/// </summary>
/// <param name="other">File to compare against</param>
/// <returns>True if any hashes are in common, false otherwise</returns>
private bool HashMatch(File other)
{
// If either have no hashes, we return false, otherwise this would be a false positive
if (!HasHashes() || !other.HasHashes())
return false;
// If neither have hashes in common, we return false, otherwise this would be a false positive
if (!HasCommonHash(other))
return false;
// Return if all hashes match according to merge rules
return MetadataExtensions.ConditionalHashEquals(_crc, other._crc)
&& MetadataExtensions.ConditionalHashEquals(_md5, other._md5)
&& MetadataExtensions.ConditionalHashEquals(_sha1, other._sha1)
&& MetadataExtensions.ConditionalHashEquals(_sha256, other._sha256);
}
#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.CRC:
key = CRC;
break;
case ItemKey.MD5:
key = MD5;
break;
case ItemKey.SHA1:
key = SHA1;
break;
case ItemKey.SHA256:
key = SHA256;
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
}
}

View File

@@ -0,0 +1,33 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents special information about a machine
/// </summary>
[JsonObject("info"), XmlRoot("info")]
public sealed class Info : DatItem<Data.Models.Metadata.Info>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Info;
#endregion
#region Constructors
public Info() : base() { }
public Info(Data.Models.Metadata.Info item) : base(item) { }
public Info(Data.Models.Metadata.Info item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Data.Extensions;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents one ListXML input
/// </summary>
[JsonObject("input"), XmlRoot("input")]
public sealed class Input : DatItem<Data.Models.Metadata.Input>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Input;
[JsonIgnore]
public bool ControlsSpecified
{
get
{
var controls = GetFieldValue<Control[]?>(Data.Models.Metadata.Input.ControlKey);
return controls is not null && controls.Length > 0;
}
}
#endregion
#region Constructors
public Input() : base() { }
public Input(Data.Models.Metadata.Input item) : base(item)
{
// Process flag values
if (GetInt64FieldValue(Data.Models.Metadata.Input.ButtonsKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Input.ButtonsKey, GetInt64FieldValue(Data.Models.Metadata.Input.ButtonsKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Input.CoinsKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Input.CoinsKey, GetInt64FieldValue(Data.Models.Metadata.Input.CoinsKey).ToString());
if (GetInt64FieldValue(Data.Models.Metadata.Input.PlayersKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Input.PlayersKey, GetInt64FieldValue(Data.Models.Metadata.Input.PlayersKey).ToString());
if (GetBoolFieldValue(Data.Models.Metadata.Input.ServiceKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Input.ServiceKey, GetBoolFieldValue(Data.Models.Metadata.Input.ServiceKey).FromYesNo());
if (GetBoolFieldValue(Data.Models.Metadata.Input.TiltKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Input.TiltKey, GetBoolFieldValue(Data.Models.Metadata.Input.TiltKey).FromYesNo());
// Handle subitems
var controls = item.ReadItemArray<Data.Models.Metadata.Control>(Data.Models.Metadata.Input.ControlKey);
if (controls is not null)
{
Control[] controlItems = Array.ConvertAll(controls, control => new Control(control));
SetFieldValue<Control[]?>(Data.Models.Metadata.Input.ControlKey, controlItems);
}
}
public Input(Data.Models.Metadata.Input item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override Data.Models.Metadata.Input GetInternalClone()
{
var inputItem = base.GetInternalClone();
var controls = GetFieldValue<Control[]?>(Data.Models.Metadata.Input.ControlKey);
if (controls is not null)
{
Data.Models.Metadata.Control[] controlItems = Array.ConvertAll(controls, control => control.GetInternalClone());
inputItem[Data.Models.Metadata.Input.ControlKey] = controlItems;
}
return inputItem;
}
#endregion
}
}

View File

@@ -0,0 +1,33 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents a single instance of another item
/// </summary>
[JsonObject("instance"), XmlRoot("instance")]
public sealed class Instance : DatItem<Data.Models.Metadata.Instance>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Instance;
#endregion
#region Constructors
public Instance() : base() { }
public Instance(Data.Models.Metadata.Instance item) : base(item) { }
public Instance(Data.Models.Metadata.Instance item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,136 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Data.Extensions;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents Aaruformat images which use internal hashes
/// </summary>
[JsonObject("media"), XmlRoot("media")]
public sealed class Media : DatItem<Data.Models.Metadata.Media>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Media;
#endregion
#region Constructors
public Media() : base()
{
SetFieldValue<DupeType>(DupeTypeKey, 0x00);
}
public Media(Data.Models.Metadata.Media item) : base(item)
{
SetFieldValue<DupeType>(DupeTypeKey, 0x00);
// Process hash values
if (GetStringFieldValue(Data.Models.Metadata.Media.MD5Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Media.MD5Key, TextHelper.NormalizeMD5(GetStringFieldValue(Data.Models.Metadata.Media.MD5Key)));
if (GetStringFieldValue(Data.Models.Metadata.Media.SHA1Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Media.SHA1Key, TextHelper.NormalizeSHA1(GetStringFieldValue(Data.Models.Metadata.Media.SHA1Key)));
if (GetStringFieldValue(Data.Models.Metadata.Media.SHA256Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Media.SHA256Key, TextHelper.NormalizeSHA256(GetStringFieldValue(Data.Models.Metadata.Media.SHA256Key)));
}
public Media(Data.Models.Metadata.Media item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
#region Cloning Methods
/// <summary>
/// Convert a media to the closest Rom approximation
/// </summary>
/// <returns></returns>
public Rom ConvertToRom()
{
var rom = new Rom(_internal.ConvertToRom()!);
rom.SetFieldValue(DupeTypeKey, GetFieldValue<DupeType>(DupeTypeKey));
rom.SetFieldValue(MachineKey, GetMachine());
rom.SetFieldValue(RemoveKey, GetBoolFieldValue(RemoveKey));
rom.SetFieldValue<Source?>(SourceKey, GetFieldValue<Source?>(SourceKey));
return rom;
}
#endregion
#region Comparision Methods
/// <summary>
/// Fill any missing size and hash information from another Media
/// </summary>
/// <param name="other">Media to fill information from</param>
public void FillMissingInformation(Media 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.MD5:
key = GetStringFieldValue(Data.Models.Metadata.Media.MD5Key);
break;
case ItemKey.SHA1:
key = GetStringFieldValue(Data.Models.Metadata.Media.SHA1Key);
break;
case ItemKey.SHA256:
key = GetStringFieldValue(Data.Models.Metadata.Media.SHA256Key);
break;
case ItemKey.SpamSum:
key = GetStringFieldValue(Data.Models.Metadata.Media.SpamSumKey);
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
}
}

View File

@@ -0,0 +1,32 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents the OpenMSX original value
/// </summary>
[JsonObject("original"), XmlRoot("original")]
public sealed class Original
{
[JsonProperty("value"), XmlElement("value")]
public bool? Value
{
get => _internal.ReadBool(Data.Models.Metadata.Original.ValueKey);
set => _internal[Data.Models.Metadata.Original.ValueKey] = value;
}
[JsonProperty("content"), XmlElement("content")]
public string? Content
{
get => _internal.ReadString(Data.Models.Metadata.Original.ContentKey);
set => _internal[Data.Models.Metadata.Original.ContentKey] = value;
}
/// <summary>
/// Internal Original model
/// </summary>
[JsonIgnore]
private readonly Data.Models.Metadata.Original _internal = [];
}
}

View File

@@ -0,0 +1,44 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// SoftwareList part information
/// </summary>
/// <remarks>One Part can contain multiple PartFeature, DataArea, DiskArea, and DipSwitch items</remarks>
[JsonObject("part"), XmlRoot("part")]
public sealed class Part : DatItem<Data.Models.Metadata.Part>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Part;
[JsonIgnore]
public bool FeaturesSpecified
{
get
{
var features = GetFieldValue<PartFeature[]?>(Data.Models.Metadata.Part.FeatureKey);
return features is not null && features.Length > 0;
}
}
#endregion
#region Constructors
public Part() : base() { }
public Part(Data.Models.Metadata.Part item) : base(item) { }
public Part(Data.Models.Metadata.Part item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,51 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents one part feature object
/// </summary>
[JsonObject("part_feature"), XmlRoot("part_feature")]
public sealed class PartFeature : DatItem<Data.Models.Metadata.Feature>
{
#region Constants
/// <summary>
/// Non-standard key for inverted logic
/// </summary>
public const string PartKey = "PART";
#endregion
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.PartFeature;
#endregion
#region Constructors
public PartFeature() : base() { }
public PartFeature(Data.Models.Metadata.Feature item) : base(item)
{
// Process flag values
if (GetStringFieldValue(Data.Models.Metadata.Feature.OverallKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Feature.OverallKey, GetStringFieldValue(Data.Models.Metadata.Feature.OverallKey).AsFeatureStatus().AsStringValue());
if (GetStringFieldValue(Data.Models.Metadata.Feature.StatusKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Feature.StatusKey, GetStringFieldValue(Data.Models.Metadata.Feature.StatusKey).AsFeatureStatus().AsStringValue());
if (GetStringFieldValue(Data.Models.Metadata.Feature.FeatureTypeKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Feature.FeatureTypeKey, GetStringFieldValue(Data.Models.Metadata.Feature.FeatureTypeKey).AsFeatureType().AsStringValue());
}
public PartFeature(Data.Models.Metadata.Feature item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Data.Extensions;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents a single port on a machine
/// </summary>
[JsonObject("port"), XmlRoot("port")]
public sealed class Port : DatItem<Data.Models.Metadata.Port>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Port;
[JsonIgnore]
public bool AnalogsSpecified
{
get
{
var analogs = GetFieldValue<Analog[]?>(Data.Models.Metadata.Port.AnalogKey);
return analogs is not null && analogs.Length > 0;
}
}
#endregion
#region Constructors
public Port() : base() { }
public Port(Data.Models.Metadata.Port item) : base(item)
{
// Handle subitems
var analogs = item.ReadItemArray<Data.Models.Metadata.Analog>(Data.Models.Metadata.Port.AnalogKey);
if (analogs is not null)
{
Analog[] analogItems = Array.ConvertAll(analogs, analog => new Analog(analog));
SetFieldValue<Analog[]?>(Data.Models.Metadata.Port.AnalogKey, analogItems);
}
}
public Port(Data.Models.Metadata.Port item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override Data.Models.Metadata.Port GetInternalClone()
{
var portItem = base.GetInternalClone();
var analogs = GetFieldValue<Analog[]?>(Data.Models.Metadata.Port.AnalogKey);
if (analogs is not null)
{
Data.Models.Metadata.Analog[] analogItems = Array.ConvertAll(analogs, analog => analog.GetInternalClone());
portItem[Data.Models.Metadata.Port.AnalogKey] = analogItems;
}
return portItem;
}
#endregion
}
}

View File

@@ -0,0 +1,39 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents which RAM option(s) is associated with a set
/// </summary>
[JsonObject("ramoption"), XmlRoot("ramoption")]
public sealed class RamOption : DatItem<Data.Models.Metadata.RamOption>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.RamOption;
#endregion
#region Constructors
public RamOption() : base() { }
public RamOption(Data.Models.Metadata.RamOption item) : base(item)
{
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.RamOption.DefaultKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.RamOption.DefaultKey, GetBoolFieldValue(Data.Models.Metadata.RamOption.DefaultKey).FromYesNo());
}
public RamOption(Data.Models.Metadata.RamOption item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,39 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents release information about a set
/// </summary>
[JsonObject("release"), XmlRoot("release")]
public sealed class Release : DatItem<Data.Models.Metadata.Release>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Release;
#endregion
#region Constructors
public Release() : base() { }
public Release(Data.Models.Metadata.Release item) : base(item)
{
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.Release.DefaultKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Release.DefaultKey, GetBoolFieldValue(Data.Models.Metadata.Release.DefaultKey).FromYesNo());
}
public Release(Data.Models.Metadata.Release item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,189 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
// TODO: Add item mappings for all fields
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents a single release details item
/// </summary>
[JsonObject("release_details"), XmlRoot("release_details")]
public sealed class ReleaseDetails : DatItem
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.ReleaseDetails;
/// <summary>
/// Id value
/// </summary>
/// <remarks>TODO: Is this required?</remarks>
[JsonProperty("id", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("id")]
public string? Id { get; set; }
/// <summary>
/// Directory name value
/// </summary>
[JsonProperty("dirname", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("dirname")]
public string? DirName { get; set; }
/// <summary>
/// Rom info value
/// </summary>
[JsonProperty("rominfo", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("rominfo")]
public string? RomInfo { get; set; }
/// <summary>
/// Category value
/// </summary>
[JsonProperty("category", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("category")]
public string? Category { get; set; }
/// <summary>
/// NFO name value
/// </summary>
[JsonProperty("nfoname", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("nfoname")]
public string? NfoName { get; set; }
/// <summary>
/// NFO size value
/// </summary>
[JsonProperty("nfosize", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("nfosize")]
public long? NfoSize { get; set; }
/// <summary>
/// NFO CRC value
/// </summary>
[JsonProperty("nfocrc", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("nfocrc")]
public string? NfoCrc { get; set; }
/// <summary>
/// Archive name value
/// </summary>
[JsonProperty("archivename", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("archivename")]
public string? ArchiveName { get; set; }
/// <summary>
/// Original format value
/// </summary>
[JsonProperty("originalformat", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("originalformat")]
public string? OriginalFormat { get; set; }
/// <summary>
/// Date value
/// </summary>
[JsonProperty("date", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("date")]
public string? Date { get; set; }
/// <summary>
/// Grpup value
/// </summary>
[JsonProperty("group", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("group")]
public string? Group { get; set; }
/// <summary>
/// Comment value
/// </summary>
[JsonProperty("comment", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("comment")]
public string? Comment { get; set; }
/// <summary>
/// Tool value
/// </summary>
[JsonProperty("tool", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("tool")]
public string? Tool { get; set; }
/// <summary>
/// Region value
/// </summary>
[JsonProperty("region", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("region")]
public string? Region { get; set; }
/// <summary>
/// Origin value
/// </summary>
[JsonProperty("origin", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("origin")]
public string? Origin { get; set; }
#endregion
#region Constructors
/// <summary>
/// Create a default, empty ReleaseDetails object
/// </summary>
public ReleaseDetails()
{
SetFieldValue(Data.Models.Metadata.DatItem.TypeKey, ItemType);
}
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override object Clone()
{
var releaseDetails = new ReleaseDetails()
{
Id = this.Id,
DirName = this.DirName,
RomInfo = this.RomInfo,
Category = this.Category,
NfoName = this.NfoName,
NfoSize = this.NfoSize,
NfoCrc = this.NfoCrc,
ArchiveName = this.ArchiveName,
OriginalFormat = this.OriginalFormat,
Date = this.Date,
Group = this.Group,
Comment = this.Comment,
Tool = this.Tool,
Region = this.Region,
Origin = this.Origin,
};
releaseDetails.SetFieldValue(DupeTypeKey, GetFieldValue<DupeType>(DupeTypeKey));
releaseDetails.SetFieldValue(MachineKey, GetMachine());
releaseDetails.SetFieldValue(RemoveKey, GetBoolFieldValue(RemoveKey));
releaseDetails.SetFieldValue<Source?>(SourceKey, GetFieldValue<Source?>(SourceKey));
releaseDetails.SetFieldValue<string?>(Data.Models.Metadata.DatItem.TypeKey, GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey).AsItemType().AsStringValue());
return releaseDetails;
}
#endregion
#region Comparision Methods
/// <inheritdoc/>
public override bool Equals(DatItem? other)
{
// If we don't have a Details, return false
if (GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey) != other?.GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey))
return false;
// Otherwise, treat it as a Details
ReleaseDetails? newOther = other as ReleaseDetails;
// If the Details information matches
return Id == newOther!.Id
&& DirName == newOther.DirName
&& RomInfo == newOther.RomInfo
&& Category == newOther.Category
&& NfoName == newOther.NfoName
&& NfoSize == newOther.NfoSize
&& NfoCrc == newOther.NfoCrc
&& ArchiveName == newOther.ArchiveName
&& OriginalFormat == newOther.OriginalFormat
&& Date == newOther.Date
&& Group == newOther.Group
&& Comment == newOther.Comment
&& Tool == newOther.Tool
&& Region == newOther.Region
&& Origin == newOther.Origin;
}
#endregion
}
}

View File

@@ -0,0 +1,312 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Data.Extensions;
using SabreTools.Metadata.Tools;
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 Constants
/// <summary>
/// Non-standard key for inverted logic
/// </summary>
public const string DataAreaKey = "DATAAREA";
/// <summary>
/// Non-standard key for inverted logic
/// </summary>
public const string PartKey = "PART";
#endregion
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Rom;
[JsonIgnore]
public bool ItemStatusSpecified
{
get
{
var status = GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus();
return status != ItemStatus.NULL && status != ItemStatus.None;
}
}
[JsonIgnore]
public bool OriginalSpecified
{
get
{
var original = GetFieldValue<Original?>("ORIGINAL");
return original is not null && original != default;
}
}
[JsonIgnore]
public bool DataAreaSpecified
{
get
{
var dataArea = GetFieldValue<DataArea?>(DataAreaKey);
return dataArea is not null
&& (!string.IsNullOrEmpty(dataArea.GetName())
|| dataArea.GetInt64FieldValue(Data.Models.Metadata.DataArea.SizeKey) is not null
|| dataArea.GetInt64FieldValue(Data.Models.Metadata.DataArea.WidthKey) is not null
|| dataArea.GetStringFieldValue(Data.Models.Metadata.DataArea.EndiannessKey).AsEndianness() != Endianness.NULL);
}
}
[JsonIgnore]
public bool PartSpecified
{
get
{
var part = GetFieldValue<Part?>(PartKey);
return part is not null
&& (!string.IsNullOrEmpty(part.GetName())
|| !string.IsNullOrEmpty(part.GetStringFieldValue(Data.Models.Metadata.Part.InterfaceKey)));
}
}
#endregion
#region Constructors
public Rom() : base()
{
SetFieldValue<DupeType>(DupeTypeKey, 0x00);
SetFieldValue<string?>(Data.Models.Metadata.Rom.StatusKey, ItemStatus.None.AsStringValue());
}
public Rom(Data.Models.Metadata.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 = OpenMSXSubType.NULL;
if (item.Read<Data.Models.Metadata.Rom>(Data.Models.Metadata.Dump.RomKey) is not null)
{
rom = item.Read<Data.Models.Metadata.Rom>(Data.Models.Metadata.Dump.RomKey);
subType = OpenMSXSubType.Rom;
}
else if (item.Read<Data.Models.Metadata.Rom>(Data.Models.Metadata.Dump.MegaRomKey) is not null)
{
rom = item.Read<Data.Models.Metadata.Rom>(Data.Models.Metadata.Dump.MegaRomKey);
subType = OpenMSXSubType.MegaRom;
}
else if (item.Read<Data.Models.Metadata.Rom>(Data.Models.Metadata.Dump.SCCPlusCartKey) is not null)
{
rom = item.Read<Data.Models.Metadata.Rom>(Data.Models.Metadata.Dump.SCCPlusCartKey);
subType = OpenMSXSubType.SCCPlusCart;
}
// Just return if nothing valid was found
if (rom is null)
return;
string name = $"{machine.GetName()}_{index++}{(!string.IsNullOrEmpty(rom!.ReadString(Data.Models.Metadata.Rom.RemarkKey)) ? $" {rom.ReadString(Data.Models.Metadata.Rom.RemarkKey)}" : string.Empty)}";
SetName(name);
SetFieldValue<string?>(Data.Models.Metadata.Rom.OffsetKey, rom.ReadString(Data.Models.Metadata.Rom.StartKey));
SetFieldValue<string?>(Data.Models.Metadata.Rom.OpenMSXMediaType, subType.AsStringValue());
SetFieldValue<string?>(Data.Models.Metadata.Rom.OpenMSXType, rom.ReadString(Data.Models.Metadata.Rom.OpenMSXType) ?? rom.ReadString(Data.Models.Metadata.DatItem.TypeKey));
SetFieldValue<string?>(Data.Models.Metadata.Rom.RemarkKey, rom.ReadString(Data.Models.Metadata.Rom.RemarkKey));
SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, rom.ReadString(Data.Models.Metadata.Rom.SHA1Key));
SetFieldValue<string?>(Data.Models.Metadata.Rom.StartKey, rom.ReadString(Data.Models.Metadata.Rom.StartKey));
SetFieldValue<Source?>(SourceKey, source);
if (item.Read<Data.Models.Metadata.Original>(Data.Models.Metadata.Dump.OriginalKey) is not null)
{
var original = item.Read<Data.Models.Metadata.Original>(Data.Models.Metadata.Dump.OriginalKey)!;
SetFieldValue<Original?>("ORIGINAL", new Original
{
Value = original.ReadBool(Data.Models.Metadata.Original.ValueKey),
Content = original.ReadString(Data.Models.Metadata.Original.ContentKey),
});
}
CopyMachineInformation(machine);
// Process hash values
if (GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey).ToString());
if (GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, TextHelper.NormalizeCRC32(GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.MD2Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.MD2Key, TextHelper.NormalizeMD2(GetStringFieldValue(Data.Models.Metadata.Rom.MD2Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.MD4Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.MD4Key, TextHelper.NormalizeMD5(GetStringFieldValue(Data.Models.Metadata.Rom.MD4Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.MD5Key, TextHelper.NormalizeMD5(GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.RIPEMD128Key, TextHelper.NormalizeRIPEMD128(GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.RIPEMD160Key, TextHelper.NormalizeRIPEMD160(GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, TextHelper.NormalizeSHA1(GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA256Key, TextHelper.NormalizeSHA256(GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.SHA384Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA384Key, TextHelper.NormalizeSHA384(GetStringFieldValue(Data.Models.Metadata.Rom.SHA384Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.SHA512Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA512Key, TextHelper.NormalizeSHA512(GetStringFieldValue(Data.Models.Metadata.Rom.SHA512Key)));
}
public Rom(Data.Models.Metadata.Rom item) : base(item)
{
SetFieldValue<DupeType>(DupeTypeKey, 0x00);
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.Rom.DisposeKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.DisposeKey, GetBoolFieldValue(Data.Models.Metadata.Rom.DisposeKey).FromYesNo());
if (GetBoolFieldValue(Data.Models.Metadata.Rom.InvertedKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.InvertedKey, GetBoolFieldValue(Data.Models.Metadata.Rom.InvertedKey).FromYesNo());
if (GetStringFieldValue(Data.Models.Metadata.Rom.LoadFlagKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.LoadFlagKey, GetStringFieldValue(Data.Models.Metadata.Rom.LoadFlagKey).AsLoadFlag().AsStringValue());
if (GetStringFieldValue(Data.Models.Metadata.Rom.OpenMSXMediaType) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.OpenMSXMediaType, GetStringFieldValue(Data.Models.Metadata.Rom.OpenMSXMediaType).AsOpenMSXSubType().AsStringValue());
if (GetBoolFieldValue(Data.Models.Metadata.Rom.MIAKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.MIAKey, GetBoolFieldValue(Data.Models.Metadata.Rom.MIAKey).FromYesNo());
if (GetBoolFieldValue(Data.Models.Metadata.Rom.OptionalKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.OptionalKey, GetBoolFieldValue(Data.Models.Metadata.Rom.OptionalKey).FromYesNo());
if (GetBoolFieldValue(Data.Models.Metadata.Rom.SoundOnlyKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.SoundOnlyKey, GetBoolFieldValue(Data.Models.Metadata.Rom.SoundOnlyKey).FromYesNo());
if (GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.StatusKey, GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus().AsStringValue());
// Process hash values
if (GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey).ToString());
if (GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, TextHelper.NormalizeCRC32(GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.MD2Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.MD2Key, TextHelper.NormalizeMD2(GetStringFieldValue(Data.Models.Metadata.Rom.MD2Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.MD4Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.MD4Key, TextHelper.NormalizeMD4(GetStringFieldValue(Data.Models.Metadata.Rom.MD4Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.MD5Key, TextHelper.NormalizeMD5(GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.RIPEMD128Key, TextHelper.NormalizeRIPEMD128(GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.RIPEMD160Key, TextHelper.NormalizeRIPEMD160(GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, TextHelper.NormalizeSHA1(GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA256Key, TextHelper.NormalizeSHA256(GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.SHA384Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA384Key, TextHelper.NormalizeSHA384(GetStringFieldValue(Data.Models.Metadata.Rom.SHA384Key)));
if (GetStringFieldValue(Data.Models.Metadata.Rom.SHA512Key) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA512Key, TextHelper.NormalizeSHA512(GetStringFieldValue(Data.Models.Metadata.Rom.SHA512Key)));
}
public Rom(Data.Models.Metadata.Rom item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
#region Comparision Methods
/// <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.CRC:
key = GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey);
break;
case ItemKey.MD2:
key = GetStringFieldValue(Data.Models.Metadata.Rom.MD2Key);
break;
case ItemKey.MD4:
key = GetStringFieldValue(Data.Models.Metadata.Rom.MD4Key);
break;
case ItemKey.MD5:
key = GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key);
break;
case ItemKey.RIPEMD128:
key = GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key);
break;
case ItemKey.RIPEMD160:
key = GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key);
break;
case ItemKey.SHA1:
key = GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key);
break;
case ItemKey.SHA256:
key = GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key);
break;
case ItemKey.SHA384:
key = GetStringFieldValue(Data.Models.Metadata.Rom.SHA384Key);
break;
case ItemKey.SHA512:
key = GetStringFieldValue(Data.Models.Metadata.Rom.SHA512Key);
break;
case ItemKey.SpamSum:
key = GetStringFieldValue(Data.Models.Metadata.Rom.SpamSumKey);
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
}
}

View File

@@ -0,0 +1,33 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents a (usually WAV-formatted) sample to be included for use in the set
/// </summary>
[JsonObject("sample"), XmlRoot("sample")]
public class Sample : DatItem<Data.Models.Metadata.Sample>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Sample;
#endregion
#region Constructors
public Sample() : base() { }
public Sample(Data.Models.Metadata.Sample item) : base(item) { }
public Sample(Data.Models.Metadata.Sample item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,180 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
// TODO: Add item mappings for all fields
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents a single serials item
/// </summary>
[JsonObject("serials"), XmlRoot("serials")]
public sealed class Serials : DatItem
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Serials;
/// <summary>
/// Digital serial 1 value
/// </summary>
[JsonProperty("digital_serial1", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("digital_serial1")]
public string? DigitalSerial1 { get; set; }
/// <summary>
/// Digital serial 2 value
/// </summary>
[JsonProperty("digital_serial2", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("digital_serial2")]
public string? DigitalSerial2 { get; set; }
/// <summary>
/// Media serial 1 value
/// </summary>
[JsonProperty("media_serial1", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("media_serial1")]
public string? MediaSerial1 { get; set; }
/// <summary>
/// Media serial 2 value
/// </summary>
[JsonProperty("media_serial2", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("media_serial2")]
public string? MediaSerial2 { get; set; }
/// <summary>
/// Media serial 3 value
/// </summary>
[JsonProperty("media_serial3", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("media_serial3")]
public string? MediaSerial3 { get; set; }
/// <summary>
/// PCB serial value
/// </summary>
[JsonProperty("pcb_serial", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("pcb_serial")]
public string? PcbSerial { get; set; }
/// <summary>
/// Rom chip serial 1 value
/// </summary>
[JsonProperty("romchip_serial1", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("romchip_serial1")]
public string? RomChipSerial1 { get; set; }
/// <summary>
/// Rom chip serial 2 value
/// </summary>
[JsonProperty("romchip_serial2", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("romchip_serial2")]
public string? RomChipSerial2 { get; set; }
/// <summary>
/// Lockout serial value
/// </summary>
[JsonProperty("lockout_serial", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("lockout_serial")]
public string? LockoutSerial { get; set; }
/// <summary>
/// Save chip serial value
/// </summary>
[JsonProperty("savechip_serial", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("savechip_serial")]
public string? SaveChipSerial { get; set; }
/// <summary>
/// Chip serial value
/// </summary>
[JsonProperty("chip_serial", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("chip_serial")]
public string? ChipSerial { get; set; }
/// <summary>
/// Box serial value
/// </summary>
[JsonProperty("box_serial", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("box_serial")]
public string? BoxSerial { get; set; }
/// <summary>
/// Media stamp value
/// </summary>
[JsonProperty("mediastamp", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("mediastamp")]
public string? MediaStamp { get; set; }
/// <summary>
/// Box barcode value
/// </summary>
[JsonProperty("box_barcode", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("box_barcode")]
public string? BoxBarcode { get; set; }
#endregion
#region Constructors
/// <summary>
/// Create a default, empty Serials object
/// </summary>
public Serials()
{
SetFieldValue(Data.Models.Metadata.DatItem.TypeKey, ItemType);
}
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override object Clone()
{
var serials = new Serials()
{
DigitalSerial1 = this.DigitalSerial1,
DigitalSerial2 = this.DigitalSerial2,
MediaSerial1 = this.MediaSerial1,
MediaSerial2 = this.MediaSerial2,
MediaSerial3 = this.MediaSerial3,
PcbSerial = this.PcbSerial,
RomChipSerial1 = this.RomChipSerial1,
RomChipSerial2 = this.RomChipSerial2,
LockoutSerial = this.LockoutSerial,
SaveChipSerial = this.SaveChipSerial,
ChipSerial = this.ChipSerial,
BoxSerial = this.BoxSerial,
MediaStamp = this.MediaStamp,
BoxBarcode = this.BoxBarcode,
};
serials.SetFieldValue(DupeTypeKey, GetFieldValue<DupeType>(DupeTypeKey));
serials.SetFieldValue(MachineKey, GetMachine());
serials.SetFieldValue(RemoveKey, GetBoolFieldValue(RemoveKey));
serials.SetFieldValue<Source?>(SourceKey, GetFieldValue<Source?>(SourceKey));
serials.SetFieldValue<string?>(Data.Models.Metadata.DatItem.TypeKey, GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey).AsItemType().AsStringValue());
return serials;
}
#endregion
#region Comparision Methods
/// <inheritdoc/>
public override bool Equals(DatItem? other)
{
// If we don't have a Serials, return false
if (GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey) != other?.GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey))
return false;
// Otherwise, treat it as a Serials
Serials? newOther = other as Serials;
// If the Serials information matches
return DigitalSerial1 == newOther!.DigitalSerial1
&& DigitalSerial2 == newOther.DigitalSerial2
&& MediaSerial1 == newOther.MediaSerial1
&& MediaSerial2 == newOther.MediaSerial2
&& MediaSerial3 == newOther.MediaSerial3
&& PcbSerial == newOther.PcbSerial
&& RomChipSerial1 == newOther.RomChipSerial1
&& RomChipSerial2 == newOther.RomChipSerial2
&& LockoutSerial == newOther.LockoutSerial
&& SaveChipSerial == newOther.SaveChipSerial
&& ChipSerial == newOther.ChipSerial
&& BoxSerial == newOther.BoxSerial
&& MediaStamp == newOther.MediaStamp
&& BoxBarcode == newOther.BoxBarcode;
}
#endregion
}
}

View File

@@ -0,0 +1,33 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents one shared feature object
/// </summary>
[JsonObject("sharedfeat"), XmlRoot("sharedfeat")]
public sealed class SharedFeat : DatItem<Data.Models.Metadata.SharedFeat>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.SharedFeat;
#endregion
#region Constructors
public SharedFeat() : base() { }
public SharedFeat(Data.Models.Metadata.SharedFeat item) : base(item) { }
public SharedFeat(Data.Models.Metadata.SharedFeat item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Data.Extensions;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents which Slot(s) is associated with a set
/// </summary>
[JsonObject("slot"), XmlRoot("slot")]
public sealed class Slot : DatItem<Data.Models.Metadata.Slot>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Slot;
[JsonIgnore]
public bool SlotOptionsSpecified
{
get
{
var slotOptions = GetFieldValue<SlotOption[]?>(Data.Models.Metadata.Slot.SlotOptionKey);
return slotOptions is not null && slotOptions.Length > 0;
}
}
#endregion
#region Constructors
public Slot() : base() { }
public Slot(Data.Models.Metadata.Slot item) : base(item)
{
// Handle subitems
var slotOptions = item.ReadItemArray<Data.Models.Metadata.SlotOption>(Data.Models.Metadata.Slot.SlotOptionKey);
if (slotOptions is not null)
{
SlotOption[] slotOptionItems = Array.ConvertAll(slotOptions, slotOption => new SlotOption(slotOption));
SetFieldValue<SlotOption[]?>(Data.Models.Metadata.Slot.SlotOptionKey, slotOptionItems);
}
}
public Slot(Data.Models.Metadata.Slot item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override Data.Models.Metadata.Slot GetInternalClone()
{
var slotItem = base.GetInternalClone();
var slotOptions = GetFieldValue<SlotOption[]?>(Data.Models.Metadata.Slot.SlotOptionKey);
if (slotOptions is not null)
{
Data.Models.Metadata.SlotOption[] slotOptionItems = Array.ConvertAll(slotOptions, slotOption => slotOption.GetInternalClone());
slotItem[Data.Models.Metadata.Slot.SlotOptionKey] = slotOptionItems;
}
return slotItem;
}
#endregion
}
}

View File

@@ -0,0 +1,39 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents one ListXML slotoption
/// </summary>
[JsonObject("slotoption"), XmlRoot("slotoption")]
public sealed class SlotOption : DatItem<Data.Models.Metadata.SlotOption>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.SlotOption;
#endregion
#region Constructors
public SlotOption() : base() { }
public SlotOption(Data.Models.Metadata.SlotOption item) : base(item)
{
// Process flag values
if (GetBoolFieldValue(Data.Models.Metadata.SlotOption.DefaultKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.SlotOption.DefaultKey, GetBoolFieldValue(Data.Models.Metadata.SlotOption.DefaultKey).FromYesNo());
}
public SlotOption(Data.Models.Metadata.SlotOption item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,41 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents which SoftwareList(s) is associated with a set
/// </summary>
[JsonObject("softwarelist"), XmlRoot("softwarelist")]
public sealed class SoftwareList : DatItem<Data.Models.Metadata.SoftwareList>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.SoftwareList;
#endregion
#region Constructors
public SoftwareList() : base() { }
public SoftwareList(Data.Models.Metadata.SoftwareList item) : base(item)
{
// Process flag values
if (GetStringFieldValue(Data.Models.Metadata.SoftwareList.StatusKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.SoftwareList.StatusKey, GetStringFieldValue(Data.Models.Metadata.SoftwareList.StatusKey).AsSoftwareListStatus().AsStringValue());
// Handle subitems
// TODO: Handle the Software subitem
}
public SoftwareList(Data.Models.Metadata.SoftwareList item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,38 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents the sound output for a machine
/// </summary>
[JsonObject("sound"), XmlRoot("sound")]
public sealed class Sound : DatItem<Data.Models.Metadata.Sound>
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.Sound;
#endregion
#region Constructors
public Sound() : base() { }
public Sound(Data.Models.Metadata.Sound item) : base(item)
{
// Process flag values
if (GetInt64FieldValue(Data.Models.Metadata.Sound.ChannelsKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Sound.ChannelsKey, GetInt64FieldValue(Data.Models.Metadata.Sound.ChannelsKey).ToString());
}
public Sound(Data.Models.Metadata.Sound item, Machine machine, Source source) : this(item)
{
SetFieldValue<Source?>(SourceKey, source);
CopyMachineInformation(machine);
}
#endregion
}
}

View File

@@ -0,0 +1,230 @@
using System.Xml.Serialization;
using Newtonsoft.Json;
// TODO: Add item mappings for all fields
namespace SabreTools.Metadata.DatItems.Formats
{
/// <summary>
/// Represents a single source details item
/// </summary>
[JsonObject("source_details"), XmlRoot("source_details")]
public sealed class SourceDetails : DatItem
{
#region Fields
/// <inheritdoc>/>
protected override ItemType ItemType => ItemType.SourceDetails;
/// <summary>
/// Id value
/// </summary>
/// <remarks>TODO: Is this required?</remarks>
[JsonProperty("id", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("id")]
public string? Id { get; set; }
/// <summary>
/// Section value
/// </summary>
[JsonProperty("section", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("section")]
public string? Section { get; set; }
/// <summary>
/// Rom info value
/// </summary>
[JsonProperty("rominfo", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("rominfo")]
public string? RomInfo { get; set; }
/// <summary>
/// Dumping date value
/// </summary>
[JsonProperty("d_date", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("d_date")]
public string? DDate { get; set; }
/// <summary>
/// Dumping date info value
/// </summary>
[JsonProperty("d_date_info", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("d_date_info")]
public string? DDateInfo { get; set; }
/// <summary>
/// Release date value
/// </summary>
[JsonProperty("r_date", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("r_date")]
public string? RDate { get; set; }
/// <summary>
/// Release date info value
/// </summary>
[JsonProperty("r_date_info", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("r_date_info")]
public string? RDateInfo { get; set; }
/// <summary>
/// Origin value
/// </summary>
[JsonProperty("origin", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("origin")]
public string? Origin { get; set; }
/// <summary>
/// Region value
/// </summary>
[JsonProperty("region", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("region")]
public string? Region { get; set; }
/// <summary>
/// Media title value
/// </summary>
[JsonProperty("media_title", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("media_title")]
public string? MediaTitle { get; set; }
/// <summary>
/// Dumper value
/// </summary>
[JsonProperty("dumper", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("dumper")]
public string? Dumper { get; set; }
/// <summary>
/// Project value
/// </summary>
[JsonProperty("project", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("project")]
public string? Project { get; set; }
/// <summary>
/// Original format value
/// </summary>
[JsonProperty("originalformat", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("originalformat")]
public string? OriginalFormat { get; set; }
/// <summary>
/// Nodump value
/// </summary>
[JsonProperty("nodump", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("nodump")]
public string? Nodump { get; set; }
/// <summary>
/// Tool value
/// </summary>
[JsonProperty("tool", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("tool")]
public string? Tool { get; set; }
/// <summary>
/// Comment 1 value
/// </summary>
[JsonProperty("comment1", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("comment1")]
public string? Comment1 { get; set; }
/// <summary>
/// Link 2 value
/// </summary>
[JsonProperty("comment2", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("comment2")]
public string? Comment2 { get; set; }
/// <summary>
/// Link 1 value
/// </summary>
[JsonProperty("link1", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("link1")]
public string? Link1 { get; set; }
/// <summary>
/// Link 2 value
/// </summary>
[JsonProperty("link2", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("link2")]
public string? Link2 { get; set; }
/// <summary>
/// Link 3 value
/// </summary>
[JsonProperty("link3", DefaultValueHandling = DefaultValueHandling.Ignore), XmlElement("link3")]
public string? Link3 { get; set; }
#endregion
#region Constructors
/// <summary>
/// Create a default, empty SourceDetails object
/// </summary>
public SourceDetails()
{
SetFieldValue(Data.Models.Metadata.DatItem.TypeKey, ItemType.SourceDetails);
}
#endregion
#region Cloning Methods
/// <inheritdoc/>
public override object Clone()
{
var sourceDetails = new SourceDetails()
{
Id = this.Id,
Section = this.Section,
RomInfo = this.RomInfo,
DDate = this.DDate,
DDateInfo = this.DDateInfo,
RDate = this.RDate,
RDateInfo = this.RDateInfo,
Origin = this.Origin,
Region = this.Region,
MediaTitle = this.MediaTitle,
Dumper = this.Dumper,
Project = this.Project,
OriginalFormat = this.OriginalFormat,
Nodump = this.Nodump,
Tool = this.Tool,
Comment1 = this.Comment1,
Comment2 = this.Comment2,
Link1 = this.Link1,
Link2 = this.Link2,
Link3 = this.Link3,
};
sourceDetails.SetFieldValue(DupeTypeKey, GetFieldValue<DupeType>(DupeTypeKey));
sourceDetails.SetFieldValue(MachineKey, GetMachine());
sourceDetails.SetFieldValue(RemoveKey, GetBoolFieldValue(RemoveKey));
sourceDetails.SetFieldValue<Source?>(SourceKey, GetFieldValue<Source?>(SourceKey));
sourceDetails.SetFieldValue<string?>(Data.Models.Metadata.DatItem.TypeKey, GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey).AsItemType().AsStringValue());
return sourceDetails;
}
#endregion
#region Comparision Methods
/// <inheritdoc/>
public override bool Equals(DatItem? other)
{
// If we don't have a SourceDetails, return false
if (GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey) != other?.GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey))
return false;
// Otherwise, treat it as a SourceDetails
SourceDetails? newOther = other as SourceDetails;
// If the Details information matches
return Id == newOther!.Id
&& Section == newOther.Section
&& RomInfo == newOther.RomInfo
&& DDate == newOther.DDate
&& DDateInfo == newOther.DDateInfo
&& RomInfo == newOther.RomInfo
&& RDate == newOther.RDate
&& RDateInfo == newOther.RDateInfo
&& Origin == newOther.Origin
&& Region == newOther.Region
&& MediaTitle == newOther.MediaTitle
&& Dumper == newOther.Dumper
&& Project == newOther.Project
&& OriginalFormat == newOther.OriginalFormat
&& Nodump == newOther.Nodump
&& Tool == newOther.Tool
&& Comment1 == newOther.Comment1
&& Comment2 == newOther.Comment2
&& Link1 == newOther.Link1
&& Link2 == newOther.Link2
&& Link3 == newOther.Link3;
}
#endregion
}
}

View File

@@ -0,0 +1,156 @@
using System;
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Data.Extensions;
using SabreTools.Metadata.Filter;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems
{
/// <summary>
/// Represents the information specific to a set/game/machine
/// </summary>
[JsonObject("machine"), XmlRoot("machine")]
public sealed class Machine : ModelBackedItem<Data.Models.Metadata.Machine>, ICloneable, IEquatable<Machine>
{
#region Constructors
public Machine()
{
_internal = [];
}
public Machine(Data.Models.Metadata.Machine machine)
{
// Get all fields to automatically copy without processing
var nonItemFields = TypeHelper.GetConstants(typeof(Data.Models.Metadata.Machine));
if (nonItemFields is null)
return;
// Populate the internal machine from non-filter fields
_internal = [];
foreach (string fieldName in nonItemFields)
{
if (machine.TryGetValue(fieldName, out var value))
_internal[fieldName] = value;
}
// Process flag values
if (GetStringFieldValue(Data.Models.Metadata.Machine.Im1CRCKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Machine.Im1CRCKey, TextHelper.NormalizeCRC32(GetStringFieldValue(Data.Models.Metadata.Machine.Im1CRCKey)));
if (GetStringFieldValue(Data.Models.Metadata.Machine.Im2CRCKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Machine.Im2CRCKey, TextHelper.NormalizeCRC32(GetStringFieldValue(Data.Models.Metadata.Machine.Im2CRCKey)));
if (GetBoolFieldValue(Data.Models.Metadata.Machine.IsBiosKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Machine.IsBiosKey, GetBoolFieldValue(Data.Models.Metadata.Machine.IsBiosKey).FromYesNo());
if (GetBoolFieldValue(Data.Models.Metadata.Machine.IsDeviceKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Machine.IsDeviceKey, GetBoolFieldValue(Data.Models.Metadata.Machine.IsDeviceKey).FromYesNo());
if (GetBoolFieldValue(Data.Models.Metadata.Machine.IsMechanicalKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Machine.IsMechanicalKey, GetBoolFieldValue(Data.Models.Metadata.Machine.IsMechanicalKey).FromYesNo());
if (GetStringFieldValue(Data.Models.Metadata.Machine.SupportedKey) is not null)
SetFieldValue<string?>(Data.Models.Metadata.Machine.SupportedKey, GetStringFieldValue(Data.Models.Metadata.Machine.SupportedKey).AsSupported().AsStringValue());
// Handle Trurip object, if it exists
if (machine.ContainsKey(Data.Models.Metadata.Machine.TruripKey))
{
var truripItem = machine.Read<Data.Models.Logiqx.Trurip>(Data.Models.Metadata.Machine.TruripKey);
if (truripItem is not null)
SetFieldValue(Data.Models.Metadata.Machine.TruripKey, new Trurip(truripItem));
}
}
#endregion
#region Accessors
/// <summary>
/// Gets the name to use for a Machine
/// </summary>
/// <returns>Name if available, null otherwise</returns>
public string? GetName() => _internal.GetName();
/// <summary>
/// Sets the name to use for a Machine
/// </summary>
/// <param name="name">Name to set for the item</param>
public void SetName(string? name) => _internal.SetName(name);
#endregion
#region Cloning methods
/// <summary>
/// Create a clone of the current machine
/// </summary>
/// <returns>New machine with the same values as the current one</returns>
public object Clone()
{
return new Machine()
{
_internal = _internal.Clone() as Data.Models.Metadata.Machine ?? [],
};
}
/// <summary>
/// Get a clone of the current internal model
/// </summary>
public Data.Models.Metadata.Machine GetInternalClone() => (_internal.Clone() as Data.Models.Metadata.Machine)!;
#endregion
#region Comparision Methods
/// <inheritdoc/>
public override bool Equals(ModelBackedItem? other)
{
// If other is null
if (other is null)
return false;
// If the type is mismatched
if (other is not Machine otherItem)
return false;
// Compare internal models
return _internal.EqualTo(otherItem._internal);
}
/// <inheritdoc/>
public override bool Equals(ModelBackedItem<Data.Models.Metadata.Machine>? other)
{
// If other is null
if (other is null)
return false;
// If the type is mismatched
if (other is not Machine otherItem)
return false;
// Compare internal models
return _internal.EqualTo(otherItem._internal);
}
/// <inheritdoc/>
public bool Equals(Machine? other)
{
// If other is null
if (other is null)
return false;
// Compare internal models
return _internal.EqualTo(other._internal);
}
#endregion
#region Manipulation
/// <summary>
/// Runs a filter and determines if it passes or not
/// </summary>
/// <param name="filterRunner">Filter runner to use for checking</param>
/// <returns>True if the Machine passes the filter, false otherwise</returns>
public bool PassesFilter(FilterRunner filterRunner) => filterRunner.Run(_internal);
#endregion
}
}

View File

@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<IncludeSymbols>true</IncludeSymbols>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>2.3.0</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Description>DatItem specific functionality for metadata file processing</Description>
<Copyright>Copyright (c) Matt Nadareski 2016-2026</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/SabreTools/SabreTools.Serialization</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>metadata dat datfile</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="SabreTools.Hashing" Version="[2.0.0]" />
<PackageReference Include="SabreTools.IO" Version="[2.0.0]" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SabreTools.Data.Extensions\SabreTools.Data.Extensions.csproj" />
<ProjectReference Include="..\SabreTools.Metadata\SabreTools.Metadata.csproj" />
<ProjectReference Include="..\SabreTools.Metadata.Filter\SabreTools.Metadata.Filter.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,41 @@
using System;
#pragma warning disable IDE0290 // Use primary constructor
namespace SabreTools.Metadata.DatItems
{
/// <summary>
/// Source information wrapper
/// </summary>
public class Source : ICloneable
{
/// <summary>
/// Source index
/// </summary>
public readonly int Index;
/// <summary>
/// Source name
/// </summary>
public readonly string? Name;
/// <summary>
/// Constructor
/// </summary>
/// <param name="id">Source ID</param>
/// <param name="source">Source name, optional</param>
public Source(int id, string? source = null)
{
Index = id;
Name = source;
}
#region Cloning
/// <summary>
/// Clone the current object
/// </summary>
public object Clone() => new Source(Index, Name);
#endregion
}
}

View File

@@ -0,0 +1,146 @@
using System;
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatItems
{
/// <summary>
/// Represents TruRip/EmuArc-specific values on a machine
/// </summary>
[JsonObject("trurip"), XmlRoot("trurip")]
public sealed class Trurip : ICloneable
{
#region Fields
/// <summary>
/// Title ID
/// </summary>
[JsonProperty("titleid", DefaultValueHandling = DefaultValueHandling.Ignore)]
[XmlElement("titleid")]
public string? TitleID { get; set; } = null;
/// <summary>
/// Machine developer
/// </summary>
[JsonProperty("developer", DefaultValueHandling = DefaultValueHandling.Ignore)]
[XmlElement("developer")]
public string? Developer { get; set; } = null;
/// <summary>
/// Game genre
/// </summary>
[JsonProperty("genre", DefaultValueHandling = DefaultValueHandling.Ignore)]
[XmlElement("genre")]
public string? Genre { get; set; } = null;
/// <summary>
/// Game subgenre
/// </summary>
[JsonProperty("subgenre", DefaultValueHandling = DefaultValueHandling.Ignore)]
[XmlElement("subgenre")]
public string? Subgenre { get; set; } = null;
/// <summary>
/// Game ratings
/// </summary>
[JsonProperty("ratings", DefaultValueHandling = DefaultValueHandling.Ignore)]
[XmlElement("ratings")]
public string? Ratings { get; set; } = null;
/// <summary>
/// Game score
/// </summary>
[JsonProperty("score", DefaultValueHandling = DefaultValueHandling.Ignore)]
[XmlElement("score")]
public string? Score { get; set; } = null;
/// <summary>
/// Is the machine enabled
/// </summary>
[JsonProperty("enabled", DefaultValueHandling = DefaultValueHandling.Ignore)]
[XmlElement("enabled")]
public string? Enabled { get; set; } = null; // bool?
/// <summary>
/// Does the game have a CRC check
/// </summary>
[JsonProperty("hascrc", DefaultValueHandling = DefaultValueHandling.Ignore)]
[XmlElement("hascrc")]
public bool? Crc { get; set; } = null;
[JsonIgnore]
public bool CrcSpecified { get { return Crc is not null; } }
/// <summary>
/// Machine relations
/// </summary>
[JsonProperty("relatedto", DefaultValueHandling = DefaultValueHandling.Ignore)]
[XmlElement("relatedto")]
public string? RelatedTo { get; set; } = null;
#endregion
#region Constructors
public Trurip() { }
public Trurip(Data.Models.Logiqx.Trurip trurip)
{
TitleID = trurip.TitleID;
Developer = trurip.Developer;
Genre = trurip.Genre;
Subgenre = trurip.Subgenre;
Ratings = trurip.Ratings;
Score = trurip.Score;
Enabled = trurip.Enabled;
Crc = trurip.CRC.AsYesNo();
RelatedTo = trurip.RelatedTo;
}
#endregion
#region Cloning methods
/// <summary>
/// Create a clone of the current object
/// </summary>
/// <returns>New object with the same values as the current one</returns>
public object Clone()
{
return new Trurip()
{
TitleID = this.TitleID,
Developer = this.Developer,
Genre = this.Genre,
Subgenre = this.Subgenre,
Ratings = this.Ratings,
Score = this.Score,
Enabled = this.Enabled,
Crc = this.Crc,
RelatedTo = this.RelatedTo,
};
}
/// <summary>
/// Convert to the internal Logiqx model
/// </summary>
public Data.Models.Logiqx.Trurip ConvertToLogiqx()
{
return new Data.Models.Logiqx.Trurip()
{
TitleID = this.TitleID,
Developer = this.Developer,
Genre = this.Genre,
Subgenre = this.Subgenre,
Ratings = this.Ratings,
Score = this.Score,
Enabled = this.Enabled,
CRC = this.Crc.FromYesNo(),
RelatedTo = this.RelatedTo,
};
}
#endregion
}
}