diff --git a/SabreTools.Library/DatFiles/DatFile.cs b/SabreTools.Library/DatFiles/DatFile.cs index e5d92771..20aca661 100644 --- a/SabreTools.Library/DatFiles/DatFile.cs +++ b/SabreTools.Library/DatFiles/DatFile.cs @@ -2299,7 +2299,7 @@ namespace SabreTools.Library.DatFiles /// /// Use game descriptions as names in the DAT, updating cloneof/romof/sampleof /// - public void MachineDescriptionToName() + private void MachineDescriptionToName() { try { @@ -2369,7 +2369,7 @@ namespace SabreTools.Library.DatFiles /// /// Strip the given hash types from the DAT /// - public void StripHashesFromItems() + private void StripHashesFromItems() { // Output the logging statement Globals.Logger.User("Stripping requested hashes"); @@ -2443,7 +2443,7 @@ namespace SabreTools.Library.DatFiles #endregion - #region Merging/Splitting Methods + #region Merging/Splitting /// /// Use cdevice_ref tags to get full non-merged sets and remove parenting tags @@ -2559,10 +2559,6 @@ namespace SabreTools.Library.DatFiles RemoveTagsFromChild(); } - #endregion - - #region Merging/Splitting Helper Methods - /// /// Use romof tags to add roms to the children /// @@ -3284,7 +3280,7 @@ namespace SabreTools.Library.DatFiles List files = Directory.EnumerateFiles(basePath, "*", SearchOption.TopDirectoryOnly).ToList(); Parallel.ForEach(files, Globals.ParallelOptions, item => { - PopulateFromDirCheckFile(item, basePath, omitFromScan, bare, archivesAsFiles, enableGzip, skipFileType, + CheckFileForHashes(item, basePath, omitFromScan, bare, archivesAsFiles, enableGzip, skipFileType, addBlanks, addDate, tempDir, copyFiles, headerToCheckAgainst); }); @@ -3295,7 +3291,7 @@ namespace SabreTools.Library.DatFiles List subfiles = Directory.EnumerateFiles(item, "*", SearchOption.AllDirectories).ToList(); Parallel.ForEach(subfiles, Globals.ParallelOptions, subitem => { - PopulateFromDirCheckFile(subitem, basePath, omitFromScan, bare, archivesAsFiles, enableGzip, skipFileType, + CheckFileForHashes(subitem, basePath, omitFromScan, bare, archivesAsFiles, enableGzip, skipFileType, addBlanks, addDate, tempDir, copyFiles, headerToCheckAgainst); }); } @@ -3352,7 +3348,7 @@ namespace SabreTools.Library.DatFiles } else if (File.Exists(basePath)) { - PopulateFromDirCheckFile(basePath, Path.GetDirectoryName(Path.GetDirectoryName(basePath)), omitFromScan, bare, archivesAsFiles, enableGzip, + CheckFileForHashes(basePath, Path.GetDirectoryName(Path.GetDirectoryName(basePath)), omitFromScan, bare, archivesAsFiles, enableGzip, skipFileType, addBlanks, addDate, tempDir, copyFiles, headerToCheckAgainst); } @@ -3381,7 +3377,7 @@ namespace SabreTools.Library.DatFiles /// Name of the directory to create a temp folder in (blank is current directory) /// True if files should be copied to the temp directory before hashing, false otherwise /// Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise - private void PopulateFromDirCheckFile(string item, string basePath, Hash omitFromScan, bool bare, bool archivesAsFiles, + private void CheckFileForHashes(string item, string basePath, Hash omitFromScan, bool bare, bool archivesAsFiles, bool enableGzip, SkipFileType skipFileType, bool addBlanks, bool addDate, string tempDir, bool copyFiles, string headerToCheckAgainst) { // Define the temporary directory @@ -3453,7 +3449,7 @@ namespace SabreTools.Library.DatFiles // If the extracted list is null, just scan the item itself if (extracted == null || archivesAsFiles) { - PopulateFromDirProcessFile(newItem, "", newBasePath, omitFromScan, addDate, headerToCheckAgainst); + ProcessFile(newItem, "", newBasePath, omitFromScan, addDate, headerToCheckAgainst); } // Otherwise, add all of the found items else @@ -3461,7 +3457,7 @@ namespace SabreTools.Library.DatFiles // First take care of the found items Parallel.ForEach(extracted, Globals.ParallelOptions, rom => { - PopulateFromDirProcessFileHelper(newItem, + ProcessFileHelper(newItem, rom, basePath, (Path.GetDirectoryName(Path.GetFullPath(item)) + Path.DirectorySeparatorChar).Remove(0, basePath.Length) + Path.GetFileNameWithoutExtension(item)); @@ -3474,7 +3470,7 @@ namespace SabreTools.Library.DatFiles Parallel.ForEach(empties, Globals.ParallelOptions, empty => { Rom emptyRom = new Rom(Path.Combine(empty, "_"), newItem, omitFromScan); - PopulateFromDirProcessFileHelper(newItem, + ProcessFileHelper(newItem, emptyRom, basePath, (Path.GetDirectoryName(Path.GetFullPath(item)) + Path.DirectorySeparatorChar).Remove(0, basePath.Length) + Path.GetFileNameWithoutExtension(item)); @@ -3501,13 +3497,13 @@ namespace SabreTools.Library.DatFiles /// Hash flag saying what hashes should not be calculated /// True if dates should be archived for all files, false otherwise /// Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise - private void PopulateFromDirProcessFile(string item, string parent, string basePath, Hash omitFromScan, + private void ProcessFile(string item, string parent, string basePath, Hash omitFromScan, bool addDate, string headerToCheckAgainst) { Globals.Logger.Verbose("'{0}' treated like a file", Path.GetFileName(item)); Rom rom = FileTools.GetFileInfo(item, omitFromScan: omitFromScan, date: addDate, header: headerToCheckAgainst); - PopulateFromDirProcessFileHelper(item, rom, basePath, parent); + ProcessFileHelper(item, rom, basePath, parent); } /// @@ -3517,7 +3513,7 @@ namespace SabreTools.Library.DatFiles /// Rom data to be used to write to file /// Path the represents the parent directory /// Parent game to be used - private void PopulateFromDirProcessFileHelper(string item, DatItem datItem, string basepath, string parent) + private void ProcessFileHelper(string item, DatItem datItem, string basepath, string parent) { // If the datItem isn't a Rom or Disk, return if (datItem.Type != ItemType.Rom && datItem.Type != ItemType.Disk) @@ -4659,7 +4655,7 @@ namespace SabreTools.Library.DatFiles /// List of extensions to split on (first DAT) /// List of extensions to split on (second DAT) /// True if split succeeded, false otherwise - public bool SplitByExt(string outDir, string basepath, List extA, List extB) + public bool SplitByExtension(string outDir, string basepath, List extA, List extB) { // Make sure all of the extensions have a dot at the beginning List newExtA = new List(); @@ -5262,32 +5258,6 @@ namespace SabreTools.Library.DatFiles #region Statistics - /// - /// Recalculate the statistics for the Dat - /// - public void RecalculateStats() - { - // Wipe out any stats already there - _datStats.Reset(); - - // If we have a blank Dat in any way, return - if (this == null || Count == 0) - { - return; - } - - // Loop through and add - List keys = Keys.ToList(); - Parallel.ForEach(keys, Globals.ParallelOptions, key => - { - List items = this[key]; - foreach (DatItem item in items) - { - _datStats.AddItem(item); - } - }); - } - /// /// Output the stats for the Dat in a human-readable format /// @@ -5450,6 +5420,32 @@ namespace SabreTools.Library.DatFiles } } + /// + /// Recalculate the statistics for the Dat + /// + private void RecalculateStats() + { + // Wipe out any stats already there + _datStats.Reset(); + + // If we have a blank Dat in any way, return + if (this == null || Count == 0) + { + return; + } + + // Loop through and add + List keys = Keys.ToList(); + Parallel.ForEach(keys, Globals.ParallelOptions, key => + { + List items = this[key]; + foreach (DatItem item in items) + { + _datStats.AddItem(item); + } + }); + } + #endregion #region Writing @@ -5669,62 +5665,46 @@ namespace SabreTools.Library.DatFiles } outDir = Path.GetFullPath(outDir); - // Get the dictionary of desired outputs - Dictionary outputs = OutputStatsGetOutputWriters(statDatFormat, reportName, outDir); + // Get the dictionary of desired output report names + Dictionary outputs = Style.CreateOutStatsNames(outDir, statDatFormat, reportName); - // Make sure we have all files - List> newinputs = new List>(); // item, basepath - Parallel.ForEach(inputs, Globals.ParallelOptions, input => - { - if (File.Exists(input)) - { - lock (newinputs) - { - newinputs.Add(Tuple.Create(Path.GetFullPath(input), Path.GetDirectoryName(Path.GetFullPath(input)))); - } - } - if (Directory.Exists(input)) - { - foreach (string file in Directory.GetFiles(input, "*", SearchOption.AllDirectories)) - { - lock (newinputs) - { - newinputs.Add(Tuple.Create(Path.GetFullPath(file), Path.GetFullPath(input))); - } - } - } - }); - - newinputs = newinputs - .OrderBy(i => Path.GetDirectoryName(i.Item1)) - .ThenBy(i => Path.GetFileName(i.Item1)) + // Make sure we have all files and then order them + List files = FileTools.GetOnlyFilesFromInputs(inputs); + files = files + .OrderBy(i => Path.GetDirectoryName(i)) + .ThenBy(i => Path.GetFileName(i)) .ToList(); + // Create output writers based on filenames + Dictionary writers = new Dictionary(); + foreach (KeyValuePair kvp in outputs) + { + writers.Add(kvp.Key, new StreamWriter(FileTools.TryOpenWrite(kvp.Value))); + } + // Write the header, if any - OutputStatsWriteHeader(outputs, statDatFormat, baddumpCol, nodumpCol); + WriteStatsHeader(writers, statDatFormat, baddumpCol, nodumpCol); // Init all total variables DatStats totalStats = new DatStats(); - long totalGame = 0; // Init directory-level variables string lastdir = null; string basepath = null; DatStats dirStats = new DatStats(); - long dirGame = 0; // Now process each of the input files - foreach (Tuple filename in newinputs) + foreach (string file in files) { // Get the directory for the current file - string thisdir = Path.GetDirectoryName(filename.Item1); - basepath = Path.GetDirectoryName(filename.Item2); + string thisdir = Path.GetDirectoryName(file); + basepath = Path.GetDirectoryName(Path.GetDirectoryName(file)); // If we don't have the first file and the directory has changed, show the previous directory stats and reset if (lastdir != null && thisdir != lastdir) { // Output separator if needed - OutputStatsWriteMidSeparator(outputs, statDatFormat, baddumpCol, nodumpCol); + WriteStatsMidSeparator(writers, statDatFormat, baddumpCol, nodumpCol); DatFile lastdirdat = new DatFile { @@ -5732,48 +5712,46 @@ namespace SabreTools.Library.DatFiles _datStats = dirStats, }; - lastdirdat.OutputStats(outputs, statDatFormat, - game: dirGame, baddumpCol: baddumpCol, nodumpCol: nodumpCol); + lastdirdat.OutputStats(writers, statDatFormat, game: dirStats.GameCount, baddumpCol: baddumpCol, nodumpCol: nodumpCol); // Write the mid-footer, if any - OutputStatsWriteMidFooter(outputs, statDatFormat, baddumpCol, nodumpCol); + WriteStatsFooterSeparator(writers, statDatFormat, baddumpCol, nodumpCol); // Write the header, if any - OutputStatsWriteMidHeader(outputs, statDatFormat, baddumpCol, nodumpCol); + WriteStatsMidHeader(writers, statDatFormat, baddumpCol, nodumpCol); // Reset the directory stats dirStats.Reset(); - dirGame = 0; } - Globals.Logger.Verbose("Beginning stat collection for '{0}'", false, filename.Item1); + Globals.Logger.Verbose("Beginning stat collection for '{0}'", false, file); List games = new List(); DatFile datdata = new DatFile(); - datdata.Parse(filename.Item1, 0, 0); + datdata.Parse(file, 0, 0); datdata.BucketBy(SortedBy.Game, DedupeType.None, norename: true); // Output single DAT stats (if asked) - Globals.Logger.User("Adding stats for file '{0}'\n", false, filename.Item1); + Globals.Logger.User("Adding stats for file '{0}'\n", false, file); if (single) { - datdata.OutputStats(outputs, statDatFormat, + datdata.OutputStats(writers, statDatFormat, baddumpCol: baddumpCol, nodumpCol: nodumpCol); } // Add single DAT stats to dir dirStats.AddStats(datdata._datStats); - dirGame += datdata.Keys.Count(); + dirStats.GameCount += datdata.Keys.Count(); // Add single DAT stats to totals totalStats.AddStats(datdata._datStats); - totalGame += datdata.Keys.Count(); + totalStats.GameCount += datdata.Keys.Count(); // Make sure to assign the new directory lastdir = thisdir; } // Output the directory stats one last time - OutputStatsWriteMidSeparator(outputs, statDatFormat, baddumpCol, nodumpCol); + WriteStatsMidSeparator(writers, statDatFormat, baddumpCol, nodumpCol); if (single) { @@ -5783,19 +5761,17 @@ namespace SabreTools.Library.DatFiles _datStats = dirStats, }; - dirdat.OutputStats(outputs, statDatFormat, - game: dirGame, baddumpCol: baddumpCol, nodumpCol: nodumpCol); + dirdat.OutputStats(writers, statDatFormat, game: dirStats.GameCount, baddumpCol: baddumpCol, nodumpCol: nodumpCol); } // Write the mid-footer, if any - OutputStatsWriteMidFooter(outputs, statDatFormat, baddumpCol, nodumpCol); + WriteStatsFooterSeparator(writers, statDatFormat, baddumpCol, nodumpCol); // Write the header, if any - OutputStatsWriteMidHeader(outputs, statDatFormat, baddumpCol, nodumpCol); + WriteStatsMidHeader(writers, statDatFormat, baddumpCol, nodumpCol); // Reset the directory stats dirStats.Reset(); - dirGame = 0; // Output total DAT stats DatFile totaldata = new DatFile @@ -5804,77 +5780,22 @@ namespace SabreTools.Library.DatFiles _datStats = totalStats, }; - totaldata.OutputStats(outputs, statDatFormat, - game: totalGame, baddumpCol: baddumpCol, nodumpCol: nodumpCol); + totaldata.OutputStats(writers, statDatFormat, game: totalStats.GameCount, baddumpCol: baddumpCol, nodumpCol: nodumpCol); // Output footer if needed - OutputStatsWriteFooter(outputs, statDatFormat); + WriteStatsFooter(writers, statDatFormat); // Flush and dispose of the stream writers foreach (StatDatFormat format in outputs.Keys) { - outputs[format].Flush(); - outputs[format].Dispose(); + writers[format].Flush(); + writers[format].Dispose(); } Globals.Logger.User(@" Please check the log folder if the stats scrolled offscreen", false); } - /// - /// Get the proper extension for the stat output format - /// - /// StatDatFormat to get the extension for - /// Name of the input file to use - /// Output path to use - /// Dictionary of file types to StreamWriters - private static Dictionary OutputStatsGetOutputWriters(StatDatFormat statDatFormat, string reportName, string outDir) - { - Dictionary output = new Dictionary(); - - // First try to create the output directory if we need to - if (!Directory.Exists(outDir)) - { - Directory.CreateDirectory(outDir); - } - - // For each output format, get the appropriate stream writer - if ((statDatFormat & StatDatFormat.None) != 0) - { - reportName = Style.GetFileNameWithoutExtension(reportName) + ".txt"; - reportName = Path.Combine(outDir, reportName); - - // Create the StreamWriter for this file - output.Add(StatDatFormat.None, new StreamWriter(FileTools.TryCreate(reportName))); - } - if ((statDatFormat & StatDatFormat.CSV) != 0) - { - reportName = Style.GetFileNameWithoutExtension(reportName) + ".csv"; - reportName = Path.Combine(outDir, reportName); - - // Create the StreamWriter for this file - output.Add(StatDatFormat.CSV, new StreamWriter(FileTools.TryCreate(reportName))); - } - if ((statDatFormat & StatDatFormat.HTML) != 0) - { - reportName = Style.GetFileNameWithoutExtension(reportName) + ".html"; - reportName = Path.Combine(outDir, reportName); - - // Create the StreamWriter for this file - output.Add(StatDatFormat.HTML, new StreamWriter(FileTools.TryCreate(reportName))); - } - if ((statDatFormat & StatDatFormat.TSV) != 0) - { - reportName = Style.GetFileNameWithoutExtension(reportName) + ".csv"; - reportName = Path.Combine(outDir, reportName); - - // Create the StreamWriter for this file - output.Add(StatDatFormat.TSV, new StreamWriter(FileTools.TryCreate(reportName))); - } - - return output; - } - /// /// Write out the header to the stream, if any exists /// @@ -5882,7 +5803,7 @@ Please check the log folder if the stats scrolled offscreen", false); /// StatDatFormat representing output format /// True if baddumps should be included in output, false otherwise /// True if nodumps should be included in output, false otherwise - private static void OutputStatsWriteHeader(Dictionary outputs, StatDatFormat statDatFormat, bool baddumpCol, bool nodumpCol) + private static void WriteStatsHeader(Dictionary outputs, StatDatFormat statDatFormat, bool baddumpCol, bool nodumpCol) { if (outputs.ContainsKey(StatDatFormat.None)) { @@ -5923,7 +5844,7 @@ Please check the log folder if the stats scrolled offscreen", false); } // Now write the mid header for those who need it - OutputStatsWriteMidHeader(outputs, statDatFormat, baddumpCol, nodumpCol); + WriteStatsMidHeader(outputs, statDatFormat, baddumpCol, nodumpCol); } /// @@ -5933,7 +5854,7 @@ Please check the log folder if the stats scrolled offscreen", false); /// StatDatFormat representing output format /// True if baddumps should be included in output, false otherwise /// True if nodumps should be included in output, false otherwise - private static void OutputStatsWriteMidHeader(Dictionary outputs, StatDatFormat statDatFormat, bool baddumpCol, bool nodumpCol) + private static void WriteStatsMidHeader(Dictionary outputs, StatDatFormat statDatFormat, bool baddumpCol, bool nodumpCol) { if (outputs.ContainsKey(StatDatFormat.None)) { @@ -5962,7 +5883,7 @@ Please check the log folder if the stats scrolled offscreen", false); /// StatDatFormat representing output format /// True if baddumps should be included in output, false otherwise /// True if nodumps should be included in output, false otherwise - private static void OutputStatsWriteMidSeparator(Dictionary outputs, StatDatFormat statDatFormat, bool baddumpCol, bool nodumpCol) + private static void WriteStatsMidSeparator(Dictionary outputs, StatDatFormat statDatFormat, bool baddumpCol, bool nodumpCol) { if (outputs.ContainsKey(StatDatFormat.None)) { @@ -5996,7 +5917,7 @@ Please check the log folder if the stats scrolled offscreen", false); /// StatDatFormat representing output format /// True if baddumps should be included in output, false otherwise /// True if nodumps should be included in output, false otherwise - private static void OutputStatsWriteMidFooter(Dictionary outputs, StatDatFormat statDatFormat, bool baddumpCol, bool nodumpCol) + private static void WriteStatsFooterSeparator(Dictionary outputs, StatDatFormat statDatFormat, bool baddumpCol, bool nodumpCol) { if (outputs.ContainsKey(StatDatFormat.None)) { @@ -6028,7 +5949,7 @@ Please check the log folder if the stats scrolled offscreen", false); /// /// StreamWriter representing the output /// StatDatFormat representing output format - private static void OutputStatsWriteFooter(Dictionary outputs, StatDatFormat statDatFormat) + private static void WriteStatsFooter(Dictionary outputs, StatDatFormat statDatFormat) { if (outputs.ContainsKey(StatDatFormat.None)) { diff --git a/SabreTools.Library/DatFiles/DatStats.cs b/SabreTools.Library/DatFiles/DatStats.cs index 340166cf..90947809 100644 --- a/SabreTools.Library/DatFiles/DatStats.cs +++ b/SabreTools.Library/DatFiles/DatStats.cs @@ -26,6 +26,9 @@ namespace SabreTools.Library.DatFiles private long _romCount = 0; private long _sampleCount = 0; + // Special count only used by statistics output + private long _gameCount = 0; + // Total reported size private long _totalSize = 0; @@ -86,6 +89,13 @@ namespace SabreTools.Library.DatFiles set { _sampleCount = value; } } + // Special count only used by statistics output + public long GameCount + { + get { return _gameCount; } + set { _gameCount = value; } + } + // Total reported size public long TotalSize { @@ -231,6 +241,8 @@ namespace SabreTools.Library.DatFiles _romCount += stats.RomCount; _sampleCount += stats.SampleCount; + _gameCount += stats.GameCount; + _totalSize += stats.TotalSize; // Individual hash counts @@ -327,6 +339,8 @@ namespace SabreTools.Library.DatFiles _romCount = 0; _sampleCount = 0; + _gameCount = 0; + _totalSize = 0; _crcCount = 0; diff --git a/SabreTools.Library/Tools/Style.cs b/SabreTools.Library/Tools/Style.cs index e32d2491..a1718040 100644 --- a/SabreTools.Library/Tools/Style.cs +++ b/SabreTools.Library/Tools/Style.cs @@ -323,6 +323,74 @@ namespace SabreTools.Library.Tools return outfile; } + /// + /// Get the proper extension for the stat output format + /// + /// Output path to use + /// StatDatFormat to get the extension for + /// Name of the input file to use + /// Dictionary of output formats mapped to file names + public static Dictionary CreateOutStatsNames(string outDir, StatDatFormat statDatFormat, string reportName, bool overwrite = true) + { + Dictionary output = new Dictionary(); + + // First try to create the output directory if we need to + if (!Directory.Exists(outDir)) + { + Directory.CreateDirectory(outDir); + } + + // For each output format, get the appropriate stream writer + if ((statDatFormat & StatDatFormat.None) != 0) + { + output.Add(StatDatFormat.None, CreateOutStatsNamesHelper(outDir, ".txt", reportName, overwrite)); + } + if ((statDatFormat & StatDatFormat.CSV) != 0) + { + output.Add(StatDatFormat.CSV, CreateOutStatsNamesHelper(outDir, ".csv", reportName, overwrite)); + } + if ((statDatFormat & StatDatFormat.HTML) != 0) + { + output.Add(StatDatFormat.HTML, CreateOutStatsNamesHelper(outDir, ".html", reportName, overwrite)); + } + if ((statDatFormat & StatDatFormat.TSV) != 0) + { + output.Add(StatDatFormat.TSV, CreateOutStatsNamesHelper(outDir, ".tsv", reportName, overwrite)); + } + + return output; + } + + /// + /// Help generating the outstats name + /// + /// Output directory + /// Extension to use for the file + /// Name of the input file to use + /// True if we ignore existing files, false otherwise + /// String containing the new filename + private static string CreateOutStatsNamesHelper(string outDir, string extension, string reportName, bool overwrite) + { + string outfile = outDir + reportName + extension; + outfile = (outfile.Contains(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString()) ? + outfile.Replace(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString(), Path.DirectorySeparatorChar.ToString()) : + outfile); + if (!overwrite) + { + int i = 1; + while (File.Exists(outfile)) + { + outfile = outDir + reportName + "_" + i + extension; + outfile = (outfile.Contains(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString()) ? + outfile.Replace(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString(), Path.DirectorySeparatorChar.ToString()) : + outfile); + i++; + } + } + + return outfile; + } + #endregion #region String Manipulation diff --git a/SabreTools/SabreTools.Inits.cs b/SabreTools/SabreTools.Inits.cs index a354807c..128b8afd 100644 --- a/SabreTools/SabreTools.Inits.cs +++ b/SabreTools/SabreTools.Inits.cs @@ -191,7 +191,7 @@ namespace SabreTools { DatFile datFile = new DatFile(); datFile.Parse(Path.GetFullPath(input), 0, 0); - datFile.SplitByExt(outDir, Path.GetDirectoryName(input), exta, extb); + datFile.SplitByExtension(outDir, Path.GetDirectoryName(input), exta, extb); } else if (Directory.Exists(input)) { @@ -199,7 +199,7 @@ namespace SabreTools { DatFile datFile = new DatFile(); datFile.Parse(Path.GetFullPath(file), 0, 0); - datFile.SplitByExt(outDir, (input.EndsWith(Path.DirectorySeparatorChar.ToString()) ? input : input + Path.DirectorySeparatorChar), + datFile.SplitByExtension(outDir, (input.EndsWith(Path.DirectorySeparatorChar.ToString()) ? input : input + Path.DirectorySeparatorChar), exta, extb); } }