2020-12-09 21:52:38 -08:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Net ;
2024-03-05 03:04:47 -05:00
#if NET40_OR_GREATER | | NETCOREAPP
2020-12-09 21:52:38 -08:00
using System.Threading.Tasks ;
2024-03-05 03:04:47 -05:00
#endif
2020-12-09 21:52:38 -08:00
using SabreTools.Core ;
2024-03-11 15:23:10 -04:00
using SabreTools.Core.Tools ;
2020-12-10 23:24:09 -08:00
using SabreTools.DatFiles ;
2020-12-09 21:52:38 -08:00
using SabreTools.DatItems ;
2021-02-02 10:23:43 -08:00
using SabreTools.DatItems.Formats ;
2020-12-09 21:52:38 -08:00
using SabreTools.IO ;
2020-12-10 13:58:08 -08:00
using SabreTools.Logging ;
2024-02-29 21:20:44 -05:00
using SabreTools.Matching ;
2020-12-09 21:52:38 -08:00
2020-12-10 23:24:09 -08:00
namespace SabreTools.DatTools
2020-12-09 21:52:38 -08:00
{
2020-12-21 11:38:56 -08:00
/// <summary>
/// Helper methods for splitting DatFiles
/// </summary>
/// <remarks>TODO: Implement Level split</remarks>
2020-12-10 13:58:08 -08:00
public class Splitter
2020-12-09 21:52:38 -08:00
{
2021-02-17 17:02:54 -08:00
#region Logging
/// <summary>
/// Logging object
/// </summary>
2023-04-19 16:39:58 -04:00
private static readonly Logger logger = new ( ) ;
2021-02-17 17:02:54 -08:00
#endregion
2020-12-09 21:52:38 -08:00
/// <summary>
/// Split a DAT by input extensions
/// </summary>
2020-12-10 11:38:30 -08:00
/// <param name="datFile">Current DatFile object to split</param>
2020-12-09 21:52:38 -08:00
/// <param name="extA">List of extensions to split on (first DAT)</param>
/// <param name="extB">List of extensions to split on (second DAT)</param>
/// <returns>Extension Set A and Extension Set B DatFiles</returns>
2024-02-28 19:19:50 -05:00
public static ( DatFile ? extADat , DatFile ? extBDat ) SplitByExtension ( DatFile datFile , List < string > extA , List < string > extB )
2020-12-09 21:52:38 -08:00
{
// If roms is empty, return false
2024-03-13 01:22:59 -04:00
if ( datFile . Items . DatStatistics . TotalCount = = 0 )
2020-12-09 21:52:38 -08:00
return ( null , null ) ;
2023-04-19 16:39:58 -04:00
InternalStopwatch watch = new ( $"Splitting DAT by extension" ) ;
2021-02-02 14:09:49 -08:00
2020-12-09 21:52:38 -08:00
// Make sure all of the extensions don't have a dot at the beginning
2024-02-29 00:14:16 -05:00
var newExtA = extA . Select ( s = > s . TrimStart ( '.' ) . ToLowerInvariant ( ) ) . ToArray ( ) ;
2020-12-09 21:52:38 -08:00
string newExtAString = string . Join ( "," , newExtA ) ;
2024-02-29 00:14:16 -05:00
var newExtB = extB . Select ( s = > s . TrimStart ( '.' ) . ToLowerInvariant ( ) ) . ToArray ( ) ;
2020-12-09 21:52:38 -08:00
string newExtBString = string . Join ( "," , newExtB ) ;
// Set all of the appropriate outputs for each of the subsets
2020-12-10 11:38:30 -08:00
DatFile extADat = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
extADat . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , extADat . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + $" ({newExtAString})" ) ;
extADat . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , extADat . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + $" ({newExtAString})" ) ;
extADat . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , extADat . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + $" ({newExtAString})" ) ;
2020-12-09 21:52:38 -08:00
2020-12-10 11:38:30 -08:00
DatFile extBDat = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
extBDat . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , extBDat . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + $" ({newExtBString})" ) ;
extBDat . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , extBDat . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + $" ({newExtBString})" ) ;
extBDat . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , extBDat . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + $" ({newExtBString})" ) ;
2020-12-09 21:52:38 -08:00
// Now separate the roms accordingly
2024-02-28 22:54:56 -05:00
#if NET452_OR_GREATER | | NETCOREAPP
2020-12-10 11:38:30 -08:00
Parallel . ForEach ( datFile . Items . Keys , Globals . ParallelOptions , key = >
2024-02-28 22:54:56 -05:00
#elif NET40_OR_GREATER
Parallel . ForEach ( datFile . Items . Keys , key = >
#else
foreach ( var key in datFile . Items . Keys )
#endif
2020-12-09 21:52:38 -08:00
{
2024-02-29 00:14:16 -05:00
var items = datFile . Items [ key ] ;
2024-02-28 19:19:50 -05:00
if ( items = = null )
2024-02-29 00:14:16 -05:00
#if NET40_OR_GREATER | | NETCOREAPP
2024-02-28 19:19:50 -05:00
return ;
2024-02-29 00:14:16 -05:00
#else
continue ;
#endif
2024-02-28 19:19:50 -05:00
2020-12-09 21:52:38 -08:00
foreach ( DatItem item in items )
{
2020-12-10 22:16:53 -08:00
if ( newExtA . Contains ( ( item . GetName ( ) ? ? string . Empty ) . GetNormalizedExtension ( ) ) )
2020-12-09 21:52:38 -08:00
{
extADat . Items . Add ( key , item ) ;
}
2020-12-10 22:16:53 -08:00
else if ( newExtB . Contains ( ( item . GetName ( ) ? ? string . Empty ) . GetNormalizedExtension ( ) ) )
2020-12-09 21:52:38 -08:00
{
extBDat . Items . Add ( key , item ) ;
}
else
{
extADat . Items . Add ( key , item ) ;
extBDat . Items . Add ( key , item ) ;
}
}
2024-02-28 21:59:13 -05:00
#if NET40_OR_GREATER | | NETCOREAPP
2020-12-09 21:52:38 -08:00
} ) ;
2024-02-28 21:59:13 -05:00
#else
}
#endif
2020-12-09 21:52:38 -08:00
// Then return both DatFiles
2021-02-02 14:09:49 -08:00
watch . Stop ( ) ;
2020-12-09 21:52:38 -08:00
return ( extADat , extBDat ) ;
}
2024-03-19 23:35:29 -04:00
/// <summary>
/// Split a DAT by input extensions
/// </summary>
/// <param name="datFile">Current DatFile object to split</param>
/// <param name="extA">List of extensions to split on (first DAT)</param>
/// <param name="extB">List of extensions to split on (second DAT)</param>
/// <returns>Extension Set A and Extension Set B DatFiles</returns>
public static ( DatFile ? extADat , DatFile ? extBDat ) SplitByExtensionDB ( DatFile datFile , List < string > extA , List < string > extB )
{
// If roms is empty, return false
if ( datFile . ItemsDB . DatStatistics . TotalCount = = 0 )
return ( null , null ) ;
InternalStopwatch watch = new ( $"Splitting DAT by extension" ) ;
// Make sure all of the extensions don't have a dot at the beginning
var newExtA = extA . Select ( s = > s . TrimStart ( '.' ) . ToLowerInvariant ( ) ) . ToArray ( ) ;
string newExtAString = string . Join ( "," , newExtA ) ;
var newExtB = extB . Select ( s = > s . TrimStart ( '.' ) . ToLowerInvariant ( ) ) . ToArray ( ) ;
string newExtBString = string . Join ( "," , newExtB ) ;
// Set all of the appropriate outputs for each of the subsets
DatFile extADat = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
extADat . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , extADat . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + $" ({newExtAString})" ) ;
extADat . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , extADat . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + $" ({newExtAString})" ) ;
extADat . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , extADat . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + $" ({newExtAString})" ) ;
DatFile extBDat = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
extBDat . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , extBDat . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + $" ({newExtBString})" ) ;
extBDat . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , extBDat . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + $" ({newExtBString})" ) ;
extBDat . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , extBDat . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + $" ({newExtBString})" ) ;
// Now separate the roms accordingly
#if NET452_OR_GREATER | | NETCOREAPP
Parallel . ForEach ( datFile . ItemsDB . SortedKeys , Globals . ParallelOptions , key = >
#elif NET40_OR_GREATER
Parallel . ForEach ( datFile . ItemsDB . SortedKeys , key = >
#else
foreach ( var key in datFile . ItemsDB . SortedKeys )
#endif
{
var items = datFile . ItemsDB . GetItemsForBucket ( key ) ;
if ( items = = null )
#if NET40_OR_GREATER | | NETCOREAPP
return ;
#else
continue ;
#endif
// TODO: Determine how we figure out the machine index
foreach ( ( long , DatItem ) item in items )
{
if ( newExtA . Contains ( ( item . Item2 . GetName ( ) ? ? string . Empty ) . GetNormalizedExtension ( ) ) )
{
extADat . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
}
else if ( newExtB . Contains ( ( item . Item2 . GetName ( ) ? ? string . Empty ) . GetNormalizedExtension ( ) ) )
{
extBDat . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
}
else
{
extADat . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
extBDat . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
}
}
#if NET40_OR_GREATER | | NETCOREAPP
} ) ;
#else
}
#endif
// Then return both DatFiles
watch . Stop ( ) ;
return ( extADat , extBDat ) ;
}
2020-12-09 21:52:38 -08:00
/// <summary>
/// Split a DAT by best available hashes
/// </summary>
2020-12-10 11:38:30 -08:00
/// <param name="datFile">Current DatFile object to split</param>
2020-12-09 21:52:38 -08:00
/// <returns>Dictionary of Field to DatFile mappings</returns>
2024-03-05 23:41:00 -05:00
public static Dictionary < string , DatFile > SplitByHash ( DatFile datFile )
2020-12-09 21:52:38 -08:00
{
// Create each of the respective output DATs
2023-04-19 16:39:58 -04:00
InternalStopwatch watch = new ( $"Splitting DAT by best available hashes" ) ;
2020-12-09 21:52:38 -08:00
// Create the set of field-to-dat mappings
2024-03-05 23:41:00 -05:00
Dictionary < string , DatFile > fieldDats = [ ] ;
2020-12-09 21:52:38 -08:00
// TODO: Can this be made into a loop?
2024-03-05 23:41:00 -05:00
fieldDats [ Models . Metadata . Rom . StatusKey ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
fieldDats [ Models . Metadata . Rom . StatusKey ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ Models . Metadata . Rom . StatusKey ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (Nodump)" ) ;
fieldDats [ Models . Metadata . Rom . StatusKey ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ Models . Metadata . Rom . StatusKey ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (Nodump)" ) ;
fieldDats [ Models . Metadata . Rom . StatusKey ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ Models . Metadata . Rom . StatusKey ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (Nodump)" ) ;
2024-03-05 23:41:00 -05:00
fieldDats [ Models . Metadata . Rom . SHA512Key ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
fieldDats [ Models . Metadata . Rom . SHA512Key ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ Models . Metadata . Rom . SHA512Key ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (SHA-512)" ) ;
fieldDats [ Models . Metadata . Rom . SHA512Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ Models . Metadata . Rom . SHA512Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (SHA-512)" ) ;
fieldDats [ Models . Metadata . Rom . SHA512Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ Models . Metadata . Rom . SHA512Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (SHA-512)" ) ;
2024-03-05 23:41:00 -05:00
fieldDats [ Models . Metadata . Rom . SHA384Key ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
fieldDats [ Models . Metadata . Rom . SHA384Key ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ Models . Metadata . Rom . SHA384Key ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (SHA-384)" ) ;
fieldDats [ Models . Metadata . Rom . SHA384Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ Models . Metadata . Rom . SHA384Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (SHA-384)" ) ;
fieldDats [ Models . Metadata . Rom . SHA384Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ Models . Metadata . Rom . SHA384Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (SHA-384)" ) ;
2024-03-05 23:41:00 -05:00
fieldDats [ Models . Metadata . Rom . SHA256Key ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
fieldDats [ Models . Metadata . Rom . SHA256Key ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ Models . Metadata . Rom . SHA256Key ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (SHA-256)" ) ;
fieldDats [ Models . Metadata . Rom . SHA256Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ Models . Metadata . Rom . SHA256Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (SHA-256)" ) ;
fieldDats [ Models . Metadata . Rom . SHA256Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ Models . Metadata . Rom . SHA256Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (SHA-256)" ) ;
2024-03-05 23:41:00 -05:00
fieldDats [ Models . Metadata . Rom . SHA1Key ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
fieldDats [ Models . Metadata . Rom . SHA1Key ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ Models . Metadata . Rom . SHA1Key ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (SHA-1)" ) ;
fieldDats [ Models . Metadata . Rom . SHA1Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ Models . Metadata . Rom . SHA1Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (SHA-1)" ) ;
fieldDats [ Models . Metadata . Rom . SHA1Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ Models . Metadata . Rom . SHA1Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (SHA-1)" ) ;
2024-03-05 23:41:00 -05:00
fieldDats [ Models . Metadata . Rom . MD5Key ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
fieldDats [ Models . Metadata . Rom . MD5Key ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ Models . Metadata . Rom . MD5Key ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (MD5)" ) ;
fieldDats [ Models . Metadata . Rom . MD5Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ Models . Metadata . Rom . MD5Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (MD5)" ) ;
fieldDats [ Models . Metadata . Rom . MD5Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ Models . Metadata . Rom . MD5Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (MD5)" ) ;
2024-03-05 23:41:00 -05:00
fieldDats [ Models . Metadata . Rom . CRCKey ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
fieldDats [ Models . Metadata . Rom . CRCKey ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ Models . Metadata . Rom . CRCKey ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (CRC)" ) ;
fieldDats [ Models . Metadata . Rom . CRCKey ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ Models . Metadata . Rom . CRCKey ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (CRC)" ) ;
fieldDats [ Models . Metadata . Rom . CRCKey ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ Models . Metadata . Rom . CRCKey ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (CRC)" ) ;
2024-03-05 23:41:00 -05:00
fieldDats [ "null" ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
fieldDats [ "null" ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ "null" ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (Other)" ) ;
fieldDats [ "null" ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ "null" ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (Other)" ) ;
fieldDats [ "null" ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ "null" ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (Other)" ) ;
2020-12-09 21:52:38 -08:00
// Now populate each of the DAT objects in turn
2024-02-28 22:54:56 -05:00
#if NET452_OR_GREATER | | NETCOREAPP
2020-12-10 11:38:30 -08:00
Parallel . ForEach ( datFile . Items . Keys , Globals . ParallelOptions , key = >
2024-02-28 22:54:56 -05:00
#elif NET40_OR_GREATER
Parallel . ForEach ( datFile . Items . Keys , key = >
#else
foreach ( var key in datFile . Items . Keys )
#endif
2020-12-09 21:52:38 -08:00
{
2024-02-29 00:14:16 -05:00
var items = datFile . Items [ key ] ;
2024-02-28 19:19:50 -05:00
if ( items = = null )
2024-02-29 00:14:16 -05:00
#if NET40_OR_GREATER | | NETCOREAPP
2024-02-28 19:19:50 -05:00
return ;
2024-02-29 00:14:16 -05:00
#else
continue ;
#endif
2020-12-09 21:52:38 -08:00
foreach ( DatItem item in items )
{
// If the file is not a Disk, Media, or Rom, continue
2024-03-10 16:49:07 -04:00
switch ( item )
2020-12-09 21:52:38 -08:00
{
2024-03-10 16:49:07 -04:00
case Disk disk :
2024-03-11 16:26:28 -04:00
if ( disk . GetStringFieldValue ( Models . Metadata . Disk . StatusKey ) . AsEnumValue < ItemStatus > ( ) = = ItemStatus . Nodump )
2024-03-10 16:49:07 -04:00
fieldDats [ Models . Metadata . Disk . StatusKey ] . Items . Add ( key , item ) ;
2024-03-11 15:46:44 -04:00
else if ( ! string . IsNullOrEmpty ( disk . GetStringFieldValue ( Models . Metadata . Disk . SHA1Key ) ) )
2024-03-10 16:49:07 -04:00
fieldDats [ Models . Metadata . Disk . SHA1Key ] . Items . Add ( key , item ) ;
2024-03-11 15:46:44 -04:00
else if ( ! string . IsNullOrEmpty ( disk . GetStringFieldValue ( Models . Metadata . Disk . MD5Key ) ) )
2024-03-10 16:49:07 -04:00
fieldDats [ Models . Metadata . Disk . MD5Key ] . Items . Add ( key , item ) ;
2024-03-11 15:46:44 -04:00
else if ( ! string . IsNullOrEmpty ( disk . GetStringFieldValue ( Models . Metadata . Disk . MD5Key ) ) )
2024-03-10 16:49:07 -04:00
fieldDats [ Models . Metadata . Disk . MD5Key ] . Items . Add ( key , item ) ;
else
fieldDats [ "null" ] . Items . Add ( key , item ) ;
break ;
case Media media :
2024-03-11 15:46:44 -04:00
if ( ! string . IsNullOrEmpty ( media . GetStringFieldValue ( Models . Metadata . Media . SHA256Key ) ) )
2024-03-10 16:49:07 -04:00
fieldDats [ Models . Metadata . Media . SHA256Key ] . Items . Add ( key , item ) ;
2024-03-11 15:46:44 -04:00
else if ( ! string . IsNullOrEmpty ( media . GetStringFieldValue ( Models . Metadata . Media . SHA1Key ) ) )
2024-03-10 16:49:07 -04:00
fieldDats [ Models . Metadata . Media . SHA1Key ] . Items . Add ( key , item ) ;
2024-03-11 15:46:44 -04:00
else if ( ! string . IsNullOrEmpty ( media . GetStringFieldValue ( Models . Metadata . Media . MD5Key ) ) )
2024-03-10 16:49:07 -04:00
fieldDats [ Models . Metadata . Media . MD5Key ] . Items . Add ( key , item ) ;
else
fieldDats [ "null" ] . Items . Add ( key , item ) ;
break ;
case Rom rom :
2024-03-11 16:26:28 -04:00
if ( rom . GetStringFieldValue ( Models . Metadata . Rom . StatusKey ) . AsEnumValue < ItemStatus > ( ) = = ItemStatus . Nodump )
2024-03-10 16:49:07 -04:00
fieldDats [ Models . Metadata . Rom . StatusKey ] . Items . Add ( key , item ) ;
2024-03-11 15:46:44 -04:00
else if ( ! string . IsNullOrEmpty ( rom . GetStringFieldValue ( Models . Metadata . Rom . SHA512Key ) ) )
2024-03-10 16:49:07 -04:00
fieldDats [ Models . Metadata . Rom . SHA512Key ] . Items . Add ( key , item ) ;
2024-03-11 15:46:44 -04:00
else if ( ! string . IsNullOrEmpty ( rom . GetStringFieldValue ( Models . Metadata . Rom . SHA384Key ) ) )
2024-03-10 16:49:07 -04:00
fieldDats [ Models . Metadata . Rom . SHA384Key ] . Items . Add ( key , item ) ;
2024-03-11 15:46:44 -04:00
else if ( ! string . IsNullOrEmpty ( rom . GetStringFieldValue ( Models . Metadata . Rom . SHA256Key ) ) )
2024-03-10 16:49:07 -04:00
fieldDats [ Models . Metadata . Rom . SHA256Key ] . Items . Add ( key , item ) ;
2024-03-11 15:46:44 -04:00
else if ( ! string . IsNullOrEmpty ( rom . GetStringFieldValue ( Models . Metadata . Rom . SHA1Key ) ) )
2024-03-10 16:49:07 -04:00
fieldDats [ Models . Metadata . Rom . SHA1Key ] . Items . Add ( key , item ) ;
2024-03-11 15:46:44 -04:00
else if ( ! string . IsNullOrEmpty ( rom . GetStringFieldValue ( Models . Metadata . Rom . MD5Key ) ) )
2024-03-10 16:49:07 -04:00
fieldDats [ Models . Metadata . Rom . MD5Key ] . Items . Add ( key , item ) ;
2024-03-11 15:46:44 -04:00
else if ( ! string . IsNullOrEmpty ( rom . GetStringFieldValue ( Models . Metadata . Rom . CRCKey ) ) )
2024-03-10 16:49:07 -04:00
fieldDats [ Models . Metadata . Rom . CRCKey ] . Items . Add ( key , item ) ;
else
fieldDats [ "null" ] . Items . Add ( key , item ) ;
break ;
default :
continue ;
2020-12-09 21:52:38 -08:00
}
}
2024-02-28 21:59:13 -05:00
#if NET40_OR_GREATER | | NETCOREAPP
2020-12-09 21:52:38 -08:00
} ) ;
2024-02-28 21:59:13 -05:00
#else
}
#endif
2020-12-09 21:52:38 -08:00
2021-02-02 14:09:49 -08:00
watch . Stop ( ) ;
2020-12-09 21:52:38 -08:00
return fieldDats ;
}
2024-03-19 23:35:29 -04:00
/// <summary>
/// Split a DAT by best available hashes
/// </summary>
/// <param name="datFile">Current DatFile object to split</param>
/// <returns>Dictionary of Field to DatFile mappings</returns>
public static Dictionary < string , DatFile > SplitByHashDB ( DatFile datFile )
{
// Create each of the respective output DATs
var watch = new InternalStopwatch ( $"Splitting DAT by best available hashes" ) ;
// Create the set of field-to-dat mappings
Dictionary < string , DatFile > fieldDats = [ ] ;
// TODO: Can this be made into a loop?
fieldDats [ Models . Metadata . Rom . StatusKey ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
fieldDats [ Models . Metadata . Rom . StatusKey ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ Models . Metadata . Rom . StatusKey ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (Nodump)" ) ;
fieldDats [ Models . Metadata . Rom . StatusKey ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ Models . Metadata . Rom . StatusKey ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (Nodump)" ) ;
fieldDats [ Models . Metadata . Rom . StatusKey ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ Models . Metadata . Rom . StatusKey ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (Nodump)" ) ;
fieldDats [ Models . Metadata . Rom . SHA512Key ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
fieldDats [ Models . Metadata . Rom . SHA512Key ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ Models . Metadata . Rom . SHA512Key ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (SHA-512)" ) ;
fieldDats [ Models . Metadata . Rom . SHA512Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ Models . Metadata . Rom . SHA512Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (SHA-512)" ) ;
fieldDats [ Models . Metadata . Rom . SHA512Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ Models . Metadata . Rom . SHA512Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (SHA-512)" ) ;
fieldDats [ Models . Metadata . Rom . SHA384Key ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
fieldDats [ Models . Metadata . Rom . SHA384Key ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ Models . Metadata . Rom . SHA384Key ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (SHA-384)" ) ;
fieldDats [ Models . Metadata . Rom . SHA384Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ Models . Metadata . Rom . SHA384Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (SHA-384)" ) ;
fieldDats [ Models . Metadata . Rom . SHA384Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ Models . Metadata . Rom . SHA384Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (SHA-384)" ) ;
fieldDats [ Models . Metadata . Rom . SHA256Key ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
fieldDats [ Models . Metadata . Rom . SHA256Key ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ Models . Metadata . Rom . SHA256Key ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (SHA-256)" ) ;
fieldDats [ Models . Metadata . Rom . SHA256Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ Models . Metadata . Rom . SHA256Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (SHA-256)" ) ;
fieldDats [ Models . Metadata . Rom . SHA256Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ Models . Metadata . Rom . SHA256Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (SHA-256)" ) ;
fieldDats [ Models . Metadata . Rom . SHA1Key ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
fieldDats [ Models . Metadata . Rom . SHA1Key ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ Models . Metadata . Rom . SHA1Key ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (SHA-1)" ) ;
fieldDats [ Models . Metadata . Rom . SHA1Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ Models . Metadata . Rom . SHA1Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (SHA-1)" ) ;
fieldDats [ Models . Metadata . Rom . SHA1Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ Models . Metadata . Rom . SHA1Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (SHA-1)" ) ;
fieldDats [ Models . Metadata . Rom . MD5Key ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
fieldDats [ Models . Metadata . Rom . MD5Key ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ Models . Metadata . Rom . MD5Key ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (MD5)" ) ;
fieldDats [ Models . Metadata . Rom . MD5Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ Models . Metadata . Rom . MD5Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (MD5)" ) ;
fieldDats [ Models . Metadata . Rom . MD5Key ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ Models . Metadata . Rom . MD5Key ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (MD5)" ) ;
fieldDats [ Models . Metadata . Rom . CRCKey ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
fieldDats [ Models . Metadata . Rom . CRCKey ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ Models . Metadata . Rom . CRCKey ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (CRC)" ) ;
fieldDats [ Models . Metadata . Rom . CRCKey ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ Models . Metadata . Rom . CRCKey ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (CRC)" ) ;
fieldDats [ Models . Metadata . Rom . CRCKey ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ Models . Metadata . Rom . CRCKey ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (CRC)" ) ;
fieldDats [ "null" ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
fieldDats [ "null" ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , fieldDats [ "null" ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + " (Other)" ) ;
fieldDats [ "null" ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , fieldDats [ "null" ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + " (Other)" ) ;
fieldDats [ "null" ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , fieldDats [ "null" ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + " (Other)" ) ;
// Now populate each of the DAT objects in turn
#if NET452_OR_GREATER | | NETCOREAPP
Parallel . ForEach ( datFile . ItemsDB . SortedKeys , Globals . ParallelOptions , key = >
#elif NET40_OR_GREATER
Parallel . ForEach ( datFile . ItemsDB . SortedKeys , key = >
#else
foreach ( var key in datFile . ItemsDB . SortedKeys )
#endif
{
var items = datFile . ItemsDB . GetItemsForBucket ( key ) ;
if ( items = = null )
#if NET40_OR_GREATER | | NETCOREAPP
return ;
#else
continue ;
#endif
// TODO: Determine how we figure out the machine index
foreach ( ( long , DatItem ) item in items )
{
// If the file is not a Disk, Media, or Rom, continue
switch ( item . Item2 )
{
case Disk disk :
if ( disk . GetStringFieldValue ( Models . Metadata . Disk . StatusKey ) . AsEnumValue < ItemStatus > ( ) = = ItemStatus . Nodump )
fieldDats [ Models . Metadata . Disk . StatusKey ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
else if ( ! string . IsNullOrEmpty ( disk . GetStringFieldValue ( Models . Metadata . Disk . SHA1Key ) ) )
fieldDats [ Models . Metadata . Disk . SHA1Key ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
else if ( ! string . IsNullOrEmpty ( disk . GetStringFieldValue ( Models . Metadata . Disk . MD5Key ) ) )
fieldDats [ Models . Metadata . Disk . MD5Key ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
else if ( ! string . IsNullOrEmpty ( disk . GetStringFieldValue ( Models . Metadata . Disk . MD5Key ) ) )
fieldDats [ Models . Metadata . Disk . MD5Key ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
else
fieldDats [ "null" ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
break ;
case Media media :
if ( ! string . IsNullOrEmpty ( media . GetStringFieldValue ( Models . Metadata . Media . SHA256Key ) ) )
fieldDats [ Models . Metadata . Media . SHA256Key ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
else if ( ! string . IsNullOrEmpty ( media . GetStringFieldValue ( Models . Metadata . Media . SHA1Key ) ) )
fieldDats [ Models . Metadata . Media . SHA1Key ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
else if ( ! string . IsNullOrEmpty ( media . GetStringFieldValue ( Models . Metadata . Media . MD5Key ) ) )
fieldDats [ Models . Metadata . Media . MD5Key ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
else
fieldDats [ "null" ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
break ;
case Rom rom :
if ( rom . GetStringFieldValue ( Models . Metadata . Rom . StatusKey ) . AsEnumValue < ItemStatus > ( ) = = ItemStatus . Nodump )
fieldDats [ Models . Metadata . Rom . StatusKey ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
else if ( ! string . IsNullOrEmpty ( rom . GetStringFieldValue ( Models . Metadata . Rom . SHA512Key ) ) )
fieldDats [ Models . Metadata . Rom . SHA512Key ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
else if ( ! string . IsNullOrEmpty ( rom . GetStringFieldValue ( Models . Metadata . Rom . SHA384Key ) ) )
fieldDats [ Models . Metadata . Rom . SHA384Key ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
else if ( ! string . IsNullOrEmpty ( rom . GetStringFieldValue ( Models . Metadata . Rom . SHA256Key ) ) )
fieldDats [ Models . Metadata . Rom . SHA256Key ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
else if ( ! string . IsNullOrEmpty ( rom . GetStringFieldValue ( Models . Metadata . Rom . SHA1Key ) ) )
fieldDats [ Models . Metadata . Rom . SHA1Key ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
else if ( ! string . IsNullOrEmpty ( rom . GetStringFieldValue ( Models . Metadata . Rom . MD5Key ) ) )
fieldDats [ Models . Metadata . Rom . MD5Key ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
else if ( ! string . IsNullOrEmpty ( rom . GetStringFieldValue ( Models . Metadata . Rom . CRCKey ) ) )
fieldDats [ Models . Metadata . Rom . CRCKey ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
else
fieldDats [ "null" ] . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
break ;
default :
continue ;
}
}
#if NET40_OR_GREATER | | NETCOREAPP
} ) ;
#else
}
#endif
watch . Stop ( ) ;
return fieldDats ;
}
2020-12-09 21:52:38 -08:00
/// <summary>
/// Split a SuperDAT by lowest available directory level
/// </summary>
2020-12-10 11:38:30 -08:00
/// <param name="datFile">Current DatFile object to split</param>
2020-12-09 21:52:38 -08:00
/// <param name="outDir">Name of the directory to write the DATs out to</param>
/// <param name="shortname">True if short names should be used, false otherwise</param>
/// <param name="basedat">True if original filenames should be used as the base for output filename, false otherwise</param>
/// <returns>True if split succeeded, false otherwise</returns>
2020-12-10 11:58:46 -08:00
public static bool SplitByLevel ( DatFile datFile , string outDir , bool shortname , bool basedat )
2020-12-09 21:52:38 -08:00
{
2023-04-19 16:39:58 -04:00
InternalStopwatch watch = new ( $"Splitting DAT by level" ) ;
2021-02-02 14:09:49 -08:00
2020-12-09 21:52:38 -08:00
// First, bucket by games so that we can do the right thing
2020-12-14 15:43:01 -08:00
datFile . Items . BucketBy ( ItemKey . Machine , DedupeType . None , lower : false , norename : true ) ;
2020-12-09 21:52:38 -08:00
// Create a temporary DAT to add things to
2020-12-10 11:38:30 -08:00
DatFile tempDat = DatFile . Create ( datFile . Header ) ;
2024-03-10 04:10:37 -04:00
tempDat . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , null ) ;
2020-12-09 21:52:38 -08:00
// Sort the input keys
2024-02-28 19:19:50 -05:00
List < string > keys = [ . . datFile . Items . Keys ] ;
2020-12-09 21:52:38 -08:00
keys . Sort ( SplitByLevelSort ) ;
// Then, we loop over the games
2024-02-28 22:54:56 -05:00
#if NET452_OR_GREATER | | NETCOREAPP
2020-12-09 21:52:38 -08:00
Parallel . ForEach ( keys , Globals . ParallelOptions , key = >
2024-02-28 22:54:56 -05:00
#elif NET40_OR_GREATER
Parallel . ForEach ( keys , key = >
#else
foreach ( var key in keys )
#endif
2020-12-09 21:52:38 -08:00
{
// Here, the key is the name of the game to be used for comparison
2024-03-11 15:46:44 -04:00
if ( tempDat . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) ! = null & & tempDat . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) ! = Path . GetDirectoryName ( key ) )
2020-12-09 21:52:38 -08:00
{
// Reset the DAT for the next items
2020-12-10 11:38:30 -08:00
tempDat = DatFile . Create ( datFile . Header ) ;
2024-03-10 04:10:37 -04:00
tempDat . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , null ) ;
2020-12-09 21:52:38 -08:00
}
// Clean the input list and set all games to be pathless
2024-02-28 19:19:50 -05:00
ConcurrentList < DatItem > ? items = datFile . Items [ key ] ;
if ( items = = null )
2024-02-29 00:14:16 -05:00
#if NET40_OR_GREATER | | NETCOREAPP
2024-02-28 19:19:50 -05:00
return ;
2024-02-29 00:14:16 -05:00
#else
continue ;
#endif
2024-03-11 15:46:44 -04:00
items . ForEach ( item = > item . GetFieldValue < Machine > ( DatItem . MachineKey ) ! . SetFieldValue < string? > ( Models . Metadata . Machine . NameKey , Path . GetFileName ( item . GetFieldValue < Machine > ( DatItem . MachineKey ) ! . GetStringFieldValue ( Models . Metadata . Machine . NameKey ) ) ) ) ;
items . ForEach ( item = > item . GetFieldValue < Machine > ( DatItem . MachineKey ) ! . SetFieldValue < string? > ( Models . Metadata . Machine . DescriptionKey , Path . GetFileName ( item . GetFieldValue < Machine > ( DatItem . MachineKey ) ! . GetStringFieldValue ( Models . Metadata . Machine . DescriptionKey ) ) ) ) ;
2020-12-09 21:52:38 -08:00
// Now add the game to the output DAT
tempDat . Items . AddRange ( key , items ) ;
// Then set the DAT name to be the parent directory name
2024-03-10 04:10:37 -04:00
tempDat . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , Path . GetDirectoryName ( key ) ) ;
2024-02-28 21:59:13 -05:00
#if NET40_OR_GREATER | | NETCOREAPP
2020-12-09 21:52:38 -08:00
} ) ;
2024-02-28 21:59:13 -05:00
#else
}
#endif
2020-12-09 21:52:38 -08:00
2021-02-02 14:09:49 -08:00
watch . Stop ( ) ;
2020-12-09 21:52:38 -08:00
return true ;
}
/// <summary>
/// Helper function for SplitByLevel to sort the input game names
/// </summary>
/// <param name="a">First string to compare</param>
/// <param name="b">Second string to compare</param>
/// <returns>-1 for a coming before b, 0 for a == b, 1 for a coming after b</returns>
2020-12-10 11:58:46 -08:00
private static int SplitByLevelSort ( string a , string b )
2020-12-09 21:52:38 -08:00
{
2023-04-19 16:39:58 -04:00
NaturalComparer nc = new ( ) ;
2020-12-09 21:52:38 -08:00
int adeep = a . Count ( c = > c = = '/' | | c = = '\\' ) ;
int bdeep = b . Count ( c = > c = = '/' | | c = = '\\' ) ;
if ( adeep = = bdeep )
return nc . Compare ( a , b ) ;
return adeep - bdeep ;
}
/// <summary>
/// Helper function for SplitByLevel to clean and write out a DAT
/// </summary>
2020-12-10 11:38:30 -08:00
/// <param name="datFile">Current DatFile object to split</param>
2020-12-09 21:52:38 -08:00
/// <param name="newDatFile">DAT to clean and write out</param>
/// <param name="outDir">Directory to write out to</param>
/// <param name="shortname">True if short naming scheme should be used, false otherwise</param>
/// <param name="restore">True if original filenames should be used as the base for output filename, false otherwise</param>
2020-12-10 11:58:46 -08:00
private static void SplitByLevelHelper ( DatFile datFile , DatFile newDatFile , string outDir , bool shortname , bool restore )
2020-12-09 21:52:38 -08:00
{
// Get the name from the DAT to use separately
2024-03-11 15:46:44 -04:00
string? name = newDatFile . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) ;
2024-02-28 19:19:50 -05:00
string? expName = name ? . Replace ( "/" , " - " ) ? . Replace ( "\\" , " - " ) ;
2020-12-09 21:52:38 -08:00
// Now set the new output values
2024-02-29 00:14:16 -05:00
#if NET20 | | NET35
2024-03-10 21:41:49 -04:00
newDatFile . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , string . IsNullOrEmpty ( name )
2024-03-11 15:46:44 -04:00
? datFile . Header . GetStringFieldValue ( DatHeader . FileNameKey )
2024-02-29 00:14:16 -05:00
: ( shortname
? Path . GetFileName ( name )
: expName
2024-03-10 21:41:49 -04:00
) ) ;
2024-02-29 00:14:16 -05:00
#else
2024-03-10 21:41:49 -04:00
newDatFile . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , WebUtility . HtmlDecode ( string . IsNullOrEmpty ( name )
2024-03-11 15:46:44 -04:00
? datFile . Header . GetStringFieldValue ( DatHeader . FileNameKey )
2020-12-09 21:52:38 -08:00
: ( shortname
? Path . GetFileName ( name )
: expName
)
2024-03-10 21:41:49 -04:00
) ) ;
2024-02-29 00:14:16 -05:00
#endif
2024-03-10 21:41:49 -04:00
newDatFile . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , restore
2024-03-11 15:46:44 -04:00
? $"{datFile.Header.GetStringFieldValue(DatHeader.FileNameKey)} ({newDatFile.Header.GetStringFieldValue(DatHeader.FileNameKey)})"
: newDatFile . Header . GetStringFieldValue ( DatHeader . FileNameKey ) ) ;
newDatFile . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , $"{datFile.Header.GetStringFieldValue(Models.Metadata.Header.NameKey)} ({expName})" ) ;
newDatFile . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , string . IsNullOrEmpty ( datFile . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) )
? newDatFile . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey )
: $"{datFile.Header.GetStringFieldValue(Models.Metadata.Header.DescriptionKey)} ({expName})" ) ;
2024-03-10 04:10:37 -04:00
newDatFile . Header . SetFieldValue < string? > ( Models . Metadata . Header . TypeKey , null ) ;
2020-12-09 21:52:38 -08:00
// Write out the temporary DAT to the proper directory
2020-12-10 14:03:07 -08:00
Writer . Write ( newDatFile , outDir ) ;
2020-12-09 21:52:38 -08:00
}
/// <summary>
/// Split a DAT by size of Rom
/// </summary>
2020-12-10 11:38:30 -08:00
/// <param name="datFile">Current DatFile object to split</param>
2020-12-09 21:52:38 -08:00
/// <param name="radix">Long value representing the split point</param>
/// <returns>Less Than and Greater Than DatFiles</returns>
2020-12-10 11:58:46 -08:00
public static ( DatFile lessThan , DatFile greaterThan ) SplitBySize ( DatFile datFile , long radix )
2020-12-09 21:52:38 -08:00
{
// Create each of the respective output DATs
2023-04-19 16:39:58 -04:00
InternalStopwatch watch = new ( $"Splitting DAT by size" ) ;
2020-12-09 21:52:38 -08:00
2020-12-10 11:38:30 -08:00
DatFile lessThan = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
lessThan . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , lessThan . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + $" (less than {radix})" ) ;
lessThan . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , lessThan . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + $" (less than {radix})" ) ;
lessThan . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , lessThan . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + $" (less than {radix})" ) ;
2020-12-09 21:52:38 -08:00
2020-12-10 11:38:30 -08:00
DatFile greaterThan = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
greaterThan . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , greaterThan . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + $" (equal-greater than {radix})" ) ;
greaterThan . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , greaterThan . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + $" (equal-greater than {radix})" ) ;
greaterThan . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , greaterThan . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + $" (equal-greater than {radix})" ) ;
2020-12-09 21:52:38 -08:00
// Now populate each of the DAT objects in turn
2024-02-28 22:54:56 -05:00
#if NET452_OR_GREATER | | NETCOREAPP
2020-12-10 11:38:30 -08:00
Parallel . ForEach ( datFile . Items . Keys , Globals . ParallelOptions , key = >
2024-02-28 22:54:56 -05:00
#elif NET40_OR_GREATER
Parallel . ForEach ( datFile . Items . Keys , key = >
#else
foreach ( var key in datFile . Items . Keys )
#endif
2020-12-09 21:52:38 -08:00
{
2024-02-28 19:19:50 -05:00
ConcurrentList < DatItem > ? items = datFile . Items [ key ] ;
if ( items = = null )
2024-02-29 00:14:16 -05:00
#if NET40_OR_GREATER | | NETCOREAPP
2024-02-28 19:19:50 -05:00
return ;
2024-02-29 00:14:16 -05:00
#else
continue ;
#endif
2020-12-09 21:52:38 -08:00
foreach ( DatItem item in items )
{
// If the file is not a Rom, it automatically goes in the "lesser" dat
2024-03-10 16:49:07 -04:00
if ( item is not Rom rom )
2020-12-09 21:52:38 -08:00
lessThan . Items . Add ( key , item ) ;
// If the file is a Rom and has no size, put it in the "lesser" dat
2024-03-11 15:46:44 -04:00
else if ( rom . GetInt64FieldValue ( Models . Metadata . Rom . SizeKey ) = = null )
2020-12-09 21:52:38 -08:00
lessThan . Items . Add ( key , item ) ;
// If the file is a Rom and less than the radix, put it in the "lesser" dat
2024-03-11 15:46:44 -04:00
else if ( rom . GetInt64FieldValue ( Models . Metadata . Rom . SizeKey ) < radix )
2020-12-09 21:52:38 -08:00
lessThan . Items . Add ( key , item ) ;
// If the file is a Rom and greater than or equal to the radix, put it in the "greater" dat
2024-03-11 15:46:44 -04:00
else if ( rom . GetInt64FieldValue ( Models . Metadata . Rom . SizeKey ) > = radix )
2020-12-09 21:52:38 -08:00
greaterThan . Items . Add ( key , item ) ;
}
2024-02-28 21:59:13 -05:00
#if NET40_OR_GREATER | | NETCOREAPP
2020-12-09 21:52:38 -08:00
} ) ;
2024-02-28 21:59:13 -05:00
#else
}
#endif
2020-12-09 21:52:38 -08:00
// Then return both DatFiles
2021-02-02 14:09:49 -08:00
watch . Stop ( ) ;
2020-12-09 21:52:38 -08:00
return ( lessThan , greaterThan ) ;
}
2024-03-19 23:35:29 -04:00
/// <summary>
/// Split a DAT by size of Rom
/// </summary>
/// <param name="datFile">Current DatFile object to split</param>
/// <param name="radix">Long value representing the split point</param>
/// <returns>Less Than and Greater Than DatFiles</returns>
public static ( DatFile lessThan , DatFile greaterThan ) SplitBySizeDB ( DatFile datFile , long radix )
{
// Create each of the respective output DATs
var watch = new InternalStopwatch ( $"Splitting DAT by size" ) ;
DatFile lessThan = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
lessThan . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , lessThan . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + $" (less than {radix})" ) ;
lessThan . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , lessThan . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + $" (less than {radix})" ) ;
lessThan . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , lessThan . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + $" (less than {radix})" ) ;
DatFile greaterThan = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
greaterThan . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , greaterThan . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + $" (equal-greater than {radix})" ) ;
greaterThan . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , greaterThan . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + $" (equal-greater than {radix})" ) ;
greaterThan . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , greaterThan . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + $" (equal-greater than {radix})" ) ;
// Now populate each of the DAT objects in turn
#if NET452_OR_GREATER | | NETCOREAPP
Parallel . ForEach ( datFile . ItemsDB . SortedKeys , Globals . ParallelOptions , key = >
#elif NET40_OR_GREATER
Parallel . ForEach ( datFile . ItemsDB . SortedKeys , key = >
#else
foreach ( var key in datFile . ItemsDB . SortedKeys )
#endif
{
var items = datFile . ItemsDB . GetItemsForBucket ( key ) ;
if ( items = = null )
#if NET40_OR_GREATER | | NETCOREAPP
return ;
#else
continue ;
#endif
// TODO: Determine how we figure out the machine index
foreach ( ( long , DatItem ) item in items )
{
// If the file is not a Rom, it automatically goes in the "lesser" dat
if ( item . Item2 is not Rom rom )
lessThan . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
// If the file is a Rom and has no size, put it in the "lesser" dat
else if ( rom . GetInt64FieldValue ( Models . Metadata . Rom . SizeKey ) = = null )
lessThan . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
// If the file is a Rom and less than the radix, put it in the "lesser" dat
else if ( rom . GetInt64FieldValue ( Models . Metadata . Rom . SizeKey ) < radix )
lessThan . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
// If the file is a Rom and greater than or equal to the radix, put it in the "greater" dat
else if ( rom . GetInt64FieldValue ( Models . Metadata . Rom . SizeKey ) > = radix )
greaterThan . ItemsDB . AddItem ( item . Item2 , - 1 , false ) ;
}
#if NET40_OR_GREATER | | NETCOREAPP
} ) ;
#else
}
#endif
// Then return both DatFiles
watch . Stop ( ) ;
return ( lessThan , greaterThan ) ;
}
2021-02-17 16:47:32 -08:00
/// <summary>
/// Split a DAT by size of Rom
/// </summary>
/// <param name="datFile">Current DatFile object to split</param>
/// <param name="chunkSize">Long value representing the total size to split at</param>
/// <returns>Less Than and Greater Than DatFiles</returns>
public static List < DatFile > SplitByTotalSize ( DatFile datFile , long chunkSize )
{
// If the size is invalid, just return
if ( chunkSize < = 0 )
2024-02-28 19:19:50 -05:00
return [ ] ;
2021-02-17 16:47:32 -08:00
// Create each of the respective output DATs
2023-04-19 16:39:58 -04:00
InternalStopwatch watch = new ( $"Splitting DAT by total size" ) ;
2021-02-17 16:47:32 -08:00
// Sort the DatFile by machine name
datFile . Items . BucketBy ( ItemKey . Machine , DedupeType . None ) ;
// Get the keys in a known order for easier sorting
var keys = datFile . Items . SortedKeys ;
// Get the output list
2024-02-28 19:19:50 -05:00
List < DatFile > datFiles = [ ] ;
2021-02-17 16:47:32 -08:00
// Initialize everything
long currentSize = 0 ;
long currentIndex = 0 ;
DatFile currentDat = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
currentDat . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , currentDat . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + $"_{currentIndex}" ) ;
currentDat . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , currentDat . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + $"_{currentIndex}" ) ;
currentDat . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , currentDat . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + $"_{currentIndex}" ) ;
2021-02-17 16:47:32 -08:00
// Loop through each machine
foreach ( string machine in keys )
{
// Get the current machine
var items = datFile . Items [ machine ] ;
if ( items = = null | | ! items . Any ( ) )
2021-02-17 17:02:54 -08:00
{
logger . Error ( $"{machine} contains no items and will be skipped" ) ;
2021-02-17 16:47:32 -08:00
continue ;
2021-02-17 17:02:54 -08:00
}
2021-02-17 16:47:32 -08:00
// Get the total size of the current machine
long machineSize = 0 ;
foreach ( var item in items )
{
if ( item is Rom rom )
2021-02-17 17:02:54 -08:00
{
2021-02-17 17:13:39 -08:00
// TODO: Should there be more than just a log if a single item is larger than the chunksize?
2024-03-11 15:46:44 -04:00
machineSize + = rom . GetInt64FieldValue ( Models . Metadata . Rom . SizeKey ) ? ? 0 ;
if ( ( rom . GetInt64FieldValue ( Models . Metadata . Rom . SizeKey ) ? ? 0 ) > chunkSize )
2021-02-17 17:02:54 -08:00
logger . Error ( $"{rom.GetName() ?? string.Empty} in {machine} is larger than {chunkSize}" ) ;
}
}
// If the current machine size is greater than the chunk size by itself, we want to log and skip
// TODO: Should this eventually try to split the machine here?
if ( machineSize > chunkSize )
{
logger . Error ( $"{machine} is larger than {chunkSize} and will be skipped" ) ;
continue ;
2021-02-17 16:47:32 -08:00
}
// If the current machine size makes the current DatFile too big, split
2021-02-17 17:02:54 -08:00
else if ( currentSize + machineSize > chunkSize )
2021-02-17 16:47:32 -08:00
{
datFiles . Add ( currentDat ) ;
currentSize = 0 ;
currentIndex + + ;
currentDat = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
currentDat . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , currentDat . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + $"_{currentIndex}" ) ;
currentDat . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , currentDat . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + $"_{currentIndex}" ) ;
currentDat . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , currentDat . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + $"_{currentIndex}" ) ;
2021-02-17 16:47:32 -08:00
}
// Add the current machine to the current DatFile
currentDat . Items [ machine ] = items ;
currentSize + = machineSize ;
}
// Add the final DatFile to the list
datFiles . Add ( currentDat ) ;
// Then return the list
watch . Stop ( ) ;
return datFiles ;
}
2020-12-09 21:52:38 -08:00
/// <summary>
/// Split a DAT by type of DatItem
/// </summary>
2020-12-10 11:38:30 -08:00
/// <param name="datFile">Current DatFile object to split</param>
2020-12-09 21:52:38 -08:00
/// <returns>Dictionary of ItemType to DatFile mappings</returns>
2020-12-10 11:58:46 -08:00
public static Dictionary < ItemType , DatFile > SplitByType ( DatFile datFile )
2020-12-09 21:52:38 -08:00
{
// Create each of the respective output DATs
2023-04-19 16:39:58 -04:00
InternalStopwatch watch = new ( $"Splitting DAT by item type" ) ;
2020-12-09 21:52:38 -08:00
// Create the set of type-to-dat mappings
2024-02-28 19:19:50 -05:00
Dictionary < ItemType , DatFile > typeDats = [ ] ;
2020-12-09 21:52:38 -08:00
// We only care about a subset of types
2024-02-28 19:19:50 -05:00
List < ItemType > outputTypes =
[
2020-12-09 21:52:38 -08:00
ItemType . Disk ,
ItemType . Media ,
ItemType . Rom ,
ItemType . Sample ,
2024-02-28 19:19:50 -05:00
] ;
2020-12-09 21:52:38 -08:00
// Setup all of the DatFiles
foreach ( ItemType itemType in outputTypes )
{
2020-12-10 11:38:30 -08:00
typeDats [ itemType ] = DatFile . Create ( datFile . Header . CloneStandard ( ) ) ;
2024-03-11 15:46:44 -04:00
typeDats [ itemType ] . Header . SetFieldValue < string? > ( DatHeader . FileNameKey , typeDats [ itemType ] . Header . GetStringFieldValue ( DatHeader . FileNameKey ) + $" ({itemType})" ) ;
typeDats [ itemType ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . NameKey , typeDats [ itemType ] . Header . GetStringFieldValue ( Models . Metadata . Header . NameKey ) + $" ({itemType})" ) ;
typeDats [ itemType ] . Header . SetFieldValue < string? > ( Models . Metadata . Header . DescriptionKey , typeDats [ itemType ] . Header . GetStringFieldValue ( Models . Metadata . Header . DescriptionKey ) + $" ({itemType})" ) ;
2020-12-09 21:52:38 -08:00
}
// Now populate each of the DAT objects in turn
2024-02-28 22:54:56 -05:00
#if NET452_OR_GREATER | | NETCOREAPP
2020-12-09 21:52:38 -08:00
Parallel . ForEach ( outputTypes , Globals . ParallelOptions , itemType = >
2024-02-28 22:54:56 -05:00
#elif NET40_OR_GREATER
Parallel . ForEach ( outputTypes , itemType = >
#else
foreach ( var itemType in outputTypes )
#endif
2020-12-09 21:52:38 -08:00
{
2020-12-10 11:38:30 -08:00
FillWithItemType ( datFile , typeDats [ itemType ] , itemType ) ;
2024-03-19 23:35:29 -04:00
FillWithItemTypeDB ( datFile , typeDats [ itemType ] , itemType ) ;
2024-02-28 21:59:13 -05:00
#if NET40_OR_GREATER | | NETCOREAPP
2020-12-09 21:52:38 -08:00
} ) ;
2024-02-28 21:59:13 -05:00
#else
}
#endif
2020-12-09 21:52:38 -08:00
2021-02-02 14:09:49 -08:00
watch . Stop ( ) ;
2020-12-09 21:52:38 -08:00
return typeDats ;
}
2020-12-10 11:38:30 -08:00
/// <summary>
/// Fill a DatFile with all items with a particular ItemType
/// </summary>
/// <param name="datFile">Current DatFile object to split</param>
/// <param name="indexDat">DatFile to add found items to</param>
/// <param name="itemType">ItemType to retrieve items for</param>
/// <returns>DatFile containing all items with the ItemType/returns>
2020-12-10 11:58:46 -08:00
private static void FillWithItemType ( DatFile datFile , DatFile indexDat , ItemType itemType )
2020-12-10 11:38:30 -08:00
{
// Loop through and add the items for this index to the output
2024-02-28 22:54:56 -05:00
#if NET452_OR_GREATER | | NETCOREAPP
2020-12-10 11:38:30 -08:00
Parallel . ForEach ( datFile . Items . Keys , Globals . ParallelOptions , key = >
2024-02-28 22:54:56 -05:00
#elif NET40_OR_GREATER
Parallel . ForEach ( datFile . Items . Keys , key = >
#else
foreach ( var key in datFile . Items . Keys )
#endif
2020-12-10 11:38:30 -08:00
{
2021-07-18 21:00:01 -07:00
ConcurrentList < DatItem > items = DatItem . Merge ( datFile . Items [ key ] ) ;
2020-12-10 11:38:30 -08:00
// If the rom list is empty or null, just skip it
if ( items = = null | | items . Count = = 0 )
2024-03-05 02:52:53 -05:00
#if NET40_OR_GREATER | | NETCOREAPP
2020-12-10 11:38:30 -08:00
return ;
2024-03-05 02:52:53 -05:00
#else
continue ;
#endif
2020-12-10 11:38:30 -08:00
foreach ( DatItem item in items )
{
2024-03-11 16:26:28 -04:00
if ( item . GetStringFieldValue ( Models . Metadata . DatItem . TypeKey ) . AsEnumValue < ItemType > ( ) = = itemType )
2020-12-10 11:38:30 -08:00
indexDat . Items . Add ( key , item ) ;
}
2024-02-28 21:59:13 -05:00
#if NET40_OR_GREATER | | NETCOREAPP
2020-12-10 11:38:30 -08:00
} ) ;
2024-02-28 21:59:13 -05:00
#else
}
2024-03-19 23:35:29 -04:00
#endif
}
/// <summary>
/// Fill a DatFile with all items with a particular ItemType
/// </summary>
/// <param name="datFile">Current DatFile object to split</param>
/// <param name="indexDat">DatFile to add found items to</param>
/// <param name="itemType">ItemType to retrieve items for</param>
/// <returns>DatFile containing all items with the ItemType/returns>
private static void FillWithItemTypeDB ( DatFile datFile , DatFile indexDat , ItemType itemType )
{
// Loop through and add the items for this index to the output
#if NET452_OR_GREATER | | NETCOREAPP
Parallel . ForEach ( datFile . ItemsDB . SortedKeys , Globals . ParallelOptions , key = >
#elif NET40_OR_GREATER
Parallel . ForEach ( datFile . ItemsDB . SortedKeys , key = >
#else
foreach ( var key in datFile . ItemsDB . SortedKeys )
#endif
{
// Get the current items
var datItems = datFile . ItemsDB . GetItemsForBucket ( key ) ;
if ( datItems = = null )
#if NET40_OR_GREATER | | NETCOREAPP
return ;
#else
continue ;
#endif
ConcurrentList < DatItem > items = DatItem . Merge ( datItems . Select ( i = > i . Item2 ) . ToConcurrentList ( ) ) ;
// If the rom list is empty or null, just skip it
if ( items = = null | | items . Count = = 0 )
#if NET40_OR_GREATER | | NETCOREAPP
return ;
#else
continue ;
#endif
foreach ( DatItem item in items )
{
// TODO: Determine how we figure out the machine index
if ( item . GetStringFieldValue ( Models . Metadata . DatItem . TypeKey ) . AsEnumValue < ItemType > ( ) = = itemType )
indexDat . ItemsDB . AddItem ( item , - 1 , false ) ;
}
#if NET40_OR_GREATER | | NETCOREAPP
} ) ;
#else
}
2024-02-28 21:59:13 -05:00
#endif
2020-12-10 11:38:30 -08:00
}
2020-12-09 21:52:38 -08:00
}
}