diff --git a/SabreTools.Library/DatFiles/ClrMamePro.cs b/SabreTools.Library/DatFiles/ClrMamePro.cs index 110cd526..9e53fd6e 100644 --- a/SabreTools.Library/DatFiles/ClrMamePro.cs +++ b/SabreTools.Library/DatFiles/ClrMamePro.cs @@ -2,13 +2,13 @@ using System.Collections.Generic; using System.IO; using System.Text; -using System.Text.RegularExpressions; using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; using SabreTools.Library.Writers; using NaturalSort; +using SabreTools.Library.Readers; namespace SabreTools.Library.DatFiles { @@ -48,144 +48,148 @@ namespace SabreTools.Library.DatFiles { // Open a file reader Encoding enc = Utilities.GetEncoding(filename); - StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc); + ClrMameProReader cmpr = new ClrMameProReader(Utilities.TryOpenRead(filename), enc); + cmpr.DosCenter = false; - while (!sr.EndOfStream) + while (!cmpr.EndOfStream) { - string line = sr.ReadLine(); + cmpr.ReadNextLine(); - // Comments in CMP DATs start with a # - if (line.Trim().StartsWith("#")) + // Ignore everything not top-level + if (cmpr.RowType != CmpRowType.TopLevel) continue; - // If the line is the header or a game - if (Regex.IsMatch(line, Constants.HeaderPatternCMP)) + // Switch on the top-level name + switch (cmpr.TopLevel.ToLowerInvariant()) { - GroupCollection gc = Regex.Match(line, Constants.HeaderPatternCMP).Groups; - string normalizedValue = gc[1].Value.ToLowerInvariant(); + // Header values + case "clrmamepro": + case "romvault": + ReadHeader(cmpr, keep); + break; - // If we have a known header - if (normalizedValue == "clrmamepro" || normalizedValue == "romvault") - { - ReadHeader(sr, keep); - } - // If we have a known set type - else if (normalizedValue == "set" // Used by the most ancient DATs - || normalizedValue == "game" // Used by most CMP DATs - || normalizedValue == "machine") // Possibly used by MAME CMP DATs - { - ReadSet(sr, false, filename, sysid, srcid, clean, remUnicode); - } - else if (normalizedValue == "resource") // Used by some other DATs to denote a BIOS set - { - ReadSet(sr, true, filename, sysid, srcid, clean, remUnicode); - } + // Sets + case "set": // Used by the most ancient DATs + case "game": // Used by most CMP DATs + case "machine": // Possibly used by MAME CMP DATs + ReadSet(cmpr, false, filename, sysid, srcid, clean, remUnicode); + break; + case "resource": // Used by some other DATs to denote a BIOS set + ReadSet(cmpr, true, filename, sysid, srcid, clean, remUnicode); + break; + + default: + break; } } - sr.Dispose(); + cmpr.Dispose(); } /// /// Read header information /// - /// StreamReader to use to parse the header + /// ClrMameProReader to use to parse the header /// True if full pathnames are to be kept, false otherwise (default) - private void ReadHeader(StreamReader reader, bool keep) + private void ReadHeader(ClrMameProReader cmpr, bool keep) { bool superdat = false; // If there's no subtree to the header, skip it - if (reader == null || reader.EndOfStream) + if (cmpr == null || cmpr.EndOfStream) return; - // Otherwise, add what is possible - string line = reader.ReadLine(); - while (!Regex.IsMatch(line, Constants.EndPatternCMP)) + // While we don't hit an end element or end of stream + while (!cmpr.EndOfStream) { - // We only want elements - if (line.Trim().StartsWith("#")) - { - line = reader.ReadLine(); + cmpr.ReadNextLine(); + + // Ignore comments, internal items, and nothingness + if (cmpr.RowType == CmpRowType.None || cmpr.RowType == CmpRowType.Comment || cmpr.RowType == CmpRowType.Internal) continue; - } - // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) - GroupCollection gc = Regex.Match(line, Constants.ItemPatternCMP).Groups; - string itemval = gc[2].Value.Replace("\"", string.Empty); + // If we reached the end of a section, break + if (cmpr.RowType == CmpRowType.EndTopLevel) + break; - switch (gc[1].Value) + // If the standalone value is null, we skip + if (cmpr.Standalone == null) + continue; + + string itemKey = cmpr.Standalone?.Key.ToLowerInvariant(); + string itemVal = cmpr.Standalone?.Value; + + // For all other cases + switch (itemKey) { case "name": - Name = (string.IsNullOrWhiteSpace(Name) ? itemval : Name); - superdat = superdat || itemval.Contains(" - SuperDAT"); + Name = (string.IsNullOrWhiteSpace(Name) ? itemVal : Name); + superdat = superdat || itemVal.Contains(" - SuperDAT"); if (keep && superdat) Type = (string.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); break; case "description": - Description = (string.IsNullOrWhiteSpace(Description) ? itemval : Description); + Description = (string.IsNullOrWhiteSpace(Description) ? itemVal : Description); break; case "rootdir": - RootDir = (string.IsNullOrWhiteSpace(RootDir) ? itemval : RootDir); + RootDir = (string.IsNullOrWhiteSpace(RootDir) ? itemVal : RootDir); break; case "category": - Category = (string.IsNullOrWhiteSpace(Category) ? itemval : Category); + Category = (string.IsNullOrWhiteSpace(Category) ? itemVal : Category); break; case "version": - Version = (string.IsNullOrWhiteSpace(Version) ? itemval : Version); + Version = (string.IsNullOrWhiteSpace(Version) ? itemVal : Version); break; case "date": - Date = (string.IsNullOrWhiteSpace(Date) ? itemval : Date); + Date = (string.IsNullOrWhiteSpace(Date) ? itemVal : Date); break; case "author": - Author = (string.IsNullOrWhiteSpace(Author) ? itemval : Author); + Author = (string.IsNullOrWhiteSpace(Author) ? itemVal : Author); break; case "email": - Email = (string.IsNullOrWhiteSpace(Email) ? itemval : Email); + Email = (string.IsNullOrWhiteSpace(Email) ? itemVal : Email); break; case "homepage": - Homepage = (string.IsNullOrWhiteSpace(Homepage) ? itemval : Homepage); + Homepage = (string.IsNullOrWhiteSpace(Homepage) ? itemVal : Homepage); break; case "url": - Url = (string.IsNullOrWhiteSpace(Url) ? itemval : Url); + Url = (string.IsNullOrWhiteSpace(Url) ? itemVal : Url); break; case "comment": - Comment = (string.IsNullOrWhiteSpace(Comment) ? itemval : Comment); + Comment = (string.IsNullOrWhiteSpace(Comment) ? itemVal : Comment); break; case "header": - Header = (string.IsNullOrWhiteSpace(Header) ? itemval : Header); + Header = (string.IsNullOrWhiteSpace(Header) ? itemVal : Header); break; case "type": - Type = (string.IsNullOrWhiteSpace(Type) ? itemval : Type); - superdat = superdat || itemval.Contains("SuperDAT"); + Type = (string.IsNullOrWhiteSpace(Type) ? itemVal : Type); + superdat = superdat || itemVal.Contains("SuperDAT"); break; case "forcemerging": if (ForceMerging == ForceMerging.None) - ForceMerging = Utilities.GetForceMerging(itemval); - + ForceMerging = Utilities.GetForceMerging(itemVal); + break; case "forcezipping": if (ForcePacking == ForcePacking.None) - ForcePacking = Utilities.GetForcePacking(itemval); + ForcePacking = Utilities.GetForcePacking(itemVal); break; case "forcepacking": if (ForcePacking == ForcePacking.None) - ForcePacking = Utilities.GetForcePacking(itemval); + ForcePacking = Utilities.GetForcePacking(itemVal); break; } - - line = reader.ReadLine(); } } /// /// Read set information /// - /// StreamReader to use to parse the header + /// ClrMameProReader to use to parse the header /// True if the item is a resource (bios), false otherwise /// Name of the file to be parsed /// System ID for the DAT @@ -193,7 +197,7 @@ namespace SabreTools.Library.DatFiles /// True if game names are sanitized, false otherwise (default) /// True if we should remove non-ASCII characters from output, false otherwise (default) private void ReadSet( - StreamReader reader, + ClrMameProReader cmpr, bool resource, // Standard Dat parsing @@ -213,40 +217,87 @@ namespace SabreTools.Library.DatFiles }; // If there's no subtree to the header, skip it - if (reader == null || reader.EndOfStream) + if (cmpr == null || cmpr.EndOfStream) return; - // Otherwise, add what is possible - string line = reader.ReadLine(); - while (!Regex.IsMatch(line, Constants.EndPatternCMP)) + // While we don't hit an end element or end of stream + while (!cmpr.EndOfStream) { - // We only want elements - if (line.Trim().StartsWith("#")) - { - line = reader.ReadLine(); + cmpr.ReadNextLine(); + + // Ignore comments and nothingness + if (cmpr.RowType == CmpRowType.None || cmpr.RowType == CmpRowType.Comment) continue; + + // If we reached the end of a section, break + if (cmpr.RowType == CmpRowType.EndTopLevel) + break; + + // Handle any standalone items + if (cmpr.RowType == CmpRowType.Standalone && cmpr.Standalone != null) + { + string itemKey = cmpr.Standalone?.Key.ToLowerInvariant(); + string itemVal = cmpr.Standalone?.Value; + + switch (itemKey) + { + case "name": + machine.Name = itemVal; + break; + case "description": + machine.Description = itemVal; + break; + case "year": + machine.Year = itemVal; + break; + case "manufacturer": + machine.Manufacturer = itemVal; + break; + case "cloneof": + machine.CloneOf = itemVal; + break; + case "romof": + machine.RomOf = itemVal; + break; + case "sampleof": + machine.SampleOf = itemVal; + break; + } } - // Item-specific lines have a known pattern - string trimmedline = line.Trim(); - if (trimmedline.StartsWith("archive (") - || trimmedline.StartsWith("biosset (") - || trimmedline.StartsWith("disk (") - || trimmedline.StartsWith("release (") - || trimmedline.StartsWith("rom (") - || (trimmedline.StartsWith("sample") && !trimmedline.StartsWith("sampleof"))) + // Handle any internal items + else if (cmpr.RowType == CmpRowType.Internal + && !string.IsNullOrWhiteSpace(cmpr.InternalName) + && cmpr.Internal != null) { containsItems = true; - ItemType temptype = ItemType.Rom; - if (trimmedline.StartsWith("rom (")) - temptype = ItemType.Rom; - else if (trimmedline.StartsWith("disk (")) - temptype = ItemType.Disk; - else if (trimmedline.StartsWith("sample")) - temptype = ItemType.Sample; + string itemKey = cmpr.InternalName; + + ItemType itemType = ItemType.Rom; + switch (itemKey) + { + case "archive": + itemType = ItemType.Archive; + break; + case "biosset": + itemType = ItemType.BiosSet; + break; + case "disk": + itemType = ItemType.Disk; + break; + case "release": + itemType = ItemType.Release; + break; + case "rom": + itemType = ItemType.Rom; + break; + case "sample": + itemType = ItemType.Sample; + break; + } // Create the proper DatItem based on the type - DatItem item = Utilities.GetDatItem(temptype); + DatItem item = Utilities.GetDatItem(itemType); // Then populate it with information item.CopyMachineInformation(machine); @@ -255,55 +306,27 @@ namespace SabreTools.Library.DatFiles item.System = filename; item.SourceID = srcid; - // If we have a sample, treat it special - if (temptype == ItemType.Sample) + // Loop through all of the attributes + foreach (var kvp in cmpr.Internal) { - line = line.Trim().Remove(0, 6).Trim().Replace("\"", string.Empty); // Remove "sample" from the input string - item.Name = line; + string attrKey = kvp.Key; + string attrVal = kvp.Value; - // Now process and add the sample - ParseAddHelper(item, clean, remUnicode); - line = reader.ReadLine(); - continue; - } - - // Get the line split by spaces and quotes - string[] linegc = Utilities.SplitLineAsCMP(line); - - // Loop over all attributes normally and add them if possible - for (int i = 0; i < linegc.Length; i++) - { - // Look at the current item and use it if possible - string quoteless = linegc[i].Replace("\"", string.Empty); - switch (quoteless) + switch (attrKey) { //If the item is empty, we automatically skip it because it's a fluke case "": continue; - // Special cases for standalone item statuses - case "baddump": - case "good": - case "nodump": - case "verified": - ItemStatus tempStandaloneStatus = Utilities.GetItemStatus(quoteless); - if (item.ItemType == ItemType.Rom) - ((Rom)item).ItemStatus = tempStandaloneStatus; - else if (item.ItemType == ItemType.Disk) - ((Disk)item).ItemStatus = tempStandaloneStatus; - - break; - // Regular attributes case "name": - quoteless = linegc[++i].Replace("\"", string.Empty); - item.Name = quoteless; + item.Name = attrVal; break; + case "size": if (item.ItemType == ItemType.Rom) { - quoteless = linegc[++i].Replace("\"", string.Empty); - if (Int64.TryParse(quoteless, out long size)) + if (Int64.TryParse(attrVal, out long size)) ((Rom)item).Size = size; else ((Rom)item).Size = -1; @@ -312,95 +335,53 @@ namespace SabreTools.Library.DatFiles break; case "crc": if (item.ItemType == ItemType.Rom) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Rom)item).CRC = Utilities.CleanHashData(quoteless, Constants.CRCLength); - } + (item as Rom).CRC = Utilities.CleanHashData(attrVal, Constants.CRCLength); break; case "md5": if (item.ItemType == ItemType.Rom) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Rom)item).MD5 = Utilities.CleanHashData(quoteless, Constants.MD5Length); - } + (item as Rom).MD5 = Utilities.CleanHashData(attrVal, Constants.MD5Length); else if (item.ItemType == ItemType.Disk) - { - i++; - quoteless = linegc[i].Replace("\"", string.Empty); - ((Disk)item).MD5 = Utilities.CleanHashData(quoteless, Constants.MD5Length); - } + ((Disk)item).MD5 = Utilities.CleanHashData(attrVal, Constants.MD5Length); break; case "ripemd160": if (item.ItemType == ItemType.Rom) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Rom)item).RIPEMD160 = Utilities.CleanHashData(quoteless, Constants.RIPEMD160Length); - } + (item as Rom).RIPEMD160 = Utilities.CleanHashData(attrVal, Constants.RIPEMD160Length); else if (item.ItemType == ItemType.Disk) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Disk)item).RIPEMD160 = Utilities.CleanHashData(quoteless, Constants.RIPEMD160Length); - } + ((Disk)item).RIPEMD160 = Utilities.CleanHashData(attrVal, Constants.RIPEMD160Length); break; case "sha1": if (item.ItemType == ItemType.Rom) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Rom)item).SHA1 = Utilities.CleanHashData(quoteless, Constants.SHA1Length); - } + (item as Rom).SHA1 = Utilities.CleanHashData(attrVal, Constants.SHA1Length); else if (item.ItemType == ItemType.Disk) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Disk)item).SHA1 = Utilities.CleanHashData(quoteless, Constants.SHA1Length); - } + ((Disk)item).SHA1 = Utilities.CleanHashData(attrVal, Constants.SHA1Length); break; case "sha256": if (item.ItemType == ItemType.Rom) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Rom)item).SHA256 = Utilities.CleanHashData(quoteless, Constants.SHA256Length); - } + ((Rom)item).SHA256 = Utilities.CleanHashData(attrVal, Constants.SHA256Length); else if (item.ItemType == ItemType.Disk) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Disk)item).SHA256 = Utilities.CleanHashData(quoteless, Constants.SHA256Length); - } + ((Disk)item).SHA256 = Utilities.CleanHashData(attrVal, Constants.SHA256Length); break; case "sha384": if (item.ItemType == ItemType.Rom) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Rom)item).SHA384 = Utilities.CleanHashData(quoteless, Constants.SHA384Length); - } + ((Rom)item).SHA384 = Utilities.CleanHashData(attrVal, Constants.SHA384Length); else if (item.ItemType == ItemType.Disk) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Disk)item).SHA384 = Utilities.CleanHashData(quoteless, Constants.SHA384Length); - } + ((Disk)item).SHA384 = Utilities.CleanHashData(attrVal, Constants.SHA384Length); break; case "sha512": if (item.ItemType == ItemType.Rom) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Rom)item).SHA512 = Utilities.CleanHashData(quoteless, Constants.SHA512Length); - } + ((Rom)item).SHA512 = Utilities.CleanHashData(attrVal, Constants.SHA512Length); else if (item.ItemType == ItemType.Disk) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Disk)item).SHA512 = Utilities.CleanHashData(quoteless, Constants.SHA512Length); - } + ((Disk)item).SHA512 = Utilities.CleanHashData(attrVal, Constants.SHA512Length); break; case "status": - case "flags": - quoteless = linegc[++i].Replace("\"", string.Empty); - ItemStatus tempFlagStatus = Utilities.GetItemStatus(quoteless); + ItemStatus tempFlagStatus = Utilities.GetItemStatus(attrVal); if (item.ItemType == ItemType.Rom) ((Rom)item).ItemStatus = tempFlagStatus; else if (item.ItemType == ItemType.Disk) @@ -409,66 +390,31 @@ namespace SabreTools.Library.DatFiles break; case "date": if (item.ItemType == ItemType.Rom) - { - // If we have quotes in the next item, assume only one item - if (linegc[i + 1].Contains("\"")) - quoteless = linegc[++i].Replace("\"", string.Empty); - - // Otherwise, we assume we need to read the next two items - else - quoteless = $"{linegc[++i].Replace("\"", string.Empty)} {linegc[++i].Replace("\"", string.Empty)}"; - - ((Rom)item).Date = quoteless; - } + ((Rom)item).Date = attrVal; else if (item.ItemType == ItemType.Release) - { - // If we have quotes in the next item, assume only one item - if (linegc[i + 1].Contains("\"")) - quoteless = linegc[++i].Replace("\"", string.Empty); - - // Otherwise, we assume we need to read the next two items - else - quoteless = $"{linegc[++i].Replace("\"", string.Empty)} {linegc[++i].Replace("\"", string.Empty)}"; - - ((Release)item).Date = quoteless; - } + ((Release)item).Date = attrVal; break; case "default": if (item.ItemType == ItemType.BiosSet) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((BiosSet)item).Default = Utilities.GetYesNo(quoteless.ToLowerInvariant()); - } + ((BiosSet)item).Default = Utilities.GetYesNo(attrVal.ToLowerInvariant()); else if (item.ItemType == ItemType.Release) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Release)item).Default = Utilities.GetYesNo(quoteless.ToLowerInvariant()); - } + ((Release)item).Default = Utilities.GetYesNo(attrVal.ToLowerInvariant()); break; case "description": if (item.ItemType == ItemType.BiosSet) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((BiosSet)item).Description = quoteless.ToLowerInvariant(); - } + ((BiosSet)item).Description = attrVal.ToLowerInvariant(); break; case "region": if (item.ItemType == ItemType.Release) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Release)item).Region = quoteless.ToLowerInvariant(); - } + ((Release)item).Region = attrVal.ToLowerInvariant(); break; case "language": if (item.ItemType == ItemType.Release) - { - quoteless = linegc[++i].Replace("\"", string.Empty); - ((Release)item).Language = quoteless.ToLowerInvariant(); - } + ((Release)item).Language = attrVal.ToLowerInvariant(); break; } @@ -476,42 +422,7 @@ namespace SabreTools.Library.DatFiles // Now process and add the rom ParseAddHelper(item, clean, remUnicode); - - line = reader.ReadLine(); - continue; } - - // Set-specific lines have a known pattern - GroupCollection setgc = Regex.Match(line, Constants.ItemPatternCMP).Groups; - string itemval = setgc[2].Value.Replace("\"", string.Empty); - - switch (setgc[1].Value) - { - case "name": - machine.Name = (itemval.ToLowerInvariant().EndsWith(".zip") ? itemval.Remove(itemval.Length - 4) : itemval); - machine.Description = (itemval.ToLowerInvariant().EndsWith(".zip") ? itemval.Remove(itemval.Length - 4) : itemval); - break; - case "description": - machine.Description = itemval; - break; - case "year": - machine.Year = itemval; - break; - case "manufacturer": - machine.Manufacturer = itemval; - break; - case "cloneof": - machine.CloneOf = itemval; - break; - case "romof": - machine.RomOf = itemval; - break; - case "sampleof": - machine.SampleOf = itemval; - break; - } - - line = reader.ReadLine(); } // If no items were found for this machine, add a Blank placeholder diff --git a/SabreTools.Library/DatFiles/DosCenter.cs b/SabreTools.Library/DatFiles/DosCenter.cs index b5a46731..3eaae0ae 100644 --- a/SabreTools.Library/DatFiles/DosCenter.cs +++ b/SabreTools.Library/DatFiles/DosCenter.cs @@ -7,6 +7,7 @@ using System.Text.RegularExpressions; using SabreTools.Library.Data; using SabreTools.Library.DatItems; +using SabreTools.Library.Readers; using SabreTools.Library.Tools; using SabreTools.Library.Writers; using NaturalSort; @@ -49,98 +50,107 @@ namespace SabreTools.Library.DatFiles { // Open a file reader Encoding enc = Utilities.GetEncoding(filename); - StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc); + ClrMameProReader cmpr = new ClrMameProReader(Utilities.TryOpenRead(filename), enc); + cmpr.DosCenter = true; - while (!sr.EndOfStream) + while (!cmpr.EndOfStream) { - string line = sr.ReadLine(); + cmpr.ReadNextLine(); - // If the line is the header or a game - if (Regex.IsMatch(line, Constants.HeaderPatternCMP)) + // Ignore everything not top-level + if (cmpr.RowType != CmpRowType.TopLevel) + continue; + + // Switch on the top-level name + switch (cmpr.TopLevel.ToLowerInvariant()) { - GroupCollection gc = Regex.Match(line, Constants.HeaderPatternCMP).Groups; - string normalizedValue = gc[1].Value.ToLowerInvariant(); + // Header values + case "doscenter": + ReadHeader(cmpr); + break; - // If we have a known header - if (normalizedValue == "doscenter") - ReadHeader(sr, keep); + // Sets + case "game": + ReadGame(cmpr, filename, sysid, srcid, clean, remUnicode); + break; - // If we have a game - else if (normalizedValue == "game" ) - ReadGame(sr, filename, sysid, srcid, clean, remUnicode); + default: + break; } } - sr.Dispose(); + cmpr.Dispose(); } /// /// Read header information /// - /// StreamReader to use to parse the header - /// True if full pathnames are to be kept, false otherwise (default) - private void ReadHeader(StreamReader reader, bool keep) + /// ClrMameProReader to use to parse the header + private void ReadHeader(ClrMameProReader cmpr) { // If there's no subtree to the header, skip it - if (reader == null || reader.EndOfStream) + if (cmpr == null || cmpr.EndOfStream) return; - // Otherwise, add what is possible - string line = reader.ReadLine(); - while (!Regex.IsMatch(line, Constants.EndPatternCMP)) + // While we don't hit an end element or end of stream + while (!cmpr.EndOfStream) { - // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) - GroupCollection gc = Regex.Match(line, Constants.ItemPatternCMP).Groups; - string itemval = gc[2].Value.Replace("\"", string.Empty); + cmpr.ReadNextLine(); - // Some dats don't have the space between "Name:" and the dat name - if (line.Trim().StartsWith("Name:")) - { - Name = (string.IsNullOrWhiteSpace(Name) ? line.Substring("Name:".Length).Trim() : Name); - line = reader.ReadLine(); + // Ignore comments, internal items, and nothingness + if (cmpr.RowType == CmpRowType.None || cmpr.RowType == CmpRowType.Comment || cmpr.RowType == CmpRowType.Internal) continue; - } - switch (gc[1].Value) + // If we reached the end of a section, break + if (cmpr.RowType == CmpRowType.EndTopLevel) + break; + + // If the standalone value is null, we skip + if (cmpr.Standalone == null) + continue; + + string itemKey = cmpr.Standalone?.Key.ToLowerInvariant().TrimEnd(':'); + string itemVal = cmpr.Standalone?.Value; + + // For all other cases + switch (itemKey) { - case "Name:": - Name = (string.IsNullOrWhiteSpace(Name) ? itemval : Name); + case "name": + Name = (string.IsNullOrWhiteSpace(Name) ? itemVal : Name); break; - case "Description:": - Description = (string.IsNullOrWhiteSpace(Description) ? itemval : Description); + case "description": + Description = (string.IsNullOrWhiteSpace(Description) ? itemVal : Description); break; - case "Version:": - Version = (string.IsNullOrWhiteSpace(Version) ? itemval : Version); + case "dersion": + Version = (string.IsNullOrWhiteSpace(Version) ? itemVal : Version); break; - case "Date:": - Date = (string.IsNullOrWhiteSpace(Date) ? itemval : Date); + case "date": + Date = (string.IsNullOrWhiteSpace(Date) ? itemVal : Date); break; - case "Author:": - Author = (string.IsNullOrWhiteSpace(Author) ? itemval : Author); + case "author": + Author = (string.IsNullOrWhiteSpace(Author) ? itemVal : Author); break; - case "Homepage:": - Homepage = (string.IsNullOrWhiteSpace(Homepage) ? itemval : Homepage); + case "homepage": + Homepage = (string.IsNullOrWhiteSpace(Homepage) ? itemVal : Homepage); break; - case "Comment:": - Comment = (string.IsNullOrWhiteSpace(Comment) ? itemval : Comment); + case "comment": + Comment = (string.IsNullOrWhiteSpace(Comment) ? itemVal : Comment); break; } - - line = reader.ReadLine(); } } /// /// Read set information /// - /// StreamReader to use to parse the header + /// ClrMameProReader to use to parse the header /// Name of the file to be parsed /// System ID for the DAT /// Source ID for the DAT /// True if game names are sanitized, false otherwise (default) /// True if we should remove non-ASCII characters from output, false otherwise (default) private void ReadGame( - StreamReader reader, + ClrMameProReader cmpr, // Standard Dat parsing string filename, @@ -159,22 +169,46 @@ namespace SabreTools.Library.DatFiles }; // If there's no subtree to the header, skip it - if (reader == null || reader.EndOfStream) + if (cmpr == null || cmpr.EndOfStream) return; - // Otherwise, add what is possible - string line = reader.ReadLine(); - while (!Regex.IsMatch(line, Constants.EndPatternCMP)) + // While we don't hit an end element or end of stream + while (!cmpr.EndOfStream) { - // Item-specific lines have a known pattern - string trimmedline = line.Trim(); - if (trimmedline.StartsWith("file (")) + cmpr.ReadNextLine(); + + // Ignore comments and nothingness + if (cmpr.RowType == CmpRowType.None || cmpr.RowType == CmpRowType.Comment) + continue; + + // If we reached the end of a section, break + if (cmpr.RowType == CmpRowType.EndTopLevel) + break; + + // Handle any standalone items + if (cmpr.RowType == CmpRowType.Standalone && cmpr.Standalone != null) + { + string itemKey = cmpr.Standalone?.Key.ToLowerInvariant(); + string itemVal = cmpr.Standalone?.Value; + + switch (itemKey) + { + case "name": + machine.Name = (itemVal.ToLowerInvariant().EndsWith(".zip") ? itemVal.Remove(itemVal.Length - 4) : itemVal); + machine.Description = (itemVal.ToLowerInvariant().EndsWith(".zip") ? itemVal.Remove(itemVal.Length - 4) : itemVal); + break; + } + } + + // Handle any internal items + else if (cmpr.RowType == CmpRowType.Internal + && string.Equals(cmpr.InternalName, "file", StringComparison.OrdinalIgnoreCase) + && cmpr.Internal != null) { containsItems = true; - ItemType temptype = ItemType.Rom; // Create the proper DatItem based on the type - DatItem item = Utilities.GetDatItem(temptype); + Rom item = Utilities.GetDatItem(ItemType.Rom) as Rom; // Then populate it with information item.CopyMachineInformation(machine); @@ -183,71 +217,43 @@ namespace SabreTools.Library.DatFiles item.System = filename; item.SourceID = srcid; - // Get the line split by spaces and quotes - string[] linegc = Utilities.SplitLineAsCMP(line); - - // Loop over the specifics - for (int i = 0; i < linegc.Length; i++) + // Loop through all of the attributes + foreach (var kvp in cmpr.Internal) { - // Names are not quoted, for some stupid reason - if (linegc[i] == "name") + string attrKey = kvp.Key; + string attrVal = kvp.Value; + + switch (attrKey) { - // Get the name in order until we find the next flag - while (++i < linegc.Length - && linegc[i] != "size" - && linegc[i] != "date" - && linegc[i] != "crc") - { - item.Name += $"{linegc[i]}"; - } + //If the item is empty, we automatically skip it because it's a fluke + case "": + continue; - // Perform correction - item.Name = item.Name.TrimStart(); - i--; - } + // Regular attributes + case "name": + item.Name = attrVal; + break; - // Get the size from the next part - else if (linegc[i] == "size") - { - if (!Int64.TryParse(linegc[++i], out long tempsize)) - tempsize = 0; + case "size": + if (Int64.TryParse(attrVal, out long size)) + item.Size = size; + else + item.Size = -1; - ((Rom)item).Size = tempsize; - } + break; - // Get the date from the next part - else if (linegc[i] == "date") - { - ((Rom)item).Date = $"{linegc[++i].Replace("\"", string.Empty)} {linegc[++i].Replace("\"", string.Empty)}"; - } - - // Get the CRC from the next part - else if (linegc[i] == "crc") - { - ((Rom)item).CRC = linegc[++i].Replace("\"", string.Empty).ToLowerInvariant(); + case "crc": + item.CRC = Utilities.CleanHashData(attrVal, Constants.CRCLength); + break; + case "date": + item.Date = attrVal; + break; } } // Now process and add the rom ParseAddHelper(item, clean, remUnicode); - - line = reader.ReadLine(); - continue; } - - // Game-specific lines have a known pattern - GroupCollection setgc = Regex.Match(line, Constants.ItemPatternCMP).Groups; - string itemval = setgc[2].Value.Replace("\"", string.Empty); - - switch (setgc[1].Value) - { - case "name": - machine.Name = (itemval.ToLowerInvariant().EndsWith(".zip") ? itemval.Remove(itemval.Length - 4) : itemval); - machine.Description = (itemval.ToLowerInvariant().EndsWith(".zip") ? itemval.Remove(itemval.Length - 4) : itemval); - break; - } - - line = reader.ReadLine(); } // If no items were found for this machine, add a Blank placeholder diff --git a/SabreTools.Library/Data/Enums.cs b/SabreTools.Library/Data/Enums.cs index 49d2b73a..b5364e5b 100644 --- a/SabreTools.Library/Data/Enums.cs +++ b/SabreTools.Library/Data/Enums.cs @@ -450,6 +450,7 @@ Standalone, Internal, Comment, + EndTopLevel, } /// diff --git a/SabreTools.Library/Readers/ClrMameProReader.cs b/SabreTools.Library/Readers/ClrMameProReader.cs index e4134d3c..926d623f 100644 --- a/SabreTools.Library/Readers/ClrMameProReader.cs +++ b/SabreTools.Library/Readers/ClrMameProReader.cs @@ -158,6 +158,11 @@ namespace SabreTools.Library.Readers { value = $"{linegc[++i].Replace("\"", string.Empty)} {linegc[++i].Replace("\"", string.Empty)}"; } + // Default case + else + { + value = linegc[++i].Replace("\"", string.Empty); + } } else { @@ -167,6 +172,13 @@ namespace SabreTools.Library.Readers value = key; key = "status"; } + // Special case for standalone sample + else if (normalizedValue == "sample") + { + value = key; + key = "name"; + } + // Default case else { value = linegc[++i].Replace("\"", string.Empty); @@ -198,7 +210,7 @@ namespace SabreTools.Library.Readers { Internal = null; InternalName = null; - RowType = CmpRowType.None; + RowType = CmpRowType.EndTopLevel; Standalone = null; TopLevel = null; }