diff --git a/SabreTools.Library/DatFiles/DatFile.cs b/SabreTools.Library/DatFiles/DatFile.cs index 63aa7965..264ca137 100644 --- a/SabreTools.Library/DatFiles/DatFile.cs +++ b/SabreTools.Library/DatFiles/DatFile.cs @@ -1608,7 +1608,7 @@ namespace SabreTools.Library.DatFiles bool bare, bool clean, bool remUnicode, bool descAsName, Filter filter, SplitType splitType, bool trim, bool single, string root) { // If we're in merging or diffing mode, use the full list of inputs - if (merge || (diff != 0 && (diff & DiffMode.Against) == 0)) + if (merge || (diff != 0 && (diff & DiffMode.Against) == 0) && (diff & DiffMode.BaseReplace) == 0) { // Make sure there are no folders in inputs List newInputFileNames = FileTools.GetOnlyFilesFromInputs(inputPaths, appendparent: true); @@ -1644,6 +1644,11 @@ namespace SabreTools.Library.DatFiles { DiffAgainst(inputPaths, basePaths, outDir, inplace, clean, remUnicode, descAsName, filter, splitType, trim, single, root); } + // If we're in "base replacement" mode, we treat the inputs differently + else if ((diff & DiffMode.BaseReplace) != 0) + { + BaseReplace(inputPaths, basePaths, outDir, inplace, clean, remUnicode, descAsName, filter, splitType, trim, single, root); + } // Otherwise, loop through all of the inputs individually else { @@ -1716,6 +1721,122 @@ namespace SabreTools.Library.DatFiles return datHeaders.ToList(); } + /// + /// Replace item names from on a base set + /// + /// Names of the input files and/or folders + /// Names of base files and/or folders + /// Optional param for output directory + /// True if the output files should overwrite their inputs, false otherwise + /// True to clean the game names to WoD standard, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + /// True to allow SL DATs to have game names used instead of descriptions, false otherwise (default) + /// Filter object to be passed to the DatItem level + /// Type of the split that should be performed (split, merged, fully merged) + /// True if we are supposed to trim names to NTFS length, false otherwise + /// True if all games should be replaced by '!', false otherwise + /// String representing root directory to compare against for length calculation + public void BaseReplace(List inputPaths, List basePaths, string outDir, bool inplace, bool clean, bool remUnicode, + bool descAsName, Filter filter, SplitType splitType, bool trim, bool single, string root) + { + // First we want to parse all of the base DATs into the input + InternalStopwatch watch = new InternalStopwatch("Populating base DAT for replacement..."); + + List baseFileNames = FileTools.GetOnlyFilesFromInputs(basePaths); + Parallel.For(0, baseFileNames.Count, Globals.ParallelOptions, i => + { + string path = ""; + int id = 0; + + lock (baseFileNames) + { + path = baseFileNames[i]; + id = baseFileNames.Count - i; // Inverse because larger numbers take precedence + } + + Parse(path, id, id, keep: true, clean: clean, remUnicode: remUnicode, descAsName: descAsName); + }); + + watch.Stop(); + + // For comparison's sake, we want to use CRC as the base ordering + BucketBy(SortedBy.CRC, DedupeType.Full); + + // Now we want to try to replace each item in each input DAT from the base + List inputFileNames = FileTools.GetOnlyFilesFromInputs(inputPaths, appendparent: true); + foreach (string path in inputFileNames) + { + // Get the two halves of the path + string[] splitpath = path.Split('¬'); + + Globals.Logger.User("Replacing items in '{0}'' from the base DAT", splitpath[0]); + + // First we parse in the DAT internally + DatFile intDat = new DatFile(); + intDat.Parse(splitpath[0], 1, 1, keep: true, clean: clean, remUnicode: remUnicode, descAsName: descAsName); + + // For comparison's sake, we want to use CRC as the base ordering + intDat.BucketBy(SortedBy.CRC, DedupeType.Full); + + // Then we do a hashwise comparison against the base DAT + List keys = intDat.Keys.ToList(); + Parallel.ForEach(keys, Globals.ParallelOptions, key => + { + List datItems = intDat[key]; + List newDatItems = new List(); + foreach (DatItem datItem in datItems) + { + List dupes = datItem.GetDuplicates(this); + if (dupes.Count > 0) + { + datItem.Name = dupes[0].Name; + } + + newDatItems.Add(datItem); + } + + // Now add the new list to the key + intDat.Remove(key); + intDat.AddRange(key, newDatItems); + }); + + // Determine the output path for the DAT + string interOutDir = outDir; + if (inplace) + { + interOutDir = Path.GetDirectoryName(path); + } + else if (!String.IsNullOrEmpty(interOutDir)) + { + if (splitpath[0].Length == splitpath[1].Length) + { + interOutDir = Path.GetDirectoryName(Path.Combine(interOutDir, Path.GetFileName(splitpath[0]))); + } + else + { + interOutDir = Path.GetDirectoryName(Path.Combine(interOutDir, splitpath[0].Remove(0, splitpath[1].Length + 1))); + } + } + else + { + if (splitpath[0].Length == splitpath[1].Length) + { + interOutDir = Path.GetDirectoryName(Path.Combine(Environment.CurrentDirectory, Path.GetFileName(splitpath[0]))); + } + else + { + interOutDir = Path.GetDirectoryName(Path.Combine(Environment.CurrentDirectory, splitpath[0].Remove(0, splitpath[1].Length + 1))); + } + } + + // Once we're done, try writing out + intDat.WriteToFile(interOutDir); + + // Due to possible memory requirements, we force a garbage collection + GC.Collect(); + } + } + /// /// Output diffs against a base set /// @@ -1791,11 +1912,25 @@ namespace SabreTools.Library.DatFiles } else if (!String.IsNullOrEmpty(interOutDir)) { - interOutDir = Path.GetDirectoryName(Path.Combine(interOutDir, splitpath[0].Remove(0, splitpath[1].Length + 1))); + if (splitpath[0].Length == splitpath[1].Length) + { + interOutDir = Path.GetDirectoryName(Path.Combine(interOutDir, Path.GetFileName(splitpath[0]))); + } + else + { + interOutDir = Path.GetDirectoryName(Path.Combine(interOutDir, splitpath[0].Remove(0, splitpath[1].Length + 1))); + } } else { - interOutDir = Path.GetDirectoryName(Path.Combine(Environment.CurrentDirectory, splitpath[0].Remove(0, splitpath[1].Length + 1))); + if (splitpath[0].Length == splitpath[1].Length) + { + interOutDir = Path.GetDirectoryName(Path.Combine(Environment.CurrentDirectory, Path.GetFileName(splitpath[0]))); + } + else + { + interOutDir = Path.GetDirectoryName(Path.Combine(Environment.CurrentDirectory, splitpath[0].Remove(0, splitpath[1].Length + 1))); + } } // Once we're done, try writing out diff --git a/SabreTools.Library/Data/Flags.cs b/SabreTools.Library/Data/Flags.cs index a62d58bd..ad66dc27 100644 --- a/SabreTools.Library/Data/Flags.cs +++ b/SabreTools.Library/Data/Flags.cs @@ -240,6 +240,9 @@ namespace SabreTools.Library.Data // Base diffs Against = ReverseCascade << 1, + + // Not technically diffs + BaseReplace = Against << 1, } /// diff --git a/SabreTools.Library/README.1ST b/SabreTools.Library/README.1ST index be7ac9aa..25d579cc 100644 --- a/SabreTools.Library/README.1ST +++ b/SabreTools.Library/README.1ST @@ -1177,6 +1177,17 @@ Options: a second time, this will skip writing it. This can often speed up the output process. + -bn, --base-name Replace matching item names from base DAT + By default, no item names are changed except when there is a merge + occurring. This flag enables users to define a DAT or set of base + DATs to use as "replacement names" for all input DATs. Note that + the first found instance of an item in the base DAT(s) will be + used and all others will be disgarded. + + -bd=, --base-dat= Add a base DAT for replacing + Add a DAT or folder of DATs to the base set to be used in + item name replacement + -gn=, --not-game= Filter by game name -ngn=, --game-name= Exclude by game name -rn=, --rom-name= Filter by rom name diff --git a/SabreTools/SabreTools.Help.cs b/SabreTools/SabreTools.Help.cs index 40a72878..7508aff6 100644 --- a/SabreTools/SabreTools.Help.cs +++ b/SabreTools/SabreTools.Help.cs @@ -1225,6 +1225,16 @@ namespace SabreTools "Don't include the date in automatic name", FeatureType.Flag, null)); + update.AddFeature("base-name", new Feature( + new List() { "-bn", "--base-name" }, + "Replace matching item names from a base DAT", + FeatureType.Flag, + null)); + update["base-name"].AddFeature("base-dat", new Feature( + new List() { "-bd", "--base-dat" }, + "Add a base DAT for replacing", + FeatureType.List, + null)); update.AddFeature("game-name", new Feature( new List() { "-gn", "--game-name" }, "Filter by game name", diff --git a/SabreTools/SabreTools.cs b/SabreTools/SabreTools.cs index fb6fdc23..a3163a1a 100644 --- a/SabreTools/SabreTools.cs +++ b/SabreTools/SabreTools.cs @@ -304,6 +304,10 @@ namespace SabreTools case "--baddump-col": showBaddumpColumn = true; break; + case "-bn": + case "--base-name": + diffMode |= DiffMode.BaseReplace; + break; case "-c": case "--cascade": diffMode |= DiffMode.Cascade;