diff --git a/SabreTools.Core/Prepare.cs b/SabreTools.Core/Prepare.cs index 8d41d536..151a2136 100644 --- a/SabreTools.Core/Prepare.cs +++ b/SabreTools.Core/Prepare.cs @@ -12,8 +12,8 @@ namespace SabreTools.Core /// /// The current toolset version to be used by all child applications /// - public readonly static string Version = $"v1.1.0"; - //public readonly static string Version = $"v1.1.0-{File.GetCreationTime(Assembly.GetExecutingAssembly().Location):yyyy-MM-dd HH:mm:ss}"; + //public readonly static string Version = $"v1.1.0"; + public readonly static string Version = $"v1.1.0-{File.GetCreationTime(Assembly.GetExecutingAssembly().Location):yyyy-MM-dd HH:mm:ss}"; /// /// Readies the console and outputs the header diff --git a/SabreTools/Features/Batch.cs b/SabreTools/Features/Batch.cs index 18a4d6e7..57a60d25 100644 --- a/SabreTools/Features/Batch.cs +++ b/SabreTools/Features/Batch.cs @@ -80,7 +80,7 @@ Reset the internal state: reset();"; string[] lines = File.ReadAllLines(path); // Each batch file has its own state - BatchFile batchFile = new BatchFile(); + BatchState batchState = new BatchState(); // Process each command line foreach (string line in lines) @@ -101,90 +101,18 @@ Reset the internal state: reset();"; break; } - // Now switch on the command - logger.User($"Attempting to invoke {command.Name} with {(command.Arguments.Count == 0 ? "no arguments" : "the following argument(s): " + string.Join(", ", command.Arguments))}"); - switch (command.Name.ToLowerInvariant()) + // Validate that the command has the proper number and type of arguments + (bool valid, string error) = command.ValidateArguments(); + if (!valid) { - // Set a header field - case "set": - SetHeaderField(command, batchFile); - break; - - // Parse in new input file(s) - case "input": - ParseInputs(command, batchFile); - break; - - // Run DFD/D2D on path(s) - case "d2d": - case "dfd": - PopulateFromDir(command, batchFile); - break; - - // Apply a filter - case "filter": - ApplyFilter(command, batchFile); - break; - - // Apply an extra INI - case "extra": - ApplyExtra(command, batchFile); - break; - - // Apply internal split/merge - case "merge": - RunMerge(command, batchFile); - break; - - // Apply description-as-name logic - case "descname": - DescriptionAsName(command, batchFile); - break; - - // Apply 1G1R - case "1g1r": - OneGamePerRegion(command, batchFile); - break; - - // Apply one rom per game (ORPG) - case "orpg": - OneRomPerGame(command, batchFile); - break; - - // Remove a field - case "remove": - RemoveField(command, batchFile); - break; - - // Apply scene date stripping - case "sds": - SceneDateStrip(command, batchFile); - break; - - // Set new output format(s) - case "format": - SetOutputFormat(command, batchFile); - break; - - // Set output directory - case "output": - SetOutputDirectory(command, batchFile); - break; - - // Write out the current DatFile - case "write": - Write(command, batchFile); - break; - - // Reset the internal state - case "reset": - Reset(command, batchFile); - break; - - default: - logger.User($"Could not find a match for '{command.Name}'. Please see the help text for more details."); - break; + logger.User(error); + logger.User($"Usage: {command.Usage()}"); + break; } + + // Now run the command + logger.User($"Attempting to invoke {command.Name} with {(command.Arguments.Count == 0 ? "no arguments" : "the following argument(s): " + string.Join(", ", command.Arguments))}"); + command.Process(batchState); } } catch (Exception ex) @@ -193,438 +121,30 @@ Reset the internal state: reset();"; } } - #region Batch Commands - /// - /// Apply a single extras file to the internal DAT - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void ApplyExtra(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count != 2) - { - logger.User($"Invoked {command.Name} and expected 2 arguments, but {command.Arguments.Count} arguments were provided"); - logger.User("Usage: extra(field, inipath);"); - return; - } - - // Read in the individual arguments - MachineField extraMachineField = command.Arguments[0].AsMachineField(); - DatItemField extraDatItemField = command.Arguments[0].AsDatItemField(); - string extraFile = command.Arguments[1]; - - // If we had an invalid input, log and continue - if (extraMachineField == MachineField.NULL - && extraDatItemField == DatItemField.NULL) - { - logger.User($"{command.Arguments[0]} was an invalid field name"); - return; - } - if (!File.Exists(command.Arguments[1])) - { - logger.User($"{command.Arguments[1]} was an invalid file name"); - return; - } - - // Create the extra INI - ExtraIni extraIni = new ExtraIni(); - ExtraIniItem extraIniItem = new ExtraIniItem - { - MachineField = extraMachineField, - DatItemField = extraDatItemField, - }; - extraIniItem.PopulateFromFile(extraFile); - extraIni.Items.Add(extraIniItem); - - // Apply the extra INI blindly - extraIni.ApplyExtras(batchFile.DatFile); - } - - /// - /// Apply a single filter to the internal DAT - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void ApplyFilter(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count < 2 || command.Arguments.Count > 4) - { - logger.User($"Invoked {command.Name} and expected between 2-4 arguments, but {command.Arguments.Count} arguments were provided"); - logger.User("Usage: filter(field, value, [remove = false, [perMachine = false]]);"); - return; - } - - // Read in the individual arguments - DatHeaderField filterDatHeaderField = command.Arguments[0].AsDatHeaderField(); - MachineField filterMachineField = command.Arguments[0].AsMachineField(); - DatItemField filterDatItemField = command.Arguments[0].AsDatItemField(); - string filterValue = command.Arguments[1]; - bool? filterRemove = false; - if (command.Arguments.Count >= 3) - filterRemove = command.Arguments[2].AsYesNo(); - bool? filterPerMachine = false; - if (command.Arguments.Count >= 4) - filterPerMachine = command.Arguments[3].AsYesNo(); - - // If we had an invalid input, log and continue - if (filterDatHeaderField == DatHeaderField.NULL - && filterMachineField == MachineField.NULL - && filterDatItemField == DatItemField.NULL) - { - logger.User($"{command.Arguments[0]} was an invalid field name"); - return; - } - if (filterRemove == null) - { - logger.User($"{command.Arguments[2]} was an invalid true/false value"); - return; - } - if (filterPerMachine == null) - { - logger.User($"{command.Arguments[3]} was an invalid true/false value"); - return; - } - - // Create filter to run filters from - Filter filter = new Filter - { - MachineFilter = new MachineFilter { HasFilters = true }, - DatItemFilter = new DatItemFilter { HasFilters = true }, - }; - - // Set the possible filters - filter.MachineFilter.SetFilter(filterMachineField, filterValue, filterRemove.Value); - filter.DatItemFilter.SetFilter(filterDatItemField, filterValue, filterRemove.Value); - - // Apply the filters blindly - filter.ApplyFilters(batchFile.DatFile, filterPerMachine.Value); - - // Cleanup after the filter - // TODO: We might not want to remove immediately - batchFile.DatFile.Items.ClearMarked(); - batchFile.DatFile.Items.ClearEmpty(); - } - - /// - /// Apply a description-as-name to the internal DAT - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void DescriptionAsName(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count != 0) - { - logger.User($"Invoked {command.Name} and expected no arguments, but {command.Arguments.Count} arguments were provided"); - logger.User("Usage: descname();"); - return; - } - - // Apply the logic - Cleaner descNameCleaner = new Cleaner { DescriptionAsName = true }; - descNameCleaner.ApplyCleaning(batchFile.DatFile); - } - - /// - /// Apply 1G1R to the internal DAT - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void OneGamePerRegion(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count == 0) - { - logger.User($"Invoked {command.Name} but no arguments were provided"); - logger.User("Usage: 1g1r(region, ...);"); - return; - } - - // Run the 1G1R functionality - Cleaner ogorCleaner = new Cleaner { OneGamePerRegion = true, RegionList = command.Arguments }; - ogorCleaner.ApplyCleaning(batchFile.DatFile); - } - - /// - /// Apply ORPG to the internal DAT - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void OneRomPerGame(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count != 0) - { - logger.User($"Invoked {command.Name} and expected no arguments, but {command.Arguments.Count} arguments were provided"); - logger.User("Usage: orpg();"); - return; - } - - // Apply the logic - Cleaner orpgCleaner = new Cleaner { OneRomPerGame = true }; - orpgCleaner.ApplyCleaning(batchFile.DatFile); - } - - /// - /// Populate the internal DAT from one or more files - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void ParseInputs(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count == 0) - { - logger.User($"Invoked {command.Name} but no arguments were provided"); - logger.User("Usage: input(datpath, ...);"); - return; - } - - // Get only files from inputs - List datFilePaths = PathTool.GetFilesOnly(command.Arguments); - - // Assume there could be multiple - foreach (ParentablePath datFilePath in datFilePaths) - { - Parser.ParseInto(batchFile.DatFile, datFilePath, batchFile.Index++); - } - } - - /// - /// Populate the internal DAT from one or more paths - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void PopulateFromDir(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count == 0) - { - logger.User($"Invoked {command.Name} but no arguments were provided"); - logger.User("Usage: d2d(path, ...);"); - return; - } - - // TODO: Should any of the other options be added for D2D? - - // Assume there could be multiple - foreach (string input in command.Arguments) - { - DatTools.DatFromDir.PopulateFromDir(batchFile.DatFile, input, hashes: Hash.Standard); - } - - // TODO: We might not want to remove dates in the future - Remover dfdRemover = new Remover(); - dfdRemover.PopulateExclusionsFromList(new List { "DatItem.Date" }); - dfdRemover.ApplyRemovals(batchFile.DatFile); - } - - /// - /// Remove a field from the internal DAT - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void RemoveField(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count == 0) - { - logger.User($"Invoked {command.Name} but no arguments were provided"); - logger.User("Usage: remove(field, ...);"); - return; - } - - // Run the removal functionality - Remover remover = new Remover(); - remover.PopulateExclusionsFromList(command.Arguments); - remover.ApplyRemovals(batchFile.DatFile); - } - - /// - /// Remove a field from the internal DAT - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void Reset(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count != 0) - { - logger.User($"Invoked {command.Name} and expected no arguments, but {command.Arguments.Count} arguments were provided"); - logger.User("Usage: reset();"); - return; - } - - // Reset all state variables - batchFile.Reset(); - } - - /// - /// Run internal split/merge on the internal DAT - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void RunMerge(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count != 1) - { - logger.User($"Invoked {command.Name} and expected 1 argument, but {command.Arguments.Count} arguments were provided"); - logger.User("Usage: merge(split|merged|nonmerged|full|device);"); - return; - } - - // Read in the individual arguments - MergingFlag mergingFlag = command.Arguments[0].AsMergingFlag(); - - // If we had an invalid input, log and continue - if (mergingFlag == MergingFlag.None) - { - logger.User($"{command.Arguments[0]} was an invalid merging flag"); - return; - } - - // Apply the merging flag - Filtering.Splitter splitter = new Filtering.Splitter { SplitType = mergingFlag }; - splitter.ApplySplitting(batchFile.DatFile, false); - } - - /// - /// Apply scene date stripping to the internal DAT - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void SceneDateStrip(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count != 0) - { - logger.User($"Invoked {command.Name} and expected no arguments, but {command.Arguments.Count} arguments were provided"); - logger.User("Usage: sds();"); - return; - } - - // Apply the logic - Cleaner stripCleaner = new Cleaner { SceneDateStrip = true }; - stripCleaner.ApplyCleaning(batchFile.DatFile); - } - - /// - /// Set a single header field on the internal DAT - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void SetHeaderField(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count != 2) - { - logger.User($"Invoked {command.Name} but no arguments were provided"); - logger.User("Usage: set(header.field, value);"); - return; - } - - // Read in the individual arguments - DatHeaderField field = command.Arguments[0].AsDatHeaderField(); - string value = command.Arguments[1]; - - // If we had an invalid input, log and continue - if (field == DatHeaderField.NULL) - { - logger.User($"{command.Arguments[0]} was an invalid field name"); - return; - } - - // Set the header field - batchFile.DatFile.Header.SetFields(new Dictionary { [field] = value }); - } - - /// - /// Set output directory - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void SetOutputDirectory(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count != 1) - { - logger.User($"Invoked {command.Name} and expected exactly 1 argument, but {command.Arguments.Count} arguments were provided"); - logger.User("Usage: output(outdir);"); - return; - } - - // Only set the first as the output directory - batchFile.OutputDirectory = command.Arguments[0]; - } - - /// - /// Set output DatFile format - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void SetOutputFormat(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count == 0) - { - logger.User($"Invoked {command.Name} but no arguments were provided"); - logger.User("Usage: format(datformat, ...);"); - return; - } - - // Assume there could be multiple - batchFile.DatFile.Header.DatFormat = 0x00; - foreach (string format in command.Arguments) - { - batchFile.DatFile.Header.DatFormat |= GetDatFormat(format); - } - - // If we had an invalid input, log and continue - if (batchFile.DatFile.Header.DatFormat == 0x00) - { - logger.User($"No valid output format found"); - return; - } - } - - /// - /// Write out the internal DAT - /// - /// BatchCommand representing the line - /// Current BatchFile state - private void Write(BatchCommand command, BatchFile batchFile) - { - if (command.Arguments.Count > 1) - { - logger.User($"Invoked {command.Name} and expected 0-1 arguments, but {command.Arguments.Count} arguments were provided"); - logger.User("Usage: write([overwrite = true]);"); - return; - } - - // Get overwrite value, if possible - bool? overwrite = true; - if (command.Arguments.Count == 1) - overwrite = command.Arguments[0].AsYesNo(); - - // If we had an invalid input, log and continue - if (overwrite == null) - { - logger.User($"{command.Arguments[0]} was an invalid true/false value"); - return; - } - - // Write out the dat with the current state - Writer.Write(batchFile.DatFile, batchFile.OutputDirectory, overwrite: overwrite.Value); - } - - #endregion - - #region Private Helper Classes + #region Commands /// /// Internal representation of a single batch command /// - /// TODO: Should there be individual commands like there's individual features? - /// TODO: Should BatchCommand take care of the branching command values? /// TODO: Should BatchCommand be a part of SabreTools.DatTools? - private class BatchCommand + private abstract class BatchCommand { public string Name { get; private set; } public List Arguments { get; private set; } = new List(); + /// + /// Default constructor for setting arguments + /// + public BatchCommand(List arguments) + { + Arguments = arguments; + } + /// /// Create a command based on parsing a line /// + /// Current line to parse into a command public static BatchCommand Create(string line) { // Empty lines don't count @@ -649,14 +169,730 @@ Reset the internal state: reset();"; .Where(s => !string.IsNullOrWhiteSpace(s)) // TODO: This may interfere with header value replacement .ToList(); - return new BatchCommand { Name = commandName, Arguments = arguments }; + return commandName.ToLowerInvariant() switch + { + "1g1r" => new OneGamePerRegionCommand(arguments), + "d2d" => new DFDCommand(arguments), + "dfd" => new DFDCommand(arguments), + "descname" => new DescriptionAsNameCommand(arguments), + "extra" => new ExtraCommand(arguments), + "filter" => new FilterCommand(arguments), + "format" => new FormatCommand(arguments), + "input" => new InputCommand(arguments), + "merge" => new MergeCommand(arguments), + "orpg" => new OneRomPerGameCommand(arguments), + "output" => new OutputCommand(arguments), + "remove" => new RemoveCommand(arguments), + "reset" => new ResetCommand(arguments), + "sds" => new SceneDateStripCommand(arguments), + "set" => new SetCommand(arguments), + "write" => new WriteCommand(arguments), + _ => null, + }; + } + + /// + /// Return usage string for a given command + /// + public abstract string Usage(); + + /// + /// Validate that a set of arguments are sufficient for a given command + /// + public abstract (bool, string) ValidateArguments(); + + /// + /// Process a batch file state with the current command + /// + /// Current batch file state to work on + public abstract void Process(BatchState batchState); + } + + /// + /// Apply description-as-name logic + /// + private class DescriptionAsNameCommand : BatchCommand + { + /// + public DescriptionAsNameCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "descname();"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count != 0) + { + string message = $"Invoked {Name} and expected no arguments, but {Arguments.Count} arguments were provided"; + return (false, message); + } + + return (true, null); + } + + /// + public override void Process(BatchState batchState) + { + Cleaner descNameCleaner = new Cleaner { DescriptionAsName = true }; + descNameCleaner.ApplyCleaning(batchState.DatFile); + } + } + + /// + /// Run DFD/D2D on path(s) + /// + private class DFDCommand : BatchCommand + { + /// + public DFDCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "d2d(path, ...);"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count == 0) + { + string message = $"Invoked {Name} but no arguments were provided"; + return (false, message); + } + + return (true, null); + } + + /// + /// TODO: Should any of the other options be added for D2D? + public override void Process(BatchState batchState) + { + // Assume there could be multiple + foreach (string input in Arguments) + { + DatTools.DatFromDir.PopulateFromDir(batchState.DatFile, input, hashes: Hash.Standard); + } + + // TODO: We might not want to remove dates in the future + Remover dfdRemover = new Remover(); + dfdRemover.PopulateExclusionsFromList(new List { "DatItem.Date" }); + dfdRemover.ApplyRemovals(batchState.DatFile); } } /// - /// Internal representation of a single batch file + /// Apply an extra INI /// - private class BatchFile + private class ExtraCommand : BatchCommand + { + /// + public ExtraCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "extra(field, inipath);"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count != 2) + { + string message = $"Invoked {Name} and expected 2 arguments, but {Arguments.Count} arguments were provided"; + return (false, message); + } + + // Read in the individual arguments + MachineField extraMachineField = Arguments[0].AsMachineField(); + DatItemField extraDatItemField = Arguments[0].AsDatItemField(); + string extraFile = Arguments[1]; + + // If we had an invalid input, log and continue + if (extraMachineField == MachineField.NULL + && extraDatItemField == DatItemField.NULL) + { + string message = $"{Arguments[0]} was an invalid field name"; + return (false, message); + } + if (!File.Exists(extraFile)) + { + string message = $"{Arguments[1]} was an invalid file name"; + return (false, message); + } + + return (true, null); + } + + /// + public override void Process(BatchState batchState) + { + // Read in the individual arguments + MachineField extraMachineField = Arguments[0].AsMachineField(); + DatItemField extraDatItemField = Arguments[0].AsDatItemField(); + string extraFile = Arguments[1]; + + // Create the extra INI + ExtraIni extraIni = new ExtraIni(); + ExtraIniItem extraIniItem = new ExtraIniItem + { + MachineField = extraMachineField, + DatItemField = extraDatItemField, + }; + extraIniItem.PopulateFromFile(extraFile); + extraIni.Items.Add(extraIniItem); + + // Apply the extra INI blindly + extraIni.ApplyExtras(batchState.DatFile); + } + } + + /// + /// Apply a filter + /// + private class FilterCommand : BatchCommand + { + /// + public FilterCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "filter(field, value, [remove = false, [perMachine = false]]);"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count < 2 || Arguments.Count > 4) + { + string message = $"Invoked {Name} and expected between 2-4 arguments, but {Arguments.Count} arguments were provided"; + return (false, message); + } + + // Read in the individual arguments + DatHeaderField filterDatHeaderField = Arguments[0].AsDatHeaderField(); + MachineField filterMachineField = Arguments[0].AsMachineField(); + DatItemField filterDatItemField = Arguments[0].AsDatItemField(); + bool? filterRemove = false; + if (Arguments.Count >= 3) + filterRemove = Arguments[2].AsYesNo(); + bool? filterPerMachine = false; + if (Arguments.Count >= 4) + filterPerMachine = Arguments[3].AsYesNo(); + + // If we had an invalid input, log and continue + if (filterDatHeaderField == DatHeaderField.NULL + && filterMachineField == MachineField.NULL + && filterDatItemField == DatItemField.NULL) + { + string message = $"{Arguments[0]} was an invalid field name"; + return (false, message); + } + if (filterRemove == null) + { + string message = $"{Arguments[2]} was an invalid true/false value"; + return (false, message); + } + if (filterPerMachine == null) + { + string message = $"{Arguments[3]} was an invalid true/false value"; + return (false, message); + } + + return (true, null); + } + + /// + public override void Process(BatchState batchState) + { + // Read in the individual arguments + MachineField filterMachineField = Arguments[0].AsMachineField(); + DatItemField filterDatItemField = Arguments[0].AsDatItemField(); + string filterValue = Arguments[1]; + bool? filterRemove = false; + if (Arguments.Count >= 3) + filterRemove = Arguments[2].AsYesNo(); + bool? filterPerMachine = false; + if (Arguments.Count >= 4) + filterPerMachine = Arguments[3].AsYesNo(); + + // Create filter to run filters from + Filter filter = new Filter + { + MachineFilter = new MachineFilter { HasFilters = true }, + DatItemFilter = new DatItemFilter { HasFilters = true }, + }; + + // Set the possible filters + filter.MachineFilter.SetFilter(filterMachineField, filterValue, filterRemove.Value); + filter.DatItemFilter.SetFilter(filterDatItemField, filterValue, filterRemove.Value); + + // Apply the filters blindly + filter.ApplyFilters(batchState.DatFile, filterPerMachine.Value); + + // Cleanup after the filter + // TODO: We might not want to remove immediately + batchState.DatFile.Items.ClearMarked(); + batchState.DatFile.Items.ClearEmpty(); + } + } + + /// + /// Set new output format(s) + /// + private class FormatCommand : BatchCommand + { + /// + public FormatCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "format(datformat, ...);"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count == 0) + { + string message = $"Invoked {Name} but no arguments were provided"; + return (false, message); + } + + // Check all inputs to be valid formats + List unmappedFormats = new List(); + foreach (string format in Arguments) + { + if (GetDatFormat(format) == 0x0) + unmappedFormats.Add(format); + } + + // If we had any unmapped formats, return an issue + if (unmappedFormats.Any()) + { + string message = $"The following inputs were invalid formats: {string.Join(", ", unmappedFormats)}"; + return (false, message); + } + + return (true, null); + } + + /// + public override void Process(BatchState batchState) + { + // Assume there could be multiple + batchState.DatFile.Header.DatFormat = 0x00; + foreach (string format in Arguments) + { + batchState.DatFile.Header.DatFormat |= GetDatFormat(format); + } + } + } + + /// + /// Parse in new input file(s) + /// + private class InputCommand : BatchCommand + { + /// + public InputCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "input(datpath, ...);"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count == 0) + { + string message = $"Invoked {Name} but no arguments were provided"; + return (false, message); + } + + return (true, null); + } + + /// + public override void Process(BatchState batchState) + { + // Get only files from inputs + List datFilePaths = PathTool.GetFilesOnly(Arguments); + + // Assume there could be multiple + foreach (ParentablePath datFilePath in datFilePaths) + { + Parser.ParseInto(batchState.DatFile, datFilePath, batchState.Index++); + } + } + } + + /// + /// Apply internal split/merge + /// + private class MergeCommand : BatchCommand + { + /// + public MergeCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "merge(split|merged|nonmerged|full|device);"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count != 1) + { + string message = $"Invoked {Name} and expected 1 argument, but {Arguments.Count} arguments were provided"; + return (false, message); + } + + // Read in the individual arguments + MergingFlag mergingFlag = Arguments[0].AsMergingFlag(); + + // If we had an invalid input, log and continue + if (mergingFlag == MergingFlag.None) + { + string message = $"{Arguments[0]} was an invalid merging flag"; + return (false, message); + } + + return (true, null); + } + + /// + public override void Process(BatchState batchState) + { + // Read in the individual arguments + MergingFlag mergingFlag = Arguments[0].AsMergingFlag(); + + // Apply the merging flag + Filtering.Splitter splitter = new Filtering.Splitter { SplitType = mergingFlag }; + splitter.ApplySplitting(batchState.DatFile, false); + } + } + + /// + /// Apply 1G1R + /// + private class OneGamePerRegionCommand : BatchCommand + { + /// + public OneGamePerRegionCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "1g1r(region, ...);"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count == 0) + { + string message = $"Invoked {Name} but no arguments were provided"; + return (false, message); + } + + return (true, null); + } + + /// + public override void Process(BatchState batchState) + { + Cleaner ogorCleaner = new Cleaner { OneGamePerRegion = true, RegionList = Arguments }; + ogorCleaner.ApplyCleaning(batchState.DatFile); + } + } + + /// + /// Apply one rom per game (ORPG) + /// + private class OneRomPerGameCommand : BatchCommand + { + /// + public OneRomPerGameCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "orpg();"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count == 0) + { + string message = $"Invoked {Name} and expected no arguments, but {Arguments.Count} arguments were provided"; + return (false, message); + } + + return (true, null); + } + + /// + public override void Process(BatchState batchState) + { + Cleaner orpgCleaner = new Cleaner { OneRomPerGame = true }; + orpgCleaner.ApplyCleaning(batchState.DatFile); + } + } + + /// + /// Set output directory + /// + private class OutputCommand : BatchCommand + { + /// + public OutputCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "output(outdir);"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count != 1) + { + string message = $"Invoked {Name} and expected exactly 1 argument, but {Arguments.Count} arguments were provided"; + return (false, message); + } + + return (true, null); + } + + /// + public override void Process(BatchState batchState) + { + batchState.OutputDirectory = Arguments[0]; + } + } + + /// + /// Remove field(s) + /// + private class RemoveCommand : BatchCommand + { + /// + public RemoveCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "remove(field, ...);"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count == 0) + { + string message = $"Invoked {Name} but no arguments were provided"; + return (false, message); + } + + return (true, null); + } + + /// + public override void Process(BatchState batchState) + { + Remover remover = new Remover(); + remover.PopulateExclusionsFromList(Arguments); + remover.ApplyRemovals(batchState.DatFile); + } + } + + /// + /// Reset the internal state + /// + private class ResetCommand : BatchCommand + { + /// + public ResetCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "reset();"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count != 0) + { + string message = $"Invoked {Name} and expected no arguments, but {Arguments.Count} arguments were provided"; + return (false, message); + } + + return (true, null); + } + + /// + public override void Process(BatchState batchState) + { + batchState.Reset(); + } + } + + /// + /// Apply scene date stripping + /// + private class SceneDateStripCommand : BatchCommand + { + /// + public SceneDateStripCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "sds();"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count != 0) + { + string message = $"Invoked {Name} and expected no arguments, but {Arguments.Count} arguments were provided"; + return (false, message); + } + + return (true, null); + } + + /// + public override void Process(BatchState batchState) + { + Cleaner stripCleaner = new Cleaner { SceneDateStrip = true }; + stripCleaner.ApplyCleaning(batchState.DatFile); + } + } + + /// + /// Set a header field + /// + private class SetCommand : BatchCommand + { + /// + public SetCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "set(header.field, value);"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count != 2) + { + string message = $"Invoked {Name} but no arguments were provided"; + return (false, message); + } + + DatHeaderField field = Arguments[0].AsDatHeaderField(); + + // If we had an invalid input, log and continue + if (field == DatHeaderField.NULL) + { + string message = $"{Arguments[0]} was an invalid field name"; + return (false, message); + } + + return (true, null); + } + + /// + public override void Process(BatchState batchState) + { + // Read in the individual arguments + DatHeaderField field = Arguments[0].AsDatHeaderField(); + string value = Arguments[1]; + + // Set the header field + batchState.DatFile.Header.SetFields(new Dictionary { [field] = value }); + } + } + + /// + /// Write out the current DatFile + /// + private class WriteCommand : BatchCommand + { + /// + public WriteCommand(List arguments) : base(arguments) { } + + /// + public override string Usage() + { + return "write([overwrite = true]);"; + } + + /// + public override (bool, string) ValidateArguments() + { + if (Arguments.Count > 1) + { + string message = $"Invoked {Name} and expected 0-1 arguments, but {Arguments.Count} arguments were provided"; + return (false, message); + } + + // Get overwrite value, if possible + bool? overwrite = true; + if (Arguments.Count == 1) + overwrite = Arguments[0].AsYesNo(); + + // If we had an invalid input, log and continue + if (overwrite == null) + { + string message = $"{Arguments[0]} was an invalid true/false value"; + return (false, message); + } + + return (true, null); + } + + /// + public override void Process(BatchState batchState) + { + // Get overwrite value, if possible + bool? overwrite = true; + if (Arguments.Count == 1) + overwrite = Arguments[0].AsYesNo(); + + // Write out the dat with the current state + Writer.Write(batchState.DatFile, batchState.OutputDirectory, overwrite: overwrite.Value); + } + } + + #endregion + + #region Private Helper Classes + + /// + /// Internal representation of a single batch file state + /// + private class BatchState { public DatFile DatFile { get; set; } = DatFile.Create(); public int Index { get; set; } = 0; @@ -672,7 +908,7 @@ Reset the internal state: reset();"; this.OutputDirectory = null; } } - + #endregion } }