mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-09-22 06:45:11 +00:00
Port metadata functionality from ST
This commit is contained in:
25
SabreTools.Metadata.DatFiles/Formats/ArchiveDotOrg.cs
Normal file
25
SabreTools.Metadata.DatFiles/Formats/ArchiveDotOrg.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
SabreTools.Metadata.DatFiles/Formats/AttractMode.cs
Normal file
38
SabreTools.Metadata.DatFiles/Formats/AttractMode.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
203
SabreTools.Metadata.DatFiles/Formats/ClrMamePro.cs
Normal file
203
SabreTools.Metadata.DatFiles/Formats/ClrMamePro.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
SabreTools.Metadata.DatFiles/Formats/DosCenter.cs
Normal file
55
SabreTools.Metadata.DatFiles/Formats/DosCenter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
SabreTools.Metadata.DatFiles/Formats/EverdriveSmdb.cs
Normal file
55
SabreTools.Metadata.DatFiles/Formats/EverdriveSmdb.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
601
SabreTools.Metadata.DatFiles/Formats/Hashfile.cs
Normal file
601
SabreTools.Metadata.DatFiles/Formats/Hashfile.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
63
SabreTools.Metadata.DatFiles/Formats/Listrom.cs
Normal file
63
SabreTools.Metadata.DatFiles/Formats/Listrom.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
397
SabreTools.Metadata.DatFiles/Formats/Listxml.cs
Normal file
397
SabreTools.Metadata.DatFiles/Formats/Listxml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
404
SabreTools.Metadata.DatFiles/Formats/Logiqx.cs
Normal file
404
SabreTools.Metadata.DatFiles/Formats/Logiqx.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
214
SabreTools.Metadata.DatFiles/Formats/Missfile.cs
Normal file
214
SabreTools.Metadata.DatFiles/Formats/Missfile.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
47
SabreTools.Metadata.DatFiles/Formats/OfflineList.cs
Normal file
47
SabreTools.Metadata.DatFiles/Formats/OfflineList.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
88
SabreTools.Metadata.DatFiles/Formats/OpenMSX.cs
Normal file
88
SabreTools.Metadata.DatFiles/Formats/OpenMSX.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
SabreTools.Metadata.DatFiles/Formats/RomCenter.cs
Normal file
51
SabreTools.Metadata.DatFiles/Formats/RomCenter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
719
SabreTools.Metadata.DatFiles/Formats/SabreJSON.cs
Normal file
719
SabreTools.Metadata.DatFiles/Formats/SabreJSON.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
522
SabreTools.Metadata.DatFiles/Formats/SabreXML.cs
Normal file
522
SabreTools.Metadata.DatFiles/Formats/SabreXML.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
191
SabreTools.Metadata.DatFiles/Formats/SeparatedValue.cs
Normal file
191
SabreTools.Metadata.DatFiles/Formats/SeparatedValue.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
121
SabreTools.Metadata.DatFiles/Formats/SerializableDatFile.cs
Normal file
121
SabreTools.Metadata.DatFiles/Formats/SerializableDatFile.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
213
SabreTools.Metadata.DatFiles/Formats/SoftwareList.cs
Normal file
213
SabreTools.Metadata.DatFiles/Formats/SoftwareList.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user