2017-10-09 18:04:49 -07:00
using System ;
using System.Collections.Generic ;
2020-06-10 22:37:19 -07:00
using System.IO ;
2017-09-25 12:21:52 -07:00
using System.Linq ;
2019-03-29 00:15:40 -07:00
using System.Net ;
2017-10-09 18:04:49 -07:00
using System.Threading.Tasks ;
2020-06-10 22:37:19 -07:00
2017-05-04 02:41:11 -07:00
using SabreTools.Library.Data ;
2017-11-02 15:44:15 -07:00
using SabreTools.Library.DatItems ;
2020-07-15 09:41:59 -07:00
using SabreTools.Library.FileTypes ;
2017-11-07 13:56:15 -08:00
using SabreTools.Library.Reports ;
2017-10-09 20:38:15 -07:00
using SabreTools.Library.Skippers ;
2017-10-09 18:04:49 -07:00
using SabreTools.Library.Tools ;
2017-10-09 20:38:15 -07:00
using NaturalSort ;
2016-10-26 22:10:47 -07:00
2017-10-06 20:46:43 -07:00
namespace SabreTools.Library.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-07-15 09:41:59 -07:00
public abstract class DatFile
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:44:05 -07:00
#region Publically available fields
2019-01-08 11:49:31 -08:00
// Internal DatHeader values
2020-07-26 22:44:05 -07:00
public DatHeader DatHeader { get ; set ; } = new DatHeader ( ) ;
2019-01-08 11:49:31 -08:00
// DatItems dictionary
2020-07-26 22:44:05 -07:00
public ItemDictionary Items { get ; set ; } = new ItemDictionary ( ) ;
2019-01-08 11:49:31 -08:00
#endregion
2020-07-15 09:41:59 -07:00
#region Instance Methods
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
#region Accessors
2019-01-08 11:49:31 -08:00
/// <summary>
2020-07-15 09:41:59 -07:00
/// Set the Date header value
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
/// <param name="date"></param>
public void SetDate ( string date )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader . Date = date ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
2020-07-15 09:41:59 -07:00
/// Set the Description header value
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
public void SetDescription ( string description )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader . Description = description ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
2020-07-15 09:41:59 -07:00
/// Set the Name header value
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
public void SetName ( string name )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader . Name = name ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
2020-07-15 09:41:59 -07:00
/// Set the Type header value
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
public void SetType ( string type )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader . Type = type ;
2019-01-08 11:49:31 -08: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>
public DatFile ( DatFile datFile )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
if ( datFile ! = null )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader = datFile . DatHeader ;
this . 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>
/// <returns>DatFile of the specific internal type that corresponds to the inputs</returns>
public static DatFile Create ( DatFormat ? datFormat = null , DatFile baseDat = null )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
switch ( datFormat )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
case DatFormat . AttractMode :
return new AttractMode ( baseDat ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case DatFormat . ClrMamePro :
return new ClrMamePro ( baseDat ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case DatFormat . CSV :
return new SeparatedValue ( baseDat , ',' ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case DatFormat . DOSCenter :
return new DosCenter ( baseDat ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case DatFormat . EverdriveSMDB :
return new EverdriveSMDB ( baseDat ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case DatFormat . Json :
return new Json ( baseDat ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case DatFormat . Listrom :
return new Listrom ( baseDat ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case DatFormat . Listxml :
return new Listxml ( baseDat ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case DatFormat . Logiqx :
return new Logiqx ( baseDat , false ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case DatFormat . LogiqxDeprecated :
return new Logiqx ( baseDat , true ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case DatFormat . MissFile :
return new Missfile ( baseDat ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case DatFormat . OfflineList :
return new OfflineList ( baseDat ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case DatFormat . OpenMSX :
return new OpenMSX ( baseDat ) ;
case DatFormat . RedumpMD5 :
return new Hashfile ( baseDat , Hash . MD5 ) ;
#if NET_FRAMEWORK
case DatFormat . RedumpRIPEMD160 :
return new Hashfile ( baseDat , Hash . RIPEMD160 ) ;
#endif
case DatFormat . RedumpSFV :
return new Hashfile ( baseDat , Hash . CRC ) ;
case DatFormat . RedumpSHA1 :
return new Hashfile ( baseDat , Hash . SHA1 ) ;
case DatFormat . RedumpSHA256 :
return new Hashfile ( baseDat , Hash . SHA256 ) ;
case DatFormat . RedumpSHA384 :
return new Hashfile ( baseDat , Hash . SHA384 ) ;
case DatFormat . RedumpSHA512 :
return new Hashfile ( baseDat , Hash . SHA512 ) ;
case DatFormat . RomCenter :
return new RomCenter ( baseDat ) ;
case DatFormat . SabreDat :
return new SabreDat ( baseDat ) ;
case DatFormat . SoftwareList :
return new SoftwareList ( baseDat ) ;
case DatFormat . SSV :
return new SeparatedValue ( baseDat , ';' ) ;
case DatFormat . TSV :
return new SeparatedValue ( baseDat , '\t' ) ;
// We use new-style Logiqx as a backup for generic DatFile
case null :
default :
return 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
{
2020-07-15 09:41:59 -07:00
DatFile datFile = Create ( datHeader . DatFormat ) ;
datFile . DatHeader = ( DatHeader ) datHeader . Clone ( ) ;
return datFile ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
2020-07-15 09:41:59 -07:00
/// Add items from another DatFile to the existing DatFile
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
/// <param name="datFile">DatFile to add from</param>
/// <param name="delete">If items should be deleted from the source DatFile</param>
public void AddFromExisting ( DatFile datFile , bool delete = false )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// Get the list of keys from the DAT
2020-07-26 22:34:45 -07:00
foreach ( string key in datFile . Items . Keys )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// Add everything from the key to the internal DAT
2020-07-26 22:34:45 -07:00
Items . AddRange ( key , datFile . Items [ key ] ) ;
2020-07-15 09:41:59 -07:00
// Now remove the key from the source DAT
if ( delete )
2020-07-26 22:34:45 -07:00
datFile . Items . Remove ( key ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
// Now remove the file dictionary from the source DAT
if ( delete )
2020-07-26 22:34:45 -07:00
datFile . Items = null ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
2020-07-15 09:41:59 -07:00
/// Apply a DatHeader to an existing DatFile
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 void ApplyDatHeader ( DatHeader datHeader )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader . ConditionalCopy ( datHeader ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
#endregion
#region Converting and Updating
2019-01-08 11:49:31 -08:00
/// <summary>
2020-07-15 09:41:59 -07:00
/// Determine if input files should be merged, diffed, or processed invidually
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
/// <param name="inputPaths">Names of the input files and/or folders</param>
/// <param name="basePaths">Names of base files and/or folders</param>
/// <param name="outDir">Optional param for output directory</param>
/// <param name="updateMode">Non-zero flag for diffing mode, zero otherwise</param>
/// <param name="inplace">True if the output files should overwrite their inputs, false otherwise</param>
/// <param name="skip">True if the first cascaded diff file should be skipped on output, false otherwise</param>
/// <param name="filter">Filter object to be passed to the DatItem level</param>
/// <param name="updateFields">List of Fields representing what should be updated [only for base replacement]</param>
/// <param name="onlySame">True if descriptions should only be replaced if the game name is the same, false otherwise [only for base replacement]</param>
public void DetermineUpdateType (
List < string > inputPaths ,
List < string > basePaths ,
string outDir ,
UpdateMode updateMode ,
bool inplace ,
bool skip ,
Filter filter ,
List < Field > updateFields ,
bool onlySame )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// Ensure we only have files in the inputs
2020-07-26 23:39:33 -07:00
List < ParentablePath > inputFileNames = DirectoryExtensions . GetFilesOnly ( inputPaths , appendparent : true ) ;
List < ParentablePath > baseFileNames = DirectoryExtensions . GetFilesOnly ( basePaths ) ;
2020-07-15 09:41:59 -07:00
// If we're in standard update mode, run through all of the inputs
if ( updateMode = = UpdateMode . None )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
Update ( inputFileNames , outDir , inplace , filter ) ;
return ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
// Reverse inputs if we're in a required mode
if ( updateMode . HasFlag ( UpdateMode . DiffReverseCascade ) )
inputFileNames . Reverse ( ) ;
if ( updateMode . HasFlag ( UpdateMode . ReverseBaseReplace ) )
baseFileNames . Reverse ( ) ;
// If we're in merging mode
if ( updateMode . HasFlag ( UpdateMode . Merge ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// Populate the combined data and get the headers
PopulateUserData ( inputFileNames , filter ) ;
MergeNoDiff ( inputFileNames , outDir ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
// If we have one of the standard diffing modes
else if ( updateMode . HasFlag ( UpdateMode . DiffDupesOnly )
| | updateMode . HasFlag ( UpdateMode . DiffNoDupesOnly )
| | updateMode . HasFlag ( UpdateMode . DiffIndividualsOnly ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// Populate the combined data
PopulateUserData ( inputFileNames , filter ) ;
DiffNoCascade ( inputFileNames , outDir , updateMode ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
// If we have one of the cascaded diffing modes
else if ( updateMode . HasFlag ( UpdateMode . DiffCascade )
| | updateMode . HasFlag ( UpdateMode . DiffReverseCascade ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// Populate the combined data and get the headers
List < DatHeader > datHeaders = PopulateUserData ( inputFileNames , filter ) ;
DiffCascade ( inputFileNames , datHeaders , outDir , inplace , skip ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
// If we have diff against mode
else if ( updateMode . HasFlag ( UpdateMode . DiffAgainst ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// Populate the combined data
PopulateUserData ( baseFileNames , filter ) ;
DiffAgainst ( inputFileNames , outDir , inplace ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
// If we have one of the base replacement modes
else if ( updateMode . HasFlag ( UpdateMode . BaseReplace )
| | updateMode . HasFlag ( UpdateMode . ReverseBaseReplace ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// Populate the combined data
PopulateUserData ( baseFileNames , filter ) ;
BaseReplace ( inputFileNames , outDir , inplace , filter , updateFields , onlySame ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
return ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
2020-07-15 09:41:59 -07:00
/// Populate the user DatData object from the input files
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
/// <param name="inputs">Paths to DATs to parse</param>
/// <param name="filter">Filter object to be passed to the DatItem level</param>
/// <returns>List of DatData objects representing headers</returns>
2020-07-26 23:39:33 -07:00
private List < DatHeader > PopulateUserData ( List < ParentablePath > inputs , Filter filter )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatFile [ ] datFiles = new DatFile [ inputs . Count ] ;
InternalStopwatch watch = new InternalStopwatch ( "Processing individual DATs" ) ;
// Parse all of the DATs into their own DatFiles in the array
Parallel . For ( 0 , inputs . Count , Globals . ParallelOptions , i = >
2019-01-08 11:49:31 -08:00
{
2020-07-26 23:39:33 -07:00
var input = inputs [ i ] ;
Globals . Logger . User ( $"Adding DAT: {input.CurrentPath}" ) ;
2020-07-15 09:41:59 -07:00
datFiles [ i ] = Create ( DatHeader . CloneFiltering ( ) ) ;
datFiles [ i ] . Parse ( input , i , keep : true ) ;
} ) ;
watch . Stop ( ) ;
watch . Start ( "Populating internal DAT" ) ;
2020-07-23 13:54:59 -07:00
for ( int i = 0 ; i < inputs . Count ; i + + )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
AddFromExisting ( datFiles [ i ] , true ) ;
2020-07-23 13:54:59 -07:00
}
2020-07-15 09:41:59 -07:00
// Now that we have a merged DAT, filter it
filter . FilterDatFile ( this , false /* useTags */ ) ;
watch . Stop ( ) ;
return datFiles . Select ( d = > d . DatHeader ) . ToList ( ) ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
2020-07-15 09:41:59 -07:00
/// Replace item values from the base set represented by the current DAT
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
/// <param name="inputFileNames">Names of the input files</param>
/// <param name="outDir">Optional param for output directory</param>
/// <param name="inplace">True if the output files should overwrite their inputs, false otherwise</param>
/// <param name="filter">Filter object to be passed to the DatItem level</param>
/// <param name="updateFields">List of Fields representing what should be updated [only for base replacement]</param>
/// <param name="onlySame">True if descriptions should only be replaced if the game name is the same, false otherwise</param>
private void BaseReplace (
2020-07-26 23:39:33 -07:00
List < ParentablePath > inputFileNames ,
2020-07-15 09:41:59 -07:00
string outDir ,
bool inplace ,
Filter filter ,
List < Field > updateFields ,
bool onlySame )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// Fields unique to a DatItem
List < Field > datItemFields = new List < Field > ( )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
Field . Name ,
Field . PartName ,
Field . PartInterface ,
Field . Features ,
Field . AreaName ,
Field . AreaSize ,
Field . BiosDescription ,
Field . Default ,
Field . Language ,
Field . Date ,
Field . Bios ,
Field . Size ,
Field . Offset ,
Field . Merge ,
Field . Region ,
Field . Index ,
Field . Writable ,
Field . Optional ,
Field . Status ,
2020-06-06 13:53:31 -07:00
2020-07-15 09:41:59 -07:00
Field . CRC ,
Field . MD5 ,
#if NET_FRAMEWORK
Field . RIPEMD160 ,
#endif
Field . SHA1 ,
Field . SHA256 ,
Field . SHA384 ,
Field . SHA512 ,
} ;
2020-06-06 13:53:31 -07:00
2020-07-15 09:41:59 -07:00
// Fields unique to a Machine
List < Field > machineFields = new List < Field > ( )
{
Field . MachineName ,
Field . Comment ,
Field . Description ,
Field . Year ,
Field . Manufacturer ,
Field . Publisher ,
Field . RomOf ,
Field . CloneOf ,
Field . SampleOf ,
Field . Supported ,
Field . SourceFile ,
Field . Runnable ,
Field . Board ,
Field . RebuildTo ,
Field . Devices ,
Field . SlotOptions ,
Field . Infos ,
Field . MachineType ,
} ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// We want to try to replace each item in each input DAT from the base
2020-07-26 23:39:33 -07:00
foreach ( ParentablePath path in inputFileNames )
2020-07-15 09:41:59 -07:00
{
2020-07-26 23:39:33 -07:00
Globals . Logger . User ( $"Replacing items in '{path.CurrentPath}' from the base DAT" ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// First we parse in the DAT internally
DatFile intDat = Create ( DatHeader . CloneFiltering ( ) ) ;
intDat . Parse ( path , 1 , keep : true ) ;
filter . FilterDatFile ( intDat , false /* useTags */ ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// If we are matching based on DatItem fields of any sort
if ( updateFields . Intersect ( datItemFields ) . Any ( ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 10:47:13 -07:00
// For comparison's sake, we want to use CRC as the base bucketing
2020-07-26 22:34:45 -07:00
Items . BucketBy ( BucketedBy . CRC , DedupeType . Full ) ;
intDat . Items . BucketBy ( BucketedBy . CRC , DedupeType . None ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Then we do a hashwise comparison against the base DAT
2020-07-26 22:34:45 -07:00
Parallel . ForEach ( intDat . Items . Keys , Globals . ParallelOptions , key = >
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
List < DatItem > datItems = intDat . Items [ key ] ;
2019-01-08 11:49:31 -08:00
List < DatItem > newDatItems = new List < DatItem > ( ) ;
foreach ( DatItem datItem in datItems )
{
2020-07-26 22:34:45 -07:00
List < DatItem > dupes = Items . GetDuplicates ( datItem , sorted : true ) ;
2020-06-06 13:53:31 -07:00
DatItem newDatItem = datItem . Clone ( ) as DatItem ;
2020-07-15 09:41:59 -07:00
// Cast versions of the new DatItem for use below
var archive = newDatItem as Archive ;
var biosSet = newDatItem as BiosSet ;
var blank = newDatItem as Blank ;
var disk = newDatItem as Disk ;
var release = newDatItem as Release ;
var rom = newDatItem as Rom ;
var sample = newDatItem as Sample ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( dupes . Count > 0 )
{
// Get the first duplicate for grabbing information from
var firstDupe = dupes . First ( ) ;
var archiveDupe = firstDupe as Archive ;
var biosSetDupe = firstDupe as BiosSet ;
var blankDupe = firstDupe as Blank ;
var diskDupe = firstDupe as Disk ;
var releaseDupe = firstDupe as Release ;
var romDupe = firstDupe as Rom ;
var sampleDupe = firstDupe as Sample ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
#region Non - hash fields
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Name ) )
newDatItem . Name = firstDupe . Name ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . PartName ) )
newDatItem . PartName = firstDupe . PartName ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . PartInterface ) )
newDatItem . PartInterface = firstDupe . PartInterface ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Features ) )
newDatItem . Features = firstDupe . Features ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . AreaName ) )
newDatItem . AreaName = firstDupe . AreaName ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . AreaSize ) )
newDatItem . AreaSize = firstDupe . AreaSize ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . BiosDescription ) )
{
if ( newDatItem . ItemType = = ItemType . BiosSet )
biosSet . Description = biosSetDupe . Description ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Default ) )
{
if ( newDatItem . ItemType = = ItemType . BiosSet )
biosSet . Default = biosSetDupe . Default ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
else if ( newDatItem . ItemType = = ItemType . Release )
release . Default = releaseDupe . Default ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Language ) )
{
if ( newDatItem . ItemType = = ItemType . Release )
release . Language = releaseDupe . Language ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Date ) )
{
if ( newDatItem . ItemType = = ItemType . Release )
release . Date = releaseDupe . Date ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
else if ( newDatItem . ItemType = = ItemType . Rom )
rom . Date = romDupe . Date ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Bios ) )
{
if ( newDatItem . ItemType = = ItemType . Rom )
rom . Bios = romDupe . Bios ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Size ) )
{
if ( newDatItem . ItemType = = ItemType . Rom )
rom . Size = romDupe . Size ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Offset ) )
{
if ( newDatItem . ItemType = = ItemType . Rom )
rom . Offset = romDupe . Offset ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Merge ) )
{
if ( newDatItem . ItemType = = ItemType . Disk )
disk . MergeTag = diskDupe . MergeTag ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
else if ( newDatItem . ItemType = = ItemType . Rom )
rom . MergeTag = romDupe . MergeTag ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Region ) )
{
if ( newDatItem . ItemType = = ItemType . Disk )
disk . Region = diskDupe . Region ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
else if ( newDatItem . ItemType = = ItemType . Release )
release . Region = releaseDupe . Region ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
else if ( newDatItem . ItemType = = ItemType . Rom )
rom . Region = romDupe . Region ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Index ) )
{
if ( newDatItem . ItemType = = ItemType . Disk )
disk . Index = diskDupe . Index ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Writable ) )
{
if ( newDatItem . ItemType = = ItemType . Disk )
disk . Writable = diskDupe . Writable ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Optional ) )
{
if ( newDatItem . ItemType = = ItemType . Disk )
disk . Optional = diskDupe . Optional ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
else if ( newDatItem . ItemType = = ItemType . Rom )
rom . Optional = romDupe . Optional ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Status ) )
{
if ( newDatItem . ItemType = = ItemType . Disk )
disk . ItemStatus = diskDupe . ItemStatus ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
else if ( newDatItem . ItemType = = ItemType . Rom )
rom . ItemStatus = romDupe . ItemStatus ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
#endregion
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
#region Hash fields
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . CRC ) )
{
if ( newDatItem . ItemType = = ItemType . Rom )
{
if ( string . IsNullOrEmpty ( rom . CRC ) & & ! string . IsNullOrEmpty ( romDupe . CRC ) )
rom . CRC = romDupe . CRC ;
}
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . MD5 ) )
{
if ( newDatItem . ItemType = = ItemType . Disk )
{
if ( string . IsNullOrEmpty ( disk . MD5 ) & & ! string . IsNullOrEmpty ( diskDupe . MD5 ) )
disk . MD5 = diskDupe . MD5 ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( newDatItem . ItemType = = ItemType . Rom )
{
if ( string . IsNullOrEmpty ( rom . MD5 ) & & ! string . IsNullOrEmpty ( romDupe . MD5 ) )
rom . MD5 = romDupe . MD5 ;
}
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
#if NET_FRAMEWORK
if ( updateFields . Contains ( Field . RIPEMD160 ) )
{
if ( newDatItem . ItemType = = ItemType . Disk )
{
if ( string . IsNullOrEmpty ( disk . RIPEMD160 ) & & ! string . IsNullOrEmpty ( diskDupe . RIPEMD160 ) )
disk . RIPEMD160 = diskDupe . RIPEMD160 ;
}
if ( newDatItem . ItemType = = ItemType . Rom )
{
if ( string . IsNullOrEmpty ( rom . RIPEMD160 ) & & ! string . IsNullOrEmpty ( romDupe . RIPEMD160 ) )
rom . RIPEMD160 = romDupe . RIPEMD160 ;
}
}
#endif
if ( updateFields . Contains ( Field . SHA1 ) )
{
if ( newDatItem . ItemType = = ItemType . Disk )
{
if ( string . IsNullOrEmpty ( disk . SHA1 ) & & ! string . IsNullOrEmpty ( diskDupe . SHA1 ) )
disk . SHA1 = diskDupe . SHA1 ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( newDatItem . ItemType = = ItemType . Rom )
{
if ( string . IsNullOrEmpty ( rom . SHA1 ) & & ! string . IsNullOrEmpty ( romDupe . SHA1 ) )
rom . SHA1 = romDupe . SHA1 ;
}
}
2020-06-10 22:37:19 -07:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . SHA256 ) )
{
if ( newDatItem . ItemType = = ItemType . Disk )
{
if ( string . IsNullOrEmpty ( disk . SHA256 ) & & ! string . IsNullOrEmpty ( diskDupe . SHA256 ) )
disk . SHA256 = diskDupe . SHA256 ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( newDatItem . ItemType = = ItemType . Rom )
{
if ( string . IsNullOrEmpty ( rom . SHA256 ) & & ! string . IsNullOrEmpty ( romDupe . SHA256 ) )
rom . SHA256 = romDupe . SHA256 ;
}
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . SHA384 ) )
{
if ( newDatItem . ItemType = = ItemType . Disk )
{
if ( string . IsNullOrEmpty ( disk . SHA384 ) & & ! string . IsNullOrEmpty ( diskDupe . SHA384 ) )
disk . SHA384 = diskDupe . SHA384 ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( newDatItem . ItemType = = ItemType . Rom )
{
if ( string . IsNullOrEmpty ( rom . SHA384 ) & & ! string . IsNullOrEmpty ( romDupe . SHA384 ) )
rom . SHA384 = romDupe . SHA384 ;
}
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . SHA512 ) )
{
if ( newDatItem . ItemType = = ItemType . Disk )
{
if ( string . IsNullOrEmpty ( disk . SHA512 ) & & ! string . IsNullOrEmpty ( diskDupe . SHA512 ) )
disk . SHA512 = diskDupe . SHA512 ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( newDatItem . ItemType = = ItemType . Rom )
{
if ( string . IsNullOrEmpty ( rom . SHA512 ) & & ! string . IsNullOrEmpty ( romDupe . SHA512 ) )
rom . SHA512 = romDupe . SHA512 ;
}
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
#endregion
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Now assign back the duplicate archive to the original
switch ( newDatItem . ItemType )
{
case ItemType . Archive :
newDatItem = archive . Clone ( ) as Archive ;
break ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case ItemType . BiosSet :
newDatItem = biosSet . Clone ( ) as BiosSet ;
break ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case ItemType . Blank :
newDatItem = blank . Clone ( ) as Blank ;
break ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case ItemType . Disk :
newDatItem = disk . Clone ( ) as Disk ;
break ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case ItemType . Release :
newDatItem = release . Clone ( ) as Release ;
break ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case ItemType . Rom :
newDatItem = rom . Clone ( ) as Rom ;
break ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
case ItemType . Sample :
newDatItem = sample . Clone ( ) as Sample ;
break ;
}
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
newDatItems . Add ( newDatItem ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
// Now add the new list to the key
2020-07-26 22:34:45 -07:00
intDat . Items . Remove ( key ) ;
intDat . Items . AddRange ( key , newDatItems ) ;
2020-07-15 09:41:59 -07:00
} ) ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// If we are matching based on Machine fields of any sort
if ( updateFields . Intersect ( machineFields ) . Any ( ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 10:47:13 -07:00
// For comparison's sake, we want to use Machine Name as the base bucketing
2020-07-26 22:34:45 -07:00
Items . BucketBy ( BucketedBy . Game , DedupeType . Full ) ;
intDat . Items . BucketBy ( BucketedBy . Game , DedupeType . None ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Then we do a namewise comparison against the base DAT
2020-07-26 22:34:45 -07:00
Parallel . ForEach ( intDat . Items . Keys , Globals . ParallelOptions , key = >
2020-07-15 09:41:59 -07:00
{
2020-07-26 22:34:45 -07:00
List < DatItem > datItems = intDat . Items [ key ] ;
2020-07-15 09:41:59 -07:00
List < DatItem > newDatItems = new List < DatItem > ( ) ;
foreach ( DatItem datItem in datItems )
{
DatItem newDatItem = datItem . Clone ( ) as DatItem ;
2020-07-26 22:34:45 -07:00
if ( Items . ContainsKey ( key ) & & Items [ key ] . Count ( ) > 0 )
2020-07-15 09:41:59 -07:00
{
2020-07-26 22:34:45 -07:00
var firstDupe = Items [ key ] [ 0 ] ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . MachineName ) )
newDatItem . MachineName = firstDupe . MachineName ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Comment ) )
newDatItem . Comment = firstDupe . Comment ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Description ) )
{
if ( ! onlySame | | ( onlySame & & newDatItem . MachineName = = newDatItem . MachineDescription ) )
newDatItem . MachineDescription = firstDupe . MachineDescription ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Year ) )
newDatItem . Year = firstDupe . Year ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Manufacturer ) )
newDatItem . Manufacturer = firstDupe . Manufacturer ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Publisher ) )
newDatItem . Publisher = firstDupe . Publisher ;
2019-01-08 11:49:31 -08:00
2020-07-18 21:35:17 -07:00
if ( updateFields . Contains ( Field . Category ) )
newDatItem . Category = firstDupe . Category ;
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . RomOf ) )
newDatItem . RomOf = firstDupe . RomOf ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . CloneOf ) )
newDatItem . CloneOf = firstDupe . CloneOf ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . SampleOf ) )
newDatItem . SampleOf = firstDupe . SampleOf ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Supported ) )
newDatItem . Supported = firstDupe . Supported ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . SourceFile ) )
newDatItem . SourceFile = firstDupe . SourceFile ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Runnable ) )
newDatItem . Runnable = firstDupe . Runnable ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Board ) )
newDatItem . Board = firstDupe . Board ;
if ( updateFields . Contains ( Field . RebuildTo ) )
newDatItem . RebuildTo = firstDupe . RebuildTo ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Devices ) )
newDatItem . Devices = firstDupe . Devices ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . SlotOptions ) )
newDatItem . SlotOptions = firstDupe . SlotOptions ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . Infos ) )
newDatItem . Infos = firstDupe . Infos ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( updateFields . Contains ( Field . MachineType ) )
newDatItem . MachineType = firstDupe . MachineType ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
newDatItems . Add ( newDatItem ) ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Now add the new list to the key
2020-07-26 22:34:45 -07:00
intDat . Items . Remove ( key ) ;
intDat . Items . AddRange ( key , newDatItems ) ;
2020-07-15 09:41:59 -07:00
} ) ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Determine the output path for the DAT
2020-07-26 23:46:59 -07:00
string interOutDir = path . GetOutputPath ( outDir , inplace ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Once we're done, try writing out
intDat . Write ( interOutDir , overwrite : inplace ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Due to possible memory requirements, we force a garbage collection
GC . Collect ( ) ;
}
}
2019-01-08 11:49:31 -08:00
/// <summary>
2020-07-15 09:41:59 -07:00
/// Output diffs against a base set represented by the current DAT
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
/// <param name="inputFileNames">Names of the input files</param>
/// <param name="outDir">Optional param for output directory</param>
/// <param name="inplace">True if the output files should overwrite their inputs, false otherwise</param>
2020-07-26 23:39:33 -07:00
private void DiffAgainst ( List < ParentablePath > inputFileNames , string outDir , bool inplace )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// For comparison's sake, we want to use CRC as the base ordering
2020-07-26 22:34:45 -07:00
Items . BucketBy ( BucketedBy . CRC , DedupeType . Full ) ;
2020-07-15 09:41:59 -07:00
// Now we want to compare each input DAT against the base
2020-07-26 23:39:33 -07:00
foreach ( ParentablePath path in inputFileNames )
2019-01-08 11:49:31 -08:00
{
2020-07-26 23:39:33 -07:00
Globals . Logger . User ( $"Comparing '{path.CurrentPath}' to base DAT" ) ;
2020-07-15 09:41:59 -07:00
// First we parse in the DAT internally
DatFile intDat = Create ( ) ;
intDat . Parse ( path , 1 , keep : true ) ;
2020-07-15 10:47:13 -07:00
// For comparison's sake, we want to use CRC as the base bucketing
2020-07-26 22:34:45 -07:00
intDat . Items . BucketBy ( BucketedBy . CRC , DedupeType . Full ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Then we do a hashwise comparison against the base DAT
2020-07-26 22:34:45 -07:00
Parallel . ForEach ( intDat . Items . Keys , Globals . ParallelOptions , key = >
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
List < DatItem > datItems = intDat . Items [ key ] ;
2020-07-15 09:41:59 -07:00
List < DatItem > keepDatItems = new List < DatItem > ( ) ;
foreach ( DatItem datItem in datItems )
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
if ( ! Items . HasDuplicates ( datItem , true ) )
2020-07-15 09:41:59 -07:00
keepDatItems . Add ( datItem ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
// Now add the new list to the key
2020-07-26 22:34:45 -07:00
intDat . Items . Remove ( key ) ;
intDat . Items . AddRange ( key , keepDatItems ) ;
2019-01-08 11:49:31 -08:00
} ) ;
2020-07-15 09:41:59 -07:00
// Determine the output path for the DAT
2020-07-26 23:46:59 -07:00
string interOutDir = path . GetOutputPath ( outDir , inplace ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Once we're done, try writing out
intDat . Write ( interOutDir , overwrite : inplace ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Due to possible memory requirements, we force a garbage collection
GC . Collect ( ) ;
2019-01-08 11:49:31 -08:00
}
}
/// <summary>
2020-07-15 09:41:59 -07:00
/// Output cascading diffs
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
/// <param name="inputs">List of inputs to write out from</param>
/// <param name="datHeaders">Dat headers used optionally</param>
/// <param name="outDir">Output directory to write the DATs to</param>
/// <param name="inplace">True if cascaded diffs are outputted in-place, false otherwise</param>
/// <param name="skip">True if the first cascaded diff file should be skipped on output, false otherwise</param>
2020-07-26 23:39:33 -07:00
private void DiffCascade ( List < ParentablePath > inputs , List < DatHeader > datHeaders , string outDir , bool inplace , bool skip )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// Create a list of DatData objects representing output files
List < DatFile > outDats = new List < DatFile > ( ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Loop through each of the inputs and get or create a new DatData object
InternalStopwatch watch = new InternalStopwatch ( "Initializing all output DATs" ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
DatFile [ ] outDatsArray = new DatFile [ inputs . Count ] ;
Parallel . For ( 0 , inputs . Count , Globals . ParallelOptions , j = >
2019-01-08 11:49:31 -08:00
{
2020-07-26 23:46:59 -07:00
string innerpost = $" ({j} - {inputs[j].GetNormalizedFileName(true)} Only)" ;
2020-07-15 09:41:59 -07:00
DatFile diffData ;
// If we're in inplace mode or the output directory is set, take the appropriate DatData object already stored
if ( inplace | | outDir ! = Environment . CurrentDirectory )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
diffData = Create ( datHeaders [ j ] ) ;
}
else
{
diffData = Create ( DatHeader ) ;
diffData . DatHeader . FileName + = innerpost ;
diffData . DatHeader . Name + = innerpost ;
diffData . DatHeader . Description + = innerpost ;
2019-01-08 11:49:31 -08:00
}
2020-07-26 22:34:45 -07:00
diffData . Items = new ItemDictionary ( ) ;
2020-07-15 09:41:59 -07:00
outDatsArray [ j ] = diffData ;
2019-01-08 11:49:31 -08:00
} ) ;
2020-07-15 09:41:59 -07:00
outDats = outDatsArray . ToList ( ) ;
watch . Stop ( ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 10:47:13 -07:00
// Then, ensure that the internal dat can be bucketed in the best possible way
2020-07-26 22:34:45 -07:00
Items . BucketBy ( BucketedBy . CRC , DedupeType . None ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Now, loop through the dictionary and populate the correct DATs
watch . Start ( "Populating all output DATs" ) ;
2019-01-08 11:49:31 -08:00
2020-07-26 22:34:45 -07:00
Parallel . ForEach ( Items . Keys , Globals . ParallelOptions , key = >
2020-07-15 09:41:59 -07:00
{
2020-07-26 22:34:45 -07:00
List < DatItem > items = DatItem . Merge ( Items [ key ] ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// If the rom list is empty or null, just skip it
if ( items = = null | | items . Count = = 0 )
return ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
foreach ( DatItem item in items )
{
// There's odd cases where there are items with System ID < 0. Skip them for now
if ( item . IndexId < 0 )
{
Globals . Logger . Warning ( $"Item found with a <0 SystemID: {item.Name}" ) ;
continue ;
}
2019-01-08 11:49:31 -08:00
2020-07-26 22:34:45 -07:00
outDats [ item . IndexId ] . Items . Add ( key , item ) ;
2020-07-15 09:41:59 -07:00
}
} ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
watch . Stop ( ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Finally, loop through and output each of the DATs
watch . Start ( "Outputting all created DATs" ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
Parallel . For ( ( skip ? 1 : 0 ) , inputs . Count , Globals . ParallelOptions , j = >
{
2020-07-26 23:46:59 -07:00
string path = inputs [ j ] . GetOutputPath ( outDir , inplace ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Try to output the file
outDats [ j ] . Write ( path , overwrite : inplace ) ;
} ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
watch . Stop ( ) ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
2020-07-15 09:41:59 -07:00
/// Output non-cascading diffs
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
/// <param name="inputs">List of inputs to write out from</param>
/// <param name="outDir">Output directory to write the DATs to</param>
/// <param name="diff">Non-zero flag for diffing mode, zero otherwise</param>
2020-07-26 23:39:33 -07:00
private void DiffNoCascade ( List < ParentablePath > inputs , string outDir , UpdateMode diff )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
InternalStopwatch watch = new InternalStopwatch ( "Initializing all output DATs" ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Default vars for use
string post = string . Empty ;
DatFile outerDiffData = Create ( ) ;
DatFile dupeData = Create ( ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Fill in any information not in the base DAT
if ( string . IsNullOrWhiteSpace ( DatHeader . FileName ) )
DatHeader . FileName = "All DATs" ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( string . IsNullOrWhiteSpace ( DatHeader . Name ) )
DatHeader . Name = "All DATs" ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( string . IsNullOrWhiteSpace ( DatHeader . Description ) )
DatHeader . Description = "All DATs" ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Don't have External dupes
if ( diff . HasFlag ( UpdateMode . DiffNoDupesOnly ) )
{
post = " (No Duplicates)" ;
outerDiffData = Create ( DatHeader ) ;
outerDiffData . DatHeader . FileName + = post ;
outerDiffData . DatHeader . Name + = post ;
outerDiffData . DatHeader . Description + = post ;
2020-07-26 22:34:45 -07:00
outerDiffData . Items = new ItemDictionary ( ) ;
2020-07-15 09:41:59 -07:00
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Have External dupes
if ( diff . HasFlag ( UpdateMode . DiffDupesOnly ) )
{
post = " (Duplicates)" ;
dupeData = Create ( DatHeader ) ;
dupeData . DatHeader . FileName + = post ;
dupeData . DatHeader . Name + = post ;
dupeData . DatHeader . Description + = post ;
2020-07-26 22:34:45 -07:00
dupeData . Items = new ItemDictionary ( ) ;
2020-07-15 09:41:59 -07:00
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Create a list of DatData objects representing individual output files
List < DatFile > outDats = new List < DatFile > ( ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Loop through each of the inputs and get or create a new DatData object
if ( diff . HasFlag ( UpdateMode . DiffIndividualsOnly ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatFile [ ] outDatsArray = new DatFile [ inputs . Count ] ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
Parallel . For ( 0 , inputs . Count , Globals . ParallelOptions , j = >
{
2020-07-26 23:46:59 -07:00
string innerpost = $" ({j} - {inputs[j].GetNormalizedFileName(true)} Only)" ;
2020-07-15 09:41:59 -07:00
DatFile diffData = Create ( DatHeader ) ;
diffData . DatHeader . FileName + = innerpost ;
diffData . DatHeader . Name + = innerpost ;
diffData . DatHeader . Description + = innerpost ;
2020-07-26 22:34:45 -07:00
diffData . Items = new ItemDictionary ( ) ;
2020-07-15 09:41:59 -07:00
outDatsArray [ j ] = diffData ;
} ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
outDats = outDatsArray . ToList ( ) ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
watch . Stop ( ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Now, loop through the dictionary and populate the correct DATs
watch . Start ( "Populating all output DATs" ) ;
2019-01-08 11:49:31 -08:00
2020-07-26 22:34:45 -07:00
Parallel . ForEach ( Items . Keys , Globals . ParallelOptions , key = >
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
List < DatItem > items = DatItem . Merge ( Items [ key ] ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// If the rom list is empty or null, just skip it
if ( items = = null | | items . Count = = 0 )
return ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Loop through and add the items correctly
foreach ( DatItem item in items )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// No duplicates
if ( diff . HasFlag ( UpdateMode . DiffNoDupesOnly ) | | diff . HasFlag ( UpdateMode . DiffIndividualsOnly ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
if ( item . DupeType . HasFlag ( DupeType . Internal ) | | item . DupeType = = 0x00 )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// Individual DATs that are output
if ( diff . HasFlag ( UpdateMode . DiffIndividualsOnly ) )
2020-07-26 22:34:45 -07:00
outDats [ item . IndexId ] . Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Merged no-duplicates DAT
if ( diff . HasFlag ( UpdateMode . DiffNoDupesOnly ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatItem newrom = item . Clone ( ) as DatItem ;
2020-07-26 23:39:33 -07:00
newrom . MachineName + = $" ({Path.GetFileNameWithoutExtension(inputs[item.IndexId].CurrentPath)})" ;
2020-07-15 09:41:59 -07:00
2020-07-26 22:34:45 -07:00
outerDiffData . Items . Add ( key , newrom ) ;
2019-01-08 11:49:31 -08:00
}
}
}
2020-07-15 09:41:59 -07:00
// Duplicates only
if ( diff . HasFlag ( UpdateMode . DiffNoDupesOnly ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
if ( item . DupeType . HasFlag ( DupeType . External ) )
{
DatItem newrom = item . Clone ( ) as DatItem ;
2020-07-26 23:39:33 -07:00
newrom . MachineName + = $" ({Path.GetFileNameWithoutExtension(inputs[item.IndexId].CurrentPath)})" ;
2020-07-15 09:41:59 -07:00
2020-07-26 22:34:45 -07:00
dupeData . Items . Add ( key , newrom ) ;
2020-07-15 09:41:59 -07:00
}
2019-01-08 11:49:31 -08:00
}
}
2020-07-15 09:41:59 -07:00
} ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
watch . Stop ( ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Finally, loop through and output each of the DATs
watch . Start ( "Outputting all created DATs" ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Output the difflist (a-b)+(b-a) diff
if ( diff . HasFlag ( UpdateMode . DiffNoDupesOnly ) )
outerDiffData . Write ( outDir , overwrite : false ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Output the (ab) diff
if ( diff . HasFlag ( UpdateMode . DiffDupesOnly ) )
dupeData . Write ( outDir , overwrite : false ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Output the individual (a-b) DATs
if ( diff . HasFlag ( UpdateMode . DiffIndividualsOnly ) )
{
Parallel . For ( 0 , inputs . Count , Globals . ParallelOptions , j = >
2019-01-08 11:49:31 -08:00
{
2020-07-26 23:46:59 -07:00
string path = inputs [ j ] . GetOutputPath ( outDir , false /* inplace */ ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Try to output the file
outDats [ j ] . Write ( path , overwrite : false ) ;
} ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
watch . Stop ( ) ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
2020-07-15 09:41:59 -07:00
/// Output user defined merge
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
/// <param name="inputs">List of inputs to write out from</param>
/// <param name="outDir">Output directory to write the DATs to</param>
2020-07-26 23:39:33 -07:00
private void MergeNoDiff ( List < ParentablePath > inputs , string outDir )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// If we're in SuperDAT mode, prefix all games with their respective DATs
if ( DatHeader . Type = = "SuperDAT" )
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
Parallel . ForEach ( Items . Keys , Globals . ParallelOptions , key = >
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
List < DatItem > items = Items [ key ] . ToList ( ) ;
2020-07-15 09:41:59 -07:00
List < DatItem > newItems = new List < DatItem > ( ) ;
foreach ( DatItem item in items )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatItem newItem = item ;
2020-07-26 23:39:33 -07:00
string filename = inputs [ newItem . IndexId ] . CurrentPath ;
string rootpath = inputs [ newItem . IndexId ] . ParentPath ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
rootpath + = ( string . IsNullOrWhiteSpace ( rootpath ) ? string . Empty : Path . DirectorySeparatorChar . ToString ( ) ) ;
filename = filename . Remove ( 0 , rootpath . Length ) ;
newItem . MachineName = Path . GetDirectoryName ( filename ) + Path . DirectorySeparatorChar
+ Path . GetFileNameWithoutExtension ( filename ) + Path . DirectorySeparatorChar
+ newItem . MachineName ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
newItems . Add ( newItem ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-26 22:34:45 -07:00
Items . Remove ( key ) ;
Items . AddRange ( key , newItems ) ;
2020-07-15 09:41:59 -07:00
} ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
// Try to output the file
Write ( outDir , overwrite : false ) ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
2020-07-15 09:41:59 -07:00
/// Convert, update, and filter a DAT file or set of files
2019-01-08 11:49:31 -08:00
/// </summary>
2020-07-15 09:41:59 -07:00
/// <param name="inputFileNames">Names of the input files and/or folders</param>
/// <param name="outDir">Optional param for output directory</param>
/// <param name="inplace">True if the output files should overwrite their inputs, false otherwise</param>
/// <param name="filter">Filter object to be passed to the DatItem level</param>
2020-07-26 23:39:33 -07:00
private void Update ( List < ParentablePath > inputFileNames , string outDir , bool inplace , Filter filter )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// Iterate over the files
2020-07-26 23:39:33 -07:00
foreach ( ParentablePath file in inputFileNames )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatFile innerDatdata = Create ( DatHeader ) ;
2020-07-26 23:39:33 -07:00
Globals . Logger . User ( $"Processing '{Path.GetFileName(file.CurrentPath)}'" ) ;
2020-07-15 09:41:59 -07:00
innerDatdata . Parse ( file , keep : true ,
keepext : innerDatdata . DatHeader . DatFormat . HasFlag ( DatFormat . TSV )
| | innerDatdata . DatHeader . DatFormat . HasFlag ( DatFormat . CSV )
| | innerDatdata . DatHeader . DatFormat . HasFlag ( DatFormat . SSV ) ) ;
filter . FilterDatFile ( innerDatdata , false /* useTags */ ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Get the correct output path
2020-07-26 23:46:59 -07:00
string realOutDir = file . GetOutputPath ( outDir , inplace ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Try to output the file, overwriting only if it's not in the current directory
innerDatdata . Write ( realOutDir , overwrite : inplace ) ;
2019-01-08 11:49:31 -08:00
}
}
2020-07-15 09:41:59 -07:00
#endregion
2019-01-08 11:49:31 -08:00
#region Parsing
/// <summary>
2020-07-15 09:41:59 -07:00
/// Create a DatFile and parse a file into it
2019-01-08 11:49:31 -08:00
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
2020-07-15 09:41:59 -07:00
public static DatFile CreateAndParse ( string filename )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatFile datFile = Create ( ) ;
2020-07-26 23:39:33 -07:00
datFile . Parse ( new ParentablePath ( filename ) ) ;
2020-07-15 09:41:59 -07:00
return datFile ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
/// Parse a DAT and return all found games and roms within
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
2020-07-15 09:41:59 -07:00
/// <param name="indexId">Index ID for the DAT</param>
2019-01-08 11:49:31 -08:00
/// <param name="keep">True if full pathnames are to be kept, false otherwise (default)</param>
/// <param name="keepext">True if original extension should be kept, false otherwise (default)</param>
2020-07-26 23:39:33 -07:00
public void Parse ( ParentablePath filename , int indexId = 0 , bool keep = false , bool keepext = false )
2019-01-08 11:49:31 -08:00
{
2020-07-26 23:39:33 -07:00
// Get the current path from the filename
string currentPath = filename . CurrentPath ;
2019-01-08 11:49:31 -08:00
// Check the file extension first as a safeguard
2020-07-26 23:39:33 -07:00
if ( ! PathExtensions . HasValidDatExtension ( currentPath ) )
2019-01-08 11:49:31 -08:00
return ;
// If the output filename isn't set already, get the internal filename
2020-07-26 23:39:33 -07:00
DatHeader . FileName = ( string . IsNullOrWhiteSpace ( DatHeader . FileName ) ? ( keepext ? Path . GetFileName ( currentPath ) : Path . GetFileNameWithoutExtension ( currentPath ) ) : DatHeader . FileName ) ;
2019-01-08 11:49:31 -08:00
// If the output type isn't set already, get the internal output type
2020-07-26 23:39:33 -07:00
DatHeader . DatFormat = ( DatHeader . DatFormat = = 0 ? currentPath . GetDatFormat ( ) : DatHeader . DatFormat ) ;
2020-07-26 22:34:45 -07:00
Items . SetBucketedBy ( BucketedBy . CRC ) ; // Setting this because it can reduce issues later
2019-01-08 11:49:31 -08:00
// Now parse the correct type of DAT
try
{
2020-07-26 23:39:33 -07:00
Create ( currentPath . GetDatFormat ( ) , this ) ? . ParseFile ( currentPath , indexId , keep ) ;
2019-01-08 11:49:31 -08:00
}
catch ( Exception ex )
{
2020-06-10 22:37:19 -07:00
Globals . Logger . Error ( $"Error with file '{filename}': {ex}" ) ;
2019-01-08 11:49:31 -08:00
}
}
/// <summary>
/// Add a rom to the Dat after checking
/// </summary>
/// <param name="item">Item data to check against</param>
/// <returns>The key for the item</returns>
2020-07-15 09:41:59 -07:00
protected string ParseAddHelper ( DatItem item )
2019-01-08 11:49:31 -08:00
{
2020-06-10 22:37:19 -07:00
string key = string . Empty ;
2019-01-08 11:49:31 -08:00
// If there's no name in the rom, we log and skip it
if ( item . Name = = null )
{
2020-07-15 09:41:59 -07:00
Globals . Logger . Warning ( $"{DatHeader.FileName}: Rom with no name found! Skipping..." ) ;
2019-01-08 11:49:31 -08:00
return key ;
}
// If we have a Rom or a Disk, clean the hash data
2019-01-08 12:11:55 -08:00
if ( item . ItemType = = ItemType . Rom )
2019-01-08 11:49:31 -08:00
{
Rom itemRom = ( Rom ) item ;
// If we have the case where there is SHA-1 and nothing else, we don't fill in any other part of the data
if ( itemRom . Size = = - 1
2020-06-10 22:37:19 -07:00
& & string . IsNullOrWhiteSpace ( itemRom . CRC )
& & string . IsNullOrWhiteSpace ( itemRom . MD5 )
2020-07-15 09:41:59 -07:00
#if NET_FRAMEWORK
2020-06-10 22:37:19 -07:00
& & string . IsNullOrWhiteSpace ( itemRom . RIPEMD160 )
2020-07-15 09:41:59 -07:00
#endif
2020-06-10 22:37:19 -07:00
& & ! string . IsNullOrWhiteSpace ( itemRom . SHA1 )
& & string . IsNullOrWhiteSpace ( itemRom . SHA256 )
& & string . IsNullOrWhiteSpace ( itemRom . SHA384 )
& & string . IsNullOrWhiteSpace ( itemRom . SHA512 ) )
2019-01-08 11:49:31 -08:00
{
// No-op, just catch it so it doesn't go further
2020-07-15 09:41:59 -07:00
Globals . Logger . Verbose ( $"{DatHeader.FileName}: Entry with only SHA-1 found - '{itemRom.Name}'" ) ;
2019-01-08 11:49:31 -08:00
}
// If we have a rom and it's missing size AND the hashes match a 0-byte file, fill in the rest of the info
else if ( ( itemRom . Size = = 0 | | itemRom . Size = = - 1 )
2020-06-10 22:37:19 -07:00
& & ( ( itemRom . CRC = = Constants . CRCZero | | string . IsNullOrWhiteSpace ( itemRom . CRC ) )
2019-01-08 11:49:31 -08:00
| | itemRom . MD5 = = Constants . MD5Zero
2020-07-15 09:41:59 -07:00
#if NET_FRAMEWORK
2020-06-05 22:26:44 -07:00
| | itemRom . RIPEMD160 = = Constants . RIPEMD160Zero
2020-07-15 09:41:59 -07:00
#endif
2019-01-08 11:49:31 -08:00
| | itemRom . SHA1 = = Constants . SHA1Zero
| | itemRom . SHA256 = = Constants . SHA256Zero
| | itemRom . SHA384 = = Constants . SHA384Zero
| | itemRom . SHA512 = = Constants . SHA512Zero ) )
{
// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
itemRom . Size = Constants . SizeZero ;
itemRom . CRC = Constants . CRCZero ;
itemRom . MD5 = Constants . MD5Zero ;
2020-07-15 09:41:59 -07:00
#if NET_FRAMEWORK
2020-06-05 22:26:44 -07:00
itemRom . RIPEMD160 = null ;
//itemRom.RIPEMD160 = Constants.RIPEMD160Zero;
2020-07-15 09:41:59 -07:00
#endif
2019-01-08 11:49:31 -08:00
itemRom . SHA1 = Constants . SHA1Zero ;
itemRom . SHA256 = null ;
//itemRom.SHA256 = Constants.SHA256Zero;
2020-06-05 22:26:44 -07:00
itemRom . SHA384 = null ;
2019-01-08 11:49:31 -08:00
//itemRom.SHA384 = Constants.SHA384Zero;
2020-06-05 22:26:44 -07:00
itemRom . SHA512 = null ;
2019-01-08 11:49:31 -08:00
//itemRom.SHA512 = Constants.SHA512Zero;
}
// If the file has no size and it's not the above case, skip and log
else if ( itemRom . ItemStatus ! = ItemStatus . Nodump & & ( itemRom . Size = = 0 | | itemRom . Size = = - 1 ) )
{
2020-07-15 09:41:59 -07:00
Globals . Logger . Verbose ( $"{DatHeader.FileName}: Incomplete entry for '{itemRom.Name}' will be output as nodump" ) ;
2019-01-08 11:49:31 -08:00
itemRom . ItemStatus = ItemStatus . Nodump ;
}
// If the file has a size but aboslutely no hashes, skip and log
else if ( itemRom . ItemStatus ! = ItemStatus . Nodump
& & itemRom . Size > 0
2020-06-10 22:37:19 -07:00
& & string . IsNullOrWhiteSpace ( itemRom . CRC )
& & string . IsNullOrWhiteSpace ( itemRom . MD5 )
2020-07-15 09:41:59 -07:00
#if NET_FRAMEWORK
2020-06-10 22:37:19 -07:00
& & string . IsNullOrWhiteSpace ( itemRom . RIPEMD160 )
2020-07-15 09:41:59 -07:00
#endif
2020-06-10 22:37:19 -07:00
& & string . IsNullOrWhiteSpace ( itemRom . SHA1 )
& & string . IsNullOrWhiteSpace ( itemRom . SHA256 )
& & string . IsNullOrWhiteSpace ( itemRom . SHA384 )
& & string . IsNullOrWhiteSpace ( itemRom . SHA512 ) )
{
2020-07-15 09:41:59 -07:00
Globals . Logger . Verbose ( $"{DatHeader.FileName}: Incomplete entry for '{itemRom.Name}' will be output as nodump" ) ;
2019-01-08 11:49:31 -08:00
itemRom . ItemStatus = ItemStatus . Nodump ;
}
item = itemRom ;
}
2019-01-08 12:11:55 -08:00
else if ( item . ItemType = = ItemType . Disk )
2019-01-08 11:49:31 -08:00
{
Disk itemDisk = ( Disk ) item ;
// If the file has aboslutely no hashes, skip and log
if ( itemDisk . ItemStatus ! = ItemStatus . Nodump
2020-06-10 22:37:19 -07:00
& & string . IsNullOrWhiteSpace ( itemDisk . MD5 )
2020-07-15 09:41:59 -07:00
#if NET_FRAMEWORK
2020-06-10 22:37:19 -07:00
& & string . IsNullOrWhiteSpace ( itemDisk . RIPEMD160 )
2020-07-15 09:41:59 -07:00
#endif
2020-06-10 22:37:19 -07:00
& & string . IsNullOrWhiteSpace ( itemDisk . SHA1 )
& & string . IsNullOrWhiteSpace ( itemDisk . SHA256 )
& & string . IsNullOrWhiteSpace ( itemDisk . SHA384 )
& & string . IsNullOrWhiteSpace ( itemDisk . SHA512 ) )
{
Globals . Logger . Verbose ( $"Incomplete entry for '{itemDisk.Name}' will be output as nodump" ) ;
2019-01-08 11:49:31 -08:00
itemDisk . ItemStatus = ItemStatus . Nodump ;
}
item = itemDisk ;
}
// Get the key and add the file
2020-07-15 09:41:59 -07:00
key = item . GetKey ( BucketedBy . CRC ) ;
2020-07-26 22:34:45 -07:00
Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
return key ;
}
/// <summary>
/// Parse DatFile and return all found games and roms within
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
2020-07-15 09:41:59 -07:00
/// <param name="indexId">Index ID for the DAT</param>
2019-01-08 11:49:31 -08:00
/// <param name="keep">True if full pathnames are to be kept, false otherwise (default)</param>
2020-07-15 09:41:59 -07:00
protected abstract void ParseFile ( string filename , int indexId , bool keep ) ;
2019-01-08 11:49:31 -08:00
#endregion
#region Populate DAT from Directory
/// <summary>
/// Create a new Dat from a directory
/// </summary>
/// <param name="basePath">Base folder to be used in creating the DAT</param>
/// <param name="omitFromScan">Hash flag saying what hashes should not be calculated</param>
/// <param name="bare">True if the date should be omitted from the DAT, false otherwise</param>
/// <param name="archivesAsFiles">True if archives should be treated as files, false otherwise</param>
/// <param name="skipFileType">Type of files that should be skipped</param>
/// <param name="addBlanks">True if blank items should be created for empty folders, false otherwise</param>
/// <param name="addDate">True if dates should be archived for all files, false otherwise</param>
/// <param name="tempDir">Name of the directory to create a temp folder in (blank is current directory)</param>
/// <param name="outDir">Output directory to </param>
/// <param name="copyFiles">True if files should be copied to the temp directory before hashing, false otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <param name="chdsAsFiles">True if CHDs should be treated like regular files, false otherwise</param>
2019-01-08 17:55:27 -08:00
/// <param name="filter">Filter object to be passed to the DatItem level</param>
2020-07-15 09:41:59 -07:00
/// <param name="useTags">True if DatFile tags override splitting, false otherwise</param>
2019-01-08 11:49:31 -08:00
public bool PopulateFromDir ( string basePath , Hash omitFromScan , bool bare , bool archivesAsFiles , SkipFileType skipFileType ,
2020-07-15 09:41:59 -07:00
bool addBlanks , bool addDate , string tempDir , bool copyFiles , string headerToCheckAgainst , bool chdsAsFiles , Filter filter , bool useTags = false )
2019-01-08 11:49:31 -08:00
{
// If the description is defined but not the name, set the name from the description
2020-07-15 09:41:59 -07:00
if ( string . IsNullOrWhiteSpace ( DatHeader . Name ) & & ! string . IsNullOrWhiteSpace ( DatHeader . Description ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader . Name = DatHeader . Description ;
2019-01-08 11:49:31 -08:00
}
// If the name is defined but not the description, set the description from the name
2020-07-15 09:41:59 -07:00
else if ( ! string . IsNullOrWhiteSpace ( DatHeader . Name ) & & string . IsNullOrWhiteSpace ( DatHeader . Description ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader . Description = DatHeader . Name + ( bare ? string . Empty : $" ({DatHeader.Date})" ) ;
2019-01-08 11:49:31 -08:00
}
// If neither the name or description are defined, set them from the automatic values
2020-07-15 09:41:59 -07:00
else if ( string . IsNullOrWhiteSpace ( DatHeader . Name ) & & string . IsNullOrWhiteSpace ( DatHeader . Description ) )
2019-01-08 11:49:31 -08:00
{
2020-06-10 22:37:19 -07:00
string [ ] splitpath = basePath . TrimEnd ( Path . DirectorySeparatorChar ) . Split ( Path . DirectorySeparatorChar ) ;
2020-07-15 09:41:59 -07:00
DatHeader . Name = splitpath . Last ( ) ;
DatHeader . Description = DatHeader . Name + ( bare ? string . Empty : $" ({DatHeader.Date})" ) ;
2019-01-08 11:49:31 -08:00
}
// Clean the temp directory path
2020-07-15 09:41:59 -07:00
tempDir = DirectoryExtensions . Ensure ( tempDir , temp : true ) ;
2019-01-08 11:49:31 -08:00
// Process the input
if ( Directory . Exists ( basePath ) )
{
2020-06-10 22:37:19 -07:00
Globals . Logger . Verbose ( $"Folder found: {basePath}" ) ;
2019-01-08 11:49:31 -08:00
// Process the files in the main folder or any subfolder
List < string > files = Directory . EnumerateFiles ( basePath , "*" , SearchOption . AllDirectories ) . ToList ( ) ;
Parallel . ForEach ( files , Globals . ParallelOptions , item = >
{
2020-06-10 22:37:19 -07:00
CheckFileForHashes ( item , basePath , omitFromScan , archivesAsFiles , skipFileType ,
2019-01-08 11:49:31 -08:00
addBlanks , addDate , tempDir , copyFiles , headerToCheckAgainst , chdsAsFiles ) ;
} ) ;
// Now find all folders that are empty, if we are supposed to
2020-07-15 09:41:59 -07:00
if ( ! DatHeader . Romba & & addBlanks )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
List < string > empties = DirectoryExtensions . ListEmpty ( basePath ) ;
2019-01-08 11:49:31 -08:00
Parallel . ForEach ( empties , Globals . ParallelOptions , dir = >
{
// Get the full path for the directory
string fulldir = Path . GetFullPath ( dir ) ;
// Set the temporary variables
2020-06-10 22:37:19 -07:00
string gamename = string . Empty ;
string romname = string . Empty ;
2019-01-08 11:49:31 -08:00
// If we have a SuperDAT, we want anything that's not the base path as the game, and the file as the rom
2020-07-15 09:41:59 -07:00
if ( DatHeader . Type = = "SuperDAT" )
2019-01-08 11:49:31 -08:00
{
gamename = fulldir . Remove ( 0 , basePath . Length + 1 ) ;
romname = "_" ;
}
// Otherwise, we want just the top level folder as the game, and the file as everything else
else
{
gamename = fulldir . Remove ( 0 , basePath . Length + 1 ) . Split ( Path . DirectorySeparatorChar ) [ 0 ] ;
romname = Path . Combine ( fulldir . Remove ( 0 , basePath . Length + 1 + gamename . Length ) , "_" ) ;
}
// Sanitize the names
2020-06-10 22:37:19 -07:00
gamename = gamename . Trim ( Path . DirectorySeparatorChar ) ;
romname = romname . Trim ( Path . DirectorySeparatorChar ) ;
2019-01-08 11:49:31 -08:00
2020-06-10 22:37:19 -07:00
Globals . Logger . Verbose ( $"Adding blank empty folder: {gamename}" ) ;
2020-07-26 22:34:45 -07:00
Items [ "null" ] . Add ( new Rom ( romname , gamename ) ) ;
2019-01-08 11:49:31 -08:00
} ) ;
}
}
else if ( File . Exists ( basePath ) )
{
2020-06-10 22:37:19 -07:00
CheckFileForHashes ( basePath , Path . GetDirectoryName ( Path . GetDirectoryName ( basePath ) ) , omitFromScan , archivesAsFiles ,
2019-01-08 11:49:31 -08:00
skipFileType , addBlanks , addDate , tempDir , copyFiles , headerToCheckAgainst , chdsAsFiles ) ;
}
// Now that we're done, delete the temp folder (if it's not the default)
Globals . Logger . User ( "Cleaning temp folder" ) ;
if ( tempDir ! = Path . GetTempPath ( ) )
2020-07-15 09:41:59 -07:00
DirectoryExtensions . TryDelete ( tempDir ) ;
2019-01-08 11:49:31 -08:00
2019-01-08 17:55:27 -08:00
// If we have a valid filter, perform the filtering now
if ( filter ! = null & & filter ! = default ( Filter ) )
2020-07-15 09:41:59 -07:00
filter . FilterDatFile ( this , useTags ) ;
2019-01-08 17:55:27 -08:00
2019-01-08 11:49:31 -08:00
return true ;
}
/// <summary>
/// Check a given file for hashes, based on current settings
/// </summary>
/// <param name="item">Filename of the item to be checked</param>
/// <param name="basePath">Base folder to be used in creating the DAT</param>
/// <param name="omitFromScan">Hash flag saying what hashes should not be calculated</param>
/// <param name="archivesAsFiles">True if archives should be treated as files, false otherwise</param>
/// <param name="skipFileType">Type of files that should be skipped</param>
/// <param name="addBlanks">True if blank items should be created for empty folders, false otherwise</param>
/// <param name="addDate">True if dates should be archived for all files, false otherwise</param>
/// <param name="tempDir">Name of the directory to create a temp folder in (blank is current directory)</param>
/// <param name="copyFiles">True if files should be copied to the temp directory before hashing, false otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <param name="chdsAsFiles">True if CHDs should be treated like regular files, false otherwise</param>
2020-06-10 22:37:19 -07:00
private void CheckFileForHashes ( string item , string basePath , Hash omitFromScan , bool archivesAsFiles ,
2019-01-08 11:49:31 -08:00
SkipFileType skipFileType , bool addBlanks , bool addDate , string tempDir , bool copyFiles , string headerToCheckAgainst , bool chdsAsFiles )
{
// Special case for if we are in Romba mode (all names are supposed to be SHA-1 hashes)
2020-07-15 09:41:59 -07:00
if ( DatHeader . Romba )
2019-01-08 11:49:31 -08:00
{
GZipArchive gzarc = new GZipArchive ( item ) ;
BaseFile baseFile = gzarc . GetTorrentGZFileInfo ( ) ;
// If the rom is valid, write it out
if ( baseFile ! = null & & baseFile . Filename ! = null )
{
// Add the list if it doesn't exist already
Rom rom = new Rom ( baseFile ) ;
2020-07-26 22:34:45 -07:00
Items . Add ( rom . GetKey ( BucketedBy . CRC ) , rom ) ;
2020-06-10 22:37:19 -07:00
Globals . Logger . User ( $"File added: {Path.GetFileNameWithoutExtension(item)}{Environment.NewLine}" ) ;
2019-01-08 11:49:31 -08:00
}
else
{
2020-06-10 22:37:19 -07:00
Globals . Logger . User ( $"File not added: {Path.GetFileNameWithoutExtension(item)}{Environment.NewLine}" ) ;
2019-01-08 11:49:31 -08:00
return ;
}
return ;
}
// If we're copying files, copy it first and get the new filename
string newItem = item ;
string newBasePath = basePath ;
if ( copyFiles )
{
newBasePath = Path . Combine ( tempDir , Guid . NewGuid ( ) . ToString ( ) ) ;
newItem = Path . GetFullPath ( Path . Combine ( newBasePath , Path . GetFullPath ( item ) . Remove ( 0 , basePath . Length + 1 ) ) ) ;
2020-07-15 09:41:59 -07:00
DirectoryExtensions . TryCreateDirectory ( Path . GetDirectoryName ( newItem ) ) ;
2019-01-08 11:49:31 -08:00
File . Copy ( item , newItem , true ) ;
}
// Initialize possible archive variables
2020-07-15 09:41:59 -07:00
BaseArchive archive = BaseArchive . Create ( newItem ) ;
2019-01-08 11:49:31 -08:00
List < BaseFile > extracted = null ;
// If we have an archive and we're supposed to scan it
if ( archive ! = null & & ! archivesAsFiles )
extracted = archive . GetChildren ( omitFromScan : omitFromScan , date : addDate ) ;
// If the file should be skipped based on type, do so now
if ( ( extracted ! = null & & skipFileType = = SkipFileType . Archive )
| | ( extracted = = null & & skipFileType = = SkipFileType . File ) )
{
return ;
}
// If the extracted list is null, just scan the item itself
if ( extracted = = null )
{
2020-06-10 22:37:19 -07:00
ProcessFile ( newItem , string . Empty , newBasePath , omitFromScan , addDate , headerToCheckAgainst , chdsAsFiles ) ;
2019-01-08 11:49:31 -08:00
}
// Otherwise, add all of the found items
else
{
// First take care of the found items
Parallel . ForEach ( extracted , Globals . ParallelOptions , rom = >
{
2020-07-15 09:41:59 -07:00
DatItem datItem = DatItem . Create ( rom ) ;
2019-01-08 11:49:31 -08:00
ProcessFileHelper ( newItem ,
datItem ,
basePath ,
( Path . GetDirectoryName ( Path . GetFullPath ( item ) ) + Path . DirectorySeparatorChar ) . Remove ( 0 , basePath . Length ) + Path . GetFileNameWithoutExtension ( item ) ) ;
} ) ;
// Then, if we're looking for blanks, get all of the blank folders and add them
if ( addBlanks )
{
List < string > empties = new List < string > ( ) ;
// Now get all blank folders from the archive
if ( archive ! = null )
empties = archive . GetEmptyFolders ( ) ;
2019-09-20 10:30:30 -07:00
2019-01-08 11:49:31 -08:00
// Add add all of the found empties to the DAT
Parallel . ForEach ( empties , Globals . ParallelOptions , empty = >
{
2020-07-15 09:41:59 -07:00
Rom emptyRom = new Rom ( Path . Combine ( empty , "_" ) , newItem ) ;
2019-01-08 11:49:31 -08:00
ProcessFileHelper ( newItem ,
emptyRom ,
basePath ,
( Path . GetDirectoryName ( Path . GetFullPath ( item ) ) + Path . DirectorySeparatorChar ) . Remove ( 0 , basePath . Length ) + Path . GetFileNameWithoutExtension ( item ) ) ;
} ) ;
}
}
// Cue to delete the file if it's a copy
if ( copyFiles & & item ! = newItem )
2020-07-15 09:41:59 -07:00
DirectoryExtensions . TryDelete ( newBasePath ) ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
/// Process a single file as a file
/// </summary>
/// <param name="item">File to be added</param>
/// <param name="parent">Parent game to be used</param>
/// <param name="basePath">Path the represents the parent directory</param>
/// <param name="omitFromScan">Hash flag saying what hashes should not be calculated</param>
/// <param name="addDate">True if dates should be archived for all files, false otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <param name="chdsAsFiles">True if CHDs should be treated like regular files, false otherwise</param>
private void ProcessFile ( string item , string parent , string basePath , Hash omitFromScan ,
bool addDate , string headerToCheckAgainst , bool chdsAsFiles )
{
2020-06-10 22:37:19 -07:00
Globals . Logger . Verbose ( $"'{Path.GetFileName(item)}' treated like a file" ) ;
2020-07-15 09:41:59 -07:00
BaseFile baseFile = FileExtensions . GetInfo ( item , omitFromScan : omitFromScan , date : addDate , header : headerToCheckAgainst , chdsAsFiles : chdsAsFiles ) ;
ProcessFileHelper ( item , DatItem . Create ( baseFile ) , basePath , parent ) ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
/// Process a single file as a file (with found Rom data)
/// </summary>
/// <param name="item">File to be added</param>
/// <param name="item">Rom data to be used to write to file</param>
/// <param name="basepath">Path the represents the parent directory</param>
/// <param name="parent">Parent game to be used</param>
private void ProcessFileHelper ( string item , DatItem datItem , string basepath , string parent )
{
// If we somehow got something other than a Rom or Disk, cancel out
2019-01-08 12:11:55 -08:00
if ( datItem . ItemType ! = ItemType . Rom & & datItem . ItemType ! = ItemType . Disk )
2019-01-08 11:49:31 -08:00
return ;
try
{
2020-06-10 22:37:19 -07:00
// If the basepath doesn't end with a directory separator, add it
2019-01-08 11:49:31 -08:00
if ( ! basepath . EndsWith ( Path . DirectorySeparatorChar . ToString ( ) ) )
basepath + = Path . DirectorySeparatorChar . ToString ( ) ;
// Make sure we have the full item path
item = Path . GetFullPath ( item ) ;
// Process the item to sanitize names based on input
SetDatItemInfo ( datItem , item , parent , basepath ) ;
// Add the file information to the DAT
2020-07-15 09:41:59 -07:00
string key = datItem . GetKey ( BucketedBy . CRC ) ;
2020-07-26 22:34:45 -07:00
Items . Add ( key , datItem ) ;
2019-01-08 11:49:31 -08:00
2020-06-10 22:37:19 -07:00
Globals . Logger . User ( $"File added: {datItem.Name}{Environment.NewLine}" ) ;
2019-01-08 11:49:31 -08:00
}
catch ( IOException ex )
{
Globals . Logger . Error ( ex . ToString ( ) ) ;
return ;
}
}
/// <summary>
/// Set proper Game and Rom names from user inputs
/// </summary>
/// <param name="datItem">DatItem representing the input file</param>
/// <param name="item">Item name to use</param>
/// <param name="parent">Parent name to use</param>
/// <param name="basepath">Base path to use</param>
private void SetDatItemInfo ( DatItem datItem , string item , string parent , string basepath )
{
// Get the data to be added as game and item names
2020-07-15 09:41:59 -07:00
string gamename , romname ;
2019-01-08 11:49:31 -08:00
// If the parent is blank, then we have a non-archive file
2020-06-10 22:37:19 -07:00
if ( string . IsNullOrWhiteSpace ( parent ) )
2019-01-08 11:49:31 -08:00
{
// If we have a SuperDAT, we want anything that's not the base path as the game, and the file as the rom
2020-07-15 09:41:59 -07:00
if ( DatHeader . Type = = "SuperDAT" )
2019-01-08 11:49:31 -08:00
{
gamename = Path . GetDirectoryName ( item . Remove ( 0 , basepath . Length ) ) ;
romname = Path . GetFileName ( item ) ;
}
// Otherwise, we want just the top level folder as the game, and the file as everything else
else
{
gamename = item . Remove ( 0 , basepath . Length ) . Split ( Path . DirectorySeparatorChar ) [ 0 ] ;
romname = item . Remove ( 0 , ( Path . Combine ( basepath , gamename ) . Length ) ) ;
}
}
// Otherwise, we assume that we have an archive
else
{
// If we have a SuperDAT, we want the archive name as the game, and the file as everything else (?)
2020-07-15 09:41:59 -07:00
if ( DatHeader . Type = = "SuperDAT" )
2019-01-08 11:49:31 -08:00
{
gamename = parent ;
romname = datItem . Name ;
}
// Otherwise, we want the archive name as the game, and the file as everything else
else
{
gamename = parent ;
romname = datItem . Name ;
}
}
// Sanitize the names
2020-06-10 22:37:19 -07:00
gamename = gamename . Trim ( Path . DirectorySeparatorChar ) ;
romname = romname ? . Trim ( Path . DirectorySeparatorChar ) ? ? string . Empty ;
if ( ! string . IsNullOrWhiteSpace ( gamename ) & & string . IsNullOrWhiteSpace ( romname ) )
2019-01-08 11:49:31 -08:00
{
romname = gamename ;
gamename = "Default" ;
}
// Update rom information
datItem . Name = romname ;
datItem . MachineName = gamename ;
datItem . MachineDescription = gamename ;
// If we have a Disk, then the ".chd" extension needs to be removed
2019-01-08 12:11:55 -08:00
if ( datItem . ItemType = = ItemType . Disk )
2020-06-10 22:37:19 -07:00
datItem . Name = datItem . Name . Replace ( ".chd" , string . Empty ) ;
2019-01-08 11:49:31 -08:00
}
#endregion
#region Rebuilding and Verifying
/// <summary>
/// Process the DAT and find all matches in input files and folders assuming they're a depot
/// </summary>
/// <param name="inputs">List of input files/folders to check</param>
/// <param name="outDir">Output directory to use to build to</param>
/// <param name="date">True if the date from the DAT should be used if available, false otherwise</param>
/// <param name="delete">True if input files should be deleted, false otherwise</param>
/// <param name="inverse">True if the DAT should be used as a filter instead of a template, false otherwise</param>
/// <param name="outputFormat">Output format that files should be written to</param>
/// <param name="updateDat">True if the updated DAT should be output, false otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <returns>True if rebuilding was a success, false otherwise</returns>
2020-06-10 22:37:19 -07:00
public bool RebuildDepot (
List < string > inputs ,
string outDir ,
bool date ,
bool delete ,
bool inverse ,
OutputFormat outputFormat ,
bool updateDat ,
string headerToCheckAgainst )
2019-01-08 11:49:31 -08:00
{
#region Perform setup
// If the DAT is not populated and inverse is not set, inform the user and quit
2020-07-27 01:39:32 -07:00
if ( Items . TotalCount = = 0 & & ! inverse )
2019-01-08 11:49:31 -08:00
{
Globals . Logger . User ( "No entries were found to rebuild, exiting..." ) ;
return false ;
}
// Check that the output directory exists
2020-07-15 09:41:59 -07:00
outDir = DirectoryExtensions . Ensure ( outDir , create : true ) ;
2019-01-08 11:49:31 -08:00
// Now we want to get forcepack flag if it's not overridden
2020-07-15 09:41:59 -07:00
if ( outputFormat = = OutputFormat . Folder & & DatHeader . ForcePacking ! = ForcePacking . None )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
switch ( DatHeader . ForcePacking )
2019-01-08 11:49:31 -08:00
{
case ForcePacking . Zip :
outputFormat = OutputFormat . TorrentZip ;
break ;
case ForcePacking . Unzip :
outputFormat = OutputFormat . Folder ;
break ;
}
}
// Preload the Skipper list
2020-07-15 09:41:59 -07:00
Skipper . Init ( ) ;
2019-01-08 11:49:31 -08:00
#endregion
bool success = true ;
#region Rebuild from depots in order
2020-06-10 22:37:19 -07:00
string format = string . Empty ;
2019-01-08 11:49:31 -08:00
switch ( outputFormat )
{
case OutputFormat . Folder :
format = "directory" ;
break ;
case OutputFormat . TapeArchive :
format = "TAR" ;
break ;
case OutputFormat . Torrent7Zip :
format = "Torrent7Z" ;
break ;
case OutputFormat . TorrentGzip :
2020-06-10 22:37:19 -07:00
case OutputFormat . TorrentGzipRomba :
2019-01-08 11:49:31 -08:00
format = "TorrentGZ" ;
break ;
case OutputFormat . TorrentLRZip :
format = "TorrentLRZ" ;
break ;
case OutputFormat . TorrentRar :
format = "TorrentRAR" ;
break ;
case OutputFormat . TorrentXZ :
2020-07-15 09:41:59 -07:00
case OutputFormat . TorrentXZRomba :
2019-01-08 11:49:31 -08:00
format = "TorrentXZ" ;
break ;
case OutputFormat . TorrentZip :
format = "TorrentZip" ;
break ;
}
2020-06-10 22:37:19 -07:00
InternalStopwatch watch = new InternalStopwatch ( $"Rebuilding all files to {format}" ) ;
2019-01-08 11:49:31 -08:00
// Now loop through and get only directories from the input paths
List < string > directories = new List < string > ( ) ;
Parallel . ForEach ( inputs , Globals . ParallelOptions , input = >
{
// Add to the list if the input is a directory
if ( Directory . Exists ( input ) )
{
2020-06-10 22:37:19 -07:00
Globals . Logger . Verbose ( $"Adding depot: {input}" ) ;
2019-01-08 11:49:31 -08:00
lock ( directories )
{
directories . Add ( input ) ;
}
}
} ) ;
// If we don't have any directories, we want to exit
if ( directories . Count = = 0 )
return success ;
2020-07-15 10:47:13 -07:00
// Now that we have a list of depots, we want to bucket the input DAT by SHA-1
2020-07-26 22:34:45 -07:00
Items . BucketBy ( BucketedBy . SHA1 , DedupeType . None ) ;
2019-01-08 11:49:31 -08:00
// Then we want to loop through each of the hashes and see if we can rebuild
2020-07-26 22:34:45 -07:00
foreach ( string hash in Items . Keys )
2019-01-08 11:49:31 -08:00
{
// Pre-empt any issues that could arise from string length
if ( hash . Length ! = Constants . SHA1Length )
continue ;
2020-06-10 22:37:19 -07:00
Globals . Logger . User ( $"Checking hash '{hash}'" ) ;
2019-01-08 11:49:31 -08:00
// Get the extension path for the hash
2020-07-15 09:41:59 -07:00
string subpath = PathExtensions . GetRombaPath ( hash ) ;
2019-01-08 11:49:31 -08:00
// Find the first depot that includes the hash
string foundpath = null ;
foreach ( string directory in directories )
{
if ( File . Exists ( Path . Combine ( directory , subpath ) ) )
{
foundpath = Path . Combine ( directory , subpath ) ;
break ;
}
}
// If we didn't find a path, then we continue
if ( foundpath = = null )
continue ;
// If we have a path, we want to try to get the rom information
GZipArchive archive = new GZipArchive ( foundpath ) ;
BaseFile fileinfo = archive . GetTorrentGZFileInfo ( ) ;
// If the file information is null, then we continue
if ( fileinfo = = null )
continue ;
2017-10-09 20:38:15 -07:00
2019-01-02 23:17:49 -08:00
// Otherwise, we rebuild that file to all locations that we need to
2020-07-15 09:41:59 -07:00
bool usedInternally ;
2020-07-26 22:34:45 -07:00
if ( Items [ hash ] [ 0 ] . ItemType = = ItemType . Disk )
2020-07-15 09:41:59 -07:00
usedInternally = RebuildIndividualFile ( new Disk ( fileinfo ) , foundpath , outDir , date , inverse , outputFormat , updateDat , false /* isZip */ , headerToCheckAgainst ) ;
2019-01-02 23:17:49 -08:00
else
2020-07-15 09:41:59 -07:00
usedInternally = RebuildIndividualFile ( new Rom ( fileinfo ) , foundpath , outDir , date , inverse , outputFormat , updateDat , false /* isZip */ , headerToCheckAgainst ) ;
// If we are supposed to delete the depot file, do so
if ( delete & & usedInternally )
FileExtensions . TryDelete ( foundpath ) ;
2019-01-08 11:49:31 -08:00
}
watch . Stop ( ) ;
#endregion
// If we're updating the DAT, output to the rebuild directory
if ( updateDat )
{
2020-07-15 09:41:59 -07:00
DatHeader . FileName = $"fixDAT_{DatHeader.FileName}" ;
DatHeader . Name = $"fixDAT_{DatHeader.Name}" ;
DatHeader . Description = $"fixDAT_{DatHeader.Description}" ;
2020-07-26 22:34:45 -07:00
Items . ClearMarked ( ) ;
2019-01-08 11:49:31 -08:00
Write ( outDir ) ;
}
return success ;
}
/// <summary>
/// Process the DAT and find all matches in input files and folders
/// </summary>
/// <param name="inputs">List of input files/folders to check</param>
/// <param name="outDir">Output directory to use to build to</param>
/// <param name="quickScan">True to enable external scanning of archives, false otherwise</param>
/// <param name="date">True if the date from the DAT should be used if available, false otherwise</param>
/// <param name="delete">True if input files should be deleted, false otherwise</param>
/// <param name="inverse">True if the DAT should be used as a filter instead of a template, false otherwise</param>
/// <param name="outputFormat">Output format that files should be written to</param>
/// <param name="updateDat">True if the updated DAT should be output, false otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <param name="chdsAsFiles">True if CHDs should be treated like regular files, false otherwise</param>
/// <returns>True if rebuilding was a success, false otherwise</returns>
2020-06-10 22:37:19 -07:00
public bool RebuildGeneric (
List < string > inputs ,
string outDir ,
bool quickScan ,
bool date ,
bool delete ,
bool inverse ,
OutputFormat outputFormat ,
bool updateDat ,
string headerToCheckAgainst ,
bool chdsAsFiles )
2019-01-08 11:49:31 -08:00
{
#region Perform setup
// If the DAT is not populated and inverse is not set, inform the user and quit
2020-07-27 01:39:32 -07:00
if ( Items . TotalCount = = 0 & & ! inverse )
2019-01-08 11:49:31 -08:00
{
Globals . Logger . User ( "No entries were found to rebuild, exiting..." ) ;
return false ;
}
// Check that the output directory exists
if ( ! Directory . Exists ( outDir ) )
{
Directory . CreateDirectory ( outDir ) ;
outDir = Path . GetFullPath ( outDir ) ;
}
// Now we want to get forcepack flag if it's not overridden
2020-07-15 09:41:59 -07:00
if ( outputFormat = = OutputFormat . Folder & & DatHeader . ForcePacking ! = ForcePacking . None )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
switch ( DatHeader . ForcePacking )
2019-01-08 11:49:31 -08:00
{
case ForcePacking . Zip :
outputFormat = OutputFormat . TorrentZip ;
break ;
case ForcePacking . Unzip :
outputFormat = OutputFormat . Folder ;
break ;
}
}
// Preload the Skipper list
2020-07-15 09:41:59 -07:00
Skipper . Init ( ) ;
2019-01-08 11:49:31 -08:00
#endregion
bool success = true ;
#region Rebuild from sources in order
2020-06-10 22:37:19 -07:00
string format = string . Empty ;
2019-01-08 11:49:31 -08:00
switch ( outputFormat )
{
case OutputFormat . Folder :
format = "directory" ;
break ;
case OutputFormat . TapeArchive :
format = "TAR" ;
break ;
case OutputFormat . Torrent7Zip :
format = "Torrent7Z" ;
break ;
case OutputFormat . TorrentGzip :
2020-06-10 22:37:19 -07:00
case OutputFormat . TorrentGzipRomba :
2019-01-08 11:49:31 -08:00
format = "TorrentGZ" ;
break ;
case OutputFormat . TorrentLRZip :
format = "TorrentLRZ" ;
break ;
case OutputFormat . TorrentRar :
format = "TorrentRAR" ;
break ;
case OutputFormat . TorrentXZ :
2020-07-15 09:41:59 -07:00
case OutputFormat . TorrentXZRomba :
2019-01-08 11:49:31 -08:00
format = "TorrentXZ" ;
break ;
case OutputFormat . TorrentZip :
format = "TorrentZip" ;
break ;
}
2020-06-10 22:37:19 -07:00
InternalStopwatch watch = new InternalStopwatch ( $"Rebuilding all files to {format}" ) ;
2019-01-08 11:49:31 -08:00
// Now loop through all of the files in all of the inputs
foreach ( string input in inputs )
{
// If the input is a file
if ( File . Exists ( input ) )
{
2020-06-10 22:37:19 -07:00
Globals . Logger . User ( $"Checking file: {input}" ) ;
2020-07-15 09:41:59 -07:00
RebuildGenericHelper ( input , outDir , quickScan , date , delete , inverse , outputFormat , updateDat , headerToCheckAgainst , chdsAsFiles ) ;
2019-01-08 11:49:31 -08:00
}
// If the input is a directory
else if ( Directory . Exists ( input ) )
{
2020-06-10 22:37:19 -07:00
Globals . Logger . Verbose ( $"Checking directory: {input}" ) ;
2019-01-08 11:49:31 -08:00
foreach ( string file in Directory . EnumerateFiles ( input , "*" , SearchOption . AllDirectories ) )
{
2020-06-10 22:37:19 -07:00
Globals . Logger . User ( $"Checking file: {file}" ) ;
2020-07-15 09:41:59 -07:00
RebuildGenericHelper ( file , outDir , quickScan , date , delete , inverse , outputFormat , updateDat , headerToCheckAgainst , chdsAsFiles ) ;
2019-01-08 11:49:31 -08:00
}
}
}
watch . Stop ( ) ;
#endregion
// If we're updating the DAT, output to the rebuild directory
if ( updateDat )
{
2020-07-15 09:41:59 -07:00
DatHeader . FileName = $"fixDAT_{DatHeader.FileName}" ;
DatHeader . Name = $"fixDAT_{DatHeader.Name}" ;
DatHeader . Description = $"fixDAT_{DatHeader.Description}" ;
2020-07-26 22:34:45 -07:00
Items . ClearMarked ( ) ;
2019-01-08 11:49:31 -08:00
Write ( outDir ) ;
}
return success ;
}
/// <summary>
/// Attempt to add a file to the output if it matches
/// </summary>
/// <param name="file">Name of the file to process</param>
/// <param name="outDir">Output directory to use to build to</param>
/// <param name="quickScan">True to enable external scanning of archives, false otherwise</param>
/// <param name="date">True if the date from the DAT should be used if available, false otherwise</param>
/// <param name="delete">True if input files should be deleted, false otherwise</param>
/// <param name="inverse">True if the DAT should be used as a filter instead of a template, false otherwise</param>
/// <param name="outputFormat">Output format that files should be written to</param>
/// <param name="updateDat">True if the updated DAT should be output, false otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <param name="chdsAsFiles">True if CHDs should be treated like regular files, false otherwise</param>
2020-06-10 22:37:19 -07:00
private void RebuildGenericHelper (
string file ,
string outDir ,
bool quickScan ,
bool date ,
bool delete ,
bool inverse ,
OutputFormat outputFormat ,
bool updateDat ,
string headerToCheckAgainst ,
bool chdsAsFiles )
2019-01-08 11:49:31 -08:00
{
// If we somehow have a null filename, return
if ( file = = null )
return ;
// Set the deletion variables
2020-07-15 09:41:59 -07:00
bool usedExternally , usedInternally = false ;
// Scan the file externally
// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
BaseFile externalFileInfo = FileExtensions . GetInfo ( file , omitFromScan : ( quickScan ? Hash . SecureHashes : Hash . DeepHashes ) ,
header : headerToCheckAgainst , chdsAsFiles : chdsAsFiles ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
DatItem externalDatItem = null ;
if ( externalFileInfo . Type = = FileType . CHD )
externalDatItem = new Disk ( externalFileInfo ) ;
else if ( externalFileInfo . Type = = FileType . None )
externalDatItem = new Rom ( externalFileInfo ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
usedExternally = RebuildIndividualFile ( externalDatItem , file , outDir , date , inverse , outputFormat ,
updateDat , null /* isZip */ , headerToCheckAgainst ) ;
// Scan the file internally
// Create an empty list of BaseFile for archive entries
List < BaseFile > entries = null ;
// Get the TGZ status for later
GZipArchive tgz = new GZipArchive ( file ) ;
bool isTorrentGzip = tgz . IsTorrent ( ) ;
// Get the base archive first
BaseArchive archive = BaseArchive . Create ( file ) ;
// Now get all extracted items from the archive
if ( archive ! = null )
2019-01-08 11:49:31 -08:00
{
// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
2020-07-15 09:41:59 -07:00
entries = archive . GetChildren ( omitFromScan : ( quickScan ? Hash . SecureHashes : Hash . DeepHashes ) , date : date ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
// If the entries list is null, we encountered an error and should scan exteranlly
if ( entries = = null & & File . Exists ( file ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
BaseFile internalFileInfo = FileExtensions . GetInfo ( file , omitFromScan : ( quickScan ? Hash . SecureHashes : Hash . DeepHashes ) , chdsAsFiles : chdsAsFiles ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
DatItem internalDatItem = null ;
if ( internalFileInfo . Type = = FileType . CHD )
internalDatItem = new Disk ( internalFileInfo ) ;
else if ( internalFileInfo . Type = = FileType . None )
internalDatItem = new Rom ( internalFileInfo ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
usedExternally = RebuildIndividualFile ( internalDatItem , file , outDir , date , inverse , outputFormat , updateDat , null /* isZip */ , headerToCheckAgainst ) ;
}
// Otherwise, loop through the entries and try to match
else
{
foreach ( BaseFile entry in entries )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatItem internalDatItem = DatItem . Create ( entry ) ;
usedInternally | = RebuildIndividualFile ( internalDatItem , file , outDir , date , inverse , outputFormat , updateDat , ! isTorrentGzip /* isZip */ , headerToCheckAgainst ) ;
2019-01-08 11:49:31 -08:00
}
}
// If we are supposed to delete the file, do so
if ( delete & & ( usedExternally | | usedInternally ) )
2020-07-15 09:41:59 -07:00
FileExtensions . TryDelete ( file ) ;
2019-01-08 11:49:31 -08:00
}
/// <summary>
/// Find duplicates and rebuild individual files to output
/// </summary>
/// <param name="datItem">Information for the current file to rebuild from</param>
/// <param name="file">Name of the file to process</param>
/// <param name="outDir">Output directory to use to build to</param>
/// <param name="date">True if the date from the DAT should be used if available, false otherwise</param>
/// <param name="inverse">True if the DAT should be used as a filter instead of a template, false otherwise</param>
/// <param name="outputFormat">Output format that files should be written to</param>
/// <param name="updateDat">True if the updated DAT should be output, false otherwise</param>
/// <param name="isZip">True if the input file is an archive, false if the file is TGZ, null otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <returns>True if the file was able to be rebuilt, false otherwise</returns>
2020-06-10 22:37:19 -07:00
private bool RebuildIndividualFile (
DatItem datItem ,
string file ,
string outDir ,
bool date ,
bool inverse ,
OutputFormat outputFormat ,
bool updateDat ,
bool? isZip ,
string headerToCheckAgainst )
2019-01-08 11:49:31 -08:00
{
2020-04-03 13:19:21 -07:00
// Set the initial output value
bool rebuilt = false ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// If the DatItem is a Disk, force rebuilding to a folder except if TGZ or TXZ
if ( datItem . ItemType = = ItemType . Disk
& & ! ( outputFormat = = OutputFormat . TorrentGzip | | outputFormat = = OutputFormat . TorrentGzipRomba )
& & ! ( outputFormat = = OutputFormat . TorrentXZ | | outputFormat = = OutputFormat . TorrentXZRomba ) )
{
2019-01-08 11:49:31 -08:00
outputFormat = OutputFormat . Folder ;
2020-07-15 09:41:59 -07:00
}
2019-01-08 11:49:31 -08:00
2019-09-20 10:30:30 -07:00
// If we have a disk, change it into a Rom for later use
if ( datItem . ItemType = = ItemType . Disk )
datItem = ( ( Disk ) datItem ) . ConvertToRom ( ) ;
2019-01-08 11:49:31 -08:00
2019-09-20 10:30:30 -07:00
// Prepopluate a few key strings
string crc = ( ( Rom ) datItem ) . CRC ? ? string . Empty ;
string sha1 = ( ( Rom ) datItem ) . SHA1 ? ? string . Empty ;
2019-01-08 11:49:31 -08:00
// Find if the file has duplicates in the DAT
2020-07-26 22:34:45 -07:00
List < DatItem > dupes = Items . GetDuplicates ( datItem , remove : updateDat ) ;
2020-07-15 09:41:59 -07:00
bool hasDuplicates = dupes . Count > 0 ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// If either we have duplicates or we're filtering
if ( hasDuplicates ^ inverse )
2019-01-08 11:49:31 -08:00
{
// If we have a very specific TGZ->TGZ case, just copy it accordingly
GZipArchive tgz = new GZipArchive ( file ) ;
2020-07-15 09:41:59 -07:00
BaseFile tgzRom = tgz . GetTorrentGZFileInfo ( ) ;
if ( isZip = = false & & tgzRom ! = null & & ( outputFormat = = OutputFormat . TorrentGzip | | outputFormat = = OutputFormat . TorrentGzipRomba ) )
2019-01-08 11:49:31 -08:00
{
2020-06-10 22:37:19 -07:00
Globals . Logger . User ( $"Matches found for '{Path.GetFileName(datItem.Name)}', rebuilding accordingly..." ) ;
2019-01-08 11:49:31 -08:00
// Get the proper output path
2020-06-10 22:37:19 -07:00
if ( outputFormat = = OutputFormat . TorrentGzipRomba )
2020-07-15 09:41:59 -07:00
outDir = Path . Combine ( outDir , PathExtensions . GetRombaPath ( sha1 ) ) ;
2019-01-08 11:49:31 -08:00
else
outDir = Path . Combine ( outDir , sha1 + ".gz" ) ;
// Make sure the output folder is created
Directory . CreateDirectory ( Path . GetDirectoryName ( outDir ) ) ;
// Now copy the file over
try
{
File . Copy ( file , outDir ) ;
2020-04-03 13:19:21 -07:00
return true ;
2019-01-08 11:49:31 -08:00
}
catch
{
2020-04-03 13:19:21 -07:00
return false ;
2019-01-08 11:49:31 -08:00
}
}
2020-07-15 09:41:59 -07:00
// If we have a very specific TXZ->TXZ case, just copy it accordingly
XZArchive txz = new XZArchive ( file ) ;
BaseFile txzRom = txz . GetTorrentXZFileInfo ( ) ;
if ( isZip = = false & & txzRom ! = null & & ( outputFormat = = OutputFormat . TorrentXZ | | outputFormat = = OutputFormat . TorrentXZRomba ) )
2019-01-08 11:49:31 -08:00
{
2020-06-10 22:37:19 -07:00
Globals . Logger . User ( $"Matches found for '{Path.GetFileName(datItem.Name)}', rebuilding accordingly..." ) ;
2019-01-08 11:49:31 -08:00
// Get the proper output path
2020-06-10 22:37:19 -07:00
if ( outputFormat = = OutputFormat . TorrentGzipRomba )
2020-07-15 09:41:59 -07:00
outDir = Path . Combine ( outDir , PathExtensions . GetRombaPath ( sha1 ) ) ;
2019-01-08 11:49:31 -08:00
else
2020-07-15 09:41:59 -07:00
outDir = Path . Combine ( outDir , sha1 + ".xz" ) ;
2019-01-08 11:49:31 -08:00
// Make sure the output folder is created
Directory . CreateDirectory ( Path . GetDirectoryName ( outDir ) ) ;
// Now copy the file over
try
{
File . Copy ( file , outDir ) ;
2020-04-03 13:19:21 -07:00
return true ;
2019-01-08 11:49:31 -08:00
}
catch
{
2020-04-03 13:19:21 -07:00
return false ;
2019-01-08 11:49:31 -08:00
}
}
// Get a generic stream for the file
Stream fileStream = new MemoryStream ( ) ;
// If we have a zipfile, extract the stream to memory
if ( isZip ! = null )
{
2020-07-15 09:41:59 -07:00
BaseArchive archive = BaseArchive . Create ( file ) ;
2019-01-08 11:49:31 -08:00
if ( archive ! = null )
2020-07-15 09:41:59 -07:00
( fileStream , _ ) = archive . CopyToStream ( datItem . Name ) ;
2019-01-08 11:49:31 -08:00
}
// Otherwise, just open the filestream
else
{
2020-07-15 09:41:59 -07:00
fileStream = FileExtensions . TryOpenRead ( file ) ;
2019-01-08 11:49:31 -08:00
}
// If the stream is null, then continue
if ( fileStream = = null )
return false ;
2020-07-15 09:41:59 -07:00
// Seek to the beginning of the stream
if ( fileStream . CanSeek )
fileStream . Seek ( 0 , SeekOrigin . Begin ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// If we are inverse, create an output to rebuild to
if ( inverse )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
string machinename = null ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Get the item from the current file
Rom item = new Rom ( fileStream . GetInfo ( keepReadOpen : true ) ) ;
item . MachineName = Path . GetFileNameWithoutExtension ( item . Name ) ;
item . MachineDescription = Path . GetFileNameWithoutExtension ( item . Name ) ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// If we are coming from an archive, set the correct machine name
if ( machinename ! = null )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
item . MachineName = machinename ;
item . MachineDescription = machinename ;
}
2020-06-10 22:37:19 -07:00
2020-07-15 09:41:59 -07:00
dupes . Add ( item ) ;
}
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
Globals . Logger . User ( $"{(inverse ? " No matches " : " Matches ")} found for '{Path.GetFileName(datItem.Name)}', rebuilding accordingly..." ) ;
rebuilt = true ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
// Now loop through the list and rebuild accordingly
foreach ( DatItem item in dupes )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
// Get the output archive, if possible
Folder outputArchive = Folder . Create ( outputFormat ) ;
// Now rebuild to the output file
outputArchive . Write ( fileStream , outDir , ( Rom ) item , date : date , romba : outputFormat = = OutputFormat . TorrentGzipRomba | | outputFormat = = OutputFormat . TorrentXZRomba ) ;
2019-01-08 11:49:31 -08:00
}
// Close the input stream
fileStream ? . Dispose ( ) ;
}
// Now we want to take care of headers, if applicable
if ( headerToCheckAgainst ! = null )
{
// Get a generic stream for the file
Stream fileStream = new MemoryStream ( ) ;
// If we have a zipfile, extract the stream to memory
if ( isZip ! = null )
{
2020-07-15 09:41:59 -07:00
BaseArchive archive = BaseArchive . Create ( file ) ;
2019-01-08 11:49:31 -08:00
if ( archive ! = null )
2020-07-15 09:41:59 -07:00
( fileStream , _ ) = archive . CopyToStream ( datItem . Name ) ;
2019-01-08 11:49:31 -08:00
}
// Otherwise, just open the filestream
else
{
2020-07-15 09:41:59 -07:00
fileStream = FileExtensions . TryOpenRead ( file ) ;
2019-01-08 11:49:31 -08:00
}
// If the stream is null, then continue
if ( fileStream = = null )
return false ;
// Check to see if we have a matching header first
SkipperRule rule = Skipper . GetMatchingRule ( fileStream , Path . GetFileNameWithoutExtension ( headerToCheckAgainst ) ) ;
// If there's a match, create the new file to write
if ( rule . Tests ! = null & & rule . Tests . Count ! = 0 )
{
// If the file could be transformed correctly
MemoryStream transformStream = new MemoryStream ( ) ;
if ( rule . TransformStream ( fileStream , transformStream , keepReadOpen : true , keepWriteOpen : true ) )
{
// Get the file informations that we will be using
2020-07-15 09:41:59 -07:00
Rom headerless = new Rom ( transformStream . GetInfo ( keepReadOpen : true ) ) ;
2019-01-08 11:49:31 -08:00
// Find if the file has duplicates in the DAT
2020-07-26 22:34:45 -07:00
dupes = Items . GetDuplicates ( headerless , remove : updateDat ) ;
2020-07-15 09:41:59 -07:00
hasDuplicates = dupes . Count > 0 ;
2019-01-08 11:49:31 -08:00
// If it has duplicates and we're not filtering, rebuild it
if ( hasDuplicates & & ! inverse )
{
2020-06-10 22:37:19 -07:00
Globals . Logger . User ( $"Headerless matches found for '{Path.GetFileName(datItem.Name)}', rebuilding accordingly..." ) ;
2019-01-08 11:49:31 -08:00
rebuilt = true ;
// Now loop through the list and rebuild accordingly
foreach ( DatItem item in dupes )
{
// Create a headered item to use as well
datItem . CopyMachineInformation ( item ) ;
2020-06-10 22:37:19 -07:00
datItem . Name + = $"_{crc}" ;
2019-01-08 11:49:31 -08:00
// If either copy succeeds, then we want to set rebuilt to true
bool eitherSuccess = false ;
// Get the output archive, if possible
2020-07-15 09:41:59 -07:00
Folder outputArchive = Folder . Create ( outputFormat ) ;
2019-01-08 11:49:31 -08:00
// Now rebuild to the output file
2020-07-15 09:41:59 -07:00
eitherSuccess | = outputArchive . Write ( transformStream , outDir , ( Rom ) item , date : date , romba : outputFormat = = OutputFormat . TorrentGzipRomba | | outputFormat = = OutputFormat . TorrentXZRomba ) ;
eitherSuccess | = outputArchive . Write ( fileStream , outDir , ( Rom ) datItem , date : date , romba : outputFormat = = OutputFormat . TorrentGzipRomba | | outputFormat = = OutputFormat . TorrentXZRomba ) ;
2019-01-08 11:49:31 -08:00
// Now add the success of either rebuild
rebuilt & = eitherSuccess ;
}
}
}
// Dispose of the stream
transformStream ? . Dispose ( ) ;
}
// Dispose of the stream
fileStream ? . Dispose ( ) ;
}
return rebuilt ;
}
/// <summary>
/// Process the DAT and verify from the depots
/// </summary>
/// <param name="inputs">List of input directories to compare against</param>
/// <returns>True if verification was a success, false otherwise</returns>
2020-07-15 09:41:59 -07:00
public bool VerifyDepot ( List < string > inputs )
2019-01-08 11:49:31 -08:00
{
bool success = true ;
InternalStopwatch watch = new InternalStopwatch ( "Verifying all from supplied depots" ) ;
// Now loop through and get only directories from the input paths
List < string > directories = new List < string > ( ) ;
foreach ( string input in inputs )
{
// Add to the list if the input is a directory
if ( Directory . Exists ( input ) )
{
2020-06-10 22:37:19 -07:00
Globals . Logger . Verbose ( $"Adding depot: {input}" ) ;
2019-01-08 11:49:31 -08:00
directories . Add ( input ) ;
}
}
// If we don't have any directories, we want to exit
if ( directories . Count = = 0 )
return success ;
2020-07-15 10:47:13 -07:00
// Now that we have a list of depots, we want to bucket the input DAT by SHA-1
2020-07-26 22:34:45 -07:00
Items . BucketBy ( BucketedBy . SHA1 , DedupeType . None ) ;
2019-01-08 11:49:31 -08:00
// Then we want to loop through each of the hashes and see if we can rebuild
2020-07-26 22:34:45 -07:00
foreach ( string hash in Items . Keys )
2019-01-08 11:49:31 -08:00
{
// Pre-empt any issues that could arise from string length
if ( hash . Length ! = Constants . SHA1Length )
continue ;
2020-06-10 22:37:19 -07:00
Globals . Logger . User ( $"Checking hash '{hash}'" ) ;
2019-01-08 11:49:31 -08:00
// Get the extension path for the hash
2020-07-15 09:41:59 -07:00
string subpath = PathExtensions . GetRombaPath ( hash ) ;
2019-01-08 11:49:31 -08:00
// Find the first depot that includes the hash
string foundpath = null ;
foreach ( string directory in directories )
{
if ( File . Exists ( Path . Combine ( directory , subpath ) ) )
{
foundpath = Path . Combine ( directory , subpath ) ;
break ;
}
}
// If we didn't find a path, then we continue
if ( foundpath = = null )
continue ;
// If we have a path, we want to try to get the rom information
GZipArchive tgz = new GZipArchive ( foundpath ) ;
BaseFile fileinfo = tgz . GetTorrentGZFileInfo ( ) ;
// If the file information is null, then we continue
if ( fileinfo = = null )
continue ;
// Now we want to remove all duplicates from the DAT
2020-07-26 22:34:45 -07:00
Items . GetDuplicates ( new Rom ( fileinfo ) , remove : true )
. AddRange ( Items . GetDuplicates ( new Disk ( fileinfo ) , remove : true ) ) ;
2019-01-08 11:49:31 -08:00
}
watch . Stop ( ) ;
// If there are any entries in the DAT, output to the rebuild directory
2020-07-15 09:41:59 -07:00
DatHeader . FileName = $"fixDAT_{DatHeader.FileName}" ;
DatHeader . Name = $"fixDAT_{DatHeader.Name}" ;
DatHeader . Description = $"fixDAT_{DatHeader.Description}" ;
2020-07-26 22:34:45 -07:00
Items . ClearMarked ( ) ;
2019-01-08 11:49:31 -08:00
Write ( ) ;
return success ;
}
/// <summary>
/// Process the DAT and verify the output directory
/// </summary>
/// <param name="inputs">List of input directories to compare against</param>
/// <param name="hashOnly">True if only hashes should be checked, false for full file information</param>
/// <param name="quickScan">True to enable external scanning of archives, false otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <param name="chdsAsFiles">True if CHDs should be treated like regular files, false otherwise</param>
2019-01-08 17:55:27 -08:00
/// <param name="filter">Filter object to be passed to the DatItem level</param>
2019-01-08 11:49:31 -08:00
/// <returns>True if verification was a success, false otherwise</returns>
2019-01-08 17:55:27 -08:00
public bool VerifyGeneric ( List < string > inputs , bool hashOnly , bool quickScan , string headerToCheckAgainst , bool chdsAsFiles , Filter filter )
2019-01-08 11:49:31 -08:00
{
// TODO: We want the cross section of what's the folder and what's in the DAT. Right now, it just has what's in the DAT that's not in the folder
bool success = true ;
// Then, loop through and check each of the inputs
Globals . Logger . User ( "Processing files:\n" ) ;
foreach ( string input in inputs )
{
// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually
PopulateFromDir ( input , ( quickScan ? Hash . SecureHashes : Hash . DeepHashes ) /* omitFromScan */ , true /* bare */ , false /* archivesAsFiles */ ,
2020-06-10 22:37:19 -07:00
SkipFileType . None , false /* addBlanks */ , false /* addDate */ , string . Empty /* tempDir */ , false /* copyFiles */ , headerToCheckAgainst , chdsAsFiles , filter ) ;
2019-01-08 11:49:31 -08:00
}
// Setup the fixdat
2020-07-15 09:41:59 -07:00
DatFile matched = Create ( DatHeader ) ;
2020-07-26 22:34:45 -07:00
matched . Items = new ItemDictionary ( ) ;
2020-07-15 09:41:59 -07:00
matched . DatHeader . FileName = $"fixDat_{matched.DatHeader.FileName}" ;
matched . DatHeader . Name = $"fixDat_{matched.DatHeader.Name}" ;
matched . DatHeader . Description = $"fixDat_{matched.DatHeader.Description}" ;
matched . DatHeader . DatFormat = DatFormat . Logiqx ;
2019-01-08 11:49:31 -08:00
// If we are checking hashes only, essentially diff the inputs
if ( hashOnly )
{
2020-07-15 10:47:13 -07:00
// First we need to bucket and dedupe by hash to get duplicates
2020-07-26 22:34:45 -07:00
Items . BucketBy ( BucketedBy . CRC , DedupeType . Full ) ;
2019-01-08 11:49:31 -08:00
// Then follow the same tactics as before
2020-07-26 22:34:45 -07:00
foreach ( string key in Items . Keys )
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
List < DatItem > roms = Items [ key ] ;
2019-01-08 11:49:31 -08:00
foreach ( DatItem rom in roms )
{
2020-07-15 09:41:59 -07:00
if ( rom . IndexId = = 99 )
2019-01-08 11:49:31 -08:00
{
2019-01-08 12:11:55 -08:00
if ( rom . ItemType = = ItemType . Disk | | rom . ItemType = = ItemType . Rom )
2020-07-26 22:34:45 -07:00
matched . Items . Add ( ( ( Disk ) rom ) . SHA1 , rom ) ;
2019-01-08 11:49:31 -08:00
}
}
}
}
// If we are checking full names, get only files found in directory
else
{
2020-07-26 22:34:45 -07:00
foreach ( string key in Items . Keys )
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
List < DatItem > roms = Items [ key ] ;
2019-01-08 11:49:31 -08:00
List < DatItem > newroms = DatItem . Merge ( roms ) ;
foreach ( Rom rom in newroms )
{
2020-07-15 09:41:59 -07:00
if ( rom . IndexId = = 99 )
2020-07-26 22:34:45 -07:00
matched . Items . Add ( $"{rom.Size}-{rom.CRC}" , rom ) ;
2019-01-08 11:49:31 -08:00
}
}
}
// Now output the fixdat to the main folder
2020-07-26 22:34:45 -07:00
Items . ClearMarked ( ) ;
2019-01-08 11:49:31 -08:00
success & = matched . Write ( stats : true ) ;
2020-07-15 09:41:59 -07:00
return success ;
}
2019-01-08 11:49:31 -08:00
#endregion
2020-07-15 09:41:59 -07:00
// TODO: Implement Level split
2019-01-08 11:49:31 -08:00
#region Splitting
/// <summary>
/// Split a set of input DATs based on the given information
/// </summary>
/// <param name="inputs">List of inputs to be used</param>
/// <param name="outDir">Output directory for the split files</param>
/// <param name="inplace">True if files should be written to the source folders, false otherwise</param>
/// <param name="splittingMode">Type of split to perform, if any</param>
/// <param name="exta">First extension to split on (Extension Split only)</param>
/// <param name="extb">Second extension to split on (Extension Split only)</param>
/// <param name="shortname">True if short filenames should be used, false otherwise (Level Split only)</param>
/// <param name="basedat">True if original filenames should be used as the base for output filename, false otherwise (Level Split only)</param>
/// <param name="radix">Long value representing the split point (Size Split only)</param>
public void DetermineSplitType ( List < string > inputs , string outDir , bool inplace , SplittingMode splittingMode ,
List < string > exta , List < string > extb , bool shortname , bool basedat , long radix )
{
// If we somehow have the "none" split type, return
if ( splittingMode = = SplittingMode . None )
return ;
// Get only files from the inputs
2020-07-26 23:39:33 -07:00
List < ParentablePath > files = DirectoryExtensions . GetFilesOnly ( inputs , appendparent : true ) ;
2019-01-08 11:49:31 -08:00
// Loop over the input files
2020-07-26 23:39:33 -07:00
foreach ( ParentablePath file in files )
2019-01-08 11:49:31 -08:00
{
// Create and fill the new DAT
2020-07-15 09:41:59 -07:00
Parse ( file ) ;
2019-01-08 11:49:31 -08:00
// Get the output directory
2020-07-26 23:46:59 -07:00
outDir = file . GetOutputPath ( outDir , inplace ) ;
2019-01-08 11:49:31 -08:00
// Split and write the DAT
2020-07-15 09:41:59 -07:00
if ( splittingMode . HasFlag ( SplittingMode . Extension ) )
2019-01-08 11:49:31 -08:00
SplitByExtension ( outDir , exta , extb ) ;
2020-06-10 22:37:19 -07:00
2020-07-15 09:41:59 -07:00
if ( splittingMode . HasFlag ( SplittingMode . Hash ) )
2019-01-08 11:49:31 -08:00
SplitByHash ( outDir ) ;
2020-06-10 22:37:19 -07:00
2020-07-15 09:41:59 -07:00
if ( splittingMode . HasFlag ( SplittingMode . Level ) )
2019-01-08 11:49:31 -08:00
SplitByLevel ( outDir , shortname , basedat ) ;
2020-06-10 22:37:19 -07:00
2020-07-15 09:41:59 -07:00
if ( splittingMode . HasFlag ( SplittingMode . Size ) )
2019-01-08 11:49:31 -08:00
SplitBySize ( outDir , radix ) ;
2020-06-10 22:37:19 -07:00
2020-07-15 09:41:59 -07:00
if ( splittingMode . HasFlag ( SplittingMode . Type ) )
2019-01-08 11:49:31 -08:00
SplitByType ( outDir ) ;
// Now re-empty the DAT to make room for the next one
2020-07-15 09:41:59 -07:00
DatFormat tempFormat = DatHeader . DatFormat ;
DatHeader = new DatHeader ( ) ;
2020-07-26 22:34:45 -07:00
Items = new ItemDictionary ( ) ;
2020-07-15 09:41:59 -07:00
DatHeader . DatFormat = tempFormat ;
2019-01-08 11:49:31 -08:00
}
}
/// <summary>
/// Split a DAT by input extensions
/// </summary>
/// <param name="outDir">Name of the directory to write the DATs out to</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>True if split succeeded, false otherwise</returns>
2020-07-15 09:41:59 -07:00
private bool SplitByExtension ( string outDir , List < string > extA , List < string > extB )
2019-01-08 11:49:31 -08:00
{
2020-06-10 22:37:19 -07:00
// If roms is empty, return false
2020-07-27 01:39:32 -07:00
if ( Items . TotalCount = = 0 )
2020-06-10 22:37:19 -07:00
return false ;
// Make sure all of the extensions don't have a dot at the beginning
2020-07-15 09:41:59 -07:00
var newExtA = extA . Select ( s = > s . TrimStart ( '.' ) . ToLowerInvariant ( ) ) ;
2019-01-08 11:49:31 -08:00
string newExtAString = string . Join ( "," , newExtA ) ;
2020-07-15 09:41:59 -07:00
var newExtB = extB . Select ( s = > s . TrimStart ( '.' ) . ToLowerInvariant ( ) ) ;
2019-01-08 11:49:31 -08:00
string newExtBString = string . Join ( "," , newExtB ) ;
// Set all of the appropriate outputs for each of the subsets
2020-07-15 09:41:59 -07:00
DatFile datdataA = Create ( DatHeader . CloneStandard ( ) ) ;
datdataA . DatHeader . FileName + = $" ({newExtAString})" ;
datdataA . DatHeader . Name + = $" ({newExtAString})" ;
datdataA . DatHeader . Description + = $" ({newExtAString})" ;
DatFile datdataB = Create ( DatHeader . CloneStandard ( ) ) ;
datdataB . DatHeader . FileName + = $" ({newExtBString})" ;
datdataB . DatHeader . Name + = $" ({newExtBString})" ;
datdataB . DatHeader . Description + = $" ({newExtBString})" ;
2019-01-08 11:49:31 -08:00
// Now separate the roms accordingly
2020-07-26 22:34:45 -07:00
Parallel . ForEach ( Items . Keys , Globals . ParallelOptions , key = >
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
List < DatItem > items = Items [ key ] ;
2019-01-08 11:49:31 -08:00
foreach ( DatItem item in items )
{
2020-07-15 09:41:59 -07:00
if ( newExtA . Contains ( PathExtensions . GetNormalizedExtension ( item . Name ) ) )
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
datdataA . Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
else if ( newExtB . Contains ( PathExtensions . GetNormalizedExtension ( item . Name ) ) )
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
datdataB . Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
}
else
{
2020-07-26 22:34:45 -07:00
datdataA . Items . Add ( key , item ) ;
datdataB . Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
}
}
} ) ;
// Then write out both files
bool success = datdataA . Write ( outDir ) ;
success & = datdataB . Write ( outDir ) ;
return success ;
}
/// <summary>
/// Split a DAT by best available hashes
/// </summary>
/// <param name="outDir">Name of the directory to write the DATs out to</param>
/// <returns>True if split succeeded, false otherwise</returns>
2020-07-15 09:41:59 -07:00
private bool SplitByHash ( string outDir )
2019-01-08 11:49:31 -08:00
{
// Create each of the respective output DATs
Globals . Logger . User ( "Creating and populating new DATs" ) ;
2020-07-15 09:41:59 -07:00
DatFile nodump = Create ( DatHeader . CloneStandard ( ) ) ;
nodump . DatHeader . FileName + = " (Nodump)" ;
nodump . DatHeader . Name + = " (Nodump)" ;
nodump . DatHeader . Description + = " (Nodump)" ;
DatFile sha512 = Create ( DatHeader . CloneStandard ( ) ) ;
sha512 . DatHeader . FileName + = " (SHA-512)" ;
sha512 . DatHeader . Name + = " (SHA-512)" ;
sha512 . DatHeader . Description + = " (SHA-512)" ;
DatFile sha384 = Create ( DatHeader . CloneStandard ( ) ) ;
sha384 . DatHeader . FileName + = " (SHA-384)" ;
sha384 . DatHeader . Name + = " (SHA-384)" ;
sha384 . DatHeader . Description + = " (SHA-384)" ;
DatFile sha256 = Create ( DatHeader . CloneStandard ( ) ) ;
sha256 . DatHeader . FileName + = " (SHA-256)" ;
sha256 . DatHeader . Name + = " (SHA-256)" ;
sha256 . DatHeader . Description + = " (SHA-256)" ;
DatFile sha1 = Create ( DatHeader . CloneStandard ( ) ) ;
sha1 . DatHeader . FileName + = " (SHA-1)" ;
sha1 . DatHeader . Name + = " (SHA-1)" ;
sha1 . DatHeader . Description + = " (SHA-1)" ;
#if NET_FRAMEWORK
DatFile ripemd160 = Create ( DatHeader . CloneStandard ( ) ) ;
ripemd160 . DatHeader . FileName + = " (RIPEMD160)" ;
ripemd160 . DatHeader . Name + = " (RIPEMD160)" ;
ripemd160 . DatHeader . Description + = " (RIPEMD160)" ;
#endif
DatFile md5 = Create ( DatHeader . CloneStandard ( ) ) ;
md5 . DatHeader . FileName + = " (MD5)" ;
md5 . DatHeader . Name + = " (MD5)" ;
md5 . DatHeader . Description + = " (MD5)" ;
DatFile crc = Create ( DatHeader . CloneStandard ( ) ) ;
crc . DatHeader . FileName + = " (CRC)" ;
crc . DatHeader . Name + = " (CRC)" ;
crc . DatHeader . Description + = " (CRC)" ;
DatFile other = Create ( DatHeader . CloneStandard ( ) ) ;
other . DatHeader . FileName + = " (Other)" ;
other . DatHeader . Name + = " (Other)" ;
other . DatHeader . Description + = " (Other)" ;
2019-01-08 11:49:31 -08:00
// Now populate each of the DAT objects in turn
2020-07-26 22:34:45 -07:00
Parallel . ForEach ( Items . Keys , Globals . ParallelOptions , key = >
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
List < DatItem > items = Items [ key ] ;
2019-01-08 11:49:31 -08:00
foreach ( DatItem item in items )
{
// If the file is not a Rom or Disk, continue
2019-01-08 12:11:55 -08:00
if ( item . ItemType ! = ItemType . Disk & & item . ItemType ! = ItemType . Rom )
2019-01-08 11:49:31 -08:00
return ;
// If the file is a nodump
2019-01-08 12:11:55 -08:00
if ( ( item . ItemType = = ItemType . Rom & & ( ( Rom ) item ) . ItemStatus = = ItemStatus . Nodump )
| | ( item . ItemType = = ItemType . Disk & & ( ( Disk ) item ) . ItemStatus = = ItemStatus . Nodump ) )
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
nodump . Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
}
// If the file has a SHA-512
2020-06-10 22:37:19 -07:00
else if ( ( item . ItemType = = ItemType . Rom & & ! string . IsNullOrWhiteSpace ( ( ( Rom ) item ) . SHA512 ) )
| | ( item . ItemType = = ItemType . Disk & & ! string . IsNullOrWhiteSpace ( ( ( Disk ) item ) . SHA512 ) ) )
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
sha512 . Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
}
// If the file has a SHA-384
2020-06-10 22:37:19 -07:00
else if ( ( item . ItemType = = ItemType . Rom & & ! string . IsNullOrWhiteSpace ( ( ( Rom ) item ) . SHA384 ) )
| | ( item . ItemType = = ItemType . Disk & & ! string . IsNullOrWhiteSpace ( ( ( Disk ) item ) . SHA384 ) ) )
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
sha384 . Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
}
// If the file has a SHA-256
2020-06-10 22:37:19 -07:00
else if ( ( item . ItemType = = ItemType . Rom & & ! string . IsNullOrWhiteSpace ( ( ( Rom ) item ) . SHA256 ) )
| | ( item . ItemType = = ItemType . Disk & & ! string . IsNullOrWhiteSpace ( ( ( Disk ) item ) . SHA256 ) ) )
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
sha256 . Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
}
// If the file has a SHA-1
2020-06-10 22:37:19 -07:00
else if ( ( item . ItemType = = ItemType . Rom & & ! string . IsNullOrWhiteSpace ( ( ( Rom ) item ) . SHA1 ) )
| | ( item . ItemType = = ItemType . Disk & & ! string . IsNullOrWhiteSpace ( ( ( Disk ) item ) . SHA1 ) ) )
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
sha1 . Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
#if NET_FRAMEWORK
// If the file has a RIPEMD160
2020-06-10 22:37:19 -07:00
else if ( ( item . ItemType = = ItemType . Rom & & ! string . IsNullOrWhiteSpace ( ( ( Rom ) item ) . RIPEMD160 ) )
| | ( item . ItemType = = ItemType . Disk & & ! string . IsNullOrWhiteSpace ( ( ( Disk ) item ) . RIPEMD160 ) ) )
2020-06-05 22:26:44 -07:00
{
2020-07-26 22:34:45 -07:00
ripemd160 . Items . Add ( key , item ) ;
2020-06-05 22:26:44 -07:00
}
2020-07-15 09:41:59 -07:00
#endif
2020-06-05 22:26:44 -07:00
// If the file has an MD5
2020-06-10 22:37:19 -07:00
else if ( ( item . ItemType = = ItemType . Rom & & ! string . IsNullOrWhiteSpace ( ( ( Rom ) item ) . MD5 ) )
| | ( item . ItemType = = ItemType . Disk & & ! string . IsNullOrWhiteSpace ( ( ( Disk ) item ) . MD5 ) ) )
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
md5 . Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
}
2020-06-05 22:26:44 -07:00
// If the file has a CRC
2020-06-10 22:37:19 -07:00
else if ( ( item . ItemType = = ItemType . Rom & & ! string . IsNullOrWhiteSpace ( ( ( Rom ) item ) . CRC ) ) )
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
crc . Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
}
else
{
2020-07-26 22:34:45 -07:00
other . Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
}
}
} ) ;
// Now, output all of the files to the output directory
Globals . Logger . User ( "DAT information created, outputting new files" ) ;
bool success = true ;
success & = nodump . Write ( outDir ) ;
success & = sha512 . Write ( outDir ) ;
success & = sha384 . Write ( outDir ) ;
success & = sha256 . Write ( outDir ) ;
success & = sha1 . Write ( outDir ) ;
2020-07-15 09:41:59 -07:00
#if NET_FRAMEWORK
2020-06-05 22:26:44 -07:00
success & = ripemd160 . Write ( outDir ) ;
2020-07-15 09:41:59 -07:00
#endif
2019-01-08 11:49:31 -08:00
success & = md5 . Write ( outDir ) ;
success & = crc . Write ( outDir ) ;
return success ;
}
/// <summary>
/// Split a SuperDAT by lowest available directory level
/// </summary>
/// <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-07-15 09:41:59 -07:00
private bool SplitByLevel ( string outDir , bool shortname , bool basedat )
2019-01-08 11:49:31 -08:00
{
2020-07-15 10:47:13 -07:00
// First, bucket by games so that we can do the right thing
2020-07-26 22:34:45 -07:00
Items . BucketBy ( BucketedBy . Game , DedupeType . None , lower : false , norename : true ) ;
2019-01-08 11:49:31 -08:00
// Create a temporary DAT to add things to
2020-07-15 09:41:59 -07:00
DatFile tempDat = Create ( DatHeader ) ;
tempDat . DatHeader . Name = null ;
2019-01-08 11:49:31 -08:00
// Sort the input keys
2020-07-26 22:34:45 -07:00
List < string > keys = Items . Keys . ToList ( ) ;
2019-01-08 11:49:31 -08:00
keys . Sort ( SplitByLevelSort ) ;
// Then, we loop over the games
Parallel . ForEach ( keys , Globals . ParallelOptions , key = >
{
// Here, the key is the name of the game to be used for comparison
2020-07-15 09:41:59 -07:00
if ( tempDat . DatHeader . Name ! = null & & tempDat . DatHeader . Name ! = Path . GetDirectoryName ( key ) )
2019-01-08 11:49:31 -08:00
{
// Reset the DAT for the next items
2020-07-15 09:41:59 -07:00
tempDat = Create ( DatHeader ) ;
tempDat . DatHeader . Name = null ;
2019-01-08 11:49:31 -08:00
}
// Clean the input list and set all games to be pathless
2020-07-26 22:34:45 -07:00
List < DatItem > items = Items [ key ] ;
2019-01-08 11:49:31 -08:00
items . ForEach ( item = > item . MachineName = Path . GetFileName ( item . MachineName ) ) ;
items . ForEach ( item = > item . MachineDescription = Path . GetFileName ( item . MachineDescription ) ) ;
// Now add the game to the output DAT
2020-07-26 22:34:45 -07:00
tempDat . Items . AddRange ( key , items ) ;
2019-01-08 11:49:31 -08:00
// Then set the DAT name to be the parent directory name
2020-07-15 09:41:59 -07:00
tempDat . DatHeader . Name = Path . GetDirectoryName ( key ) ;
2019-01-08 11:49:31 -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>
private int SplitByLevelSort ( string a , string b )
{
NaturalComparer nc = new NaturalComparer ( ) ;
int adeep = a . Count ( c = > c = = '/' | | c = = '\\' ) ;
int bdeep = b . Count ( c = > c = = '/' | | c = = '\\' ) ;
if ( adeep = = bdeep )
return nc . Compare ( a , b ) ;
2020-06-10 22:37:19 -07:00
2019-01-08 11:49:31 -08:00
return adeep - bdeep ;
}
/// <summary>
/// Helper function for SplitByLevel to clean and write out a DAT
/// </summary>
/// <param name="datFile">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>
private void SplitByLevelHelper ( DatFile datFile , string outDir , bool shortname , bool restore )
{
// Get the name from the DAT to use separately
2020-07-15 09:41:59 -07:00
string name = datFile . DatHeader . Name ;
2019-01-08 11:49:31 -08:00
string expName = name . Replace ( "/" , " - " ) . Replace ( "\\" , " - " ) ;
// Now set the new output values
2020-07-15 09:41:59 -07:00
datFile . DatHeader . FileName = WebUtility . HtmlDecode ( string . IsNullOrWhiteSpace ( name )
? DatHeader . FileName
2019-01-08 11:49:31 -08:00
: ( shortname
? Path . GetFileName ( name )
: expName
)
) ;
2020-07-15 09:41:59 -07:00
datFile . DatHeader . FileName = ( restore ? $"{DatHeader.FileName} ({datFile.DatHeader.FileName})" : datFile . DatHeader . FileName ) ;
datFile . DatHeader . Name = $"{DatHeader.Name} ({expName})" ;
datFile . DatHeader . Description = ( string . IsNullOrWhiteSpace ( DatHeader . Description ) ? datFile . DatHeader . Name : $"{DatHeader.Description} ({expName})" ) ;
datFile . DatHeader . Type = null ;
2019-01-08 11:49:31 -08:00
// Write out the temporary DAT to the proper directory
datFile . Write ( outDir ) ;
}
/// <summary>
/// Split a DAT by size of Rom
/// </summary>
/// <param name="outDir">Name of the directory to write the DATs out to</param>
/// <param name="radix">Long value representing the split point</param>
/// <returns>True if split succeeded, false otherwise</returns>
2020-07-15 09:41:59 -07:00
private bool SplitBySize ( string outDir , long radix )
2019-01-08 11:49:31 -08:00
{
// Create each of the respective output DATs
Globals . Logger . User ( "Creating and populating new DATs" ) ;
2020-07-15 09:41:59 -07:00
DatFile lessDat = Create ( DatHeader . CloneStandard ( ) ) ;
lessDat . DatHeader . FileName + = $" (less than {radix})" ;
lessDat . DatHeader . Name + = $" (less than {radix})" ;
lessDat . DatHeader . Description + = $" (less than {radix})" ;
DatFile greaterEqualDat = Create ( DatHeader . CloneStandard ( ) ) ;
greaterEqualDat . DatHeader . FileName + = $" (equal-greater than {radix})" ;
greaterEqualDat . DatHeader . Name + = $" (equal-greater than {radix})" ;
greaterEqualDat . DatHeader . Description + = $" (equal-greater than {radix})" ;
2019-01-08 11:49:31 -08:00
// Now populate each of the DAT objects in turn
2020-07-26 22:34:45 -07:00
Parallel . ForEach ( Items . Keys , Globals . ParallelOptions , key = >
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
List < DatItem > items = Items [ key ] ;
2019-01-08 11:49:31 -08:00
foreach ( DatItem item in items )
{
// If the file is not a Rom, it automatically goes in the "lesser" dat
2019-01-08 12:11:55 -08:00
if ( item . ItemType ! = ItemType . Rom )
2020-07-26 22:34:45 -07:00
lessDat . Items . Add ( key , item ) ;
2020-06-10 22:37:19 -07:00
2019-01-08 11:49:31 -08:00
// If the file is a Rom and less than the radix, put it in the "lesser" dat
2019-01-08 12:11:55 -08:00
else if ( item . ItemType = = ItemType . Rom & & ( ( Rom ) item ) . Size < radix )
2020-07-26 22:34:45 -07:00
lessDat . Items . Add ( key , item ) ;
2020-06-10 22:37:19 -07:00
2019-01-08 11:49:31 -08:00
// If the file is a Rom and greater than or equal to the radix, put it in the "greater" dat
2019-01-08 12:11:55 -08:00
else if ( item . ItemType = = ItemType . Rom & & ( ( Rom ) item ) . Size > = radix )
2020-07-26 22:34:45 -07:00
greaterEqualDat . Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
}
} ) ;
// Now, output all of the files to the output directory
Globals . Logger . User ( "DAT information created, outputting new files" ) ;
bool success = true ;
success & = lessDat . Write ( outDir ) ;
success & = greaterEqualDat . Write ( outDir ) ;
return success ;
}
/// <summary>
/// Split a DAT by type of DatItem
/// </summary>
/// <param name="outDir">Name of the directory to write the DATs out to</param>
/// <returns>True if split succeeded, false otherwise</returns>
2020-07-15 09:41:59 -07:00
private bool SplitByType ( string outDir )
2019-01-08 11:49:31 -08:00
{
// Create each of the respective output DATs
Globals . Logger . User ( "Creating and populating new DATs" ) ;
2020-07-15 09:41:59 -07:00
DatFile romdat = Create ( DatHeader . CloneStandard ( ) ) ;
romdat . DatHeader . FileName + = " (ROM)" ;
romdat . DatHeader . Name + = " (ROM)" ;
romdat . DatHeader . Description + = " (ROM)" ;
DatFile diskdat = Create ( DatHeader . CloneStandard ( ) ) ;
diskdat . DatHeader . FileName + = " (Disk)" ;
diskdat . DatHeader . Name + = " (Disk)" ;
diskdat . DatHeader . Description + = " (Disk)" ;
DatFile sampledat = Create ( DatHeader . CloneStandard ( ) ) ;
sampledat . DatHeader . FileName + = " (Sample)" ;
sampledat . DatHeader . Name + = " (Sample)" ;
sampledat . DatHeader . Description + = " (Sample)" ;
2019-01-08 11:49:31 -08:00
// Now populate each of the DAT objects in turn
2020-07-26 22:34:45 -07:00
Parallel . ForEach ( Items . Keys , Globals . ParallelOptions , key = >
2019-01-08 11:49:31 -08:00
{
2020-07-26 22:34:45 -07:00
List < DatItem > items = Items [ key ] ;
2019-01-08 11:49:31 -08:00
foreach ( DatItem item in items )
{
// If the file is a Rom
2019-01-08 12:11:55 -08:00
if ( item . ItemType = = ItemType . Rom )
2020-07-26 22:34:45 -07:00
romdat . Items . Add ( key , item ) ;
2020-06-10 22:37:19 -07:00
2019-01-08 11:49:31 -08:00
// If the file is a Disk
2019-01-08 12:11:55 -08:00
else if ( item . ItemType = = ItemType . Disk )
2020-07-26 22:34:45 -07:00
diskdat . Items . Add ( key , item ) ;
2020-07-15 09:41:59 -07:00
2019-01-08 11:49:31 -08:00
// If the file is a Sample
2019-01-08 12:11:55 -08:00
else if ( item . ItemType = = ItemType . Sample )
2020-07-26 22:34:45 -07:00
sampledat . Items . Add ( key , item ) ;
2019-01-08 11:49:31 -08:00
}
} ) ;
// Now, output all of the files to the output directory
Globals . Logger . User ( "DAT information created, outputting new files" ) ;
bool success = true ;
success & = romdat . Write ( outDir ) ;
success & = diskdat . Write ( outDir ) ;
success & = sampledat . Write ( outDir ) ;
return success ;
}
#endregion
#region Writing
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="outDir">Set the output directory (default current directory)</param>
/// <param name="norename">True if games should only be compared on game and file name (default), false if system and source are counted</param>
/// <param name="stats">True if DAT statistics should be output on write, false otherwise (default)</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <param name="overwrite">True if files should be overwritten (default), false if they should be renamed instead</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public bool Write ( string outDir = null , bool norename = true , bool stats = false , bool ignoreblanks = false , bool overwrite = true )
{
// If there's nothing there, abort
2020-07-27 01:39:32 -07:00
if ( Items . TotalCount = = 0 )
2019-01-08 11:49:31 -08:00
{
Globals . Logger . User ( "There were no items to write out!" ) ;
return false ;
}
// Ensure the output directory is set and created
2020-07-15 09:41:59 -07:00
outDir = DirectoryExtensions . Ensure ( outDir , create : true ) ;
2019-01-08 11:49:31 -08:00
// If the DAT has no output format, default to XML
2020-07-15 09:41:59 -07:00
if ( DatHeader . DatFormat = = 0 )
2019-01-08 11:49:31 -08:00
{
Globals . Logger . Verbose ( "No DAT format defined, defaulting to XML" ) ;
2020-07-15 09:41:59 -07:00
DatHeader . DatFormat = DatFormat . Logiqx ;
2019-01-08 11:49:31 -08:00
}
// Make sure that the three essential fields are filled in
2020-07-15 09:41:59 -07:00
if ( string . IsNullOrWhiteSpace ( DatHeader . FileName ) & & string . IsNullOrWhiteSpace ( DatHeader . Name ) & & string . IsNullOrWhiteSpace ( DatHeader . Description ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader . FileName = DatHeader . Name = DatHeader . Description = "Default" ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
else if ( string . IsNullOrWhiteSpace ( DatHeader . FileName ) & & string . IsNullOrWhiteSpace ( DatHeader . Name ) & & ! string . IsNullOrWhiteSpace ( DatHeader . Description ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader . FileName = DatHeader . Name = DatHeader . Description ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
else if ( string . IsNullOrWhiteSpace ( DatHeader . FileName ) & & ! string . IsNullOrWhiteSpace ( DatHeader . Name ) & & string . IsNullOrWhiteSpace ( DatHeader . Description ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader . FileName = DatHeader . Description = DatHeader . Name ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
else if ( string . IsNullOrWhiteSpace ( DatHeader . FileName ) & & ! string . IsNullOrWhiteSpace ( DatHeader . Name ) & & ! string . IsNullOrWhiteSpace ( DatHeader . Description ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader . FileName = DatHeader . Description ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
else if ( ! string . IsNullOrWhiteSpace ( DatHeader . FileName ) & & string . IsNullOrWhiteSpace ( DatHeader . Name ) & & string . IsNullOrWhiteSpace ( DatHeader . Description ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader . Name = DatHeader . Description = DatHeader . FileName ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
else if ( ! string . IsNullOrWhiteSpace ( DatHeader . FileName ) & & string . IsNullOrWhiteSpace ( DatHeader . Name ) & & ! string . IsNullOrWhiteSpace ( DatHeader . Description ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader . Name = DatHeader . Description ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
else if ( ! string . IsNullOrWhiteSpace ( DatHeader . FileName ) & & ! string . IsNullOrWhiteSpace ( DatHeader . Name ) & & string . IsNullOrWhiteSpace ( DatHeader . Description ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
DatHeader . Description = DatHeader . Name ;
2019-01-08 11:49:31 -08:00
}
2020-07-15 09:41:59 -07:00
else if ( ! string . IsNullOrWhiteSpace ( DatHeader . FileName ) & & ! string . IsNullOrWhiteSpace ( DatHeader . Name ) & & ! string . IsNullOrWhiteSpace ( DatHeader . Description ) )
2019-01-08 11:49:31 -08:00
{
// Nothing is needed
}
// Output initial statistics, for kicks
if ( stats )
2020-07-15 09:41:59 -07:00
{
2020-07-27 01:39:32 -07:00
if ( Items . RomCount + Items . DiskCount = = 0 )
2020-07-26 22:34:45 -07:00
Items . RecalculateStats ( ) ;
2020-07-15 09:41:59 -07:00
2020-07-26 22:34:45 -07:00
Items . BucketBy ( BucketedBy . Game , DedupeType . None , norename : true ) ;
2020-07-15 09:41:59 -07:00
var consoleOutput = BaseReport . Create ( StatReportFormat . None , null , true , true ) ;
2020-07-27 01:39:32 -07:00
consoleOutput . ReplaceStatistics ( DatHeader . FileName , Items . Keys . Count ( ) , Items ) ;
2020-07-15 09:41:59 -07:00
}
2019-01-08 11:49:31 -08:00
// Bucket and dedupe according to the flag
2020-07-15 09:41:59 -07:00
if ( DatHeader . DedupeRoms = = DedupeType . Full )
2020-07-26 22:34:45 -07:00
Items . BucketBy ( BucketedBy . CRC , DatHeader . DedupeRoms , norename : norename ) ;
2020-07-15 09:41:59 -07:00
else if ( DatHeader . DedupeRoms = = DedupeType . Game )
2020-07-26 22:34:45 -07:00
Items . BucketBy ( BucketedBy . Game , DatHeader . DedupeRoms , norename : norename ) ;
2019-01-08 11:49:31 -08:00
// Bucket roms by game name, if not already
2020-07-26 22:34:45 -07:00
Items . BucketBy ( BucketedBy . Game , DedupeType . None , norename : norename ) ;
2019-01-08 11:49:31 -08:00
// Output the number of items we're going to be writing
2020-07-27 01:39:32 -07:00
Globals . Logger . User ( $"A total of {Items.TotalCount} items will be written out to '{DatHeader.FileName}'" ) ;
2019-01-08 11:49:31 -08:00
// Get the outfile names
2020-07-15 09:41:59 -07:00
Dictionary < DatFormat , string > outfiles = DatHeader . CreateOutFileNames ( outDir , overwrite ) ;
2019-01-08 11:49:31 -08:00
try
{
// Write out all required formats
Parallel . ForEach ( outfiles . Keys , Globals . ParallelOptions , datFormat = >
{
string outfile = outfiles [ datFormat ] ;
try
{
2020-07-15 09:41:59 -07:00
Create ( datFormat , this ) ? . WriteToFile ( outfile , ignoreblanks ) ;
2019-01-08 11:49:31 -08:00
}
catch ( Exception ex )
{
2020-06-10 22:37:19 -07:00
Globals . Logger . Error ( $"Datfile {outfile} could not be written out: {ex}" ) ;
2019-01-08 11:49:31 -08:00
}
2019-09-20 10:30:30 -07:00
2019-01-08 11:49:31 -08:00
} ) ;
}
catch ( Exception ex )
{
Globals . Logger . Error ( ex . ToString ( ) ) ;
return false ;
}
return true ;
}
/// <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>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
2020-07-15 09:41:59 -07:00
public abstract bool WriteToFile ( string outfile , bool ignoreblanks = false ) ;
2019-01-08 11:49:31 -08:00
/// <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 )
{
string name = item . Name ;
// Backup relevant values and set new ones accordingly
2020-07-15 09:41:59 -07:00
bool quotesBackup = DatHeader . Quotes ;
bool useRomNameBackup = DatHeader . UseRomName ;
2019-01-08 11:49:31 -08:00
if ( forceRemoveQuotes )
2020-07-15 09:41:59 -07:00
DatHeader . Quotes = false ;
2020-06-10 22:37:19 -07:00
2019-01-08 11:49:31 -08:00
if ( forceRomName )
2020-07-15 09:41:59 -07:00
DatHeader . UseRomName = true ;
2019-01-08 11:49:31 -08:00
// Create the proper Prefix and Postfix
string pre = CreatePrefixPostfix ( item , true ) ;
string post = CreatePrefixPostfix ( item , false ) ;
// If we're in Romba mode, take care of that instead
2020-07-15 09:41:59 -07:00
if ( DatHeader . Romba )
2019-01-08 11:49:31 -08:00
{
2019-01-08 12:11:55 -08:00
if ( item . ItemType = = ItemType . Rom )
2019-01-08 11:49:31 -08:00
{
2020-06-10 22:37:19 -07:00
Rom romItem = item as Rom ;
2019-01-08 11:49:31 -08:00
// We can only write out if there's a SHA-1
2020-06-10 22:37:19 -07:00
if ( ! string . IsNullOrWhiteSpace ( romItem . SHA1 ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
name = PathExtensions . GetRombaPath ( romItem . SHA1 ) . Replace ( '\\' , '/' ) ;
2020-06-10 22:37:19 -07:00
item . Name = $"{pre}{name}{post}" ;
2019-01-08 11:49:31 -08:00
}
}
2019-01-08 12:11:55 -08:00
else if ( item . ItemType = = ItemType . Disk )
2019-01-08 11:49:31 -08:00
{
2020-06-10 22:37:19 -07:00
Disk diskItem = item as Disk ;
2019-01-08 11:49:31 -08:00
// We can only write out if there's a SHA-1
2020-06-10 22:37:19 -07:00
if ( ! string . IsNullOrWhiteSpace ( diskItem . SHA1 ) )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
name = PathExtensions . GetRombaPath ( diskItem . SHA1 ) . Replace ( '\\' , '/' ) ;
2019-01-08 11:49:31 -08:00
item . Name = pre + name + post ;
}
}
return ;
}
2020-07-15 09:41:59 -07:00
if ( ! string . IsNullOrWhiteSpace ( DatHeader . ReplaceExtension ) | | DatHeader . RemoveExtension )
2019-01-08 11:49:31 -08:00
{
2020-07-15 09:41:59 -07:00
if ( DatHeader . RemoveExtension )
DatHeader . ReplaceExtension = string . Empty ;
2019-01-08 11:49:31 -08:00
string dir = Path . GetDirectoryName ( name ) ;
2020-06-10 22:37:19 -07:00
dir = dir . TrimStart ( Path . DirectorySeparatorChar ) ;
2020-07-15 09:41:59 -07:00
name = Path . Combine ( dir , Path . GetFileNameWithoutExtension ( name ) + DatHeader . ReplaceExtension ) ;
2019-01-08 11:49:31 -08:00
}
2020-06-10 22:37:19 -07:00
2020-07-15 09:41:59 -07:00
if ( ! string . IsNullOrWhiteSpace ( DatHeader . AddExtension ) )
name + = DatHeader . AddExtension ;
2019-01-08 11:49:31 -08:00
2020-07-15 09:41:59 -07:00
if ( DatHeader . UseRomName & & DatHeader . GameName )
2019-01-08 11:49:31 -08:00
name = Path . Combine ( item . MachineName , name ) ;
// Now assign back the item name
item . Name = pre + name + post ;
// Restore all relevant values
if ( forceRemoveQuotes )
2020-07-15 09:41:59 -07:00
DatHeader . Quotes = quotesBackup ;
2020-06-10 22:37:19 -07:00
2019-01-08 11:49:31 -08:00
if ( forceRomName )
2020-07-15 09:41:59 -07:00
DatHeader . UseRomName = useRomNameBackup ;
2019-01-08 11:49:31 -08:00
}
/// <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-06-10 22:37:19 -07:00
string fix = string . Empty ,
2019-01-08 11:49:31 -08:00
game = item . MachineName ,
name = item . Name ,
manufacturer = item . Manufacturer ,
publisher = item . Publisher ,
2020-07-18 21:35:17 -07:00
category = item . Category ,
2019-01-08 11:49:31 -08:00
crc = string . Empty ,
md5 = string . Empty ,
2020-06-05 22:26:44 -07:00
ripemd160 = string . Empty ,
2019-01-08 11:49:31 -08:00
sha1 = string . Empty ,
sha256 = string . Empty ,
sha384 = string . Empty ,
sha512 = string . Empty ,
size = string . Empty ;
// If we have a prefix
if ( prefix )
2020-07-15 09:41:59 -07:00
fix = DatHeader . Prefix + ( DatHeader . Quotes ? "\"" : string . Empty ) ;
2020-06-10 22:37:19 -07:00
2019-01-08 11:49:31 -08:00
// If we have a postfix
else
2020-07-15 09:41:59 -07:00
fix = ( DatHeader . Quotes ? "\"" : string . Empty ) + DatHeader . Postfix ;
2019-01-08 11:49:31 -08:00
// Ensure we have the proper values for replacement
2019-01-08 12:11:55 -08:00
if ( item . ItemType = = ItemType . Rom )
2019-01-08 11:49:31 -08:00
{
crc = ( ( Rom ) item ) . CRC ;
md5 = ( ( Rom ) item ) . MD5 ;
2020-07-15 09:41:59 -07:00
#if NET_FRAMEWORK
2020-06-05 22:26:44 -07:00
ripemd160 = ( ( Rom ) item ) . RIPEMD160 ;
2020-07-15 09:41:59 -07:00
#endif
2019-01-08 11:49:31 -08:00
sha1 = ( ( Rom ) item ) . SHA1 ;
sha256 = ( ( Rom ) item ) . SHA256 ;
sha384 = ( ( Rom ) item ) . SHA384 ;
sha512 = ( ( Rom ) item ) . SHA512 ;
size = ( ( Rom ) item ) . Size . ToString ( ) ;
}
2019-01-08 12:11:55 -08:00
else if ( item . ItemType = = ItemType . Disk )
2019-01-08 11:49:31 -08:00
{
md5 = ( ( Disk ) item ) . MD5 ;
2020-07-15 09:41:59 -07:00
#if NET_FRAMEWORK
2020-06-05 22:26:44 -07:00
ripemd160 = ( ( Disk ) item ) . RIPEMD160 ;
2020-07-15 09:41:59 -07:00
#endif
2019-01-08 11:49:31 -08:00
sha1 = ( ( Disk ) item ) . SHA1 ;
sha256 = ( ( Disk ) item ) . SHA256 ;
sha384 = ( ( Disk ) item ) . SHA384 ;
sha512 = ( ( Disk ) item ) . SHA512 ;
}
// Now do bulk replacement where possible
fix = fix
. Replace ( "%game%" , game )
. Replace ( "%machine%" , game )
. Replace ( "%name%" , name )
. Replace ( "%manufacturer%" , manufacturer )
. Replace ( "%publisher%" , publisher )
2020-07-18 21:35:17 -07:00
. Replace ( "%category%" , category )
2019-01-08 11:49:31 -08:00
. Replace ( "%crc%" , crc )
. Replace ( "%md5%" , md5 )
2020-06-05 22:26:44 -07:00
. Replace ( "%ripemd160%" , ripemd160 )
2019-01-08 11:49:31 -08:00
. Replace ( "%sha1%" , sha1 )
. Replace ( "%sha256%" , sha256 )
. Replace ( "%sha384%" , sha384 )
. Replace ( "%sha512%" , sha512 )
. Replace ( "%size%" , size ) ;
// TODO: Add GameName logic here too?
return fix ;
}
#endregion
#endregion // Instance Methods
}
2016-04-19 01:11:23 -07:00
}