2020-07-31 23:17:12 -07:00
using System ;
using System.Collections.Generic ;
2020-08-27 21:40:08 -07:00
using System.IO ;
2020-08-27 22:19:31 -07:00
using System.Threading.Tasks ;
2020-08-28 10:32:17 -07:00
2020-12-08 13:23:59 -08:00
using SabreTools.Core ;
2020-12-08 16:37:08 -08:00
using SabreTools.DatFiles ;
2020-12-10 23:24:09 -08:00
using SabreTools.DatTools ;
2020-12-07 13:57:26 -08:00
using SabreTools.Help ;
2020-12-07 15:08:57 -08:00
using SabreTools.IO ;
2020-12-08 11:09:05 -08:00
using SabreTools.Logging ;
2020-07-31 23:17:12 -07:00
namespace SabreTools.Features
{
internal class Update : BaseFeature
{
public const string Value = "Update" ;
public Update ( )
{
Name = Value ;
Flags = new List < string > ( ) { "-ud" , "--update" } ;
Description = "Update and manipulate DAT(s)" ;
2020-12-07 13:57:26 -08:00
_featureType = ParameterType . Flag ;
2020-07-31 23:17:12 -07:00
LongDescription = "This is the multitool part of the program, allowing for almost every manipulation to a DAT, or set of DATs. This is also a combination of many different programs that performed DAT manipulation that work better together." ;
2020-12-07 13:57:26 -08:00
Features = new Dictionary < string , Help . Feature > ( ) ;
2020-07-31 23:17:12 -07:00
// Output Formats
AddFeature ( OutputTypeListInput ) ;
this [ OutputTypeListInput ] . AddFeature ( PrefixStringInput ) ;
this [ OutputTypeListInput ] . AddFeature ( PostfixStringInput ) ;
this [ OutputTypeListInput ] . AddFeature ( QuotesFlag ) ;
this [ OutputTypeListInput ] . AddFeature ( RomsFlag ) ;
this [ OutputTypeListInput ] . AddFeature ( GamePrefixFlag ) ;
this [ OutputTypeListInput ] . AddFeature ( AddExtensionStringInput ) ;
this [ OutputTypeListInput ] . AddFeature ( ReplaceExtensionStringInput ) ;
this [ OutputTypeListInput ] . AddFeature ( RemoveExtensionsFlag ) ;
this [ OutputTypeListInput ] . AddFeature ( RombaFlag ) ;
2020-08-18 23:39:13 -07:00
this [ OutputTypeListInput ] [ RombaFlag ] . AddFeature ( RombaDepthInt32Input ) ;
2020-07-31 23:17:12 -07:00
this [ OutputTypeListInput ] . AddFeature ( DeprecatedFlag ) ;
AddHeaderFeatures ( ) ;
AddFeature ( KeepEmptyGamesFlag ) ;
AddFeature ( CleanFlag ) ;
AddFeature ( RemoveUnicodeFlag ) ;
AddFeature ( DescriptionAsNameFlag ) ;
AddInternalSplitFeatures ( ) ;
AddFeature ( TrimFlag ) ;
this [ TrimFlag ] . AddFeature ( RootDirStringInput ) ;
AddFeature ( SingleSetFlag ) ;
AddFeature ( DedupFlag ) ;
AddFeature ( GameDedupFlag ) ;
AddFeature ( MergeFlag ) ;
this [ MergeFlag ] . AddFeature ( NoAutomaticDateFlag ) ;
AddFeature ( DiffAllFlag ) ;
this [ DiffAllFlag ] . AddFeature ( NoAutomaticDateFlag ) ;
AddFeature ( DiffDuplicatesFlag ) ;
this [ DiffDuplicatesFlag ] . AddFeature ( NoAutomaticDateFlag ) ;
AddFeature ( DiffIndividualsFlag ) ;
this [ DiffIndividualsFlag ] . AddFeature ( NoAutomaticDateFlag ) ;
AddFeature ( DiffNoDuplicatesFlag ) ;
this [ DiffNoDuplicatesFlag ] . AddFeature ( NoAutomaticDateFlag ) ;
AddFeature ( DiffAgainstFlag ) ;
this [ DiffAgainstFlag ] . AddFeature ( BaseDatListInput ) ;
2020-08-01 15:10:41 -07:00
this [ DiffAgainstFlag ] . AddFeature ( ByGameFlag ) ;
2020-07-31 23:17:12 -07:00
AddFeature ( BaseReplaceFlag ) ;
this [ BaseReplaceFlag ] . AddFeature ( BaseDatListInput ) ;
this [ BaseReplaceFlag ] . AddFeature ( UpdateFieldListInput ) ;
this [ BaseReplaceFlag ] [ UpdateFieldListInput ] . AddFeature ( OnlySameFlag ) ;
AddFeature ( ReverseBaseReplaceFlag ) ;
this [ ReverseBaseReplaceFlag ] . AddFeature ( BaseDatListInput ) ;
this [ ReverseBaseReplaceFlag ] . AddFeature ( UpdateFieldListInput ) ;
this [ ReverseBaseReplaceFlag ] [ UpdateFieldListInput ] . AddFeature ( OnlySameFlag ) ;
AddFeature ( DiffCascadeFlag ) ;
this [ DiffCascadeFlag ] . AddFeature ( SkipFirstOutputFlag ) ;
AddFeature ( DiffReverseCascadeFlag ) ;
this [ DiffReverseCascadeFlag ] . AddFeature ( SkipFirstOutputFlag ) ;
2020-08-21 10:38:42 -07:00
AddFeature ( ExtraIniListInput ) ;
2020-07-31 23:17:12 -07:00
AddFilteringFeatures ( ) ;
AddFeature ( OutputDirStringInput ) ;
AddFeature ( InplaceFlag ) ;
AddFeature ( ThreadsInt32Input ) ;
}
2020-12-07 13:57:26 -08:00
public override void ProcessFeatures ( Dictionary < string , Help . Feature > features )
2020-07-31 23:17:12 -07:00
{
base . ProcessFeatures ( features ) ;
// Get feature flags
2020-12-13 13:22:06 -08:00
var updateDatItemFields = GetUpdateDatItemFields ( features ) ;
var updateMachineFields = GetUpdateMachineFields ( features ) ;
2020-07-31 23:17:12 -07:00
var updateMode = GetUpdateMode ( features ) ;
// Normalize the extensions
Header . AddExtension = ( string . IsNullOrWhiteSpace ( Header . AddExtension ) | | Header . AddExtension . StartsWith ( "." )
? Header . AddExtension
: $".{Header.AddExtension}" ) ;
Header . ReplaceExtension = ( string . IsNullOrWhiteSpace ( Header . ReplaceExtension ) | | Header . ReplaceExtension . StartsWith ( "." )
? Header . ReplaceExtension
: $".{Header.ReplaceExtension}" ) ;
// If we're in a special update mode and the names aren't set, set defaults
if ( updateMode ! = 0 )
{
// Get the values that will be used
if ( string . IsNullOrWhiteSpace ( Header . Date ) )
Header . Date = DateTime . Now . ToString ( "yyyy-MM-dd" ) ;
if ( string . IsNullOrWhiteSpace ( Header . Name ) )
{
Header . Name = ( updateMode ! = 0 ? "DiffDAT" : "MergeDAT" )
+ ( Header . Type = = "SuperDAT" ? "-SuperDAT" : string . Empty )
2020-08-30 23:11:05 -07:00
+ ( Cleaner . DedupeRoms ! = DedupeType . None ? "-deduped" : string . Empty ) ;
2020-07-31 23:17:12 -07:00
}
if ( string . IsNullOrWhiteSpace ( Header . Description ) )
{
Header . Description = ( updateMode ! = 0 ? "DiffDAT" : "MergeDAT" )
+ ( Header . Type = = "SuperDAT" ? "-SuperDAT" : string . Empty )
2020-08-30 23:11:05 -07:00
+ ( Cleaner . DedupeRoms ! = DedupeType . None ? " - deduped" : string . Empty ) ;
2020-07-31 23:17:12 -07:00
if ( ! GetBoolean ( features , NoAutomaticDateValue ) )
Header . Description + = $" ({Header.Date})" ;
}
if ( string . IsNullOrWhiteSpace ( Header . Category ) & & updateMode ! = 0 )
Header . Category = "DiffDAT" ;
if ( string . IsNullOrWhiteSpace ( Header . Author ) )
Header . Author = "SabreTools" ;
}
// If no update fields are set, default to Names
2020-12-13 13:22:06 -08:00
if ( updateDatItemFields = = null | | updateDatItemFields . Count = = 0 )
updateDatItemFields = new List < DatItemField > ( ) { DatItemField . Name } ;
2020-07-31 23:17:12 -07:00
2020-08-01 21:42:28 -07:00
// Ensure we only have files in the inputs
2020-12-10 22:16:53 -08:00
List < ParentablePath > inputPaths = PathTool . GetFilesOnly ( Inputs , appendparent : true ) ;
List < ParentablePath > basePaths = PathTool . GetFilesOnly ( GetList ( features , BaseDatListValue ) ) ;
2020-08-01 21:42:28 -07:00
2020-12-18 22:37:33 -08:00
// Ensure the output directory
OutputDir = OutputDir . Ensure ( ) ;
2020-08-01 21:42:28 -07:00
// If we're in standard update mode, run through all of the inputs
if ( updateMode = = UpdateMode . None )
{
2020-08-27 21:40:08 -07:00
// Loop through each input and update
2020-08-27 22:19:31 -07:00
Parallel . ForEach ( inputPaths , Globals . ParallelOptions , inputPath = >
2020-08-27 21:40:08 -07:00
{
// Create a new base DatFile
DatFile datFile = DatFile . Create ( Header ) ;
2020-10-07 15:42:30 -07:00
logger . User ( $"Processing '{Path.GetFileName(inputPath.CurrentPath)}'" ) ;
2020-12-10 13:53:34 -08:00
Parser . ParseInto ( datFile , inputPath , keep : true ,
2020-08-27 21:40:08 -07:00
keepext : datFile . Header . DatFormat . HasFlag ( DatFormat . TSV )
| | datFile . Header . DatFormat . HasFlag ( DatFormat . CSV )
| | datFile . Header . DatFormat . HasFlag ( DatFormat . SSV ) ) ;
2020-08-28 13:59:42 -07:00
// Perform additional processing steps
2020-12-10 14:11:35 -08:00
Modification . ApplyExtras ( datFile , Extras ) ;
Modification . ApplySplitting ( datFile , GetSplitType ( features ) , false ) ;
2020-12-13 13:22:06 -08:00
Modification . ApplyFilters ( datFile , Cleaner ) ;
2020-12-10 14:11:35 -08:00
Modification . ApplyCleaning ( datFile , Cleaner ) ;
2020-08-27 21:40:08 -07:00
// Get the correct output path
string realOutDir = inputPath . GetOutputPath ( OutputDir , GetBoolean ( features , InplaceValue ) ) ;
// Try to output the file, overwriting only if it's not in the current directory
2020-12-10 14:03:07 -08:00
Writer . Write ( datFile , realOutDir , overwrite : GetBoolean ( features , InplaceValue ) ) ;
2020-08-27 22:19:31 -07:00
} ) ;
2020-08-27 21:40:08 -07:00
2020-08-01 21:42:28 -07:00
return ;
}
// Reverse inputs if we're in a required mode
if ( updateMode . HasFlag ( UpdateMode . DiffReverseCascade ) )
{
updateMode | = UpdateMode . DiffCascade ;
2020-08-27 21:40:08 -07:00
inputPaths . Reverse ( ) ;
2020-08-01 21:42:28 -07:00
}
if ( updateMode . HasFlag ( UpdateMode . ReverseBaseReplace ) )
{
updateMode | = UpdateMode . BaseReplace ;
2020-08-27 21:40:08 -07:00
basePaths . Reverse ( ) ;
2020-08-01 21:42:28 -07:00
}
// Create a DAT to capture inputs
2020-07-31 23:17:12 -07:00
DatFile userInputDat = DatFile . Create ( Header ) ;
2020-08-01 21:42:28 -07:00
// Populate using the correct set
List < DatHeader > datHeaders ;
if ( updateMode . HasFlag ( UpdateMode . DiffAgainst ) | | updateMode . HasFlag ( UpdateMode . BaseReplace ) )
2020-12-13 23:09:24 -08:00
datHeaders = DatFileTool . PopulateUserData ( userInputDat , basePaths ) ;
2020-08-01 21:42:28 -07:00
else
2020-12-13 23:09:24 -08:00
datHeaders = DatFileTool . PopulateUserData ( userInputDat , inputPaths ) ;
2020-08-27 21:46:19 -07:00
2020-08-28 13:59:42 -07:00
// Perform additional processing steps
2020-12-10 14:11:35 -08:00
Modification . ApplyExtras ( userInputDat , Extras ) ;
Modification . ApplySplitting ( userInputDat , GetSplitType ( features ) , false ) ;
2020-12-13 13:22:06 -08:00
Modification . ApplyFilters ( userInputDat , Cleaner ) ;
2020-12-10 14:11:35 -08:00
Modification . ApplyCleaning ( userInputDat , Cleaner ) ;
2020-08-01 21:42:28 -07:00
// Output only DatItems that are duplicated across inputs
if ( updateMode . HasFlag ( UpdateMode . DiffDupesOnly ) )
2020-08-27 22:27:23 -07:00
{
2020-12-13 23:09:24 -08:00
DatFile dupeData = DatFileTool . DiffDuplicates ( userInputDat , inputPaths ) ;
2020-08-27 22:27:23 -07:00
InternalStopwatch watch = new InternalStopwatch ( "Outputting duplicate DAT" ) ;
2020-12-10 14:03:07 -08:00
Writer . Write ( dupeData , OutputDir , overwrite : false ) ;
2020-08-27 22:27:23 -07:00
watch . Stop ( ) ;
}
2020-08-01 21:42:28 -07:00
// Output only DatItems that are not duplicated across inputs
if ( updateMode . HasFlag ( UpdateMode . DiffNoDupesOnly ) )
2020-08-27 22:27:23 -07:00
{
2020-12-13 23:09:24 -08:00
DatFile outerDiffData = DatFileTool . DiffNoDuplicates ( userInputDat , inputPaths ) ;
2020-08-27 22:27:23 -07:00
InternalStopwatch watch = new InternalStopwatch ( "Outputting no duplicate DAT" ) ;
2020-12-10 14:03:07 -08:00
Writer . Write ( outerDiffData , OutputDir , overwrite : false ) ;
2020-08-27 22:27:23 -07:00
watch . Stop ( ) ;
}
2020-08-01 21:42:28 -07:00
// Output only DatItems that are unique to each input
if ( updateMode . HasFlag ( UpdateMode . DiffIndividualsOnly ) )
2020-08-27 22:27:23 -07:00
{
// Get all of the output DatFiles
2020-12-13 23:09:24 -08:00
List < DatFile > datFiles = DatFileTool . DiffIndividuals ( userInputDat , inputPaths ) ;
2020-08-27 22:27:23 -07:00
// Loop through and output the new DatFiles
InternalStopwatch watch = new InternalStopwatch ( "Outputting all individual DATs" ) ;
Parallel . For ( 0 , inputPaths . Count , Globals . ParallelOptions , j = >
{
string path = inputPaths [ j ] . GetOutputPath ( OutputDir , GetBoolean ( features , InplaceValue ) ) ;
// Try to output the file
2020-12-10 14:03:07 -08:00
Writer . Write ( datFiles [ j ] , path , overwrite : GetBoolean ( features , InplaceValue ) ) ;
2020-08-27 22:27:23 -07:00
} ) ;
watch . Stop ( ) ;
}
2020-08-01 21:42:28 -07:00
// Output cascaded diffs
if ( updateMode . HasFlag ( UpdateMode . DiffCascade ) )
{
2020-08-27 22:19:31 -07:00
// Preprocess the DatHeaders
Parallel . For ( 0 , datHeaders . Count , Globals . ParallelOptions , j = >
{
// If we're outputting to the runtime folder, rename
if ( ! GetBoolean ( features , InplaceValue ) & & OutputDir = = Environment . CurrentDirectory )
{
string innerpost = $" ({j} - {inputPaths[j].GetNormalizedFileName(true)} Only)" ;
datHeaders [ j ] = userInputDat . Header ;
datHeaders [ j ] . FileName + = innerpost ;
datHeaders [ j ] . Name + = innerpost ;
datHeaders [ j ] . Description + = innerpost ;
}
} ) ;
// Get all of the output DatFiles
2020-12-13 23:09:24 -08:00
List < DatFile > datFiles = DatFileTool . DiffCascade ( userInputDat , datHeaders ) ;
2020-08-27 22:19:31 -07:00
// Loop through and output the new DatFiles
InternalStopwatch watch = new InternalStopwatch ( "Outputting all created DATs" ) ;
int startIndex = GetBoolean ( features , SkipFirstOutputValue ) ? 1 : 0 ;
Parallel . For ( startIndex , inputPaths . Count , Globals . ParallelOptions , j = >
{
string path = inputPaths [ j ] . GetOutputPath ( OutputDir , GetBoolean ( features , InplaceValue ) ) ;
// Try to output the file
2020-12-10 14:03:07 -08:00
Writer . Write ( datFiles [ j ] , path , overwrite : GetBoolean ( features , InplaceValue ) ) ;
2020-08-27 22:19:31 -07:00
} ) ;
watch . Stop ( ) ;
2020-08-01 21:42:28 -07:00
}
// Output differences against a base DAT
if ( updateMode . HasFlag ( UpdateMode . DiffAgainst ) )
{
2020-08-27 21:40:08 -07:00
// Loop through each input and diff against the base
2020-08-27 22:19:31 -07:00
Parallel . ForEach ( inputPaths , Globals . ParallelOptions , inputPath = >
2020-08-27 21:40:08 -07:00
{
2020-08-28 13:59:42 -07:00
// Parse the path to a new DatFile
2020-08-27 21:40:08 -07:00
DatFile repDat = DatFile . Create ( userInputDat . Header . CloneFiltering ( ) ) ;
2020-12-10 13:53:34 -08:00
Parser . ParseInto ( repDat , inputPath , indexId : 1 , keep : true ) ;
2020-08-28 13:59:42 -07:00
// Perform additional processing steps
2020-12-10 14:11:35 -08:00
Modification . ApplyExtras ( repDat , Extras ) ;
Modification . ApplySplitting ( repDat , GetSplitType ( features ) , false ) ;
2020-12-13 13:22:06 -08:00
Modification . ApplyFilters ( repDat , Cleaner ) ;
2020-12-10 14:11:35 -08:00
Modification . ApplyCleaning ( repDat , Cleaner ) ;
2020-08-27 21:40:08 -07:00
// Now replace the fields from the base DatFile
2020-12-13 23:09:24 -08:00
DatFileTool . DiffAgainst ( userInputDat , repDat , GetBoolean ( Features , ByGameValue ) ) ;
2020-08-27 21:40:08 -07:00
// Finally output the diffed DatFile
string interOutDir = inputPath . GetOutputPath ( OutputDir , GetBoolean ( features , InplaceValue ) ) ;
2020-12-10 14:03:07 -08:00
Writer . Write ( repDat , interOutDir , overwrite : GetBoolean ( features , InplaceValue ) ) ;
2020-08-27 22:19:31 -07:00
} ) ;
2020-08-01 21:42:28 -07:00
}
2020-08-27 21:40:08 -07:00
// Output DATs after replacing fields from a base DatFile
2020-08-01 21:42:28 -07:00
if ( updateMode . HasFlag ( UpdateMode . BaseReplace ) )
{
2020-08-27 21:40:08 -07:00
// Loop through each input and apply the base DatFile
2020-08-27 22:19:31 -07:00
Parallel . ForEach ( inputPaths , Globals . ParallelOptions , inputPath = >
2020-08-27 21:40:08 -07:00
{
2020-08-28 13:59:42 -07:00
// Parse the path to a new DatFile
2020-08-27 21:40:08 -07:00
DatFile repDat = DatFile . Create ( userInputDat . Header . CloneFiltering ( ) ) ;
2020-12-10 13:53:34 -08:00
Parser . ParseInto ( repDat , inputPath , indexId : 1 , keep : true ) ;
2020-08-28 13:59:42 -07:00
// Perform additional processing steps
2020-12-10 14:11:35 -08:00
Modification . ApplyExtras ( repDat , Extras ) ;
Modification . ApplySplitting ( repDat , GetSplitType ( features ) , false ) ;
2020-12-13 13:22:06 -08:00
Modification . ApplyFilters ( repDat , Cleaner ) ;
2020-12-10 14:11:35 -08:00
Modification . ApplyCleaning ( repDat , Cleaner ) ;
2020-08-27 21:40:08 -07:00
// Now replace the fields from the base DatFile
2020-12-13 23:09:24 -08:00
DatFileTool . BaseReplace (
2020-12-13 13:22:06 -08:00
userInputDat ,
repDat ,
updateMachineFields ,
updateDatItemFields ,
GetBoolean ( features , OnlySameValue ) ) ;
2020-08-27 21:40:08 -07:00
// Finally output the replaced DatFile
string interOutDir = inputPath . GetOutputPath ( OutputDir , GetBoolean ( features , InplaceValue ) ) ;
2020-12-10 14:03:07 -08:00
Writer . Write ( repDat , interOutDir , overwrite : GetBoolean ( features , InplaceValue ) ) ;
2020-08-27 22:19:31 -07:00
} ) ;
2020-08-01 21:42:28 -07:00
}
2020-08-01 21:54:42 -07:00
// Merge all input files and write
// This has to be last due to the SuperDAT handling
if ( updateMode . HasFlag ( UpdateMode . Merge ) )
2020-08-27 21:40:08 -07:00
{
// If we're in SuperDAT mode, prefix all games with their respective DATs
if ( string . Equals ( userInputDat . Header . Type , "SuperDAT" , StringComparison . OrdinalIgnoreCase ) )
2020-12-10 14:11:35 -08:00
Modification . ApplySuperDAT ( userInputDat , inputPaths ) ;
2020-08-27 21:40:08 -07:00
2020-12-10 14:03:07 -08:00
Writer . Write ( userInputDat , OutputDir ) ;
2020-08-27 21:40:08 -07:00
}
2020-07-31 23:17:12 -07:00
}
}
}