2020-12-10 10:58:00 -08:00
using System ;
2022-11-03 16:46:53 -07:00
using System.Collections.Generic ;
2020-12-10 10:58:00 -08:00
using System.IO ;
2017-09-25 12:21:52 -07:00
using System.Linq ;
2020-09-07 14:47:27 -07:00
using System.Xml.Serialization ;
2022-11-03 16:05:07 -07:00
using Newtonsoft.Json ;
2020-12-08 13:23:59 -08:00
using SabreTools.Core ;
2024-03-12 22:52:36 -04:00
using SabreTools.Core.Filter ;
2020-12-10 22:16:53 -08:00
using SabreTools.Core.Tools ;
2020-12-09 22:11:35 -08:00
using SabreTools.DatFiles.Formats ;
2020-12-10 10:58:00 -08:00
using SabreTools.DatItems ;
2021-02-02 10:23:43 -08:00
using SabreTools.DatItems.Formats ;
2024-03-04 23:56:05 -05:00
using SabreTools.Hashing ;
2020-12-07 14:29:45 -08:00
using SabreTools.Logging ;
2024-03-12 19:54:43 -04:00
using SabreTools.Serialization.Interfaces ;
2016-10-26 22:10:47 -07:00
2020-12-08 16:37:08 -08:00
namespace SabreTools.DatFiles
2016-04-19 01:11:23 -07:00
{
2019-01-08 11:49:31 -08:00
/// <summary>
/// Represents a format-agnostic DAT
/// </summary>
2020-09-08 10:12:41 -07:00
[JsonObject("datfile"), XmlRoot("datfile")]
2024-03-10 00:20:56 -05:00
public abstract partial class DatFile
2019-01-08 11:49:31 -08:00
{
2020-07-31 14:04:10 -07:00
#region Fields
2019-01-08 11:49:31 -08:00
/// <summary>
2020-07-27 10:26:08 -07:00
/// Header values
2019-01-08 11:49:31 -08:00
/// </summary>
2020-10-07 15:42:30 -07:00
[JsonProperty("header"), XmlElement("header")]
2020-07-27 10:26:08 -07:00
public DatHeader Header { get ; set ; } = new DatHeader ( ) ;
2019-01-08 11:49:31 -08:00
/// <summary>
2020-07-27 10:26:08 -07:00
/// DatItems and related statistics
2019-01-08 11:49:31 -08:00
/// </summary>
2020-10-07 15:42:30 -07:00
[JsonProperty("items"), XmlElement("items")]
2024-02-28 19:19:50 -05:00
public ItemDictionary Items { get ; set ; } = [ ] ;
2019-01-08 11:49:31 -08:00
#endregion
2020-10-07 15:42:30 -07:00
#region Logging
/// <summary>
/// Logging object
/// </summary>
[JsonIgnore, XmlIgnore]
2020-10-07 16:37:10 -07:00
protected Logger logger ;
2020-10-07 15:42:30 -07:00
#endregion
2020-07-15 09:41:59 -07:00
#region Constructors
2019-01-08 11:49:31 -08:00
/// <summary>
2020-07-15 09:41:59 -07:00
/// Create a new DatFile from an existing one
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
/// <param name="datFile">DatFile to get the values from</param>
2023-08-10 23:22:14 -04:00
public DatFile ( DatFile ? datFile )
2019-01-08 11:49:31 -08:00
{
2020-10-07 16:37:10 -07:00
logger = new Logger ( this ) ;
2020-07-15 09:41:59 -07:00
if ( datFile ! = null )
2019-01-08 11:49:31 -08:00
{
2020-07-27 10:26:08 -07:00
Header = datFile . Header ;
2020-07-31 14:04:10 -07:00
Items = datFile . Items ;
2019-01-08 11:49:31 -08:00
}
}
/// <summary>
2020-07-15 09:41:59 -07:00
/// Create a specific type of DatFile to be used based on a format and a base DAT
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
/// <param name="datFormat">Format of the DAT to be created</param>
/// <param name="baseDat">DatFile containing the information to use in specific operations</param>
2020-09-20 21:12:57 -07:00
/// <param name="quotes">For relevant types, assume the usage of quotes</param>
2020-07-15 09:41:59 -07:00
/// <returns>DatFile of the specific internal type that corresponds to the inputs</returns>
2023-08-10 23:22:14 -04:00
public static DatFile Create ( DatFormat ? datFormat = null , DatFile ? baseDat = null , bool quotes = true )
2019-01-08 11:49:31 -08:00
{
2020-12-14 16:01:28 -08:00
return datFormat switch
2019-01-08 11:49:31 -08:00
{
2021-07-19 10:39:21 -07:00
DatFormat . ArchiveDotOrg = > new ArchiveDotOrg ( baseDat ) ,
2020-12-14 16:01:28 -08:00
DatFormat . AttractMode = > new AttractMode ( baseDat ) ,
DatFormat . ClrMamePro = > new ClrMamePro ( baseDat , quotes ) ,
2024-03-12 22:03:18 -04:00
DatFormat . CSV = > new CommaSeparatedValue ( baseDat ) ,
2020-12-14 16:01:28 -08:00
DatFormat . DOSCenter = > new DosCenter ( baseDat ) ,
DatFormat . EverdriveSMDB = > new EverdriveSMDB ( baseDat ) ,
DatFormat . Listrom = > new Listrom ( baseDat ) ,
DatFormat . Listxml = > new Listxml ( baseDat ) ,
DatFormat . Logiqx = > new Logiqx ( baseDat , false ) ,
DatFormat . LogiqxDeprecated = > new Logiqx ( baseDat , true ) ,
DatFormat . MissFile = > new Missfile ( baseDat ) ,
DatFormat . OfflineList = > new OfflineList ( baseDat ) ,
DatFormat . OpenMSX = > new OpenMSX ( baseDat ) ,
2024-03-12 22:03:18 -04:00
DatFormat . RedumpMD5 = > new Md5File ( baseDat ) ,
DatFormat . RedumpSFV = > new SfvFile ( baseDat ) ,
DatFormat . RedumpSHA1 = > new Sha1File ( baseDat ) ,
DatFormat . RedumpSHA256 = > new Sha256File ( baseDat ) ,
DatFormat . RedumpSHA384 = > new Sha384File ( baseDat ) ,
DatFormat . RedumpSHA512 = > new Sha512File ( baseDat ) ,
DatFormat . RedumpSpamSum = > new SpamSumFile ( baseDat ) ,
2020-12-14 16:01:28 -08:00
DatFormat . RomCenter = > new RomCenter ( baseDat ) ,
DatFormat . SabreJSON = > new SabreJSON ( baseDat ) ,
DatFormat . SabreXML = > new SabreXML ( baseDat ) ,
DatFormat . SoftwareList = > new Formats . SoftwareList ( baseDat ) ,
2024-03-12 22:03:18 -04:00
DatFormat . SSV = > new SemicolonSeparatedValue ( baseDat ) ,
DatFormat . TSV = > new TabSeparatedValue ( baseDat ) ,
2023-08-10 23:22:14 -04:00
2020-07-15 09:41:59 -07:00
// We use new-style Logiqx as a backup for generic DatFile
2020-12-14 16:01:28 -08:00
_ = > new Logiqx ( baseDat , false ) ,
} ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
2020-07-15 09:41:59 -07:00
/// Create a new DatFile from an existing DatHeader
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
/// <param name="datHeader">DatHeader to get the values from</param>
public static DatFile Create ( DatHeader datHeader )
2019-01-08 11:49:31 -08:00
{
2024-03-10 21:54:07 -04:00
DatFile datFile = Create ( datHeader . GetFieldValue < DatFormat > ( DatHeader . DatFormatKey ) ) ;
2020-07-27 10:26:08 -07:00
datFile . Header = ( DatHeader ) datHeader . Clone ( ) ;
2020-07-15 09:41:59 -07:00
return datFile ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
2020-08-27 20:56:50 -07:00
/// Fill the header values based on existing Header and path
/// </summary>
/// <param name="path">Path used for creating a name, if necessary</param>
/// <param name="bare">True if the date should be omitted from name and description, false otherwise</param>
public void FillHeaderFromPath ( string path , bool bare )
{
// If the description is defined but not the name, set the name from the description
2024-03-11 15:46:44 -04:00
if ( string . IsNullOrEmpty ( Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) ) & & ! string . IsNullOrEmpty ( Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) ) )
2020-08-27 20:56:50 -07:00
{
2024-03-11 15:46:44 -04:00
Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) ) ;
2020-08-27 20:56:50 -07:00
}
// If the name is defined but not the description, set the description from the name
2024-03-11 15:46:44 -04:00
else if ( ! string . IsNullOrEmpty ( Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) ) & & string . IsNullOrEmpty ( Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) ) )
2020-08-27 20:56:50 -07:00
{
2024-03-11 15:46:44 -04:00
Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + ( bare ? string . Empty : $" ({Header.GetStringFieldValue(Models.Metadata.Header.DateKey)})" ) ) ;
2020-08-27 20:56:50 -07:00
}
// If neither the name or description are defined, set them from the automatic values
2024-03-11 15:46:44 -04:00
else if ( string . IsNullOrEmpty ( Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) ) & & string . IsNullOrEmpty ( Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) ) )
2020-08-27 20:56:50 -07:00
{
string [ ] splitpath = path . TrimEnd ( Path . DirectorySeparatorChar ) . Split ( Path . DirectorySeparatorChar ) ;
2024-03-10 04:10:37 -04:00
Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , splitpath . Last ( ) ) ;
2024-03-11 15:46:44 -04:00
Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + ( bare ? string . Empty : $" ({Header.GetStringFieldValue(Models.Metadata.Header.DateKey)})" ) ) ;
2020-08-27 20:56:50 -07:00
}
}
2020-07-15 09:41:59 -07:00
#endregion
2023-08-10 23:22:14 -04:00
2024-03-05 02:52:53 -05:00
#region Filtering
/// <summary>
/// Execute all filters in a filter runner on the items in the dictionary
/// </summary>
/// <param name="filterRunner">Preconfigured filter runner to use</param>
public void ExecuteFilters ( FilterRunner filterRunner )
2024-03-19 14:25:54 -04:00
= > Items . ExecuteFilters ( filterRunner ) ;
2024-03-05 02:52:53 -05:00
#endregion
2020-12-10 10:58:00 -08:00
#region Parsing
/// <summary>
/// Parse DatFile and return all found games and roms within
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
/// <param name="keep">True if full pathnames are to be kept, false otherwise (default)</param>
2020-12-23 13:55:09 -08:00
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
2020-12-10 10:58:00 -08:00
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
2020-12-23 13:55:09 -08:00
public abstract void ParseFile ( string filename , int indexId , bool keep , bool statsOnly = false , bool throwOnError = false ) ;
2020-12-10 10:58:00 -08:00
/// <summary>
/// Add a rom to the Dat after checking
/// </summary>
/// <param name="item">Item data to check against</param>
2020-12-23 13:55:09 -08:00
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
2020-12-10 10:58:00 -08:00
/// <returns>The key for the item</returns>
2020-12-23 13:55:09 -08:00
protected string ParseAddHelper ( DatItem item , bool statsOnly )
2024-03-19 14:39:57 -04:00
= > Items . AddItem ( item , statsOnly ) ;
2020-12-10 10:58:00 -08:00
#endregion
2023-08-10 23:22:14 -04:00
2020-12-10 11:28:11 -08:00
#region Writing
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="outfile">Name of the file to write to</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public abstract bool WriteToFile ( string outfile , bool ignoreblanks = false , bool throwOnError = false ) ;
/// <summary>
/// Create a prefix or postfix from inputs
/// </summary>
/// <param name="item">DatItem to create a prefix/postfix for</param>
/// <param name="prefix">True for prefix, false for postfix</param>
/// <returns>Sanitized string representing the postfix or prefix</returns>
protected string CreatePrefixPostfix ( DatItem item , bool prefix )
{
// Initialize strings
2020-12-14 16:01:28 -08:00
string fix ,
2024-03-11 15:46:44 -04:00
game = item . GetFieldValue < Machine > ( DatItem . MachineKey ) ! . GetStringFieldValue ( Models . Metadata . Machine . NameKey ) ? ? string . Empty ,
2024-03-11 16:26:28 -04:00
name = item . GetName ( ) ? ? item . GetStringFieldValue ( Models . Metadata . DatItem . TypeKey ) . AsEnumValue < ItemType > ( ) . AsStringValue ( ) ? ? string . Empty ,
2020-12-10 11:28:11 -08:00
crc = string . Empty ,
md5 = string . Empty ,
sha1 = string . Empty ,
sha256 = string . Empty ,
sha384 = string . Empty ,
sha512 = string . Empty ,
size = string . Empty ,
spamsum = string . Empty ;
// If we have a prefix
if ( prefix )
2024-03-11 15:46:44 -04:00
fix = Header . GetStringFieldValue ( DatHeader . PrefixKey ) + ( Header . GetBoolFieldValue ( DatHeader . QuotesKey ) = = true ? "\"" : string . Empty ) ;
2020-12-10 11:28:11 -08:00
// If we have a postfix
else
2024-03-11 15:46:44 -04:00
fix = ( Header . GetBoolFieldValue ( DatHeader . QuotesKey ) = = true ? "\"" : string . Empty ) + Header . GetStringFieldValue ( DatHeader . PostfixKey ) ;
2020-12-10 11:28:11 -08:00
// Ensure we have the proper values for replacement
2024-03-10 16:49:07 -04:00
if ( item is Disk disk )
2020-12-10 11:28:11 -08:00
{
2024-03-11 15:46:44 -04:00
md5 = disk . GetStringFieldValue ( Models . Metadata . Disk . MD5Key ) ? ? string . Empty ;
sha1 = disk . GetStringFieldValue ( Models . Metadata . Disk . SHA1Key ) ? ? string . Empty ;
2020-12-10 11:28:11 -08:00
}
2024-03-10 16:49:07 -04:00
else if ( item is Media media )
2020-12-10 11:28:11 -08:00
{
2024-03-11 15:46:44 -04:00
md5 = media . GetStringFieldValue ( Models . Metadata . Media . MD5Key ) ? ? string . Empty ;
sha1 = media . GetStringFieldValue ( Models . Metadata . Media . SHA1Key ) ? ? string . Empty ;
sha256 = media . GetStringFieldValue ( Models . Metadata . Media . SHA256Key ) ? ? string . Empty ;
spamsum = media . GetStringFieldValue ( Models . Metadata . Media . SpamSumKey ) ? ? string . Empty ;
2020-12-10 11:28:11 -08:00
}
2024-03-10 16:49:07 -04:00
else if ( item is Rom rom )
2020-12-10 11:28:11 -08:00
{
2024-03-11 15:46:44 -04:00
crc = rom . GetStringFieldValue ( Models . Metadata . Rom . CRCKey ) ? ? string . Empty ;
md5 = rom . GetStringFieldValue ( Models . Metadata . Rom . MD5Key ) ? ? string . Empty ;
sha1 = rom . GetStringFieldValue ( Models . Metadata . Rom . SHA1Key ) ? ? string . Empty ;
sha256 = rom . GetStringFieldValue ( Models . Metadata . Rom . SHA256Key ) ? ? string . Empty ;
sha384 = rom . GetStringFieldValue ( Models . Metadata . Rom . SHA384Key ) ? ? string . Empty ;
sha512 = rom . GetStringFieldValue ( Models . Metadata . Rom . SHA512Key ) ? ? string . Empty ;
size = rom . GetInt64FieldValue ( Models . Metadata . Rom . SizeKey ) . ToString ( ) ? ? string . Empty ;
spamsum = rom . GetStringFieldValue ( Models . Metadata . Rom . SpamSumKey ) ? ? string . Empty ;
2020-12-10 11:28:11 -08:00
}
// Now do bulk replacement where possible
2024-03-11 21:30:24 -04:00
var machine = item . GetFieldValue < Machine > ( DatItem . MachineKey ) ;
2020-12-10 11:28:11 -08:00
fix = fix
. Replace ( "%game%" , game )
. Replace ( "%machine%" , game )
. Replace ( "%name%" , name )
2024-03-11 21:30:24 -04:00
. Replace ( "%manufacturer%" , machine ! . GetStringFieldValue ( Models . Metadata . Machine . ManufacturerKey ) ? ? string . Empty )
. Replace ( "%publisher%" , machine ! . GetStringFieldValue ( Models . Metadata . Machine . PublisherKey ) ? ? string . Empty )
. Replace ( "%category%" , machine ! . GetStringFieldValue ( Models . Metadata . Machine . CategoryKey ) ? ? string . Empty )
2020-12-10 11:28:11 -08:00
. Replace ( "%crc%" , crc )
. Replace ( "%md5%" , md5 )
. Replace ( "%sha1%" , sha1 )
. Replace ( "%sha256%" , sha256 )
. Replace ( "%sha384%" , sha384 )
. Replace ( "%sha512%" , sha512 )
. Replace ( "%size%" , size )
. Replace ( "%spamsum%" , spamsum ) ;
return fix ;
}
/// <summary>
/// Process an item and correctly set the item name
/// </summary>
/// <param name="item">DatItem to update</param>
/// <param name="forceRemoveQuotes">True if the Quotes flag should be ignored, false otherwise</param>
/// <param name="forceRomName">True if the UseRomName should be always on (default), false otherwise</param>
protected void ProcessItemName ( DatItem item , bool forceRemoveQuotes , bool forceRomName = true )
{
// Backup relevant values and set new ones accordingly
2024-03-11 15:46:44 -04:00
bool? quotesBackup = Header . GetBoolFieldValue ( DatHeader . QuotesKey ) ;
bool? useRomNameBackup = Header . GetBoolFieldValue ( DatHeader . UseRomNameKey ) ;
2020-12-10 11:28:11 -08:00
if ( forceRemoveQuotes )
2024-03-10 16:49:07 -04:00
Header . SetFieldValue < bool > ( DatHeader . QuotesKey , false ) ;
2020-12-10 11:28:11 -08:00
if ( forceRomName )
2024-03-10 16:49:07 -04:00
Header . SetFieldValue < bool > ( DatHeader . UseRomNameKey , true ) ;
2020-12-10 11:28:11 -08:00
2021-03-19 20:56:12 -07:00
// Get the name to update
2024-03-11 15:46:44 -04:00
string? name = ( Header . GetBoolFieldValue ( DatHeader . UseRomNameKey ) = = true
2024-03-10 16:49:07 -04:00
? item . GetName ( )
2024-03-11 15:46:44 -04:00
: item . GetFieldValue < Machine > ( DatItem . MachineKey ) ! . GetStringFieldValue ( Models . Metadata . Machine . NameKey ) ) ? ? string . Empty ;
2021-03-19 20:56:12 -07:00
2020-12-10 11:28:11 -08:00
// Create the proper Prefix and Postfix
string pre = CreatePrefixPostfix ( item , true ) ;
string post = CreatePrefixPostfix ( item , false ) ;
// If we're in Depot mode, take care of that instead
2024-03-10 22:08:08 -04:00
if ( Header . GetFieldValue < DepotInformation ? > ( DatHeader . OutputDepotKey ) ? . IsActive = = true )
2020-12-10 11:28:11 -08:00
{
2024-03-10 16:49:07 -04:00
if ( item is Disk disk )
2020-12-10 11:28:11 -08:00
{
// We can only write out if there's a SHA-1
2024-03-11 15:46:44 -04:00
if ( ! string . IsNullOrEmpty ( disk . GetStringFieldValue ( Models . Metadata . Disk . SHA1Key ) ) )
2020-12-10 11:28:11 -08:00
{
2024-03-11 15:46:44 -04:00
name = Utilities . GetDepotPath ( disk . GetStringFieldValue ( Models . Metadata . Disk . SHA1Key ) , Header . GetFieldValue < DepotInformation ? > ( DatHeader . OutputDepotKey ) ! . Depth ) ? . Replace ( '\\' , '/' ) ;
2020-12-14 10:11:20 -08:00
item . SetName ( $"{pre}{name}{post}" ) ;
2020-12-10 11:28:11 -08:00
}
}
2024-03-10 16:49:07 -04:00
else if ( item is Media media )
2020-12-10 11:28:11 -08:00
{
// We can only write out if there's a SHA-1
2024-03-11 15:46:44 -04:00
if ( ! string . IsNullOrEmpty ( media . GetStringFieldValue ( Models . Metadata . Media . SHA1Key ) ) )
2020-12-10 11:28:11 -08:00
{
2024-03-11 15:46:44 -04:00
name = Utilities . GetDepotPath ( media . GetStringFieldValue ( Models . Metadata . Media . SHA1Key ) , Header . GetFieldValue < DepotInformation ? > ( DatHeader . OutputDepotKey ) ! . Depth ) ? . Replace ( '\\' , '/' ) ;
2020-12-14 10:11:20 -08:00
item . SetName ( $"{pre}{name}{post}" ) ;
2020-12-10 11:28:11 -08:00
}
}
2024-03-10 16:49:07 -04:00
else if ( item is Rom rom )
2020-12-10 11:28:11 -08:00
{
// We can only write out if there's a SHA-1
2024-03-11 15:46:44 -04:00
if ( ! string . IsNullOrEmpty ( rom . GetStringFieldValue ( Models . Metadata . Rom . SHA1Key ) ) )
2020-12-10 11:28:11 -08:00
{
2024-03-11 15:46:44 -04:00
name = Utilities . GetDepotPath ( rom . GetStringFieldValue ( Models . Metadata . Rom . SHA1Key ) , Header . GetFieldValue < DepotInformation ? > ( DatHeader . OutputDepotKey ) ! . Depth ) ? . Replace ( '\\' , '/' ) ;
2020-12-14 10:11:20 -08:00
item . SetName ( $"{pre}{name}{post}" ) ;
2020-12-10 11:28:11 -08:00
}
}
return ;
}
2024-03-11 15:46:44 -04:00
if ( ! string . IsNullOrEmpty ( Header . GetStringFieldValue ( DatHeader . ReplaceExtensionKey ) ) | | Header . GetBoolFieldValue ( DatHeader . RemoveExtensionKey ) = = true )
2020-12-10 11:28:11 -08:00
{
2024-03-11 15:46:44 -04:00
if ( Header . GetBoolFieldValue ( DatHeader . RemoveExtensionKey ) = = true )
2024-03-10 16:49:07 -04:00
Header . SetFieldValue < string? > ( DatHeader . ReplaceExtensionKey , string . Empty ) ;
2020-12-10 11:28:11 -08:00
2023-08-10 23:22:14 -04:00
string? dir = Path . GetDirectoryName ( name ) ;
if ( dir ! = null )
{
dir = dir . TrimStart ( Path . DirectorySeparatorChar ) ;
2024-03-11 15:46:44 -04:00
name = Path . Combine ( dir , Path . GetFileNameWithoutExtension ( name ) + Header . GetStringFieldValue ( DatHeader . ReplaceExtensionKey ) ) ;
2023-08-10 23:22:14 -04:00
}
2020-12-10 11:28:11 -08:00
}
2024-03-11 15:46:44 -04:00
if ( ! string . IsNullOrEmpty ( Header . GetStringFieldValue ( DatHeader . AddExtensionKey ) ) )
name + = Header . GetStringFieldValue ( DatHeader . AddExtensionKey ) ;
2020-12-10 11:28:11 -08:00
2024-03-11 15:46:44 -04:00
if ( Header . GetBoolFieldValue ( DatHeader . UseRomNameKey ) = = true & & Header . GetBoolFieldValue ( DatHeader . GameNameKey ) = = true )
name = Path . Combine ( item . GetFieldValue < Machine > ( DatItem . MachineKey ) ! . GetStringFieldValue ( Models . Metadata . Machine . NameKey ) ? ? string . Empty , name ) ;
2020-12-10 11:28:11 -08:00
2021-03-19 20:56:12 -07:00
// Now assign back the formatted name
name = $"{pre}{name}{post}" ;
2024-03-11 15:46:44 -04:00
if ( Header . GetBoolFieldValue ( DatHeader . UseRomNameKey ) = = true )
2021-03-19 20:56:12 -07:00
item . SetName ( name ) ;
2024-03-10 16:49:07 -04:00
else if ( item . GetFieldValue < Machine > ( DatItem . MachineKey ) ! = null )
item . GetFieldValue < Machine > ( DatItem . MachineKey ) ! . SetFieldValue < string? > ( Models . Metadata . Machine . NameKey , name ) ;
2020-12-10 11:28:11 -08:00
// Restore all relevant values
if ( forceRemoveQuotes )
2024-03-11 15:46:44 -04:00
Header . SetFieldValue < bool? > ( DatHeader . QuotesKey , quotesBackup ) ;
2020-12-10 11:28:11 -08:00
if ( forceRomName )
2024-03-11 15:46:44 -04:00
Header . SetFieldValue < bool? > ( DatHeader . UseRomNameKey , useRomNameBackup ) ;
2020-12-10 11:28:11 -08:00
}
/// <summary>
/// Process any DatItems that are "null", usually created from directory population
/// </summary>
/// <param name="datItem">DatItem to check for "null" status</param>
/// <returns>Cleaned DatItem</returns>
protected DatItem ProcessNullifiedItem ( DatItem datItem )
{
// If we don't have a Rom, we can ignore it
2023-08-10 23:22:14 -04:00
if ( datItem is not Rom rom )
return datItem ;
2020-12-10 11:28:11 -08:00
// If the Rom has "null" characteristics, ensure all fields
2024-03-11 15:46:44 -04:00
if ( rom . GetInt64FieldValue ( Models . Metadata . Rom . SizeKey ) = = null & & rom . GetStringFieldValue ( Models . Metadata . Rom . CRCKey ) = = "null" )
2020-12-10 11:28:11 -08:00
{
2024-03-11 15:46:44 -04:00
logger . Verbose ( $"Empty folder found: {datItem.GetFieldValue<Machine>(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)}" ) ;
2020-12-10 11:28:11 -08:00
2024-03-08 20:42:24 -05:00
rom . SetName ( rom . GetName ( ) = = "null" ? "-" : rom . GetName ( ) ) ;
2024-03-11 15:23:10 -04:00
rom . SetFieldValue < string? > ( Models . Metadata . Rom . SizeKey , Constants . SizeZero . ToString ( ) ) ;
2024-03-11 15:46:44 -04:00
rom . SetFieldValue < string? > ( Models . Metadata . Rom . CRCKey , rom . GetStringFieldValue ( Models . Metadata . Rom . CRCKey ) = = "null" ? Constants . CRCZero : null ) ;
rom . SetFieldValue < string? > ( Models . Metadata . Rom . MD5Key , rom . GetStringFieldValue ( Models . Metadata . Rom . MD5Key ) = = "null" ? Constants . MD5Zero : null ) ;
rom . SetFieldValue < string? > ( Models . Metadata . Rom . SHA1Key , rom . GetStringFieldValue ( Models . Metadata . Rom . SHA1Key ) = = "null" ? Constants . SHA1Zero : null ) ;
rom . SetFieldValue < string? > ( Models . Metadata . Rom . SHA256Key , rom . GetStringFieldValue ( Models . Metadata . Rom . SHA256Key ) = = "null" ? Constants . SHA256Zero : null ) ;
rom . SetFieldValue < string? > ( Models . Metadata . Rom . SHA384Key , rom . GetStringFieldValue ( Models . Metadata . Rom . SHA384Key ) = = "null" ? Constants . SHA384Zero : null ) ;
rom . SetFieldValue < string? > ( Models . Metadata . Rom . SHA512Key , rom . GetStringFieldValue ( Models . Metadata . Rom . SHA512Key ) = = "null" ? Constants . SHA512Zero : null ) ;
rom . SetFieldValue < string? > ( Models . Metadata . Rom . SpamSumKey , rom . GetStringFieldValue ( Models . Metadata . Rom . SpamSumKey ) = = "null" ? Constants . SpamSumZero : null ) ;
2020-12-10 11:28:11 -08:00
}
return rom ;
}
/// <summary>
/// Get supported types for write
/// </summary>
/// <returns>List of supported types for writing</returns>
protected virtual ItemType [ ] GetSupportedTypes ( )
{
2024-02-28 19:19:50 -05:00
return Enum . GetValues ( typeof ( ItemType ) ) as ItemType [ ] ? ? [ ] ;
2020-12-10 11:28:11 -08:00
}
2022-11-03 16:14:42 -07:00
/// <summary>
2022-11-03 16:46:53 -07:00
/// Return list of required fields missing from a DatItem
2022-11-03 16:14:42 -07:00
/// </summary>
2022-11-03 16:46:53 -07:00
/// <returns>List of missing required fields, null or empty if none were found</returns>
2024-03-05 23:41:00 -05:00
protected virtual List < string > ? GetMissingRequiredFields ( DatItem datItem ) = > null ;
2022-11-03 16:14:42 -07:00
2020-12-10 11:28:11 -08:00
/// <summary>
/// Get if a machine contains any writable items
/// </summary>
/// <param name="datItems">DatItems to check</param>
/// <returns>True if the machine contains at least one writable item, false otherwise</returns>
/// <remarks>Empty machines are kept with this</remarks>
2021-07-18 21:00:01 -07:00
protected bool ContainsWritable ( ConcurrentList < DatItem > datItems )
2020-12-10 11:28:11 -08:00
{
// Empty machines are considered writable
if ( datItems = = null | | datItems . Count = = 0 )
return true ;
foreach ( DatItem datItem in datItems )
{
2024-03-11 16:26:28 -04:00
if ( GetSupportedTypes ( ) . Contains ( datItem . GetStringFieldValue ( Models . Metadata . DatItem . TypeKey ) . AsEnumValue < ItemType > ( ) ) )
2020-12-10 11:28:11 -08:00
return true ;
}
return false ;
}
/// <summary>
/// Get if an item should be ignored on write
/// </summary>
/// <param name="datItem">DatItem to check</param>
2022-11-03 16:05:07 -07:00
/// <param name="ignoreBlanks">True if blank roms should be skipped on output, false otherwise</param>
2020-12-10 11:28:11 -08:00
/// <returns>True if the item should be skipped on write, false otherwise</returns>
2023-08-10 23:22:14 -04:00
protected bool ShouldIgnore ( DatItem ? datItem , bool ignoreBlanks )
2020-12-10 11:28:11 -08:00
{
2022-11-03 16:29:06 -07:00
// If this is invoked with a null DatItem, we ignore
if ( datItem = = null )
{
logger ? . Verbose ( $"Item was skipped because it was null" ) ;
return true ;
}
2020-12-10 11:28:11 -08:00
// If the item is supposed to be removed, we ignore
2024-03-11 15:46:44 -04:00
if ( datItem . GetBoolFieldValue ( DatItem . RemoveKey ) = = true )
2022-11-03 16:27:58 -07:00
{
string itemString = JsonConvert . SerializeObject ( datItem , Formatting . None ) ;
logger ? . Verbose ( $"Item '{itemString}' was skipped because it was marked for removal" ) ;
2020-12-10 11:28:11 -08:00
return true ;
2022-11-03 16:27:58 -07:00
}
2020-12-10 11:28:11 -08:00
// If we have the Blank dat item, we ignore
2024-03-10 16:49:07 -04:00
if ( datItem is Blank )
2022-11-03 16:27:58 -07:00
{
string itemString = JsonConvert . SerializeObject ( datItem , Formatting . None ) ;
logger ? . Verbose ( $"Item '{itemString}' was skipped because it was of type 'Blank'" ) ;
2020-12-10 11:28:11 -08:00
return true ;
2022-11-03 16:27:58 -07:00
}
2020-12-10 11:28:11 -08:00
// If we're ignoring blanks and we have a Rom
2024-03-10 16:49:07 -04:00
if ( ignoreBlanks & & datItem is Rom rom )
2020-12-10 11:28:11 -08:00
{
// If we have a 0-size or blank rom, then we ignore
2024-03-11 15:46:44 -04:00
long? size = rom . GetInt64FieldValue ( Models . Metadata . Rom . SizeKey ) ;
2024-03-11 15:23:10 -04:00
if ( size = = 0 | | size = = null )
2022-11-03 16:27:58 -07:00
{
string itemString = JsonConvert . SerializeObject ( datItem , Formatting . None ) ;
logger ? . Verbose ( $"Item '{itemString}' was skipped because it had an invalid size" ) ;
2020-12-10 11:28:11 -08:00
return true ;
2022-11-03 16:27:58 -07:00
}
2020-12-10 11:28:11 -08:00
}
// If we have an item type not in the list of supported values
2024-03-11 16:26:28 -04:00
if ( ! GetSupportedTypes ( ) . Contains ( datItem . GetStringFieldValue ( Models . Metadata . DatItem . TypeKey ) . AsEnumValue < ItemType > ( ) ) )
2022-11-03 16:27:58 -07:00
{
string itemString = JsonConvert . SerializeObject ( datItem , Formatting . None ) ;
2024-03-10 21:54:07 -04:00
logger ? . Verbose ( $"Item '{itemString}' was skipped because it was not supported in {Header?.GetFieldValue<DatFormat>(DatHeader.DatFormatKey)}" ) ;
2020-12-10 11:28:11 -08:00
return true ;
2022-11-03 16:27:58 -07:00
}
2020-12-10 11:28:11 -08:00
2022-11-03 16:05:07 -07:00
// If we have an item with missing required fields
2024-03-05 23:41:00 -05:00
List < string > ? missingFields = GetMissingRequiredFields ( datItem ) ;
2022-12-22 09:27:02 -08:00
if ( missingFields ! = null & & missingFields . Count ! = 0 )
2022-11-03 16:27:58 -07:00
{
string itemString = JsonConvert . SerializeObject ( datItem , Formatting . None ) ;
2024-02-28 22:54:56 -05:00
#if NET20 | | NET35
2024-03-10 21:54:07 -04:00
logger ? . Verbose ( $"Item '{itemString}' was skipped because it was missing required fields for {Header?.GetFieldValue<DatFormat>(DatHeader.DatFormatKey)}: {string.Join(" , ", [.. missingFields])}" ) ;
2024-02-28 22:54:56 -05:00
#else
2024-03-10 21:54:07 -04:00
logger ? . Verbose ( $"Item '{itemString}' was skipped because it was missing required fields for {Header?.GetFieldValue<DatFormat>(DatHeader.DatFormatKey)}: {string.Join(" , ", missingFields)}" ) ;
2024-02-28 22:54:56 -05:00
#endif
2022-11-03 16:05:07 -07:00
return true ;
2022-11-03 16:27:58 -07:00
}
2022-11-03 16:01:03 -07:00
2020-12-10 11:28:11 -08:00
return false ;
}
#endregion
2019-01-08 11:49:31 -08:00
}
2024-03-12 19:54:43 -04:00
/// <summary>
/// Represents a DAT that can be serialized
/// </summary>
/// <typeparam name="T">Base internal model for the DAT type</typeparam>
/// <typeparam name="U">IFileSerializer type to use for conversion</typeparam>
/// <typeparam name="V">IModelSerializer for cross-model serialization</typeparam>
public abstract class SerializableDatFile < T , U , V > : DatFile
where U : IFileSerializer < T >
where V : IModelSerializer < T , Models . Metadata . MetadataFile >
{
/// <inheritdoc/>
protected SerializableDatFile ( DatFile ? datFile ) : base ( datFile ) { }
/// <inheritdoc/>
public override void ParseFile ( string filename , int indexId , bool keep , bool statsOnly = false , bool throwOnError = false )
{
try
{
// Deserialize the input file in two steps
var specificFormat = Activator . CreateInstance < U > ( ) . Deserialize ( filename ) ;
var internalFormat = Activator . CreateInstance < V > ( ) . Serialize ( specificFormat ) ;
// Convert to the internal format
ConvertMetadata ( internalFormat , filename , indexId , keep , statsOnly ) ;
}
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 = ConvertMetadata ( ignoreblanks ) ;
var specificFormat = Activator . CreateInstance < V > ( ) . Deserialize ( internalFormat ) ;
if ( ! Activator . CreateInstance < U > ( ) . Serialize ( 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 ;
}
}
2016-04-19 01:11:23 -07:00
}