using System; using System.IO; using System.Text.RegularExpressions; using SabreTools.Core.Tools; using SabreTools.DatFiles; using SabreTools.DatItems; using SabreTools.IO; using SabreTools.Logging; namespace SabreTools.DatTools { /// /// Helper methods for parsing into DatFiles /// public class Parser { #region Logging /// /// Logging object /// private static readonly Logger logger = new Logger(); #endregion /// /// Create a DatFile and parse a file into it /// /// Name of the file to be parsed /// True to only add item statistics while parsing, false otherwise /// True if the error that is thrown should be thrown back to the caller, false otherwise public static DatFile CreateAndParse(string filename, bool statsOnly = false, bool throwOnError = false) { DatFile datFile = DatFile.Create(); ParseInto(datFile, new ParentablePath(filename), statsOnly: statsOnly, throwOnError: throwOnError); return datFile; } /// /// Parse a DAT and return all found games and roms within /// /// Current DatFile object to add to /// Name of the file to be parsed /// Index ID for the DAT /// True if full pathnames are to be kept, false otherwise (default) /// True if original extension should be kept, false otherwise (default) /// True if quotes are assumed in supported types (default), false otherwise /// True to only add item statistics while parsing, false otherwise /// True if the error that is thrown should be thrown back to the caller, false otherwise public static void ParseInto( DatFile datFile, string filename, int indexId = 0, bool keep = false, bool keepext = false, bool quotes = true, bool statsOnly = false, bool throwOnError = false) { ParentablePath path = new ParentablePath(filename.Trim('"')); ParseInto(datFile, path, indexId, keep, keepext, quotes, statsOnly, throwOnError); } /// /// Parse a DAT and return all found games and roms within /// /// Current DatFile object to add to /// Name of the file to be parsed /// Index ID for the DAT /// True if full pathnames are to be kept, false otherwise (default) /// True if original extension should be kept, false otherwise (default) /// True if quotes are assumed in supported types (default), false otherwise /// True to only add item statistics while parsing, false otherwise /// True if the error that is thrown should be thrown back to the caller, false otherwise public static void ParseInto( DatFile datFile, ParentablePath input, int indexId = 0, bool keep = false, bool keepext = false, bool quotes = true, bool statsOnly = false, bool throwOnError = true) { // Get the current path from the filename string currentPath = input.CurrentPath; // Check the file extension first as a safeguard if (!Utilities.HasValidDatExtension(currentPath)) return; // If the output filename isn't set already, get the internal filename datFile.Header.FileName = string.IsNullOrWhiteSpace(datFile.Header.FileName) ? (keepext ? Path.GetFileName(currentPath) : Path.GetFileNameWithoutExtension(currentPath)) : datFile.Header.FileName; // If the output type isn't set already, get the internal output type DatFormat currentPathFormat = GetDatFormat(currentPath); datFile.Header.DatFormat = datFile.Header.DatFormat == 0 ? currentPathFormat : datFile.Header.DatFormat; datFile.Items.SetBucketedBy(ItemKey.CRC); // Setting this because it can reduce issues later InternalStopwatch watch = new InternalStopwatch($"Parsing '{currentPath}' into internal DAT"); // Now parse the correct type of DAT try { var parsingDatFile = DatFile.Create(currentPathFormat, datFile, quotes); parsingDatFile?.ParseFile(currentPath, indexId, keep, statsOnly: statsOnly, throwOnError: throwOnError); } catch (Exception ex) when (!throwOnError) { logger.Error(ex, $"Error with file '{currentPath}'"); } watch.Stop(); } /// /// Get what type of DAT the input file is /// /// Name of the file to be parsed /// The DatFormat corresponding to the DAT private static DatFormat GetDatFormat(string filename) { // Limit the output formats based on extension if (!Utilities.HasValidDatExtension(filename)) return 0; // Get the extension from the filename string ext = filename.GetNormalizedExtension(); // Check if file exists if (!File.Exists(filename)) return 0; // Some formats should only require the extension to know switch (ext) { case "csv": return DatFormat.CSV; case "json": return DatFormat.SabreJSON; case "md5": return DatFormat.RedumpMD5; case "sfv": return DatFormat.RedumpSFV; case "sha1": return DatFormat.RedumpSHA1; case "sha256": return DatFormat.RedumpSHA256; case "sha384": return DatFormat.RedumpSHA384; case "sha512": return DatFormat.RedumpSHA512; case "spamsum": return DatFormat.RedumpSpamSum; case "ssv": return DatFormat.SSV; case "tsv": return DatFormat.TSV; } // For everything else, we need to read it // Get the first two non-whitespace, non-comment lines to check, if possible string first = string.Empty, second = string.Empty; try { using StreamReader sr = File.OpenText(filename); first = FindNextLine(sr); second = FindNextLine(sr); } catch { } // If we have an XML-based DAT if (first.Contains("")) { if (second.StartsWith(" /// Find the next non-whitespace, non-comment line from an input /// /// StreamReader representing the input /// The next complete line, if possible private static string FindNextLine(StreamReader sr) { // If we're at the end of the stream, we can't do anything if (sr.EndOfStream) return string.Empty; // Find the first line that's not whitespace or an XML comment string line = sr.ReadLine().ToLowerInvariant().Trim(); bool inComment = line.StartsWith("")) { inComment = false; line = sr.ReadLine().ToLowerInvariant().Trim(); } // Start of block comments else if (line.StartsWith("")) { line = sr.ReadLine().ToLowerInvariant().Trim(); inComment = line.StartsWith("