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,23 @@
using SabreTools.Metadata.Tools;
using Xunit;
namespace SabreTools.Metadata.DatFiles.Test
{
public class ConvertersTests
{
#region Generators
[Theory]
[InlineData(MergingFlag.None, 12)]
[InlineData(NodumpFlag.None, 4)]
[InlineData(PackingFlag.None, 8)]
public void GenerateToEnumTest<T>(T value, int expected)
{
var actual = Converters.GenerateToEnum<T>();
Assert.Equal(default, value);
Assert.Equal(expected, actual.Keys.Count);
}
#endregion
}
}

View File

@@ -0,0 +1,304 @@
using System.Collections.Generic;
using SabreTools.Metadata.DatFiles.Formats;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
using SabreTools.Metadata.Filter;
using Xunit;
namespace SabreTools.Metadata.DatFiles.Test
{
public partial class DatFileTests
{
#region ExecuteFilters
[Fact]
public void ExecuteFilters_Items()
{
FilterObject filterObject = new FilterObject("rom.crc", "deadbeef", Operation.NotEquals);
FilterRunner filterRunner = new FilterRunner([filterObject]);
Source source = new Source(0, source: null);
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "machine");
DatItem datItem = new Rom();
datItem.SetName("rom.bin");
datItem.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
datItem.SetFieldValue(DatItem.MachineKey, machine);
datItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
datFile.AddItem(datItem, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.ExecuteFilters(filterRunner);
var actualDatItems = datFile.GetItemsForBucket("machine");
DatItem actualRom = Assert.Single(actualDatItems);
Assert.Equal(true, actualRom.GetBoolFieldValue(DatItem.RemoveKey));
}
[Fact]
public void ExecuteFilters_ItemsDB()
{
FilterObject filterObject = new FilterObject("rom.crc", "deadbeef", Operation.NotEquals);
FilterRunner filterRunner = new FilterRunner([filterObject]);
Source source = new Source(0, source: null);
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "machine");
DatItem datItem = new Rom();
datItem.SetName("rom.bin");
datItem.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
datItem.SetFieldValue(DatItem.MachineKey, machine);
datItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
long sourceIndex = datFile.AddSourceDB(source);
long machineIndex = datFile.AddMachineDB(machine);
_ = datFile.AddItemDB(datItem, machineIndex, sourceIndex, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.ExecuteFilters(filterRunner);
var actualDatItems = datFile.GetItemsForBucketDB("machine");
DatItem actualRom = Assert.Single(actualDatItems).Value;
Assert.Equal(true, actualRom.GetBoolFieldValue(DatItem.RemoveKey));
}
#endregion
#region MachineDescriptionToName
[Fact]
public void MachineDescriptionToName_Items()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "machine");
machine.SetFieldValue(Data.Models.Metadata.Machine.DescriptionKey, "description");
DatItem datItem = new Rom();
datItem.SetFieldValue(DatItem.MachineKey, machine);
datItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
datFile.AddItem(datItem, statsOnly: false);
datFile.MachineDescriptionToName();
// The name of the bucket is not expected to change
DatItem actual = Assert.Single(datFile.GetItemsForBucket("machine"));
Machine? actualMachine = actual.GetMachine();
Assert.NotNull(actualMachine);
Assert.Equal("description", actualMachine.GetName());
Assert.Equal("description", actualMachine.GetStringFieldValue(Data.Models.Metadata.Machine.DescriptionKey));
}
[Fact]
public void MachineDescriptionToName_ItemsDB()
{
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "machine");
machine.SetFieldValue(Data.Models.Metadata.Machine.DescriptionKey, "description");
DatFile datFile = new Logiqx(datFile: null, useGame: false);
_ = datFile.AddMachineDB(machine);
datFile.MachineDescriptionToName();
Machine actualMachine = Assert.Single(datFile.GetMachinesDB()).Value;
Assert.Equal("description", actualMachine.GetName());
Assert.Equal("description", actualMachine.GetStringFieldValue(Data.Models.Metadata.Machine.DescriptionKey));
}
#endregion
#region SetOneRomPerGame
[Fact]
public void SetOneRomPerGame_Items()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "machine");
DatItem rom = new Rom();
rom.SetName("rom.bin");
rom.SetFieldValue(DatItem.MachineKey, machine);
rom.SetFieldValue(DatItem.SourceKey, source);
DatItem disk = new Disk();
disk.SetName("disk");
disk.SetFieldValue(DatItem.MachineKey, machine);
disk.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
datFile.AddItem(rom, statsOnly: false);
datFile.AddItem(disk, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.SetOneRomPerGame();
var actualDatItems = datFile.GetItemsForBucket("machine");
Assert.Equal(2, actualDatItems.Count);
DatItem actualRom = Assert.Single(actualDatItems.FindAll(i => i is Rom));
Machine? actualRomMachine = actualRom.GetMachine();
Assert.NotNull(actualRomMachine);
Assert.Equal("machine/rom", actualRomMachine.GetName());
DatItem actualDisk = Assert.Single(actualDatItems.FindAll(i => i is Disk));
Machine? actualDiskMachine = actualDisk.GetMachine();
Assert.NotNull(actualDiskMachine);
Assert.Equal("machine/disk", actualDiskMachine.GetName());
}
[Fact]
public void SetOneRomPerGame_ItemsDB()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "machine");
DatItem rom = new Rom();
rom.SetName("rom.bin");
DatItem disk = new Disk();
disk.SetName("disk");
DatFile datFile = new Logiqx(datFile: null, useGame: false);
long sourceIndex = datFile.AddSourceDB(source);
long machineIndex = datFile.AddMachineDB(machine);
_ = datFile.AddItemDB(rom, machineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(disk, machineIndex, sourceIndex, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.SetOneRomPerGame();
var actualDatItems = datFile.GetItemsForBucketDB("machine");
Assert.Equal(2, actualDatItems.Count);
var actualRom = Assert.Single(actualDatItems, i => i.Value is Rom);
var actualRomMachine = datFile.GetMachineForItemDB(actualRom.Key);
Assert.NotNull(actualRomMachine.Value);
Assert.Equal("machine/rom", actualRomMachine.Value.GetName());
var actualDisk = Assert.Single(actualDatItems, i => i.Value is Disk);
var actualDiskMachine = datFile.GetMachineForItemDB(actualDisk.Key);
Assert.NotNull(actualDiskMachine.Value);
Assert.Equal("machine/disk", actualDiskMachine.Value.GetName());
}
#endregion
#region SetOneGamePerRegion
[Fact]
public void SetOneGamePerRegion_Items()
{
Machine nowhereMachine = new Machine();
nowhereMachine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "machine (Nowhere)");
Machine worldMachine = new Machine();
worldMachine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "machine (World)");
worldMachine.SetFieldValue(Data.Models.Metadata.Machine.CloneOfKey, "machine (Nowhere)");
DatItem nowhereRom = new Rom();
nowhereRom.SetName("rom.bin");
nowhereRom.SetFieldValue(DatItem.MachineKey, nowhereMachine);
DatItem worldRom = new Rom();
worldRom.SetName("rom.nib");
worldRom.SetFieldValue(DatItem.MachineKey, worldMachine);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
datFile.AddItem(nowhereRom, statsOnly: false);
datFile.AddItem(worldRom, statsOnly: false);
List<string> regions = ["World", "Nowhere"];
datFile.SetOneGamePerRegion(regions);
Assert.Empty(datFile.GetItemsForBucket("machine (nowhere)"));
var actualDatItems = datFile.GetItemsForBucket("machine (world)");
DatItem actualWorldRom = Assert.Single(actualDatItems);
Machine? actualWorldMachine = actualWorldRom.GetMachine();
Assert.NotNull(actualWorldMachine);
Assert.Equal("machine (World)", actualWorldMachine.GetName());
}
[Fact]
public void SetOneGamePerRegion_ItemsDB()
{
Machine nowhereMachine = new Machine();
nowhereMachine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "machine (Nowhere)");
Machine worldMachine = new Machine();
worldMachine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "machine (World)");
worldMachine.SetFieldValue(Data.Models.Metadata.Machine.CloneOfKey, "machine (Nowhere)");
DatFile datFile = new Logiqx(datFile: null, useGame: false);
_ = datFile.AddMachineDB(nowhereMachine);
_ = datFile.AddMachineDB(worldMachine);
List<string> regions = ["World", "Nowhere"];
datFile.SetOneGamePerRegion(regions);
var actualWorldMachine = Assert.Single(datFile.GetMachinesDB());
Assert.NotNull(actualWorldMachine.Value);
Assert.Equal("machine (World)", actualWorldMachine.Value.GetName());
}
#endregion
#region StripSceneDatesFromItems
[Fact]
public void StripSceneDatesFromItems_Items()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "10.10.10-machine-name");
DatItem datItem = new Rom();
datItem.SetFieldValue(DatItem.MachineKey, machine);
datItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
datFile.AddItem(datItem, statsOnly: false);
datFile.StripSceneDatesFromItems();
// The name of the bucket is not expected to change
DatItem actual = Assert.Single(datFile.GetItemsForBucket("10.10.10-machine-name"));
Machine? actualMachine = actual.GetMachine();
Assert.NotNull(actualMachine);
Assert.Equal("machine-name", actualMachine.GetName());
}
[Fact]
public void StripSceneDatesFromItems_ItemsDB()
{
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "10.10.10-machine-name");
DatFile datFile = new Logiqx(datFile: null, useGame: false);
_ = datFile.AddMachineDB(machine);
datFile.StripSceneDatesFromItems();
Machine actualMachine = Assert.Single(datFile.GetMachinesDB()).Value;
Assert.Equal("machine-name", actualMachine.GetName());
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,847 @@
using SabreTools.Metadata.DatFiles.Formats;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
using Xunit;
namespace SabreTools.Metadata.DatFiles.Test
{
public partial class DatFileTests
{
#region AddItemsFromChildren
[Fact]
public void AddItemsFromChildren_Items_Dedup()
{
Source source = new Source(0, source: null);
Machine parentMachine = new Machine();
parentMachine.SetName("parent");
Machine childMachine = new Machine();
childMachine.SetName("child");
childMachine.SetFieldValue<string?>(Data.Models.Metadata.Machine.CloneOfKey, "parent");
childMachine.SetFieldValue(Data.Models.Metadata.Machine.IsBiosKey, true);
DatItem parentItem = new Rom();
parentItem.SetName("parent_rom");
parentItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
parentItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
parentItem.SetFieldValue(DatItem.MachineKey, parentMachine);
parentItem.SetFieldValue(DatItem.SourceKey, source);
DatItem matchChildItem = new Rom();
matchChildItem.SetName("match_child_rom");
matchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
matchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
matchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
matchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatItem noMatchChildItem = new Rom();
noMatchChildItem.SetName("no_match_child_rom");
noMatchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
noMatchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "beefdead");
noMatchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
noMatchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
datFile.AddItem(parentItem, statsOnly: false);
datFile.AddItem(matchChildItem, statsOnly: false);
datFile.AddItem(noMatchChildItem, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.AddItemsFromChildren(subfolder: true, skipDedup: false);
Assert.Equal(2, datFile.GetItemsForBucket("parent").Count);
}
[Fact]
public void AddItemsFromChildren_Items_SkipDedup()
{
Source source = new Source(0, source: null);
Machine parentMachine = new Machine();
parentMachine.SetName("parent");
Machine childMachine = new Machine();
childMachine.SetName("child");
childMachine.SetFieldValue<string?>(Data.Models.Metadata.Machine.CloneOfKey, "parent");
childMachine.SetFieldValue(Data.Models.Metadata.Machine.IsBiosKey, true);
DatItem parentItem = new Rom();
parentItem.SetName("parent_rom");
parentItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
parentItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
parentItem.SetFieldValue(DatItem.MachineKey, parentMachine);
parentItem.SetFieldValue(DatItem.SourceKey, source);
DatItem matchChildItem = new Rom();
matchChildItem.SetName("match_child_rom");
matchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
matchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
matchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
matchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatItem noMatchChildItem = new Rom();
noMatchChildItem.SetName("no_match_child_rom");
noMatchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
noMatchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "beefdead");
noMatchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
noMatchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
datFile.AddItem(parentItem, statsOnly: false);
datFile.AddItem(matchChildItem, statsOnly: false);
datFile.AddItem(noMatchChildItem, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.AddItemsFromChildren(subfolder: true, skipDedup: true);
Assert.Equal(3, datFile.GetItemsForBucket("parent").Count);
}
[Fact]
public void AddItemsFromChildren_ItemsDB_Dedup()
{
Source source = new Source(0, source: null);
Machine parentMachine = new Machine();
parentMachine.SetName("parent");
Machine childMachine = new Machine();
childMachine.SetName("child");
childMachine.SetFieldValue<string?>(Data.Models.Metadata.Machine.CloneOfKey, "parent");
childMachine.SetFieldValue(Data.Models.Metadata.Machine.IsBiosKey, true);
DatItem parentItem = new Rom();
parentItem.SetName("parent_rom");
parentItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
parentItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
parentItem.SetFieldValue(DatItem.MachineKey, parentMachine);
parentItem.SetFieldValue(DatItem.SourceKey, source);
DatItem matchChildItem = new Rom();
matchChildItem.SetName("match_child_rom");
matchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
matchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
matchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
matchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatItem noMatchChildItem = new Rom();
noMatchChildItem.SetName("no_match_child_rom");
noMatchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
noMatchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "beefdead");
noMatchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
noMatchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
long biosMachineIndex = datFile.AddMachineDB(parentMachine);
long deviceMachineIndex = datFile.AddMachineDB(childMachine);
long sourceIndex = datFile.AddSourceDB(source);
_ = datFile.AddItemDB(parentItem, biosMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(matchChildItem, deviceMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(noMatchChildItem, deviceMachineIndex, sourceIndex, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.AddItemsFromChildren(subfolder: true, skipDedup: false);
Assert.Equal(2, datFile.GetItemsForBucketDB("parent").Count);
}
[Fact]
public void AddItemsFromChildren_ItemsDB_SkipDedup()
{
Source source = new Source(0, source: null);
Machine parentMachine = new Machine();
parentMachine.SetName("parent");
Machine childMachine = new Machine();
childMachine.SetName("child");
childMachine.SetFieldValue<string?>(Data.Models.Metadata.Machine.CloneOfKey, "parent");
childMachine.SetFieldValue(Data.Models.Metadata.Machine.IsBiosKey, true);
DatItem parentItem = new Rom();
parentItem.SetName("parent_rom");
parentItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
parentItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
parentItem.SetFieldValue(DatItem.MachineKey, parentMachine);
parentItem.SetFieldValue(DatItem.SourceKey, source);
DatItem matchChildItem = new Rom();
matchChildItem.SetName("match_child_rom");
matchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
matchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
matchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
matchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatItem noMatchChildItem = new Rom();
noMatchChildItem.SetName("no_match_child_rom");
noMatchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
noMatchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "beefdead");
noMatchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
noMatchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
long biosMachineIndex = datFile.AddMachineDB(parentMachine);
long deviceMachineIndex = datFile.AddMachineDB(childMachine);
long sourceIndex = datFile.AddSourceDB(source);
_ = datFile.AddItemDB(parentItem, biosMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(matchChildItem, deviceMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(noMatchChildItem, deviceMachineIndex, sourceIndex, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.AddItemsFromChildren(subfolder: true, skipDedup: true);
Assert.Equal(3, datFile.GetItemsForBucketDB("parent").Count);
}
#endregion
#region AddItemsFromCloneOfParent
[Fact]
public void AddItemsFromCloneOfParent_Items()
{
Source source = new Source(0, source: null);
Machine parentMachine = new Machine();
parentMachine.SetName("parent");
Machine childMachine = new Machine();
childMachine.SetName("child");
childMachine.SetFieldValue<string?>(Data.Models.Metadata.Machine.CloneOfKey, "parent");
childMachine.SetFieldValue(Data.Models.Metadata.Machine.IsBiosKey, true);
DatItem parentItem = new Rom();
parentItem.SetName("parent_rom");
parentItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
parentItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
parentItem.SetFieldValue(DatItem.MachineKey, parentMachine);
parentItem.SetFieldValue(DatItem.SourceKey, source);
DatItem matchChildItem = new Rom();
matchChildItem.SetName("match_child_rom");
matchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
matchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
matchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
matchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatItem noMatchChildItem = new Rom();
noMatchChildItem.SetName("no_match_child_rom");
noMatchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
noMatchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "beefdead");
noMatchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
noMatchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
datFile.AddItem(parentItem, statsOnly: false);
datFile.AddItem(matchChildItem, statsOnly: false);
datFile.AddItem(noMatchChildItem, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.AddItemsFromCloneOfParent();
Assert.Equal(2, datFile.GetItemsForBucket("child").Count);
}
[Fact]
public void AddItemsFromCloneOfParent_ItemsDB()
{
Source source = new Source(0, source: null);
Machine parentMachine = new Machine();
parentMachine.SetName("parent");
Machine childMachine = new Machine();
childMachine.SetName("child");
childMachine.SetFieldValue<string?>(Data.Models.Metadata.Machine.CloneOfKey, "parent");
childMachine.SetFieldValue(Data.Models.Metadata.Machine.IsBiosKey, true);
DatItem parentItem = new Rom();
parentItem.SetName("parent_rom");
parentItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
parentItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
parentItem.SetFieldValue(DatItem.MachineKey, parentMachine);
parentItem.SetFieldValue(DatItem.SourceKey, source);
DatItem matchChildItem = new Rom();
matchChildItem.SetName("match_child_rom");
matchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
matchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
matchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
matchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatItem noMatchChildItem = new Rom();
noMatchChildItem.SetName("no_match_child_rom");
noMatchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
noMatchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "beefdead");
noMatchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
noMatchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
long biosMachineIndex = datFile.AddMachineDB(parentMachine);
long deviceMachineIndex = datFile.AddMachineDB(childMachine);
long sourceIndex = datFile.AddSourceDB(source);
_ = datFile.AddItemDB(parentItem, biosMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(matchChildItem, deviceMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(noMatchChildItem, deviceMachineIndex, sourceIndex, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.AddItemsFromCloneOfParent();
Assert.Equal(2, datFile.GetItemsForBucketDB("child").Count);
}
#endregion
#region AddItemsFromDevices
[Theory]
[InlineData(false, false, 4)]
[InlineData(false, true, 4)]
[InlineData(true, false, 3)]
[InlineData(true, true, 3)]
public void AddItemsFromDevices_Items(bool deviceOnly, bool useSlotOptions, int expected)
{
Source source = new Source(0, source: null);
Machine deviceMachine = new Machine();
deviceMachine.SetName("device");
deviceMachine.SetFieldValue(Data.Models.Metadata.Machine.IsDeviceKey, true);
Machine slotOptionMachine = new Machine();
slotOptionMachine.SetName("slotoption");
Machine itemMachine = new Machine();
itemMachine.SetName("machine");
DatItem deviceItem = new Sample();
deviceItem.SetName("device_item");
deviceItem.SetFieldValue(DatItem.MachineKey, deviceMachine);
deviceItem.SetFieldValue(DatItem.SourceKey, source);
DatItem slotOptionItem = new Sample();
slotOptionItem.SetName("slot_option_item");
slotOptionItem.SetFieldValue(DatItem.MachineKey, slotOptionMachine);
slotOptionItem.SetFieldValue(DatItem.SourceKey, source);
DatItem datItem = new Rom();
datItem.SetName("rom");
datItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
datItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
datItem.SetFieldValue(DatItem.MachineKey, itemMachine);
datItem.SetFieldValue(DatItem.SourceKey, source);
DatItem deviceRef = new DeviceRef();
deviceRef.SetName("device");
deviceRef.SetFieldValue(DatItem.MachineKey, itemMachine);
deviceRef.SetFieldValue(DatItem.SourceKey, source);
DatItem slotOption = new SlotOption();
slotOption.SetName("slotoption");
slotOption.SetFieldValue(DatItem.MachineKey, itemMachine);
slotOption.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
datFile.AddItem(deviceItem, statsOnly: false);
datFile.AddItem(slotOptionItem, statsOnly: false);
datFile.AddItem(datItem, statsOnly: false);
datFile.AddItem(deviceRef, statsOnly: false);
datFile.AddItem(slotOption, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.AddItemsFromDevices(deviceOnly, useSlotOptions);
Assert.Equal(expected, datFile.GetItemsForBucket("machine").Count);
}
[Theory]
[InlineData(false, false, 4)]
[InlineData(false, true, 4)]
[InlineData(true, false, 3)]
[InlineData(true, true, 3)]
public void AddItemsFromDevices_ItemsDB(bool deviceOnly, bool useSlotOptions, int expected)
{
Source source = new Source(0, source: null);
Machine deviceMachine = new Machine();
deviceMachine.SetName("device");
deviceMachine.SetFieldValue(Data.Models.Metadata.Machine.IsDeviceKey, true);
Machine slotOptionMachine = new Machine();
slotOptionMachine.SetName("slotoption");
Machine itemMachine = new Machine();
itemMachine.SetName("machine");
DatItem deviceItem = new Sample();
deviceItem.SetName("device_item");
DatItem slotOptionItem = new Sample();
slotOptionItem.SetName("slot_option_item");
DatItem datItem = new Rom();
datItem.SetName("rom");
datItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
datItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
DatItem deviceRef = new DeviceRef();
deviceRef.SetName("device");
DatItem slotOption = new SlotOption();
slotOption.SetName("slotoption");
DatFile datFile = new Logiqx(datFile: null, useGame: false);
long deviceMachineIndex = datFile.AddMachineDB(deviceMachine);
long slotOptionMachineIndex = datFile.AddMachineDB(slotOptionMachine);
long itemMachineIndex = datFile.AddMachineDB(itemMachine);
long sourceIndex = datFile.AddSourceDB(source);
_ = datFile.AddItemDB(deviceItem, deviceMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(slotOptionItem, slotOptionMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(datItem, itemMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(deviceRef, itemMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(slotOption, itemMachineIndex, sourceIndex, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.AddItemsFromDevices(deviceOnly, useSlotOptions);
Assert.Equal(expected, datFile.GetItemsForBucketDB("machine").Count);
}
#endregion
#region AddItemsFromRomOfParent
[Fact]
public void AddItemsFromRomOfParent_Items()
{
Source source = new Source(0, source: null);
Machine parentMachine = new Machine();
parentMachine.SetName("parent");
Machine childMachine = new Machine();
childMachine.SetName("child");
childMachine.SetFieldValue<string?>(Data.Models.Metadata.Machine.RomOfKey, "parent");
childMachine.SetFieldValue(Data.Models.Metadata.Machine.IsBiosKey, true);
DatItem parentItem = new Rom();
parentItem.SetName("parent_rom");
parentItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
parentItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
parentItem.SetFieldValue(DatItem.MachineKey, parentMachine);
parentItem.SetFieldValue(DatItem.SourceKey, source);
DatItem matchChildItem = new Rom();
matchChildItem.SetName("match_child_rom");
matchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
matchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
matchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
matchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatItem noMatchChildItem = new Rom();
noMatchChildItem.SetName("no_match_child_rom");
noMatchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
noMatchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "beefdead");
noMatchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
noMatchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
datFile.AddItem(parentItem, statsOnly: false);
datFile.AddItem(matchChildItem, statsOnly: false);
datFile.AddItem(noMatchChildItem, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.AddItemsFromRomOfParent();
Assert.Equal(2, datFile.GetItemsForBucket("child").Count);
}
[Fact]
public void AddItemsFromRomOfParent_ItemsDB()
{
Source source = new Source(0, source: null);
Machine parentMachine = new Machine();
parentMachine.SetName("parent");
Machine childMachine = new Machine();
childMachine.SetName("child");
childMachine.SetFieldValue<string?>(Data.Models.Metadata.Machine.RomOfKey, "parent");
childMachine.SetFieldValue(Data.Models.Metadata.Machine.IsBiosKey, true);
DatItem parentItem = new Rom();
parentItem.SetName("parent_rom");
parentItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
parentItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
parentItem.SetFieldValue(DatItem.MachineKey, parentMachine);
parentItem.SetFieldValue(DatItem.SourceKey, source);
DatItem matchChildItem = new Rom();
matchChildItem.SetName("match_child_rom");
matchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
matchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
matchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
matchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatItem noMatchChildItem = new Rom();
noMatchChildItem.SetName("no_match_child_rom");
noMatchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
noMatchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "beefdead");
noMatchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
noMatchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
long biosMachineIndex = datFile.AddMachineDB(parentMachine);
long deviceMachineIndex = datFile.AddMachineDB(childMachine);
long sourceIndex = datFile.AddSourceDB(source);
_ = datFile.AddItemDB(parentItem, biosMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(matchChildItem, deviceMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(noMatchChildItem, deviceMachineIndex, sourceIndex, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.AddItemsFromRomOfParent();
Assert.Equal(2, datFile.GetItemsForBucketDB("child").Count);
}
#endregion
#region RemoveBiosAndDeviceSets
[Fact]
public void RemoveBiosAndDeviceSets_Items()
{
Source source = new Source(0, source: null);
Machine biosMachine = new Machine();
biosMachine.SetName("bios");
biosMachine.SetFieldValue(Data.Models.Metadata.Machine.IsBiosKey, true);
Machine deviceMachine = new Machine();
deviceMachine.SetName("device");
deviceMachine.SetFieldValue(Data.Models.Metadata.Machine.IsDeviceKey, true);
DatItem biosItem = new Rom();
biosItem.SetFieldValue(DatItem.MachineKey, biosMachine);
biosItem.SetFieldValue(DatItem.SourceKey, source);
DatItem deviceItem = new Rom();
deviceItem.SetFieldValue(DatItem.MachineKey, deviceMachine);
deviceItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
datFile.AddItem(biosItem, statsOnly: false);
datFile.AddItem(deviceItem, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.RemoveBiosAndDeviceSets();
Assert.Empty(datFile.GetItemsForBucket("bios"));
Assert.Empty(datFile.GetItemsForBucket("device"));
}
[Fact]
public void RemoveBiosAndDeviceSets_ItemsDB()
{
Source source = new Source(0, source: null);
Machine biosMachine = new Machine();
biosMachine.SetName("bios");
biosMachine.SetFieldValue(Data.Models.Metadata.Machine.IsBiosKey, true);
Machine deviceMachine = new Machine();
deviceMachine.SetName("device");
deviceMachine.SetFieldValue(Data.Models.Metadata.Machine.IsDeviceKey, true);
DatItem biosItem = new Rom();
DatItem deviceItem = new Rom();
DatFile datFile = new Logiqx(datFile: null, useGame: false);
long biosMachineIndex = datFile.AddMachineDB(biosMachine);
long deviceMachineIndex = datFile.AddMachineDB(deviceMachine);
long sourceIndex = datFile.AddSourceDB(source);
_ = datFile.AddItemDB(biosItem, biosMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(deviceItem, deviceMachineIndex, sourceIndex, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.RemoveBiosAndDeviceSets();
Assert.Empty(datFile.GetMachinesDB());
}
#endregion
#region RemoveItemsFromCloneOfChild
[Fact]
public void RemoveItemsFromCloneOfChild_Items()
{
Source source = new Source(0, source: null);
Machine parentMachine = new Machine();
parentMachine.SetName("parent");
parentMachine.SetFieldValue<string?>(Data.Models.Metadata.Machine.RomOfKey, "romof");
Machine childMachine = new Machine();
childMachine.SetName("child");
childMachine.SetFieldValue<string?>(Data.Models.Metadata.Machine.CloneOfKey, "parent");
DatItem parentItem = new Rom();
parentItem.SetName("parent_rom");
parentItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
parentItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
parentItem.SetFieldValue(DatItem.MachineKey, parentMachine);
parentItem.SetFieldValue(DatItem.SourceKey, source);
DatItem matchChildItem = new Rom();
matchChildItem.SetName("match_child_rom");
matchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
matchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
matchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
matchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatItem noMatchChildItem = new Rom();
noMatchChildItem.SetName("no_match_child_rom");
noMatchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
noMatchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "beefdead");
noMatchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
noMatchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
datFile.AddItem(parentItem, statsOnly: false);
datFile.AddItem(matchChildItem, statsOnly: false);
datFile.AddItem(noMatchChildItem, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.RemoveItemsFromCloneOfChild();
Assert.Single(datFile.GetItemsForBucket("parent"));
DatItem actual = Assert.Single(datFile.GetItemsForBucket("child"));
Machine? actualMachine = actual.GetMachine();
Assert.NotNull(actualMachine);
Assert.Equal("child", actualMachine.GetName());
Assert.Equal("romof", actualMachine.GetStringFieldValue(Data.Models.Metadata.Machine.RomOfKey));
}
[Fact]
public void RemoveItemsFromCloneOfChild_ItemsDB()
{
Source source = new Source(0, source: null);
Machine parentMachine = new Machine();
parentMachine.SetName("parent");
parentMachine.SetFieldValue<string?>(Data.Models.Metadata.Machine.RomOfKey, "romof");
Machine childMachine = new Machine();
childMachine.SetName("child");
childMachine.SetFieldValue<string?>(Data.Models.Metadata.Machine.CloneOfKey, "parent");
DatItem parentItem = new Rom();
parentItem.SetName("parent_rom");
parentItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
parentItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
parentItem.SetFieldValue(DatItem.MachineKey, parentMachine);
parentItem.SetFieldValue(DatItem.SourceKey, source);
DatItem matchChildItem = new Rom();
matchChildItem.SetName("match_child_rom");
matchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
matchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
matchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
matchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatItem noMatchChildItem = new Rom();
noMatchChildItem.SetName("no_match_child_rom");
noMatchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
noMatchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "beefdead");
noMatchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
noMatchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
long biosMachineIndex = datFile.AddMachineDB(parentMachine);
long deviceMachineIndex = datFile.AddMachineDB(childMachine);
long sourceIndex = datFile.AddSourceDB(source);
_ = datFile.AddItemDB(parentItem, biosMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(matchChildItem, deviceMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(noMatchChildItem, deviceMachineIndex, sourceIndex, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.RemoveItemsFromCloneOfChild();
Assert.Single(datFile.GetItemsForBucketDB("parent"));
long actual = Assert.Single(datFile.GetItemsForBucketDB("child")).Key;
Machine? actualMachine = datFile.GetMachineForItemDB(actual).Value;
Assert.NotNull(actualMachine);
Assert.Equal("child", actualMachine.GetName());
Assert.Equal("romof", actualMachine.GetStringFieldValue(Data.Models.Metadata.Machine.RomOfKey));
}
#endregion
#region RemoveItemsFromRomOfChild
[Fact]
public void RemoveItemsFromRomOfChild_Items()
{
Source source = new Source(0, source: null);
Machine parentMachine = new Machine();
parentMachine.SetName("parent");
Machine childMachine = new Machine();
childMachine.SetName("child");
childMachine.SetFieldValue<string?>(Data.Models.Metadata.Machine.RomOfKey, "parent");
childMachine.SetFieldValue(Data.Models.Metadata.Machine.IsBiosKey, true);
DatItem parentItem = new Rom();
parentItem.SetName("parent_rom");
parentItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
parentItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
parentItem.SetFieldValue(DatItem.MachineKey, parentMachine);
parentItem.SetFieldValue(DatItem.SourceKey, source);
DatItem matchChildItem = new Rom();
matchChildItem.SetName("match_child_rom");
matchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
matchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
matchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
matchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatItem noMatchChildItem = new Rom();
noMatchChildItem.SetName("no_match_child_rom");
noMatchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
noMatchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "beefdead");
noMatchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
noMatchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
datFile.AddItem(parentItem, statsOnly: false);
datFile.AddItem(matchChildItem, statsOnly: false);
datFile.AddItem(noMatchChildItem, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.RemoveItemsFromRomOfChild();
Assert.Single(datFile.GetItemsForBucket("parent"));
DatItem actual = Assert.Single(datFile.GetItemsForBucket("child"));
Machine? actualMachine = actual.GetMachine();
Assert.NotNull(actualMachine);
Assert.Equal("child", actualMachine.GetName());
}
[Fact]
public void RemoveItemsFromRomOfChild_ItemsDB()
{
Source source = new Source(0, source: null);
Machine parentMachine = new Machine();
parentMachine.SetName("parent");
Machine childMachine = new Machine();
childMachine.SetName("child");
childMachine.SetFieldValue<string?>(Data.Models.Metadata.Machine.RomOfKey, "parent");
childMachine.SetFieldValue(Data.Models.Metadata.Machine.IsBiosKey, true);
DatItem parentItem = new Rom();
parentItem.SetName("parent_rom");
parentItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
parentItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
parentItem.SetFieldValue(DatItem.MachineKey, parentMachine);
parentItem.SetFieldValue(DatItem.SourceKey, source);
DatItem matchChildItem = new Rom();
matchChildItem.SetName("match_child_rom");
matchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
matchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
matchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
matchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatItem noMatchChildItem = new Rom();
noMatchChildItem.SetName("no_match_child_rom");
noMatchChildItem.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 12345);
noMatchChildItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "beefdead");
noMatchChildItem.SetFieldValue(DatItem.MachineKey, childMachine);
noMatchChildItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
long biosMachineIndex = datFile.AddMachineDB(parentMachine);
long deviceMachineIndex = datFile.AddMachineDB(childMachine);
long sourceIndex = datFile.AddSourceDB(source);
_ = datFile.AddItemDB(parentItem, biosMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(matchChildItem, deviceMachineIndex, sourceIndex, statsOnly: false);
_ = datFile.AddItemDB(noMatchChildItem, deviceMachineIndex, sourceIndex, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.RemoveItemsFromRomOfChild();
Assert.Single(datFile.GetItemsForBucketDB("parent"));
long actual = Assert.Single(datFile.GetItemsForBucketDB("child")).Key;
Machine? actualMachine = datFile.GetMachineForItemDB(actual).Value;
Assert.NotNull(actualMachine);
Assert.Equal("child", actualMachine.GetName());
}
#endregion
#region RemoveMachineRelationshipTags
[Fact]
public void RemoveMachineRelationshipTags_Items()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
machine.SetName("machine");
machine.SetFieldValue<string?>(Data.Models.Metadata.Machine.CloneOfKey, "XXXXXX");
machine.SetFieldValue<string?>(Data.Models.Metadata.Machine.RomOfKey, "XXXXXX");
machine.SetFieldValue<string?>(Data.Models.Metadata.Machine.SampleOfKey, "XXXXXX");
DatItem datItem = new Rom();
datItem.SetFieldValue(DatItem.MachineKey, machine);
datItem.SetFieldValue(DatItem.SourceKey, source);
DatFile datFile = new Logiqx(datFile: null, useGame: false);
datFile.AddItem(datItem, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.RemoveMachineRelationshipTags();
DatItem actualItem = Assert.Single(datFile.GetItemsForBucket("machine"));
Machine? actual = actualItem.GetMachine();
Assert.NotNull(actual);
Assert.Null(actual.GetStringFieldValue(Data.Models.Metadata.Machine.CloneOfKey));
Assert.Null(actual.GetStringFieldValue(Data.Models.Metadata.Machine.RomOfKey));
Assert.Null(actual.GetStringFieldValue(Data.Models.Metadata.Machine.SampleOfKey));
}
[Fact]
public void RemoveMachineRelationshipTags_ItemsDB()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
machine.SetName("machine");
machine.SetFieldValue<string?>(Data.Models.Metadata.Machine.CloneOfKey, "XXXXXX");
machine.SetFieldValue<string?>(Data.Models.Metadata.Machine.RomOfKey, "XXXXXX");
machine.SetFieldValue<string?>(Data.Models.Metadata.Machine.SampleOfKey, "XXXXXX");
DatItem datItem = new Rom();
DatFile datFile = new Logiqx(datFile: null, useGame: false);
long machineIndex = datFile.AddMachineDB(machine);
long sourceIndex = datFile.AddSourceDB(source);
_ = datFile.AddItemDB(datItem, machineIndex, sourceIndex, statsOnly: false);
datFile.BucketBy(ItemKey.Machine);
datFile.RemoveMachineRelationshipTags();
Machine actual = Assert.Single(datFile.GetMachinesDB()).Value;
Assert.Null(actual.GetStringFieldValue(Data.Models.Metadata.Machine.CloneOfKey));
Assert.Null(actual.GetStringFieldValue(Data.Models.Metadata.Machine.RomOfKey));
Assert.Null(actual.GetStringFieldValue(Data.Models.Metadata.Machine.SampleOfKey));
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,211 @@
using Xunit;
namespace SabreTools.Metadata.DatFiles.Test
{
public class DatHeaderTests
{
#region CanOpenSpecified
[Fact]
public void CanOpenSpecified_Missing()
{
DatHeader header = new DatHeader();
header.SetFieldValue<string[]>(Data.Models.Metadata.Header.CanOpenKey, null);
Assert.False(header.CanOpenSpecified);
}
[Fact]
public void CanOpenSpecified_Empty()
{
DatHeader header = new DatHeader();
header.SetFieldValue<string[]>(Data.Models.Metadata.Header.CanOpenKey, []);
Assert.False(header.CanOpenSpecified);
}
[Fact]
public void CanOpenSpecified_Exists()
{
DatHeader header = new DatHeader();
header.SetFieldValue<string[]>(Data.Models.Metadata.Header.CanOpenKey, ["value"]);
Assert.True(header.CanOpenSpecified);
}
#endregion
#region ImagesSpecified
[Fact]
public void ImagesSpecified_Missing()
{
DatHeader header = new DatHeader();
header.SetFieldValue<Data.Models.OfflineList.Images>(Data.Models.Metadata.Header.ImagesKey, null);
Assert.False(header.ImagesSpecified);
}
[Fact]
public void ImagesSpecified_Exists()
{
DatHeader header = new DatHeader();
header.SetFieldValue<Data.Models.OfflineList.Images>(Data.Models.Metadata.Header.ImagesKey, new());
Assert.True(header.ImagesSpecified);
}
#endregion
#region InfosSpecified
[Fact]
public void InfosSpecified_Missing()
{
DatHeader header = new DatHeader();
header.SetFieldValue<Data.Models.OfflineList.Infos>(Data.Models.Metadata.Header.InfosKey, null);
Assert.False(header.InfosSpecified);
}
[Fact]
public void InfosSpecified_Exists()
{
DatHeader header = new DatHeader();
header.SetFieldValue<Data.Models.OfflineList.Infos>(Data.Models.Metadata.Header.InfosKey, new());
Assert.True(header.InfosSpecified);
}
#endregion
#region NewDatSpecified
[Fact]
public void NewDatSpecified_Missing()
{
DatHeader header = new DatHeader();
header.SetFieldValue<Data.Models.OfflineList.NewDat>(Data.Models.Metadata.Header.NewDatKey, null);
Assert.False(header.NewDatSpecified);
}
[Fact]
public void NewDatSpecified_Exists()
{
DatHeader header = new DatHeader();
header.SetFieldValue<Data.Models.OfflineList.NewDat>(Data.Models.Metadata.Header.NewDatKey, new());
Assert.True(header.NewDatSpecified);
}
#endregion
#region SearchSpecified
[Fact]
public void SearchSpecified_Missing()
{
DatHeader header = new DatHeader();
header.SetFieldValue<Data.Models.OfflineList.Search>(Data.Models.Metadata.Header.SearchKey, null);
Assert.False(header.SearchSpecified);
}
[Fact]
public void SearchSpecified_Exists()
{
DatHeader header = new DatHeader();
header.SetFieldValue<Data.Models.OfflineList.Search>(Data.Models.Metadata.Header.SearchKey, new());
Assert.True(header.SearchSpecified);
}
#endregion
#region Clone
[Fact]
public void CloneTest()
{
DatHeader header = new DatHeader();
header.SetFieldValue(Data.Models.Metadata.Header.NameKey, "name");
object clone = header.Clone();
DatHeader? actual = clone as DatHeader;
Assert.NotNull(actual);
Assert.Equal("name", actual.GetStringFieldValue(Data.Models.Metadata.Header.NameKey));
}
#endregion
#region CloneFormat
[Fact]
public void CloneFormatTest()
{
DatHeader header = new DatHeader();
header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.Logiqx);
object clone = header.Clone();
DatHeader? actual = clone as DatHeader;
Assert.NotNull(actual);
Assert.Equal(DatFormat.Logiqx, actual.GetFieldValue<DatFormat>(DatHeader.DatFormatKey));
}
#endregion
#region GetInternalClone
[Fact]
public void GetInternalCloneTest()
{
DatHeader header = new DatHeader();
header.SetFieldValue(Data.Models.Metadata.Header.NameKey, "name");
Data.Models.Metadata.Header actual = header.GetInternalClone();
Assert.Equal("name", actual[Data.Models.Metadata.Header.NameKey]);
}
#endregion
#region Equals
[Fact]
public void Equals_Null_False()
{
DatHeader self = new DatHeader();
DatHeader? other = null;
bool actual = self.Equals(other);
Assert.False(actual);
}
[Fact]
public void Equals_DefaultInternal_True()
{
DatHeader self = new DatHeader();
DatHeader? other = new DatHeader();
bool actual = self.Equals(other);
Assert.True(actual);
}
[Fact]
public void Equals_MismatchedInternal_False()
{
DatHeader self = new DatHeader();
self.SetFieldValue(Data.Models.Metadata.Header.NameKey, "self");
DatHeader? other = new DatHeader();
other.SetFieldValue(Data.Models.Metadata.Header.NameKey, "other");
bool actual = self.Equals(other);
Assert.False(actual);
}
[Fact]
public void Equals_EqualInternal_True()
{
DatHeader self = new DatHeader();
self.SetFieldValue(Data.Models.Metadata.Header.NameKey, "name");
DatHeader? other = new DatHeader();
other.SetFieldValue(Data.Models.Metadata.Header.NameKey, "name");
bool actual = self.Equals(other);
Assert.True(actual);
}
#endregion
}
}

View File

@@ -0,0 +1,326 @@
using SabreTools.Hashing;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
using Xunit;
namespace SabreTools.Metadata.DatFiles.Test
{
public class DatStatisticsTests
{
#region Constructor
[Fact]
public void DefaultConstructorTest()
{
var stats = new DatStatistics();
Assert.Null(stats.DisplayName);
Assert.Equal(0, stats.MachineCount);
Assert.False(stats.IsDirectory);
}
[Fact]
public void NamedConstructorTest()
{
var stats = new DatStatistics("name", isDirectory: true);
Assert.Equal("name", stats.DisplayName);
Assert.Equal(0, stats.MachineCount);
Assert.True(stats.IsDirectory);
}
#endregion
#region End to End
[Fact]
public void AddRemoveStatisticsTest()
{
// Get items for testing
var disk = CreateDisk();
var file = CreateFile();
var media = CreateMedia();
var rom = CreateRom();
var sample = CreateSample();
// Create an empty stats object
var stats = new DatStatistics();
// Validate pre-add values
Assert.Equal(0, stats.TotalCount);
Assert.Equal(0, stats.TotalSize);
Assert.Equal(0, stats.GetHashCount(HashType.CRC32));
Assert.Equal(0, stats.GetHashCount(HashType.MD2));
Assert.Equal(0, stats.GetHashCount(HashType.MD4));
Assert.Equal(0, stats.GetHashCount(HashType.MD5));
Assert.Equal(0, stats.GetHashCount(HashType.RIPEMD128));
Assert.Equal(0, stats.GetHashCount(HashType.RIPEMD160));
Assert.Equal(0, stats.GetHashCount(HashType.SHA1));
Assert.Equal(0, stats.GetHashCount(HashType.SHA256));
Assert.Equal(0, stats.GetHashCount(HashType.SHA384));
Assert.Equal(0, stats.GetHashCount(HashType.SHA512));
Assert.Equal(0, stats.GetHashCount(HashType.SpamSum));
Assert.Equal(0, stats.GetItemCount(ItemType.Disk));
Assert.Equal(0, stats.GetItemCount(ItemType.File));
Assert.Equal(0, stats.GetItemCount(ItemType.Media));
Assert.Equal(0, stats.GetItemCount(ItemType.Rom));
Assert.Equal(0, stats.GetItemCount(ItemType.Sample));
Assert.Equal(0, stats.GetStatusCount(ItemStatus.Good));
// AddItemStatistics
stats.AddItemStatistics(disk);
stats.AddItemStatistics(file);
stats.AddItemStatistics(media);
stats.AddItemStatistics(rom);
stats.AddItemStatistics(sample);
// Validate post-add values
Assert.Equal(5, stats.TotalCount);
Assert.Equal(2, stats.TotalSize);
Assert.Equal(2, stats.GetHashCount(HashType.CRC32));
Assert.Equal(1, stats.GetHashCount(HashType.MD2));
Assert.Equal(1, stats.GetHashCount(HashType.MD4));
Assert.Equal(4, stats.GetHashCount(HashType.MD5));
Assert.Equal(1, stats.GetHashCount(HashType.RIPEMD128));
Assert.Equal(1, stats.GetHashCount(HashType.RIPEMD160));
Assert.Equal(4, stats.GetHashCount(HashType.SHA1));
Assert.Equal(3, stats.GetHashCount(HashType.SHA256));
Assert.Equal(1, stats.GetHashCount(HashType.SHA384));
Assert.Equal(1, stats.GetHashCount(HashType.SHA512));
Assert.Equal(2, stats.GetHashCount(HashType.SpamSum));
Assert.Equal(1, stats.GetItemCount(ItemType.Disk));
Assert.Equal(1, stats.GetItemCount(ItemType.File));
Assert.Equal(1, stats.GetItemCount(ItemType.Media));
Assert.Equal(1, stats.GetItemCount(ItemType.Rom));
Assert.Equal(1, stats.GetItemCount(ItemType.Sample));
Assert.Equal(2, stats.GetStatusCount(ItemStatus.Good));
// RemoveItemStatistics
stats.RemoveItemStatistics(disk);
stats.RemoveItemStatistics(file);
stats.RemoveItemStatistics(media);
stats.RemoveItemStatistics(rom);
stats.RemoveItemStatistics(sample);
// Validate post-remove values
Assert.Equal(0, stats.TotalCount);
Assert.Equal(0, stats.TotalSize);
Assert.Equal(0, stats.GetHashCount(HashType.CRC32));
Assert.Equal(0, stats.GetHashCount(HashType.MD2));
Assert.Equal(0, stats.GetHashCount(HashType.MD4));
Assert.Equal(0, stats.GetHashCount(HashType.MD5));
Assert.Equal(0, stats.GetHashCount(HashType.RIPEMD128));
Assert.Equal(0, stats.GetHashCount(HashType.RIPEMD160));
Assert.Equal(0, stats.GetHashCount(HashType.SHA1));
Assert.Equal(0, stats.GetHashCount(HashType.SHA256));
Assert.Equal(0, stats.GetHashCount(HashType.SHA384));
Assert.Equal(0, stats.GetHashCount(HashType.SHA512));
Assert.Equal(0, stats.GetHashCount(HashType.SpamSum));
Assert.Equal(0, stats.GetItemCount(ItemType.Disk));
Assert.Equal(0, stats.GetItemCount(ItemType.File));
Assert.Equal(0, stats.GetItemCount(ItemType.Media));
Assert.Equal(0, stats.GetItemCount(ItemType.Rom));
Assert.Equal(0, stats.GetItemCount(ItemType.Sample));
Assert.Equal(0, stats.GetStatusCount(ItemStatus.Good));
}
[Fact]
public void ResetStatisticsTest()
{
// Get items for testing
var disk = CreateDisk();
var file = CreateFile();
var media = CreateMedia();
var rom = CreateRom();
var sample = CreateSample();
// Create an empty stats object
var stats = new DatStatistics();
// Validate pre-add values
Assert.Equal(0, stats.TotalCount);
Assert.Equal(0, stats.TotalSize);
Assert.Equal(0, stats.GetHashCount(HashType.CRC32));
Assert.Equal(0, stats.GetHashCount(HashType.MD2));
Assert.Equal(0, stats.GetHashCount(HashType.MD4));
Assert.Equal(0, stats.GetHashCount(HashType.MD5));
Assert.Equal(0, stats.GetHashCount(HashType.RIPEMD128));
Assert.Equal(0, stats.GetHashCount(HashType.RIPEMD160));
Assert.Equal(0, stats.GetHashCount(HashType.SHA1));
Assert.Equal(0, stats.GetHashCount(HashType.SHA256));
Assert.Equal(0, stats.GetHashCount(HashType.SHA384));
Assert.Equal(0, stats.GetHashCount(HashType.SHA512));
Assert.Equal(0, stats.GetHashCount(HashType.SpamSum));
Assert.Equal(0, stats.GetItemCount(ItemType.Disk));
Assert.Equal(0, stats.GetItemCount(ItemType.File));
Assert.Equal(0, stats.GetItemCount(ItemType.Media));
Assert.Equal(0, stats.GetItemCount(ItemType.Rom));
Assert.Equal(0, stats.GetItemCount(ItemType.Sample));
Assert.Equal(0, stats.GetStatusCount(ItemStatus.Good));
// AddItemStatistics
stats.AddItemStatistics(disk);
stats.AddItemStatistics(file);
stats.AddItemStatistics(media);
stats.AddItemStatistics(rom);
stats.AddItemStatistics(sample);
// Validate post-add values
Assert.Equal(5, stats.TotalCount);
Assert.Equal(2, stats.TotalSize);
Assert.Equal(2, stats.GetHashCount(HashType.CRC32));
Assert.Equal(1, stats.GetHashCount(HashType.MD2));
Assert.Equal(1, stats.GetHashCount(HashType.MD4));
Assert.Equal(4, stats.GetHashCount(HashType.MD5));
Assert.Equal(1, stats.GetHashCount(HashType.RIPEMD128));
Assert.Equal(1, stats.GetHashCount(HashType.RIPEMD160));
Assert.Equal(4, stats.GetHashCount(HashType.SHA1));
Assert.Equal(3, stats.GetHashCount(HashType.SHA256));
Assert.Equal(1, stats.GetHashCount(HashType.SHA384));
Assert.Equal(1, stats.GetHashCount(HashType.SHA512));
Assert.Equal(2, stats.GetHashCount(HashType.SpamSum));
Assert.Equal(1, stats.GetItemCount(ItemType.Disk));
Assert.Equal(1, stats.GetItemCount(ItemType.File));
Assert.Equal(1, stats.GetItemCount(ItemType.Media));
Assert.Equal(1, stats.GetItemCount(ItemType.Rom));
Assert.Equal(1, stats.GetItemCount(ItemType.Sample));
Assert.Equal(2, stats.GetStatusCount(ItemStatus.Good));
// ResetStatistics
stats.ResetStatistics();
// Validate post-reset values
Assert.Equal(0, stats.TotalCount);
Assert.Equal(0, stats.TotalSize);
Assert.Equal(0, stats.GetHashCount(HashType.CRC32));
Assert.Equal(0, stats.GetHashCount(HashType.MD2));
Assert.Equal(0, stats.GetHashCount(HashType.MD4));
Assert.Equal(0, stats.GetHashCount(HashType.MD5));
Assert.Equal(0, stats.GetHashCount(HashType.RIPEMD128));
Assert.Equal(0, stats.GetHashCount(HashType.RIPEMD160));
Assert.Equal(0, stats.GetHashCount(HashType.SHA1));
Assert.Equal(0, stats.GetHashCount(HashType.SHA256));
Assert.Equal(0, stats.GetHashCount(HashType.SHA384));
Assert.Equal(0, stats.GetHashCount(HashType.SHA512));
Assert.Equal(0, stats.GetHashCount(HashType.SpamSum));
Assert.Equal(0, stats.GetItemCount(ItemType.Disk));
Assert.Equal(0, stats.GetItemCount(ItemType.File));
Assert.Equal(0, stats.GetItemCount(ItemType.Media));
Assert.Equal(0, stats.GetItemCount(ItemType.Rom));
Assert.Equal(0, stats.GetItemCount(ItemType.Sample));
Assert.Equal(0, stats.GetStatusCount(ItemStatus.Good));
}
#endregion
#region AddStatistics
[Fact]
public void AddStatisticsTest()
{
var rom = CreateRom();
var origStats = new DatStatistics();
origStats.AddItemStatistics(rom);
var newStats = new DatStatistics();
newStats.AddStatistics(origStats);
Assert.Equal(1, newStats.TotalCount);
Assert.Equal(1, newStats.TotalSize);
Assert.Equal(1, newStats.GetHashCount(HashType.CRC32));
Assert.Equal(1, newStats.GetHashCount(HashType.MD2));
Assert.Equal(1, newStats.GetHashCount(HashType.MD4));
Assert.Equal(1, newStats.GetHashCount(HashType.MD5));
Assert.Equal(1, newStats.GetHashCount(HashType.RIPEMD128));
Assert.Equal(1, newStats.GetHashCount(HashType.RIPEMD160));
Assert.Equal(1, newStats.GetHashCount(HashType.SHA1));
Assert.Equal(1, newStats.GetHashCount(HashType.SHA256));
Assert.Equal(1, newStats.GetHashCount(HashType.SHA384));
Assert.Equal(1, newStats.GetHashCount(HashType.SHA512));
Assert.Equal(1, newStats.GetHashCount(HashType.SpamSum));
Assert.Equal(1, newStats.GetItemCount(ItemType.Rom));
Assert.Equal(1, newStats.GetStatusCount(ItemStatus.Good));
}
#endregion
#region Helpers
/// <summary>
/// Create a Disk for testing
/// </summary>
private static Disk CreateDisk()
{
var disk = new Disk();
disk.SetFieldValue<string?>(Data.Models.Metadata.Disk.StatusKey, ItemStatus.Good.AsStringValue());
disk.SetFieldValue<string?>(Data.Models.Metadata.Disk.MD5Key, HashType.MD5.ZeroString);
disk.SetFieldValue<string?>(Data.Models.Metadata.Disk.SHA1Key, HashType.SHA1.ZeroString);
return disk;
}
/// <summary>
/// Create a File for testing
/// </summary>
private static File CreateFile()
{
var file = new File
{
Size = 1,
CRC = HashType.CRC32.ZeroString,
MD5 = HashType.MD5.ZeroString,
SHA1 = HashType.SHA1.ZeroString,
SHA256 = HashType.SHA256.ZeroString
};
return file;
}
/// <summary>
/// Create a Media for testing
/// </summary>
private static Media CreateMedia()
{
var media = new Media();
media.SetFieldValue<string?>(Data.Models.Metadata.Media.MD5Key, HashType.MD5.ZeroString);
media.SetFieldValue<string?>(Data.Models.Metadata.Media.SHA1Key, HashType.SHA1.ZeroString);
media.SetFieldValue<string?>(Data.Models.Metadata.Media.SHA256Key, HashType.SHA256.ZeroString);
media.SetFieldValue<string?>(Data.Models.Metadata.Media.SpamSumKey, HashType.SpamSum.ZeroString);
return media;
}
/// <summary>
/// Create a Rom for testing
/// </summary>
private static Rom CreateRom()
{
var rom = new Rom();
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.StatusKey, ItemStatus.Good.AsStringValue());
rom.SetFieldValue<long>(Data.Models.Metadata.Rom.SizeKey, 1);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, HashType.CRC32.ZeroString);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.MD2Key, HashType.MD2.ZeroString);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.MD4Key, HashType.MD4.ZeroString);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.MD5Key, HashType.MD5.ZeroString);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.RIPEMD128Key, HashType.RIPEMD128.ZeroString);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.RIPEMD160Key, HashType.RIPEMD160.ZeroString);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, HashType.SHA1.ZeroString);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA256Key, HashType.SHA256.ZeroString);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA384Key, HashType.SHA384.ZeroString);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA512Key, HashType.SHA512.ZeroString);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SpamSumKey, HashType.SpamSum.ZeroString);
return rom;
}
/// <summary>
/// Create a Sample for testing
/// </summary>
private static Sample CreateSample() => new();
#endregion
}
}

View File

@@ -0,0 +1,114 @@
using Xunit;
namespace SabreTools.Metadata.DatFiles.Test
{
public class ExtensionsTests
{
#region String to Enum
[Theory]
[InlineData(null, MergingFlag.None)]
[InlineData("none", MergingFlag.None)]
[InlineData("split", MergingFlag.Split)]
[InlineData("merged", MergingFlag.Merged)]
[InlineData("nonmerged", MergingFlag.NonMerged)]
[InlineData("unmerged", MergingFlag.NonMerged)]
[InlineData("fullmerged", MergingFlag.FullMerged)]
[InlineData("device", MergingFlag.DeviceNonMerged)]
[InlineData("devicenonmerged", MergingFlag.DeviceNonMerged)]
[InlineData("deviceunmerged", MergingFlag.DeviceNonMerged)]
[InlineData("full", MergingFlag.FullNonMerged)]
[InlineData("fullnonmerged", MergingFlag.FullNonMerged)]
[InlineData("fullunmerged", MergingFlag.FullNonMerged)]
public void AsMergingFlagTest(string? field, MergingFlag expected)
{
MergingFlag actual = field.AsMergingFlag();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, NodumpFlag.None)]
[InlineData("none", NodumpFlag.None)]
[InlineData("obsolete", NodumpFlag.Obsolete)]
[InlineData("required", NodumpFlag.Required)]
[InlineData("ignore", NodumpFlag.Ignore)]
public void AsNodumpFlagTest(string? field, NodumpFlag expected)
{
NodumpFlag actual = field.AsNodumpFlag();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, PackingFlag.None)]
[InlineData("none", PackingFlag.None)]
[InlineData("yes", PackingFlag.Zip)]
[InlineData("zip", PackingFlag.Zip)]
[InlineData("no", PackingFlag.Unzip)]
[InlineData("unzip", PackingFlag.Unzip)]
[InlineData("partial", PackingFlag.Partial)]
[InlineData("flat", PackingFlag.Flat)]
[InlineData("fileonly", PackingFlag.FileOnly)]
public void AsPackingFlagTest(string? field, PackingFlag expected)
{
PackingFlag actual = field.AsPackingFlag();
Assert.Equal(expected, actual);
}
#endregion
#region Enum to String
[Theory]
[InlineData(MergingFlag.None, true, "none")]
[InlineData(MergingFlag.None, false, "none")]
[InlineData(MergingFlag.Split, true, "split")]
[InlineData(MergingFlag.Split, false, "split")]
[InlineData(MergingFlag.Merged, true, "merged")]
[InlineData(MergingFlag.Merged, false, "merged")]
[InlineData(MergingFlag.NonMerged, true, "unmerged")]
[InlineData(MergingFlag.NonMerged, false, "nonmerged")]
[InlineData(MergingFlag.FullMerged, true, "fullmerged")]
[InlineData(MergingFlag.FullMerged, false, "fullmerged")]
[InlineData(MergingFlag.DeviceNonMerged, true, "deviceunmerged")]
[InlineData(MergingFlag.DeviceNonMerged, false, "device")]
[InlineData(MergingFlag.FullNonMerged, true, "fullunmerged")]
[InlineData(MergingFlag.FullNonMerged, false, "full")]
public void FromMergingFlagTest(MergingFlag field, bool useSecond, string? expected)
{
string? actual = field.AsStringValue(useSecond);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(NodumpFlag.None, "none")]
[InlineData(NodumpFlag.Obsolete, "obsolete")]
[InlineData(NodumpFlag.Required, "required")]
[InlineData(NodumpFlag.Ignore, "ignore")]
public void FromNodumpFlagTest(NodumpFlag field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(PackingFlag.None, true, "none")]
[InlineData(PackingFlag.None, false, "none")]
[InlineData(PackingFlag.Zip, true, "yes")]
[InlineData(PackingFlag.Zip, false, "zip")]
[InlineData(PackingFlag.Unzip, true, "no")]
[InlineData(PackingFlag.Unzip, false, "unzip")]
[InlineData(PackingFlag.Partial, true, "partial")]
[InlineData(PackingFlag.Partial, false, "partial")]
[InlineData(PackingFlag.Flat, true, "flat")]
[InlineData(PackingFlag.Flat, false, "flat")]
[InlineData(PackingFlag.FileOnly, true, "fileonly")]
[InlineData(PackingFlag.FileOnly, false, "fileonly")]
public void FromPackingFlagTest(PackingFlag field, bool useSecond, string? expected)
{
string? actual = field.AsStringValue(useSecond);
Assert.Equal(expected, actual);
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,818 @@
using System.Collections.Generic;
using SabreTools.Hashing;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
using Xunit;
namespace SabreTools.Metadata.DatFiles.Test
{
public class ItemDictionaryTests
{
#region AddItem
[Fact]
public void AddItem_Disk_WithHashes()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
DatItem disk = new Disk();
disk.SetName("item");
disk.SetFieldValue<string?>(Data.Models.Metadata.Disk.SHA1Key, "deadbeef");
disk.SetFieldValue<Source?>(DatItem.SourceKey, source);
disk.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(disk, statsOnly: false);
DatItem actual = Assert.Single(dict.GetItemsForBucket("default"));
Assert.True(actual is Disk);
Assert.Equal("none", actual.GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey));
}
[Fact]
public void AddItem_Disk_WithoutHashes()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
DatItem disk = new Disk();
disk.SetName("item");
disk.SetFieldValue<Source?>(DatItem.SourceKey, source);
disk.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(disk, statsOnly: false);
DatItem actual = Assert.Single(dict.GetItemsForBucket("default"));
Assert.True(actual is Disk);
Assert.Equal("nodump", actual.GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey));
}
[Fact]
public void AddItem_File_WithHashes()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
var file = new File();
file.SetName("item");
file.SHA1 = "deadbeef";
file.SetFieldValue<Source?>(DatItem.SourceKey, source);
file.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(file, statsOnly: false);
DatItem actual = Assert.Single(dict.GetItemsForBucket("default"));
Assert.True(actual is File);
//Assert.Equal("none", actual.GetStringFieldValue(File.StatusKey));
}
[Fact]
public void AddItem_File_WithoutHashes()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
DatItem file = new File();
file.SetName("item");
file.SetFieldValue<Source?>(DatItem.SourceKey, source);
file.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(file, statsOnly: false);
DatItem actual = Assert.Single(dict.GetItemsForBucket("default"));
Assert.True(actual is File);
//Assert.Equal("nodump", actual.GetStringFieldValue(File.StatusKey));
}
[Fact]
public void AddItem_Media_WithHashes()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
DatItem media = new Media();
media.SetName("item");
media.SetFieldValue<string?>(Data.Models.Metadata.Media.SHA1Key, "deadbeef");
media.SetFieldValue<Source?>(DatItem.SourceKey, source);
media.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(media, statsOnly: false);
DatItem actual = Assert.Single(dict.GetItemsForBucket("default"));
Assert.True(actual is Media);
//Assert.Equal("none", actual.GetStringFieldValue(Data.Models.Metadata.Media.StatusKey));
}
[Fact]
public void AddItem_Media_WithoutHashes()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
DatItem media = new Media();
media.SetName("item");
media.SetFieldValue<Source?>(DatItem.SourceKey, source);
media.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(media, statsOnly: false);
DatItem actual = Assert.Single(dict.GetItemsForBucket("default"));
Assert.True(actual is Media);
//Assert.Equal("nodump", actual.GetStringFieldValue(Data.Models.Metadata.Media.StatusKey));
}
[Fact]
public void AddItem_Rom_WithHashesWithSize()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
DatItem rom = new Rom();
rom.SetName("item");
rom.SetFieldValue<long?>(Data.Models.Metadata.Rom.SizeKey, 12345);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "deadbeef");
rom.SetFieldValue<Source?>(DatItem.SourceKey, source);
rom.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(rom, statsOnly: false);
DatItem actual = Assert.Single(dict.GetItemsForBucket("default"));
Assert.True(actual is Rom);
Assert.Equal(12345, actual.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey));
Assert.Equal("deadbeef", actual.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key));
Assert.Equal("none", actual.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey));
}
[Fact]
public void AddItem_Rom_WithoutHashesWithSize()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
DatItem rom = new Rom();
rom.SetName("item");
rom.SetFieldValue<long?>(Data.Models.Metadata.Rom.SizeKey, 12345);
rom.SetFieldValue<Source?>(DatItem.SourceKey, source);
rom.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(rom, statsOnly: false);
DatItem actual = Assert.Single(dict.GetItemsForBucket("default"));
Assert.True(actual is Rom);
Assert.Equal(12345, actual.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey));
Assert.Null(actual.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key));
Assert.Equal("nodump", actual.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey));
}
[Fact]
public void AddItem_Rom_WithHashesWithoutSize()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
DatItem rom = new Rom();
rom.SetName("item");
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "deadbeef");
rom.SetFieldValue<Source?>(DatItem.SourceKey, source);
rom.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(rom, statsOnly: false);
DatItem actual = Assert.Single(dict.GetItemsForBucket("default"));
Assert.True(actual is Rom);
Assert.Null(actual.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey));
Assert.Equal("deadbeef", actual.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key));
Assert.Equal("none", actual.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey));
}
[Fact]
public void AddItem_Rom_WithoutHashesWithoutSize()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
DatItem rom = new Rom();
rom.SetName("item");
rom.SetFieldValue<Source?>(DatItem.SourceKey, source);
rom.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(rom, statsOnly: false);
DatItem actual = Assert.Single(dict.GetItemsForBucket("default"));
Assert.True(actual is Rom);
Assert.Equal(0, actual.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey));
Assert.Equal(HashType.SHA1.ZeroString, actual.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key));
Assert.Equal("none", actual.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey));
}
[Fact]
public void AddItem_StatsOnly()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
DatItem item = new Rom();
item.SetName("item");
item.SetFieldValue<Source?>(DatItem.SourceKey, source);
item.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(item, statsOnly: true);
Assert.Empty(dict.GetItemsForBucket("default"));
}
[Fact]
public void AddItem_NormalAdd()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
DatItem item = new Rom();
item.SetName("item");
item.SetFieldValue<Source?>(DatItem.SourceKey, source);
item.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(item, statsOnly: false);
Assert.Single(dict.GetItemsForBucket("default"));
}
#endregion
#region ClearMarked
[Fact]
public void ClearMarkedTest()
{
// Setup the items
Machine machine = new Machine();
machine.SetName("game-1");
DatItem rom1 = new Rom();
rom1.SetName("rom-1");
rom1.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "DEAEEF");
rom1.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "0000000fbbb37f8488100b1b4697012de631a5e6");
rom1.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "1024");
rom1.CopyMachineInformation(machine);
DatItem rom2 = new Rom();
rom2.SetName("rom-2");
rom2.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "DEAEEF");
rom2.SetFieldValue<bool?>(DatItem.RemoveKey, true);
rom2.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "000000e948edcb4f7704b8af85a77a3339ecce44");
rom2.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "1024");
rom2.CopyMachineInformation(machine);
// Setup the dictionary
var dict = new ItemDictionary();
dict.AddItem(rom1, statsOnly: false);
dict.AddItem(rom2, statsOnly: false);
dict.ClearMarked();
Assert.Empty(dict.GetItemsForBucket("default"));
List<DatItem> items = dict.GetItemsForBucket("game-1");
Assert.Single(items);
}
#endregion
#region GetItemsForBucket
[Fact]
public void GetItemsForBucket_NullBucketName()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
machine.SetName("machine");
DatItem item = new Rom();
item.SetFieldValue<Source?>(DatItem.SourceKey, source);
item.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(item, statsOnly: false);
var actual = dict.GetItemsForBucket(null, filter: false);
Assert.Empty(actual);
}
[Fact]
public void GetItemsForBucket_InvalidBucketName()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
machine.SetName("machine");
DatItem item = new Rom();
item.SetFieldValue<Source?>(DatItem.SourceKey, source);
item.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(item, statsOnly: false);
var actual = dict.GetItemsForBucket("INVALID", filter: false);
Assert.Empty(actual);
}
[Fact]
public void GetItemsForBucket_RemovedFilter()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
machine.SetName("machine");
DatItem item = new Rom();
item.SetFieldValue<bool?>(DatItem.RemoveKey, true);
item.SetFieldValue<Source?>(DatItem.SourceKey, source);
item.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(item, statsOnly: false);
var actual = dict.GetItemsForBucket("machine", filter: true);
Assert.Empty(actual);
}
[Fact]
public void GetItemsForBucket_RemovedNoFilter()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
machine.SetName("machine");
DatItem item = new Rom();
item.SetFieldValue<bool?>(DatItem.RemoveKey, true);
item.SetFieldValue<Source?>(DatItem.SourceKey, source);
item.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(item, statsOnly: false);
var actual = dict.GetItemsForBucket("machine", filter: false);
Assert.Single(actual);
}
[Fact]
public void GetItemsForBucket_Standard()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
machine.SetName("machine");
DatItem item = new Rom();
item.SetFieldValue<Source?>(DatItem.SourceKey, source);
item.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(item, statsOnly: false);
var actual = dict.GetItemsForBucket("machine", filter: false);
Assert.Single(actual);
}
#endregion
#region RemoveBucket
[Fact]
public void RemoveBucketTest()
{
Machine machine = new Machine();
machine.SetName("game-1");
DatItem datItem = new Rom();
datItem.SetName("rom-1");
datItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "DEAEEF");
datItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "0000000fbbb37f8488100b1b4697012de631a5e6");
datItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "1024");
datItem.CopyMachineInformation(machine);
var dict = new ItemDictionary();
dict.AddItem(datItem, statsOnly: false);
dict.RemoveBucket("game-1");
Assert.Empty(dict.GetItemsForBucket("default"));
Assert.Empty(dict.GetItemsForBucket("game-1"));
}
#endregion
#region RemoveItem
[Fact]
public void RemoveItemTest()
{
Machine machine = new Machine();
machine.SetName("game-1");
DatItem datItem = new Rom();
datItem.SetName("rom-1");
datItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "DEAEEF");
datItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "0000000fbbb37f8488100b1b4697012de631a5e6");
datItem.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "1024");
datItem.CopyMachineInformation(machine);
var dict = new ItemDictionary();
dict.AddItem(datItem, statsOnly: false);
dict.RemoveItem("game-1", (Rom)datItem.Clone(), 0);
Assert.Empty(dict.GetItemsForBucket("default"));
Assert.Empty(dict.GetItemsForBucket("game-1"));
}
#endregion
#region BucketBy
[Theory]
[InlineData(ItemKey.NULL, 2)]
[InlineData(ItemKey.Machine, 2)]
[InlineData(ItemKey.CRC, 1)]
[InlineData(ItemKey.SHA1, 4)]
public void BucketByTest(ItemKey itemKey, int expected)
{
// Setup the items
Machine machine1 = new Machine();
machine1.SetName("game-1");
Machine machine2 = new Machine();
machine2.SetName("game-2");
DatItem rom1 = new Rom();
rom1.SetName("rom-1");
rom1.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "DEAEEF");
rom1.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "0000000fbbb37f8488100b1b4697012de631a5e6");
rom1.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "1024");
rom1.CopyMachineInformation(machine1);
DatItem rom2 = new Rom();
rom2.SetName("rom-2");
rom2.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "DEAEEF");
rom2.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "000000e948edcb4f7704b8af85a77a3339ecce44");
rom2.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "1024");
rom1.CopyMachineInformation(machine1);
DatItem rom3 = new Rom();
rom3.SetName("rom-3");
rom3.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "DEAEEF");
rom3.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "00000ea4014ce66679e7e17d56ac510f67e39e26");
rom3.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "1024");
rom1.CopyMachineInformation(machine2);
DatItem rom4 = new Rom();
rom4.SetName("rom-4");
rom4.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "DEAEEF");
rom4.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "00000151d437442e74e5134023fab8bf694a2487");
rom4.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "1024");
rom1.CopyMachineInformation(machine2);
// Setup the dictionary
var dict = new ItemDictionary();
dict.AddItem(rom1, statsOnly: false);
dict.AddItem(rom2, statsOnly: false);
dict.AddItem(rom3, statsOnly: false);
dict.AddItem(rom4, statsOnly: false);
dict.BucketBy(itemKey);
Assert.Equal(expected, dict.SortedKeys.Length);
}
#endregion
#region Deduplicate
[Fact]
public void DeduplicateTest()
{
// Setup the items
Machine machine = new Machine();
machine.SetName("game-1");
DatItem rom1 = new Rom();
rom1.SetName("rom-1");
rom1.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "0000000fbbb37f8488100b1b4697012de631a5e6");
rom1.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "1024");
rom1.CopyMachineInformation(machine);
DatItem rom2 = new Rom();
rom2.SetName("rom-2");
rom2.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "0000000fbbb37f8488100b1b4697012de631a5e6");
rom2.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "1024");
rom2.CopyMachineInformation(machine);
// Setup the dictionary
var dict = new ItemDictionary();
dict.AddItem(rom1, statsOnly: false);
dict.AddItem(rom2, statsOnly: false);
dict.Deduplicate();
Assert.Equal(1, dict.DatStatistics.TotalCount);
}
#endregion
#region GetDuplicateStatus
[Fact]
public void GetDuplicateStatus_NullOther_NoDupe()
{
var dict = new ItemDictionary();
DatItem item = new Rom();
DatItem? lastItem = null;
var actual = dict.GetDuplicateStatus(item, lastItem);
Assert.Equal((DupeType)0x00, actual);
}
[Fact]
public void GetDuplicateStatus_DifferentTypes_NoDupe()
{
var dict = new ItemDictionary();
var rom = new Rom();
DatItem? lastItem = new Disk();
var actual = dict.GetDuplicateStatus(rom, lastItem);
Assert.Equal((DupeType)0x00, actual);
}
[Fact]
public void GetDuplicateStatus_MismatchedHashes_NoDupe()
{
var dict = new ItemDictionary();
Machine? machineA = new Machine();
machineA.SetName("name-same");
Machine? machineB = new Machine();
machineB.SetName("name-same");
var romA = new Rom();
romA.SetName("same-name");
romA.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "BEEFDEAD");
romA.SetFieldValue<Source?>(DatItem.SourceKey, new Source(0));
romA.CopyMachineInformation(machineA);
var romB = new Rom();
romB.SetName("same-name");
romB.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "DEADBEEF");
romB.SetFieldValue<Source?>(DatItem.SourceKey, new Source(1));
romB.CopyMachineInformation(machineB);
var actual = dict.GetDuplicateStatus(romA, romB);
Assert.Equal((DupeType)0x00, actual);
}
[Fact]
public void GetDuplicateStatus_DifferentSource_NameMatch_ExternalAll()
{
var dict = new ItemDictionary();
Machine? machineA = new Machine();
machineA.SetName("name-same");
Machine? machineB = new Machine();
machineB.SetName("name-same");
var romA = new Rom();
romA.SetName("same-name");
romA.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "DEADBEEF");
romA.SetFieldValue<Source?>(DatItem.SourceKey, new Source(0));
romA.CopyMachineInformation(machineA);
var romB = new Rom();
romB.SetName("same-name");
romB.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "DEADBEEF");
romB.SetFieldValue<Source?>(DatItem.SourceKey, new Source(1));
romB.CopyMachineInformation(machineB);
var actual = dict.GetDuplicateStatus(romA, romB);
Assert.Equal(DupeType.External | DupeType.All, actual);
}
[Fact]
public void GetDuplicateStatus_DifferentSource_NoNameMatch_ExternalHash()
{
var dict = new ItemDictionary();
Machine? machineA = new Machine();
machineA.SetName("name-same");
Machine? machineB = new Machine();
machineB.SetName("not-name-same");
var romA = new Rom();
romA.SetName("same-name");
romA.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "DEADBEEF");
romA.SetFieldValue<Source?>(DatItem.SourceKey, new Source(0));
romA.CopyMachineInformation(machineA);
var romB = new Rom();
romB.SetName("same-name");
romB.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "DEADBEEF");
romB.SetFieldValue<Source?>(DatItem.SourceKey, new Source(1));
romB.CopyMachineInformation(machineB);
var actual = dict.GetDuplicateStatus(romA, romB);
Assert.Equal(DupeType.External | DupeType.Hash, actual);
}
[Fact]
public void GetDuplicateStatus_SameSource_NameMatch_InternalAll()
{
var dict = new ItemDictionary();
Machine? machineA = new Machine();
machineA.SetName("name-same");
Machine? machineB = new Machine();
machineB.SetName("name-same");
var romA = new Rom();
romA.SetName("same-name");
romA.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "DEADBEEF");
romA.SetFieldValue<Source?>(DatItem.SourceKey, new Source(0));
romA.CopyMachineInformation(machineA);
var romB = new Rom();
romB.SetName("same-name");
romB.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "DEADBEEF");
romB.SetFieldValue<Source?>(DatItem.SourceKey, new Source(0));
romB.CopyMachineInformation(machineB);
var actual = dict.GetDuplicateStatus(romA, romB);
Assert.Equal(DupeType.Internal | DupeType.All, actual);
}
[Fact]
public void GetDuplicateStatus_SameSource_NoNameMatch_InternalHash()
{
var dict = new ItemDictionary();
Machine? machineA = new Machine();
machineA.SetName("name-same");
Machine? machineB = new Machine();
machineB.SetName("not-name-same");
var romA = new Rom();
romA.SetName("same-name");
romA.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "DEADBEEF");
romA.SetFieldValue<Source?>(DatItem.SourceKey, new Source(0));
romA.CopyMachineInformation(machineA);
var romB = new Rom();
romB.SetName("same-name");
romB.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "DEADBEEF");
romB.SetFieldValue<Source?>(DatItem.SourceKey, new Source(0));
romB.CopyMachineInformation(machineB);
var actual = dict.GetDuplicateStatus(romA, romB);
Assert.Equal(DupeType.Internal | DupeType.Hash, actual);
}
#endregion
#region GetDuplicates
[Theory]
[InlineData(true, 1)]
[InlineData(false, 0)]
public void GetDuplicatesTest(bool hasDuplicate, int expected)
{
// Setup the items
Machine machine = new Machine();
machine.SetName("game-1");
DatItem rom1 = new Rom();
rom1.SetName("rom-1");
rom1.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "0000000fbbb37f8488100b1b4697012de631a5e6");
rom1.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "1024");
rom1.CopyMachineInformation(machine);
DatItem rom2 = new Rom();
rom2.SetName("rom-2");
rom2.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "000000e948edcb4f7704b8af85a77a3339ecce44");
rom2.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "1024");
rom2.CopyMachineInformation(machine);
// Setup the dictionary
var dict = new ItemDictionary();
dict.AddItem(rom1, statsOnly: false);
dict.AddItem(rom2, statsOnly: false);
// Setup the test item
DatItem rom = new Rom();
rom.SetName("rom-1");
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "0000000fbbb37f8488100b1b4697012de631a5e6");
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, hasDuplicate ? "1024" : "2048");
rom.CopyMachineInformation(machine);
var actual = dict.GetDuplicates(rom);
Assert.Equal(expected, actual.Count);
}
#endregion
#region HasDuplicates
[Theory]
[InlineData(true)]
[InlineData(false)]
public void HasDuplicatesTest(bool expected)
{
// Setup the items
Machine machine = new Machine();
machine.SetName("game-1");
DatItem rom1 = new Rom();
rom1.SetName("rom-1");
rom1.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "0000000fbbb37f8488100b1b4697012de631a5e6");
rom1.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "1024");
rom1.CopyMachineInformation(machine);
DatItem rom2 = new Rom();
rom2.SetName("rom-2");
rom2.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "000000e948edcb4f7704b8af85a77a3339ecce44");
rom2.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "1024");
rom1.CopyMachineInformation(machine);
// Setup the dictionary
var dict = new ItemDictionary();
dict.AddItem("game-1", rom1);
dict.AddItem("game-1", rom2);
// Setup the test item
DatItem rom = new Rom();
rom.SetName("rom-1");
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, "0000000fbbb37f8488100b1b4697012de631a5e6");
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, expected ? "1024" : "2048");
rom1.CopyMachineInformation(machine);
bool actual = dict.HasDuplicates(rom);
Assert.Equal(expected, actual);
}
#endregion
#region RecalculateStats
[Fact]
public void RecalculateStatsTest()
{
Source source = new Source(0, source: null);
Machine machine = new Machine();
machine.SetName("machine");
DatItem item = new Rom();
item.SetName("rom");
item.SetFieldValue<long?>(Data.Models.Metadata.Rom.SizeKey, 12345);
item.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, "deadbeef");
item.SetFieldValue<Source?>(DatItem.SourceKey, source);
item.SetFieldValue<Machine?>(DatItem.MachineKey, machine);
var dict = new ItemDictionary();
_ = dict.AddItem(item, statsOnly: false);
Assert.Equal(1, dict.DatStatistics.TotalCount);
Assert.Equal(1, dict.DatStatistics.GetItemCount(ItemType.Rom));
Assert.Equal(12345, dict.DatStatistics.TotalSize);
Assert.Equal(1, dict.DatStatistics.GetHashCount(HashType.CRC32));
Assert.Equal(0, dict.DatStatistics.GetHashCount(HashType.MD5));
item.SetFieldValue<string?>(Data.Models.Metadata.Rom.MD5Key, "deadbeef");
dict.RecalculateStats();
Assert.Equal(1, dict.DatStatistics.TotalCount);
Assert.Equal(1, dict.DatStatistics.GetItemCount(ItemType.Rom));
Assert.Equal(12345, dict.DatStatistics.TotalSize);
Assert.Equal(1, dict.DatStatistics.GetHashCount(HashType.CRC32));
Assert.Equal(1, dict.DatStatistics.GetHashCount(HashType.MD5));
}
#endregion
}
}

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="8.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageReference Include="SabreTools.Hashing" Version="[2.0.0]" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SabreTools.Metadata\SabreTools.Metadata.csproj" />
<ProjectReference Include="..\SabreTools.Metadata.DatFiles\SabreTools.Metadata.DatFiles.csproj" />
<ProjectReference Include="..\SabreTools.Metadata.DatItems\SabreTools.Metadata.DatItems.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,801 @@
using System;
using System.Collections.Generic;
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
using System.Collections.Concurrent;
#endif
using System.IO;
using System.Text.RegularExpressions;
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
using System.Threading.Tasks;
#endif
using SabreTools.Metadata.Filter;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
#pragma warning disable IDE0057 // Use range operator
#pragma warning disable IDE0059 // Unnecessary assignment of a value
namespace SabreTools.Metadata.DatFiles
{
public partial class DatFile
{
#region Constants
/// <summary>
/// Scene name Regex pattern
/// </summary>
private const string SceneNamePattern = @"([0-9]{2}\.[0-9]{2}\.[0-9]{2}-)(.*?-.*?)";
#endregion
#region Filtering
/// <summary>
/// Execute all filters in a filter runner on the items in the dictionary
/// </summary>
/// <param name="filterRunner">Preconfigured filter runner to use</param>
public void ExecuteFilters(FilterRunner filterRunner)
{
ExecuteFiltersImpl(filterRunner);
ExecuteFiltersImplDB(filterRunner);
}
/// <summary>
/// Use game descriptions as names, updating cloneof/romof/sampleof
/// </summary>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
public void MachineDescriptionToName(bool throwOnError = false)
{
MachineDescriptionToNameImpl(throwOnError);
MachineDescriptionToNameImplDB(throwOnError);
}
/// <summary>
/// Ensure that all roms are in their own game (or at least try to ensure)
/// </summary>
public void SetOneRomPerGame()
{
SetOneRomPerGameImpl();
SetOneRomPerGameImplDB();
}
/// <summary>
/// Filter a DAT using 1G1R logic given an ordered set of regions
/// </summary>
/// <param name="regionList">List of regions in order of priority</param>
/// <remarks>
/// In the most technical sense, the way that the region list is being used does not
/// confine its values to be just regions. Since it's essentially acting like a
/// specialized version of the machine name filter, anything that is usually encapsulated
/// in parenthesis would be matched on, including disc numbers, languages, editions,
/// and anything else commonly used. Please note that, unlike other existing 1G1R
/// solutions, this does not have the ability to contain custom mappings of parent
/// to clone sets based on name, nor does it have the ability to match on the
/// Release DatItem type.
/// </remarks>
public void SetOneGamePerRegion(List<string> regionList)
{
SetOneGamePerRegionImpl(regionList);
SetOneGamePerRegionImplDB(regionList);
}
/// <summary>
/// Strip the dates from the beginning of scene-style set names
/// </summary>
public void StripSceneDatesFromItems()
{
StripSceneDatesFromItemsImpl();
StripSceneDatesFromItemsImplDB();
}
#endregion
#region Filtering Implementations
/// <summary>
/// Create machine to description mapping dictionary
/// </summary>
/// <remarks>Applies to <see cref="Items"/></remarks>
private IDictionary<string, string> CreateMachineToDescriptionMapping()
{
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
ConcurrentDictionary<string, string> mapping = new();
#else
Dictionary<string, string> mapping = [];
#endif
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
Parallel.ForEach(Items.SortedKeys, key =>
#else
foreach (var key in Items.SortedKeys)
#endif
{
var items = GetItemsForBucket(key);
if (items is null)
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
return;
#else
continue;
#endif
foreach (DatItem item in items)
{
// Get the current machine
var machine = item.GetMachine();
if (machine is null)
continue;
// Get the values to check against
string? machineName = machine.GetName();
string? machineDesc = machine.GetStringFieldValue(Data.Models.Metadata.Machine.DescriptionKey);
if (machineName is null || machineDesc is null)
continue;
// Adjust the description
machineDesc = machineDesc.Replace('/', '_').Replace("\"", "''").Replace(":", " -");
if (machineName == machineDesc)
continue;
// If the key mapping doesn't exist, add it
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
mapping.TryAdd(machineName, machineDesc);
#else
if (!mapping.ContainsKey(machineName))
mapping[machineName] = machineDesc;
#endif
}
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
});
#else
}
#endif
return mapping;
}
/// <summary>
/// Create machine to description mapping dictionary
/// </summary>
/// <remarks>Applies to <see cref="ItemsDB"/></remarks>
private Dictionary<string, string> CreateMachineToDescriptionMappingDB()
{
Dictionary<string, string> mapping = [];
foreach (var machine in GetMachinesDB())
{
// Get the current machine
if (machine.Value is null)
continue;
// Get the values to check against
string? machineName = machine.Value.GetName();
string? machineDesc = machine.Value.GetStringFieldValue(Data.Models.Metadata.Machine.DescriptionKey);
if (machineName is null || machineDesc is null)
continue;
// Adjust the description
machineDesc = machineDesc.Replace('/', '_').Replace("\"", "''").Replace(":", " -");
if (machineName == machineDesc)
continue;
// If the key mapping doesn't exist, add it
if (!mapping.ContainsKey(machineName))
mapping[machineName] = machineDesc;
}
return mapping;
}
/// <summary>
/// Execute all filters in a filter runner on the items in the dictionary
/// </summary>
/// <param name="filterRunner">Preconfigured filter runner to use</param>
/// <remarks>Applies to <see cref="Items"/></remarks>
private void ExecuteFiltersImpl(FilterRunner filterRunner)
{
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
Parallel.ForEach(Items.SortedKeys, key =>
#else
foreach (var key in Items.SortedKeys)
#endif
{
ExecuteFilterOnBucket(filterRunner, key);
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
});
#else
}
#endif
}
/// <summary>
/// Execute all filters in a filter runner on the items in the dictionary
/// </summary>
/// <param name="filterRunner">Preconfigured filter runner to use</param>
/// <remarks>Applies to <see cref="ItemsDB"/></remarks>
private void ExecuteFiltersImplDB(FilterRunner filterRunner)
{
List<string> keys = [.. ItemsDB.SortedKeys];
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
Parallel.ForEach(keys, key =>
#else
foreach (var key in keys)
#endif
{
ExecuteFilterOnBucketDB(filterRunner, key);
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
});
#else
}
#endif
}
/// <summary>
/// Execute all filters in a filter runner on a single bucket
/// </summary>
/// <param name="filterRunner">Preconfigured filter runner to use</param>
/// <param name="bucketName">Name of the bucket to filter on</param>
/// <remarks>Applies to <see cref="Items"/></remarks>
private void ExecuteFilterOnBucket(FilterRunner filterRunner, string bucketName)
{
List<DatItem>? items = GetItemsForBucket(bucketName);
if (items is null)
return;
// Filter all items in the current key
foreach (var item in items)
{
if (!item.PassesFilter(filterRunner))
item.SetFieldValue<bool?>(DatItem.RemoveKey, true);
}
}
/// <summary>
/// Execute all filters in a filter runner on a single bucket
/// </summary>
/// <param name="filterRunner">Preconfigured filter runner to use</param>
/// <param name="bucketName">Name of the bucket to filter on</param>
/// <remarks>Applies to <see cref="ItemsDB"/></remarks>
private void ExecuteFilterOnBucketDB(FilterRunner filterRunner, string bucketName)
{
var items = GetItemsForBucketDB(bucketName);
if (items is null)
return;
// Filter all items in the current key
List<long> newItems = [];
foreach (var item in items)
{
if (!item.Value.PassesFilterDB(filterRunner))
item.Value.SetFieldValue<bool?>(DatItem.RemoveKey, true);
}
}
/// <summary>
/// Use game descriptions as names, updating cloneof/romof/sampleof
/// </summary>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <remarks>Applies to <see cref="Items"/></remarks>
private void MachineDescriptionToNameImpl(bool throwOnError = false)
{
try
{
// First we want to get a mapping for all games to description
var mapping = CreateMachineToDescriptionMapping();
// Now we loop through every item and update accordingly
UpdateMachineNamesFromDescriptions(mapping);
}
catch (Exception ex) when (!throwOnError)
{
_logger.Warning(ex.ToString());
}
}
/// <summary>
/// Use game descriptions as names, updating cloneof/romof/sampleof
/// </summary>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <remarks>Applies to <see cref="ItemsDB"/></remarks>
private void MachineDescriptionToNameImplDB(bool throwOnError = false)
{
try
{
// First we want to get a mapping for all games to description
var mapping = CreateMachineToDescriptionMappingDB();
// Now we loop through every item and update accordingly
UpdateMachineNamesFromDescriptionsDB(mapping);
}
catch (Exception ex) when (!throwOnError)
{
_logger.Warning(ex.ToString());
}
}
/// <summary>
/// Filter a DAT using 1G1R logic given an ordered set of regions
/// </summary>
/// <param name="regionList">List of regions in order of priority</param>
/// <remarks>Applies to <see cref="Items"/></remarks>
private void SetOneGamePerRegionImpl(List<string> regionList)
{
// For sake of ease, the first thing we want to do is bucket by game
BucketBy(ItemKey.Machine, norename: true);
// Then we want to get a mapping of all machines to parents
Dictionary<string, List<string>> parents = [];
foreach (string key in Items.SortedKeys)
{
DatItem item = GetItemsForBucket(key)[0];
// Get machine information
Machine? machine = item.GetMachine();
string? machineName = machine?.GetName()?.ToLowerInvariant();
if (machine is null || machineName is null)
continue;
// Get the string values
string? cloneOf = machine.GetStringFieldValue(Data.Models.Metadata.Machine.CloneOfKey)?.ToLowerInvariant();
string? romOf = machine.GetStringFieldValue(Data.Models.Metadata.Machine.RomOfKey)?.ToLowerInvariant();
// Match on CloneOf first
if (!string.IsNullOrEmpty(cloneOf))
{
if (!parents.ContainsKey(cloneOf!))
parents.Add(cloneOf!, []);
parents[cloneOf!].Add(machineName);
}
// Then by RomOf
else if (!string.IsNullOrEmpty(romOf))
{
if (!parents.ContainsKey(romOf!))
parents.Add(romOf!, []);
parents[romOf!].Add(machineName);
}
// Otherwise, treat it as a parent
else
{
if (!parents.ContainsKey(machineName))
parents.Add(machineName, []);
parents[machineName].Add(machineName);
}
}
// Once we have the full list of mappings, filter out games to keep
foreach (string key in parents.Keys)
{
// Find the first machine that matches the regions in order, if possible
string? machine = default;
foreach (string region in regionList)
{
machine = parents[key].Find(m => Regex.IsMatch(m, @"\(.*" + region + @".*\)", RegexOptions.IgnoreCase));
if (machine != default)
break;
}
// If we didn't get a match, use the parent
if (machine == default)
machine = key;
// Remove the key from the list
parents[key].Remove(machine);
// Remove the rest of the items from this key
parents[key].ForEach(k => RemoveBucket(k));
}
// Finally, strip out the parent tags
RemoveMachineRelationshipTagsImpl();
}
/// <summary>
/// Filter a DAT using 1G1R logic given an ordered set of regions
/// </summary>
/// <param name="regionList">List of regions in order of priority</param>
/// <remarks>Applies to <see cref="ItemsDB"/></remarks>
private void SetOneGamePerRegionImplDB(List<string> regionList)
{
// Then we want to get a mapping of all machines to parents
Dictionary<string, List<string>> parents = [];
foreach (var machine in GetMachinesDB())
{
if (machine.Value is null)
continue;
// Get machine information
Machine? machineObj = machine.Value;
string? machineName = machineObj?.GetName()?.ToLowerInvariant();
if (machineObj is null || machineName is null)
continue;
// Get the string values
string? cloneOf = machineObj.GetStringFieldValue(Data.Models.Metadata.Machine.CloneOfKey)?.ToLowerInvariant();
string? romOf = machineObj.GetStringFieldValue(Data.Models.Metadata.Machine.RomOfKey)?.ToLowerInvariant();
// Match on CloneOf first
if (!string.IsNullOrEmpty(cloneOf))
{
if (!parents.ContainsKey(cloneOf!))
parents.Add(cloneOf!, []);
parents[cloneOf!].Add(machineName);
}
// Then by RomOf
else if (!string.IsNullOrEmpty(romOf))
{
if (!parents.ContainsKey(romOf!))
parents.Add(romOf!, []);
parents[romOf!].Add(machineName);
}
// Otherwise, treat it as a parent
else
{
if (!parents.ContainsKey(machineName))
parents.Add(machineName, []);
parents[machineName].Add(machineName);
}
}
// Once we have the full list of mappings, filter out games to keep
foreach (string key in parents.Keys)
{
// Find the first machine that matches the regions in order, if possible
string? machine = default;
foreach (string region in regionList)
{
machine = parents[key].Find(m => Regex.IsMatch(m, @"\(.*" + region + @".*\)", RegexOptions.IgnoreCase));
if (machine != default)
break;
}
// If we didn't get a match, use the parent
if (machine == default)
machine = key;
// Remove the key from the list
parents[key].Remove(machine);
// Remove the rest of the items from this key
parents[key].ForEach(k => RemoveMachineDB(k));
}
// Finally, strip out the parent tags
RemoveMachineRelationshipTagsImplDB();
}
/// <summary>
/// Ensure that all roms are in their own game (or at least try to ensure)
/// </summary>
/// <remarks>Applies to <see cref="Items"/></remarks>
private void SetOneRomPerGameImpl()
{
// For each rom, we want to update the game to be "<game name>/<rom name>"
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
Parallel.ForEach(Items.SortedKeys, key =>
#else
foreach (var key in Items.SortedKeys)
#endif
{
var items = GetItemsForBucket(key);
if (items is null)
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
return;
#else
continue;
#endif
for (int i = 0; i < items.Count; i++)
{
SetOneRomPerGameImpl(items[i]);
}
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
});
#else
}
#endif
}
/// <summary>
/// Set internal names to match One Rom Per Game (ORPG) logic
/// </summary>
/// <param name="datItem">DatItem to run logic on</param>
/// <remarks>Applies to <see cref="Items"/></remarks>
private static void SetOneRomPerGameImpl(DatItem datItem)
{
// If the item name is null
string? itemName = datItem.GetName();
if (itemName is null)
return;
// Get the current machine
var machine = datItem.GetMachine();
if (machine is null)
return;
// Clone current machine to avoid conflict
machine = (Machine)machine.Clone();
// Reassign the item to the new machine
datItem.SetFieldValue(DatItem.MachineKey, machine);
// Remove extensions from File and Rom items
if (datItem is DatItems.Formats.File || datItem is Rom)
{
string[] splitname = itemName.Split('.');
itemName = machine.GetName()
+ $"/{string.Join(".", splitname, 0, splitname.Length > 1 ? splitname.Length - 1 : 1)}";
}
else
{
itemName = machine.GetName() + $"/{itemName}";
}
// Strip off "Default" prefix only for ORPG
if (itemName.StartsWith("Default"))
itemName = itemName.Substring("Default".Length + 1);
machine.SetName(itemName);
datItem.SetName(Path.GetFileName(datItem.GetName()));
}
/// <summary>
/// Ensure that all roms are in their own game (or at least try to ensure)
/// </summary>
/// <remarks>Applies to <see cref="ItemsDB"/></remarks>
private void SetOneRomPerGameImplDB()
{
// For each rom, we want to update the game to be "<game name>/<rom name>"
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
Parallel.ForEach(ItemsDB.SortedKeys, key =>
#else
foreach (var key in ItemsDB.SortedKeys)
#endif
{
var items = GetItemsForBucketDB(key);
if (items is null)
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
return;
#else
continue;
#endif
foreach (var item in items)
{
SetOneRomPerGameImplDB(item);
}
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
});
#else
}
#endif
}
/// <summary>
/// Set internal names to match One Rom Per Game (ORPG) logic
/// </summary>
/// <param name="datItem">DatItem to run logic on</param>
/// <remarks>Applies to <see cref="ItemsDB"/></remarks>
private void SetOneRomPerGameImplDB(KeyValuePair<long, DatItem> datItem)
{
// If the item name is null
string? itemName = datItem.Value.GetName();
if (datItem.Key < 0 || itemName is null)
return;
// Get the current machine
var machine = GetMachineForItemDB(datItem.Key);
if (machine.Value is null)
return;
// Clone current machine to avoid conflict
long newMachineIndex = AddMachineDB((Machine)machine.Value.Clone());
machine = new KeyValuePair<long, Machine?>(newMachineIndex, ItemsDB.GetMachine(newMachineIndex));
if (machine.Value is null)
return;
// Reassign the item to the new machine
ItemsDB.RemapDatItemToMachine(datItem.Key, newMachineIndex);
// Remove extensions from File and Rom items
if (datItem.Value is DatItems.Formats.File || datItem.Value is Rom)
{
string[] splitname = itemName.Split('.');
itemName = machine.Value.GetName()
+ $"/{string.Join(".", splitname, 0, splitname.Length > 1 ? splitname.Length - 1 : 1)}";
}
else
{
itemName = machine.Value.GetName() + $"/{itemName}";
}
// Strip off "Default" prefix only for ORPG
if (itemName.StartsWith("Default"))
itemName = itemName.Substring("Default".Length + 1);
machine.Value.SetName(itemName);
datItem.Value.SetName(Path.GetFileName(datItem.Value.GetName()));
}
/// <summary>
/// Strip the dates from the beginning of scene-style set names
/// </summary>
/// <remarks>Applies to <see cref="Items"/></remarks>
private void StripSceneDatesFromItemsImpl()
{
// Now process all of the roms
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
Parallel.ForEach(Items.SortedKeys, key =>
#else
foreach (var key in Items.SortedKeys)
#endif
{
var items = GetItemsForBucket(key);
if (items is null)
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
return;
#else
continue;
#endif
foreach (DatItem item in items)
{
// Get the current machine
var machine = item.GetMachine();
if (machine is null)
continue;
// Get the values to check against
string? machineName = machine.GetName();
string? machineDesc = machine.GetStringFieldValue(Data.Models.Metadata.Machine.DescriptionKey);
if (machineName is not null && Regex.IsMatch(machineName, SceneNamePattern))
item.GetMachine()!.SetName(Regex.Replace(machineName, SceneNamePattern, "$2"));
if (machineDesc is not null && Regex.IsMatch(machineDesc, SceneNamePattern))
item.GetMachine()!.SetFieldValue<string?>(Data.Models.Metadata.Machine.DescriptionKey, Regex.Replace(machineDesc, SceneNamePattern, "$2"));
}
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
});
#else
}
#endif
}
/// <summary>
/// Strip the dates from the beginning of scene-style set names
/// </summary>
/// <remarks>Applies to <see cref="ItemsDB"/></remarks>
private void StripSceneDatesFromItemsImplDB()
{
// Now process all of the machines
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
Parallel.ForEach(GetMachinesDB(), machine =>
#else
foreach (var machine in GetMachinesDB())
#endif
{
// Get the current machine
if (machine.Value is null)
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
return;
#else
continue;
#endif
// Get the values to check against
string? machineName = machine.Value.GetName();
string? machineDesc = machine.Value.GetStringFieldValue(Data.Models.Metadata.Machine.DescriptionKey);
if (machineName is not null && Regex.IsMatch(machineName, SceneNamePattern))
machine.Value.SetName(Regex.Replace(machineName, SceneNamePattern, "$2"));
if (machineDesc is not null && Regex.IsMatch(machineDesc, SceneNamePattern))
machine.Value.SetFieldValue<string?>(Data.Models.Metadata.Machine.DescriptionKey, Regex.Replace(machineDesc, SceneNamePattern, "$2"));
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
});
#else
}
#endif
}
/// <summary>
/// Update machine names from descriptions according to mappings
/// </summary>
/// <remarks>Applies to <see cref="Items"/></remarks>
private void UpdateMachineNamesFromDescriptions(IDictionary<string, string> mapping)
{
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
Parallel.ForEach(Items.SortedKeys, key =>
#else
foreach (var key in Items.SortedKeys)
#endif
{
var items = GetItemsForBucket(key);
if (items is null)
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
return;
#else
continue;
#endif
foreach (DatItem item in items)
{
// Get the current machine
var machine = item.GetMachine();
if (machine is null)
continue;
// Get the values to check against
string? machineName = machine.GetName();
string? cloneOf = machine.GetStringFieldValue(Data.Models.Metadata.Machine.CloneOfKey);
string? romOf = machine.GetStringFieldValue(Data.Models.Metadata.Machine.RomOfKey);
string? sampleOf = machine.GetStringFieldValue(Data.Models.Metadata.Machine.SampleOfKey);
// Update machine name
if (machineName is not null && mapping.ContainsKey(machineName))
machine.SetName(mapping[machineName]);
// Update cloneof
if (cloneOf is not null && mapping.ContainsKey(cloneOf))
machine.SetFieldValue<string?>(Data.Models.Metadata.Machine.CloneOfKey, mapping[cloneOf]);
// Update romof
if (romOf is not null && mapping.ContainsKey(romOf))
machine.SetFieldValue<string?>(Data.Models.Metadata.Machine.RomOfKey, mapping[romOf]);
// Update sampleof
if (sampleOf is not null && mapping.ContainsKey(sampleOf))
machine.SetFieldValue<string?>(Data.Models.Metadata.Machine.SampleOfKey, mapping[sampleOf]);
}
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
});
#else
}
#endif
}
/// <summary>
/// Update machine names from descriptions according to mappings
/// </summary>
/// <remarks>Applies to <see cref="ItemsDB"/></remarks>
private void UpdateMachineNamesFromDescriptionsDB(Dictionary<string, string> mapping)
{
foreach (var machine in GetMachinesDB())
{
// Get the current machine
if (machine.Value is null)
continue;
// Get the values to check against
string? machineName = machine.Value.GetName();
string? cloneOf = machine.Value.GetStringFieldValue(Data.Models.Metadata.Machine.CloneOfKey);
string? romOf = machine.Value.GetStringFieldValue(Data.Models.Metadata.Machine.RomOfKey);
string? sampleOf = machine.Value.GetStringFieldValue(Data.Models.Metadata.Machine.SampleOfKey);
// Update machine name
if (machineName is not null && mapping.ContainsKey(machineName))
machine.Value.SetName(mapping[machineName]);
// Update cloneof
if (cloneOf is not null && mapping.ContainsKey(cloneOf))
machine.Value.SetFieldValue<string?>(Data.Models.Metadata.Machine.CloneOfKey, mapping[cloneOf]);
// Update romof
if (romOf is not null && mapping.ContainsKey(romOf))
machine.Value.SetFieldValue<string?>(Data.Models.Metadata.Machine.RomOfKey, mapping[romOf]);
// Update sampleof
if (sampleOf is not null && mapping.ContainsKey(sampleOf))
machine.Value.SetFieldValue<string?>(Data.Models.Metadata.Machine.SampleOfKey, mapping[sampleOf]);
}
}
#endregion
}
}

View File

@@ -0,0 +1,715 @@
using System;
using System.Collections.Generic;
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
using System.Threading.Tasks;
#endif
using SabreTools.Data.Extensions;
using SabreTools.Metadata.Filter;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
#pragma warning disable IDE0056 // Use index operator
#pragma warning disable IDE0060 // Remove unused parameter
namespace SabreTools.Metadata.DatFiles
{
public partial class DatFile
{
#region From Metadata
/// <summary>
/// Convert metadata information
/// </summary>
/// <param name="item">Metadata file to convert</param>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
/// <param name="keep">True if full pathnames are to be kept, false otherwise</param>
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
/// <param name="filterRunner">Optional FilterRunner to filter items on parse</param>
internal void ConvertFromMetadata(Data.Models.Metadata.MetadataFile? item,
string filename,
int indexId,
bool keep,
bool statsOnly,
FilterRunner? filterRunner)
{
// If the metadata file is invalid, we can't do anything
if (item is null || item.Count == 0)
return;
// Create an internal source and add to the dictionary
var source = new Source(indexId, filename);
// long sourceIndex = AddSourceDB(source);
// Get the header from the metadata
var header = item.Read<Data.Models.Metadata.Header>(Data.Models.Metadata.MetadataFile.HeaderKey);
if (header is not null)
ConvertHeader(header, keep);
// Get the machines from the metadata
var machines = item.ReadItemArray<Data.Models.Metadata.Machine>(Data.Models.Metadata.MetadataFile.MachineKey);
if (machines is not null)
ConvertMachines(machines, source, sourceIndex: 0, statsOnly, filterRunner);
}
/// <summary>
/// Convert header information
/// </summary>
/// <param name="item">Header to convert</param>
/// <param name="keep">True if full pathnames are to be kept, false otherwise</param>
private void ConvertHeader(Data.Models.Metadata.Header? item, bool keep)
{
// If the header is invalid, we can't do anything
if (item is null || item.Count == 0)
return;
// Create an internal header
var header = new DatHeader(item);
// Convert subheader values
if (item.ContainsKey(Data.Models.Metadata.Header.CanOpenKey))
{
var canOpen = item.Read<Data.Models.OfflineList.CanOpen>(Data.Models.Metadata.Header.CanOpenKey);
if (canOpen?.Extension is not null)
Header.SetFieldValue<string[]?>(Data.Models.Metadata.Header.CanOpenKey, canOpen.Extension);
}
if (item.ContainsKey(Data.Models.Metadata.Header.ImagesKey))
{
var images = item.Read<Data.Models.OfflineList.Images>(Data.Models.Metadata.Header.ImagesKey);
Header.SetFieldValue<Data.Models.OfflineList.Images?>(Data.Models.Metadata.Header.ImagesKey, images);
}
if (item.ContainsKey(Data.Models.Metadata.Header.InfosKey))
{
var infos = item.Read<Data.Models.OfflineList.Infos>(Data.Models.Metadata.Header.InfosKey);
Header.SetFieldValue<Data.Models.OfflineList.Infos?>(Data.Models.Metadata.Header.InfosKey, infos);
}
if (item.ContainsKey(Data.Models.Metadata.Header.NewDatKey))
{
var newDat = item.Read<Data.Models.OfflineList.NewDat>(Data.Models.Metadata.Header.NewDatKey);
Header.SetFieldValue<Data.Models.OfflineList.NewDat?>(Data.Models.Metadata.Header.NewDatKey, newDat);
}
if (item.ContainsKey(Data.Models.Metadata.Header.SearchKey))
{
var search = item.Read<Data.Models.OfflineList.Search>(Data.Models.Metadata.Header.SearchKey);
Header.SetFieldValue<Data.Models.OfflineList.Search?>(Data.Models.Metadata.Header.SearchKey, search);
}
// Selectively set all possible fields -- TODO: Figure out how to make this less manual
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.AuthorKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.AuthorKey, header.GetStringFieldValue(Data.Models.Metadata.Header.AuthorKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.BiosModeKey).AsMergingFlag() == MergingFlag.None)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.BiosModeKey, header.GetStringFieldValue(Data.Models.Metadata.Header.BiosModeKey).AsMergingFlag().AsStringValue());
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.BuildKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.BuildKey, header.GetStringFieldValue(Data.Models.Metadata.Header.BuildKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.CategoryKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.CategoryKey, header.GetStringFieldValue(Data.Models.Metadata.Header.CategoryKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.CommentKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.CommentKey, header.GetStringFieldValue(Data.Models.Metadata.Header.CommentKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.DateKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.DateKey, header.GetStringFieldValue(Data.Models.Metadata.Header.DateKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.DatVersionKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.DatVersionKey, header.GetStringFieldValue(Data.Models.Metadata.Header.DatVersionKey));
if (Header.GetBoolFieldValue(Data.Models.Metadata.Header.DebugKey) is null)
Header.SetFieldValue(Data.Models.Metadata.Header.DebugKey, header.GetBoolFieldValue(Data.Models.Metadata.Header.DebugKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.DescriptionKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.DescriptionKey, header.GetStringFieldValue(Data.Models.Metadata.Header.DescriptionKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.EmailKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.EmailKey, header.GetStringFieldValue(Data.Models.Metadata.Header.EmailKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.EmulatorVersionKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.EmulatorVersionKey, header.GetStringFieldValue(Data.Models.Metadata.Header.EmulatorVersionKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.ForceMergingKey).AsMergingFlag() == MergingFlag.None)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.ForceMergingKey, header.GetStringFieldValue(Data.Models.Metadata.Header.ForceMergingKey).AsMergingFlag().AsStringValue());
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.ForceNodumpKey).AsNodumpFlag() == NodumpFlag.None)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.ForceNodumpKey, header.GetStringFieldValue(Data.Models.Metadata.Header.ForceNodumpKey).AsNodumpFlag().AsStringValue());
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.ForcePackingKey).AsPackingFlag() == PackingFlag.None)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.ForcePackingKey, header.GetStringFieldValue(Data.Models.Metadata.Header.ForcePackingKey).AsPackingFlag().AsStringValue());
if (Header.GetBoolFieldValue(Data.Models.Metadata.Header.ForceZippingKey) is null)
Header.SetFieldValue(Data.Models.Metadata.Header.ForceZippingKey, header.GetBoolFieldValue(Data.Models.Metadata.Header.ForceZippingKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.HeaderKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.HeaderKey, header.GetStringFieldValue(Data.Models.Metadata.Header.HeaderKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.HomepageKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.HomepageKey, header.GetStringFieldValue(Data.Models.Metadata.Header.HomepageKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.IdKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.IdKey, header.GetStringFieldValue(Data.Models.Metadata.Header.IdKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.ImFolderKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.ImFolderKey, header.GetStringFieldValue(Data.Models.Metadata.Header.ImFolderKey));
if (Header.GetBoolFieldValue(Data.Models.Metadata.Header.LockBiosModeKey) is null)
Header.SetFieldValue(Data.Models.Metadata.Header.LockBiosModeKey, header.GetBoolFieldValue(Data.Models.Metadata.Header.LockBiosModeKey));
if (Header.GetBoolFieldValue(Data.Models.Metadata.Header.LockRomModeKey) is null)
Header.SetFieldValue(Data.Models.Metadata.Header.LockRomModeKey, header.GetBoolFieldValue(Data.Models.Metadata.Header.LockRomModeKey));
if (Header.GetBoolFieldValue(Data.Models.Metadata.Header.LockSampleModeKey) is null)
Header.SetFieldValue(Data.Models.Metadata.Header.LockSampleModeKey, header.GetBoolFieldValue(Data.Models.Metadata.Header.LockSampleModeKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.MameConfigKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.MameConfigKey, header.GetStringFieldValue(Data.Models.Metadata.Header.MameConfigKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.NameKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.NameKey, header.GetStringFieldValue(Data.Models.Metadata.Header.NameKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.NotesKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.NotesKey, header.GetStringFieldValue(Data.Models.Metadata.Header.NotesKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.PluginKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.PluginKey, header.GetStringFieldValue(Data.Models.Metadata.Header.PluginKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.RefNameKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.RefNameKey, header.GetStringFieldValue(Data.Models.Metadata.Header.RefNameKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.RomModeKey).AsMergingFlag() == MergingFlag.None)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.RomModeKey, header.GetStringFieldValue(Data.Models.Metadata.Header.RomModeKey).AsMergingFlag().AsStringValue());
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.RomTitleKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.RomTitleKey, header.GetStringFieldValue(Data.Models.Metadata.Header.RomTitleKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.RootDirKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.RootDirKey, header.GetStringFieldValue(Data.Models.Metadata.Header.RootDirKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.SampleModeKey).AsMergingFlag() == MergingFlag.None)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.SampleModeKey, header.GetStringFieldValue(Data.Models.Metadata.Header.SampleModeKey).AsMergingFlag().AsStringValue());
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.SchemaLocationKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.SchemaLocationKey, header.GetStringFieldValue(Data.Models.Metadata.Header.SchemaLocationKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.ScreenshotsHeightKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.ScreenshotsHeightKey, header.GetStringFieldValue(Data.Models.Metadata.Header.ScreenshotsHeightKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.ScreenshotsWidthKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.ScreenshotsWidthKey, header.GetStringFieldValue(Data.Models.Metadata.Header.ScreenshotsWidthKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.SystemKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.SystemKey, header.GetStringFieldValue(Data.Models.Metadata.Header.SystemKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.TimestampKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.TimestampKey, header.GetStringFieldValue(Data.Models.Metadata.Header.TimestampKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.TypeKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.TypeKey, header.GetStringFieldValue(Data.Models.Metadata.Header.TypeKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.UrlKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.UrlKey, header.GetStringFieldValue(Data.Models.Metadata.Header.UrlKey));
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.VersionKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.VersionKey, header.GetStringFieldValue(Data.Models.Metadata.Header.VersionKey));
// Handle implied SuperDAT
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.NameKey)?.Contains(" - SuperDAT") == true && keep)
{
if (Header.GetStringFieldValue(Data.Models.Metadata.Header.TypeKey) is null)
Header.SetFieldValue<string?>(Data.Models.Metadata.Header.TypeKey, "SuperDAT");
}
}
/// <summary>
/// Convert machines information
/// </summary>
/// <param name="items">Machine array to convert</param>
/// <param name="source">Source to use with the converted items</param>
/// <param name="sourceIndex">Index of the Source to use with the converted items</param>
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
/// <param name="filterRunner">Optional FilterRunner to filter items on parse</param>
private void ConvertMachines(Data.Models.Metadata.Machine[]? items,
Source source,
long sourceIndex,
bool statsOnly,
FilterRunner? filterRunner)
{
// If the array is invalid, we can't do anything
if (items is null || items.Length == 0)
return;
// Loop through the machines and add
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
Parallel.ForEach(items, machine =>
#else
foreach (var machine in items)
#endif
{
ConvertMachine(machine, source, sourceIndex, statsOnly, filterRunner);
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
});
#else
}
#endif
}
/// <summary>
/// Convert machine information
/// </summary>
/// <param name="item">Machine to convert</param>
/// <param name="source">Source to use with the converted items</param>
/// <param name="sourceIndex">Index of the Source to use with the converted items</param>
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
/// <param name="filterRunner">Optional FilterRunner to filter items on parse</param>
private void ConvertMachine(Data.Models.Metadata.Machine? item,
Source source,
long sourceIndex,
bool statsOnly,
FilterRunner? filterRunner)
{
// If the machine is invalid, we can't do anything
if (item is null || item.Count == 0)
return;
// If the machine doesn't pass the filter
if (filterRunner is not null && !filterRunner.Run(item))
return;
// Create an internal machine and add to the dictionary
var machine = new Machine(item);
// long machineIndex = AddMachineDB(machine);
// Convert items in the machine
if (item.ContainsKey(Data.Models.Metadata.Machine.AdjusterKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Adjuster>(Data.Models.Metadata.Machine.AdjusterKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Adjuster(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.ArchiveKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Archive>(Data.Models.Metadata.Machine.ArchiveKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Archive(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.BiosSetKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.BiosSet>(Data.Models.Metadata.Machine.BiosSetKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new BiosSet(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.ChipKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Chip>(Data.Models.Metadata.Machine.ChipKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Chip(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.ConfigurationKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Configuration>(Data.Models.Metadata.Machine.ConfigurationKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Configuration(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.DeviceKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Device>(Data.Models.Metadata.Machine.DeviceKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Device(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.DeviceRefKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.DeviceRef>(Data.Models.Metadata.Machine.DeviceRefKey) ?? [];
// Do not filter these due to later use
Array.ForEach(items, item =>
{
var datItem = new DeviceRef(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.DipSwitchKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.DipSwitch>(Data.Models.Metadata.Machine.DipSwitchKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new DipSwitch(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.DiskKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Disk>(Data.Models.Metadata.Machine.DiskKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Disk(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.DisplayKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Display>(Data.Models.Metadata.Machine.DisplayKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Display(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.DriverKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Driver>(Data.Models.Metadata.Machine.DriverKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Driver(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.DumpKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Dump>(Data.Models.Metadata.Machine.DumpKey) ?? [];
for (int i = 0; i < items.Length; i++)
{
var datItem = new Rom(items[i], machine, source, i);
if (datItem.GetName() is not null)
{
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
}
}
}
if (item.ContainsKey(Data.Models.Metadata.Machine.FeatureKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Feature>(Data.Models.Metadata.Machine.FeatureKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Feature(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.InfoKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Info>(Data.Models.Metadata.Machine.InfoKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Info(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.InputKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Input>(Data.Models.Metadata.Machine.InputKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Input(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.MediaKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Media>(Data.Models.Metadata.Machine.MediaKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Media(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.PartKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Part>(Data.Models.Metadata.Machine.PartKey) ?? [];
ProcessItems(items, machine, machineIndex: 0, source, sourceIndex, statsOnly, filterRunner);
}
if (item.ContainsKey(Data.Models.Metadata.Machine.PortKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Port>(Data.Models.Metadata.Machine.PortKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Port(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.RamOptionKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.RamOption>(Data.Models.Metadata.Machine.RamOptionKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new RamOption(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.ReleaseKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Release>(Data.Models.Metadata.Machine.ReleaseKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Release(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.RomKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Rom>(Data.Models.Metadata.Machine.RomKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Rom(item, machine, source);
datItem.SetFieldValue<Source?>(DatItem.SourceKey, source);
datItem.CopyMachineInformation(machine);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.SampleKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Sample>(Data.Models.Metadata.Machine.SampleKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Sample(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.SharedFeatKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.SharedFeat>(Data.Models.Metadata.Machine.SharedFeatKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new SharedFeat(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.SlotKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Slot>(Data.Models.Metadata.Machine.SlotKey) ?? [];
// Do not filter these due to later use
Array.ForEach(items, item =>
{
var datItem = new Slot(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.SoftwareListKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.SoftwareList>(Data.Models.Metadata.Machine.SoftwareListKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new SoftwareList(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.SoundKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Sound>(Data.Models.Metadata.Machine.SoundKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Sound(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
if (item.ContainsKey(Data.Models.Metadata.Machine.VideoKey))
{
var items = item.ReadItemArray<Data.Models.Metadata.Video>(Data.Models.Metadata.Machine.VideoKey) ?? [];
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
var datItem = new Display(item, machine, source);
AddItem(datItem, statsOnly);
// AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
});
}
}
/// <summary>
/// Convert Part information
/// </summary>
/// <param name="items">Array of internal items to convert</param>
/// <param name="machine">Machine to use with the converted items</param>
/// <param name="machineIndex">Index of the Machine to use with the converted items</param>
/// <param name="source">Source to use with the converted items</param>
/// <param name="sourceIndex">Index of the Source to use with the converted items</param>
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
/// <param name="filterRunner">Optional FilterRunner to filter items on parse</param>
private void ProcessItems(Data.Models.Metadata.Part[] items,
Machine machine,
long machineIndex,
Source source,
long sourceIndex,
bool statsOnly,
FilterRunner? filterRunner)
{
// If the array is null or empty, return without processing
if (items.Length == 0)
return;
// Loop through the items and add
foreach (var item in items)
{
var partItem = new Part(item, machine, source);
// Handle subitems
var dataAreas = item.ReadItemArray<Data.Models.Metadata.DataArea>(Data.Models.Metadata.Part.DataAreaKey);
if (dataAreas is not null)
{
foreach (var dataArea in dataAreas)
{
var dataAreaItem = new DataArea(dataArea, machine, source);
var roms = dataArea.ReadItemArray<Data.Models.Metadata.Rom>(Data.Models.Metadata.DataArea.RomKey);
if (roms is null)
continue;
// Handle "offset" roms
List<Rom> addRoms = [];
foreach (var rom in roms)
{
// If the item doesn't pass the filter
if (filterRunner is not null && !filterRunner.Run(rom))
continue;
// Convert the item
var romItem = new Rom(rom, machine, source);
long? size = romItem.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey);
// If the rom is a continue or ignore
string? loadFlag = rom.ReadString(Data.Models.Metadata.Rom.LoadFlagKey);
if (loadFlag is not null
&& (loadFlag.Equals("continue", StringComparison.OrdinalIgnoreCase)
|| loadFlag.Equals("ignore", StringComparison.OrdinalIgnoreCase)))
{
var lastRom = addRoms[addRoms.Count - 1];
long? lastSize = lastRom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey);
lastRom.SetFieldValue(Data.Models.Metadata.Rom.SizeKey, lastSize + size);
continue;
}
romItem.SetFieldValue<DataArea?>(Rom.DataAreaKey, dataAreaItem);
romItem.SetFieldValue<Part?>(Rom.PartKey, partItem);
addRoms.Add(romItem);
}
// Add all of the adjusted roms
foreach (var romItem in addRoms)
{
AddItem(romItem, statsOnly);
// AddItemDB(romItem, machineIndex, sourceIndex, statsOnly);
}
}
}
var diskAreas = item.ReadItemArray<Data.Models.Metadata.DiskArea>(Data.Models.Metadata.Part.DiskAreaKey);
if (diskAreas is not null)
{
foreach (var diskArea in diskAreas)
{
var diskAreaitem = new DiskArea(diskArea, machine, source);
var disks = diskArea.ReadItemArray<Data.Models.Metadata.Disk>(Data.Models.Metadata.DiskArea.DiskKey);
if (disks is null)
continue;
foreach (var disk in disks)
{
// If the item doesn't pass the filter
if (filterRunner is not null && !filterRunner.Run(disk))
continue;
var diskItem = new Disk(disk, machine, source);
diskItem.SetFieldValue<DiskArea?>(Disk.DiskAreaKey, diskAreaitem);
diskItem.SetFieldValue<Part?>(Disk.PartKey, partItem);
AddItem(diskItem, statsOnly);
// AddItemDB(diskItem, machineIndex, sourceIndex, statsOnly);
}
}
}
var dipSwitches = item.ReadItemArray<Data.Models.Metadata.DipSwitch>(Data.Models.Metadata.Part.DipSwitchKey);
if (dipSwitches is not null)
{
foreach (var dipSwitch in dipSwitches)
{
// If the item doesn't pass the filter
if (filterRunner is not null && !filterRunner.Run(dipSwitch))
continue;
var dipSwitchItem = new DipSwitch(dipSwitch, machine, source);
dipSwitchItem.SetFieldValue<Part?>(DipSwitch.PartKey, partItem);
AddItem(dipSwitchItem, statsOnly);
// AddItemDB(dipSwitchItem, machineIndex, sourceIndex, statsOnly);
}
}
var partFeatures = item.ReadItemArray<Data.Models.Metadata.Feature>(Data.Models.Metadata.Part.FeatureKey);
if (partFeatures is not null)
{
foreach (var partFeature in partFeatures)
{
// If the item doesn't pass the filter
if (filterRunner is not null && !filterRunner.Run(partFeature))
continue;
var partFeatureItem = new PartFeature(partFeature);
partFeatureItem.SetFieldValue<Part?>(DipSwitch.PartKey, partItem);
partFeatureItem.SetFieldValue<Source?>(DatItem.SourceKey, source);
partFeatureItem.CopyMachineInformation(machine);
AddItem(partFeatureItem, statsOnly);
// AddItemDB(partFeatureItem, machineIndex, sourceIndex, statsOnly);
}
}
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,229 @@
using System;
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Filter;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatFiles
{
/// <summary>
/// Represents all possible DAT header information
/// </summary>
[JsonObject("header"), XmlRoot("header")]
public sealed class DatHeader : ModelBackedItem<Data.Models.Metadata.Header>, ICloneable
{
#region Constants
/// <summary>
/// Read or write format
/// </summary>
public const string DatFormatKey = "DATFORMAT";
/// <summary>
/// External name of the DAT
/// </summary>
public const string FileNameKey = "FILENAME";
#endregion
#region Fields
[JsonIgnore]
public bool CanOpenSpecified
{
get
{
var canOpen = GetStringArrayFieldValue(Data.Models.Metadata.Header.CanOpenKey);
return canOpen is not null && canOpen.Length > 0;
}
}
[JsonIgnore]
public bool ImagesSpecified
{
get
{
return GetFieldValue<Data.Models.OfflineList.Images?>(Data.Models.Metadata.Header.ImagesKey) is not null;
}
}
[JsonIgnore]
public bool InfosSpecified
{
get
{
return GetFieldValue<Data.Models.OfflineList.Infos?>(Data.Models.Metadata.Header.InfosKey) is not null;
}
}
[JsonIgnore]
public bool NewDatSpecified
{
get
{
return GetFieldValue<Data.Models.OfflineList.NewDat?>(Data.Models.Metadata.Header.NewDatKey) is not null;
}
}
[JsonIgnore]
public bool SearchSpecified
{
get
{
return GetFieldValue<Data.Models.OfflineList.Search?>(Data.Models.Metadata.Header.SearchKey) is not null;
}
}
#endregion
#region Constructors
public DatHeader() { }
public DatHeader(Data.Models.Metadata.Header header)
{
// Create a new internal model
_internal = [];
// Get all fields to automatically copy without processing
var nonItemFields = TypeHelper.GetConstants(typeof(Data.Models.Metadata.Header));
if (nonItemFields is not null)
{
// Populate the internal machine from non-filter fields
foreach (string fieldName in nonItemFields)
{
if (header.ContainsKey(fieldName))
_internal[fieldName] = header[fieldName];
}
}
// Get all fields specific to the DatFiles implementation
var nonStandardFields = TypeHelper.GetConstants(typeof(DatHeader));
if (nonStandardFields is not null)
{
// Populate the internal machine from filter fields
foreach (string fieldName in nonStandardFields)
{
if (header.ContainsKey(fieldName))
_internal[fieldName] = header[fieldName];
}
}
// Get all no-filter fields
if (header.ContainsKey(Data.Models.Metadata.Header.CanOpenKey))
_internal[Data.Models.Metadata.Header.CanOpenKey] = header[Data.Models.Metadata.Header.CanOpenKey];
if (header.ContainsKey(Data.Models.Metadata.Header.ImagesKey))
_internal[Data.Models.Metadata.Header.ImagesKey] = header[Data.Models.Metadata.Header.ImagesKey];
if (header.ContainsKey(Data.Models.Metadata.Header.InfosKey))
_internal[Data.Models.Metadata.Header.InfosKey] = header[Data.Models.Metadata.Header.InfosKey];
if (header.ContainsKey(Data.Models.Metadata.Header.NewDatKey))
_internal[Data.Models.Metadata.Header.NewDatKey] = header[Data.Models.Metadata.Header.NewDatKey];
if (header.ContainsKey(Data.Models.Metadata.Header.SearchKey))
_internal[Data.Models.Metadata.Header.SearchKey] = header[Data.Models.Metadata.Header.SearchKey];
}
#endregion
#region Cloning Methods
/// <summary>
/// Clone the current header
/// </summary>
public object Clone() => new DatHeader(GetInternalClone());
/// <summary>
/// Clone just the format from the current header
/// </summary>
public DatHeader CloneFormat()
{
var header = new DatHeader();
header.SetFieldValue(DatFormatKey, GetFieldValue<DatFormat>(DatFormatKey));
return header;
}
/// <summary>
/// Get a clone of the current internal model
/// </summary>
public Data.Models.Metadata.Header GetInternalClone()
{
var header = (_internal.Clone() as Data.Models.Metadata.Header)!;
// Remove fields with default values
if (header.ReadString(Data.Models.Metadata.Header.ForceMergingKey).AsMergingFlag() == MergingFlag.None)
header.Remove(Data.Models.Metadata.Header.ForceMergingKey);
if (header.ReadString(Data.Models.Metadata.Header.ForceNodumpKey).AsNodumpFlag() == NodumpFlag.None)
header.Remove(Data.Models.Metadata.Header.ForceNodumpKey);
if (header.ReadString(Data.Models.Metadata.Header.ForcePackingKey).AsPackingFlag() == PackingFlag.None)
header.Remove(Data.Models.Metadata.Header.ForcePackingKey);
if (header.ReadString(Data.Models.Metadata.Header.BiosModeKey).AsMergingFlag() == MergingFlag.None)
header.Remove(Data.Models.Metadata.Header.BiosModeKey);
if (header.ReadString(Data.Models.Metadata.Header.RomModeKey).AsMergingFlag() == MergingFlag.None)
header.Remove(Data.Models.Metadata.Header.RomModeKey);
if (header.ReadString(Data.Models.Metadata.Header.SampleModeKey).AsMergingFlag() == MergingFlag.None)
header.Remove(Data.Models.Metadata.Header.SampleModeKey);
// Convert subheader values
if (CanOpenSpecified)
header[Data.Models.Metadata.Header.CanOpenKey] = new Data.Models.OfflineList.CanOpen { Extension = GetStringArrayFieldValue(Data.Models.Metadata.Header.CanOpenKey) };
if (ImagesSpecified)
header[Data.Models.Metadata.Header.ImagesKey] = GetFieldValue<Data.Models.OfflineList.Images>(Data.Models.Metadata.Header.ImagesKey);
if (InfosSpecified)
header[Data.Models.Metadata.Header.InfosKey] = GetFieldValue<Data.Models.OfflineList.Infos>(Data.Models.Metadata.Header.InfosKey);
if (NewDatSpecified)
header[Data.Models.Metadata.Header.NewDatKey] = GetFieldValue<Data.Models.OfflineList.NewDat>(Data.Models.Metadata.Header.NewDatKey);
if (SearchSpecified)
header[Data.Models.Metadata.Header.SearchKey] = GetFieldValue<Data.Models.OfflineList.Search>(Data.Models.Metadata.Header.SearchKey);
return header;
}
#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 DatHeader otherItem)
return false;
// Compare internal models
return _internal.EqualTo(otherItem._internal);
}
/// <inheritdoc/>
public override bool Equals(ModelBackedItem<Data.Models.Metadata.Header>? other)
{
// If other is null
if (other is null)
return false;
// If the type is mismatched
if (other is not DatHeader otherItem)
return false;
// Compare internal models
return _internal.EqualTo(otherItem._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,88 @@
using System;
namespace SabreTools.Metadata.DatFiles
{
/// <summary>
/// Represents various modifiers that can be applied to a DAT
/// </summary>
public sealed class DatModifiers : ICloneable
{
#region Fields
/// <summary>
/// Text to prepend to all outputted lines
/// </summary>
public string? Prefix { get; set; } = null;
/// <summary>
/// Text to append to all outputted lines
/// </summary>
public string? Postfix { get; set; } = null;
/// <summary>
/// Add a new extension to all items
/// </summary>
public string? AddExtension { get; set; } = null;
/// <summary>
/// Remove all item extensions
/// </summary>
public bool RemoveExtension { get; set; } = false;
/// <summary>
/// Replace all item extensions
/// </summary>
public string? ReplaceExtension { get; set; } = null;
/// <summary>
/// Output the machine name before the item name
/// </summary>
public bool GameName { get; set; } = false;
/// <summary>
/// Wrap quotes around the entire line, sans prefix and postfix
/// </summary>
public bool Quotes { get; set; } = false;
/// <summary>
/// Use the item name instead of machine name on output
/// </summary>
public bool UseRomName { get; set; } = false;
/// <summary>
/// Input depot information
/// </summary>
public DepotInformation? InputDepot { get; set; } = null;
/// <summary>
/// Output depot information
/// </summary>
public DepotInformation? OutputDepot { get; set; } = null;
#endregion
#region Cloning Methods
/// <summary>
/// Clone the current modifiers
/// </summary>
public object Clone()
{
return new DatModifiers
{
Prefix = this.Prefix,
Postfix = this.Postfix,
AddExtension = this.AddExtension,
RemoveExtension = this.RemoveExtension,
ReplaceExtension = this.ReplaceExtension,
GameName = this.GameName,
Quotes = this.Quotes,
UseRomName = this.UseRomName,
InputDepot = (DepotInformation?)this.InputDepot?.Clone(),
OutputDepot = (DepotInformation?)this.OutputDepot?.Clone(),
};
}
#endregion
}
}

View File

@@ -0,0 +1,725 @@
using System.Collections.Generic;
using SabreTools.Hashing;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
namespace SabreTools.Metadata.DatFiles
{
/// <summary>
/// Statistics wrapper for outputting
/// </summary>
public class DatStatistics
{
#region Private instance variables
/// <summary>
/// Number of items for each hash type
/// </summary>
private readonly Dictionary<HashType, long> _hashCounts = [];
/// <summary>
/// Number of items for each item type
/// </summary>
private readonly Dictionary<ItemType, long> _itemCounts = [];
/// <summary>
/// Number of items for each item status
/// </summary>
private readonly Dictionary<ItemStatus, long> _statusCounts = [];
/// <summary>
/// Lock for statistics calculation
/// </summary>
private readonly object statsLock = new();
#endregion
#region Fields
/// <summary>
/// Overall item count
/// </summary>
public long TotalCount { get; private set; } = 0;
/// <summary>
/// Number of machines
/// </summary>
/// <remarks>Special count only used by statistics output</remarks>
public long GameCount { get; set; } = 0;
/// <summary>
/// Total uncompressed size
/// </summary>
public long TotalSize { get; private set; } = 0;
/// <summary>
/// Number of items with the remove flag
/// </summary>
public long RemovedCount { get; private set; } = 0;
/// <summary>
/// Name to display on output
/// </summary>
public string? DisplayName { get; set; }
/// <summary>
/// Total machine count to use on output
/// </summary>
public long MachineCount { get; set; }
/// <summary>
/// Determines if statistics are for a directory or not
/// </summary>
public readonly bool IsDirectory;
#endregion
#region Constructors
/// <summary>
/// Default constructor
/// </summary>
public DatStatistics()
{
DisplayName = null;
MachineCount = 0;
IsDirectory = false;
}
/// <summary>
/// Constructor for aggregate data
/// </summary>
public DatStatistics(string? displayName, bool isDirectory)
{
DisplayName = displayName;
MachineCount = 0;
IsDirectory = isDirectory;
}
#endregion
#region Accessors
/// <summary>
/// Add to the statistics for a given DatItem
/// </summary>
/// <param name="item">Item to add info from</param>
public void AddItemStatistics(DatItem item)
{
lock (statsLock)
{
// No matter what the item is, we increment the count
TotalCount++;
// Increment removal count
if (item.GetBoolFieldValue(DatItem.RemoveKey) == true)
RemovedCount++;
// Increment the item count for the type
AddItemCount(item.GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey).AsItemType());
// Some item types require special processing
switch (item)
{
case Disk disk:
AddItemStatistics(disk);
break;
case File file:
AddItemStatistics(file);
break;
case Media media:
AddItemStatistics(media);
break;
case Rom rom:
AddItemStatistics(rom);
break;
default:
break;
}
}
}
/// <summary>
/// Add to the statistics for a given DatItem
/// </summary>
/// <param name="item">Item to add info from</param>
public void AddItemStatistics(Data.Models.Metadata.DatItem item)
{
lock (statsLock)
{
// No matter what the item is, we increment the count
TotalCount++;
// Increment removal count
if (item.ReadBool(DatItem.RemoveKey) == true)
RemovedCount++;
// Increment the item count for the type
AddItemCount(item.ReadString(Data.Models.Metadata.DatItem.TypeKey).AsItemType());
#pragma warning disable IDE0010
// Some item types require special processing
switch (item)
{
case Data.Models.Metadata.Disk disk:
AddItemStatistics(disk);
break;
case Data.Models.Metadata.Media media:
AddItemStatistics(media);
break;
case Data.Models.Metadata.Rom rom:
AddItemStatistics(rom);
break;
}
#pragma warning restore IDE0010
}
}
/// <summary>
/// Add statistics from another DatStatistics object
/// </summary>
/// <param name="stats">DatStatistics object to add from</param>
public void AddStatistics(DatStatistics stats)
{
TotalCount += stats.TotalCount;
// Loop through and add stats for all items
foreach (var itemCountKvp in stats._itemCounts)
{
AddItemCount(itemCountKvp.Key, itemCountKvp.Value);
}
GameCount += stats.GameCount;
TotalSize += stats.TotalSize;
// Individual hash counts
foreach (var hashCountKvp in stats._hashCounts)
{
AddHashCount(hashCountKvp.Key, hashCountKvp.Value);
}
// Individual status counts
foreach (var statusCountKvp in stats._statusCounts)
{
AddStatusCount(statusCountKvp.Key, statusCountKvp.Value);
}
RemovedCount += stats.RemovedCount;
}
/// <summary>
/// Get the item count for a given hash type, defaulting to 0 if it does not exist
/// </summary>
/// <param name="hashType">Hash type to retrieve</param>
/// <returns>The number of items with that hash, if it exists</returns>
public long GetHashCount(HashType hashType)
{
lock (_hashCounts)
{
if (!_hashCounts.TryGetValue(hashType, out long value))
return 0;
return value;
}
}
/// <summary>
/// Get the item count for a given item type, defaulting to 0 if it does not exist
/// </summary>
/// <param name="itemType">Item type to retrieve</param>
/// <returns>The number of items of that type, if it exists</returns>
public long GetItemCount(ItemType itemType)
{
lock (_itemCounts)
{
if (!_itemCounts.TryGetValue(itemType, out long value))
return 0;
return value;
}
}
/// <summary>
/// Get the item count for a given item status, defaulting to 0 if it does not exist
/// </summary>
/// <param name="itemStatus">Item status to retrieve</param>
/// <returns>The number of items of that type, if it exists</returns>
public long GetStatusCount(ItemStatus itemStatus)
{
lock (_statusCounts)
{
if (!_statusCounts.TryGetValue(itemStatus, out long value))
return 0;
return value;
}
}
/// <summary>
/// Remove from the statistics given a DatItem
/// </summary>
/// <param name="item">Item to remove info for</param>
public void RemoveItemStatistics(DatItem item)
{
// If we have a null item, we can't do anything
if (item is null)
return;
lock (statsLock)
{
// No matter what the item is, we decrease the count
TotalCount--;
// Decrement removal count
if (item.GetBoolFieldValue(DatItem.RemoveKey) == true)
RemovedCount--;
// Decrement the item count for the type
RemoveItemCount(item.GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey).AsItemType());
#pragma warning disable IDE0010
// Some item types require special processing
switch (item)
{
case Disk disk:
RemoveItemStatistics(disk);
break;
case File file:
RemoveItemStatistics(file);
break;
case Media media:
RemoveItemStatistics(media);
break;
case Rom rom:
RemoveItemStatistics(rom);
break;
}
#pragma warning restore IDE0010
}
}
/// <summary>
/// Remove from the statistics given a DatItem
/// </summary>
/// <param name="item">Item to remove info for</param>
public void RemoveItemStatistics(Data.Models.Metadata.DatItem item)
{
// If we have a null item, we can't do anything
if (item is null)
return;
lock (statsLock)
{
// No matter what the item is, we decrease the count
TotalCount--;
// Decrement removal count
if (item.ReadBool(DatItem.RemoveKey) == true)
RemovedCount--;
// Decrement the item count for the type
RemoveItemCount(item.ReadString(Data.Models.Metadata.DatItem.TypeKey).AsItemType());
#pragma warning disable IDE0010
// Some item types require special processing
switch (item)
{
case Data.Models.Metadata.Disk disk:
RemoveItemStatistics(disk);
break;
case Data.Models.Metadata.Media media:
RemoveItemStatistics(media);
break;
case Data.Models.Metadata.Rom rom:
RemoveItemStatistics(rom);
break;
}
#pragma warning restore IDE0010
}
}
/// <summary>
/// Reset all statistics
/// </summary>
public void ResetStatistics()
{
_hashCounts.Clear();
_itemCounts.Clear();
_statusCounts.Clear();
TotalCount = 0;
GameCount = 0;
TotalSize = 0;
RemovedCount = 0;
}
/// <summary>
/// Increment the hash count for a given hash type
/// </summary>
/// <param name="hashType">Hash type to increment</param>
/// <param name="interval">Amount to increment by, defaults to 1</param>
private void AddHashCount(HashType hashType, long interval = 1)
{
lock (_hashCounts)
{
if (!_hashCounts.ContainsKey(hashType))
_hashCounts[hashType] = 0;
_hashCounts[hashType] += interval;
if (_hashCounts[hashType] < 0)
_hashCounts[hashType] = 0;
}
}
/// <summary>
/// Increment the item count for a given item type
/// </summary>
/// <param name="itemType">Item type to increment</param>
/// <param name="interval">Amount to increment by, defaults to 1</param>
private void AddItemCount(ItemType itemType, long interval = 1)
{
lock (_itemCounts)
{
if (!_itemCounts.ContainsKey(itemType))
_itemCounts[itemType] = 0;
_itemCounts[itemType] += interval;
if (_itemCounts[itemType] < 0)
_itemCounts[itemType] = 0;
}
}
/// <summary>
/// Add to the statistics for a given Disk
/// </summary>
/// <param name="disk">Item to add info from</param>
private void AddItemStatistics(Disk disk)
{
if (disk.GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() != ItemStatus.Nodump)
{
AddHashCount(HashType.MD5, string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.MD5Key)) ? 0 : 1);
AddHashCount(HashType.SHA1, string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.SHA1Key)) ? 0 : 1);
}
AddStatusCount(ItemStatus.BadDump, disk.GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.BadDump ? 1 : 0);
AddStatusCount(ItemStatus.Good, disk.GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.Good ? 1 : 0);
AddStatusCount(ItemStatus.Nodump, disk.GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.Nodump ? 1 : 0);
AddStatusCount(ItemStatus.Verified, disk.GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.Verified ? 1 : 0);
}
/// <summary>
/// Add to the statistics for a given Disk
/// </summary>
/// <param name="disk">Item to add info from</param>
private void AddItemStatistics(Data.Models.Metadata.Disk disk)
{
if (disk.ReadString(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() != ItemStatus.Nodump)
{
AddHashCount(HashType.MD5, string.IsNullOrEmpty(disk.ReadString(Data.Models.Metadata.Disk.MD5Key)) ? 0 : 1);
AddHashCount(HashType.SHA1, string.IsNullOrEmpty(disk.ReadString(Data.Models.Metadata.Disk.SHA1Key)) ? 0 : 1);
}
AddStatusCount(ItemStatus.BadDump, disk.ReadString(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.BadDump ? 1 : 0);
AddStatusCount(ItemStatus.Good, disk.ReadString(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.Good ? 1 : 0);
AddStatusCount(ItemStatus.Nodump, disk.ReadString(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.Nodump ? 1 : 0);
AddStatusCount(ItemStatus.Verified, disk.ReadString(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.Verified ? 1 : 0);
}
/// <summary>
/// Add to the statistics for a given File
/// </summary>
/// <param name="file">Item to add info from</param>
private void AddItemStatistics(File file)
{
TotalSize += file.Size ?? 0;
AddHashCount(HashType.CRC32, string.IsNullOrEmpty(file.CRC) ? 0 : 1);
AddHashCount(HashType.MD5, string.IsNullOrEmpty(file.MD5) ? 0 : 1);
AddHashCount(HashType.SHA1, string.IsNullOrEmpty(file.SHA1) ? 0 : 1);
AddHashCount(HashType.SHA256, string.IsNullOrEmpty(file.SHA256) ? 0 : 1);
}
/// <summary>
/// Add to the statistics for a given Media
/// </summary>
/// <param name="media">Item to add info from</param>
private void AddItemStatistics(Media media)
{
AddHashCount(HashType.MD5, string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.MD5Key)) ? 0 : 1);
AddHashCount(HashType.SHA1, string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SHA1Key)) ? 0 : 1);
AddHashCount(HashType.SHA256, string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SHA256Key)) ? 0 : 1);
AddHashCount(HashType.SpamSum, string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SpamSumKey)) ? 0 : 1);
}
/// <summary>
/// Add to the statistics for a given Media
/// </summary>
/// <param name="media">Item to add info from</param>
private void AddItemStatistics(Data.Models.Metadata.Media media)
{
AddHashCount(HashType.MD5, string.IsNullOrEmpty(media.ReadString(Data.Models.Metadata.Media.MD5Key)) ? 0 : 1);
AddHashCount(HashType.SHA1, string.IsNullOrEmpty(media.ReadString(Data.Models.Metadata.Media.SHA1Key)) ? 0 : 1);
AddHashCount(HashType.SHA256, string.IsNullOrEmpty(media.ReadString(Data.Models.Metadata.Media.SHA256Key)) ? 0 : 1);
AddHashCount(HashType.SpamSum, string.IsNullOrEmpty(media.ReadString(Data.Models.Metadata.Media.SpamSumKey)) ? 0 : 1);
}
/// <summary>
/// Add to the statistics for a given Rom
/// </summary>
/// <param name="rom">Item to add info from</param>
private void AddItemStatistics(Rom rom)
{
if (rom.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() != ItemStatus.Nodump)
{
TotalSize += rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) ?? 0;
AddHashCount(HashType.CRC32, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey)) ? 0 : 1);
AddHashCount(HashType.MD2, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.MD2Key)) ? 0 : 1);
AddHashCount(HashType.MD4, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.MD4Key)) ? 0 : 1);
AddHashCount(HashType.MD5, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key)) ? 0 : 1);
AddHashCount(HashType.RIPEMD128, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key)) ? 0 : 1);
AddHashCount(HashType.RIPEMD160, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key)) ? 0 : 1);
AddHashCount(HashType.SHA1, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key)) ? 0 : 1);
AddHashCount(HashType.SHA256, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key)) ? 0 : 1);
AddHashCount(HashType.SHA384, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA384Key)) ? 0 : 1);
AddHashCount(HashType.SHA512, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA512Key)) ? 0 : 1);
AddHashCount(HashType.SpamSum, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SpamSumKey)) ? 0 : 1);
}
AddStatusCount(ItemStatus.BadDump, rom.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.BadDump ? 1 : 0);
AddStatusCount(ItemStatus.Good, rom.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.Good ? 1 : 0);
AddStatusCount(ItemStatus.Nodump, rom.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.Nodump ? 1 : 0);
AddStatusCount(ItemStatus.Verified, rom.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.Verified ? 1 : 0);
}
/// <summary>
/// Add to the statistics for a given Rom
/// </summary>
/// <param name="rom">Item to add info from</param>
private void AddItemStatistics(Data.Models.Metadata.Rom rom)
{
if (rom.ReadString(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() != ItemStatus.Nodump)
{
TotalSize += rom.ReadLong(Data.Models.Metadata.Rom.SizeKey) ?? 0;
AddHashCount(HashType.CRC32, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.CRCKey)) ? 0 : 1);
AddHashCount(HashType.MD2, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.MD2Key)) ? 0 : 1);
AddHashCount(HashType.MD4, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.MD4Key)) ? 0 : 1);
AddHashCount(HashType.MD5, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.MD5Key)) ? 0 : 1);
AddHashCount(HashType.RIPEMD128, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.RIPEMD128Key)) ? 0 : 1);
AddHashCount(HashType.RIPEMD160, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.RIPEMD160Key)) ? 0 : 1);
AddHashCount(HashType.SHA1, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.SHA1Key)) ? 0 : 1);
AddHashCount(HashType.SHA256, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.SHA256Key)) ? 0 : 1);
AddHashCount(HashType.SHA384, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.SHA384Key)) ? 0 : 1);
AddHashCount(HashType.SHA512, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.SHA512Key)) ? 0 : 1);
AddHashCount(HashType.SpamSum, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.SpamSumKey)) ? 0 : 1);
}
AddStatusCount(ItemStatus.BadDump, rom.ReadString(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.BadDump ? 1 : 0);
AddStatusCount(ItemStatus.Good, rom.ReadString(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.Good ? 1 : 0);
AddStatusCount(ItemStatus.Nodump, rom.ReadString(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.Nodump ? 1 : 0);
AddStatusCount(ItemStatus.Verified, rom.ReadString(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.Verified ? 1 : 0);
}
/// <summary>
/// Increment the item count for a given item status
/// </summary>
/// <param name="itemStatus">Item type to increment</param>
/// <param name="interval">Amount to increment by, defaults to 1</param>
private void AddStatusCount(ItemStatus itemStatus, long interval = 1)
{
lock (_statusCounts)
{
if (!_statusCounts.ContainsKey(itemStatus))
_statusCounts[itemStatus] = 0;
_statusCounts[itemStatus] += interval;
if (_statusCounts[itemStatus] < 0)
_statusCounts[itemStatus] = 0;
}
}
/// <summary>
/// Decrement the hash count for a given hash type
/// </summary>
/// <param name="hashType">Hash type to increment</param>
/// <param name="interval">Amount to increment by, defaults to 1</param>
private void RemoveHashCount(HashType hashType, long interval = 1)
{
lock (_hashCounts)
{
if (!_hashCounts.ContainsKey(hashType))
return;
_hashCounts[hashType] -= interval;
if (_hashCounts[hashType] < 0)
_hashCounts[hashType] = 0;
}
}
/// <summary>
/// Decrement the item count for a given item type
/// </summary>
/// <param name="itemType">Item type to decrement</param>
/// <param name="interval">Amount to increment by, defaults to 1</param>
private void RemoveItemCount(ItemType itemType, long interval = 1)
{
lock (_itemCounts)
{
if (!_itemCounts.ContainsKey(itemType))
return;
_itemCounts[itemType] -= interval;
if (_itemCounts[itemType] < 0)
_itemCounts[itemType] = 0;
}
}
/// <summary>
/// Remove from the statistics given a Disk
/// </summary>
/// <param name="disk">Item to remove info for</param>
private void RemoveItemStatistics(Disk disk)
{
if (disk.GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() != ItemStatus.Nodump)
{
RemoveHashCount(HashType.MD5, string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.MD5Key)) ? 0 : 1);
RemoveHashCount(HashType.SHA1, string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.SHA1Key)) ? 0 : 1);
}
RemoveStatusCount(ItemStatus.BadDump, disk.GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.BadDump ? 1 : 0);
RemoveStatusCount(ItemStatus.Good, disk.GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.Good ? 1 : 0);
RemoveStatusCount(ItemStatus.Nodump, disk.GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.Nodump ? 1 : 0);
RemoveStatusCount(ItemStatus.Verified, disk.GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.Verified ? 1 : 0);
}
/// <summary>
/// Remove from the statistics given a Disk
/// </summary>
/// <param name="disk">Item to remove info for</param>
private void RemoveItemStatistics(Data.Models.Metadata.Disk disk)
{
if (disk.ReadString(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() != ItemStatus.Nodump)
{
RemoveHashCount(HashType.MD5, string.IsNullOrEmpty(disk.ReadString(Data.Models.Metadata.Disk.MD5Key)) ? 0 : 1);
RemoveHashCount(HashType.SHA1, string.IsNullOrEmpty(disk.ReadString(Data.Models.Metadata.Disk.SHA1Key)) ? 0 : 1);
}
RemoveStatusCount(ItemStatus.BadDump, disk.ReadString(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.BadDump ? 1 : 0);
RemoveStatusCount(ItemStatus.Good, disk.ReadString(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.Good ? 1 : 0);
RemoveStatusCount(ItemStatus.Nodump, disk.ReadString(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.Nodump ? 1 : 0);
RemoveStatusCount(ItemStatus.Verified, disk.ReadString(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.Verified ? 1 : 0);
}
/// <summary>
/// Remove from the statistics given a File
/// </summary>
/// <param name="file">Item to remove info for</param>
private void RemoveItemStatistics(File file)
{
TotalSize -= file.Size ?? 0;
RemoveHashCount(HashType.CRC32, string.IsNullOrEmpty(file.CRC) ? 0 : 1);
RemoveHashCount(HashType.MD5, string.IsNullOrEmpty(file.MD5) ? 0 : 1);
RemoveHashCount(HashType.SHA1, string.IsNullOrEmpty(file.SHA1) ? 0 : 1);
RemoveHashCount(HashType.SHA256, string.IsNullOrEmpty(file.SHA256) ? 0 : 1);
}
/// <summary>
/// Remove from the statistics given a Media
/// </summary>
/// <param name="media">Item to remove info for</param>
private void RemoveItemStatistics(Media media)
{
RemoveHashCount(HashType.MD5, string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.MD5Key)) ? 0 : 1);
RemoveHashCount(HashType.SHA1, string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SHA1Key)) ? 0 : 1);
RemoveHashCount(HashType.SHA256, string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SHA256Key)) ? 0 : 1);
RemoveHashCount(HashType.SpamSum, string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SpamSumKey)) ? 0 : 1);
}
/// <summary>
/// Remove from the statistics given a Media
/// </summary>
/// <param name="media">Item to remove info for</param>
private void RemoveItemStatistics(Data.Models.Metadata.Media media)
{
RemoveHashCount(HashType.MD5, string.IsNullOrEmpty(media.ReadString(Data.Models.Metadata.Media.MD5Key)) ? 0 : 1);
RemoveHashCount(HashType.SHA1, string.IsNullOrEmpty(media.ReadString(Data.Models.Metadata.Media.SHA1Key)) ? 0 : 1);
RemoveHashCount(HashType.SHA256, string.IsNullOrEmpty(media.ReadString(Data.Models.Metadata.Media.SHA256Key)) ? 0 : 1);
RemoveHashCount(HashType.SpamSum, string.IsNullOrEmpty(media.ReadString(Data.Models.Metadata.Media.SpamSumKey)) ? 0 : 1);
}
/// <summary>
/// Remove from the statistics given a Rom
/// </summary>
/// <param name="rom">Item to remove info for</param>
private void RemoveItemStatistics(Rom rom)
{
if (rom.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() != ItemStatus.Nodump)
{
TotalSize -= rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) ?? 0;
RemoveHashCount(HashType.CRC32, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey)) ? 0 : 1);
RemoveHashCount(HashType.MD2, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.MD2Key)) ? 0 : 1);
RemoveHashCount(HashType.MD4, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.MD4Key)) ? 0 : 1);
RemoveHashCount(HashType.MD5, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key)) ? 0 : 1);
RemoveHashCount(HashType.RIPEMD128, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key)) ? 0 : 1);
RemoveHashCount(HashType.RIPEMD160, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key)) ? 0 : 1);
RemoveHashCount(HashType.SHA1, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key)) ? 0 : 1);
RemoveHashCount(HashType.SHA256, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key)) ? 0 : 1);
RemoveHashCount(HashType.SHA384, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA384Key)) ? 0 : 1);
RemoveHashCount(HashType.SHA512, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA512Key)) ? 0 : 1);
RemoveHashCount(HashType.SpamSum, string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SpamSumKey)) ? 0 : 1);
}
RemoveStatusCount(ItemStatus.BadDump, rom.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.BadDump ? 1 : 0);
RemoveStatusCount(ItemStatus.Good, rom.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.Good ? 1 : 0);
RemoveStatusCount(ItemStatus.Nodump, rom.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.Nodump ? 1 : 0);
RemoveStatusCount(ItemStatus.Verified, rom.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.Verified ? 1 : 0);
}
/// <summary>
/// Remove from the statistics given a Rom
/// </summary>
/// <param name="rom">Item to remove info for</param>
private void RemoveItemStatistics(Data.Models.Metadata.Rom rom)
{
if (rom.ReadString(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() != ItemStatus.Nodump)
{
TotalSize -= rom.ReadLong(Data.Models.Metadata.Rom.SizeKey) ?? 0;
RemoveHashCount(HashType.CRC32, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.CRCKey)) ? 0 : 1);
RemoveHashCount(HashType.MD2, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.MD2Key)) ? 0 : 1);
RemoveHashCount(HashType.MD4, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.MD4Key)) ? 0 : 1);
RemoveHashCount(HashType.MD5, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.MD5Key)) ? 0 : 1);
RemoveHashCount(HashType.RIPEMD128, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.RIPEMD128Key)) ? 0 : 1);
RemoveHashCount(HashType.RIPEMD160, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.RIPEMD160Key)) ? 0 : 1);
RemoveHashCount(HashType.SHA1, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.SHA1Key)) ? 0 : 1);
RemoveHashCount(HashType.SHA256, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.SHA256Key)) ? 0 : 1);
RemoveHashCount(HashType.SHA384, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.SHA384Key)) ? 0 : 1);
RemoveHashCount(HashType.SHA512, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.SHA512Key)) ? 0 : 1);
RemoveHashCount(HashType.SpamSum, string.IsNullOrEmpty(rom.ReadString(Data.Models.Metadata.Rom.SpamSumKey)) ? 0 : 1);
}
RemoveStatusCount(ItemStatus.BadDump, rom.ReadString(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.BadDump ? 1 : 0);
RemoveStatusCount(ItemStatus.Good, rom.ReadString(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.Good ? 1 : 0);
RemoveStatusCount(ItemStatus.Nodump, rom.ReadString(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.Nodump ? 1 : 0);
RemoveStatusCount(ItemStatus.Verified, rom.ReadString(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.Verified ? 1 : 0);
}
/// <summary>
/// Decrement the item count for a given item status
/// </summary>
/// <param name="itemStatus">Item type to decrement</param>
/// <param name="interval">Amount to increment by, defaults to 1</param>
private void RemoveStatusCount(ItemStatus itemStatus, long interval = 1)
{
lock (_statusCounts)
{
if (!_statusCounts.ContainsKey(itemStatus))
return;
_statusCounts[itemStatus] -= interval;
if (_statusCounts[itemStatus] < 0)
_statusCounts[itemStatus] = 0;
}
}
#endregion
}
}

View File

@@ -0,0 +1,66 @@
using System;
using SabreTools.Hashing;
namespace SabreTools.Metadata.DatFiles
{
/// <summary>
/// Depot information wrapper
/// </summary>
public class DepotInformation : ICloneable
{
/// <summary>
/// Name or path of the Depot
/// </summary>
public string? Name { get; }
/// <summary>
/// Whether to use this Depot or not
/// </summary>
public bool IsActive { get; }
/// <summary>
/// Depot byte-depth
/// </summary>
public int Depth { get; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="isActive">Set active state</param>
/// <param name="depth">Set depth between 0 and SHA-1's byte length</param>
public DepotInformation(bool isActive, int depth)
: this(null, isActive, depth)
{
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="name">Identifier for the depot</param>
/// <param name="isActive">Set active state</param>
/// <param name="depth">Set depth between 0 and SHA-1's byte length</param>
public DepotInformation(string? name, bool isActive, int depth)
{
Name = name;
IsActive = isActive;
Depth = depth;
// Limit depth value
if (Depth == int.MinValue)
Depth = 4;
else if (Depth < 0)
Depth = 0;
else if (Depth > HashType.SHA1.ZeroBytes.Length)
Depth = HashType.SHA1.ZeroBytes.Length;
}
#region Cloning
/// <summary>
/// Clone the current object
/// </summary>
public object Clone() => new DepotInformation(Name, IsActive, Depth);
#endregion
}
}

View File

@@ -0,0 +1,267 @@
using System;
namespace SabreTools.Metadata.DatFiles
{
/// <summary>
/// DAT output formats
/// </summary>
[Flags]
public enum DatFormat : ulong
{
#region XML Formats
/// <summary>
/// Logiqx XML (using machine)
/// </summary>
Logiqx = 1 << 0,
/// <summary>
/// Logiqx XML (using game)
/// </summary>
LogiqxDeprecated = 1 << 1,
/// <summary>
/// MAME Softare List XML
/// </summary>
SoftwareList = 1 << 2,
/// <summary>
/// MAME Listxml output
/// </summary>
Listxml = 1 << 3,
/// <summary>
/// OfflineList XML
/// </summary>
OfflineList = 1 << 4,
/// <summary>
/// SabreDAT XML
/// </summary>
SabreXML = 1 << 5,
/// <summary>
/// openMSX Software List XML
/// </summary>
OpenMSX = 1 << 6,
/// <summary>
/// Archive.org file list XML
/// </summary>
ArchiveDotOrg = 1 << 7,
#endregion
#region Propietary Formats
/// <summary>
/// ClrMamePro custom
/// </summary>
ClrMamePro = 1 << 8,
/// <summary>
/// RomCenter INI-based
/// </summary>
RomCenter = 1 << 9,
/// <summary>
/// DOSCenter custom
/// </summary>
DOSCenter = 1 << 10,
/// <summary>
/// AttractMode custom
/// </summary>
AttractMode = 1 << 11,
#endregion
#region Standardized Text Formats
/// <summary>
/// ClrMamePro missfile
/// </summary>
MissFile = 1 << 12,
/// <summary>
/// Comma-Separated Values (standardized)
/// </summary>
CSV = 1 << 13,
/// <summary>
/// Semicolon-Separated Values (standardized)
/// </summary>
SSV = 1 << 14,
/// <summary>
/// Tab-Separated Values (standardized)
/// </summary>
TSV = 1 << 15,
/// <summary>
/// MAME Listrom output
/// </summary>
Listrom = 1 << 16,
/// <summary>
/// Everdrive Packs SMDB
/// </summary>
EverdriveSMDB = 1 << 17,
/// <summary>
/// SabreJSON
/// </summary>
SabreJSON = 1 << 18,
#endregion
#region SFV-similar Formats
/// <summary>
/// CRC32 hash list
/// </summary>
RedumpSFV = 1 << 19,
/// <summary>
/// MD2 hash list
/// </summary>
RedumpMD2 = 1 << 20,
/// <summary>
/// MD4 hash list
/// </summary>
RedumpMD4 = 1 << 21,
/// <summary>
/// MD5 hash list
/// </summary>
RedumpMD5 = 1 << 22,
/// <summary>
/// RIPEMD128 hash list
/// </summary>
RedumpRIPEMD128 = 1 << 23,
/// <summary>
/// RIPEMD160 hash list
/// </summary>
RedumpRIPEMD160 = 1 << 24,
/// <summary>
/// SHA-1 hash list
/// </summary>
RedumpSHA1 = 1 << 25,
/// <summary>
/// SHA-256 hash list
/// </summary>
RedumpSHA256 = 1 << 26,
/// <summary>
/// SHA-384 hash list
/// </summary>
RedumpSHA384 = 1 << 27,
/// <summary>
/// SHA-512 hash list
/// </summary>
RedumpSHA512 = 1 << 28,
/// <summary>
/// SpamSum hash list
/// </summary>
RedumpSpamSum = 1 << 29,
#endregion
// Specialty combinations
ALL = ulong.MaxValue,
}
/// <summary>
/// Determines merging tag handling for DAT output
/// </summary>
public enum MergingFlag
{
[Mapping("none")]
None = 0,
[Mapping("split")]
Split,
[Mapping("merged")]
Merged,
[Mapping("nonmerged", "unmerged")]
NonMerged,
/// <remarks>This is not usually defined for Merging flags</remarks>
[Mapping("fullmerged")]
FullMerged,
/// <remarks>This is not usually defined for Merging flags</remarks>
[Mapping("device", "deviceunmerged", "devicenonmerged")]
DeviceNonMerged,
/// <remarks>This is not usually defined for Merging flags</remarks>
[Mapping("full", "fullunmerged", "fullnonmerged")]
FullNonMerged,
}
/// <summary>
/// Determines nodump tag handling for DAT output
/// </summary>
public enum NodumpFlag
{
[Mapping("none")]
None = 0,
[Mapping("obsolete")]
Obsolete,
[Mapping("required")]
Required,
[Mapping("ignore")]
Ignore,
}
/// <summary>
/// Determines packing tag handling for DAT output
/// </summary>
public enum PackingFlag
{
[Mapping("none")]
None = 0,
/// <summary>
/// Force all sets to be in archives, except disk and media
/// </summary>
[Mapping("zip", "yes")]
Zip,
/// <summary>
/// Force all sets to be extracted into subfolders
/// </summary>
[Mapping("unzip", "no")]
Unzip,
/// <summary>
/// Force sets with single items to be extracted to the parent folder
/// </summary>
[Mapping("partial")]
Partial,
/// <summary>
/// Force all sets to be extracted to the parent folder
/// </summary>
[Mapping("flat")]
Flat,
/// <summary>
/// Force all sets to have all archives treated as files
/// </summary>
[Mapping("fileonly")]
FileOnly,
}
}

View File

@@ -0,0 +1,172 @@
using System.Collections.Generic;
using SabreTools.Metadata.Tools;
namespace SabreTools.Metadata.DatFiles
{
public static class Extensions
{
#region Private Maps
/// <summary>
/// Set of enum to string mappings for MergingFlag
/// </summary>
private static readonly Dictionary<string, MergingFlag> _toMergingFlagMap = Converters.GenerateToEnum<MergingFlag>();
/// <summary>
/// Set of string to enum mappings for MergingFlag
/// </summary>
private static readonly Dictionary<MergingFlag, string> _fromMergingFlagMap = Converters.GenerateToString<MergingFlag>(useSecond: false);
/// <summary>
/// Set of string to enum mappings for MergingFlag (secondary)
/// </summary>
private static readonly Dictionary<MergingFlag, string> _fromMergingFlagSecondaryMap = Converters.GenerateToString<MergingFlag>(useSecond: true);
/// <summary>
/// Set of enum to string mappings for NodumpFlag
/// </summary>
private static readonly Dictionary<string, NodumpFlag> _toNodumpFlagMap = Converters.GenerateToEnum<NodumpFlag>();
/// <summary>
/// Set of string to enum mappings for NodumpFlag
/// </summary>
private static readonly Dictionary<NodumpFlag, string> _fromNodumpFlagMap = Converters.GenerateToString<NodumpFlag>(useSecond: false);
/// <summary>
/// Set of enum to string mappings for PackingFlag
/// </summary>
private static readonly Dictionary<string, PackingFlag> _toPackingFlagMap = Converters.GenerateToEnum<PackingFlag>();
/// <summary>
/// Set of string to enum mappings for PackingFlag
/// </summary>
private static readonly Dictionary<PackingFlag, string> _fromPackingFlagMap = Converters.GenerateToString<PackingFlag>(useSecond: false);
/// <summary>
/// Set of string to enum mappings for PackingFlag (secondary)
/// </summary>
private static readonly Dictionary<PackingFlag, string> _fromPackingFlagSecondaryMap = Converters.GenerateToString<PackingFlag>(useSecond: true);
#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 MergingFlag AsMergingFlag(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 (_toMergingFlagMap.ContainsKey(value))
return _toMergingFlagMap[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 NodumpFlag AsNodumpFlag(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 (_toNodumpFlagMap.ContainsKey(value))
return _toNodumpFlagMap[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 PackingFlag AsPackingFlag(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 (_toPackingFlagMap.ContainsKey(value))
return _toPackingFlagMap[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 MergingFlag value, bool useSecond = false)
{
// Try to get the value from the mappings
if (!useSecond && _fromMergingFlagMap.ContainsKey(value))
return _fromMergingFlagMap[value];
else if (useSecond && _fromMergingFlagSecondaryMap.ContainsKey(value))
return _fromMergingFlagSecondaryMap[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 NodumpFlag value)
{
// Try to get the value from the mappings
if (_fromNodumpFlagMap.ContainsKey(value))
return _fromNodumpFlagMap[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 PackingFlag value, bool useSecond = false)
{
// Try to get the value from the mappings
if (!useSecond && _fromPackingFlagMap.ContainsKey(value))
return _fromPackingFlagMap[value];
else if (useSecond && _fromPackingFlagSecondaryMap.ContainsKey(value))
return _fromPackingFlagSecondaryMap[value];
// Otherwise, return null
return null;
}
#endregion
}
}

View File

@@ -0,0 +1,25 @@
using SabreTools.Metadata.DatItems;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents a Archive.org file list
/// </summary>
public sealed class ArchiveDotOrg : SerializableDatFile<Data.Models.ArchiveDotOrg.Files, Serialization.Readers.ArchiveDotOrg, Serialization.Writers.ArchiveDotOrg, Serialization.CrossModel.ArchiveDotOrg>
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public ArchiveDotOrg(DatFile? datFile) : base(datFile)
{
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.ArchiveDotOrg);
}
}
}

View File

@@ -0,0 +1,38 @@
using System.Collections.Generic;
using SabreTools.Metadata.DatItems;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents an AttractMode DAT
/// </summary>
public sealed class AttractMode : SerializableDatFile<Data.Models.AttractMode.MetadataFile, Serialization.Readers.AttractMode, Serialization.Writers.AttractMode, Serialization.CrossModel.AttractMode>
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public AttractMode(DatFile? datFile) : base(datFile)
{
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.AttractMode);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
return missingFields;
}
}
}

View File

@@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using SabreTools.Metadata.Filter;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents a ClrMamePro DAT
/// </summary>
public sealed class ClrMamePro : SerializableDatFile<Data.Models.ClrMamePro.MetadataFile, Serialization.Readers.ClrMamePro, Serialization.Writers.ClrMamePro, Serialization.CrossModel.ClrMamePro>
{
#region Fields
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Archive,
ItemType.BiosSet,
ItemType.Chip,
ItemType.DipSwitch,
ItemType.Disk,
ItemType.Display,
ItemType.Driver,
ItemType.Input,
ItemType.Media,
ItemType.Release,
ItemType.Rom,
ItemType.Sample,
ItemType.Sound,
];
#endregion
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public ClrMamePro(DatFile? datFile) : base(datFile)
{
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.ClrMamePro);
}
/// <inheritdoc/>
public override void ParseFile(string filename,
int indexId,
bool keep,
bool statsOnly = false,
FilterRunner? filterRunner = null,
bool throwOnError = false)
{
try
{
// Deserialize the input file
var metadataFile = new Serialization.Readers.ClrMamePro().Deserialize(filename, quotes: true);
var metadata = new Serialization.CrossModel.ClrMamePro().Serialize(metadataFile);
// Convert to the internal format
ConvertFromMetadata(metadata, filename, indexId, keep, statsOnly, filterRunner);
}
catch (Exception ex) when (!throwOnError)
{
string message = $"'{filename}' - An error occurred during parsing";
_logger.Error(ex, message);
}
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
#pragma warning disable IDE0010
switch (datItem)
{
case Release release:
if (string.IsNullOrEmpty(release.GetName()))
missingFields.Add(Data.Models.Metadata.Release.NameKey);
if (string.IsNullOrEmpty(release.GetStringFieldValue(Data.Models.Metadata.Release.RegionKey)))
missingFields.Add(Data.Models.Metadata.Release.RegionKey);
break;
case BiosSet biosset:
if (string.IsNullOrEmpty(biosset.GetName()))
missingFields.Add(Data.Models.Metadata.BiosSet.NameKey);
if (string.IsNullOrEmpty(biosset.GetStringFieldValue(Data.Models.Metadata.BiosSet.DescriptionKey)))
missingFields.Add(Data.Models.Metadata.BiosSet.DescriptionKey);
break;
case Rom rom:
if (string.IsNullOrEmpty(rom.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
if (rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) is null || rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) < 0)
missingFields.Add(Data.Models.Metadata.Rom.SizeKey);
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey))
&& string.IsNullOrEmpty(rom.GetStringFieldValue("MD2"))
&& string.IsNullOrEmpty(rom.GetStringFieldValue("MD4"))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA384Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA512Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SpamSumKey)))
{
missingFields.Add(Data.Models.Metadata.Rom.SHA1Key);
}
break;
case Disk disk:
if (string.IsNullOrEmpty(disk.GetName()))
missingFields.Add(Data.Models.Metadata.Disk.NameKey);
if (string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.MD5Key))
&& string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.SHA1Key)))
{
missingFields.Add(Data.Models.Metadata.Disk.SHA1Key);
}
break;
case Sample sample:
if (string.IsNullOrEmpty(sample.GetName()))
missingFields.Add(Data.Models.Metadata.Sample.NameKey);
break;
case Archive archive:
if (string.IsNullOrEmpty(archive.GetName()))
missingFields.Add(Data.Models.Metadata.Archive.NameKey);
break;
case Chip chip:
if (chip.GetStringFieldValue(Data.Models.Metadata.Chip.ChipTypeKey).AsChipType() == ChipType.NULL)
missingFields.Add(Data.Models.Metadata.Chip.ChipTypeKey);
if (string.IsNullOrEmpty(chip.GetName()))
missingFields.Add(Data.Models.Metadata.Chip.NameKey);
break;
case Display display:
if (display.GetStringFieldValue(Data.Models.Metadata.Display.DisplayTypeKey).AsDisplayType() == DisplayType.NULL)
missingFields.Add(Data.Models.Metadata.Display.DisplayTypeKey);
if (display.GetInt64FieldValue(Data.Models.Metadata.Display.RotateKey) is null)
missingFields.Add(Data.Models.Metadata.Display.RotateKey);
break;
case Sound sound:
if (sound.GetInt64FieldValue(Data.Models.Metadata.Sound.ChannelsKey) is null)
missingFields.Add(Data.Models.Metadata.Sound.ChannelsKey);
break;
case Input input:
if (input.GetInt64FieldValue(Data.Models.Metadata.Input.PlayersKey) is null)
missingFields.Add(Data.Models.Metadata.Input.PlayersKey);
if (!input.ControlsSpecified)
missingFields.Add(Data.Models.Metadata.Input.ControlKey);
break;
case DipSwitch dipswitch:
if (string.IsNullOrEmpty(dipswitch.GetName()))
missingFields.Add(Data.Models.Metadata.DipSwitch.NameKey);
break;
case Driver driver:
if (driver.GetStringFieldValue(Data.Models.Metadata.Driver.StatusKey).AsSupportStatus() == SupportStatus.NULL)
missingFields.Add(Data.Models.Metadata.Driver.StatusKey);
if (driver.GetStringFieldValue(Data.Models.Metadata.Driver.EmulationKey).AsSupportStatus() == SupportStatus.NULL)
missingFields.Add(Data.Models.Metadata.Driver.EmulationKey);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
/// <inheritdoc/>
public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false)
{
try
{
_logger.User($"Writing to '{outfile}'...");
// Serialize the input file
var metadata = ConvertToMetadata(ignoreblanks);
var metadataFile = new Serialization.CrossModel.ClrMamePro().Deserialize(metadata);
if (!new Serialization.Writers.ClrMamePro().SerializeFile(metadataFile, outfile, quotes: true))
{
_logger.Warning($"File '{outfile}' could not be written! See the log for more details.");
return false;
}
}
catch (Exception ex) when (!throwOnError)
{
_logger.Error(ex);
return false;
}
_logger.User($"'{outfile}' written!{Environment.NewLine}");
return true;
}
}
}

View File

@@ -0,0 +1,55 @@
using System.Collections.Generic;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents parsing and writing of a DosCenter DAT
/// </summary>
public sealed class DosCenter : SerializableDatFile<Data.Models.DosCenter.MetadataFile, Serialization.Readers.DosCenter, Serialization.Writers.DosCenter, Serialization.CrossModel.DosCenter>
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public DosCenter(DatFile? datFile) : base(datFile)
{
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.DOSCenter);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Rom rom:
if (rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) is null || rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) < 0)
missingFields.Add(Data.Models.Metadata.Rom.SizeKey);
// if (string.IsNullOrEmpty(rom.Date))
// missingFields.Add(Data.Models.Metadata.Rom.DateKey);
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey)))
missingFields.Add(Data.Models.Metadata.Rom.CRCKey);
// if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key)))
// missingFields.Add(Data.Models.Metadata.Rom.SHA1Key);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
}

View File

@@ -0,0 +1,55 @@
using System.Collections.Generic;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents parsing and writing of an Everdrive SMDB file
/// </summary>
public sealed class EverdriveSMDB : SerializableDatFile<Data.Models.EverdriveSMDB.MetadataFile, Serialization.Readers.EverdriveSMDB, Serialization.Writers.EverdriveSMDB, Serialization.CrossModel.EverdriveSMDB>
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public EverdriveSMDB(DatFile? datFile) : base(datFile)
{
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.EverdriveSMDB);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Rom rom:
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key)))
missingFields.Add(Data.Models.Metadata.Rom.SHA256Key);
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key)))
missingFields.Add(Data.Models.Metadata.Rom.SHA1Key);
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key)))
missingFields.Add(Data.Models.Metadata.Rom.MD5Key);
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey)))
missingFields.Add(Data.Models.Metadata.Rom.CRCKey);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
}

View File

@@ -0,0 +1,601 @@
using System;
using System.Collections.Generic;
using SabreTools.Metadata.Filter;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
using SabreTools.Hashing;
#pragma warning disable IDE0290 // Use primary constructor
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents a hashfile such as an SFV, MD5, or SHA-1 file
/// </summary>
public abstract class Hashfile : SerializableDatFile<Data.Models.Hashfile.Hashfile, Serialization.Readers.Hashfile, Serialization.Writers.Hashfile, Serialization.CrossModel.Hashfile>
{
#region Fields
// Private instance variables specific to Hashfile DATs
protected HashType _hash;
#endregion
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Hashfile(DatFile? datFile) : base(datFile)
{
}
/// <inheritdoc/>
public override void ParseFile(string filename,
int indexId,
bool keep,
bool statsOnly = false,
FilterRunner? filterRunner = null,
bool throwOnError = false)
{
try
{
// Deserialize the input file
var hashfile = new Serialization.Readers.Hashfile().Deserialize(filename, _hash);
var metadata = new Serialization.CrossModel.Hashfile().Serialize(hashfile);
// Convert to the internal format
ConvertFromMetadata(metadata, filename, indexId, keep, statsOnly, filterRunner);
}
catch (Exception ex) when (!throwOnError)
{
string message = $"'{filename}' - An error occurred during parsing";
_logger.Error(ex, message);
}
}
/// <inheritdoc/>
public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false)
{
try
{
_logger.User($"Writing to '{outfile}'...");
// Serialize the input file
var metadata = ConvertToMetadata(ignoreblanks);
var hashfile = new Serialization.CrossModel.Hashfile().Deserialize(metadata, _hash);
if (!new Serialization.Writers.Hashfile().SerializeFile(hashfile, outfile, _hash))
{
_logger.Warning($"File '{outfile}' could not be written! See the log for more details.");
return false;
}
}
catch (Exception ex) when (!throwOnError)
{
_logger.Error(ex);
return false;
}
_logger.User($"'{outfile}' written!{Environment.NewLine}");
return true;
}
}
/// <summary>
/// Represents an SFV (CRC-32) hashfile
/// </summary>
public sealed class SfvFile : Hashfile
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public SfvFile(DatFile? datFile) : base(datFile)
{
_hash = HashType.CRC32;
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.RedumpSFV);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Rom rom:
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey)))
missingFields.Add(Data.Models.Metadata.Rom.CRCKey);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
/// <summary>
/// Represents an MD2 hashfile
/// </summary>
public sealed class Md2File : Hashfile
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Md2File(DatFile? datFile) : base(datFile)
{
_hash = HashType.MD2;
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.RedumpMD2);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Rom rom:
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.MD2Key)))
missingFields.Add(Data.Models.Metadata.Rom.MD2Key);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
/// <summary>
/// Represents an MD4 hashfile
/// </summary>
public sealed class Md4File : Hashfile
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Md4File(DatFile? datFile) : base(datFile)
{
_hash = HashType.MD4;
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.RedumpMD4);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Rom rom:
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.MD4Key)))
missingFields.Add(Data.Models.Metadata.Rom.MD4Key);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
/// <summary>
/// Represents an MD5 hashfile
/// </summary>
public sealed class Md5File : Hashfile
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Disk,
ItemType.Media,
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Md5File(DatFile? datFile) : base(datFile)
{
_hash = HashType.MD5;
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.RedumpMD5);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Disk disk:
if (string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.MD5Key)))
missingFields.Add(Data.Models.Metadata.Disk.MD5Key);
break;
case Media medium:
if (string.IsNullOrEmpty(medium.GetStringFieldValue(Data.Models.Metadata.Media.MD5Key)))
missingFields.Add(Data.Models.Metadata.Media.MD5Key);
break;
case Rom rom:
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key)))
missingFields.Add(Data.Models.Metadata.Rom.MD5Key);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
/// <summary>
/// Represents an RIPEMD128 hashfile
/// </summary>
public sealed class RipeMD128File : Hashfile
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public RipeMD128File(DatFile? datFile) : base(datFile)
{
_hash = HashType.RIPEMD128;
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.RedumpRIPEMD128);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Rom rom:
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key)))
missingFields.Add(Data.Models.Metadata.Rom.RIPEMD128Key);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
/// <summary>
/// Represents an RIPEMD160 hashfile
/// </summary>
public sealed class RipeMD160File : Hashfile
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public RipeMD160File(DatFile? datFile) : base(datFile)
{
_hash = HashType.RIPEMD160;
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.RedumpRIPEMD160);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Rom rom:
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key)))
missingFields.Add(Data.Models.Metadata.Rom.RIPEMD160Key);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
/// <summary>
/// Represents an SHA-1 hashfile
/// </summary>
public sealed class Sha1File : Hashfile
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Disk,
ItemType.Media,
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Sha1File(DatFile? datFile) : base(datFile)
{
_hash = HashType.SHA1;
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.RedumpSHA1);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Disk disk:
if (string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.SHA1Key)))
missingFields.Add(Data.Models.Metadata.Disk.SHA1Key);
break;
case Media medium:
if (string.IsNullOrEmpty(medium.GetStringFieldValue(Data.Models.Metadata.Media.SHA1Key)))
missingFields.Add(Data.Models.Metadata.Media.SHA1Key);
break;
case Rom rom:
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key)))
missingFields.Add(Data.Models.Metadata.Rom.SHA1Key);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
/// <summary>
/// Represents an SHA-256 hashfile
/// </summary>
public sealed class Sha256File : Hashfile
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Media,
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Sha256File(DatFile? datFile) : base(datFile)
{
_hash = HashType.SHA256;
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.RedumpSHA256);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Media medium:
if (string.IsNullOrEmpty(medium.GetStringFieldValue(Data.Models.Metadata.Media.SHA256Key)))
missingFields.Add(Data.Models.Metadata.Media.SHA256Key);
break;
case Rom rom:
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key)))
missingFields.Add(Data.Models.Metadata.Rom.SHA256Key);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
/// <summary>
/// Represents an SHA-384 hashfile
/// </summary>
public sealed class Sha384File : Hashfile
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Sha384File(DatFile? datFile) : base(datFile)
{
_hash = HashType.SHA384;
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.RedumpSHA384);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Rom rom:
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA384Key)))
missingFields.Add(Data.Models.Metadata.Rom.SHA384Key);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
/// <summary>
/// Represents an SHA-512 hashfile
/// </summary>
public sealed class Sha512File : Hashfile
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Sha512File(DatFile? datFile) : base(datFile)
{
_hash = HashType.SHA512;
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.RedumpSHA512);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Rom rom:
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA512Key)))
missingFields.Add(Data.Models.Metadata.Rom.SHA512Key);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
/// <summary>
/// Represents an SpamSum hashfile
/// </summary>
public sealed class SpamSumFile : Hashfile
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Media,
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public SpamSumFile(DatFile? datFile) : base(datFile)
{
_hash = HashType.SpamSum;
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.RedumpSpamSum);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Media medium:
if (string.IsNullOrEmpty(medium.GetStringFieldValue(Data.Models.Metadata.Media.SpamSumKey)))
missingFields.Add(Data.Models.Metadata.Media.SpamSumKey);
break;
case Rom rom:
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SpamSumKey)))
missingFields.Add(Data.Models.Metadata.Rom.SpamSumKey);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
}

View File

@@ -0,0 +1,63 @@
using System.Collections.Generic;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents a MAME Listrom file
/// </summary>
public sealed class Listrom : SerializableDatFile<Data.Models.Listrom.MetadataFile, Serialization.Readers.Listrom, Serialization.Writers.Listrom, Serialization.CrossModel.Listrom>
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Disk,
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Listrom(DatFile? datFile) : base(datFile)
{
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.Listrom);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Disk disk:
if (string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.MD5Key))
&& string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.SHA1Key)))
{
missingFields.Add(Data.Models.Metadata.Disk.SHA1Key);
}
break;
case Rom rom:
if (rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) is null || rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) < 0)
missingFields.Add(Data.Models.Metadata.Rom.SizeKey);
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey)))
missingFields.Add(Data.Models.Metadata.Rom.CRCKey);
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key)))
missingFields.Add(Data.Models.Metadata.Rom.SHA1Key);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
}

View File

@@ -0,0 +1,397 @@
using System;
using System.Collections.Generic;
using SabreTools.Metadata.Filter;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents a MAME/M1 XML DAT
/// </summary>
public sealed class Listxml : SerializableDatFile<Data.Models.Listxml.Mame, Serialization.Readers.Listxml, Serialization.Writers.Listxml, Serialization.CrossModel.Listxml>
{
#region Constants
/// <summary>
/// DTD for original MAME XML DATs
/// </summary>
internal const string MAMEDTD = @"<!DOCTYPE mame [
<!ELEMENT mame (machine+)>
<!ATTLIST mame build CDATA #IMPLIED>
<!ATTLIST mame debug (yes|no) ""no"">
<!ATTLIST mame mameconfig CDATA #REQUIRED>
<!ELEMENT machine (description, year?, manufacturer?, biosset*, rom*, disk*, device_ref*, sample*, chip*, display*, sound?, input?, dipswitch*, configuration*, port*, adjuster*, driver?, feature*, device*, slot*, softwarelist*, ramoption*)>
<!ATTLIST machine name CDATA #REQUIRED>
<!ATTLIST machine sourcefile CDATA #IMPLIED>
<!ATTLIST machine isbios (yes|no) ""no"">
<!ATTLIST machine isdevice (yes|no) ""no"">
<!ATTLIST machine ismechanical (yes|no) ""no"">
<!ATTLIST machine runnable (yes|no) ""yes"">
<!ATTLIST machine cloneof CDATA #IMPLIED>
<!ATTLIST machine romof CDATA #IMPLIED>
<!ATTLIST machine sampleof CDATA #IMPLIED>
<!ELEMENT description (#PCDATA)>
<!ELEMENT year (#PCDATA)>
<!ELEMENT manufacturer (#PCDATA)>
<!ELEMENT biosset EMPTY>
<!ATTLIST biosset name CDATA #REQUIRED>
<!ATTLIST biosset description CDATA #REQUIRED>
<!ATTLIST biosset default (yes|no) ""no"">
<!ELEMENT rom EMPTY>
<!ATTLIST rom name CDATA #REQUIRED>
<!ATTLIST rom bios CDATA #IMPLIED>
<!ATTLIST rom size CDATA #REQUIRED>
<!ATTLIST rom crc CDATA #IMPLIED>
<!ATTLIST rom sha1 CDATA #IMPLIED>
<!ATTLIST rom merge CDATA #IMPLIED>
<!ATTLIST rom region CDATA #IMPLIED>
<!ATTLIST rom offset CDATA #IMPLIED>
<!ATTLIST rom status (baddump|nodump|good) ""good"">
<!ATTLIST rom optional (yes|no) ""no"">
<!ELEMENT disk EMPTY>
<!ATTLIST disk name CDATA #REQUIRED>
<!ATTLIST disk sha1 CDATA #IMPLIED>
<!ATTLIST disk merge CDATA #IMPLIED>
<!ATTLIST disk region CDATA #IMPLIED>
<!ATTLIST disk index CDATA #IMPLIED>
<!ATTLIST disk writable (yes|no) ""no"">
<!ATTLIST disk status (baddump|nodump|good) ""good"">
<!ATTLIST disk optional (yes|no) ""no"">
<!ELEMENT device_ref EMPTY>
<!ATTLIST device_ref name CDATA #REQUIRED>
<!ELEMENT sample EMPTY>
<!ATTLIST sample name CDATA #REQUIRED>
<!ELEMENT chip EMPTY>
<!ATTLIST chip name CDATA #REQUIRED>
<!ATTLIST chip tag CDATA #IMPLIED>
<!ATTLIST chip type (cpu|audio) #REQUIRED>
<!ATTLIST chip clock CDATA #IMPLIED>
<!ELEMENT display EMPTY>
<!ATTLIST display tag CDATA #IMPLIED>
<!ATTLIST display type (raster|vector|lcd|svg|unknown) #REQUIRED>
<!ATTLIST display rotate (0|90|180|270) #IMPLIED>
<!ATTLIST display flipx (yes|no) ""no"">
<!ATTLIST display width CDATA #IMPLIED>
<!ATTLIST display height CDATA #IMPLIED>
<!ATTLIST display refresh CDATA #REQUIRED>
<!ATTLIST display pixclock CDATA #IMPLIED>
<!ATTLIST display htotal CDATA #IMPLIED>
<!ATTLIST display hbend CDATA #IMPLIED>
<!ATTLIST display hbstart CDATA #IMPLIED>
<!ATTLIST display vtotal CDATA #IMPLIED>
<!ATTLIST display vbend CDATA #IMPLIED>
<!ATTLIST display vbstart CDATA #IMPLIED>
<!ELEMENT sound EMPTY>
<!ATTLIST sound channels CDATA #REQUIRED>
<!ELEMENT condition EMPTY>
<!ATTLIST condition tag CDATA #REQUIRED>
<!ATTLIST condition mask CDATA #REQUIRED>
<!ATTLIST condition relation (eq|ne|gt|le|lt|ge) #REQUIRED>
<!ATTLIST condition value CDATA #REQUIRED>
<!ELEMENT input (control*)>
<!ATTLIST input service (yes|no) ""no"">
<!ATTLIST input tilt (yes|no) ""no"">
<!ATTLIST input players CDATA #REQUIRED>
<!ATTLIST input coins CDATA #IMPLIED>
<!ELEMENT control EMPTY>
<!ATTLIST control type CDATA #REQUIRED>
<!ATTLIST control player CDATA #IMPLIED>
<!ATTLIST control buttons CDATA #IMPLIED>
<!ATTLIST control reqbuttons CDATA #IMPLIED>
<!ATTLIST control minimum CDATA #IMPLIED>
<!ATTLIST control maximum CDATA #IMPLIED>
<!ATTLIST control sensitivity CDATA #IMPLIED>
<!ATTLIST control keydelta CDATA #IMPLIED>
<!ATTLIST control reverse (yes|no) ""no"">
<!ATTLIST control ways CDATA #IMPLIED>
<!ATTLIST control ways2 CDATA #IMPLIED>
<!ATTLIST control ways3 CDATA #IMPLIED>
<!ELEMENT dipswitch (condition?, diplocation*, dipvalue*)>
<!ATTLIST dipswitch name CDATA #REQUIRED>
<!ATTLIST dipswitch tag CDATA #REQUIRED>
<!ATTLIST dipswitch mask CDATA #REQUIRED>
<!ELEMENT diplocation EMPTY>
<!ATTLIST diplocation name CDATA #REQUIRED>
<!ATTLIST diplocation number CDATA #REQUIRED>
<!ATTLIST diplocation inverted (yes|no) ""no"">
<!ELEMENT dipvalue (condition?)>
<!ATTLIST dipvalue name CDATA #REQUIRED>
<!ATTLIST dipvalue value CDATA #REQUIRED>
<!ATTLIST dipvalue default (yes|no) ""no"">
<!ELEMENT configuration (condition?, conflocation*, confsetting*)>
<!ATTLIST configuration name CDATA #REQUIRED>
<!ATTLIST configuration tag CDATA #REQUIRED>
<!ATTLIST configuration mask CDATA #REQUIRED>
<!ELEMENT conflocation EMPTY>
<!ATTLIST conflocation name CDATA #REQUIRED>
<!ATTLIST conflocation number CDATA #REQUIRED>
<!ATTLIST conflocation inverted (yes|no) ""no"">
<!ELEMENT confsetting (condition?)>
<!ATTLIST confsetting name CDATA #REQUIRED>
<!ATTLIST confsetting value CDATA #REQUIRED>
<!ATTLIST confsetting default (yes|no) ""no"">
<!ELEMENT port (analog*)>
<!ATTLIST port tag CDATA #REQUIRED>
<!ELEMENT analog EMPTY>
<!ATTLIST analog mask CDATA #REQUIRED>
<!ELEMENT adjuster (condition?)>
<!ATTLIST adjuster name CDATA #REQUIRED>
<!ATTLIST adjuster default CDATA #REQUIRED>
<!ELEMENT driver EMPTY>
<!ATTLIST driver status (good|imperfect|preliminary) #REQUIRED>
<!ATTLIST driver emulation (good|imperfect|preliminary) #REQUIRED>
<!ATTLIST driver cocktail (good|imperfect|preliminary) #IMPLIED>
<!ATTLIST driver savestate (supported|unsupported) #REQUIRED>
<!ATTLIST driver requiresartwork (yes|no) ""no"">
<!ATTLIST driver unofficial (yes|no) ""no"">
<!ATTLIST driver nosoundhardware (yes|no) ""no"">
<!ATTLIST driver incomplete (yes|no) ""no"">
<!ELEMENT feature EMPTY>
<!ATTLIST feature type (protection|timing|graphics|palette|sound|capture|camera|microphone|controls|keyboard|mouse|media|disk|printer|tape|punch|drum|rom|comms|lan|wan) #REQUIRED>
<!ATTLIST feature status (unemulated|imperfect) #IMPLIED>
<!ATTLIST feature overall (unemulated|imperfect) #IMPLIED>
<!ELEMENT device (instance?, extension*)>
<!ATTLIST device type CDATA #REQUIRED>
<!ATTLIST device tag CDATA #IMPLIED>
<!ATTLIST device fixed_image CDATA #IMPLIED>
<!ATTLIST device mandatory CDATA #IMPLIED>
<!ATTLIST device interface CDATA #IMPLIED>
<!ELEMENT instance EMPTY>
<!ATTLIST instance name CDATA #REQUIRED>
<!ATTLIST instance briefname CDATA #REQUIRED>
<!ELEMENT extension EMPTY>
<!ATTLIST extension name CDATA #REQUIRED>
<!ELEMENT slot (slotoption*)>
<!ATTLIST slot name CDATA #REQUIRED>
<!ELEMENT slotoption EMPTY>
<!ATTLIST slotoption name CDATA #REQUIRED>
<!ATTLIST slotoption devname CDATA #REQUIRED>
<!ATTLIST slotoption default (yes|no) ""no"">
<!ELEMENT softwarelist EMPTY>
<!ATTLIST softwarelist tag CDATA #REQUIRED>
<!ATTLIST softwarelist name CDATA #REQUIRED>
<!ATTLIST softwarelist status (original|compatible) #REQUIRED>
<!ATTLIST softwarelist filter CDATA #IMPLIED>
<!ELEMENT ramoption (#PCDATA)>
<!ATTLIST ramoption name CDATA #REQUIRED>
<!ATTLIST ramoption default CDATA #IMPLIED>
]>
";
#endregion
#region Fields
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Adjuster,
ItemType.BiosSet,
ItemType.Chip,
ItemType.Condition,
ItemType.Configuration,
ItemType.Device,
ItemType.DeviceRef,
ItemType.DipSwitch,
ItemType.Disk,
ItemType.Display,
ItemType.Driver,
ItemType.Feature,
ItemType.Input,
ItemType.Port,
ItemType.RamOption,
ItemType.Rom,
ItemType.Sample,
ItemType.Slot,
ItemType.SoftwareList,
ItemType.Sound,
];
#endregion
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Listxml(DatFile? datFile) : base(datFile)
{
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.Listxml);
}
/// <inheritdoc/>
public override void ParseFile(string filename,
int indexId,
bool keep,
bool statsOnly = false,
FilterRunner? filterRunner = null,
bool throwOnError = false)
{
try
{
// Deserialize the input file
var mame = new Serialization.Readers.Listxml().Deserialize(filename);
Data.Models.Metadata.MetadataFile? metadata;
if (mame is null)
{
var m1 = new Serialization.Readers.M1().Deserialize(filename);
metadata = new Serialization.CrossModel.M1().Serialize(m1);
}
else
{
metadata = new Serialization.CrossModel.Listxml().Serialize(mame);
}
// Convert to the internal format
ConvertFromMetadata(metadata, filename, indexId, keep, statsOnly, filterRunner);
}
catch (Exception ex) when (!throwOnError)
{
string message = $"'{filename}' - An error occurred during parsing";
_logger.Error(ex, message);
}
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
#pragma warning disable IDE0010
switch (datItem)
{
case BiosSet biosset:
if (string.IsNullOrEmpty(biosset.GetName()))
missingFields.Add(Data.Models.Metadata.BiosSet.NameKey);
if (string.IsNullOrEmpty(biosset.GetStringFieldValue(Data.Models.Metadata.BiosSet.DescriptionKey)))
missingFields.Add(Data.Models.Metadata.BiosSet.DescriptionKey);
break;
case Rom rom:
if (string.IsNullOrEmpty(rom.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
if (rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) is null || rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) < 0)
missingFields.Add(Data.Models.Metadata.Rom.SizeKey);
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key)))
{
missingFields.Add(Data.Models.Metadata.Rom.SHA1Key);
}
break;
case Disk disk:
if (string.IsNullOrEmpty(disk.GetName()))
missingFields.Add(Data.Models.Metadata.Disk.NameKey);
if (string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.MD5Key))
&& string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.SHA1Key)))
{
missingFields.Add(Data.Models.Metadata.Disk.SHA1Key);
}
break;
case DeviceRef deviceref:
if (string.IsNullOrEmpty(deviceref.GetName()))
missingFields.Add(Data.Models.Metadata.DeviceRef.NameKey);
break;
case Sample sample:
if (string.IsNullOrEmpty(sample.GetName()))
missingFields.Add(Data.Models.Metadata.Sample.NameKey);
break;
case Chip chip:
if (string.IsNullOrEmpty(chip.GetName()))
missingFields.Add(Data.Models.Metadata.Chip.NameKey);
if (chip.GetStringFieldValue(Data.Models.Metadata.Chip.ChipTypeKey).AsChipType() == ChipType.NULL)
missingFields.Add(Data.Models.Metadata.Chip.ChipTypeKey);
break;
case Display display:
if (display.GetStringFieldValue(Data.Models.Metadata.Display.DisplayTypeKey).AsDisplayType() == DisplayType.NULL)
missingFields.Add(Data.Models.Metadata.Display.DisplayTypeKey);
if (display.GetDoubleFieldValue(Data.Models.Metadata.Display.RefreshKey) is null)
missingFields.Add(Data.Models.Metadata.Display.RefreshKey);
break;
case Sound sound:
if (sound.GetInt64FieldValue(Data.Models.Metadata.Sound.ChannelsKey) is null)
missingFields.Add(Data.Models.Metadata.Sound.ChannelsKey);
break;
case Input input:
if (input.GetInt64FieldValue(Data.Models.Metadata.Input.PlayersKey) is null)
missingFields.Add(Data.Models.Metadata.Input.PlayersKey);
break;
case DipSwitch dipswitch:
if (string.IsNullOrEmpty(dipswitch.GetName()))
missingFields.Add(Data.Models.Metadata.DipSwitch.NameKey);
if (string.IsNullOrEmpty(dipswitch.GetStringFieldValue(Data.Models.Metadata.DipSwitch.TagKey)))
missingFields.Add(Data.Models.Metadata.DipSwitch.TagKey);
break;
case Configuration configuration:
if (string.IsNullOrEmpty(configuration.GetName()))
missingFields.Add(Data.Models.Metadata.Configuration.NameKey);
if (string.IsNullOrEmpty(configuration.GetStringFieldValue(Data.Models.Metadata.Configuration.TagKey)))
missingFields.Add(Data.Models.Metadata.Configuration.TagKey);
break;
case Port port:
if (string.IsNullOrEmpty(port.GetStringFieldValue(Data.Models.Metadata.Port.TagKey)))
missingFields.Add(Data.Models.Metadata.Port.TagKey);
break;
case Adjuster adjuster:
if (string.IsNullOrEmpty(adjuster.GetName()))
missingFields.Add(Data.Models.Metadata.Adjuster.NameKey);
break;
case Driver driver:
if (driver.GetStringFieldValue(Data.Models.Metadata.Driver.StatusKey).AsSupportStatus() == SupportStatus.NULL)
missingFields.Add(Data.Models.Metadata.Driver.StatusKey);
if (driver.GetStringFieldValue(Data.Models.Metadata.Driver.EmulationKey).AsSupportStatus() == SupportStatus.NULL)
missingFields.Add(Data.Models.Metadata.Driver.EmulationKey);
if (driver.GetStringFieldValue(Data.Models.Metadata.Driver.CocktailKey).AsSupportStatus() == SupportStatus.NULL)
missingFields.Add(Data.Models.Metadata.Driver.CocktailKey);
if (driver.GetStringFieldValue(Data.Models.Metadata.Driver.SaveStateKey).AsSupportStatus() == SupportStatus.NULL)
missingFields.Add(Data.Models.Metadata.Driver.SaveStateKey);
break;
case Feature feature:
if (feature.GetStringFieldValue(Data.Models.Metadata.Feature.FeatureTypeKey).AsFeatureType() == FeatureType.NULL)
missingFields.Add(Data.Models.Metadata.Feature.FeatureTypeKey);
break;
case Device device:
if (device.GetStringFieldValue(Data.Models.Metadata.Device.DeviceTypeKey).AsDeviceType() == DeviceType.NULL)
missingFields.Add(Data.Models.Metadata.Device.DeviceTypeKey);
break;
case Slot slot:
if (string.IsNullOrEmpty(slot.GetName()))
missingFields.Add(Data.Models.Metadata.Slot.NameKey);
break;
case DatItems.Formats.SoftwareList softwarelist:
if (string.IsNullOrEmpty(softwarelist.GetStringFieldValue(Data.Models.Metadata.SoftwareList.TagKey)))
missingFields.Add(Data.Models.Metadata.SoftwareList.TagKey);
if (string.IsNullOrEmpty(softwarelist.GetName()))
missingFields.Add(Data.Models.Metadata.SoftwareList.NameKey);
if (softwarelist.GetStringFieldValue(Data.Models.Metadata.SoftwareList.StatusKey).AsSoftwareListStatus() == SoftwareListStatus.None)
missingFields.Add(Data.Models.Metadata.SoftwareList.StatusKey);
break;
case RamOption ramoption:
if (string.IsNullOrEmpty(ramoption.GetName()))
missingFields.Add(Data.Models.Metadata.RamOption.NameKey);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
}

View File

@@ -0,0 +1,404 @@
using System;
using System.Collections.Generic;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents a Logiqx-derived DAT
/// </summary>
public sealed class Logiqx : SerializableDatFile<Data.Models.Logiqx.Datafile, Serialization.Readers.Logiqx, Serialization.Writers.Logiqx, Serialization.CrossModel.Logiqx>
{
#region Constants
/// <summary>
/// DTD for original Logiqx DATs
/// </summary>
/// <remarks>This has been edited to reflect actual current standards</remarks>
internal const string LogiqxDTD = @"<!--
ROM Management Datafile - DTD
For further information, see: http://www.logiqx.com/
This DTD module is identified by the PUBLIC and SYSTEM identifiers:
PUBLIC "" -//Logiqx//DTD ROM Management Datafile//EN""
SYSTEM ""http://www.logiqx.com/Dats/datafile.dtd""
$Revision: 1.5 $
$Date: 2008/10/28 21:39:16 $
-->
<!ELEMENT datafile(header?, game*, machine*)>
<!ATTLIST datafile build CDATA #IMPLIED>
<!ATTLIST datafile debug (yes|no) ""no"">
<!ELEMENT header (id?, name, description, rootdir?, type?, category?, version, date?, author, email?, homepage?, url?, comment?, clrmamepro?, romcenter?)>
<!ELEMENT id (#PCDATA)>
<!ELEMENT name(#PCDATA)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT rootdir (#PCDATA)>
<!ELEMENT type (#PCDATA)>
<!ELEMENT category (#PCDATA)>
<!ELEMENT version (#PCDATA)>
<!ELEMENT date (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT email (#PCDATA)>
<!ELEMENT homepage (#PCDATA)>
<!ELEMENT url (#PCDATA)>
<!ELEMENT comment (#PCDATA)>
<!ELEMENT clrmamepro EMPTY>
<!ATTLIST clrmamepro header CDATA #IMPLIED>
<!ATTLIST clrmamepro forcemerging (none|split|merged|nonmerged|fullmerged|device|full) ""split"">
<!ATTLIST clrmamepro forcenodump(obsolete|required|ignore) ""obsolete"">
<!ATTLIST clrmamepro forcepacking(zip|unzip) ""zip"">
<!ELEMENT romcenter EMPTY>
<!ATTLIST romcenter plugin CDATA #IMPLIED>
<!ATTLIST romcenter rommode (none|split|merged|unmerged|fullmerged|device|full) ""split"">
<!ATTLIST romcenter biosmode (none|split|merged|unmerged|fullmerged|device|full) ""split"">
<!ATTLIST romcenter samplemode (none|split|merged|unmerged|fullmerged|device|full) ""merged"">
<!ATTLIST romcenter lockrommode(yes|no) ""no"">
<!ATTLIST romcenter lockbiosmode(yes|no) ""no"">
<!ATTLIST romcenter locksamplemode(yes|no) ""no"">
<!ELEMENT game (comment*, description, year?, manufacturer?, publisher?, category?, trurip?, release*, biosset*, rom*, disk*, media*, sample*, archive*)>
<!ATTLIST game name CDATA #REQUIRED>
<!ATTLIST game sourcefile CDATA #IMPLIED>
<!ATTLIST game isbios (yes|no) ""no"">
<!ATTLIST game cloneof CDATA #IMPLIED>
<!ATTLIST game romof CDATA #IMPLIED>
<!ATTLIST game sampleof CDATA #IMPLIED>
<!ATTLIST game board CDATA #IMPLIED>
<!ATTLIST game rebuildto CDATA #IMPLIED>
<!ATTLIST game id CDATA #IMPLIED>
<!ATTLIST game cloneofid CDATA #IMPLIED>
<!ATTLIST game runnable (no|partial|yes) ""no"" #IMPLIED>
<!ELEMENT year (#PCDATA)>
<!ELEMENT manufacturer (#PCDATA)>
<!ELEMENT publisher (#PCDATA)>
<!ELEMENT trurip (titleid?, publisher?, developer?, year?, genre?, subgenre?, ratings?, score?, players?, enabled?, crc?, source?, cloneof?, relatedto?)>
<!ELEMENT titleid (#PCDATA)>
<!ELEMENT developer (#PCDATA)>
<!ELEMENT year (#PCDATA)>
<!ELEMENT genre (#PCDATA)>
<!ELEMENT subgenre (#PCDATA)>
<!ELEMENT ratings (#PCDATA)>
<!ELEMENT score (#PCDATA)>
<!ELEMENT players (#PCDATA)>
<!ELEMENT enabled (#PCDATA)>
<!ELEMENT crc (#PCDATA)>
<!ELEMENT source (#PCDATA)>
<!ELEMENT cloneof (#PCDATA)>
<!ELEMENT relatedto (#PCDATA)>
<!ELEMENT release EMPTY>
<!ATTLIST release name CDATA #REQUIRED>
<!ATTLIST release region CDATA #REQUIRED>
<!ATTLIST release language CDATA #IMPLIED>
<!ATTLIST release date CDATA #IMPLIED>
<!ATTLIST release default (yes|no) ""no"">
<!ELEMENT biosset EMPTY>
<!ATTLIST biosset name CDATA #REQUIRED>
<!ATTLIST biosset description CDATA #REQUIRED>
<!ATTLIST biosset default (yes|no) ""no"">
<!ELEMENT rom EMPTY>
<!ATTLIST rom name CDATA #REQUIRED>
<!ATTLIST rom size CDATA #REQUIRED>
<!ATTLIST rom crc CDATA #IMPLIED>
<!ATTLIST rom md5 CDATA #IMPLIED>
<!ATTLIST rom sha1 CDATA #IMPLIED>
<!ATTLIST rom sha256 CDATA #IMPLIED>
<!ATTLIST rom sha384 CDATA #IMPLIED>
<!ATTLIST rom sha512 CDATA #IMPLIED>
<!ATTLIST rom spamsum CDATA #IMPLIED>
<!ATTLIST rom xxh3_64 CDATA #IMPLIED>
<!ATTLIST rom xxh3_128 CDATA #IMPLIED>
<!ATTLIST rom merge CDATA #IMPLIED>
<!ATTLIST rom status (baddump|nodump|good|verified) ""good"">
<!ATTLIST rom serial CDATA #IMPLIED>
<!ATTLIST rom header CDATA #IMPLIED>
<!ATTLIST rom date CDATA #IMPLIED>
<!ATTLIST rom inverted CDATA #IMPLIED>
<!ATTLIST rom mia CDATA #IMPLIED>
<!ELEMENT disk EMPTY>
<!ATTLIST disk name CDATA #REQUIRED>
<!ATTLIST disk md5 CDATA #IMPLIED>
<!ATTLIST disk sha1 CDATA #IMPLIED>
<!ATTLIST disk merge CDATA #IMPLIED>
<!ATTLIST disk status (baddump|nodump|good|verified) ""good"">
<!ELEMENT media EMPTY>
<!ATTLIST media name CDATA #REQUIRED>
<!ATTLIST media md5 CDATA #IMPLIED>
<!ATTLIST media sha1 CDATA #IMPLIED>
<!ATTLIST media sha256 CDATA #IMPLIED>
<!ATTLIST media spamsum CDATA #IMPLIED>
<!ELEMENT sample EMPTY>
<!ATTLIST sample name CDATA #REQUIRED>
<!ELEMENT archive EMPTY>
<!ATTLIST archive name CDATA #REQUIRED>
<!ELEMENT machine (comment*, description, year?, manufacturer?, publisher?, category?, trurip?, release*, biosset*, rom*, disk*, media*, sample*, archive*)>
<!ATTLIST game name CDATA #REQUIRED>
<!ATTLIST game sourcefile CDATA #IMPLIED>
<!ATTLIST game isbios (yes|no) ""no"">
<!ATTLIST game cloneof CDATA #IMPLIED>
<!ATTLIST game romof CDATA #IMPLIED>
<!ATTLIST game sampleof CDATA #IMPLIED>
<!ATTLIST game board CDATA #IMPLIED>
<!ATTLIST game rebuildto CDATA #IMPLIED>
<!ATTLIST game id CDATA #IMPLIED>
<!ATTLIST game cloneofid CDATA #IMPLIED>
<!ATTLIST game runnable (no|partial|yes) ""no"" #IMPLIED>
<!ELEMENT dir (game*, machine*)>
<!ATTLIST dir name CDATA #REQUIRED>
";
/// <summary>
/// XSD for No-Intro Logiqx-derived DATs
/// </summary>
internal const string NoIntroXSD = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema attributeFormDefault=""unqualified"" elementFormDefault=""qualified"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
<xs:element name=""datafile"">
<xs:complexType>
<xs:sequence>
<xs:element name=""header"">
<xs:complexType>
<xs:sequence>
<xs:element name=""id"" type=""xs:int""/>
<xs:element name=""name"" type=""xs:string""/>
<xs:element name=""description"" type=""xs:string""/>
<xs:element name=""version"" type=""xs:string""/>
<xs:element name=""author"" type=""xs:string""/>
<xs:element name=""homepage"" type=""xs:string""/>
<xs:element name=""url"" type=""xs:string""/>
<xs:element name=""clrmamepro"">
<xs:complexType>
<xs:attribute name=""forcenodump"" default=""obsolete"" use=""optional"">
<xs:simpleType>
<xs:restriction base=""xs:token"">
<xs:enumeration value=""obsolete""/>
<xs:enumeration value=""required""/>
<xs:enumeration value=""ignore""/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name=""header"" type=""xs:string"" use=""optional""/>
</xs:complexType>
</xs:element>
<xs:element name=""romcenter"" minOccurs=""0"">
<xs:complexType>
<xs:attribute name=""plugin"" type=""xs:string"" use=""optional""/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element maxOccurs=""unbounded"" name=""game"">
<xs:complexType>
<xs:sequence>
<xs:element name=""description"" type=""xs:string""/>
<xs:element name=""rom"">
<xs:complexType>
<xs:attribute name=""name"" type=""xs:string"" use=""required""/>
<xs:attribute name=""size"" type=""xs:unsignedInt"" use=""required""/>
<xs:attribute name=""crc"" type=""xs:string"" use=""required""/>
<xs:attribute name=""md5"" type=""xs:string"" use=""required""/>
<xs:attribute name=""sha1"" type=""xs:string"" use=""required""/>
<xs:attribute name=""sha256"" type=""xs:string"" use=""optional""/>
<xs:attribute name=""status"" type=""xs:string"" use=""optional""/>
<xs:attribute name=""serial"" type=""xs:string"" use=""optional""/>
<xs:attribute name=""header"" type=""xs:string"" use=""optional""/>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name=""name"" type=""xs:string"" use=""required""/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
";
#endregion
#region Fields
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Archive,
ItemType.BiosSet,
ItemType.DeviceRef,
ItemType.Disk,
ItemType.Driver,
ItemType.Media,
ItemType.Release,
ItemType.Rom,
ItemType.Sample,
ItemType.SoftwareList,
];
/// <summary>
/// Indicates if game should be used instead of machine
/// </summary>
private readonly bool _useGame;
#endregion
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
/// <param name="useGame">True if the output uses "game", false if the output uses "machine"</param>
public Logiqx(DatFile? datFile, bool useGame) : base(datFile)
{
_useGame = useGame;
if (useGame)
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.LogiqxDeprecated);
else
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.Logiqx);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
#pragma warning disable IDE0010
switch (datItem)
{
case Release release:
if (string.IsNullOrEmpty(release.GetName()))
missingFields.Add(Data.Models.Metadata.Release.NameKey);
if (string.IsNullOrEmpty(release.GetStringFieldValue(Data.Models.Metadata.Release.RegionKey)))
missingFields.Add(Data.Models.Metadata.Release.RegionKey);
break;
case BiosSet biosset:
if (string.IsNullOrEmpty(biosset.GetName()))
missingFields.Add(Data.Models.Metadata.BiosSet.NameKey);
if (string.IsNullOrEmpty(biosset.GetStringFieldValue(Data.Models.Metadata.BiosSet.DescriptionKey)))
missingFields.Add(Data.Models.Metadata.BiosSet.DescriptionKey);
break;
case Rom rom:
if (string.IsNullOrEmpty(rom.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
if (rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) is null || rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) < 0)
missingFields.Add(Data.Models.Metadata.Rom.SizeKey);
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey))
&& string.IsNullOrEmpty(rom.GetStringFieldValue("MD2"))
&& string.IsNullOrEmpty(rom.GetStringFieldValue("MD4"))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA384Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA512Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SpamSumKey)))
{
missingFields.Add(Data.Models.Metadata.Rom.SHA1Key);
}
break;
case Disk disk:
if (string.IsNullOrEmpty(disk.GetName()))
missingFields.Add(Data.Models.Metadata.Disk.NameKey);
if (string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.MD5Key))
&& string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.SHA1Key)))
{
missingFields.Add(Data.Models.Metadata.Disk.SHA1Key);
}
break;
case Media media:
if (string.IsNullOrEmpty(media.GetName()))
missingFields.Add(Data.Models.Metadata.Media.NameKey);
if (string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.MD5Key))
&& string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SHA1Key))
&& string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SHA256Key))
&& string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SpamSumKey)))
{
missingFields.Add(Data.Models.Metadata.Media.SHA1Key);
}
break;
case DeviceRef deviceref:
if (string.IsNullOrEmpty(deviceref.GetName()))
missingFields.Add(Data.Models.Metadata.DeviceRef.NameKey);
break;
case Sample sample:
if (string.IsNullOrEmpty(sample.GetName()))
missingFields.Add(Data.Models.Metadata.Sample.NameKey);
break;
case Archive archive:
if (string.IsNullOrEmpty(archive.GetName()))
missingFields.Add(Data.Models.Metadata.Archive.NameKey);
break;
case Driver driver:
if (driver.GetStringFieldValue(Data.Models.Metadata.Driver.StatusKey).AsSupportStatus() == SupportStatus.NULL)
missingFields.Add(Data.Models.Metadata.Driver.StatusKey);
if (driver.GetStringFieldValue(Data.Models.Metadata.Driver.EmulationKey).AsSupportStatus() == SupportStatus.NULL)
missingFields.Add(Data.Models.Metadata.Driver.EmulationKey);
if (driver.GetStringFieldValue(Data.Models.Metadata.Driver.CocktailKey).AsSupportStatus() == SupportStatus.NULL)
missingFields.Add(Data.Models.Metadata.Driver.CocktailKey);
if (driver.GetStringFieldValue(Data.Models.Metadata.Driver.SaveStateKey).AsSupportStatus() == SupportStatus.NULL)
missingFields.Add(Data.Models.Metadata.Driver.SaveStateKey);
break;
case DatItems.Formats.SoftwareList softwarelist:
if (string.IsNullOrEmpty(softwarelist.GetStringFieldValue(Data.Models.Metadata.SoftwareList.TagKey)))
missingFields.Add(Data.Models.Metadata.SoftwareList.TagKey);
if (string.IsNullOrEmpty(softwarelist.GetName()))
missingFields.Add(Data.Models.Metadata.SoftwareList.NameKey);
if (softwarelist.GetStringFieldValue(Data.Models.Metadata.SoftwareList.StatusKey).AsSoftwareListStatus() == SoftwareListStatus.None)
missingFields.Add(Data.Models.Metadata.SoftwareList.StatusKey);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
/// <inheritdoc/>
public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false)
{
try
{
_logger.User($"Writing to '{outfile}'...");
// Serialize the input file
var metadata = ConvertToMetadata(ignoreblanks);
var datafile = new Serialization.CrossModel.Logiqx().Deserialize(metadata, _useGame);
// TODO: Reenable doctype writing
// Only write the doctype if we don't have No-Intro data
bool success;
if (string.IsNullOrEmpty(Header.GetStringFieldValue(Data.Models.Metadata.Header.IdKey)))
success = new Serialization.Writers.Logiqx().Serialize(datafile, outfile, null, null, null, null);
else
success = new Serialization.Writers.Logiqx().Serialize(datafile, outfile, null, null, null, null);
if (!success)
{
_logger.Warning($"File '{outfile}' could not be written! See the log for more details.");
return false;
}
}
catch (Exception ex) when (!throwOnError)
{
_logger.Error(ex);
return false;
}
_logger.User($"'{outfile}' written!{Environment.NewLine}");
return true;
}
}
}

View File

@@ -0,0 +1,214 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SabreTools.Metadata.Filter;
using SabreTools.Metadata.DatItems;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents a Missfile
/// </summary>
public sealed class Missfile : DatFile
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> Enum.GetValues(typeof(ItemType)) as ItemType[] ?? [];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Missfile(DatFile? datFile) : base(datFile)
{
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.MissFile);
}
/// <inheritdoc/>
/// <remarks>There is no consistent way to parse a missfile</remarks>
public override void ParseFile(string filename,
int indexId,
bool keep,
bool statsOnly = false,
FilterRunner? filterRunner = null,
bool throwOnError = false)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
// TODO: Check required fields
return null;
}
/// <inheritdoc/>
public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false)
{
try
{
_logger.User($"Writing to '{outfile}'...");
FileStream fs = File.Create(outfile);
// If we get back null for some reason, just log and return
if (fs is null)
{
_logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable");
return false;
}
StreamWriter sw = new(fs, new UTF8Encoding(false));
// Write out each of the machines and roms
string? lastgame = null;
// Use a sorted list of games to output
foreach (string key in Items.SortedKeys)
{
List<DatItem> datItems = GetItemsForBucket(key, filter: true);
// If this machine doesn't contain any writable items, skip
if (!ContainsWritable(datItems))
continue;
// Resolve the names in the block
datItems = ResolveNames(datItems);
for (int index = 0; index < datItems.Count; index++)
{
DatItem datItem = datItems[index];
// Check for a "null" item
datItem = ProcessNullifiedItem(datItem);
// Write out the item if we're using machine names or we're not ignoring
if (!Modifiers.UseRomName || !ShouldIgnore(datItem, ignoreblanks))
WriteDatItem(sw, datItem, lastgame);
// Set the new data to compare against
lastgame = datItem.GetMachine()!.GetName();
}
}
_logger.User($"'{outfile}' written!{Environment.NewLine}");
sw.Dispose();
fs.Dispose();
}
catch (Exception ex) when (!throwOnError)
{
_logger.Error(ex);
return false;
}
return true;
}
/// <inheritdoc/>
public override bool WriteToFileDB(string outfile, bool ignoreblanks = false, bool throwOnError = false)
{
try
{
_logger.User($"Writing to '{outfile}'...");
FileStream fs = File.Create(outfile);
// If we get back null for some reason, just log and return
if (fs is null)
{
_logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable");
return false;
}
StreamWriter sw = new(fs, new UTF8Encoding(false));
// Write out each of the machines and roms
string? lastgame = null;
// Use a sorted list of games to output
foreach (string key in ItemsDB.SortedKeys)
{
// If this machine doesn't contain any writable items, skip
var itemsDict = GetItemsForBucketDB(key, filter: true);
if (itemsDict is null || !ContainsWritable([.. itemsDict.Values]))
continue;
// Resolve the names in the block
var items = ResolveNamesDB([.. itemsDict]);
foreach (var kvp in items)
{
// Check for a "null" item
var datItem = new KeyValuePair<long, DatItem>(kvp.Key, ProcessNullifiedItem(kvp.Value));
// Get the machine for the item
var machine = GetMachineForItemDB(datItem.Key);
// Write out the item if we're using machine names or we're not ignoring
if (!Modifiers.UseRomName || !ShouldIgnore(datItem.Value, ignoreblanks))
WriteDatItemDB(sw, datItem, lastgame);
// Set the new data to compare against
lastgame = machine.Value!.GetName();
}
}
_logger.User($"'{outfile}' written!{Environment.NewLine}");
sw.Dispose();
fs.Dispose();
}
catch (Exception ex) when (!throwOnError)
{
_logger.Error(ex);
return false;
}
return true;
}
/// <summary>
/// Write out DatItem using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="datItem">DatItem object to be output</param>
/// <param name="lastgame">The name of the last game to be output</param>
private void WriteDatItem(StreamWriter sw, DatItem datItem, string? lastgame)
{
var machine = datItem.GetMachine();
WriteDatItemImpl(sw, datItem, machine!, lastgame);
}
/// <summary>
/// Write out DatItem using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="datItem">DatItem object to be output</param>
/// <param name="lastgame">The name of the last game to be output</param>
private void WriteDatItemDB(StreamWriter sw, KeyValuePair<long, DatItem> datItem, string? lastgame)
{
var machine = GetMachineForItemDB(datItem.Key).Value;
WriteDatItemImpl(sw, datItem.Value, machine!, lastgame);
}
/// <summary>
/// Write out DatItem using the supplied StreamWriter
/// </summary>
/// <param name="sw">StreamWriter to output to</param>
/// <param name="datItem">DatItem object to be output</param>
/// <param name="machine">Machine object representing the set the item is in</param>
/// <param name="lastgame">The name of the last game to be output</param>
private void WriteDatItemImpl(StreamWriter sw, DatItem datItem, Machine machine, string? lastgame)
{
// Process the item name
ProcessItemName(datItem, machine, forceRemoveQuotes: false, forceRomName: false);
// Romba mode automatically uses item name
if (Modifiers.OutputDepot?.IsActive == true || Modifiers.UseRomName)
sw.Write($"{datItem.GetName() ?? string.Empty}\n");
else if (!Modifiers.UseRomName && machine!.GetName() != lastgame)
sw.Write($"{machine!.GetName() ?? string.Empty}\n");
sw.Flush();
}
}
}

View File

@@ -0,0 +1,47 @@
using System.Collections.Generic;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents an OfflineList XML DAT
/// </summary>
public sealed class OfflineList : SerializableDatFile<Data.Models.OfflineList.Dat, Serialization.Readers.OfflineList, Serialization.Writers.OfflineList, Serialization.CrossModel.OfflineList>
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public OfflineList(DatFile? datFile) : base(datFile)
{
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.OfflineList);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
#pragma warning disable IDE0010
switch (datItem)
{
case Rom rom:
if (rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) is null || rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) < 0)
missingFields.Add(Data.Models.Metadata.Rom.SizeKey);
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey)))
missingFields.Add(Data.Models.Metadata.Rom.CRCKey);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
}

View File

@@ -0,0 +1,88 @@
using System.Collections.Generic;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents an openMSX softawre list XML DAT
/// </summary>
public sealed class OpenMSX : SerializableDatFile<Data.Models.OpenMSX.SoftwareDb, Serialization.Readers.OpenMSX, Serialization.Writers.OpenMSX, Serialization.CrossModel.OpenMSX>
{
#region Constants
/// <summary>
/// DTD for original openMSX DATs
/// </summary>
internal const string OpenMSXDTD = @"<!ELEMENT softwaredb (person*)>
<!ELEMENT software (title, genmsxid?, system, company,year,country,dump)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT genmsxid (#PCDATA)>
<!ELEMENT system (#PCDATA)>
<!ELEMENT company (#PCDATA)>
<!ELEMENT year (#PCDATA)>
<!ELEMENT country (#PCDATA)>
<!ELEMENT dump (#PCDATA)>
";
internal const string OpenMSXCredits = @"<!-- Credits -->
<![CDATA[
The softwaredb.xml file contains information about rom mapper types
- Copyright 2003 Nicolas Beyaert (Initial Database)
- Copyright 2004-2013 BlueMSX Team
- Copyright 2005-2023 openMSX Team
- Generation MSXIDs by www.generation-msx.nl
- Thanks go out to:
- - Generation MSX/Sylvester for the incredible source of information
- p_gimeno and diedel for their help adding and valdiating ROM additions
- GDX for additional ROM info and validations and corrections
]]>";
#endregion
#region Fields
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Rom,
];
#endregion
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public OpenMSX(DatFile? datFile) : base(datFile)
{
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.OpenMSX);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Rom rom:
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key)))
missingFields.Add(Data.Models.Metadata.Rom.SHA1Key);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
}

View File

@@ -0,0 +1,51 @@
using System.Collections.Generic;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents a RomCenter INI file
/// </summary>
public sealed class RomCenter : SerializableDatFile<Data.Models.RomCenter.MetadataFile, Serialization.Readers.RomCenter, Serialization.Writers.RomCenter, Serialization.CrossModel.RomCenter>
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Rom,
];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public RomCenter(DatFile? datFile) : base(datFile)
{
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.RomCenter);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Rom rom:
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey)))
missingFields.Add(Data.Models.Metadata.Rom.CRCKey);
if (rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) is null || rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) < 0)
missingFields.Add(Data.Models.Metadata.Rom.SizeKey);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
}

View File

@@ -0,0 +1,719 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using SabreTools.Metadata.Filter;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents parsing and writing of a reference SabreDAT JSON
/// </summary>
/// TODO: Transform this into direct serialization and deserialization of the Metadata type
public sealed class SabreJSON : DatFile
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> Enum.GetValues(typeof(ItemType)) as ItemType[] ?? [];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public SabreJSON(DatFile? datFile) : base(datFile)
{
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.SabreJSON);
}
/// <inheritdoc/>
public override void ParseFile(string filename,
int indexId,
bool keep,
bool statsOnly = false,
FilterRunner? filterRunner = null,
bool throwOnError = false)
{
// Prepare all internal variables
var fs = System.IO.File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var sr = new StreamReader(fs, new UTF8Encoding(false));
var jtr = new JsonTextReader(sr);
var source = new Source(indexId, filename);
// long sourceIndex = AddSourceDB(source);
// If we got a null reader, just return
if (jtr is null)
return;
// Otherwise, read the file to the end
try
{
jtr.Read();
while (!sr.EndOfStream)
{
// Skip everything not a property name
if (jtr.TokenType != JsonToken.PropertyName)
{
jtr.Read();
continue;
}
switch (jtr.Value)
{
// Header value
case "header":
ReadHeader(jtr);
jtr.Read();
break;
// Machine array
case "machines":
ReadMachines(jtr, statsOnly, source, sourceIndex: 0, filterRunner);
jtr.Read();
break;
default:
jtr.Read();
break;
}
}
}
catch (Exception ex) when (!throwOnError)
{
_logger.Warning($"Exception found while parsing '{filename}': {ex}");
}
jtr.Close();
}
/// <summary>
/// Read header information
/// </summary>
/// <param name="jtr">JsonTextReader to use to parse the header</param>
private void ReadHeader(JsonTextReader jtr)
{
// If the reader is invalid, skip
if (jtr is null)
return;
// Read in the header and apply any new fields
jtr.Read();
JsonSerializer js = new();
DatHeader? header = js.Deserialize<DatHeader>(jtr);
SetHeader(header);
}
/// <summary>
/// Read machine array information
/// </summary>
/// <param name="jtr">JsonTextReader to use to parse the machine</param>
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
/// <param name="source">Source representing the DAT</param>
/// <param name="sourceIndex">Index of the Source representing the DAT</param>
/// <param name="filterRunner">Optional FilterRunner to filter items on parse</param>
private void ReadMachines(JsonTextReader jtr, bool statsOnly, Source source, long sourceIndex, FilterRunner? filterRunner)
{
// If the reader is invalid, skip
if (jtr is null)
return;
// Read in the machine array
jtr.Read();
var js = new JsonSerializer();
JArray machineArray = js.Deserialize<JArray>(jtr) ?? [];
// Loop through each machine object and process
foreach (JObject machineObj in machineArray.Cast<JObject>())
{
ReadMachine(machineObj, statsOnly, source, sourceIndex, filterRunner);
}
}
/// <summary>
/// Read machine object information
/// </summary>
/// <param name="machineObj">JObject representing a single machine</param>
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
/// <param name="source">Source representing the DAT</param>
/// <param name="sourceIndex">Index of the Source representing the DAT</param>
/// <param name="filterRunner">Optional FilterRunner to filter items on parse</param>
private void ReadMachine(JObject machineObj, bool statsOnly, Source source, long sourceIndex, FilterRunner? filterRunner)
{
// If object is invalid, skip it
if (machineObj is null)
return;
// Prepare internal variables
Machine? machine = null;
// Read the machine info, if possible
if (machineObj.ContainsKey("machine"))
machine = machineObj["machine"]?.ToObject<Machine>();
// If the machine doesn't pass the filter
if (machine is not null && filterRunner is not null && !machine.PassesFilter(filterRunner))
return;
// Add the machine to the dictionary
// long machineIndex = -1;
// if (machine is not null)
// machineIndex = AddMachineDB(machine);
// Read items, if possible
if (machineObj.ContainsKey("items"))
{
ReadItems(machineObj["items"] as JArray,
statsOnly,
source,
sourceIndex,
machine,
machineIndex: 0,
filterRunner);
}
}
/// <summary>
/// Read item array information
/// </summary>
/// <param name="itemsArr">JArray representing the items list</param>
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
/// <param name="source">Source representing the DAT</param>
/// <param name="sourceIndex">Index of the Source representing the DAT</param>
/// <param name="machine">Machine information to add to the parsed items</param>
/// <param name="machineIndex">Index of the Machine to add to the parsed items</param>
/// <param name="filterRunner">Optional FilterRunner to filter items on parse</param>
private void ReadItems(
JArray? itemsArr,
bool statsOnly,
// Standard Dat parsing
Source source,
long sourceIndex,
// Miscellaneous
Machine? machine,
long machineIndex,
FilterRunner? filterRunner)
{
// If the array is invalid, skip
if (itemsArr is null)
return;
// Loop through each datitem object and process
foreach (JObject itemObj in itemsArr.Cast<JObject>())
{
ReadItem(itemObj, statsOnly, source, sourceIndex, machine, machineIndex, filterRunner);
}
}
/// <summary>
/// Read item information
/// </summary>
/// <param name="itemObj">JObject representing a single datitem</param>
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
/// <param name="source">Source representing the DAT</param>
/// <param name="sourceIndex">Index of the Source representing the DAT</param>
/// <param name="machine">Machine information to add to the parsed items</param>
/// <param name="machineIndex">Index of the Machine to add to the parsed items</param>
/// <param name="filterRunner">Optional FilterRunner to filter items on parse</param>
private void ReadItem(
JObject itemObj,
bool statsOnly,
// Standard Dat parsing
Source source,
long sourceIndex,
// Miscellaneous
Machine? machine,
long machineIndex,
FilterRunner? filterRunner)
{
// If we have an empty item, skip it
if (itemObj is null)
return;
// Prepare internal variables
DatItem? datItem = null;
// Read the datitem info, if possible
if (itemObj.ContainsKey("datitem"))
{
JToken? datItemObj = itemObj["datitem"];
if (datItemObj is null)
return;
#pragma warning disable IDE0010
switch (datItemObj.Value<string>("type").AsItemType())
{
case ItemType.Adjuster:
datItem = datItemObj.ToObject<Adjuster>();
break;
case ItemType.Analog:
datItem = datItemObj.ToObject<Analog>();
break;
case ItemType.Archive:
datItem = datItemObj.ToObject<Archive>();
break;
case ItemType.BiosSet:
datItem = datItemObj.ToObject<BiosSet>();
break;
case ItemType.Blank:
datItem = datItemObj.ToObject<Blank>();
break;
case ItemType.Chip:
datItem = datItemObj.ToObject<Chip>();
break;
case ItemType.Condition:
datItem = datItemObj.ToObject<Condition>();
break;
case ItemType.Configuration:
datItem = datItemObj.ToObject<Configuration>();
break;
case ItemType.ConfLocation:
datItem = datItemObj.ToObject<ConfLocation>();
break;
case ItemType.ConfSetting:
datItem = datItemObj.ToObject<ConfSetting>();
break;
case ItemType.Control:
datItem = datItemObj.ToObject<Control>();
break;
case ItemType.DataArea:
datItem = datItemObj.ToObject<DataArea>();
break;
case ItemType.Device:
datItem = datItemObj.ToObject<Device>();
break;
case ItemType.DeviceRef:
datItem = datItemObj.ToObject<DeviceRef>();
break;
case ItemType.DipLocation:
datItem = datItemObj.ToObject<DipLocation>();
break;
case ItemType.DipValue:
datItem = datItemObj.ToObject<DipValue>();
break;
case ItemType.DipSwitch:
datItem = datItemObj.ToObject<DipSwitch>();
break;
case ItemType.Disk:
datItem = datItemObj.ToObject<Disk>();
break;
case ItemType.DiskArea:
datItem = datItemObj.ToObject<DiskArea>();
break;
case ItemType.Display:
datItem = datItemObj.ToObject<Display>();
break;
case ItemType.Driver:
datItem = datItemObj.ToObject<Driver>();
break;
case ItemType.Extension:
datItem = datItemObj.ToObject<Extension>();
break;
case ItemType.Feature:
datItem = datItemObj.ToObject<Feature>();
break;
case ItemType.Info:
datItem = datItemObj.ToObject<Info>();
break;
case ItemType.Input:
datItem = datItemObj.ToObject<Input>();
break;
case ItemType.Instance:
datItem = datItemObj.ToObject<Instance>();
break;
case ItemType.Media:
datItem = datItemObj.ToObject<Media>();
break;
case ItemType.Part:
datItem = datItemObj.ToObject<Part>();
break;
case ItemType.PartFeature:
datItem = datItemObj.ToObject<PartFeature>();
break;
case ItemType.Port:
datItem = datItemObj.ToObject<Port>();
break;
case ItemType.RamOption:
datItem = datItemObj.ToObject<RamOption>();
break;
case ItemType.Release:
datItem = datItemObj.ToObject<Release>();
break;
case ItemType.ReleaseDetails:
datItem = datItemObj.ToObject<ReleaseDetails>();
break;
case ItemType.Rom:
datItem = datItemObj.ToObject<Rom>();
break;
case ItemType.Sample:
datItem = datItemObj.ToObject<Sample>();
break;
case ItemType.Serials:
datItem = datItemObj.ToObject<Serials>();
break;
case ItemType.SharedFeat:
datItem = datItemObj.ToObject<SharedFeat>();
break;
case ItemType.Slot:
datItem = datItemObj.ToObject<Slot>();
break;
case ItemType.SlotOption:
datItem = datItemObj.ToObject<SlotOption>();
break;
case ItemType.SoftwareList:
datItem = datItemObj.ToObject<DatItems.Formats.SoftwareList>();
break;
case ItemType.Sound:
datItem = datItemObj.ToObject<Sound>();
break;
case ItemType.SourceDetails:
datItem = datItemObj.ToObject<SourceDetails>();
break;
}
#pragma warning restore IDE0010
}
// If we got a valid datitem, copy machine info and add
if (datItem is not null)
{
// If the item doesn't pass the filter
if (filterRunner is not null && !datItem.PassesFilter(filterRunner))
return;
datItem.CopyMachineInformation(machine);
datItem.SetFieldValue<Source?>(DatItem.SourceKey, source);
AddItem(datItem, statsOnly);
AddItemDB(datItem, machineIndex, sourceIndex, statsOnly);
}
}
/// <inheritdoc/>
public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false)
{
try
{
_logger.User($"Writing to '{outfile}'...");
FileStream fs = System.IO.File.Create(outfile);
// If we get back null for some reason, just log and return
if (fs is null)
{
_logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable");
return false;
}
StreamWriter sw = new(fs, new UTF8Encoding(false));
JsonTextWriter jtw = new(sw)
{
Formatting = Formatting.Indented,
IndentChar = '\t',
Indentation = 1
};
// Write out the header
WriteHeader(jtw);
// Write out each of the machines and roms
string? lastgame = null;
// Use a sorted list of games to output
foreach (string key in Items.SortedKeys)
{
List<DatItem> datItems = GetItemsForBucket(key, filter: true);
// If this machine doesn't contain any writable items, skip
if (!ContainsWritable(datItems))
continue;
// Resolve the names in the block
datItems = ResolveNames(datItems);
for (int index = 0; index < datItems.Count; index++)
{
DatItem datItem = datItems[index];
// If we have a different game and we're not at the start of the list, output the end of last item
if (lastgame is not null && !string.Equals(lastgame, datItem.GetMachine()!.GetName(), StringComparison.OrdinalIgnoreCase))
WriteEndGame(jtw);
// If we have a new game, output the beginning of the new item
if (lastgame is null || !string.Equals(lastgame, datItem.GetMachine()!.GetName(), StringComparison.OrdinalIgnoreCase))
WriteStartGame(jtw, datItem);
// Check for a "null" item
datItem = ProcessNullifiedItem(datItem);
// Write out the item if we're not ignoring
if (!ShouldIgnore(datItem, ignoreblanks))
WriteDatItem(jtw, datItem);
// Set the new data to compare against
lastgame = datItem.GetMachine()!.GetName();
}
}
// Write the file footer out
WriteFooter(jtw);
_logger.User($"'{outfile}' written!{Environment.NewLine}");
jtw.Close();
fs.Dispose();
}
catch (Exception ex) when (!throwOnError)
{
_logger.Error(ex);
return false;
}
return true;
}
/// <inheritdoc/>
public override bool WriteToFileDB(string outfile, bool ignoreblanks = false, bool throwOnError = false)
{
try
{
_logger.User($"Writing to '{outfile}'...");
FileStream fs = System.IO.File.Create(outfile);
// If we get back null for some reason, just log and return
if (fs is null)
{
_logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable");
return false;
}
StreamWriter sw = new(fs, new UTF8Encoding(false));
JsonTextWriter jtw = new(sw)
{
Formatting = Formatting.Indented,
IndentChar = '\t',
Indentation = 1
};
// Write out the header
WriteHeader(jtw);
// Write out each of the machines and roms
string? lastgame = null;
// Use a sorted list of games to output
foreach (string key in ItemsDB.SortedKeys)
{
// If this machine doesn't contain any writable items, skip
var itemsDict = GetItemsForBucketDB(key, filter: true);
if (itemsDict is null || !ContainsWritable([.. itemsDict.Values]))
continue;
// Resolve the names in the block
var items = ResolveNamesDB([.. itemsDict]);
foreach (var kvp in items)
{
// Get the machine for the item
var machine = GetMachineForItemDB(kvp.Key);
// If we have a different game and we're not at the start of the list, output the end of last item
if (lastgame is not null && !string.Equals(lastgame, machine.Value!.GetName(), StringComparison.OrdinalIgnoreCase))
WriteEndGame(jtw);
// If we have a new game, output the beginning of the new item
if (lastgame is null || !string.Equals(lastgame, machine.Value!.GetName(), StringComparison.OrdinalIgnoreCase))
WriteStartGame(jtw, kvp.Value);
// Check for a "null" item
var datItem = new KeyValuePair<long, DatItem>(kvp.Key, ProcessNullifiedItem(kvp.Value));
// Write out the item if we're not ignoring
if (!ShouldIgnore(datItem.Value, ignoreblanks))
WriteDatItemDB(jtw, datItem);
// Set the new data to compare against
lastgame = machine.Value!.GetName();
}
}
// Write the file footer out
WriteFooter(jtw);
_logger.User($"'{outfile}' written!{Environment.NewLine}");
jtw.Close();
fs.Dispose();
}
catch (Exception ex) when (!throwOnError)
{
_logger.Error(ex);
return false;
}
return true;
}
/// <summary>
/// Write out DAT header using the supplied JsonTextWriter
/// </summary>
/// <param name="jtw">JsonTextWriter to output to</param>
private void WriteHeader(JsonTextWriter jtw)
{
jtw.WriteStartObject();
// Write the DatHeader
jtw.WritePropertyName("header");
JsonSerializer js = new() { Formatting = Formatting.Indented };
js.Serialize(jtw, Header);
jtw.WritePropertyName("machines");
jtw.WriteStartArray();
jtw.Flush();
}
/// <summary>
/// Write out Game start using the supplied JsonTextWriter
/// </summary>
/// <param name="jtw">JsonTextWriter to output to</param>
/// <param name="datItem">DatItem object to be output</param>
private static void WriteStartGame(JsonTextWriter jtw, DatItem datItem)
{
// No game should start with a path separator
if (!string.IsNullOrEmpty(datItem.GetMachine()!.GetName()))
datItem.GetMachine()!.SetName(datItem.GetMachine()!.GetName()!.TrimStart(Path.DirectorySeparatorChar));
// Build the state
jtw.WriteStartObject();
// Write the Machine
jtw.WritePropertyName("machine");
JsonSerializer js = new() { Formatting = Formatting.Indented };
js.Serialize(jtw, datItem.GetMachine()!);
jtw.WritePropertyName("items");
jtw.WriteStartArray();
jtw.Flush();
}
/// <summary>
/// Write out Game end using the supplied JsonTextWriter
/// </summary>
/// <param name="jtw">JsonTextWriter to output to</param>
private static void WriteEndGame(JsonTextWriter jtw)
{
// End items
jtw.WriteEndArray();
// End machine
jtw.WriteEndObject();
jtw.Flush();
}
/// <summary>
/// Write out DatItem using the supplied JsonTextWriter
/// </summary>
/// <param name="jtw">JsonTextWriter to output to</param>
/// <param name="datItem">DatItem object to be output</param>
private void WriteDatItem(JsonTextWriter jtw, DatItem datItem)
{
// Get the machine for the item
var machine = datItem.GetMachine();
// Pre-process the item name
ProcessItemName(datItem, machine, forceRemoveQuotes: true, forceRomName: false);
// Build the state
jtw.WriteStartObject();
// Write the DatItem
jtw.WritePropertyName("datitem");
JsonSerializer js = new() { ContractResolver = new BaseFirstContractResolver(), Formatting = Formatting.Indented };
js.Serialize(jtw, datItem);
// End item
jtw.WriteEndObject();
jtw.Flush();
}
/// <summary>
/// Write out DatItem using the supplied JsonTextWriter
/// </summary>
/// <param name="jtw">JsonTextWriter to output to</param>
/// <param name="datItem">DatItem object to be output</param>
private void WriteDatItemDB(JsonTextWriter jtw, KeyValuePair<long, DatItem> datItem)
{
// Get the machine for the item
var machine = GetMachineForItemDB(datItem.Key);
// Pre-process the item name
ProcessItemName(datItem.Value, machine.Value, forceRemoveQuotes: true, forceRomName: false);
// Build the state
jtw.WriteStartObject();
// Write the DatItem
jtw.WritePropertyName("datitem");
JsonSerializer js = new() { ContractResolver = new BaseFirstContractResolver(), Formatting = Formatting.Indented };
js.Serialize(jtw, datItem);
// End item
jtw.WriteEndObject();
jtw.Flush();
}
/// <summary>
/// Write out DAT footer using the supplied JsonTextWriter
/// </summary>
/// <param name="jtw">JsonTextWriter to output to</param>
private static void WriteFooter(JsonTextWriter jtw)
{
// End items
jtw.WriteEndArray();
// End machine
jtw.WriteEndObject();
// End machines
jtw.WriteEndArray();
// End file
jtw.WriteEndObject();
jtw.Flush();
}
// https://github.com/dotnet/runtime/issues/728
private class BaseFirstContractResolver : DefaultContractResolver
{
public BaseFirstContractResolver()
{
NamingStrategy = new CamelCaseNamingStrategy();
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
return [.. base.CreateProperties(type, memberSerialization)
.Where(p => p is not null)
.OrderBy(p => BaseTypesAndSelf(p.DeclaringType).Count())];
static IEnumerable<Type?> BaseTypesAndSelf(Type? t)
{
while (t is not null)
{
yield return t;
t = t.BaseType;
}
}
}
}
}
}

View File

@@ -0,0 +1,522 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using SabreTools.Metadata.Filter;
using SabreTools.Metadata.DatItems;
#pragma warning disable IDE0060 // Remove unused parameter
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents parsing and writing of a SabreDAT XML
/// </summary>
/// TODO: Transform this into direct serialization and deserialization of the Metadata type
public sealed class SabreXML : DatFile
{
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> Enum.GetValues(typeof(ItemType)) as ItemType[] ?? [];
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public SabreXML(DatFile? datFile) : base(datFile)
{
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.SabreXML);
}
/// <inheritdoc/>
public override void ParseFile(string filename,
int indexId,
bool keep,
bool statsOnly = false,
FilterRunner? filterRunner = null,
bool throwOnError = false)
{
// Prepare all internal variables
XmlReader? xtr = XmlReader.Create(filename, new XmlReaderSettings
{
CheckCharacters = false,
#if NET40_OR_GREATER
DtdProcessing = DtdProcessing.Ignore,
#endif
IgnoreComments = true,
IgnoreWhitespace = true,
ValidationFlags = XmlSchemaValidationFlags.None,
ValidationType = ValidationType.None,
});
var source = new Source(indexId, filename);
long sourceIndex = AddSourceDB(source);
// If we got a null reader, just return
if (xtr is null)
return;
// Otherwise, read the file to the end
try
{
xtr.MoveToContent();
while (!xtr.EOF)
{
// We only want elements
if (xtr.NodeType != XmlNodeType.Element)
{
xtr.Read();
continue;
}
switch (xtr.Name)
{
case "header":
XmlSerializer xs = new(typeof(DatHeader));
DatHeader? header = xs.Deserialize(xtr.ReadSubtree()) as DatHeader;
SetHeader(header);
xtr.Skip();
break;
case "directory":
ReadDirectory(xtr.ReadSubtree(), statsOnly, source, sourceIndex, filterRunner);
// Skip the directory node now that we've processed it
xtr.Read();
break;
default:
xtr.Read();
break;
}
}
}
catch (Exception ex) when (!throwOnError)
{
_logger.Warning(ex, $"Exception found while parsing '{filename}'");
// For XML errors, just skip the affected node
xtr?.Read();
}
#if NET452_OR_GREATER
xtr?.Dispose();
#endif
}
/// <summary>
/// Read directory information
/// </summary>
/// <param name="xtr">XmlReader to use to parse the header</param>
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
/// <param name="source">Source representing the DAT</param>
/// <param name="sourceIndex">Index of the Source representing the DAT</param>
/// <param name="filterRunner">Optional FilterRunner to filter items on parse</param>
private void ReadDirectory(XmlReader xtr,
bool statsOnly,
Source source,
long sourceIndex,
FilterRunner? filterRunner)
{
// If the reader is invalid, skip
if (xtr is null)
return;
// Prepare internal variables
Machine? machine = null;
long machineIndex = -1;
// Otherwise, read the directory
xtr.MoveToContent();
while (!xtr.EOF)
{
// We only want elements
if (xtr.NodeType != XmlNodeType.Element)
{
xtr.Read();
continue;
}
switch (xtr.Name)
{
case "machine":
XmlSerializer xs = new(typeof(Machine));
machine = xs?.Deserialize(xtr.ReadSubtree()) as Machine;
// If the machine doesn't pass the filter
if (machine is not null && filterRunner is not null && !machine.PassesFilter(filterRunner))
machine = null;
if (machine is not null)
machineIndex = AddMachineDB(machine);
xtr.Skip();
break;
case "files":
ReadFiles(xtr.ReadSubtree(),
machine,
machineIndex,
statsOnly,
source,
sourceIndex,
filterRunner);
// Skip the directory node now that we've processed it
xtr.Read();
break;
default:
xtr.Read();
break;
}
}
}
/// <summary>
/// Read Files information
/// </summary>
/// <param name="xtr">XmlReader to use to parse the header</param>
/// <param name="machine">Machine to copy information from</param>
/// <param name="machineIndex">Index of the Machine to add to the parsed items</param>
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
/// <param name="source">Source representing the DAT</param>
/// <param name="sourceIndex">Index of the Source representing the DAT</param>
/// <param name="filterRunner">Optional FilterRunner to filter items on parse</param>
private void ReadFiles(XmlReader xtr,
Machine? machine,
long machineIndex,
bool statsOnly,
Source source,
long sourceIndex,
FilterRunner? filterRunner)
{
// If the reader is invalid, skip
if (xtr is null)
return;
// Otherwise, read the items
xtr.MoveToContent();
while (!xtr.EOF)
{
// We only want elements
if (xtr.NodeType != XmlNodeType.Element)
{
xtr.Read();
continue;
}
switch (xtr.Name)
{
case "datitem":
XmlSerializer xs = new(typeof(DatItem));
if (xs.Deserialize(xtr.ReadSubtree()) is DatItem item)
{
// If the item doesn't pass the filter
if (filterRunner is not null && !item.PassesFilter(filterRunner))
{
xtr.Skip();
break;
}
item.CopyMachineInformation(machine);
item.SetFieldValue<Source?>(DatItem.SourceKey, source);
AddItem(item, statsOnly);
// AddItemDB(item, machineIndex, sourceIndex, statsOnly);
}
xtr.Skip();
break;
default:
xtr.Read();
break;
}
}
}
/// <inheritdoc/>
public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false)
{
try
{
_logger.User($"Writing to '{outfile}'...");
FileStream fs = File.Create(outfile);
// If we get back null for some reason, just log and return
if (fs is null)
{
_logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable");
return false;
}
XmlTextWriter xtw = new(fs, new UTF8Encoding(false))
{
Formatting = Formatting.Indented,
IndentChar = '\t',
Indentation = 1,
};
// Write out the header
WriteHeader(xtw);
// Write out each of the machines and roms
string? lastgame = null;
// Use a sorted list of games to output
foreach (string key in Items.SortedKeys)
{
List<DatItem> datItems = GetItemsForBucket(key, filter: true);
// If this machine doesn't contain any writable items, skip
if (!ContainsWritable(datItems))
continue;
// Resolve the names in the block
datItems = ResolveNames(datItems);
for (int index = 0; index < datItems.Count; index++)
{
DatItem datItem = datItems[index];
// If we have a different game and we're not at the start of the list, output the end of last item
if (lastgame is not null && !string.Equals(lastgame, datItem.GetMachine()!.GetName(), StringComparison.OrdinalIgnoreCase))
WriteEndGame(xtw);
// If we have a new game, output the beginning of the new item
if (lastgame is null || !string.Equals(lastgame, datItem.GetMachine()!.GetName(), StringComparison.OrdinalIgnoreCase))
WriteStartGame(xtw, datItem);
// Check for a "null" item
datItem = ProcessNullifiedItem(datItem);
// Write out the item if we're not ignoring
if (!ShouldIgnore(datItem, ignoreblanks))
WriteDatItem(xtw, datItem);
// Set the new data to compare against
lastgame = datItem.GetMachine()!.GetName();
}
}
// Write the file footer out
WriteFooter(xtw);
_logger.User($"'{outfile}' written!{Environment.NewLine}");
#if NET452_OR_GREATER
xtw.Dispose();
#endif
fs.Dispose();
}
catch (Exception ex) when (!throwOnError)
{
_logger.Error(ex);
return false;
}
return true;
}
/// <inheritdoc/>
public override bool WriteToFileDB(string outfile, bool ignoreblanks = false, bool throwOnError = false)
{
try
{
_logger.User($"Writing to '{outfile}'...");
FileStream fs = File.Create(outfile);
// If we get back null for some reason, just log and return
if (fs is null)
{
_logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable");
return false;
}
XmlTextWriter xtw = new(fs, new UTF8Encoding(false))
{
Formatting = Formatting.Indented,
IndentChar = '\t',
Indentation = 1,
};
// Write out the header
WriteHeader(xtw);
// Write out each of the machines and roms
string? lastgame = null;
// Use a sorted list of games to output
foreach (string key in ItemsDB.SortedKeys)
{
// If this machine doesn't contain any writable items, skip
var itemsDict = GetItemsForBucketDB(key, filter: true);
if (itemsDict is null || !ContainsWritable([.. itemsDict.Values]))
continue;
// Resolve the names in the block
var items = ResolveNamesDB([.. itemsDict]);
foreach (var kvp in items)
{
// Get the machine for the item
var machine = GetMachineForItemDB(kvp.Key);
// If we have a different game and we're not at the start of the list, output the end of last item
if (lastgame is not null && !string.Equals(lastgame, machine.Value!.GetName(), StringComparison.OrdinalIgnoreCase))
WriteEndGame(xtw);
// If we have a new game, output the beginning of the new item
if (lastgame is null || !string.Equals(lastgame, machine.Value!.GetName(), StringComparison.OrdinalIgnoreCase))
WriteStartGame(xtw, kvp.Value);
// Check for a "null" item
var datItem = new KeyValuePair<long, DatItem>(kvp.Key, ProcessNullifiedItem(kvp.Value));
// Write out the item if we're not ignoring
if (!ShouldIgnore(datItem.Value, ignoreblanks))
WriteDatItemDB(xtw, datItem);
// Set the new data to compare against
lastgame = machine.Value!.GetName();
}
}
// Write the file footer out
WriteFooter(xtw);
_logger.User($"'{outfile}' written!{Environment.NewLine}");
#if NET452_OR_GREATER
xtw.Dispose();
#endif
fs.Dispose();
}
catch (Exception ex) when (!throwOnError)
{
_logger.Error(ex);
return false;
}
return true;
}
/// <summary>
/// Write out DAT header using the supplied StreamWriter
/// </summary>
/// <param name="xtw">XmlTextWriter to output to</param>
private void WriteHeader(XmlTextWriter xtw)
{
xtw.WriteStartDocument();
xtw.WriteStartElement("datafile");
XmlSerializer xs = new(typeof(DatHeader));
XmlSerializerNamespaces ns = new();
ns.Add("", "");
xs.Serialize(xtw, Header, ns);
xtw.WriteStartElement("data");
xtw.Flush();
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="xtw">XmlTextWriter to output to</param>
/// <param name="datItem">DatItem object to be output</param>
private static void WriteStartGame(XmlTextWriter xtw, DatItem datItem)
{
// No game should start with a path separator
datItem.GetMachine()!.SetName(datItem.GetMachine()!.GetName()?.TrimStart(Path.DirectorySeparatorChar) ?? string.Empty);
// Write the machine
xtw.WriteStartElement("directory");
XmlSerializer xs = new(typeof(Machine));
XmlSerializerNamespaces ns = new();
ns.Add("", "");
xs.Serialize(xtw, datItem.GetMachine(), ns);
xtw.WriteStartElement("files");
xtw.Flush();
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="xtw">XmlTextWriter to output to</param>
private static void WriteEndGame(XmlTextWriter xtw)
{
// End files
xtw.WriteEndElement();
// End directory
xtw.WriteEndElement();
xtw.Flush();
}
/// <summary>
/// Write out DatItem using the supplied StreamWriter
/// </summary>
/// <param name="xtw">XmlTextWriter to output to</param>
/// <param name="datItem">DatItem object to be output</param>
private void WriteDatItem(XmlTextWriter xtw, DatItem datItem)
{
// Get the machine for the item
var machine = datItem.GetMachine();
// Pre-process the item name
ProcessItemName(datItem, machine, forceRemoveQuotes: true, forceRomName: false);
// Write the DatItem
XmlSerializer xs = new(typeof(DatItem));
XmlSerializerNamespaces ns = new();
ns.Add("", "");
xs.Serialize(xtw, datItem, ns);
xtw.Flush();
}
/// <summary>
/// Write out DatItem using the supplied StreamWriter
/// </summary>
/// <param name="xtw">XmlTextWriter to output to</param>
/// <param name="datItem">DatItem object to be output</param>
private void WriteDatItemDB(XmlTextWriter xtw, KeyValuePair<long, DatItem> datItem)
{
// Get the machine for the item
var machine = GetMachineForItemDB(datItem.Key);
// Pre-process the item name
ProcessItemName(datItem.Value, machine.Value, forceRemoveQuotes: true, forceRomName: false);
// Write the DatItem
XmlSerializer xs = new(typeof(DatItem));
XmlSerializerNamespaces ns = new();
ns.Add("", "");
xs.Serialize(xtw, datItem, ns);
xtw.Flush();
}
/// <summary>
/// Write out DAT footer using the supplied StreamWriter
/// </summary>
/// <param name="xtw">XmlTextWriter to output to</param>
private static void WriteFooter(XmlTextWriter xtw)
{
// End files
xtw.WriteEndElement();
// End directory
xtw.WriteEndElement();
// End data
xtw.WriteEndElement();
// End datafile
xtw.WriteEndElement();
xtw.Flush();
}
}
}

View File

@@ -0,0 +1,191 @@
using System;
using System.Collections.Generic;
using SabreTools.Metadata.Filter;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
#pragma warning disable IDE0290 // Use primary constructor
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents a value-separated DAT
/// </summary>
public abstract class SeparatedValue : SerializableDatFile<Data.Models.SeparatedValue.MetadataFile, Serialization.Readers.SeparatedValue, Serialization.Writers.SeparatedValue, Serialization.CrossModel.SeparatedValue>
{
#region Fields
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.Disk,
ItemType.Media,
ItemType.Rom,
];
/// <summary>
/// Represents the delimiter between fields
/// </summary>
protected char _delim;
#endregion
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public SeparatedValue(DatFile? datFile) : base(datFile)
{
}
/// <inheritdoc/>
public override void ParseFile(string filename,
int indexId,
bool keep,
bool statsOnly = false,
FilterRunner? filterRunner = null,
bool throwOnError = false)
{
try
{
// Deserialize the input file
var metadataFile = new Serialization.Readers.SeparatedValue().Deserialize(filename, _delim);
var metadata = new Serialization.CrossModel.SeparatedValue().Serialize(metadataFile);
// Convert to the internal format
ConvertFromMetadata(metadata, filename, indexId, keep, statsOnly, filterRunner);
}
catch (Exception ex) when (!throwOnError)
{
string message = $"'{filename}' - An error occurred during parsing";
_logger.Error(ex, message);
}
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
// Check item name
if (string.IsNullOrEmpty(datItem.GetName()))
missingFields.Add(Data.Models.Metadata.Rom.NameKey);
#pragma warning disable IDE0010
switch (datItem)
{
case Disk disk:
if (string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.MD5Key))
&& string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.SHA1Key)))
{
missingFields.Add(Data.Models.Metadata.Disk.SHA1Key);
}
break;
case Media media:
if (string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.MD5Key))
&& string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SHA1Key))
&& string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SHA256Key))
&& string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SpamSumKey)))
{
missingFields.Add(Data.Models.Metadata.Media.SHA1Key);
}
break;
case Rom rom:
if (rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) is null || rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey) < 0)
missingFields.Add(Data.Models.Metadata.Rom.SizeKey);
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA384Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA512Key))
&& string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SpamSumKey)))
{
missingFields.Add(Data.Models.Metadata.Rom.SHA1Key);
}
break;
}
#pragma warning restore IDE0010
return missingFields;
}
/// <inheritdoc/>
public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false)
{
try
{
_logger.User($"Writing to '{outfile}'...");
// Serialize the input file
var metadata = ConvertToMetadata(ignoreblanks);
var metadataFile = new Serialization.CrossModel.SeparatedValue().Deserialize(metadata);
if (!new Serialization.Writers.SeparatedValue().SerializeFile(metadataFile, outfile, _delim, longHeader: false))
{
_logger.Warning($"File '{outfile}' could not be written! See the log for more details.");
return false;
}
}
catch (Exception ex) when (!throwOnError)
{
_logger.Error(ex);
return false;
}
_logger.User($"'{outfile}' written!{Environment.NewLine}");
return true;
}
}
/// <summary>
/// Represents a comma-separated value file
/// </summary>
public sealed class CommaSeparatedValue : SeparatedValue
{
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public CommaSeparatedValue(DatFile? datFile) : base(datFile)
{
_delim = ',';
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.CSV);
}
}
/// <summary>
/// Represents a semicolon-separated value file
/// </summary>
public sealed class SemicolonSeparatedValue : SeparatedValue
{
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public SemicolonSeparatedValue(DatFile? datFile) : base(datFile)
{
_delim = ';';
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.SSV);
}
}
/// <summary>
/// Represents a tab-separated value file
/// </summary>
public sealed class TabSeparatedValue : SeparatedValue
{
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public TabSeparatedValue(DatFile? datFile) : base(datFile)
{
_delim = '\t';
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.TSV);
}
}
}

View File

@@ -0,0 +1,121 @@
using System;
using SabreTools.Metadata.Filter;
using SabreTools.Data.Models.Metadata;
using SabreTools.Serialization.CrossModel;
using SabreTools.Serialization.Readers;
using SabreTools.Serialization.Writers;
#pragma warning disable IDE0290 // Use primary constructor
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents a DAT that can be serialized
/// </summary>
/// <typeparam name="TModel">Base internal model for the DAT type</typeparam>
/// <typeparam name="TFileReader">IFileReader type to use for conversion</typeparam>
/// <typeparam name="TFileWriter">IFileWriter type to use for conversion</typeparam>
/// <typeparam name="TCrossModel">ICrossModel for cross-model serialization</typeparam>
public abstract class SerializableDatFile<TModel, TFileReader, TFileWriter, TCrossModel> : DatFile
where TFileReader : IFileReader<TModel>
where TFileWriter : IFileWriter<TModel>
where TCrossModel : ICrossModel<TModel, MetadataFile>
{
#region Static Serialization Instances
/// <summary>
/// File deserializer instance
/// </summary>
private static readonly TFileReader FileDeserializer = Activator.CreateInstance<TFileReader>();
/// <summary>
/// File serializer instance
/// </summary>
private static readonly TFileWriter FileSerializer = Activator.CreateInstance<TFileWriter>();
/// <summary>
/// Cross-model serializer instance
/// </summary>
private static readonly TCrossModel CrossModelSerializer = Activator.CreateInstance<TCrossModel>();
#endregion
/// <inheritdoc/>
protected SerializableDatFile(DatFile? datFile) : base(datFile) { }
/// <inheritdoc/>
public override void ParseFile(string filename,
int indexId,
bool keep,
bool statsOnly = false,
FilterRunner? filterRunner = null,
bool throwOnError = false)
{
try
{
// Deserialize the input file in two steps
var specificFormat = FileDeserializer.Deserialize(filename);
var internalFormat = CrossModelSerializer.Serialize(specificFormat);
// Convert to the internal format
ConvertFromMetadata(internalFormat, filename, indexId, keep, statsOnly, filterRunner);
}
catch (Exception ex) when (!throwOnError)
{
string message = $"'{filename}' - An error occurred during parsing";
_logger.Error(ex, message);
}
}
/// <inheritdoc/>
public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false)
{
try
{
_logger.User($"Writing to '{outfile}'...");
// Serialize the input file in two steps
var internalFormat = ConvertToMetadata(ignoreblanks);
var specificFormat = CrossModelSerializer.Deserialize(internalFormat);
if (!FileSerializer.SerializeFile(specificFormat, outfile))
{
_logger.Warning($"File '{outfile}' could not be written! See the log for more details.");
return false;
}
}
catch (Exception ex) when (!throwOnError)
{
_logger.Error(ex);
return false;
}
_logger.User($"'{outfile}' written!{Environment.NewLine}");
return true;
}
/// <inheritdoc/>
public override bool WriteToFileDB(string outfile, bool ignoreblanks = false, bool throwOnError = false)
{
try
{
_logger.User($"Writing to '{outfile}'...");
// Serialize the input file in two steps
var internalFormat = ConvertToMetadataDB(ignoreblanks);
var specificFormat = CrossModelSerializer.Deserialize(internalFormat);
if (!FileSerializer.SerializeFile(specificFormat, outfile))
{
_logger.Warning($"File '{outfile}' could not be written! See the log for more details.");
return false;
}
}
catch (Exception ex) when (!throwOnError)
{
_logger.Error(ex);
return false;
}
_logger.User($"'{outfile}' written!{Environment.NewLine}");
return true;
}
}
}

View File

@@ -0,0 +1,213 @@
using System;
using System.Collections.Generic;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
namespace SabreTools.Metadata.DatFiles.Formats
{
/// <summary>
/// Represents parsing and writing of a SoftwareList
/// </summary>
public sealed class SoftwareList : SerializableDatFile<Data.Models.SoftwareList.SoftwareList, Serialization.Readers.SoftwareList, Serialization.Writers.SoftwareList, Serialization.CrossModel.SoftwareList>
{
#region Constants
/// <summary>
/// DTD for original MAME Software List DATs
/// </summary>
/// <remarks>
/// TODO: See if there's an updated DTD and then check for required fields
/// </remarks>
internal const string SoftwareListDTD = @"<!ELEMENT softwarelist (notes?, software+)>
<!ATTLIST softwarelist name CDATA #REQUIRED>
<!ATTLIST softwarelist description CDATA #IMPLIED>
<!ELEMENT notes (#PCDATA)>
<!ELEMENT software (description, year, publisher, notes?, info*, sharedfeat*, part*)>
<!ATTLIST software name CDATA #REQUIRED>
<!ATTLIST software cloneof CDATA #IMPLIED>
<!ATTLIST software supported (yes|partial|no) ""yes"">
<!ELEMENT description (#PCDATA)>
<!ELEMENT year (#PCDATA)>
<!ELEMENT publisher (#PCDATA)>
<!ELEMENT info EMPTY>
<!ATTLIST info name CDATA #REQUIRED>
<!ATTLIST info value CDATA #IMPLIED>
<!ELEMENT sharedfeat EMPTY>
<!ATTLIST sharedfeat name CDATA #REQUIRED>
<!ATTLIST sharedfeat value CDATA #IMPLIED>
<!ELEMENT part (feature*, dataarea*, diskarea*, dipswitch*)>
<!ATTLIST part name CDATA #REQUIRED>
<!ATTLIST part interface CDATA #REQUIRED>
<!-- feature is used to store things like pcb-type, mapper type, etc. Specific values depend on the system. -->
<!ELEMENT feature EMPTY>
<!ATTLIST feature name CDATA #REQUIRED>
<!ATTLIST feature value CDATA #IMPLIED>
<!ELEMENT dataarea (rom*)>
<!ATTLIST dataarea name CDATA #REQUIRED>
<!ATTLIST dataarea size CDATA #REQUIRED>
<!ATTLIST dataarea width (8|16|32|64) ""8"">
<!ATTLIST dataarea endianness (big|little) ""little"">
<!ELEMENT rom EMPTY>
<!ATTLIST rom name CDATA #IMPLIED>
<!ATTLIST rom size CDATA #IMPLIED>
<!ATTLIST rom crc CDATA #IMPLIED>
<!ATTLIST rom sha1 CDATA #IMPLIED>
<!ATTLIST rom offset CDATA #IMPLIED>
<!ATTLIST rom value CDATA #IMPLIED>
<!ATTLIST rom status (baddump|nodump|good) ""good"">
<!ATTLIST rom loadflag (load16_byte|load16_word|load16_word_swap|load32_byte|load32_word|load32_word_swap|load32_dword|load64_word|load64_word_swap|reload|fill|continue|reload_plain|ignore) #IMPLIED>
<!ELEMENT diskarea (disk*)>
<!ATTLIST diskarea name CDATA #REQUIRED>
<!ELEMENT disk EMPTY>
<!ATTLIST disk name CDATA #REQUIRED>
<!ATTLIST disk sha1 CDATA #IMPLIED>
<!ATTLIST disk status (baddump|nodump|good) ""good"">
<!ATTLIST disk writeable (yes|no) ""no"">
<!ELEMENT dipswitch (dipvalue*)>
<!ATTLIST dipswitch name CDATA #REQUIRED>
<!ATTLIST dipswitch tag CDATA #REQUIRED>
<!ATTLIST dipswitch mask CDATA #REQUIRED>
<!ELEMENT dipvalue EMPTY>
<!ATTLIST dipvalue name CDATA #REQUIRED>
<!ATTLIST dipvalue value CDATA #REQUIRED>
<!ATTLIST dipvalue default (yes|no) ""no"">
";
#endregion
#region Fields
/// <inheritdoc/>
public override ItemType[] SupportedTypes
=> [
ItemType.DipSwitch,
ItemType.Disk,
ItemType.Info,
ItemType.PartFeature,
ItemType.Rom,
ItemType.SharedFeat,
];
#endregion
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public SoftwareList(DatFile? datFile) : base(datFile)
{
Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.SoftwareList);
}
/// <inheritdoc/>
protected internal override List<string>? GetMissingRequiredFields(DatItem datItem)
{
List<string> missingFields = [];
#pragma warning disable IDE0010
switch (datItem)
{
case DipSwitch dipSwitch:
if (!dipSwitch.PartSpecified)
{
missingFields.Add(Data.Models.Metadata.Part.NameKey);
missingFields.Add(Data.Models.Metadata.Part.InterfaceKey);
}
else
{
if (string.IsNullOrEmpty(dipSwitch.GetFieldValue<Part?>(DipSwitch.PartKey)!.GetName()))
missingFields.Add(Data.Models.Metadata.Part.NameKey);
if (string.IsNullOrEmpty(dipSwitch.GetFieldValue<Part?>(DipSwitch.PartKey)!.GetStringFieldValue(Data.Models.Metadata.Part.InterfaceKey)))
missingFields.Add(Data.Models.Metadata.Part.InterfaceKey);
}
if (string.IsNullOrEmpty(dipSwitch.GetName()))
missingFields.Add(Data.Models.Metadata.DipSwitch.NameKey);
if (string.IsNullOrEmpty(dipSwitch.GetStringFieldValue(Data.Models.Metadata.DipSwitch.TagKey)))
missingFields.Add(Data.Models.Metadata.DipSwitch.TagKey);
if (string.IsNullOrEmpty(dipSwitch.GetStringFieldValue(Data.Models.Metadata.DipSwitch.MaskKey)))
missingFields.Add(Data.Models.Metadata.DipSwitch.MaskKey);
if (dipSwitch.ValuesSpecified)
{
var dipValues = dipSwitch.GetFieldValue<DipValue[]?>(Data.Models.Metadata.DipSwitch.DipValueKey);
if (Array.Find(dipValues!, dv => string.IsNullOrEmpty(dv.GetName())) is not null)
missingFields.Add(Data.Models.Metadata.DipValue.NameKey);
if (Array.Find(dipValues!, dv => string.IsNullOrEmpty(dv.GetStringFieldValue(Data.Models.Metadata.DipValue.ValueKey))) is not null)
missingFields.Add(Data.Models.Metadata.DipValue.ValueKey);
}
break;
case Disk disk:
if (!disk.PartSpecified)
{
missingFields.Add(Data.Models.Metadata.Part.NameKey);
missingFields.Add(Data.Models.Metadata.Part.InterfaceKey);
}
else
{
if (string.IsNullOrEmpty(disk.GetFieldValue<Part?>(Disk.PartKey)!.GetName()))
missingFields.Add(Data.Models.Metadata.Part.NameKey);
if (string.IsNullOrEmpty(disk.GetFieldValue<Part?>(Disk.PartKey)!.GetStringFieldValue(Data.Models.Metadata.Part.InterfaceKey)))
missingFields.Add(Data.Models.Metadata.Part.InterfaceKey);
}
if (!disk.DiskAreaSpecified)
{
missingFields.Add(Data.Models.Metadata.DiskArea.NameKey);
}
else
{
if (string.IsNullOrEmpty(disk.GetFieldValue<DiskArea?>(Disk.DiskAreaKey)!.GetName()))
missingFields.Add(Data.Models.Metadata.DiskArea.NameKey);
}
if (string.IsNullOrEmpty(disk.GetName()))
missingFields.Add(Data.Models.Metadata.Disk.NameKey);
break;
case Info info:
if (string.IsNullOrEmpty(info.GetName()))
missingFields.Add(Data.Models.Metadata.Info.NameKey);
break;
case Rom rom:
if (!rom.PartSpecified)
{
missingFields.Add(Data.Models.Metadata.Part.NameKey);
missingFields.Add(Data.Models.Metadata.Part.InterfaceKey);
}
else
{
if (string.IsNullOrEmpty(rom.GetFieldValue<Part?>(Rom.PartKey)!.GetName()))
missingFields.Add(Data.Models.Metadata.Part.NameKey);
if (string.IsNullOrEmpty(rom.GetFieldValue<Part?>(Rom.PartKey)!.GetStringFieldValue(Data.Models.Metadata.Part.InterfaceKey)))
missingFields.Add(Data.Models.Metadata.Part.InterfaceKey);
}
if (!rom.DataAreaSpecified)
{
missingFields.Add(Data.Models.Metadata.DataArea.NameKey);
missingFields.Add(Data.Models.Metadata.DataArea.SizeKey);
}
else
{
if (string.IsNullOrEmpty(rom.GetFieldValue<DataArea?>(Rom.DataAreaKey)!.GetName()))
missingFields.Add(Data.Models.Metadata.DataArea.NameKey);
if (rom.GetFieldValue<DataArea?>(Rom.DataAreaKey)!.GetInt64FieldValue(Data.Models.Metadata.DataArea.SizeKey) is null)
missingFields.Add(Data.Models.Metadata.DataArea.SizeKey);
}
break;
case SharedFeat sharedFeat:
if (string.IsNullOrEmpty(sharedFeat.GetName()))
missingFields.Add(Data.Models.Metadata.SharedFeat.NameKey);
break;
}
#pragma warning restore IDE0010
return missingFields;
}
}
}

View File

@@ -0,0 +1,931 @@
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
using System.Collections.Concurrent;
#endif
using System.Collections.Generic;
using System.IO;
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
using System.Threading.Tasks;
#endif
using System.Xml.Serialization;
using Newtonsoft.Json;
using SabreTools.Metadata.Tools;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
using SabreTools.Hashing;
using SabreTools.IO.Logging;
using SabreTools.Text.Compare;
namespace SabreTools.Metadata.DatFiles
{
/// <summary>
/// Item dictionary with statistics, bucketing, and sorting
/// </summary>
[JsonObject("items"), XmlRoot("items")]
public class ItemDictionary
{
#region Private instance variables
/// <summary>
/// Determine the bucketing key for all items
/// </summary>
private ItemKey _bucketedBy = ItemKey.NULL;
/// <summary>
/// Internal dictionary for the class
/// </summary>
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
private readonly ConcurrentDictionary<string, List<DatItem>?> _items = [];
#else
private readonly Dictionary<string, List<DatItem>?> _items = [];
#endif
/// <summary>
/// Logging object
/// </summary>
private readonly Logger _logger;
#endregion
#region Fields
/// <summary>
/// Get the keys in sorted order from the file dictionary
/// </summary>
/// <returns>List of the keys in sorted order</returns>
[JsonIgnore, XmlIgnore]
public string[] SortedKeys
{
get
{
List<string> keys = [.. _items.Keys];
keys.Sort(new NaturalComparer());
return [.. keys];
}
}
/// <summary>
/// DAT statistics
/// </summary>
[JsonIgnore, XmlIgnore]
public DatStatistics DatStatistics { get; } = new DatStatistics();
#endregion
#region Constructors
/// <summary>
/// Generic constructor
/// </summary>
public ItemDictionary()
{
_logger = new Logger(this);
}
#endregion
#region Accessors
/// <summary>
/// Add a DatItem to the dictionary after checking
/// </summary>
/// <param name="item">Item data to check against</param>
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
/// <returns>The key for the item</returns>
public string AddItem(DatItem item, bool statsOnly)
{
// If we have a Disk, File, Media, or Rom, clean the hash data
if (item is Disk disk)
{
// If the file has aboslutely no hashes, skip and log
if (disk.GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() != ItemStatus.Nodump
&& string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.MD5Key))
&& string.IsNullOrEmpty(disk.GetStringFieldValue(Data.Models.Metadata.Disk.SHA1Key)))
{
_logger.Verbose($"Incomplete entry for '{disk.GetName()}' will be output as nodump");
disk.SetFieldValue<string?>(Data.Models.Metadata.Disk.StatusKey, ItemStatus.Nodump.AsStringValue());
}
item = disk;
}
else if (item is DatItems.Formats.File file)
{
// If the file has aboslutely no hashes, skip and log
if (string.IsNullOrEmpty(file.CRC)
&& string.IsNullOrEmpty(file.MD5)
&& string.IsNullOrEmpty(file.SHA1)
&& string.IsNullOrEmpty(file.SHA256))
{
_logger.Verbose($"Incomplete entry for '{file.GetName()}' will be output as nodump");
}
item = file;
}
else if (item is Media media)
{
// If the file has aboslutely no hashes, skip and log
if (string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.MD5Key))
&& string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SHA1Key))
&& string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SHA256Key))
&& string.IsNullOrEmpty(media.GetStringFieldValue(Data.Models.Metadata.Media.SpamSumKey)))
{
_logger.Verbose($"Incomplete entry for '{media.GetName()}' will be output as nodump");
}
item = media;
}
else if (item is Rom rom)
{
long? size = rom.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey);
// If we have the case where there is SHA-1 and nothing else, we don't fill in any other part of the data
if (size is null && !string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key)))
{
// No-op, just catch it so it doesn't go further
//logger.Verbose($"{Header.GetStringFieldValue(DatHeader.FileNameKey)}: Entry with only SHA-1 found - '{rom.GetName()}'");
}
// If we have a rom and it's missing size AND the hashes match a 0-byte file, fill in the rest of the info
else if ((size == 0 || size is null)
&& (string.IsNullOrEmpty(rom.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey)) || rom.HasZeroHash()))
{
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SizeKey, "0");
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.CRCKey, HashType.CRC32.ZeroString);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.MD2Key, null); // HashType.MD2.ZeroString
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.MD4Key, null); // HashType.MD4.ZeroString
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.MD5Key, HashType.MD5.ZeroString);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.RIPEMD128Key, null); // HashType.RIPEMD128.ZeroString
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.RIPEMD160Key, null); // HashType.RIPEMD160.ZeroString
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA1Key, HashType.SHA1.ZeroString);
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA256Key, null); // HashType.SHA256.ZeroString;
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA384Key, null); // HashType.SHA384.ZeroString;
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SHA512Key, null); // HashType.SHA512.ZeroString;
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.SpamSumKey, null); // HashType.SpamSum.ZeroString;
}
// If the file has no size and it's not the above case, skip and log
else if (rom.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() != ItemStatus.Nodump && (size == 0 || size is null))
{
//logger.Verbose($"{Header.GetStringFieldValue(DatHeader.FileNameKey)}: Incomplete entry for '{rom.GetName()}' will be output as nodump");
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.StatusKey, ItemStatus.Nodump.AsStringValue());
}
// If the file has a size but aboslutely no hashes, skip and log
else if (rom.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() != ItemStatus.Nodump
&& size is not null && size > 0
&& !rom.HasHashes())
{
//logger.Verbose($"{Header.GetStringFieldValue(DatHeader.FileNameKey)}: Incomplete entry for '{rom.GetName()}' will be output as nodump");
rom.SetFieldValue<string?>(Data.Models.Metadata.Rom.StatusKey, ItemStatus.Nodump.AsStringValue());
}
item = rom;
}
// Get the key and add the file
string key = GetBucketKey(item, _bucketedBy, lower: true, norename: true);
// If only adding statistics, we add an empty key for games and then just item stats
if (statsOnly)
{
EnsureBucketingKey(key);
DatStatistics.AddItemStatistics(item);
}
else
{
AddItem(key, item);
}
return key;
}
/// <summary>
/// Remove all items marked for removal
/// </summary>
public void ClearMarked()
{
string[] keys = [.. SortedKeys];
#if NET452_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
Parallel.ForEach(keys, Core.Globals.ParallelOptions, key =>
#elif NET40_OR_GREATER
Parallel.ForEach(keys, key =>
#else
foreach (var key in keys)
#endif
{
var list = GetItemsForBucket(key, filter: true);
RemoveBucket(key);
list.ForEach(item => AddItem(key, item));
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
});
#else
}
#endif
}
/// <summary>
/// Get the items associated with a bucket name
/// </summary>
/// <param name="bucketName">Name of the bucket to retrive items for</param>
/// <param name="filter">Indicates if RemoveKey filtering is performed</param>
/// <returns>List representing the bucket items, empty on missing</returns>
public List<DatItem> GetItemsForBucket(string? bucketName, bool filter = false)
{
if (bucketName is null)
return [];
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
if (!_items.TryGetValue(bucketName, out var items))
return [];
#else
if (!_items.ContainsKey(bucketName))
return [];
var items = _items[bucketName];
#endif
if (items is null || !filter)
return [.. items ?? []];
var datItems = new List<DatItem>();
foreach (DatItem item in items)
{
if (item.GetBoolFieldValue(DatItem.RemoveKey) != true)
datItems.Add(item);
}
return datItems;
}
/// <summary>
/// Remove a key from the file dictionary if it exists
/// </summary>
/// <param name="key">Key in the dictionary to remove</param>
public bool RemoveBucket(string key)
{
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
bool removed = _items.TryRemove(key, out var list);
#else
if (!_items.ContainsKey(key))
return false;
bool removed = true;
var list = _items[key];
_items.Remove(key);
#endif
if (list is null)
return removed;
foreach (var item in list)
{
DatStatistics.RemoveItemStatistics(item);
}
return removed;
}
/// <summary>
/// Remove the indexed instance of a value from the file dictionary if it exists
/// </summary>
/// <param name="key">Key in the dictionary to remove from</param>
/// <param name="value">Value to remove from the dictionary</param>
/// <param name="index">Index of the item to be removed</param>
public bool RemoveItem(string key, DatItem value, int index)
{
// Explicit lock for some weird corner cases
lock (key)
{
// If the key doesn't exist, return
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
if (!_items.TryGetValue(key, out var list) || list is null)
return false;
#else
if (!_items.ContainsKey(key))
return false;
var list = _items[key];
if (list is null)
return false;
#endif
// If the value doesn't exist in the key, assume it has been removed
if (index < 0)
return false;
// Remove the statistics first
DatStatistics.RemoveItemStatistics(value);
list.RemoveAt(index);
return true;
}
}
/// <summary>
/// Override the internal ItemKey value
/// </summary>
/// <param name="newBucket"></param>
public void SetBucketedBy(ItemKey newBucket)
{
_bucketedBy = newBucket;
}
/// <summary>
/// Add a value to the file dictionary
/// </summary>
/// <param name="key">Key in the dictionary to add to</param>
/// <param name="value">Value to add to the dictionary</param>
internal void AddItem(string key, DatItem value)
{
// Explicit lock for some weird corner cases
lock (key)
{
// Ensure the key exists
EnsureBucketingKey(key);
// If item is null, don't add it
if (value is null)
return;
// Now add the value
_items[key]!.Add(value);
// Now update the statistics
DatStatistics.AddItemStatistics(value);
}
}
#endregion
#region Bucketing
/// <summary>
/// Take the arbitrarily bucketed Files Dictionary and convert to one bucketed by a user-defined method
/// </summary>
/// <param name="bucketBy">ItemKey enum representing how to bucket the individual items</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>
public void BucketBy(ItemKey bucketBy, bool lower = true, bool norename = true)
{
// If we have a situation where there's no dictionary or no keys at all, we skip
if (_items is null || _items.Count == 0)
return;
// If the sorted type isn't the same, we want to sort the dictionary accordingly
if (_bucketedBy != bucketBy && bucketBy != ItemKey.NULL)
{
_logger.User($"Organizing roms by {bucketBy}");
PerformBucketing(bucketBy, lower, norename);
}
// Sort the dictionary to be consistent
_logger.User($"Sorting roms by {bucketBy}");
PerformSorting(norename);
}
/// <summary>
/// Perform deduplication on the current sorted dictionary
/// </summary>
public void Deduplicate()
{
#if NET452_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
Parallel.ForEach(SortedKeys, Core.Globals.ParallelOptions, key =>
#elif NET40_OR_GREATER
Parallel.ForEach(SortedKeys, key =>
#else
foreach (var key in SortedKeys)
#endif
{
// Get the possibly unsorted list
List<DatItem> sortedList = GetItemsForBucket(key);
// Sort and merge the list
Sort(ref sortedList, norename: false);
sortedList = Merge(sortedList);
// Add the list back to the dictionary
RemoveBucket(key);
sortedList.ForEach(item => AddItem(key, item));
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
});
#else
}
#endif
}
/// <summary>
/// Return the duplicate status of two items
/// </summary>
/// <param name="self">Current DatItem</param>
/// <param name="last">DatItem to check against</param>
/// <returns>The DupeType corresponding to the relationship between the two</returns>
public DupeType GetDuplicateStatus(DatItem? self, DatItem? last)
{
DupeType output = 0x00;
// If either item is null
if (self is null || last is null)
return output;
// If we don't have a duplicate at all, return none
if (!self.Equals(last))
return output;
// Get the sources for comparison
var selfSource = self.GetFieldValue<Source?>(DatItem.SourceKey);
var lastSource = last.GetFieldValue<Source?>(DatItem.SourceKey);
// Get the machines for comparison
var selfMachine = self.GetMachine();
string? selfMachineName = selfMachine?.GetName();
var lastMachine = last.GetMachine();
string? lastMachineName = lastMachine?.GetName();
// If the duplicate is external already
#if NET20 || NET35
if ((last.GetFieldValue<DupeType>(DatItem.DupeTypeKey) & DupeType.External) != 0)
#else
if (last.GetFieldValue<DupeType>(DatItem.DupeTypeKey).HasFlag(DupeType.External))
#endif
output |= DupeType.External;
// If the duplicate should be external
else if (lastSource?.Index != selfSource?.Index)
output |= DupeType.External;
// Otherwise, it's considered an internal dupe
else
output |= DupeType.Internal;
// If the item and machine names match
if (lastMachineName == selfMachineName && last.GetName() == self.GetName())
output |= DupeType.All;
// Otherwise, hash match is assumed
else
output |= DupeType.Hash;
return output;
}
/// <summary>
/// Merge an arbitrary set of DatItems based on the supplied information
/// </summary>
/// <param name="items">List of DatItem objects representing the items to be merged</param>
/// <returns>A List of DatItem objects representing the merged items</returns>
/// TODO: Make this internal like the DB counterpart
public static List<DatItem> Merge(List<DatItem>? items)
{
// Check for null or blank inputs first
if (items is null || items.Count == 0)
return [];
// Create placeholder object for checking duplicates
var dupDict = new ItemDictionary();
// Create output list
List<DatItem> output = [];
// Then deduplicate them by checking to see if data matches previous saved roms
int nodumpCount = 0;
foreach (DatItem datItem in items)
{
// If we don't have a Disk, File, Media, or Rom, we skip checking for duplicates
if (datItem is not Disk && datItem is not DatItems.Formats.File && datItem is not Media && datItem is not Rom)
continue;
// If it's a nodump, add and skip
if (datItem is Rom rom && rom.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey).AsItemStatus() == ItemStatus.Nodump)
{
output.Add(datItem);
nodumpCount++;
continue;
}
else if (datItem is Disk disk && disk.GetStringFieldValue(Data.Models.Metadata.Disk.StatusKey).AsItemStatus() == ItemStatus.Nodump)
{
output.Add(datItem);
nodumpCount++;
continue;
}
// If it's the first non-nodump item in the list, don't touch it
if (output.Count == nodumpCount)
{
output.Add(datItem);
continue;
}
// Find the index of the first duplicate, if one exists
int pos = output.FindIndex(lastItem => dupDict.GetDuplicateStatus(datItem, lastItem) != 0x00);
if (pos < 0)
{
output.Add(datItem);
continue;
}
// Get the duplicate item
DatItem savedItem = output[pos];
DupeType dupetype = dupDict.GetDuplicateStatus(datItem, savedItem);
// Disks, File, Media, and Roms have more information to fill
if (datItem is Disk diskItem && savedItem is Disk savedDisk)
savedDisk.FillMissingInformation(diskItem);
else if (datItem is DatItems.Formats.File fileItem && savedItem is DatItems.Formats.File savedFile)
savedFile.FillMissingInformation(fileItem);
else if (datItem is Media mediaItem && savedItem is Media savedMedia)
savedMedia.FillMissingInformation(mediaItem);
else if (datItem is Rom romItem && savedItem is Rom savedRom)
savedRom.FillMissingInformation(romItem);
// Set the duplicate type on the saved item
savedItem.SetFieldValue(DatItem.DupeTypeKey, dupetype);
// Get the sources associated with the items
var savedSource = savedItem.GetFieldValue<Source?>(DatItem.SourceKey);
var itemSource = datItem.GetFieldValue<Source?>(DatItem.SourceKey);
// Get the machines associated with the items
var savedMachine = savedItem.GetMachine();
var itemMachine = datItem.GetMachine();
// If the current source has a lower ID than the saved, use the saved source
if (itemSource?.Index < savedSource?.Index)
{
datItem.SetFieldValue<Source?>(DatItem.SourceKey, savedSource.Clone() as Source);
savedItem.CopyMachineInformation(datItem);
savedItem.SetName(datItem.GetName());
}
// If the saved machine is a child of the current machine, use the current machine instead
if (savedMachine?.GetStringFieldValue(Data.Models.Metadata.Machine.CloneOfKey) == itemMachine?.GetName()
|| savedMachine?.GetStringFieldValue(Data.Models.Metadata.Machine.RomOfKey) == itemMachine?.GetName())
{
savedItem.CopyMachineInformation(datItem);
savedItem.SetName(datItem.GetName());
}
// Replace the original item in the list
output.RemoveAt(pos);
output.Insert(pos, savedItem);
}
// Then return the result
return output;
}
/// <summary>
/// List all duplicates found in a DAT based on a DatItem
/// </summary>
/// <param name="datItem">Item to try to match</param>
/// <param name="sorted">True if the DAT is already sorted accordingly, false otherwise (default)</param>
/// <returns>List of matched DatItem objects</returns>
/// <remarks>This also sets the remove flag on any duplicates found</remarks>
/// TODO: Figure out if removal should be a flag or just removed entirely
internal List<DatItem> GetDuplicates(DatItem datItem, bool sorted = false)
{
// Check for an empty rom list first
if (DatStatistics.TotalCount == 0)
return [];
// We want to get the proper key for the DatItem
string key = SortAndGetKey(datItem, sorted);
// Get the items for the current key, if possible
List<DatItem> items = GetItemsForBucket(key, filter: false);
if (items.Count == 0)
return [];
// Try to find duplicates
List<DatItem> output = [];
foreach (DatItem other in items)
{
// Skip items marked for removal
if (other.GetBoolFieldValue(DatItem.RemoveKey) == true)
continue;
// Mark duplicates for future removal
if (datItem.Equals(other))
{
other.SetFieldValue<bool?>(DatItem.RemoveKey, true);
output.Add(other);
}
}
// Return any matching items
return output;
}
/// <summary>
/// Check if a DAT contains the given DatItem
/// </summary>
/// <param name="datItem">Item to try to match</param>
/// <param name="sorted">True if the DAT is already sorted accordingly, false otherwise (default)</param>
/// <returns>True if it contains the rom, false otherwise</returns>
internal bool HasDuplicates(DatItem datItem, bool sorted = false)
{
// Check for an empty rom list first
if (DatStatistics.TotalCount == 0)
return false;
// We want to get the proper key for the DatItem
string key = SortAndGetKey(datItem, sorted);
// Try to find duplicates
List<DatItem> roms = GetItemsForBucket(key);
if (roms.Count == 0)
return false;
return roms.FindIndex(datItem.Equals) > -1;
}
/// <summary>
/// Ensure the key exists in the items dictionary
/// </summary>
/// <param name="key">Key to ensure</param>
private void EnsureBucketingKey(string key)
{
// If the key is missing from the dictionary, add it
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
_items.GetOrAdd(key, []);
#else
if (!_items.ContainsKey(key))
_items[key] = [];
#endif
}
/// <summary>
/// Get the highest-order Field value that represents the statistics
/// </summary>
private ItemKey GetBestAvailable()
{
// Get the required counts
long diskCount = DatStatistics.GetItemCount(ItemType.Disk);
long mediaCount = DatStatistics.GetItemCount(ItemType.Media);
long romCount = DatStatistics.GetItemCount(ItemType.Rom);
long nodumpCount = DatStatistics.GetStatusCount(ItemStatus.Nodump);
// If all items are supposed to have a SHA-512, we bucket by that
if (diskCount + mediaCount + romCount - nodumpCount == DatStatistics.GetHashCount(HashType.SHA512))
return ItemKey.SHA512;
// If all items are supposed to have a SHA-384, we bucket by that
else if (diskCount + mediaCount + romCount - nodumpCount == DatStatistics.GetHashCount(HashType.SHA384))
return ItemKey.SHA384;
// If all items are supposed to have a SHA-256, we bucket by that
else if (diskCount + mediaCount + romCount - nodumpCount == DatStatistics.GetHashCount(HashType.SHA256))
return ItemKey.SHA256;
// If all items are supposed to have a SHA-1, we bucket by that
else if (diskCount + mediaCount + romCount - nodumpCount == DatStatistics.GetHashCount(HashType.SHA1))
return ItemKey.SHA1;
// If all items are supposed to have a RIPEMD160, we bucket by that
else if (diskCount + mediaCount + romCount - nodumpCount == DatStatistics.GetHashCount(HashType.RIPEMD160))
return ItemKey.RIPEMD160;
// If all items are supposed to have a RIPEMD128, we bucket by that
else if (diskCount + mediaCount + romCount - nodumpCount == DatStatistics.GetHashCount(HashType.RIPEMD128))
return ItemKey.RIPEMD128;
// If all items are supposed to have a MD5, we bucket by that
else if (diskCount + mediaCount + romCount - nodumpCount == DatStatistics.GetHashCount(HashType.MD5))
return ItemKey.MD5;
// If all items are supposed to have a MD4, we bucket by that
else if (diskCount + mediaCount + romCount - nodumpCount == DatStatistics.GetHashCount(HashType.MD4))
return ItemKey.MD4;
// If all items are supposed to have a MD2, we bucket by that
else if (diskCount + mediaCount + romCount - nodumpCount == DatStatistics.GetHashCount(HashType.MD2))
return ItemKey.MD2;
// Otherwise, we bucket by CRC
else
return ItemKey.CRC;
}
/// <summary>
/// Get the bucketing key for a given item
/// <param name="datItem">The current item</param>
/// <param name="bucketBy">ItemKey value representing what key to get</param>
/// <param name="lower">True if the key should be lowercased, 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>
/// </summary>
private static string GetBucketKey(DatItem datItem, ItemKey bucketBy, bool lower, bool norename)
{
if (datItem is null)
return string.Empty;
// Treat NULL like machine
if (bucketBy == ItemKey.NULL)
bucketBy = ItemKey.Machine;
// Get the machine and source
var machine = datItem.GetMachine();
var source = datItem.GetFieldValue<Source?>(DatItem.SourceKey);
// Get the bucket key
return datItem.GetKey(bucketBy, machine, source, lower, norename);
}
/// <summary>
/// Perform bucketing based on the item key provided
/// </summary>
/// <param name="bucketBy">ItemKey enum representing how to bucket the individual items</param>
/// <param name="lower">True if the key should be lowercased, 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>
private void PerformBucketing(ItemKey bucketBy, bool lower, bool norename)
{
// Set the sorted type
_bucketedBy = bucketBy;
// First do the initial sort of all of the roms inplace
List<string> oldkeys = [.. SortedKeys];
#if NET452_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
Parallel.For(0, oldkeys.Count, Core.Globals.ParallelOptions, k =>
#elif NET40_OR_GREATER
Parallel.For(0, oldkeys.Count, k =>
#else
for (int k = 0; k < oldkeys.Count; k++)
#endif
{
string key = oldkeys[k];
if (GetItemsForBucket(key, filter: true).Count == 0)
RemoveBucket(key);
// Now add each of the roms to their respective keys
for (int i = 0; i < GetItemsForBucket(key).Count; i++)
{
DatItem item = GetItemsForBucket(key)[i];
if (item is null || item.GetBoolFieldValue(DatItem.RemoveKey) == true)
continue;
// Get the machine and source
var machine = item.GetMachine();
var source = item.GetFieldValue<Source?>(DatItem.SourceKey);
// We want to get the key most appropriate for the given sorting type
string newkey = item.GetKey(bucketBy, machine, source, lower, norename);
// If the key is different, move the item to the new key
if (newkey != key)
{
AddItem(newkey, item);
bool removed = RemoveItem(key, item, i);
if (!removed)
continue;
i--; // This make sure that the pointer stays on the correct since one was removed
}
}
// If the key is now empty, remove it
if (GetItemsForBucket(key, filter: true).Count == 0)
RemoveBucket(key);
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
});
#else
}
#endif
}
/// <summary>
/// Perform inplace sorting of the dictionary
/// </summary>
private void PerformSorting(bool norename)
{
#if NET452_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
Parallel.ForEach(SortedKeys, Core.Globals.ParallelOptions, key =>
#elif NET40_OR_GREATER
Parallel.ForEach(SortedKeys, key =>
#else
foreach (var key in SortedKeys)
#endif
{
// Get the possibly unsorted list
List<DatItem> sortedList = GetItemsForBucket(key);
// Sort the list of items to be consistent
Sort(ref sortedList, norename);
// Add the list back to the dictionary
RemoveBucket(key);
sortedList.ForEach(item => AddItem(key, item));
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
});
#else
}
#endif
}
/// <summary>
/// Sort a list of DatItem objects by SourceID, Game, and Name (in order)
/// </summary>
/// <param name="items">List of DatItem objects representing the items to be sorted</param>
/// <param name="norename">True if files are not renamed, false otherwise</param>
/// <returns>True if it sorted correctly, false otherwise</returns>
private bool Sort(ref List<DatItem> items, bool norename)
{
// Create the comparer extenal to the delegate
var nc = new NaturalComparer();
// Sort by machine, type, item name, and source
items.Sort(delegate (DatItem x, DatItem y)
{
try
{
// Compare on source if renaming
if (!norename)
{
int xSourceIndex = x.GetFieldValue<Source?>(DatItem.SourceKey)?.Index ?? 0;
int ySourceIndex = y.GetFieldValue<Source?>(DatItem.SourceKey)?.Index ?? 0;
if (xSourceIndex != ySourceIndex)
return xSourceIndex - ySourceIndex;
}
// Get the machines
Machine? xMachine = x.GetMachine();
Machine? yMachine = y.GetMachine();
// If machine names don't match
string? xMachineName = xMachine?.GetName();
string? yMachineName = yMachine?.GetName();
if (xMachineName != yMachineName)
return nc.Compare(xMachineName, yMachineName);
// If types don't match
string? xType = x.GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey);
string? yType = y.GetStringFieldValue(Data.Models.Metadata.DatItem.TypeKey);
if (xType != yType)
return xType.AsItemType() - yType.AsItemType();
// If directory names don't match
string? xDirectoryName = Path.GetDirectoryName(TextHelper.RemovePathUnsafeCharacters(x.GetName() ?? string.Empty));
string? yDirectoryName = Path.GetDirectoryName(TextHelper.RemovePathUnsafeCharacters(y.GetName() ?? string.Empty));
if (xDirectoryName != yDirectoryName)
return nc.Compare(xDirectoryName, yDirectoryName);
// If item names don't match
string? xName = Path.GetFileName(TextHelper.RemovePathUnsafeCharacters(x.GetName() ?? string.Empty));
string? yName = Path.GetFileName(TextHelper.RemovePathUnsafeCharacters(y.GetName() ?? string.Empty));
return nc.Compare(xName, yName);
}
catch
{
// Absorb the error
return 0;
}
});
return true;
}
/// <summary>
/// Sort the input DAT and get the key to be used by the item
/// </summary>
/// <param name="datItem">Item to try to match</param>
/// <param name="sorted">True if the DAT is already sorted accordingly, false otherwise (default)</param>
/// <returns>Key to try to use</returns>
private string SortAndGetKey(DatItem datItem, bool sorted = false)
{
// If we're not already sorted, take care of it
if (!sorted)
BucketBy(GetBestAvailable());
// Now that we have the sorted type, we get the proper key
return GetBucketKey(datItem, _bucketedBy, lower: true, norename: true);
}
#endregion
#region Statistics
/// <summary>
/// Recalculate the statistics for the Dat
/// </summary>
public void RecalculateStats()
{
// Wipe out any stats already there
DatStatistics.ResetStatistics();
// If we have a blank Dat in any way, return
if (_items is null || _items.Count == 0)
return;
// Loop through and add
foreach (string key in _items.Keys)
{
List<DatItem>? datItems = _items[key];
if (datItems is null)
continue;
foreach (DatItem item in datItems)
{
DatStatistics.AddItemStatistics(item);
}
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
namespace SabreTools.Metadata.DatFiles
{
/// <summary>
/// Class used during deduplication
/// </summary>
public struct ItemMappings(DatItems.DatItem item, long machineId, long sourceId)
{
public DatItems.DatItem Item = item;
public long MachineId = machineId;
public long SourceId = sourceId;
}
}

View File

@@ -0,0 +1,53 @@
<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>DatFile 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>
<InternalsVisibleTo Include="SabreTools.Metadata.DatFiles.Test" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SabreTools.Data.Extensions\SabreTools.Data.Extensions.csproj" />
<ProjectReference Include="..\SabreTools.Metadata\SabreTools.Metadata.csproj" />
<ProjectReference Include="..\SabreTools.Metadata.DatItems\SabreTools.Metadata.DatItems.csproj" />
<ProjectReference Include="..\SabreTools.Metadata.Filter\SabreTools.Metadata.Filter.csproj" />
<ProjectReference Include="..\SabreTools.Serialization.CrossModel\SabreTools.Serialization.CrossModel.csproj" />
<ProjectReference Include="..\SabreTools.Serialization.Readers\SabreTools.Serialization.Readers.csproj" />
<ProjectReference Include="..\SabreTools.Serialization.Writers\SabreTools.Serialization.Writers.csproj" />
</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>
</Project>

View File

@@ -0,0 +1,37 @@
using SabreTools.Metadata.Tools;
using Xunit;
namespace SabreTools.Metadata.DatItems.Test
{
public class ConvertersTests
{
#region Generators
[Theory]
[InlineData(ChipType.NULL, 2)]
[InlineData(ControlType.NULL, 15)]
[InlineData(DeviceType.NULL, 21)]
[InlineData(DisplayType.NULL, 5)]
[InlineData(Endianness.NULL, 2)]
[InlineData(FeatureStatus.NULL, 2)]
[InlineData(FeatureType.NULL, 14)]
[InlineData(ItemStatus.NULL, 7)]
[InlineData(ItemType.NULL, 54)]
[InlineData(LoadFlag.NULL, 14)]
[InlineData(MachineType.None, 6)]
[InlineData(OpenMSXSubType.NULL, 3)]
[InlineData(Relation.NULL, 6)]
[InlineData(Runnable.NULL, 3)]
[InlineData(SoftwareListStatus.None, 3)]
[InlineData(Supported.NULL, 5)]
[InlineData(SupportStatus.NULL, 3)]
public void GenerateToEnumTest<T>(T value, int expected)
{
var actual = Converters.GenerateToEnum<T>();
Assert.Equal(default, value);
Assert.Equal(expected, actual.Keys.Count);
}
#endregion
}
}

View File

@@ -0,0 +1,539 @@
using SabreTools.Metadata.DatItems.Formats;
using Xunit;
namespace SabreTools.Metadata.DatItems.Test
{
public class DatItemTests
{
#region Private Testing Classes
/// <summary>
/// Testing implementation of Data.Models.Metadata.DatItem
/// </summary>
private class TestDatItemModel : Data.Models.Metadata.DatItem
{
public const string NameKey = "__NAME__";
}
/// <summary>
/// Testing implementation of DatItem
/// </summary>
private class TestDatItem : DatItem<TestDatItemModel>
{
private readonly string? _nameKey;
protected override ItemType ItemType => ItemType.Blank;
public TestDatItem() => _nameKey = TestDatItemModel.NameKey;
public TestDatItem(string? nameKey) => _nameKey = nameKey;
/// <inheritdoc/>
public override string? GetName() => _nameKey is not null ? _internal.ReadString(_nameKey) : null;
/// <inheritdoc/>
public override void SetName(string? name)
{
if (_nameKey is not null)
_internal[_nameKey] = name;
}
}
#endregion
#region CopyMachineInformation
[Fact]
public void CopyMachineInformation_NewItem_Overwrite()
{
Machine? machineA = new Machine();
machineA.SetName("machineA");
var romA = new Rom();
var romB = new Rom();
romB.RemoveField(DatItem.MachineKey);
romA.CopyMachineInformation(romB);
var actualMachineA = romA.GetMachine();
Assert.NotNull(actualMachineA);
Assert.Null(actualMachineA.GetName());
}
[Fact]
public void CopyMachineInformation_EmptyItem_NoChange()
{
Machine? machineA = new Machine();
machineA.SetName("machineA");
var romA = new Rom();
romA.SetFieldValue(DatItem.MachineKey, machineA);
var romB = new Rom();
romB.RemoveField(DatItem.MachineKey);
romA.CopyMachineInformation(romB);
var actualMachineA = romA.GetMachine();
Assert.NotNull(actualMachineA);
Assert.Equal("machineA", actualMachineA.GetName());
}
[Fact]
public void CopyMachineInformation_NullMachine_NoChange()
{
Machine? machineA = new Machine();
machineA.SetName("machineA");
Machine? machineB = null;
var romA = new Rom();
romA.SetFieldValue(DatItem.MachineKey, machineA);
var romB = new Rom();
romB.SetFieldValue(DatItem.MachineKey, machineB);
romA.CopyMachineInformation(romB);
var actualMachineA = romA.GetMachine();
Assert.NotNull(actualMachineA);
Assert.Equal("machineA", actualMachineA.GetName());
}
[Fact]
public void CopyMachineInformation_EmptyMachine_Overwrite()
{
Machine? machineA = new Machine();
machineA.SetName("machineA");
Machine? machineB = new Machine();
var romA = new Rom();
romA.SetFieldValue(DatItem.MachineKey, machineA);
var romB = new Rom();
romB.SetFieldValue(DatItem.MachineKey, machineB);
romA.CopyMachineInformation(romB);
var actualMachineA = romA.GetMachine();
Assert.NotNull(actualMachineA);
Assert.Null(actualMachineA.GetName());
}
[Fact]
public void CopyMachineInformation_FilledMachine_Overwrite()
{
Machine? machineA = new Machine();
machineA.SetName("machineA");
Machine? machineB = new Machine();
machineB.SetName("machineB");
var romA = new Rom();
romA.SetFieldValue(DatItem.MachineKey, machineA);
var romB = new Rom();
romB.SetFieldValue(DatItem.MachineKey, machineB);
romA.CopyMachineInformation(romB);
var actualMachineA = romA.GetMachine();
Assert.NotNull(actualMachineA);
Assert.Equal("machineB", actualMachineA.GetName());
}
[Fact]
public void CopyMachineInformation_MismatchedType_Overwrite()
{
Machine? machineA = new Machine();
machineA.SetName("machineA");
Machine? machineB = new Machine();
machineB.SetName("machineB");
var romA = new Rom();
romA.SetFieldValue(DatItem.MachineKey, machineA);
var diskB = new Disk();
diskB.SetFieldValue(DatItem.MachineKey, machineB);
romA.CopyMachineInformation(diskB);
var actualMachineA = romA.GetMachine();
Assert.NotNull(actualMachineA);
Assert.Equal("machineB", actualMachineA.GetName());
}
#endregion
#region CompareTo
[Fact]
public void CompareTo_NullOther_Returns1()
{
DatItem self = new Rom();
DatItem? other = null;
int actual = self.CompareTo(other);
Assert.Equal(1, actual);
}
[Fact]
public void CompareTo_DifferentOther_Returns1()
{
DatItem self = new Rom();
self.SetName("name");
DatItem? other = new Disk();
other.SetName("name");
int actual = self.CompareTo(other);
Assert.Equal(1, actual);
}
[Fact]
public void CompareTo_Empty_Returns1()
{
DatItem self = new Rom();
DatItem? other = new Rom();
int actual = self.CompareTo(other);
Assert.Equal(1, actual);
}
[Theory]
[InlineData(null, null, 0)]
[InlineData("name", null, 1)]
[InlineData("name", "other", -1)]
[InlineData(null, "name", -1)]
[InlineData("other", "name", 1)]
[InlineData("name", "name", 0)]
public void CompareTo_NamesOnly(string? selfName, string? otherName, int expected)
{
DatItem self = new Rom();
self.SetName(selfName);
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "DEADBEEF");
DatItem? other = new Rom();
other.SetName(otherName);
other.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "DEADBEEF");
int actual = self.CompareTo(other);
Assert.Equal(expected, actual);
}
#endregion
#region Equals
[Fact]
public void Equals_Null_False()
{
DatItem self = new TestDatItem();
DatItem? other = null;
bool actual = self.Equals(other);
Assert.False(actual);
}
[Fact]
public void Equals_MismatchedType_False()
{
DatItem self = new TestDatItem();
DatItem? other = new Rom();
bool actual = self.Equals(other);
Assert.False(actual);
}
[Fact]
public void Equals_DefaultInternal_True()
{
DatItem self = new TestDatItem();
DatItem? other = new TestDatItem();
bool actual = self.Equals(other);
Assert.True(actual);
}
[Fact]
public void Equals_MismatchedInternal_False()
{
DatItem self = new TestDatItem();
self.SetName("self");
DatItem? other = new TestDatItem();
other.SetName("other");
bool actual = self.Equals(other);
Assert.False(actual);
}
[Fact]
public void Equals_EqualInternal_True()
{
DatItem self = new TestDatItem();
self.SetName("name");
DatItem? other = new TestDatItem();
other.SetName("name");
bool actual = self.Equals(other);
Assert.True(actual);
}
#endregion
// TODO: Change when Machine retrieval gets fixed
#region GetKey
[Theory]
[InlineData(ItemKey.NULL, false, false, "")]
[InlineData(ItemKey.NULL, false, true, "")]
[InlineData(ItemKey.NULL, true, false, "")]
[InlineData(ItemKey.NULL, true, true, "")]
[InlineData(ItemKey.Machine, false, false, "0000000000-Machine")]
[InlineData(ItemKey.Machine, false, true, "Machine")]
[InlineData(ItemKey.Machine, true, false, "0000000000-machine")]
[InlineData(ItemKey.Machine, true, true, "machine")]
[InlineData(ItemKey.CRC, false, false, "00000000")]
[InlineData(ItemKey.CRC, false, true, "00000000")]
[InlineData(ItemKey.CRC, true, false, "00000000")]
[InlineData(ItemKey.CRC, true, true, "00000000")]
[InlineData(ItemKey.MD2, false, false, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD2, false, true, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD2, true, false, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD2, true, true, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD4, false, false, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD4, false, true, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD4, true, false, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD4, true, true, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD5, false, false, "d41d8cd98f00b204e9800998ecf8427e")]
[InlineData(ItemKey.MD5, false, true, "d41d8cd98f00b204e9800998ecf8427e")]
[InlineData(ItemKey.MD5, true, false, "d41d8cd98f00b204e9800998ecf8427e")]
[InlineData(ItemKey.MD5, true, true, "d41d8cd98f00b204e9800998ecf8427e")]
[InlineData(ItemKey.RIPEMD128, false, false, "cdf26213a150dc3ecb610f18f6b38b46")]
[InlineData(ItemKey.RIPEMD128, false, true, "cdf26213a150dc3ecb610f18f6b38b46")]
[InlineData(ItemKey.RIPEMD128, true, false, "cdf26213a150dc3ecb610f18f6b38b46")]
[InlineData(ItemKey.RIPEMD128, true, true, "cdf26213a150dc3ecb610f18f6b38b46")]
[InlineData(ItemKey.RIPEMD160, false, false, "9c1185a5c5e9fc54612808977ee8f548b2258d31")]
[InlineData(ItemKey.RIPEMD160, false, true, "9c1185a5c5e9fc54612808977ee8f548b2258d31")]
[InlineData(ItemKey.RIPEMD160, true, false, "9c1185a5c5e9fc54612808977ee8f548b2258d31")]
[InlineData(ItemKey.RIPEMD160, true, true, "9c1185a5c5e9fc54612808977ee8f548b2258d31")]
[InlineData(ItemKey.SHA1, false, false, "da39a3ee5e6b4b0d3255bfef95601890afd80709")]
[InlineData(ItemKey.SHA1, false, true, "da39a3ee5e6b4b0d3255bfef95601890afd80709")]
[InlineData(ItemKey.SHA1, true, false, "da39a3ee5e6b4b0d3255bfef95601890afd80709")]
[InlineData(ItemKey.SHA1, true, true, "da39a3ee5e6b4b0d3255bfef95601890afd80709")]
[InlineData(ItemKey.SHA256, false, false, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")]
[InlineData(ItemKey.SHA256, false, true, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")]
[InlineData(ItemKey.SHA256, true, false, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")]
[InlineData(ItemKey.SHA256, true, true, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")]
[InlineData(ItemKey.SHA384, false, false, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA384, false, true, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA384, true, false, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA384, true, true, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA512, false, false, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SHA512, false, true, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SHA512, true, false, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SHA512, true, true, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SpamSum, false, false, "3::")]
[InlineData(ItemKey.SpamSum, false, true, "3::")]
[InlineData(ItemKey.SpamSum, true, false, "3::")]
[InlineData(ItemKey.SpamSum, true, true, "3::")]
public void GetKeyDB_DefaultImplementation(ItemKey bucketedBy, bool lower, bool norename, string expected)
{
Source source = new Source(0);
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "Machine");
DatItem datItem = new Blank();
string actual = datItem.GetKey(bucketedBy, machine, source, lower, norename);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(ItemKey.NULL, false, false, "")]
[InlineData(ItemKey.NULL, false, true, "")]
[InlineData(ItemKey.NULL, true, false, "")]
[InlineData(ItemKey.NULL, true, true, "")]
[InlineData(ItemKey.Machine, false, false, "0000000000-Machine")]
[InlineData(ItemKey.Machine, false, true, "Machine")]
[InlineData(ItemKey.Machine, true, false, "0000000000-machine")]
[InlineData(ItemKey.Machine, true, true, "machine")]
[InlineData(ItemKey.CRC, false, false, "DEADBEEF")]
[InlineData(ItemKey.CRC, false, true, "DEADBEEF")]
[InlineData(ItemKey.CRC, true, false, "deadbeef")]
[InlineData(ItemKey.CRC, true, true, "deadbeef")]
[InlineData(ItemKey.MD2, false, false, "DEADBEEF")]
[InlineData(ItemKey.MD2, false, true, "DEADBEEF")]
[InlineData(ItemKey.MD2, true, false, "deadbeef")]
[InlineData(ItemKey.MD2, true, true, "deadbeef")]
[InlineData(ItemKey.MD4, false, false, "DEADBEEF")]
[InlineData(ItemKey.MD4, false, true, "DEADBEEF")]
[InlineData(ItemKey.MD4, true, false, "deadbeef")]
[InlineData(ItemKey.MD4, true, true, "deadbeef")]
[InlineData(ItemKey.MD5, false, false, "DEADBEEF")]
[InlineData(ItemKey.MD5, false, true, "DEADBEEF")]
[InlineData(ItemKey.MD5, true, false, "deadbeef")]
[InlineData(ItemKey.MD5, true, true, "deadbeef")]
[InlineData(ItemKey.RIPEMD128, false, false, "DEADBEEF")]
[InlineData(ItemKey.RIPEMD128, false, true, "DEADBEEF")]
[InlineData(ItemKey.RIPEMD128, true, false, "deadbeef")]
[InlineData(ItemKey.RIPEMD128, true, true, "deadbeef")]
[InlineData(ItemKey.RIPEMD160, false, false, "DEADBEEF")]
[InlineData(ItemKey.RIPEMD160, false, true, "DEADBEEF")]
[InlineData(ItemKey.RIPEMD160, true, false, "deadbeef")]
[InlineData(ItemKey.RIPEMD160, true, true, "deadbeef")]
[InlineData(ItemKey.SHA1, false, false, "DEADBEEF")]
[InlineData(ItemKey.SHA1, false, true, "DEADBEEF")]
[InlineData(ItemKey.SHA1, true, false, "deadbeef")]
[InlineData(ItemKey.SHA1, true, true, "deadbeef")]
[InlineData(ItemKey.SHA256, false, false, "DEADBEEF")]
[InlineData(ItemKey.SHA256, false, true, "DEADBEEF")]
[InlineData(ItemKey.SHA256, true, false, "deadbeef")]
[InlineData(ItemKey.SHA256, true, true, "deadbeef")]
[InlineData(ItemKey.SHA384, false, false, "DEADBEEF")]
[InlineData(ItemKey.SHA384, false, true, "DEADBEEF")]
[InlineData(ItemKey.SHA384, true, false, "deadbeef")]
[InlineData(ItemKey.SHA384, true, true, "deadbeef")]
[InlineData(ItemKey.SHA512, false, false, "DEADBEEF")]
[InlineData(ItemKey.SHA512, false, true, "DEADBEEF")]
[InlineData(ItemKey.SHA512, true, false, "deadbeef")]
[InlineData(ItemKey.SHA512, true, true, "deadbeef")]
[InlineData(ItemKey.SpamSum, false, false, "BASE64")]
[InlineData(ItemKey.SpamSum, false, true, "BASE64")]
[InlineData(ItemKey.SpamSum, true, false, "base64")]
[InlineData(ItemKey.SpamSum, true, true, "base64")]
public void GetKeyDB_CustomImplementation(ItemKey bucketedBy, bool lower, bool norename, string expected)
{
Source source = new Source(0);
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "Machine");
DatItem datItem = new Rom();
datItem.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, "BASE64");
string actual = datItem.GetKey(bucketedBy, machine, source, lower, norename);
Assert.Equal(expected, actual);
}
#endregion
#region GetName
[Fact]
public void GetName_NoNameKey_Null()
{
DatItem item = new TestDatItem(nameKey: null);
item.SetFieldValue(TestDatItemModel.NameKey, "name");
string? actual = item.GetName();
Assert.Null(actual);
}
[Fact]
public void GetName_EmptyNameKey_Null()
{
DatItem item = new TestDatItem(nameKey: string.Empty);
item.SetFieldValue(TestDatItemModel.NameKey, "name");
string? actual = item.GetName();
Assert.Null(actual);
}
[Fact]
public void GetName_NameKeyNotExists_Null()
{
DatItem item = new TestDatItem(nameKey: "INVALID");
item.SetFieldValue(TestDatItemModel.NameKey, "name");
string? actual = item.GetName();
Assert.Null(actual);
}
[Fact]
public void GetName_NameKeyExists_Filled()
{
DatItem item = new TestDatItem(nameKey: TestDatItemModel.NameKey);
item.SetFieldValue(TestDatItemModel.NameKey, "name");
string? actual = item.GetName();
Assert.Equal("name", actual);
}
#endregion
#region SetName
[Fact]
public void SetName_NoNameKey_Null()
{
DatItem item = new TestDatItem(nameKey: null);
item.SetName("name");
string? actual = item.GetName();
Assert.Null(actual);
}
[Fact]
public void SetName_EmptyNameKey_Null()
{
DatItem item = new TestDatItem(nameKey: string.Empty);
item.SetName("name");
string? actual = item.GetName();
Assert.Null(actual);
}
[Fact]
public void SetName_NameKeyNonEmpty_Filled()
{
DatItem item = new TestDatItem(nameKey: TestDatItemModel.NameKey);
item.SetName("name");
string? actual = item.GetName();
Assert.Equal("name", actual);
}
#endregion
#region Clone
[Fact]
public void CloneTest()
{
DatItem item = new Sample();
item.SetName("name");
object clone = item.Clone();
Sample? actual = clone as Sample;
Assert.NotNull(actual);
Assert.Equal("name", actual.GetName());
}
#endregion
#region GetInternalClone
[Fact]
public void GetInternalCloneTest()
{
DatItem<TestDatItemModel> item = new TestDatItem();
item.SetName("name");
TestDatItemModel actual = item.GetInternalClone();
Assert.Equal("name", actual[TestDatItemModel.NameKey]);
}
#endregion
}
}

View File

@@ -0,0 +1,590 @@
using Xunit;
namespace SabreTools.Metadata.DatItems.Test
{
public class ExtensionsTests
{
#region String to Enum
[Theory]
[InlineData(null, ChipType.NULL)]
[InlineData("cpu", ChipType.CPU)]
[InlineData("audio", ChipType.Audio)]
public void AsChipTypeTest(string? field, ChipType expected)
{
ChipType actual = field.AsChipType();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, ControlType.NULL)]
[InlineData("joy", ControlType.Joy)]
[InlineData("stick", ControlType.Stick)]
[InlineData("paddle", ControlType.Paddle)]
[InlineData("pedal", ControlType.Pedal)]
[InlineData("lightgun", ControlType.Lightgun)]
[InlineData("positional", ControlType.Positional)]
[InlineData("dial", ControlType.Dial)]
[InlineData("trackball", ControlType.Trackball)]
[InlineData("mouse", ControlType.Mouse)]
[InlineData("only_buttons", ControlType.OnlyButtons)]
[InlineData("keypad", ControlType.Keypad)]
[InlineData("keyboard", ControlType.Keyboard)]
[InlineData("mahjong", ControlType.Mahjong)]
[InlineData("hanafuda", ControlType.Hanafuda)]
[InlineData("gambling", ControlType.Gambling)]
public void AsControlTypeTest(string? field, ControlType expected)
{
ControlType actual = field.AsControlType();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, DeviceType.NULL)]
[InlineData("unknown", DeviceType.Unknown)]
[InlineData("cartridge", DeviceType.Cartridge)]
[InlineData("floppydisk", DeviceType.FloppyDisk)]
[InlineData("harddisk", DeviceType.HardDisk)]
[InlineData("cylinder", DeviceType.Cylinder)]
[InlineData("cassette", DeviceType.Cassette)]
[InlineData("punchcard", DeviceType.PunchCard)]
[InlineData("punchtape", DeviceType.PunchTape)]
[InlineData("printout", DeviceType.Printout)]
[InlineData("serial", DeviceType.Serial)]
[InlineData("parallel", DeviceType.Parallel)]
[InlineData("snapshot", DeviceType.Snapshot)]
[InlineData("quickload", DeviceType.QuickLoad)]
[InlineData("memcard", DeviceType.MemCard)]
[InlineData("cdrom", DeviceType.CDROM)]
[InlineData("magtape", DeviceType.MagTape)]
[InlineData("romimage", DeviceType.ROMImage)]
[InlineData("midiin", DeviceType.MIDIIn)]
[InlineData("midiout", DeviceType.MIDIOut)]
[InlineData("picture", DeviceType.Picture)]
[InlineData("vidfile", DeviceType.VidFile)]
public void AsDeviceTypeTest(string? field, DeviceType expected)
{
DeviceType actual = field.AsDeviceType();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, DisplayType.NULL)]
[InlineData("raster", DisplayType.Raster)]
[InlineData("vector", DisplayType.Vector)]
[InlineData("lcd", DisplayType.LCD)]
[InlineData("svg", DisplayType.SVG)]
[InlineData("unknown", DisplayType.Unknown)]
public void AsDisplayTypeTest(string? field, DisplayType expected)
{
DisplayType actual = field.AsDisplayType();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, Endianness.NULL)]
[InlineData("big", Endianness.Big)]
[InlineData("little", Endianness.Little)]
public void AsEndiannessTest(string? field, Endianness expected)
{
Endianness actual = field.AsEndianness();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, FeatureStatus.NULL)]
[InlineData("unemulated", FeatureStatus.Unemulated)]
[InlineData("imperfect", FeatureStatus.Imperfect)]
public void AsFeatureStatusTest(string? field, FeatureStatus expected)
{
FeatureStatus actual = field.AsFeatureStatus();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, FeatureType.NULL)]
[InlineData("protection", FeatureType.Protection)]
[InlineData("palette", FeatureType.Palette)]
[InlineData("graphics", FeatureType.Graphics)]
[InlineData("sound", FeatureType.Sound)]
[InlineData("controls", FeatureType.Controls)]
[InlineData("keyboard", FeatureType.Keyboard)]
[InlineData("mouse", FeatureType.Mouse)]
[InlineData("microphone", FeatureType.Microphone)]
[InlineData("camera", FeatureType.Camera)]
[InlineData("disk", FeatureType.Disk)]
[InlineData("printer", FeatureType.Printer)]
[InlineData("lan", FeatureType.Lan)]
[InlineData("wan", FeatureType.Wan)]
[InlineData("timing", FeatureType.Timing)]
public void AsFeatureTypeTest(string? field, FeatureType expected)
{
FeatureType actual = field.AsFeatureType();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, ItemStatus.NULL)]
[InlineData("none", ItemStatus.None)]
[InlineData("no", ItemStatus.None)]
[InlineData("good", ItemStatus.Good)]
[InlineData("baddump", ItemStatus.BadDump)]
[InlineData("nodump", ItemStatus.Nodump)]
[InlineData("yes", ItemStatus.Nodump)]
[InlineData("verified", ItemStatus.Verified)]
public void AsItemStatusTest(string? field, ItemStatus expected)
{
ItemStatus actual = field.AsItemStatus();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, ItemType.NULL)]
[InlineData("adjuster", ItemType.Adjuster)]
[InlineData("analog", ItemType.Analog)]
[InlineData("archive", ItemType.Archive)]
[InlineData("biosset", ItemType.BiosSet)]
[InlineData("blank", ItemType.Blank)]
[InlineData("chip", ItemType.Chip)]
[InlineData("condition", ItemType.Condition)]
[InlineData("configuration", ItemType.Configuration)]
[InlineData("conflocation", ItemType.ConfLocation)]
[InlineData("confsetting", ItemType.ConfSetting)]
[InlineData("control", ItemType.Control)]
[InlineData("dataarea", ItemType.DataArea)]
[InlineData("device", ItemType.Device)]
[InlineData("deviceref", ItemType.DeviceRef)]
[InlineData("device_ref", ItemType.DeviceRef)]
[InlineData("diplocation", ItemType.DipLocation)]
[InlineData("dipswitch", ItemType.DipSwitch)]
[InlineData("dipvalue", ItemType.DipValue)]
[InlineData("disk", ItemType.Disk)]
[InlineData("diskarea", ItemType.DiskArea)]
[InlineData("display", ItemType.Display)]
[InlineData("driver", ItemType.Driver)]
[InlineData("extension", ItemType.Extension)]
[InlineData("feature", ItemType.Feature)]
[InlineData("file", ItemType.File)]
[InlineData("info", ItemType.Info)]
[InlineData("input", ItemType.Input)]
[InlineData("instance", ItemType.Instance)]
[InlineData("media", ItemType.Media)]
[InlineData("part", ItemType.Part)]
[InlineData("partfeature", ItemType.PartFeature)]
[InlineData("part_feature", ItemType.PartFeature)]
[InlineData("port", ItemType.Port)]
[InlineData("ramoption", ItemType.RamOption)]
[InlineData("ram_option", ItemType.RamOption)]
[InlineData("release", ItemType.Release)]
[InlineData("releasedetails", ItemType.ReleaseDetails)]
[InlineData("release_details", ItemType.ReleaseDetails)]
[InlineData("rom", ItemType.Rom)]
[InlineData("sample", ItemType.Sample)]
[InlineData("serials", ItemType.Serials)]
[InlineData("sharedfeat", ItemType.SharedFeat)]
[InlineData("shared_feat", ItemType.SharedFeat)]
[InlineData("sharedfeature", ItemType.SharedFeat)]
[InlineData("shared_feature", ItemType.SharedFeat)]
[InlineData("slot", ItemType.Slot)]
[InlineData("slotoption", ItemType.SlotOption)]
[InlineData("slot_option", ItemType.SlotOption)]
[InlineData("softwarelist", ItemType.SoftwareList)]
[InlineData("software_list", ItemType.SoftwareList)]
[InlineData("sound", ItemType.Sound)]
[InlineData("sourcedetails", ItemType.SourceDetails)]
[InlineData("source_details", ItemType.SourceDetails)]
public void AsItemTypeTest(string? field, ItemType expected)
{
ItemType actual = field.AsItemType();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, LoadFlag.NULL)]
[InlineData("load16_byte", LoadFlag.Load16Byte)]
[InlineData("load16_word", LoadFlag.Load16Word)]
[InlineData("load16_word_swap", LoadFlag.Load16WordSwap)]
[InlineData("load32_byte", LoadFlag.Load32Byte)]
[InlineData("load32_word", LoadFlag.Load32Word)]
[InlineData("load32_word_swap", LoadFlag.Load32WordSwap)]
[InlineData("load32_dword", LoadFlag.Load32DWord)]
[InlineData("load64_word", LoadFlag.Load64Word)]
[InlineData("load64_word_swap", LoadFlag.Load64WordSwap)]
[InlineData("reload", LoadFlag.Reload)]
[InlineData("fill", LoadFlag.Fill)]
[InlineData("continue", LoadFlag.Continue)]
[InlineData("reload_plain", LoadFlag.ReloadPlain)]
[InlineData("ignore", LoadFlag.Ignore)]
public void AsLoadFlagTest(string? field, LoadFlag expected)
{
LoadFlag actual = field.AsLoadFlag();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, MachineType.None)]
[InlineData("none", MachineType.None)]
[InlineData("bios", MachineType.Bios)]
[InlineData("dev", MachineType.Device)]
[InlineData("device", MachineType.Device)]
[InlineData("mech", MachineType.Mechanical)]
[InlineData("mechanical", MachineType.Mechanical)]
public void AsMachineTypeTest(string? field, MachineType expected)
{
MachineType actual = field.AsMachineType();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, OpenMSXSubType.NULL)]
[InlineData("rom", OpenMSXSubType.Rom)]
[InlineData("megarom", OpenMSXSubType.MegaRom)]
[InlineData("sccpluscart", OpenMSXSubType.SCCPlusCart)]
public void AsOpenMSXSubTypeTest(string? field, OpenMSXSubType expected)
{
OpenMSXSubType actual = field.AsOpenMSXSubType();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, Relation.NULL)]
[InlineData("eq", Relation.Equal)]
[InlineData("ne", Relation.NotEqual)]
[InlineData("gt", Relation.GreaterThan)]
[InlineData("le", Relation.LessThanOrEqual)]
[InlineData("lt", Relation.LessThan)]
[InlineData("ge", Relation.GreaterThanOrEqual)]
public void AsRelationTest(string? field, Relation expected)
{
Relation actual = field.AsRelation();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, Runnable.NULL)]
[InlineData("no", Runnable.No)]
[InlineData("partial", Runnable.Partial)]
[InlineData("yes", Runnable.Yes)]
public void AsRunnableTest(string? field, Runnable expected)
{
Runnable actual = field.AsRunnable();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, SoftwareListStatus.None)]
[InlineData("none", SoftwareListStatus.None)]
[InlineData("original", SoftwareListStatus.Original)]
[InlineData("compatible", SoftwareListStatus.Compatible)]
public void AsSoftwareListStatusTest(string? field, SoftwareListStatus expected)
{
SoftwareListStatus actual = field.AsSoftwareListStatus();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, Supported.NULL)]
[InlineData("no", Supported.No)]
[InlineData("unsupported", Supported.No)]
[InlineData("partial", Supported.Partial)]
[InlineData("yes", Supported.Yes)]
[InlineData("supported", Supported.Yes)]
public void AsSupportedTest(string? field, Supported expected)
{
Supported actual = field.AsSupported();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, SupportStatus.NULL)]
[InlineData("good", SupportStatus.Good)]
[InlineData("imperfect", SupportStatus.Imperfect)]
[InlineData("preliminary", SupportStatus.Preliminary)]
public void AsSupportStatusTest(string? field, SupportStatus expected)
{
SupportStatus actual = field.AsSupportStatus();
Assert.Equal(expected, actual);
}
#endregion
#region Enum to String
[Theory]
[InlineData(ChipType.NULL, null)]
[InlineData(ChipType.CPU, "cpu")]
[InlineData(ChipType.Audio, "audio")]
public void FromChipTypeTest(ChipType field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(ControlType.NULL, null)]
[InlineData(ControlType.Joy, "joy")]
[InlineData(ControlType.Stick, "stick")]
[InlineData(ControlType.Paddle, "paddle")]
[InlineData(ControlType.Pedal, "pedal")]
[InlineData(ControlType.Lightgun, "lightgun")]
[InlineData(ControlType.Positional, "positional")]
[InlineData(ControlType.Dial, "dial")]
[InlineData(ControlType.Trackball, "trackball")]
[InlineData(ControlType.Mouse, "mouse")]
[InlineData(ControlType.OnlyButtons, "only_buttons")]
[InlineData(ControlType.Keypad, "keypad")]
[InlineData(ControlType.Keyboard, "keyboard")]
[InlineData(ControlType.Mahjong, "mahjong")]
[InlineData(ControlType.Hanafuda, "hanafuda")]
[InlineData(ControlType.Gambling, "gambling")]
public void FromControlTypeTest(ControlType field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(DeviceType.NULL, null)]
[InlineData(DeviceType.Unknown, "unknown")]
[InlineData(DeviceType.Cartridge, "cartridge")]
[InlineData(DeviceType.FloppyDisk, "floppydisk")]
[InlineData(DeviceType.HardDisk, "harddisk")]
[InlineData(DeviceType.Cylinder, "cylinder")]
[InlineData(DeviceType.Cassette, "cassette")]
[InlineData(DeviceType.PunchCard, "punchcard")]
[InlineData(DeviceType.PunchTape, "punchtape")]
[InlineData(DeviceType.Printout, "printout")]
[InlineData(DeviceType.Serial, "serial")]
[InlineData(DeviceType.Parallel, "parallel")]
[InlineData(DeviceType.Snapshot, "snapshot")]
[InlineData(DeviceType.QuickLoad, "quickload")]
[InlineData(DeviceType.MemCard, "memcard")]
[InlineData(DeviceType.CDROM, "cdrom")]
[InlineData(DeviceType.MagTape, "magtape")]
[InlineData(DeviceType.ROMImage, "romimage")]
[InlineData(DeviceType.MIDIIn, "midiin")]
[InlineData(DeviceType.MIDIOut, "midiout")]
[InlineData(DeviceType.Picture, "picture")]
[InlineData(DeviceType.VidFile, "vidfile")]
public void FromDeviceTypeTest(DeviceType field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(DisplayType.NULL, null)]
[InlineData(DisplayType.Raster, "raster")]
[InlineData(DisplayType.Vector, "vector")]
[InlineData(DisplayType.LCD, "lcd")]
[InlineData(DisplayType.SVG, "svg")]
[InlineData(DisplayType.Unknown, "unknown")]
public void FromDisplayTypeTest(DisplayType field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(Endianness.NULL, null)]
[InlineData(Endianness.Big, "big")]
[InlineData(Endianness.Little, "little")]
public void FromEndiannessTest(Endianness field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(FeatureStatus.NULL, null)]
[InlineData(FeatureStatus.Unemulated, "unemulated")]
[InlineData(FeatureStatus.Imperfect, "imperfect")]
public void FromFeatureStatusTest(FeatureStatus field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(FeatureType.NULL, null)]
[InlineData(FeatureType.Protection, "protection")]
[InlineData(FeatureType.Palette, "palette")]
[InlineData(FeatureType.Graphics, "graphics")]
[InlineData(FeatureType.Sound, "sound")]
[InlineData(FeatureType.Controls, "controls")]
[InlineData(FeatureType.Keyboard, "keyboard")]
[InlineData(FeatureType.Mouse, "mouse")]
[InlineData(FeatureType.Microphone, "microphone")]
[InlineData(FeatureType.Camera, "camera")]
[InlineData(FeatureType.Disk, "disk")]
[InlineData(FeatureType.Printer, "printer")]
[InlineData(FeatureType.Lan, "lan")]
[InlineData(FeatureType.Wan, "wan")]
[InlineData(FeatureType.Timing, "timing")]
public void FromFeatureTypeTest(FeatureType field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(ItemStatus.NULL, null)]
[InlineData(ItemStatus.None, "none")]
[InlineData(ItemStatus.Good, "good")]
[InlineData(ItemStatus.BadDump, "baddump")]
[InlineData(ItemStatus.Nodump, "nodump")]
[InlineData(ItemStatus.Verified, "verified")]
public void FromItemStatusTest(ItemStatus field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(ItemType.NULL, null)]
[InlineData(ItemType.Adjuster, "adjuster")]
[InlineData(ItemType.Analog, "analog")]
[InlineData(ItemType.Archive, "archive")]
[InlineData(ItemType.BiosSet, "biosset")]
[InlineData(ItemType.Blank, "blank")]
[InlineData(ItemType.Chip, "chip")]
[InlineData(ItemType.Condition, "condition")]
[InlineData(ItemType.Configuration, "configuration")]
[InlineData(ItemType.ConfLocation, "conflocation")]
[InlineData(ItemType.ConfSetting, "confsetting")]
[InlineData(ItemType.Control, "control")]
[InlineData(ItemType.DataArea, "dataarea")]
[InlineData(ItemType.Device, "device")]
[InlineData(ItemType.DeviceRef, "device_ref")]
[InlineData(ItemType.DipLocation, "diplocation")]
[InlineData(ItemType.DipSwitch, "dipswitch")]
[InlineData(ItemType.DipValue, "dipvalue")]
[InlineData(ItemType.Disk, "disk")]
[InlineData(ItemType.DiskArea, "diskarea")]
[InlineData(ItemType.Display, "display")]
[InlineData(ItemType.Driver, "driver")]
[InlineData(ItemType.Extension, "extension")]
[InlineData(ItemType.Feature, "feature")]
[InlineData(ItemType.File, "file")]
[InlineData(ItemType.Info, "info")]
[InlineData(ItemType.Input, "input")]
[InlineData(ItemType.Instance, "instance")]
[InlineData(ItemType.Media, "media")]
[InlineData(ItemType.Part, "part")]
[InlineData(ItemType.PartFeature, "part_feature")]
[InlineData(ItemType.Port, "port")]
[InlineData(ItemType.RamOption, "ramoption")]
[InlineData(ItemType.Release, "release")]
[InlineData(ItemType.ReleaseDetails, "release_details")]
[InlineData(ItemType.Rom, "rom")]
[InlineData(ItemType.Sample, "sample")]
[InlineData(ItemType.Serials, "serials")]
[InlineData(ItemType.SharedFeat, "sharedfeat")]
[InlineData(ItemType.Slot, "slot")]
[InlineData(ItemType.SlotOption, "slotoption")]
[InlineData(ItemType.SoftwareList, "softwarelist")]
[InlineData(ItemType.Sound, "sound")]
[InlineData(ItemType.SourceDetails, "source_details")]
public void FromItemTypeTest(ItemType field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(LoadFlag.NULL, null)]
[InlineData(LoadFlag.Load16Byte, "load16_byte")]
[InlineData(LoadFlag.Load16Word, "load16_word")]
[InlineData(LoadFlag.Load16WordSwap, "load16_word_swap")]
[InlineData(LoadFlag.Load32Byte, "load32_byte")]
[InlineData(LoadFlag.Load32Word, "load32_word")]
[InlineData(LoadFlag.Load32WordSwap, "load32_word_swap")]
[InlineData(LoadFlag.Load32DWord, "load32_dword")]
[InlineData(LoadFlag.Load64Word, "load64_word")]
[InlineData(LoadFlag.Load64WordSwap, "load64_word_swap")]
[InlineData(LoadFlag.Reload, "reload")]
[InlineData(LoadFlag.Fill, "fill")]
[InlineData(LoadFlag.Continue, "continue")]
[InlineData(LoadFlag.ReloadPlain, "reload_plain")]
[InlineData(LoadFlag.Ignore, "ignore")]
public void FromLoadFlagTest(LoadFlag field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(OpenMSXSubType.NULL, null)]
[InlineData(OpenMSXSubType.Rom, "rom")]
[InlineData(OpenMSXSubType.MegaRom, "megarom")]
[InlineData(OpenMSXSubType.SCCPlusCart, "sccpluscart")]
public void FromOpenMSXSubTypeTest(OpenMSXSubType field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(Relation.NULL, null)]
[InlineData(Relation.Equal, "eq")]
[InlineData(Relation.NotEqual, "ne")]
[InlineData(Relation.GreaterThan, "gt")]
[InlineData(Relation.LessThanOrEqual, "le")]
[InlineData(Relation.LessThan, "lt")]
[InlineData(Relation.GreaterThanOrEqual, "ge")]
public void FromRelationTest(Relation field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(Runnable.NULL, null)]
[InlineData(Runnable.No, "no")]
[InlineData(Runnable.Partial, "partial")]
[InlineData(Runnable.Yes, "yes")]
public void FromRunnableTest(Runnable field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(SoftwareListStatus.None, "none")]
[InlineData(SoftwareListStatus.Original, "original")]
[InlineData(SoftwareListStatus.Compatible, "compatible")]
public void FromSoftwareListStatusTest(SoftwareListStatus field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(Supported.NULL, true, null)]
[InlineData(Supported.NULL, false, null)]
[InlineData(Supported.No, true, "unsupported")]
[InlineData(Supported.No, false, "no")]
[InlineData(Supported.Partial, true, "partial")]
[InlineData(Supported.Partial, false, "partial")]
[InlineData(Supported.Yes, true, "supported")]
[InlineData(Supported.Yes, false, "yes")]
public void FromSupportedTest(Supported field, bool useSecond, string? expected)
{
string? actual = field.AsStringValue(useSecond);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(SupportStatus.NULL, null)]
[InlineData(SupportStatus.Good, "good")]
[InlineData(SupportStatus.Imperfect, "imperfect")]
[InlineData(SupportStatus.Preliminary, "preliminary")]
public void FromSupportStatusTest(SupportStatus field, string? expected)
{
string? actual = field.AsStringValue();
Assert.Equal(expected, actual);
}
#endregion
}
}

View File

@@ -0,0 +1,269 @@
using SabreTools.Hashing;
using Xunit;
namespace SabreTools.Metadata.DatItems.Formats.Test
{
public class DiskTests
{
#region ConvertToRom
[Fact]
public void ConvertToRomTest()
{
DiskArea diskArea = new DiskArea();
diskArea.SetName("XXXXXX");
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "XXXXXX");
Part part = new Part();
part.SetName("XXXXXX");
Source source = new Source(0, "XXXXXX");
Disk disk = new Disk();
disk.SetName("XXXXXX");
disk.SetFieldValue(Disk.DiskAreaKey, diskArea);
disk.SetFieldValue(Data.Models.Metadata.Disk.MergeKey, "XXXXXX");
disk.SetFieldValue(Data.Models.Metadata.Disk.RegionKey, "XXXXXX");
disk.SetFieldValue(Data.Models.Metadata.Disk.StatusKey, "good");
disk.SetFieldValue(Data.Models.Metadata.Disk.OptionalKey, "XXXXXX");
disk.SetFieldValue(Data.Models.Metadata.Disk.MD5Key, HashType.MD5.ZeroString);
disk.SetFieldValue(Data.Models.Metadata.Disk.SHA1Key, HashType.SHA1.ZeroString);
disk.SetFieldValue(DatItem.DupeTypeKey, DupeType.All | DupeType.External);
disk.SetFieldValue(DatItem.MachineKey, machine);
disk.SetFieldValue(Disk.PartKey, part);
disk.SetFieldValue(DatItem.RemoveKey, (bool?)false);
disk.SetFieldValue(DatItem.SourceKey, source);
Rom actual = disk.ConvertToRom();
Assert.Equal("XXXXXX.chd", actual.GetName());
Assert.Equal("XXXXXX", actual.GetStringFieldValue(Data.Models.Metadata.Rom.MergeKey));
Assert.Equal("XXXXXX", actual.GetStringFieldValue(Data.Models.Metadata.Rom.RegionKey));
Assert.Equal("good", actual.GetStringFieldValue(Data.Models.Metadata.Rom.StatusKey));
Assert.Equal("XXXXXX", actual.GetStringFieldValue(Data.Models.Metadata.Rom.OptionalKey));
Assert.Equal(HashType.MD5.ZeroString, actual.GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key));
Assert.Equal(HashType.SHA1.ZeroString, actual.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key));
Assert.Equal(DupeType.All | DupeType.External, actual.GetFieldValue<DupeType>(DatItem.DupeTypeKey));
DataArea? actualDataArea = actual.GetFieldValue<DataArea?>(Rom.DataAreaKey);
Assert.NotNull(actualDataArea);
Assert.Equal("XXXXXX", actualDataArea.GetStringFieldValue(Data.Models.Metadata.DataArea.NameKey));
Machine? actualMachine = actual.GetMachine();
Assert.NotNull(actualMachine);
Assert.Equal("XXXXXX", actualMachine.GetName());
Assert.Equal(false, actual.GetBoolFieldValue(DatItem.RemoveKey));
Part? actualPart = actual.GetFieldValue<Part?>(Rom.PartKey);
Assert.NotNull(actualPart);
Assert.Equal("XXXXXX", actualPart.GetStringFieldValue(Data.Models.Metadata.Part.NameKey));
Source? actualSource = actual.GetFieldValue<Source?>(DatItem.SourceKey);
Assert.NotNull(actualSource);
Assert.Equal(0, actualSource.Index);
Assert.Equal("XXXXXX", actualSource.Name);
}
#endregion
#region FillMissingInformation
[Fact]
public void FillMissingInformation_BothEmpty()
{
Disk self = new Disk();
Disk other = new Disk();
self.FillMissingInformation(other);
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Disk.MD5Key));
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Disk.SHA1Key));
}
[Fact]
public void FillMissingInformation_AllMissing()
{
Disk self = new Disk();
Disk other = new Disk();
other.SetFieldValue(Data.Models.Metadata.Disk.MD5Key, "XXXXXX");
other.SetFieldValue(Data.Models.Metadata.Disk.SHA1Key, "XXXXXX");
self.FillMissingInformation(other);
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Disk.MD5Key));
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Disk.SHA1Key));
}
#endregion
#region HasHashes
[Fact]
public void HasHashes_NoHash_False()
{
Disk self = new Disk();
bool actual = self.HasHashes();
Assert.False(actual);
}
[Fact]
public void HasHashes_MD5_True()
{
Disk self = new Disk();
self.SetFieldValue(Data.Models.Metadata.Disk.MD5Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Disk.SHA1Key, string.Empty);
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_SHA1_True()
{
Disk self = new Disk();
self.SetFieldValue(Data.Models.Metadata.Disk.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Disk.SHA1Key, "XXXXXX");
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_All_True()
{
Disk self = new Disk();
self.SetFieldValue(Data.Models.Metadata.Disk.MD5Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Disk.SHA1Key, "XXXXXX");
bool actual = self.HasHashes();
Assert.True(actual);
}
#endregion
#region HasZeroHash
[Fact]
public void HasZeroHash_NoHash_True()
{
Disk self = new Disk();
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_NonZeroHash_False()
{
Disk self = new Disk();
self.SetFieldValue(Data.Models.Metadata.Disk.MD5Key, "DEADBEEF");
self.SetFieldValue(Data.Models.Metadata.Disk.SHA1Key, "DEADBEEF");
bool actual = self.HasZeroHash();
Assert.False(actual);
}
[Fact]
public void HasZeroHash_ZeroMD5_True()
{
Disk self = new Disk();
self.SetFieldValue(Data.Models.Metadata.Disk.MD5Key, HashType.MD5.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Disk.SHA1Key, string.Empty);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroSHA1_True()
{
Disk self = new Disk();
self.SetFieldValue(Data.Models.Metadata.Disk.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Disk.SHA1Key, HashType.SHA1.ZeroString);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroAll_True()
{
Disk self = new Disk();
self.SetFieldValue(Data.Models.Metadata.Disk.MD5Key, HashType.MD5.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Disk.SHA1Key, HashType.SHA1.ZeroString);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
#endregion
// TODO: Change when Machine retrieval gets fixed
#region GetKey
[Theory]
[InlineData(ItemKey.NULL, false, false, "")]
[InlineData(ItemKey.NULL, false, true, "")]
[InlineData(ItemKey.NULL, true, false, "")]
[InlineData(ItemKey.NULL, true, true, "")]
[InlineData(ItemKey.Machine, false, false, "0000000000-Machine")]
[InlineData(ItemKey.Machine, false, true, "Machine")]
[InlineData(ItemKey.Machine, true, false, "0000000000-machine")]
[InlineData(ItemKey.Machine, true, true, "machine")]
[InlineData(ItemKey.CRC, false, false, "00000000")]
[InlineData(ItemKey.CRC, false, true, "00000000")]
[InlineData(ItemKey.CRC, true, false, "00000000")]
[InlineData(ItemKey.CRC, true, true, "00000000")]
[InlineData(ItemKey.MD2, false, false, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD2, false, true, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD2, true, false, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD2, true, true, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD4, false, false, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD4, false, true, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD4, true, false, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD4, true, true, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD5, false, false, "DEADBEEF")]
[InlineData(ItemKey.MD5, false, true, "DEADBEEF")]
[InlineData(ItemKey.MD5, true, false, "deadbeef")]
[InlineData(ItemKey.MD5, true, true, "deadbeef")]
[InlineData(ItemKey.SHA1, false, false, "DEADBEEF")]
[InlineData(ItemKey.SHA1, false, true, "DEADBEEF")]
[InlineData(ItemKey.SHA1, true, false, "deadbeef")]
[InlineData(ItemKey.SHA1, true, true, "deadbeef")]
[InlineData(ItemKey.SHA256, false, false, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")]
[InlineData(ItemKey.SHA256, false, true, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")]
[InlineData(ItemKey.SHA256, true, false, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")]
[InlineData(ItemKey.SHA256, true, true, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")]
[InlineData(ItemKey.SHA384, false, false, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA384, false, true, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA384, true, false, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA384, true, true, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA512, false, false, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SHA512, false, true, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SHA512, true, false, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SHA512, true, true, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SpamSum, false, false, "3::")]
[InlineData(ItemKey.SpamSum, false, true, "3::")]
[InlineData(ItemKey.SpamSum, true, false, "3::")]
[InlineData(ItemKey.SpamSum, true, true, "3::")]
public void GetKeyDBTest(ItemKey bucketedBy, bool lower, bool norename, string expected)
{
Source source = new Source(0);
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "Machine");
DatItem datItem = new Disk();
datItem.SetFieldValue(Data.Models.Metadata.Disk.MD5Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Disk.SHA1Key, "DEADBEEF");
string actual = datItem.GetKey(bucketedBy, machine, source, lower, norename);
Assert.Equal(expected, actual);
}
#endregion
}
}

View File

@@ -0,0 +1,360 @@
using SabreTools.Hashing;
using Xunit;
namespace SabreTools.Metadata.DatItems.Formats.Test
{
public class FileTests
{
#region ConvertToRom
[Fact]
public void ConvertToRomTest()
{
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "XXXXXX");
Source source = new Source(0, "XXXXXX");
var file = new File
{
Id = "XXXXXX",
Extension = "XXXXXX",
Size = 12345,
CRC = "DEADBEEF",
MD5 = "DEADBEEF",
SHA1 = "DEADBEEF",
SHA256 = "DEADBEEF",
Format = "XXXXXX"
};
file.SetFieldValue(DatItem.DupeTypeKey, DupeType.All | DupeType.External);
file.SetFieldValue(DatItem.MachineKey, machine);
file.SetFieldValue(DatItem.RemoveKey, (bool?)false);
file.SetFieldValue(DatItem.SourceKey, source);
file.SetFieldValue(DatItem.MachineKey, machine);
file.SetFieldValue(DatItem.SourceKey, source);
Rom actual = file.ConvertToRom();
Assert.Equal("XXXXXX.XXXXXX", actual.GetName());
Assert.Equal(12345, actual.GetInt64FieldValue(Data.Models.Metadata.Rom.SizeKey));
Assert.Equal("deadbeef", actual.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey));
Assert.Equal("000000000000000000000000deadbeef", actual.GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key));
Assert.Equal("00000000000000000000000000000000deadbeef", actual.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key));
Assert.Equal("00000000000000000000000000000000000000000000000000000000deadbeef", actual.GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key));
Assert.Equal(DupeType.All | DupeType.External, actual.GetFieldValue<DupeType>(DatItem.DupeTypeKey));
Machine? actualMachine = actual.GetMachine();
Assert.NotNull(actualMachine);
Assert.Equal("XXXXXX", actualMachine.GetName());
Assert.Equal(false, actual.GetBoolFieldValue(DatItem.RemoveKey));
Source? actualSource = actual.GetFieldValue<Source?>(DatItem.SourceKey);
Assert.NotNull(actualSource);
Assert.Equal(0, actualSource.Index);
Assert.Equal("XXXXXX", actualSource.Name);
}
#endregion
#region FillMissingInformation
[Fact]
public void FillMissingInformation_BothEmpty()
{
File self = new File();
File other = new File();
self.FillMissingInformation(other);
Assert.Null(self.Size);
Assert.Null(self.CRC);
Assert.Null(self.MD5);
Assert.Null(self.SHA1);
Assert.Null(self.SHA256);
}
[Fact]
public void FillMissingInformation_AllMissing()
{
File self = new File();
File other = new File
{
Size = 12345,
CRC = "DEADBEEF",
MD5 = "DEADBEEF",
SHA1 = "DEADBEEF",
SHA256 = "DEADBEEF",
};
self.FillMissingInformation(other);
Assert.Equal(12345, self.Size);
Assert.Equal("deadbeef", self.CRC);
Assert.Equal("000000000000000000000000deadbeef", self.MD5);
Assert.Equal("00000000000000000000000000000000deadbeef", self.SHA1);
Assert.Equal("00000000000000000000000000000000000000000000000000000000deadbeef", self.SHA256);
}
#endregion
#region HasHashes
[Fact]
public void HasHashes_NoHash_False()
{
File self = new File();
bool actual = self.HasHashes();
Assert.False(actual);
}
[Fact]
public void HasHashes_CRC_True()
{
File self = new File
{
CRC = "deadbeef",
MD5 = string.Empty,
SHA1 = string.Empty,
SHA256 = string.Empty,
};
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_MD5_True()
{
File self = new File
{
CRC = string.Empty,
MD5 = "deadbeef",
SHA1 = string.Empty,
SHA256 = string.Empty,
};
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_SHA1_True()
{
File self = new File
{
CRC = string.Empty,
MD5 = string.Empty,
SHA1 = "deadbeef",
SHA256 = string.Empty,
};
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_SHA256_True()
{
File self = new File
{
CRC = string.Empty,
MD5 = string.Empty,
SHA1 = string.Empty,
SHA256 = "deadbeef",
};
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_All_True()
{
File self = new File
{
CRC = "deadbeef",
MD5 = "deadbeef",
SHA1 = "deadbeef",
SHA256 = "deadbeef",
};
bool actual = self.HasHashes();
Assert.True(actual);
}
#endregion
#region HasZeroHash
[Fact]
public void HasZeroHash_NoHash_True()
{
File self = new File();
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_NonZeroHash_False()
{
File self = new File
{
CRC = "deadbeef",
MD5 = "deadbeef",
SHA1 = "deadbeef",
SHA256 = "deadbeef",
};
bool actual = self.HasZeroHash();
Assert.False(actual);
}
[Fact]
public void HasZeroHash_ZeroCRC_True()
{
File self = new File
{
CRC = HashType.CRC32.ZeroString,
MD5 = string.Empty,
SHA1 = string.Empty,
SHA256 = string.Empty,
};
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroMD5_True()
{
File self = new File
{
CRC = string.Empty,
MD5 = HashType.MD5.ZeroString,
SHA1 = string.Empty,
SHA256 = string.Empty,
};
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroSHA1_True()
{
File self = new File
{
CRC = string.Empty,
MD5 = string.Empty,
SHA1 = HashType.SHA1.ZeroString,
SHA256 = string.Empty,
};
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroSHA256_True()
{
File self = new File
{
CRC = string.Empty,
MD5 = string.Empty,
SHA1 = string.Empty,
SHA256 = HashType.SHA256.ZeroString,
};
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroAll_True()
{
File self = new File
{
CRC = HashType.CRC32.ZeroString,
MD5 = HashType.MD5.ZeroString,
SHA1 = HashType.SHA1.ZeroString,
SHA256 = HashType.SHA256.ZeroString,
};
bool actual = self.HasZeroHash();
Assert.True(actual);
}
#endregion
// TODO: Change when Machine retrieval gets fixed
#region GetKey
[Theory]
[InlineData(ItemKey.NULL, false, false, "")]
[InlineData(ItemKey.NULL, false, true, "")]
[InlineData(ItemKey.NULL, true, false, "")]
[InlineData(ItemKey.NULL, true, true, "")]
[InlineData(ItemKey.Machine, false, false, "0000000000-Machine")]
[InlineData(ItemKey.Machine, false, true, "Machine")]
[InlineData(ItemKey.Machine, true, false, "0000000000-machine")]
[InlineData(ItemKey.Machine, true, true, "machine")]
[InlineData(ItemKey.CRC, false, false, "deadbeef")]
[InlineData(ItemKey.CRC, false, true, "deadbeef")]
[InlineData(ItemKey.CRC, true, false, "deadbeef")]
[InlineData(ItemKey.CRC, true, true, "deadbeef")]
[InlineData(ItemKey.MD2, false, false, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD2, false, true, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD2, true, false, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD2, true, true, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD4, false, false, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD4, false, true, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD4, true, false, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD4, true, true, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD5, false, false, "000000000000000000000000deadbeef")]
[InlineData(ItemKey.MD5, false, true, "000000000000000000000000deadbeef")]
[InlineData(ItemKey.MD5, true, false, "000000000000000000000000deadbeef")]
[InlineData(ItemKey.MD5, true, true, "000000000000000000000000deadbeef")]
[InlineData(ItemKey.SHA1, false, false, "00000000000000000000000000000000deadbeef")]
[InlineData(ItemKey.SHA1, false, true, "00000000000000000000000000000000deadbeef")]
[InlineData(ItemKey.SHA1, true, false, "00000000000000000000000000000000deadbeef")]
[InlineData(ItemKey.SHA1, true, true, "00000000000000000000000000000000deadbeef")]
[InlineData(ItemKey.SHA256, false, false, "00000000000000000000000000000000000000000000000000000000deadbeef")]
[InlineData(ItemKey.SHA256, false, true, "00000000000000000000000000000000000000000000000000000000deadbeef")]
[InlineData(ItemKey.SHA256, true, false, "00000000000000000000000000000000000000000000000000000000deadbeef")]
[InlineData(ItemKey.SHA256, true, true, "00000000000000000000000000000000000000000000000000000000deadbeef")]
[InlineData(ItemKey.SHA384, false, false, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA384, false, true, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA384, true, false, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA384, true, true, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA512, false, false, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SHA512, false, true, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SHA512, true, false, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SHA512, true, true, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SpamSum, false, false, "3::")]
[InlineData(ItemKey.SpamSum, false, true, "3::")]
[InlineData(ItemKey.SpamSum, true, false, "3::")]
[InlineData(ItemKey.SpamSum, true, true, "3::")]
public void GetKeyDBTest(ItemKey bucketedBy, bool lower, bool norename, string expected)
{
Source source = new Source(0);
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "Machine");
DatItem datItem = new File
{
CRC = "DEADBEEF",
MD5 = "DEADBEEF",
SHA1 = "DEADBEEF",
SHA256 = "DEADBEEF",
};
string actual = datItem.GetKey(bucketedBy, machine, source, lower, norename);
Assert.Equal(expected, actual);
}
#endregion
}
}

View File

@@ -0,0 +1,323 @@
using SabreTools.Hashing;
using Xunit;
namespace SabreTools.Metadata.DatItems.Formats.Test
{
public class MediaTests
{
#region ConvertToRom
[Fact]
public void ConvertToRomTest()
{
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "XXXXXX");
Source source = new Source(0, "XXXXXX");
Media media = new Media();
media.SetName("XXXXXX");
media.SetFieldValue(Data.Models.Metadata.Media.MD5Key, HashType.MD5.ZeroString);
media.SetFieldValue(Data.Models.Metadata.Media.SHA1Key, HashType.SHA1.ZeroString);
media.SetFieldValue(Data.Models.Metadata.Media.SHA256Key, HashType.SHA256.ZeroString);
media.SetFieldValue(Data.Models.Metadata.Media.SpamSumKey, HashType.SpamSum.ZeroString);
media.SetFieldValue(DatItem.DupeTypeKey, DupeType.All | DupeType.External);
media.SetFieldValue(DatItem.MachineKey, machine);
media.SetFieldValue(DatItem.RemoveKey, (bool?)false);
media.SetFieldValue(DatItem.SourceKey, source);
Rom actual = media.ConvertToRom();
Assert.Equal("XXXXXX.aaruf", actual.GetName());
Assert.Equal(HashType.MD5.ZeroString, actual.GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key));
Assert.Equal(HashType.SHA1.ZeroString, actual.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key));
Assert.Equal(HashType.SHA256.ZeroString, actual.GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key));
Assert.Equal(HashType.SpamSum.ZeroString, actual.GetStringFieldValue(Data.Models.Metadata.Rom.SpamSumKey));
Assert.Equal(DupeType.All | DupeType.External, actual.GetFieldValue<DupeType>(DatItem.DupeTypeKey));
Machine? actualMachine = actual.GetMachine();
Assert.NotNull(actualMachine);
Assert.Equal("XXXXXX", actualMachine.GetName());
Assert.Equal(false, actual.GetBoolFieldValue(DatItem.RemoveKey));
Source? actualSource = actual.GetFieldValue<Source?>(DatItem.SourceKey);
Assert.NotNull(actualSource);
Assert.Equal(0, actualSource.Index);
Assert.Equal("XXXXXX", actualSource.Name);
}
#endregion
#region FillMissingInformation
[Fact]
public void FillMissingInformation_BothEmpty()
{
Media self = new Media();
Media other = new Media();
self.FillMissingInformation(other);
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Media.MD5Key));
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Media.SHA1Key));
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Media.SHA256Key));
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Media.SpamSumKey));
}
[Fact]
public void FillMissingInformation_AllMissing()
{
Media self = new Media();
Media other = new Media();
other.SetFieldValue(Data.Models.Metadata.Media.MD5Key, "XXXXXX");
other.SetFieldValue(Data.Models.Metadata.Media.SHA1Key, "XXXXXX");
other.SetFieldValue(Data.Models.Metadata.Media.SHA256Key, "XXXXXX");
other.SetFieldValue(Data.Models.Metadata.Media.SpamSumKey, "XXXXXX");
self.FillMissingInformation(other);
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Media.MD5Key));
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Media.SHA1Key));
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Media.SHA256Key));
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Media.SpamSumKey));
}
#endregion
#region HasHashes
[Fact]
public void HasHashes_NoHash_False()
{
Media self = new Media();
bool actual = self.HasHashes();
Assert.False(actual);
}
[Fact]
public void HasHashes_MD5_True()
{
Media self = new Media();
self.SetFieldValue(Data.Models.Metadata.Media.MD5Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Media.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SpamSumKey, string.Empty);
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_SHA1_True()
{
Media self = new Media();
self.SetFieldValue(Data.Models.Metadata.Media.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SHA1Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Media.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SpamSumKey, string.Empty);
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_SHA256_True()
{
Media self = new Media();
self.SetFieldValue(Data.Models.Metadata.Media.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SHA256Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Media.SpamSumKey, string.Empty);
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_SpamSum_True()
{
Media self = new Media();
self.SetFieldValue(Data.Models.Metadata.Media.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SpamSumKey, "XXXXXX");
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_All_True()
{
Media self = new Media();
self.SetFieldValue(Data.Models.Metadata.Media.MD5Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Media.SHA1Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Media.SHA256Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Media.SpamSumKey, "XXXXXX");
bool actual = self.HasHashes();
Assert.True(actual);
}
#endregion
#region HasZeroHash
[Fact]
public void HasZeroHash_NoHash_True()
{
Media self = new Media();
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_NonZeroHash_False()
{
Media self = new Media();
self.SetFieldValue(Data.Models.Metadata.Media.MD5Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Media.SHA1Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Media.SHA256Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Media.SpamSumKey, "XXXXXX");
bool actual = self.HasZeroHash();
Assert.False(actual);
}
[Fact]
public void HasZeroHash_ZeroMD5_True()
{
Media self = new Media();
self.SetFieldValue(Data.Models.Metadata.Media.MD5Key, HashType.MD5.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Media.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SpamSumKey, string.Empty);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroSHA1_True()
{
Media self = new Media();
self.SetFieldValue(Data.Models.Metadata.Media.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SHA1Key, HashType.SHA1.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Media.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SpamSumKey, string.Empty);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroSHA256_True()
{
Media self = new Media();
self.SetFieldValue(Data.Models.Metadata.Media.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SHA256Key, HashType.SHA256.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Media.SpamSumKey, string.Empty);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroSpamSum_True()
{
Media self = new Media();
self.SetFieldValue(Data.Models.Metadata.Media.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Media.SpamSumKey, HashType.SpamSum.ZeroString);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroAll_True()
{
Media self = new Media();
self.SetFieldValue(Data.Models.Metadata.Media.MD5Key, HashType.MD5.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Media.SHA1Key, HashType.SHA1.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Media.SHA256Key, HashType.SHA256.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Media.SpamSumKey, HashType.SpamSum.ZeroString);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
#endregion
// TODO: Change when Machine retrieval gets fixed
#region GetKey
[Theory]
[InlineData(ItemKey.NULL, false, false, "")]
[InlineData(ItemKey.NULL, false, true, "")]
[InlineData(ItemKey.NULL, true, false, "")]
[InlineData(ItemKey.NULL, true, true, "")]
[InlineData(ItemKey.Machine, false, false, "0000000000-Machine")]
[InlineData(ItemKey.Machine, false, true, "Machine")]
[InlineData(ItemKey.Machine, true, false, "0000000000-machine")]
[InlineData(ItemKey.Machine, true, true, "machine")]
[InlineData(ItemKey.CRC, false, false, "00000000")]
[InlineData(ItemKey.CRC, false, true, "00000000")]
[InlineData(ItemKey.CRC, true, false, "00000000")]
[InlineData(ItemKey.CRC, true, true, "00000000")]
[InlineData(ItemKey.MD2, false, false, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD2, false, true, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD2, true, false, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD2, true, true, "8350e5a3e24c153df2275c9f80692773")]
[InlineData(ItemKey.MD4, false, false, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD4, false, true, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD4, true, false, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD4, true, true, "31d6cfe0d16ae931b73c59d7e0c089c0")]
[InlineData(ItemKey.MD5, false, false, "DEADBEEF")]
[InlineData(ItemKey.MD5, false, true, "DEADBEEF")]
[InlineData(ItemKey.MD5, true, false, "deadbeef")]
[InlineData(ItemKey.MD5, true, true, "deadbeef")]
[InlineData(ItemKey.SHA1, false, false, "DEADBEEF")]
[InlineData(ItemKey.SHA1, false, true, "DEADBEEF")]
[InlineData(ItemKey.SHA1, true, false, "deadbeef")]
[InlineData(ItemKey.SHA1, true, true, "deadbeef")]
[InlineData(ItemKey.SHA256, false, false, "DEADBEEF")]
[InlineData(ItemKey.SHA256, false, true, "DEADBEEF")]
[InlineData(ItemKey.SHA256, true, false, "deadbeef")]
[InlineData(ItemKey.SHA256, true, true, "deadbeef")]
[InlineData(ItemKey.SHA384, false, false, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA384, false, true, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA384, true, false, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA384, true, true, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")]
[InlineData(ItemKey.SHA512, false, false, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SHA512, false, true, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SHA512, true, false, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SHA512, true, true, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData(ItemKey.SpamSum, false, false, "DEADBEEF")]
[InlineData(ItemKey.SpamSum, false, true, "DEADBEEF")]
[InlineData(ItemKey.SpamSum, true, false, "deadbeef")]
[InlineData(ItemKey.SpamSum, true, true, "deadbeef")]
public void GetKeyDBTest(ItemKey bucketedBy, bool lower, bool norename, string expected)
{
Source source = new Source(0);
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "Machine");
DatItem datItem = new Media();
datItem.SetFieldValue(Data.Models.Metadata.Media.MD5Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Media.SHA1Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Media.SHA256Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Media.SpamSumKey, "DEADBEEF");
string actual = datItem.GetKey(bucketedBy, machine, source, lower, norename);
Assert.Equal(expected, actual);
}
#endregion
}
}

View File

@@ -0,0 +1,672 @@
using SabreTools.Hashing;
using Xunit;
namespace SabreTools.Metadata.DatItems.Formats.Test
{
public class RomTests
{
#region FillMissingInformation
[Fact]
public void FillMissingInformation_BothEmpty()
{
Rom self = new Rom();
Rom other = new Rom();
self.FillMissingInformation(other);
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey));
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Rom.MD2Key));
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Rom.MD4Key));
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key));
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key));
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key));
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key));
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key));
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Rom.SHA384Key));
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Rom.SHA512Key));
Assert.Null(self.GetStringFieldValue(Data.Models.Metadata.Rom.SpamSumKey));
}
[Fact]
public void FillMissingInformation_AllMissing()
{
Rom self = new Rom();
Rom other = new Rom();
other.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "XXXXXX");
other.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, "XXXXXX");
other.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, "XXXXXX");
other.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, "XXXXXX");
other.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, "XXXXXX");
other.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, "XXXXXX");
other.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, "XXXXXX");
other.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, "XXXXXX");
other.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, "XXXXXX");
other.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, "XXXXXX");
other.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, "XXXXXX");
self.FillMissingInformation(other);
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Rom.CRCKey));
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Rom.MD2Key));
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Rom.MD4Key));
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Rom.MD5Key));
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key));
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key));
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Rom.SHA1Key));
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Rom.SHA256Key));
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Rom.SHA384Key));
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Rom.SHA512Key));
Assert.Equal("XXXXXX", self.GetStringFieldValue(Data.Models.Metadata.Rom.SpamSumKey));
}
#endregion
#region HasHashes
[Fact]
public void HasHashes_NoHash_False()
{
Rom self = new Rom();
bool actual = self.HasHashes();
Assert.False(actual);
}
[Fact]
public void HasHashes_CRC_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_MD2_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_MD4_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_MD5_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_RIPEMD128_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_RIPEMD160_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_SHA1_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_SHA256_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_SHA384_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_SHA512_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_SpamSum_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, "XXXXXX");
bool actual = self.HasHashes();
Assert.True(actual);
}
[Fact]
public void HasHashes_All_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, "XXXXXX");
bool actual = self.HasHashes();
Assert.True(actual);
}
#endregion
#region HasZeroHash
[Fact]
public void HasZeroHash_NoHash_True()
{
Rom self = new Rom();
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_NonZeroHash_False()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, "XXXXXX");
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, "XXXXXX");
bool actual = self.HasZeroHash();
Assert.False(actual);
}
[Fact]
public void HasZeroHash_ZeroCRC_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, HashType.CRC32.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroMD2_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, HashType.MD2.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroMD4_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, HashType.MD4.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroMD5_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, HashType.MD5.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroRIPEMD128_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, HashType.RIPEMD128.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroRIPEMD160_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, HashType.RIPEMD160.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroSHA1_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, HashType.SHA1.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroSHA256_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, HashType.SHA256.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroSHA384_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, HashType.SHA384.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroSHA512_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, HashType.SHA512.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, string.Empty);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroSpamSum_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, string.Empty);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, HashType.SpamSum.ZeroString);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
[Fact]
public void HasZeroHash_ZeroAll_True()
{
Rom self = new Rom();
self.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, HashType.CRC32.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, HashType.MD2.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, HashType.MD4.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, HashType.MD5.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, HashType.RIPEMD128.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, HashType.RIPEMD160.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, HashType.SHA1.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, HashType.SHA256.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, HashType.SHA384.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, HashType.SHA512.ZeroString);
self.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, HashType.SpamSum.ZeroString);
bool actual = self.HasZeroHash();
Assert.True(actual);
}
#endregion
// TODO: Change when Machine retrieval gets fixed
#region GetKey
[Theory]
[InlineData(ItemKey.NULL, false, false, "")]
[InlineData(ItemKey.NULL, false, true, "")]
[InlineData(ItemKey.NULL, true, false, "")]
[InlineData(ItemKey.NULL, true, true, "")]
[InlineData(ItemKey.Machine, false, false, "0000000000-Machine")]
[InlineData(ItemKey.Machine, false, true, "Machine")]
[InlineData(ItemKey.Machine, true, false, "0000000000-machine")]
[InlineData(ItemKey.Machine, true, true, "machine")]
[InlineData(ItemKey.CRC, false, false, "DEADBEEF")]
[InlineData(ItemKey.CRC, false, true, "DEADBEEF")]
[InlineData(ItemKey.CRC, true, false, "deadbeef")]
[InlineData(ItemKey.CRC, true, true, "deadbeef")]
[InlineData(ItemKey.MD2, false, false, "DEADBEEF")]
[InlineData(ItemKey.MD2, false, true, "DEADBEEF")]
[InlineData(ItemKey.MD2, true, false, "deadbeef")]
[InlineData(ItemKey.MD2, true, true, "deadbeef")]
[InlineData(ItemKey.MD4, false, false, "DEADBEEF")]
[InlineData(ItemKey.MD4, false, true, "DEADBEEF")]
[InlineData(ItemKey.MD4, true, false, "deadbeef")]
[InlineData(ItemKey.MD4, true, true, "deadbeef")]
[InlineData(ItemKey.MD5, false, false, "DEADBEEF")]
[InlineData(ItemKey.MD5, false, true, "DEADBEEF")]
[InlineData(ItemKey.MD5, true, false, "deadbeef")]
[InlineData(ItemKey.MD5, true, true, "deadbeef")]
[InlineData(ItemKey.RIPEMD128, false, false, "DEADBEEF")]
[InlineData(ItemKey.RIPEMD128, false, true, "DEADBEEF")]
[InlineData(ItemKey.RIPEMD128, true, false, "deadbeef")]
[InlineData(ItemKey.RIPEMD128, true, true, "deadbeef")]
[InlineData(ItemKey.RIPEMD160, false, false, "DEADBEEF")]
[InlineData(ItemKey.RIPEMD160, false, true, "DEADBEEF")]
[InlineData(ItemKey.RIPEMD160, true, false, "deadbeef")]
[InlineData(ItemKey.RIPEMD160, true, true, "deadbeef")]
[InlineData(ItemKey.SHA1, false, false, "DEADBEEF")]
[InlineData(ItemKey.SHA1, false, true, "DEADBEEF")]
[InlineData(ItemKey.SHA1, true, false, "deadbeef")]
[InlineData(ItemKey.SHA1, true, true, "deadbeef")]
[InlineData(ItemKey.SHA256, false, false, "DEADBEEF")]
[InlineData(ItemKey.SHA256, false, true, "DEADBEEF")]
[InlineData(ItemKey.SHA256, true, false, "deadbeef")]
[InlineData(ItemKey.SHA256, true, true, "deadbeef")]
[InlineData(ItemKey.SHA384, false, false, "DEADBEEF")]
[InlineData(ItemKey.SHA384, false, true, "DEADBEEF")]
[InlineData(ItemKey.SHA384, true, false, "deadbeef")]
[InlineData(ItemKey.SHA384, true, true, "deadbeef")]
[InlineData(ItemKey.SHA512, false, false, "DEADBEEF")]
[InlineData(ItemKey.SHA512, false, true, "DEADBEEF")]
[InlineData(ItemKey.SHA512, true, false, "deadbeef")]
[InlineData(ItemKey.SHA512, true, true, "deadbeef")]
[InlineData(ItemKey.SpamSum, false, false, "DEADBEEF")]
[InlineData(ItemKey.SpamSum, false, true, "DEADBEEF")]
[InlineData(ItemKey.SpamSum, true, false, "deadbeef")]
[InlineData(ItemKey.SpamSum, true, true, "deadbeef")]
public void GetKeyDBTest(ItemKey bucketedBy, bool lower, bool norename, string expected)
{
Source source = new Source(0);
Machine machine = new Machine();
machine.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "Machine");
DatItem datItem = new Rom();
datItem.SetFieldValue(Data.Models.Metadata.Rom.CRCKey, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.MD2Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.MD4Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.MD5Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD128Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.RIPEMD160Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.SHA1Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.SHA256Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.SHA384Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.SHA512Key, "DEADBEEF");
datItem.SetFieldValue(Data.Models.Metadata.Rom.SpamSumKey, "DEADBEEF");
string actual = datItem.GetKey(bucketedBy, machine, source, lower, norename);
Assert.Equal(expected, actual);
}
#endregion
}
}

View File

@@ -0,0 +1,87 @@
using Xunit;
namespace SabreTools.Metadata.DatItems.Test
{
public class MachineTests
{
#region Clone
[Fact]
public void CloneTest()
{
Machine item = new Machine();
item.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "name");
object clone = item.Clone();
Machine? actual = clone as Machine;
Assert.NotNull(actual);
Assert.Equal("name", actual.GetName());
}
#endregion
#region GetInternalClone
[Fact]
public void GetInternalCloneTest()
{
Machine item = new Machine();
item.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "name");
Data.Models.Metadata.Machine actual = item.GetInternalClone();
Assert.Equal("name", actual[Data.Models.Metadata.Machine.NameKey]);
}
#endregion
#region Equals
[Fact]
public void Equals_Null_False()
{
Machine self = new Machine();
Machine? other = null;
bool actual = self.Equals(other);
Assert.False(actual);
}
[Fact]
public void Equals_DefaultInternal_True()
{
Machine self = new Machine();
Machine? other = new Machine();
bool actual = self.Equals(other);
Assert.True(actual);
}
[Fact]
public void Equals_MismatchedInternal_False()
{
Machine self = new Machine();
self.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "self");
Machine? other = new Machine();
other.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "other");
bool actual = self.Equals(other);
Assert.False(actual);
}
[Fact]
public void Equals_EqualInternal_True()
{
Machine self = new Machine();
self.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "name");
Machine? other = new Machine();
other.SetFieldValue(Data.Models.Metadata.Machine.NameKey, "name");
bool actual = self.Equals(other);
Assert.True(actual);
}
#endregion
}
}

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="8.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageReference Include="SabreTools.Hashing" Version="[2.0.0]" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SabreTools.Metadata.DatItems\SabreTools.Metadata.DatItems.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,23 @@
using Xunit;
namespace SabreTools.Metadata.DatItems.Test
{
public class SourceTests
{
#region Clone
[Fact]
public void CloneTest()
{
Source item = new Source(1, source: "src");
object clone = item.Clone();
Source? actual = clone as Source;
Assert.NotNull(actual);
Assert.Equal(1, actual.Index);
Assert.Equal("src", actual.Name);
}
#endregion
}
}

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
}
}

Some files were not shown because too many files have changed in this diff Show More