diff --git a/SabreTools.Library/DatFiles/AttractMode.cs b/SabreTools.Library/DatFiles/AttractMode.cs index 31f76f0b..1b554b25 100644 --- a/SabreTools.Library/DatFiles/AttractMode.cs +++ b/SabreTools.Library/DatFiles/AttractMode.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Text; - using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -19,248 +18,248 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { - /// - /// Represents parsing and writing of an AttractMode DAT - /// - internal class AttractMode : DatFile - { - /// - /// Constructor designed for casting a base DatFile - /// - /// Parent DatFile to copy from - public AttractMode(DatFile datFile) - : base(datFile, cloneHeader: false) - { - } + /// + /// Represents parsing and writing of an AttractMode DAT + /// + internal class AttractMode : DatFile + { + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + public AttractMode(DatFile datFile) + : base(datFile, cloneHeader: false) + { + } - /// - /// Parse an AttractMode DAT and return all found games within - /// - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - public override void ParseFile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, + /// + /// Parse an AttractMode DAT and return all found games within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Open a file reader - Encoding enc = Utilities.GetEncoding(filename); - StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc); + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Open a file reader + Encoding enc = Utilities.GetEncoding(filename); + StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc); - sr.ReadLine(); // Skip the first line since it's the header - while (!sr.EndOfStream) - { - string line = sr.ReadLine(); + sr.ReadLine(); // Skip the first line since it's the header + while (!sr.EndOfStream) + { + string line = sr.ReadLine(); - /* - The gameinfo order is as follows - 0 - game name - 1 - game description - 2 - emulator name (filename) - 3 - cloneof - 4 - year - 5 - manufacturer - 6 - category - 7 - players - 8 - rotation - 9 - control - 10 - status - 11 - displaycount - 12 - displaytype - 13 - alt romname - 14 - alt title - 15 - extra - 16 - buttons - */ + /* + The gameinfo order is as follows + 0 - game name + 1 - game description + 2 - emulator name (filename) + 3 - cloneof + 4 - year + 5 - manufacturer + 6 - category + 7 - players + 8 - rotation + 9 - control + 10 - status + 11 - displaycount + 12 - displaytype + 13 - alt romname + 14 - alt title + 15 - extra + 16 - buttons + */ - string[] gameinfo = line.Split(';'); + string[] gameinfo = line.Split(';'); - Rom rom = new Rom - { - Name = "-", - Size = Constants.SizeZero, - CRC = Constants.CRCZero, - MD5 = Constants.MD5Zero, - SHA1 = Constants.SHA1Zero, - ItemStatus = ItemStatus.None, + Rom rom = new Rom + { + Name = "-", + Size = Constants.SizeZero, + CRC = Constants.CRCZero, + MD5 = Constants.MD5Zero, + SHA1 = Constants.SHA1Zero, + ItemStatus = ItemStatus.None, - MachineName = gameinfo[0], - MachineDescription = gameinfo[1], - CloneOf = gameinfo[3], - Year = gameinfo[4], - Manufacturer = gameinfo[5], - Comment = gameinfo[15], - }; + MachineName = gameinfo[0], + MachineDescription = gameinfo[1], + CloneOf = gameinfo[3], + Year = gameinfo[4], + Manufacturer = gameinfo[5], + Comment = gameinfo[15], + }; - // Now process and add the rom - ParseAddHelper(rom, clean, remUnicode); - } + // Now process and add the rom + ParseAddHelper(rom, clean, remUnicode); + } - sr.Dispose(); - } + sr.Dispose(); + } - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if the DAT was written correctly, false otherwise - public bool WriteToFile(string outfile) - { - try - { - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = Utilities.TryCreate(outfile); + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if the DAT was written correctly, false otherwise + public bool WriteToFile(string outfile) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); - // If we get back null for some reason, just log and return - if (fs == null) - { - Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); - return false; - } + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); - // Write out the header - WriteHeader(sw); + // Write out the header + WriteHeader(sw); - // Write out each of the machines and roms - string lastgame = null; + // Write out each of the machines and roms + string lastgame = null; - // Get a properly sorted set of keys - List keys = Keys; - keys.Sort(new NaturalComparer()); + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); - foreach (string key in keys) - { - List roms = this[key]; + foreach (string key in keys) + { + List roms = this[key]; - // Resolve the names in the block - roms = DatItem.ResolveNames(roms); + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); - for (int index = 0; index < roms.Count; index++) - { - DatItem item = roms[index]; + for (int index = 0; index < roms.Count; index++) + { + DatItem item = roms[index]; - // There are apparently times when a null rom can skip by, skip them - if (item.Name == null || item.MachineName == null) - { - Globals.Logger.Warning("Null rom found!"); - continue; - } + // There are apparently times when a null rom can skip by, skip them + if (item.Name == null || item.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } - // If we have a new game, output the beginning of the new item - if (lastgame == null || lastgame.ToLowerInvariant() != item.MachineName.ToLowerInvariant()) - { - WriteStartGame(sw, item); - } + // If we have a new game, output the beginning of the new item + if (lastgame == null || lastgame.ToLowerInvariant() != item.MachineName.ToLowerInvariant()) + { + WriteStartGame(sw, item); + } - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (item.ItemType == ItemType.Rom - && ((Rom)item).Size == -1 - && ((Rom)item).CRC == "null") - { - Globals.Logger.Verbose("Empty folder found: {0}", item.MachineName); + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (item.ItemType == ItemType.Rom + && ((Rom)item).Size == -1 + && ((Rom)item).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", item.MachineName); - item.Name = (item.Name == "null" ? "-" : item.Name); - ((Rom)item).Size = Constants.SizeZero; - } + item.Name = (item.Name == "null" ? "-" : item.Name); + ((Rom)item).Size = Constants.SizeZero; + } - // Set the new data to compare against - lastgame = item.MachineName; - } - } + // Set the new data to compare against + lastgame = item.MachineName; + } + } - Globals.Logger.Verbose("File written!" + Environment.NewLine); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteHeader(StreamWriter sw) - { - try - { - string header = "#Title;Name;Emulator;CloneOf;Year;Manufacturer;Category;Players;Rotation;Control;Status;DisplayCount;DisplayType;AltRomname;AltTitle;Extra;Buttons\n"; + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteHeader(StreamWriter sw) + { + try + { + string header = "#Title;Name;Emulator;CloneOf;Year;Manufacturer;Category;Players;Rotation;Control;Status;DisplayCount;DisplayType;AltRomname;AltTitle;Extra;Buttons\n"; - // Write the header out - sw.Write(header); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + // Write the header out + sw.Write(header); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out Game start using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if the data was written, false on error - private bool WriteStartGame(StreamWriter sw, DatItem rom) - { - try - { - // No game should start with a path separator - if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) - { - rom.MachineName = rom.MachineName.Substring(1); - } + /// + /// Write out Game start using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if the data was written, false on error + private bool WriteStartGame(StreamWriter sw, DatItem rom) + { + try + { + // No game should start with a path separator + if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) + { + rom.MachineName = rom.MachineName.Substring(1); + } - string state = (!ExcludeFields[(int)Field.MachineName] ? rom.MachineName : "") + ";" - + (!ExcludeFields[(int)Field.Description] ? rom.MachineDescription : "") + ";" - + FileName + ";" - + (!ExcludeFields[(int)Field.CloneOf] ? rom.CloneOf : "") + ";" - + (!ExcludeFields[(int)Field.Year] ? rom.Year : "") + ";" - + (!ExcludeFields[(int)Field.Manufacturer] ? rom.Manufacturer : "") + ";" - /* + rom.Category */ + ";" - /* + rom.Players */ + ";" - /* + rom.Rotation */ + ";" - /* + rom.Control */ + ";" - /* + rom.Status */ + ";" - /* + rom.DisplayCount */ + ";" - /* + rom.DisplayType */ + ";" - /* + rom.AltRomname */ + ";" - /* + rom.AltTitle */ + ";" - + (!ExcludeFields[(int)Field.Comment] ? rom.Comment : "") + ";" - /* + rom.Buttons */ + "\n"; + string state = (!ExcludeFields[(int)Field.MachineName] ? rom.MachineName : "") + ";" + + (!ExcludeFields[(int)Field.Description] ? rom.MachineDescription : "") + ";" + + FileName + ";" + + (!ExcludeFields[(int)Field.CloneOf] ? rom.CloneOf : "") + ";" + + (!ExcludeFields[(int)Field.Year] ? rom.Year : "") + ";" + + (!ExcludeFields[(int)Field.Manufacturer] ? rom.Manufacturer : "") + ";" + /* + rom.Category */ + ";" + /* + rom.Players */ + ";" + /* + rom.Rotation */ + ";" + /* + rom.Control */ + ";" + /* + rom.Status */ + ";" + /* + rom.DisplayCount */ + ";" + /* + rom.DisplayType */ + ";" + /* + rom.AltRomname */ + ";" + /* + rom.AltTitle */ + ";" + + (!ExcludeFields[(int)Field.Comment] ? rom.Comment : "") + ";" + /* + rom.Buttons */ + "\n"; - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } - } + return true; + } + } } diff --git a/SabreTools.Library/DatFiles/ClrMamePro.cs b/SabreTools.Library/DatFiles/ClrMamePro.cs index 5dd0c8c2..de359106 100644 --- a/SabreTools.Library/DatFiles/ClrMamePro.cs +++ b/SabreTools.Library/DatFiles/ClrMamePro.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; - using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -20,968 +19,968 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { - /// - /// Represents parsing and writing of a ClrMamePro DAT - /// - /// TODO: Verify that all write for this DatFile type is correct - internal class ClrMamePro : DatFile - { - /// - /// Constructor designed for casting a base DatFile - /// - /// Parent DatFile to copy from - public ClrMamePro(DatFile datFile) - : base(datFile, cloneHeader: false) - { - } - - /// - /// Parse a ClrMamePro DAT and return all found games and roms within - /// - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - public override void ParseFile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Open a file reader - Encoding enc = Utilities.GetEncoding(filename); - StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc); - - while (!sr.EndOfStream) - { - string line = sr.ReadLine(); - - // Comments in CMP DATs start with a # - if (line.Trim().StartsWith("#")) - { - continue; - } - - // If the line is the header or a game - if (Regex.IsMatch(line, Constants.HeaderPatternCMP)) - { - GroupCollection gc = Regex.Match(line, Constants.HeaderPatternCMP).Groups; - string normalizedValue = gc[1].Value.ToLowerInvariant(); - - // If we have a known header - if (normalizedValue == "clrmamepro" - || normalizedValue == "romvault" - || normalizedValue == "doscenter") - { - 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, keep, clean, remUnicode); - } - else if (normalizedValue == "resource") // Used by some other DATs to denote a BIOS set - { - ReadSet(sr, true, filename, sysid, srcid, keep, clean, remUnicode); - } - } - } - - sr.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) - { - bool superdat = false; - - // If there's no subtree to the header, skip it - if (reader == null || reader.EndOfStream) - { - return; - } - - // Otherwise, add what is possible - string line = reader.ReadLine(); - while (!Regex.IsMatch(line, Constants.EndPatternCMP)) - { - // We only want elements - if (line.Trim().StartsWith("#")) - { - line = reader.ReadLine(); - 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("\"", ""); - - if (line.Trim().StartsWith("Name:")) - { - Name = (String.IsNullOrWhiteSpace(Name) ? line.Substring(6) : Name); - superdat = superdat || itemval.Contains(" - SuperDAT"); - if (keep && superdat) - { - Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); - } - - line = reader.ReadLine(); - continue; - } - - switch (gc[1].Value) - { - case "name": - case "Name:": - Name = (String.IsNullOrWhiteSpace(Name) ? itemval : Name); - superdat = superdat || itemval.Contains(" - SuperDAT"); - if (keep && superdat) - { - Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); - } - break; - case "description": - case "Description:": - Description = (String.IsNullOrWhiteSpace(Description) ? itemval : Description); - break; - case "rootdir": - case "Rootdir:": - RootDir = (String.IsNullOrWhiteSpace(RootDir) ? itemval : RootDir); - break; - case "category": - case "Category:": - Category = (String.IsNullOrWhiteSpace(Category) ? itemval : Category); - break; - case "version": - case "Version:": - Version = (String.IsNullOrWhiteSpace(Version) ? itemval : Version); - break; - case "date": - case "Date:": - Date = (String.IsNullOrWhiteSpace(Date) ? itemval : Date); - break; - case "author": - case "Author:": - Author = (String.IsNullOrWhiteSpace(Author) ? itemval : Author); - break; - case "email": - case "Email:": - Email = (String.IsNullOrWhiteSpace(Email) ? itemval : Email); - break; - case "homepage": - case "Homepage:": - Homepage = (String.IsNullOrWhiteSpace(Homepage) ? itemval : Homepage); - break; - case "url": - case "Url:": - Url = (String.IsNullOrWhiteSpace(Url) ? itemval : Url); - break; - case "comment": - case "Comment:": - Comment = (String.IsNullOrWhiteSpace(Comment) ? itemval : Comment); - break; - case "header": - case "Header:": - Header = (String.IsNullOrWhiteSpace(Header) ? itemval : Header); - break; - case "type": - case "Type:": - Type = (String.IsNullOrWhiteSpace(Type) ? itemval : Type); - superdat = superdat || itemval.Contains("SuperDAT"); - break; - case "forcemerging": - if (ForceMerging == ForceMerging.None) - { - ForceMerging = Utilities.GetForceMerging(itemval); - } - break; - case "forcezipping": - if (ForcePacking == ForcePacking.None) - { - ForcePacking = Utilities.GetForcePacking(itemval); - } - break; - case "forcepacking": - if (ForcePacking == ForcePacking.None) - { - ForcePacking = Utilities.GetForcePacking(itemval); - } - break; - } - - line = reader.ReadLine(); - } - } - - /// - /// Read set information - /// - /// StreamReader 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 - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// 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, - bool resource, - - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Prepare all internal variables - bool containsItems = false; - Machine machine = new Machine() - { - MachineType = (resource ? MachineType.Bios : MachineType.None), - }; - - // If there's no subtree to the header, skip it - if (reader == null || reader.EndOfStream) - { - return; - } - - // Otherwise, add what is possible - string line = reader.ReadLine(); - while (!Regex.IsMatch(line, Constants.EndPatternCMP)) - { - // We only want elements - if (line.Trim().StartsWith("#")) - { - line = reader.ReadLine(); - continue; - } - - // Item-specific lines have a known pattern - string trimmedline = line.Trim(); - if (trimmedline.StartsWith("archive (") - || trimmedline.StartsWith("biosset (") - || trimmedline.StartsWith("disk (") - || trimmedline.StartsWith("file (") // This is a DOSCenter file, not a SabreDAT file - || trimmedline.StartsWith("release (") - || trimmedline.StartsWith("rom (") - || (trimmedline.StartsWith("sample") && !trimmedline.StartsWith("sampleof"))) - { - containsItems = true; - ItemType temptype = ItemType.Rom; - if (line.Trim().StartsWith("rom (")) - { - temptype = ItemType.Rom; - } - else if (line.Trim().StartsWith("disk (")) - { - temptype = ItemType.Disk; - } - else if (line.Trim().StartsWith("file (")) - { - temptype = ItemType.Rom; - } - else if (line.Trim().StartsWith("sample")) - { - temptype = ItemType.Sample; - } - - // Create the proper DatItem based on the type - DatItem item = Utilities.GetDatItem(temptype); - - // Then populate it with information - item.CopyMachineInformation(machine); - - item.SystemID = sysid; - item.System = filename; - item.SourceID = srcid; - - // If we have a sample, treat it special - if (temptype == ItemType.Sample) - { - line = line.Trim().Remove(0, 6).Trim().Replace("\"", ""); // Remove "sample" from the input string - item.Name = line; - - // 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); - - // Special cases for DOSCenter DATs only because of how the lines are arranged - if (line.Trim().StartsWith("file (")) - { - // Loop over the specifics - for (int i = 0; i < linegc.Length; i++) - { - // Names are not quoted, for some stupid reason - if (linegc[i] == "name") - { - // Get the name in order until we find the next flag - while (++i < linegc.Length && linegc[i] != "size" - && linegc[i] != "date" - && linegc[i] != "crc" - && linegc[i] != "md5" - && linegc[i] != "sha1" - && linegc[i] != "sha256" - && linegc[i] != "sha384" - && linegc[i] != "sha512") - { - item.Name += " " + linegc[i]; - } - - // Perform correction - item.Name = item.Name.TrimStart(); - i--; - } - - // Get the size from the next part - else if (linegc[i] == "size") - { - long tempsize = -1; - if (!Int64.TryParse(linegc[++i], out tempsize)) - { - tempsize = 0; - } - ((Rom)item).Size = tempsize; - } - - // Get the date from the next part - else if (linegc[i] == "date") - { - ((Rom)item).Date = linegc[++i].Replace("\"", "") + " " + linegc[++i].Replace("\"", ""); - } - - // Get the CRC from the next part - else if (linegc[i] == "crc") - { - ((Rom)item).CRC = linegc[++i].Replace("\"", "").ToLowerInvariant(); - } - - // Get the MD5 from the next part - else if (linegc[i] == "md5") - { - ((Rom)item).MD5 = linegc[++i].Replace("\"", "").ToLowerInvariant(); - } - - // Get the SHA1 from the next part - else if (linegc[i] == "sha1") - { - ((Rom)item).SHA1 = linegc[++i].Replace("\"", "").ToLowerInvariant(); - } - - // Get the SHA256 from the next part - else if (linegc[i] == "sha256") - { - ((Rom)item).SHA256 = linegc[++i].Replace("\"", "").ToLowerInvariant(); - } - - // Get the SHA384 from the next part - else if (linegc[i] == "sha384") - { - ((Rom)item).SHA384 = linegc[++i].Replace("\"", "").ToLowerInvariant(); - } - - // Get the SHA512 from the next part - else if (linegc[i] == "sha512") - { - ((Rom)item).SHA512 = linegc[++i].Replace("\"", "").ToLowerInvariant(); - } - } - - // Now process and add the rom - ParseAddHelper(item, clean, remUnicode); - line = reader.ReadLine(); - continue; - } - - // 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("\"", ""); - switch (quoteless) - { - //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("\"", ""); - item.Name = quoteless; - break; - case "size": - if (item.ItemType == ItemType.Rom) - { - quoteless = linegc[++i].Replace("\"", ""); - if (Int64.TryParse(quoteless, out long size)) - { - ((Rom)item).Size = size; - } - else - { - ((Rom)item).Size = -1; - } - } - break; - case "crc": - if (item.ItemType == ItemType.Rom) - { - quoteless = linegc[++i].Replace("\"", ""); - ((Rom)item).CRC = Utilities.CleanHashData(quoteless, Constants.CRCLength); - } - break; - case "md5": - if (item.ItemType == ItemType.Rom) - { - quoteless = linegc[++i].Replace("\"", ""); - ((Rom)item).MD5 = Utilities.CleanHashData(quoteless, Constants.MD5Length); - } - else if (item.ItemType == ItemType.Disk) - { - i++; - quoteless = linegc[i].Replace("\"", ""); - ((Disk)item).MD5 = Utilities.CleanHashData(quoteless, Constants.MD5Length); - } - break; - case "sha1": - if (item.ItemType == ItemType.Rom) - { - quoteless = linegc[++i].Replace("\"", ""); - ((Rom)item).SHA1 = Utilities.CleanHashData(quoteless, Constants.SHA1Length); - } - else if (item.ItemType == ItemType.Disk) - { - quoteless = linegc[++i].Replace("\"", ""); - ((Disk)item).SHA1 = Utilities.CleanHashData(quoteless, Constants.SHA1Length); - } - break; - case "sha256": - if (item.ItemType == ItemType.Rom) - { - quoteless = linegc[++i].Replace("\"", ""); - ((Rom)item).SHA256 = Utilities.CleanHashData(quoteless, Constants.SHA256Length); - } - else if (item.ItemType == ItemType.Disk) - { - quoteless = linegc[++i].Replace("\"", ""); - ((Disk)item).SHA256 = Utilities.CleanHashData(quoteless, Constants.SHA256Length); - } - break; - case "sha384": - if (item.ItemType == ItemType.Rom) - { - quoteless = linegc[++i].Replace("\"", ""); - ((Rom)item).SHA384 = Utilities.CleanHashData(quoteless, Constants.SHA384Length); - } - else if (item.ItemType == ItemType.Disk) - { - quoteless = linegc[++i].Replace("\"", ""); - ((Disk)item).SHA384 = Utilities.CleanHashData(quoteless, Constants.SHA384Length); - } - break; - case "sha512": - if (item.ItemType == ItemType.Rom) - { - quoteless = linegc[++i].Replace("\"", ""); - ((Rom)item).SHA512 = Utilities.CleanHashData(quoteless, Constants.SHA512Length); - } - else if (item.ItemType == ItemType.Disk) - { - quoteless = linegc[++i].Replace("\"", ""); - ((Disk)item).SHA512 = Utilities.CleanHashData(quoteless, Constants.SHA512Length); - } - break; - case "status": - case "flags": - quoteless = linegc[++i].Replace("\"", ""); - ItemStatus tempFlagStatus = Utilities.GetItemStatus(quoteless); - if (item.ItemType == ItemType.Rom) - { - ((Rom)item).ItemStatus = tempFlagStatus; - } - else if (item.ItemType == ItemType.Disk) - { - ((Disk)item).ItemStatus = tempFlagStatus; - } - 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("\"", ""); - } - // Otherwise, we assume we need to read the next two items - else - { - quoteless = linegc[++i].Replace("\"", "") + " " + linegc[++i].Replace("\"", ""); - } - ((Rom)item).Date = quoteless; - } - 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("\"", ""); - } - // Otherwise, we assume we need to read the next two items - else - { - quoteless = linegc[++i].Replace("\"", "") + " " + linegc[++i].Replace("\"", ""); - } - ((Release)item).Date = quoteless; - } - break; - case "default": - if (item.ItemType == ItemType.BiosSet) - { - quoteless = linegc[++i].Replace("\"", ""); - ((BiosSet)item).Default = Utilities.GetYesNo(quoteless.ToLowerInvariant()); - } - else if (item.ItemType == ItemType.Release) - { - quoteless = linegc[++i].Replace("\"", ""); - ((Release)item).Default = Utilities.GetYesNo(quoteless.ToLowerInvariant()); - } - break; - case "description": - if (item.ItemType == ItemType.BiosSet) - { - quoteless = linegc[++i].Replace("\"", ""); - ((BiosSet)item).Description = quoteless.ToLowerInvariant(); - } - break; - case "region": - if (item.ItemType == ItemType.Release) - { - quoteless = linegc[++i].Replace("\"", ""); - ((Release)item).Region = quoteless.ToLowerInvariant(); - } - break; - case "language": - if (item.ItemType == ItemType.Release) - { - quoteless = linegc[++i].Replace("\"", ""); - ((Release)item).Language = quoteless.ToLowerInvariant(); - } - break; - } - } - - // 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("\"", ""); - - 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 - if (!containsItems) - { - Blank blank = new Blank() - { - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - blank.CopyMachineInformation(machine); - - // Now process and add the rom - ParseAddHelper(blank, clean, remUnicode); - } - } - - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the DAT was written correctly, false otherwise - public override bool WriteToFile(string outfile, bool ignoreblanks = false) - { - try - { - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = Utilities.TryCreate(outfile); - - // If we get back null for some reason, just log and return - if (fs == null) - { - Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); - return false; - } - - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); - - // Write out the header - WriteHeader(sw); - - // Write out each of the machines and roms - string lastgame = null; - - // Get a properly sorted set of keys - List keys = Keys; - keys.Sort(new NaturalComparer()); - - foreach (string key in keys) - { - List roms = this[key]; - - // Resolve the names in the block - roms = DatItem.ResolveNames(roms); - - for (int index = 0; index < roms.Count; index++) - { - DatItem rom = roms[index]; - - // There are apparently times when a null rom can skip by, skip them - if (rom.Name == null || rom.MachineName == null) - { - Globals.Logger.Warning("Null rom found!"); - continue; - } - - // If we have a different game and we're not at the start of the list, output the end of last item - if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteEndGame(sw, rom); - } - - // If we have a new game, output the beginning of the new item - if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteStartGame(sw, rom); - } - - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (rom.ItemType == ItemType.Rom - && ((Rom)rom).Size == -1 - && ((Rom)rom).CRC == "null") - { - Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); - - // If we're in a mode that doesn't allow for actual empty folders, add the blank info - rom.Name = (rom.Name == "null" ? "-" : rom.Name); - ((Rom)rom).Size = Constants.SizeZero; - ((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null; - ((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null; - ((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null; - ((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null; - ((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null; - ((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null; - } - - // Now, output the rom data - WriteDatItem(sw, rom, ignoreblanks); - - // Set the new data to compare against - lastgame = rom.MachineName; - } - } - - // Write the file footer out - WriteFooter(sw); - - Globals.Logger.Verbose("File written!" + Environment.NewLine); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteHeader(StreamWriter sw) - { - try - { - string header = "clrmamepro (\n" + - "\tname \"" + Name + "\"\n" + - "\tdescription \"" + Description + "\"\n" + - (!String.IsNullOrWhiteSpace(Category) ? "\tcategory \"" + Category + "\"\n" : "") + - "\tversion \"" + Version + "\"\n" + - (!String.IsNullOrWhiteSpace(Date) ? "\tdate \"" + Date + "\"\n" : "") + - "\tauthor \"" + Author + "\"\n" + - (!String.IsNullOrWhiteSpace(Email) ? "\temail \"" + Email + "\"\n" : "") + - (!String.IsNullOrWhiteSpace(Homepage) ? "\thomepage \"" + Homepage + "\"\n" : "") + - (!String.IsNullOrWhiteSpace(Url) ? "\turl \"" + Url + "\"\n" : "") + - (!String.IsNullOrWhiteSpace(Comment) ? "\tcomment \"" + Comment + "\"\n" : "") + - (ForcePacking == ForcePacking.Unzip ? "\tforcezipping no\n" : "") + - (ForcePacking == ForcePacking.Zip ? "\tforcezipping yes\n" : "") + - (ForceMerging == ForceMerging.Full ? "\tforcemerging full\n" : "") + - (ForceMerging == ForceMerging.Split ? "\tforcemerging split\n" : "") + - (ForceMerging == ForceMerging.Merged ? "\tforcemerging merged\n" : "") + - (ForceMerging == ForceMerging.NonMerged ? "\tforcemerging nonmerged\n" : "") + - ")\n"; - - // Write the header out - sw.Write(header); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if the data was written, false on error - private bool WriteStartGame(StreamWriter sw, DatItem rom) - { - try - { - // No game should start with a path separator - if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) - { - rom.MachineName = rom.MachineName.Substring(1); - } - - string state = (rom.MachineType == MachineType.Bios ? "resource" : "game") + " (\n\tname \"" + (!ExcludeFields[(int)Field.MachineName] ? rom.MachineName : "") + "\"\n" + - (!ExcludeFields[(int)Field.RomOf] && String.IsNullOrWhiteSpace(rom.RomOf) ? "" : "\tromof \"" + rom.RomOf + "\"\n") + - (!ExcludeFields[(int)Field.CloneOf] && String.IsNullOrWhiteSpace(rom.CloneOf) ? "" : "\tcloneof \"" + rom.CloneOf + "\"\n") + - (!ExcludeFields[(int)Field.SampleOf] && String.IsNullOrWhiteSpace(rom.SampleOf) ? "" : "\tsampleof \"" + rom.SampleOf + "\"\n") + - (!ExcludeFields[(int)Field.Description] ? "\tdescription \"" + (String.IsNullOrWhiteSpace(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription) + "\"\n" : "") + - (!ExcludeFields[(int)Field.Year] && String.IsNullOrWhiteSpace(rom.Year) ? "" : "\tyear " + rom.Year + "\n") + - (!ExcludeFields[(int)Field.Manufacturer] && String.IsNullOrWhiteSpace(rom.Manufacturer) ? "" : "\tmanufacturer \"" + rom.Manufacturer + "\"\n"); - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out Game end using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if the data was written, false on error - private bool WriteEndGame(StreamWriter sw, DatItem rom) - { - try - { - string state = (!ExcludeFields[(int)Field.SampleOf] && String.IsNullOrWhiteSpace(rom.SampleOf) ? "" : "\tsampleof \"" + rom.SampleOf + "\"\n") + ")\n"; - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// DatFile to write out from - /// StreamWriter to output to - /// DatItem object to be output - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the data was written, false on error - private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) - { - // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip - if (ignoreblanks - && (rom.ItemType == ItemType.Rom - && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) - { - return true; - } - - try - { - string state = ""; - - // Pre-process the item name - ProcessItemName(rom, true); - - switch (rom.ItemType) - { - case ItemType.Archive: - state += "\tarchive ( name\"" + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\"" - + " )\n"; - break; - case ItemType.BiosSet: - state += "\tbiosset ( name\"" + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\"" - + (!ExcludeFields[(int)Field.Description] && !String.IsNullOrWhiteSpace(((BiosSet)rom).Description) ? " description \"" + ((BiosSet)rom).Description + "\"" : "") - + (!ExcludeFields[(int)Field.Default] && ((BiosSet)rom).Default != null - ? "default " + ((BiosSet)rom).Default.ToString().ToLowerInvariant() - : "") - + " )\n"; - break; - case ItemType.Disk: - state += "\tdisk ( name \"" + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\"" - + (!ExcludeFields[(int)Field.MD5] && !String.IsNullOrWhiteSpace(((Disk)rom).MD5) ? " md5 " + ((Disk)rom).MD5.ToLowerInvariant() : "") - + (!ExcludeFields[(int)Field.SHA1] && !String.IsNullOrWhiteSpace(((Disk)rom).SHA1) ? " sha1 " + ((Disk)rom).SHA1.ToLowerInvariant() : "") - + (!ExcludeFields[(int)Field.SHA256] && !String.IsNullOrWhiteSpace(((Disk)rom).SHA256) ? " sha256 " + ((Disk)rom).SHA256.ToLowerInvariant() : "") - + (!ExcludeFields[(int)Field.SHA384] && !String.IsNullOrWhiteSpace(((Disk)rom).SHA384) ? " sha384 " + ((Disk)rom).SHA384.ToLowerInvariant() : "") - + (!ExcludeFields[(int)Field.SHA512] && !String.IsNullOrWhiteSpace(((Disk)rom).SHA512) ? " sha512 " + ((Disk)rom).SHA512.ToLowerInvariant() : "") - + (!ExcludeFields[(int)Field.Status] && ((Disk)rom).ItemStatus != ItemStatus.None ? " flags " + ((Disk)rom).ItemStatus.ToString().ToLowerInvariant() : "") - + " )\n"; - break; - case ItemType.Release: - state += "\trelease ( name\"" + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\"" - + (!ExcludeFields[(int)Field.Region] && !String.IsNullOrWhiteSpace(((Release)rom).Region) ? " region \"" + ((Release)rom).Region + "\"" : "") - + (!ExcludeFields[(int)Field.Language] && !String.IsNullOrWhiteSpace(((Release)rom).Language) ? " language \"" + ((Release)rom).Language + "\"" : "") - + (!ExcludeFields[(int)Field.Date] && !String.IsNullOrWhiteSpace(((Release)rom).Date) ? " date \"" + ((Release)rom).Date + "\"" : "") - + (!ExcludeFields[(int)Field.Default] && ((Release)rom).Default != null - ? "default " + ((Release)rom).Default.ToString().ToLowerInvariant() - : "") - + " )\n"; - break; - case ItemType.Rom: - state += "\trom ( name \"" + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\"" - + (!ExcludeFields[(int)Field.Size] && ((Rom)rom).Size != -1 ? " size " + ((Rom)rom).Size : "") - + (!ExcludeFields[(int)Field.CRC] && !String.IsNullOrWhiteSpace(((Rom)rom).CRC) ? " crc " + ((Rom)rom).CRC.ToLowerInvariant() : "") - + (!ExcludeFields[(int)Field.MD5] && !String.IsNullOrWhiteSpace(((Rom)rom).MD5) ? " md5 " + ((Rom)rom).MD5.ToLowerInvariant() : "") - + (!ExcludeFields[(int)Field.SHA1] && !String.IsNullOrWhiteSpace(((Rom)rom).SHA1) ? " sha1 " + ((Rom)rom).SHA1.ToLowerInvariant() : "") - + (!ExcludeFields[(int)Field.SHA256] && !String.IsNullOrWhiteSpace(((Rom)rom).SHA256) ? " sha256 " + ((Rom)rom).SHA256.ToLowerInvariant() : "") - + (!ExcludeFields[(int)Field.SHA384] && !String.IsNullOrWhiteSpace(((Rom)rom).SHA384) ? " sha384 " + ((Rom)rom).SHA384.ToLowerInvariant() : "") - + (!ExcludeFields[(int)Field.SHA512] && !String.IsNullOrWhiteSpace(((Rom)rom).SHA512) ? " sha512 " + ((Rom)rom).SHA512.ToLowerInvariant() : "") - + (!ExcludeFields[(int)Field.Date] && !String.IsNullOrWhiteSpace(((Rom)rom).Date) ? " date \"" + ((Rom)rom).Date + "\"" : "") - + (!ExcludeFields[(int)Field.Status] && ((Rom)rom).ItemStatus != ItemStatus.None ? " flags " + ((Rom)rom).ItemStatus.ToString().ToLowerInvariant() : "") - + " )\n"; - break; - case ItemType.Sample: - state += "\tsample ( name\"" + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\"" - + " )\n"; - break; - } - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DAT footer using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteFooter(StreamWriter sw) - { - try - { - string footer = footer = ")\n"; - - // Write the footer out - sw.Write(footer); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - } + /// + /// Represents parsing and writing of a ClrMamePro DAT + /// + /// TODO: Verify that all write for this DatFile type is correct + internal class ClrMamePro : DatFile + { + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + public ClrMamePro(DatFile datFile) + : base(datFile, cloneHeader: false) + { + } + + /// + /// Parse a ClrMamePro DAT and return all found games and roms within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Open a file reader + Encoding enc = Utilities.GetEncoding(filename); + StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc); + + while (!sr.EndOfStream) + { + string line = sr.ReadLine(); + + // Comments in CMP DATs start with a # + if (line.Trim().StartsWith("#")) + { + continue; + } + + // If the line is the header or a game + if (Regex.IsMatch(line, Constants.HeaderPatternCMP)) + { + GroupCollection gc = Regex.Match(line, Constants.HeaderPatternCMP).Groups; + string normalizedValue = gc[1].Value.ToLowerInvariant(); + + // If we have a known header + if (normalizedValue == "clrmamepro" + || normalizedValue == "romvault" + || normalizedValue == "doscenter") + { + 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, keep, clean, remUnicode); + } + else if (normalizedValue == "resource") // Used by some other DATs to denote a BIOS set + { + ReadSet(sr, true, filename, sysid, srcid, keep, clean, remUnicode); + } + } + } + + sr.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) + { + bool superdat = false; + + // If there's no subtree to the header, skip it + if (reader == null || reader.EndOfStream) + { + return; + } + + // Otherwise, add what is possible + string line = reader.ReadLine(); + while (!Regex.IsMatch(line, Constants.EndPatternCMP)) + { + // We only want elements + if (line.Trim().StartsWith("#")) + { + line = reader.ReadLine(); + 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("\"", ""); + + if (line.Trim().StartsWith("Name:")) + { + Name = (String.IsNullOrWhiteSpace(Name) ? line.Substring(6) : Name); + superdat = superdat || itemval.Contains(" - SuperDAT"); + if (keep && superdat) + { + Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); + } + + line = reader.ReadLine(); + continue; + } + + switch (gc[1].Value) + { + case "name": + case "Name:": + Name = (String.IsNullOrWhiteSpace(Name) ? itemval : Name); + superdat = superdat || itemval.Contains(" - SuperDAT"); + if (keep && superdat) + { + Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); + } + break; + case "description": + case "Description:": + Description = (String.IsNullOrWhiteSpace(Description) ? itemval : Description); + break; + case "rootdir": + case "Rootdir:": + RootDir = (String.IsNullOrWhiteSpace(RootDir) ? itemval : RootDir); + break; + case "category": + case "Category:": + Category = (String.IsNullOrWhiteSpace(Category) ? itemval : Category); + break; + case "version": + case "Version:": + Version = (String.IsNullOrWhiteSpace(Version) ? itemval : Version); + break; + case "date": + case "Date:": + Date = (String.IsNullOrWhiteSpace(Date) ? itemval : Date); + break; + case "author": + case "Author:": + Author = (String.IsNullOrWhiteSpace(Author) ? itemval : Author); + break; + case "email": + case "Email:": + Email = (String.IsNullOrWhiteSpace(Email) ? itemval : Email); + break; + case "homepage": + case "Homepage:": + Homepage = (String.IsNullOrWhiteSpace(Homepage) ? itemval : Homepage); + break; + case "url": + case "Url:": + Url = (String.IsNullOrWhiteSpace(Url) ? itemval : Url); + break; + case "comment": + case "Comment:": + Comment = (String.IsNullOrWhiteSpace(Comment) ? itemval : Comment); + break; + case "header": + case "Header:": + Header = (String.IsNullOrWhiteSpace(Header) ? itemval : Header); + break; + case "type": + case "Type:": + Type = (String.IsNullOrWhiteSpace(Type) ? itemval : Type); + superdat = superdat || itemval.Contains("SuperDAT"); + break; + case "forcemerging": + if (ForceMerging == ForceMerging.None) + { + ForceMerging = Utilities.GetForceMerging(itemval); + } + break; + case "forcezipping": + if (ForcePacking == ForcePacking.None) + { + ForcePacking = Utilities.GetForcePacking(itemval); + } + break; + case "forcepacking": + if (ForcePacking == ForcePacking.None) + { + ForcePacking = Utilities.GetForcePacking(itemval); + } + break; + } + + line = reader.ReadLine(); + } + } + + /// + /// Read set information + /// + /// StreamReader 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 + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// 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, + bool resource, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Prepare all internal variables + bool containsItems = false; + Machine machine = new Machine() + { + MachineType = (resource ? MachineType.Bios : MachineType.None), + }; + + // If there's no subtree to the header, skip it + if (reader == null || reader.EndOfStream) + { + return; + } + + // Otherwise, add what is possible + string line = reader.ReadLine(); + while (!Regex.IsMatch(line, Constants.EndPatternCMP)) + { + // We only want elements + if (line.Trim().StartsWith("#")) + { + line = reader.ReadLine(); + continue; + } + + // Item-specific lines have a known pattern + string trimmedline = line.Trim(); + if (trimmedline.StartsWith("archive (") + || trimmedline.StartsWith("biosset (") + || trimmedline.StartsWith("disk (") + || trimmedline.StartsWith("file (") // This is a DOSCenter file, not a SabreDAT file + || trimmedline.StartsWith("release (") + || trimmedline.StartsWith("rom (") + || (trimmedline.StartsWith("sample") && !trimmedline.StartsWith("sampleof"))) + { + containsItems = true; + ItemType temptype = ItemType.Rom; + if (line.Trim().StartsWith("rom (")) + { + temptype = ItemType.Rom; + } + else if (line.Trim().StartsWith("disk (")) + { + temptype = ItemType.Disk; + } + else if (line.Trim().StartsWith("file (")) + { + temptype = ItemType.Rom; + } + else if (line.Trim().StartsWith("sample")) + { + temptype = ItemType.Sample; + } + + // Create the proper DatItem based on the type + DatItem item = Utilities.GetDatItem(temptype); + + // Then populate it with information + item.CopyMachineInformation(machine); + + item.SystemID = sysid; + item.System = filename; + item.SourceID = srcid; + + // If we have a sample, treat it special + if (temptype == ItemType.Sample) + { + line = line.Trim().Remove(0, 6).Trim().Replace("\"", ""); // Remove "sample" from the input string + item.Name = line; + + // 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); + + // Special cases for DOSCenter DATs only because of how the lines are arranged + if (line.Trim().StartsWith("file (")) + { + // Loop over the specifics + for (int i = 0; i < linegc.Length; i++) + { + // Names are not quoted, for some stupid reason + if (linegc[i] == "name") + { + // Get the name in order until we find the next flag + while (++i < linegc.Length && linegc[i] != "size" + && linegc[i] != "date" + && linegc[i] != "crc" + && linegc[i] != "md5" + && linegc[i] != "sha1" + && linegc[i] != "sha256" + && linegc[i] != "sha384" + && linegc[i] != "sha512") + { + item.Name += " " + linegc[i]; + } + + // Perform correction + item.Name = item.Name.TrimStart(); + i--; + } + + // Get the size from the next part + else if (linegc[i] == "size") + { + long tempsize = -1; + if (!Int64.TryParse(linegc[++i], out tempsize)) + { + tempsize = 0; + } + ((Rom)item).Size = tempsize; + } + + // Get the date from the next part + else if (linegc[i] == "date") + { + ((Rom)item).Date = linegc[++i].Replace("\"", "") + " " + linegc[++i].Replace("\"", ""); + } + + // Get the CRC from the next part + else if (linegc[i] == "crc") + { + ((Rom)item).CRC = linegc[++i].Replace("\"", "").ToLowerInvariant(); + } + + // Get the MD5 from the next part + else if (linegc[i] == "md5") + { + ((Rom)item).MD5 = linegc[++i].Replace("\"", "").ToLowerInvariant(); + } + + // Get the SHA1 from the next part + else if (linegc[i] == "sha1") + { + ((Rom)item).SHA1 = linegc[++i].Replace("\"", "").ToLowerInvariant(); + } + + // Get the SHA256 from the next part + else if (linegc[i] == "sha256") + { + ((Rom)item).SHA256 = linegc[++i].Replace("\"", "").ToLowerInvariant(); + } + + // Get the SHA384 from the next part + else if (linegc[i] == "sha384") + { + ((Rom)item).SHA384 = linegc[++i].Replace("\"", "").ToLowerInvariant(); + } + + // Get the SHA512 from the next part + else if (linegc[i] == "sha512") + { + ((Rom)item).SHA512 = linegc[++i].Replace("\"", "").ToLowerInvariant(); + } + } + + // Now process and add the rom + ParseAddHelper(item, clean, remUnicode); + line = reader.ReadLine(); + continue; + } + + // 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("\"", ""); + switch (quoteless) + { + //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("\"", ""); + item.Name = quoteless; + break; + case "size": + if (item.ItemType == ItemType.Rom) + { + quoteless = linegc[++i].Replace("\"", ""); + if (Int64.TryParse(quoteless, out long size)) + { + ((Rom)item).Size = size; + } + else + { + ((Rom)item).Size = -1; + } + } + break; + case "crc": + if (item.ItemType == ItemType.Rom) + { + quoteless = linegc[++i].Replace("\"", ""); + ((Rom)item).CRC = Utilities.CleanHashData(quoteless, Constants.CRCLength); + } + break; + case "md5": + if (item.ItemType == ItemType.Rom) + { + quoteless = linegc[++i].Replace("\"", ""); + ((Rom)item).MD5 = Utilities.CleanHashData(quoteless, Constants.MD5Length); + } + else if (item.ItemType == ItemType.Disk) + { + i++; + quoteless = linegc[i].Replace("\"", ""); + ((Disk)item).MD5 = Utilities.CleanHashData(quoteless, Constants.MD5Length); + } + break; + case "sha1": + if (item.ItemType == ItemType.Rom) + { + quoteless = linegc[++i].Replace("\"", ""); + ((Rom)item).SHA1 = Utilities.CleanHashData(quoteless, Constants.SHA1Length); + } + else if (item.ItemType == ItemType.Disk) + { + quoteless = linegc[++i].Replace("\"", ""); + ((Disk)item).SHA1 = Utilities.CleanHashData(quoteless, Constants.SHA1Length); + } + break; + case "sha256": + if (item.ItemType == ItemType.Rom) + { + quoteless = linegc[++i].Replace("\"", ""); + ((Rom)item).SHA256 = Utilities.CleanHashData(quoteless, Constants.SHA256Length); + } + else if (item.ItemType == ItemType.Disk) + { + quoteless = linegc[++i].Replace("\"", ""); + ((Disk)item).SHA256 = Utilities.CleanHashData(quoteless, Constants.SHA256Length); + } + break; + case "sha384": + if (item.ItemType == ItemType.Rom) + { + quoteless = linegc[++i].Replace("\"", ""); + ((Rom)item).SHA384 = Utilities.CleanHashData(quoteless, Constants.SHA384Length); + } + else if (item.ItemType == ItemType.Disk) + { + quoteless = linegc[++i].Replace("\"", ""); + ((Disk)item).SHA384 = Utilities.CleanHashData(quoteless, Constants.SHA384Length); + } + break; + case "sha512": + if (item.ItemType == ItemType.Rom) + { + quoteless = linegc[++i].Replace("\"", ""); + ((Rom)item).SHA512 = Utilities.CleanHashData(quoteless, Constants.SHA512Length); + } + else if (item.ItemType == ItemType.Disk) + { + quoteless = linegc[++i].Replace("\"", ""); + ((Disk)item).SHA512 = Utilities.CleanHashData(quoteless, Constants.SHA512Length); + } + break; + case "status": + case "flags": + quoteless = linegc[++i].Replace("\"", ""); + ItemStatus tempFlagStatus = Utilities.GetItemStatus(quoteless); + if (item.ItemType == ItemType.Rom) + { + ((Rom)item).ItemStatus = tempFlagStatus; + } + else if (item.ItemType == ItemType.Disk) + { + ((Disk)item).ItemStatus = tempFlagStatus; + } + 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("\"", ""); + } + // Otherwise, we assume we need to read the next two items + else + { + quoteless = linegc[++i].Replace("\"", "") + " " + linegc[++i].Replace("\"", ""); + } + ((Rom)item).Date = quoteless; + } + 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("\"", ""); + } + // Otherwise, we assume we need to read the next two items + else + { + quoteless = linegc[++i].Replace("\"", "") + " " + linegc[++i].Replace("\"", ""); + } + ((Release)item).Date = quoteless; + } + break; + case "default": + if (item.ItemType == ItemType.BiosSet) + { + quoteless = linegc[++i].Replace("\"", ""); + ((BiosSet)item).Default = Utilities.GetYesNo(quoteless.ToLowerInvariant()); + } + else if (item.ItemType == ItemType.Release) + { + quoteless = linegc[++i].Replace("\"", ""); + ((Release)item).Default = Utilities.GetYesNo(quoteless.ToLowerInvariant()); + } + break; + case "description": + if (item.ItemType == ItemType.BiosSet) + { + quoteless = linegc[++i].Replace("\"", ""); + ((BiosSet)item).Description = quoteless.ToLowerInvariant(); + } + break; + case "region": + if (item.ItemType == ItemType.Release) + { + quoteless = linegc[++i].Replace("\"", ""); + ((Release)item).Region = quoteless.ToLowerInvariant(); + } + break; + case "language": + if (item.ItemType == ItemType.Release) + { + quoteless = linegc[++i].Replace("\"", ""); + ((Release)item).Language = quoteless.ToLowerInvariant(); + } + break; + } + } + + // 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("\"", ""); + + 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 + if (!containsItems) + { + Blank blank = new Blank() + { + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + blank.CopyMachineInformation(machine); + + // Now process and add the rom + ParseAddHelper(blank, clean, remUnicode); + } + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public override bool WriteToFile(string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); + + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } + + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + + // Write out the header + WriteHeader(sw); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = this[key]; + + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); + + for (int index = 0; index < roms.Count; index++) + { + DatItem rom = roms[index]; + + // There are apparently times when a null rom can skip by, skip them + if (rom.Name == null || rom.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } + + // If we have a different game and we're not at the start of the list, output the end of last item + if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteEndGame(sw, rom); + } + + // If we have a new game, output the beginning of the new item + if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteStartGame(sw, rom); + } + + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.ItemType == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + + // If we're in a mode that doesn't allow for actual empty folders, add the blank info + rom.Name = (rom.Name == "null" ? "-" : rom.Name); + ((Rom)rom).Size = Constants.SizeZero; + ((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null; + ((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null; + ((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null; + ((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null; + ((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null; + ((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null; + } + + // Now, output the rom data + WriteDatItem(sw, rom, ignoreblanks); + + // Set the new data to compare against + lastgame = rom.MachineName; + } + } + + // Write the file footer out + WriteFooter(sw); + + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteHeader(StreamWriter sw) + { + try + { + string header = "clrmamepro (\n" + + "\tname \"" + Name + "\"\n" + + "\tdescription \"" + Description + "\"\n" + + (!String.IsNullOrWhiteSpace(Category) ? "\tcategory \"" + Category + "\"\n" : "") + + "\tversion \"" + Version + "\"\n" + + (!String.IsNullOrWhiteSpace(Date) ? "\tdate \"" + Date + "\"\n" : "") + + "\tauthor \"" + Author + "\"\n" + + (!String.IsNullOrWhiteSpace(Email) ? "\temail \"" + Email + "\"\n" : "") + + (!String.IsNullOrWhiteSpace(Homepage) ? "\thomepage \"" + Homepage + "\"\n" : "") + + (!String.IsNullOrWhiteSpace(Url) ? "\turl \"" + Url + "\"\n" : "") + + (!String.IsNullOrWhiteSpace(Comment) ? "\tcomment \"" + Comment + "\"\n" : "") + + (ForcePacking == ForcePacking.Unzip ? "\tforcezipping no\n" : "") + + (ForcePacking == ForcePacking.Zip ? "\tforcezipping yes\n" : "") + + (ForceMerging == ForceMerging.Full ? "\tforcemerging full\n" : "") + + (ForceMerging == ForceMerging.Split ? "\tforcemerging split\n" : "") + + (ForceMerging == ForceMerging.Merged ? "\tforcemerging merged\n" : "") + + (ForceMerging == ForceMerging.NonMerged ? "\tforcemerging nonmerged\n" : "") + + ")\n"; + + // Write the header out + sw.Write(header); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if the data was written, false on error + private bool WriteStartGame(StreamWriter sw, DatItem rom) + { + try + { + // No game should start with a path separator + if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) + { + rom.MachineName = rom.MachineName.Substring(1); + } + + string state = (rom.MachineType == MachineType.Bios ? "resource" : "game") + " (\n\tname \"" + (!ExcludeFields[(int)Field.MachineName] ? rom.MachineName : "") + "\"\n" + + (!ExcludeFields[(int)Field.RomOf] && String.IsNullOrWhiteSpace(rom.RomOf) ? "" : "\tromof \"" + rom.RomOf + "\"\n") + + (!ExcludeFields[(int)Field.CloneOf] && String.IsNullOrWhiteSpace(rom.CloneOf) ? "" : "\tcloneof \"" + rom.CloneOf + "\"\n") + + (!ExcludeFields[(int)Field.SampleOf] && String.IsNullOrWhiteSpace(rom.SampleOf) ? "" : "\tsampleof \"" + rom.SampleOf + "\"\n") + + (!ExcludeFields[(int)Field.Description] ? "\tdescription \"" + (String.IsNullOrWhiteSpace(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription) + "\"\n" : "") + + (!ExcludeFields[(int)Field.Year] && String.IsNullOrWhiteSpace(rom.Year) ? "" : "\tyear " + rom.Year + "\n") + + (!ExcludeFields[(int)Field.Manufacturer] && String.IsNullOrWhiteSpace(rom.Manufacturer) ? "" : "\tmanufacturer \"" + rom.Manufacturer + "\"\n"); + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out Game end using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if the data was written, false on error + private bool WriteEndGame(StreamWriter sw, DatItem rom) + { + try + { + string state = (!ExcludeFields[(int)Field.SampleOf] && String.IsNullOrWhiteSpace(rom.SampleOf) ? "" : "\tsampleof \"" + rom.SampleOf + "\"\n") + ")\n"; + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// DatFile to write out from + /// StreamWriter to output to + /// DatItem object to be output + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the data was written, false on error + private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.ItemType == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + + // Pre-process the item name + ProcessItemName(rom, true); + + switch (rom.ItemType) + { + case ItemType.Archive: + state += "\tarchive ( name\"" + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\"" + + " )\n"; + break; + case ItemType.BiosSet: + state += "\tbiosset ( name\"" + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\"" + + (!ExcludeFields[(int)Field.Description] && !String.IsNullOrWhiteSpace(((BiosSet)rom).Description) ? " description \"" + ((BiosSet)rom).Description + "\"" : "") + + (!ExcludeFields[(int)Field.Default] && ((BiosSet)rom).Default != null + ? "default " + ((BiosSet)rom).Default.ToString().ToLowerInvariant() + : "") + + " )\n"; + break; + case ItemType.Disk: + state += "\tdisk ( name \"" + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\"" + + (!ExcludeFields[(int)Field.MD5] && !String.IsNullOrWhiteSpace(((Disk)rom).MD5) ? " md5 " + ((Disk)rom).MD5.ToLowerInvariant() : "") + + (!ExcludeFields[(int)Field.SHA1] && !String.IsNullOrWhiteSpace(((Disk)rom).SHA1) ? " sha1 " + ((Disk)rom).SHA1.ToLowerInvariant() : "") + + (!ExcludeFields[(int)Field.SHA256] && !String.IsNullOrWhiteSpace(((Disk)rom).SHA256) ? " sha256 " + ((Disk)rom).SHA256.ToLowerInvariant() : "") + + (!ExcludeFields[(int)Field.SHA384] && !String.IsNullOrWhiteSpace(((Disk)rom).SHA384) ? " sha384 " + ((Disk)rom).SHA384.ToLowerInvariant() : "") + + (!ExcludeFields[(int)Field.SHA512] && !String.IsNullOrWhiteSpace(((Disk)rom).SHA512) ? " sha512 " + ((Disk)rom).SHA512.ToLowerInvariant() : "") + + (!ExcludeFields[(int)Field.Status] && ((Disk)rom).ItemStatus != ItemStatus.None ? " flags " + ((Disk)rom).ItemStatus.ToString().ToLowerInvariant() : "") + + " )\n"; + break; + case ItemType.Release: + state += "\trelease ( name\"" + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\"" + + (!ExcludeFields[(int)Field.Region] && !String.IsNullOrWhiteSpace(((Release)rom).Region) ? " region \"" + ((Release)rom).Region + "\"" : "") + + (!ExcludeFields[(int)Field.Language] && !String.IsNullOrWhiteSpace(((Release)rom).Language) ? " language \"" + ((Release)rom).Language + "\"" : "") + + (!ExcludeFields[(int)Field.Date] && !String.IsNullOrWhiteSpace(((Release)rom).Date) ? " date \"" + ((Release)rom).Date + "\"" : "") + + (!ExcludeFields[(int)Field.Default] && ((Release)rom).Default != null + ? "default " + ((Release)rom).Default.ToString().ToLowerInvariant() + : "") + + " )\n"; + break; + case ItemType.Rom: + state += "\trom ( name \"" + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\"" + + (!ExcludeFields[(int)Field.Size] && ((Rom)rom).Size != -1 ? " size " + ((Rom)rom).Size : "") + + (!ExcludeFields[(int)Field.CRC] && !String.IsNullOrWhiteSpace(((Rom)rom).CRC) ? " crc " + ((Rom)rom).CRC.ToLowerInvariant() : "") + + (!ExcludeFields[(int)Field.MD5] && !String.IsNullOrWhiteSpace(((Rom)rom).MD5) ? " md5 " + ((Rom)rom).MD5.ToLowerInvariant() : "") + + (!ExcludeFields[(int)Field.SHA1] && !String.IsNullOrWhiteSpace(((Rom)rom).SHA1) ? " sha1 " + ((Rom)rom).SHA1.ToLowerInvariant() : "") + + (!ExcludeFields[(int)Field.SHA256] && !String.IsNullOrWhiteSpace(((Rom)rom).SHA256) ? " sha256 " + ((Rom)rom).SHA256.ToLowerInvariant() : "") + + (!ExcludeFields[(int)Field.SHA384] && !String.IsNullOrWhiteSpace(((Rom)rom).SHA384) ? " sha384 " + ((Rom)rom).SHA384.ToLowerInvariant() : "") + + (!ExcludeFields[(int)Field.SHA512] && !String.IsNullOrWhiteSpace(((Rom)rom).SHA512) ? " sha512 " + ((Rom)rom).SHA512.ToLowerInvariant() : "") + + (!ExcludeFields[(int)Field.Date] && !String.IsNullOrWhiteSpace(((Rom)rom).Date) ? " date \"" + ((Rom)rom).Date + "\"" : "") + + (!ExcludeFields[(int)Field.Status] && ((Rom)rom).ItemStatus != ItemStatus.None ? " flags " + ((Rom)rom).ItemStatus.ToString().ToLowerInvariant() : "") + + " )\n"; + break; + case ItemType.Sample: + state += "\tsample ( name\"" + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\"" + + " )\n"; + break; + } + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT footer using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteFooter(StreamWriter sw) + { + try + { + string footer = footer = ")\n"; + + // Write the footer out + sw.Write(footer); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + } } diff --git a/SabreTools.Library/DatFiles/DosCenter.cs b/SabreTools.Library/DatFiles/DosCenter.cs index 5d475489..2c1b95e4 100644 --- a/SabreTools.Library/DatFiles/DosCenter.cs +++ b/SabreTools.Library/DatFiles/DosCenter.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; - using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -19,313 +18,313 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { - /// - /// Represents parsing and writing of a DosCenter DAT - /// - internal class DosCenter : DatFile - { - /// - /// Constructor designed for casting a base DatFile - /// - /// Parent DatFile to copy from - public DosCenter(DatFile datFile) - : base(datFile, cloneHeader: false) - { - } + /// + /// Represents parsing and writing of a DosCenter DAT + /// + internal class DosCenter : DatFile + { + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + public DosCenter(DatFile datFile) + : base(datFile, cloneHeader: false) + { + } - /// - /// Parse a DosCenter DAT and return all found games and roms within - /// - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - /// TODO: Pull parsing into this file instead of relying on CMP - public override void ParseFile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, + /// + /// Parse a DosCenter DAT and return all found games and roms within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + /// TODO: Pull parsing into this file instead of relying on CMP + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // ClrMamePro and DosCenter parsing are identical so it just calls one implementation - new ClrMamePro(this).ParseFile(filename, sysid, srcid, keep, clean, remUnicode); - } + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // ClrMamePro and DosCenter parsing are identical so it just calls one implementation + new ClrMamePro(this).ParseFile(filename, sysid, srcid, keep, clean, remUnicode); + } - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the DAT was written correctly, false otherwise - public override bool WriteToFile(string outfile, bool ignoreblanks = false) - { - try - { - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = Utilities.TryCreate(outfile); + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public override bool WriteToFile(string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); - // If we get back null for some reason, just log and return - if (fs == null) - { - Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); - return false; - } + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); - // Write out the header - WriteHeader(sw); + // Write out the header + WriteHeader(sw); - // Write out each of the machines and roms - string lastgame = null; + // Write out each of the machines and roms + string lastgame = null; - // Get a properly sorted set of keys - List keys = Keys; - keys.Sort(new NaturalComparer()); + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); - foreach (string key in keys) - { - List roms = this[key]; + foreach (string key in keys) + { + List roms = this[key]; - // Resolve the names in the block - roms = DatItem.ResolveNames(roms); + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); - for (int index = 0; index < roms.Count; index++) - { - DatItem rom = roms[index]; + for (int index = 0; index < roms.Count; index++) + { + DatItem rom = roms[index]; - // There are apparently times when a null rom can skip by, skip them - if (rom.Name == null || rom.MachineName == null) - { - Globals.Logger.Warning("Null rom found!"); - continue; - } + // There are apparently times when a null rom can skip by, skip them + if (rom.Name == null || rom.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } - List newsplit = rom.MachineName.Split('\\').ToList(); + List newsplit = rom.MachineName.Split('\\').ToList(); - // If we have a different game and we're not at the start of the list, output the end of last item - if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteEndGame(sw, rom); - } + // If we have a different game and we're not at the start of the list, output the end of last item + if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteEndGame(sw, rom); + } - // If we have a new game, output the beginning of the new item - if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteStartGame(sw, rom); - } + // If we have a new game, output the beginning of the new item + if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteStartGame(sw, rom); + } - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (rom.ItemType == ItemType.Rom - && ((Rom)rom).Size == -1 - && ((Rom)rom).CRC == "null") - { - Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.ItemType == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); - rom.Name = (rom.Name == "null" ? "-" : rom.Name); - ((Rom)rom).Size = Constants.SizeZero; - ((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null; - ((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null; - ((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null; - ((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null; - ((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null; - ((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null; - } + rom.Name = (rom.Name == "null" ? "-" : rom.Name); + ((Rom)rom).Size = Constants.SizeZero; + ((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null; + ((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null; + ((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null; + ((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null; + ((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null; + ((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null; + } - // Now, output the rom data - WriteDatItem(sw, rom, ignoreblanks); + // Now, output the rom data + WriteDatItem(sw, rom, ignoreblanks); - // Set the new data to compare against - lastgame = rom.MachineName; - } - } + // Set the new data to compare against + lastgame = rom.MachineName; + } + } - // Write the file footer out - WriteFooter(sw); + // Write the file footer out + WriteFooter(sw); - Globals.Logger.Verbose("File written!" + Environment.NewLine); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteHeader(StreamWriter sw) - { - try - { - string header = "DOSCenter (\n" + - "\tName: " + Name + "\n" + - "\tDescription: " + Description + "\n" + - "\tVersion: " + Version + "\n" + - "\tDate: " + Date + "\n" + - "\tAuthor: " + Author + "\n" + - "\tHomepage: " + Homepage + "\n" + - "\tComment: " + Comment + "\n" + - ")\n"; + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteHeader(StreamWriter sw) + { + try + { + string header = "DOSCenter (\n" + + "\tName: " + Name + "\n" + + "\tDescription: " + Description + "\n" + + "\tVersion: " + Version + "\n" + + "\tDate: " + Date + "\n" + + "\tAuthor: " + Author + "\n" + + "\tHomepage: " + Homepage + "\n" + + "\tComment: " + Comment + "\n" + + ")\n"; - // Write the header out - sw.Write(header); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + // Write the header out + sw.Write(header); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out Game start using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if the data was written, false on error - private bool WriteStartGame(StreamWriter sw, DatItem rom) - { - try - { - // No game should start with a path separator - if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) - { - rom.MachineName = rom.MachineName.Substring(1); - } + /// + /// Write out Game start using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if the data was written, false on error + private bool WriteStartGame(StreamWriter sw, DatItem rom) + { + try + { + // No game should start with a path separator + if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) + { + rom.MachineName = rom.MachineName.Substring(1); + } - string state = "game (\n\tname \"" + (!ExcludeFields[(int)Field.MachineName] ? rom.MachineName + ".zip" : "") + "\"\n"; + string state = "game (\n\tname \"" + (!ExcludeFields[(int)Field.MachineName] ? rom.MachineName + ".zip" : "") + "\"\n"; - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out Game end using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if the data was written, false on error - private bool WriteEndGame(StreamWriter sw, DatItem rom) - { - try - { - string state = (!ExcludeFields[(int)Field.SampleOf] && String.IsNullOrWhiteSpace(rom.SampleOf) ? "" : "\tsampleof \"" + rom.SampleOf + "\"\n") + ")\n"; + /// + /// Write out Game end using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if the data was written, false on error + private bool WriteEndGame(StreamWriter sw, DatItem rom) + { + try + { + string state = (!ExcludeFields[(int)Field.SampleOf] && String.IsNullOrWhiteSpace(rom.SampleOf) ? "" : "\tsampleof \"" + rom.SampleOf + "\"\n") + ")\n"; - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the data was written, false on error - private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) - { - // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip - if (ignoreblanks - && (rom.ItemType == ItemType.Rom - && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) - { - return true; - } + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the data was written, false on error + private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.ItemType == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } - try - { - string state = ""; + try + { + string state = ""; - // Pre-process the item name - ProcessItemName(rom, true); + // Pre-process the item name + ProcessItemName(rom, true); - switch (rom.ItemType) - { - case ItemType.Archive: - case ItemType.BiosSet: - case ItemType.Disk: - case ItemType.Release: - case ItemType.Sample: - // We don't output these at all for DosCenter - break; - case ItemType.Rom: - state += "\tfile ( name " + (!ExcludeFields[(int)Field.Name] ? ((Rom)rom).Name : "") - + (!ExcludeFields[(int)Field.Size] && ((Rom)rom).Size != -1 ? " size " + ((Rom)rom).Size : "") - + (!ExcludeFields[(int)Field.Date] && !String.IsNullOrWhiteSpace(((Rom)rom).Date) ? " date " + ((Rom)rom).Date : "") - + (!ExcludeFields[(int)Field.CRC] && !String.IsNullOrWhiteSpace(((Rom)rom).CRC) ? " crc " + ((Rom)rom).CRC.ToLowerInvariant() : "") - + " )\n"; - break; - } + switch (rom.ItemType) + { + case ItemType.Archive: + case ItemType.BiosSet: + case ItemType.Disk: + case ItemType.Release: + case ItemType.Sample: + // We don't output these at all for DosCenter + break; + case ItemType.Rom: + state += "\tfile ( name " + (!ExcludeFields[(int)Field.Name] ? ((Rom)rom).Name : "") + + (!ExcludeFields[(int)Field.Size] && ((Rom)rom).Size != -1 ? " size " + ((Rom)rom).Size : "") + + (!ExcludeFields[(int)Field.Date] && !String.IsNullOrWhiteSpace(((Rom)rom).Date) ? " date " + ((Rom)rom).Date : "") + + (!ExcludeFields[(int)Field.CRC] && !String.IsNullOrWhiteSpace(((Rom)rom).CRC) ? " crc " + ((Rom)rom).CRC.ToLowerInvariant() : "") + + " )\n"; + break; + } - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out DAT footer using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteFooter(StreamWriter sw) - { - try - { - string footer = ")\n"; + /// + /// Write out DAT footer using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteFooter(StreamWriter sw) + { + try + { + string footer = ")\n"; - // Write the footer out - sw.Write(footer); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + // Write the footer out + sw.Write(footer); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } - } + return true; + } + } } diff --git a/SabreTools.Library/DatFiles/Hashfile.cs b/SabreTools.Library/DatFiles/Hashfile.cs index 0fa1920d..8c11d9bb 100644 --- a/SabreTools.Library/DatFiles/Hashfile.cs +++ b/SabreTools.Library/DatFiles/Hashfile.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Text; - using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -19,282 +18,282 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { - /// - /// Represents parsing and writing of a hashfile such as an SFV, MD5, or SHA-1 file - /// - internal class Hashfile : DatFile - { - // Private instance variables specific to Hashfile DATs - Hash _hash; + /// + /// Represents parsing and writing of a hashfile such as an SFV, MD5, or SHA-1 file + /// + internal class Hashfile : DatFile + { + // Private instance variables specific to Hashfile DATs + Hash _hash; - /// - /// Constructor designed for casting a base DatFile - /// - /// Parent DatFile to copy from - /// Type of hash that is associated with this DAT - public Hashfile(DatFile datFile, Hash hash) - : base(datFile, cloneHeader: false) - { - _hash = hash; - } + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + /// Type of hash that is associated with this DAT + public Hashfile(DatFile datFile, Hash hash) + : base(datFile, cloneHeader: false) + { + _hash = hash; + } - /// - /// Parse a hashfile or SFV and return all found games and roms within - /// - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - public override void ParseFile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, + /// + /// Parse a hashfile or SFV and return all found games and roms within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Open a file reader - Encoding enc = Utilities.GetEncoding(filename); - StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc); + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Open a file reader + Encoding enc = Utilities.GetEncoding(filename); + StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc); - while (!sr.EndOfStream) - { - string line = sr.ReadLine(); + while (!sr.EndOfStream) + { + string line = sr.ReadLine(); - // Split the line and get the name and hash - string[] split = line.Split(' '); - string name = ""; - string hash = ""; + // Split the line and get the name and hash + string[] split = line.Split(' '); + string name = ""; + string hash = ""; - // If we have CRC, then it's an SFV file and the name is first are - if ((_hash & Hash.CRC) != 0) - { - name = split[0].Replace("*", String.Empty); - hash = split[1]; - } - // Otherwise, the name is second - else - { - name = split[1].Replace("*", String.Empty); - hash = split[0]; - } + // If we have CRC, then it's an SFV file and the name is first are + if ((_hash & Hash.CRC) != 0) + { + name = split[0].Replace("*", String.Empty); + hash = split[1]; + } + // Otherwise, the name is second + else + { + name = split[1].Replace("*", String.Empty); + hash = split[0]; + } - Rom rom = new Rom - { - Name = name, - Size = -1, - CRC = ((_hash & Hash.CRC) != 0 ? Utilities.CleanHashData(hash, Constants.CRCLength) : null), - MD5 = ((_hash & Hash.MD5) != 0 ? Utilities.CleanHashData(hash, Constants.MD5Length) : null), - SHA1 = ((_hash & Hash.SHA1) != 0 ? Utilities.CleanHashData(hash, Constants.SHA1Length) : null), - SHA256 = ((_hash & Hash.SHA256) != 0 ? Utilities.CleanHashData(hash, Constants.SHA256Length) : null), - SHA384 = ((_hash & Hash.SHA384) != 0 ? Utilities.CleanHashData(hash, Constants.SHA384Length) : null), - SHA512 = ((_hash & Hash.SHA512) != 0 ? Utilities.CleanHashData(hash, Constants.SHA512Length) : null), - ItemStatus = ItemStatus.None, + Rom rom = new Rom + { + Name = name, + Size = -1, + CRC = ((_hash & Hash.CRC) != 0 ? Utilities.CleanHashData(hash, Constants.CRCLength) : null), + MD5 = ((_hash & Hash.MD5) != 0 ? Utilities.CleanHashData(hash, Constants.MD5Length) : null), + SHA1 = ((_hash & Hash.SHA1) != 0 ? Utilities.CleanHashData(hash, Constants.SHA1Length) : null), + SHA256 = ((_hash & Hash.SHA256) != 0 ? Utilities.CleanHashData(hash, Constants.SHA256Length) : null), + SHA384 = ((_hash & Hash.SHA384) != 0 ? Utilities.CleanHashData(hash, Constants.SHA384Length) : null), + SHA512 = ((_hash & Hash.SHA512) != 0 ? Utilities.CleanHashData(hash, Constants.SHA512Length) : null), + ItemStatus = ItemStatus.None, - MachineName = Path.GetFileNameWithoutExtension(filename), + MachineName = Path.GetFileNameWithoutExtension(filename), - SystemID = sysid, - SourceID = srcid, - }; + SystemID = sysid, + SourceID = srcid, + }; - // Now process and add the rom - ParseAddHelper(rom, clean, remUnicode); - } + // Now process and add the rom + ParseAddHelper(rom, clean, remUnicode); + } - sr.Dispose(); - } + sr.Dispose(); + } - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the DAT was written correctly, false otherwise - public override bool WriteToFile(string outfile, bool ignoreblanks = false) - { - try - { - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = Utilities.TryCreate(outfile); + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public override bool WriteToFile(string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); - // If we get back null for some reason, just log and return - if (fs == null) - { - Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); - return false; - } + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); - // Get a properly sorted set of keys - List keys = Keys; - keys.Sort(new NaturalComparer()); + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); - foreach (string key in keys) - { - List roms = this[key]; + foreach (string key in keys) + { + List roms = this[key]; - // Resolve the names in the block - roms = DatItem.ResolveNames(roms); + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); - for (int index = 0; index < roms.Count; index++) - { - DatItem rom = roms[index]; + for (int index = 0; index < roms.Count; index++) + { + DatItem rom = roms[index]; - // There are apparently times when a null rom can skip by, skip them - if (rom.Name == null || rom.MachineName == null) - { - Globals.Logger.Warning("Null rom found!"); - continue; - } + // There are apparently times when a null rom can skip by, skip them + if (rom.Name == null || rom.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (rom.ItemType == ItemType.Rom - && ((Rom)rom).Size == -1 - && ((Rom)rom).CRC == "null") - { - Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); - } + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.ItemType == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + } - // Now, output the rom data - WriteDatItem(sw, rom, ignoreblanks); - } - } + // Now, output the rom data + WriteDatItem(sw, rom, ignoreblanks); + } + } - Globals.Logger.Verbose("File written!" + Environment.NewLine); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the data was written, false on error - private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) - { - // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip - if (ignoreblanks - && (rom.ItemType == ItemType.Rom - && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) - { - return true; - } + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the data was written, false on error + private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.ItemType == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } - try - { - string state = ""; + try + { + string state = ""; - // Pre-process the item name - ProcessItemName(rom, true); + // Pre-process the item name + ProcessItemName(rom, true); - switch (_hash) - { - case Hash.MD5: - if (rom.ItemType == ItemType.Rom) - { - state += (!ExcludeFields[(int)Field.MD5] ? ((Rom)rom).MD5 : "") - + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") - + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; - } - else if (rom.ItemType == ItemType.Disk) - { - state += (!ExcludeFields[(int)Field.MD5] ? ((Disk)rom).MD5 : "") - + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") - + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; - } - break; - case Hash.CRC: - if (rom.ItemType == ItemType.Rom) - { - state += (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") - + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") - + " " + (!ExcludeFields[(int)Field.CRC] ? ((Rom)rom).CRC : "") + "\n"; - } - break; - case Hash.SHA1: - if (rom.ItemType == ItemType.Rom) - { - state += (!ExcludeFields[(int)Field.SHA1] ? ((Rom)rom).SHA1 : "") - + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") - + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; - } - else if (rom.ItemType == ItemType.Disk) - { - state += (!ExcludeFields[(int)Field.SHA1] ? ((Disk)rom).SHA1 : "") - + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") - + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; - } - break; - case Hash.SHA256: - if (rom.ItemType == ItemType.Rom) - { - state += (!ExcludeFields[(int)Field.SHA256] ? ((Rom)rom).SHA256 : "") - + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") - + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; - } - else if (rom.ItemType == ItemType.Disk) - { - state += (!ExcludeFields[(int)Field.SHA256] ? ((Disk)rom).SHA256 : "") - + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") - + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; - } - break; - case Hash.SHA384: - if (rom.ItemType == ItemType.Rom) - { - state += (!ExcludeFields[(int)Field.SHA384] ? ((Rom)rom).SHA384 : "") - + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") - + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; - } - else if (rom.ItemType == ItemType.Disk) - { - state += (!ExcludeFields[(int)Field.SHA384] ? ((Disk)rom).SHA384 : "") - + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") - + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; - } - break; - case Hash.SHA512: - if (rom.ItemType == ItemType.Rom) - { - state += (!ExcludeFields[(int)Field.SHA512] ? ((Rom)rom).SHA512 : "") - + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") - + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; - } - else if (rom.ItemType == ItemType.Disk) - { - state += (!ExcludeFields[(int)Field.SHA512] ? ((Disk)rom).SHA512 : "") - + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") - + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; - } - break; - } + switch (_hash) + { + case Hash.MD5: + if (rom.ItemType == ItemType.Rom) + { + state += (!ExcludeFields[(int)Field.MD5] ? ((Rom)rom).MD5 : "") + + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; + } + else if (rom.ItemType == ItemType.Disk) + { + state += (!ExcludeFields[(int)Field.MD5] ? ((Disk)rom).MD5 : "") + + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; + } + break; + case Hash.CRC: + if (rom.ItemType == ItemType.Rom) + { + state += (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + + " " + (!ExcludeFields[(int)Field.CRC] ? ((Rom)rom).CRC : "") + "\n"; + } + break; + case Hash.SHA1: + if (rom.ItemType == ItemType.Rom) + { + state += (!ExcludeFields[(int)Field.SHA1] ? ((Rom)rom).SHA1 : "") + + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; + } + else if (rom.ItemType == ItemType.Disk) + { + state += (!ExcludeFields[(int)Field.SHA1] ? ((Disk)rom).SHA1 : "") + + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; + } + break; + case Hash.SHA256: + if (rom.ItemType == ItemType.Rom) + { + state += (!ExcludeFields[(int)Field.SHA256] ? ((Rom)rom).SHA256 : "") + + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; + } + else if (rom.ItemType == ItemType.Disk) + { + state += (!ExcludeFields[(int)Field.SHA256] ? ((Disk)rom).SHA256 : "") + + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; + } + break; + case Hash.SHA384: + if (rom.ItemType == ItemType.Rom) + { + state += (!ExcludeFields[(int)Field.SHA384] ? ((Rom)rom).SHA384 : "") + + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; + } + else if (rom.ItemType == ItemType.Disk) + { + state += (!ExcludeFields[(int)Field.SHA384] ? ((Disk)rom).SHA384 : "") + + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; + } + break; + case Hash.SHA512: + if (rom.ItemType == ItemType.Rom) + { + state += (!ExcludeFields[(int)Field.SHA512] ? ((Rom)rom).SHA512 : "") + + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; + } + else if (rom.ItemType == ItemType.Disk) + { + state += (!ExcludeFields[(int)Field.SHA512] ? ((Disk)rom).SHA512 : "") + + " *" + (!ExcludeFields[(int)Field.MachineName] && GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + + (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; + } + break; + } - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } - } + return true; + } + } } diff --git a/SabreTools.Library/DatFiles/Listrom.cs b/SabreTools.Library/DatFiles/Listrom.cs index bf68455e..c123988d 100644 --- a/SabreTools.Library/DatFiles/Listrom.cs +++ b/SabreTools.Library/DatFiles/Listrom.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; - using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -20,497 +19,497 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { - /// - /// Represents parsing and writing of a MAME Listrom DAT - /// - internal class Listrom : DatFile - { - /// - /// Constructor designed for casting a base DatFile - /// - /// Parent DatFile to copy from - public Listrom(DatFile datFile) - : base(datFile, cloneHeader: false) - { - } - - /// - /// Parse a MAME Listrom DAT and return all found games and roms within - /// - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - /// - /// In a new style MAME listrom DAT, each game has the following format: - /// - /// ROMs required for driver "005". - /// Name Size Checksum - /// 1346b.cpu-u25 2048 CRC(8e68533e) SHA1(a257c556d31691068ed5c991f1fb2b51da4826db) - /// 6331.sound-u8 32 BAD CRC(1d298cb0) SHA1(bb0bb62365402543e3154b9a77be9c75010e6abc) BAD_DUMP - /// 16v8h-blue.u24 279 NO GOOD DUMP KNOWN - /// - public override void ParseFile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Open a file reader - Encoding enc = Utilities.GetEncoding(filename); - StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc); - - string gamename = ""; - while (!sr.EndOfStream) - { - string line = sr.ReadLine().Trim(); - - // If we have a blank line, we just skip it - if (String.IsNullOrWhiteSpace(line)) - { - continue; - } - - // If we have the descriptor line, ignore it - else if (line == "Name Size Checksum") - { - continue; - } - - // If we have the beginning of a game, set the name of the game - else if (line.StartsWith("ROMs required for")) - { - gamename = Regex.Match(line, @"^ROMs required for \S*? ""(.*?)""\.").Groups[1].Value; - } - - // If we have a machine with no required roms (usually internal devices), skip it - else if (line.StartsWith("No ROMs required for")) - { - continue; - } - - // Otherwise, we assume we have a rom that we need to add - else - { - // First, we preprocess the line so that the rom name is consistently correct - string romname = ""; - string[] split = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); - - // If the line doesn't have the 4 spaces of padding, check for 3 - if (split.Length == 1) - { - split = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); - } - - // If the split is still unsuccessful, log it and skip - if (split.Length == 1) - { - Globals.Logger.Warning("Possibly malformed line: '{0}'", line); - } - - romname = split[0]; - line = line.Substring(romname.Length); - - // Next we separate the ROM into pieces - split = line.Split(new char[0], StringSplitOptions.RemoveEmptyEntries); - - // Standard Disks have 2 pieces (name, sha1) - if (split.Length == 1) - { - Disk disk = new Disk() - { - Name = romname, - SHA1 = Utilities.CleanListromHashData(split[0]), - - MachineName = gamename, - }; - - ParseAddHelper(disk, clean, remUnicode); - } - - // Baddump Disks have 4 pieces (name, BAD, sha1, BAD_DUMP) - else if (split.Length == 3 && line.EndsWith("BAD_DUMP")) - { - Disk disk = new Disk() - { - Name = romname, - SHA1 = Utilities.CleanListromHashData(split[1]), - ItemStatus = ItemStatus.BadDump, - - MachineName = gamename, - }; - - ParseAddHelper(disk, clean, remUnicode); - } - - // Standard ROMs have 4 pieces (name, size, crc, sha1) - else if (split.Length == 3) - { - if (!Int64.TryParse(split[0], out long size)) - { - size = 0; - } - - Rom rom = new Rom() - { - Name = romname, - Size = size, - CRC = Utilities.CleanListromHashData(split[1]), - SHA1 = Utilities.CleanListromHashData(split[2]), - - MachineName = gamename, - }; - - ParseAddHelper(rom, clean, remUnicode); - } - - // Nodump Disks have 5 pieces (name, NO, GOOD, DUMP, KNOWN) - else if (split.Length == 4 && line.EndsWith("NO GOOD DUMP KNOWN")) - { - Disk disk = new Disk() - { - Name = romname, - ItemStatus = ItemStatus.Nodump, - - MachineName = gamename, - }; - - ParseAddHelper(disk, clean, remUnicode); - } - - // Baddump ROMs have 6 pieces (name, size, BAD, crc, sha1, BAD_DUMP) - else if (split.Length == 5 && line.EndsWith("BAD_DUMP")) - { - if (!Int64.TryParse(split[0], out long size)) - { - size = 0; - } - - Rom rom = new Rom() - { - Name = romname, - Size = size, - CRC = Utilities.CleanListromHashData(split[2]), - SHA1 = Utilities.CleanListromHashData(split[3]), - ItemStatus = ItemStatus.BadDump, - - MachineName = gamename, - }; - - ParseAddHelper(rom, clean, remUnicode); - } - - // Nodump ROMs have 6 pieces (name, size, NO, GOOD, DUMP, KNOWN) - else if (split.Length == 5 && line.EndsWith("NO GOOD DUMP KNOWN")) - { - if (!Int64.TryParse(split[0], out long size)) - { - size = 0; - } - - Rom rom = new Rom() - { - Name = romname, - Size = size, - ItemStatus = ItemStatus.Nodump, - - MachineName = gamename, - }; - - ParseAddHelper(rom, clean, remUnicode); - } - - // If we have something else, it's invalid - else - { - Globals.Logger.Warning("Invalid line detected: '{0} {1}'", romname, line); - } - } - } - } - - - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the DAT was written correctly, false otherwise - public override bool WriteToFile(string outfile, bool ignoreblanks = false) - { - try - { - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = Utilities.TryCreate(outfile); - - // If we get back null for some reason, just log and return - if (fs == null) - { - Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); - return false; - } - - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); - - // Write out each of the machines and roms - string lastgame = null; - - // Get a properly sorted set of keys - List keys = Keys; - keys.Sort(new NaturalComparer()); - - foreach (string key in keys) - { - List roms = this[key]; - - // Resolve the names in the block - roms = DatItem.ResolveNames(roms); - - for (int index = 0; index < roms.Count; index++) - { - DatItem rom = roms[index]; - - // There are apparently times when a null rom can skip by, skip them - if (rom.Name == null || rom.MachineName == null) - { - Globals.Logger.Warning("Null rom found!"); - continue; - } - - // If we have a different game and we're not at the start of the list, output the end of last item - if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteEndGame(sw); - } - - // If we have a new game, output the beginning of the new item - if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteStartGame(sw, rom); - } - - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (rom.ItemType == ItemType.Rom - && ((Rom)rom).Size == -1 - && ((Rom)rom).CRC == "null") - { - Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); - - rom.Name = (rom.Name == "null" ? "-" : rom.Name); - ((Rom)rom).Size = Constants.SizeZero; - ((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null; - ((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null; - ((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null; - ((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null; - ((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null; - ((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null; - } - - // Now, output the rom data - WriteDatItem(sw, rom, ignoreblanks); - - // Set the new data to compare against - lastgame = rom.MachineName; - } - } - - Globals.Logger.Verbose("File written!" + Environment.NewLine); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if the data was written, false on error - private bool WriteStartGame(StreamWriter sw, DatItem rom) - { - try - { - // No game should start with a path separator - if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) - { - rom.MachineName = rom.MachineName.Substring(1); - } - - string state = "ROMs required for driver \"" + (!ExcludeFields[(int)Field.MachineName] ? rom.MachineName : "") + "\".\n" + - "Name Size Checksum\n"; - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out Game end using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteEndGame(StreamWriter sw) - { - try - { - string state = "\n"; - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the data was written, false on error - private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) - { - // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip - if (ignoreblanks - && (rom.ItemType == ItemType.Rom - && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) - { - return true; - } - - try - { - string state = ""; - - // Pre-process the item name - ProcessItemName(rom, true); - - switch (rom.ItemType) - { - case ItemType.Archive: - case ItemType.BiosSet: - case ItemType.Release: - case ItemType.Sample: - // We don't output these at all - break; - case ItemType.Disk: - // The name is padded out to a particular length - if (rom.Name.Length < 43) - { - state += rom.Name.PadRight(43, ' '); - } - else - { - state += rom.Name + " "; - } - - // If we have a baddump, put the first indicator - if (!ExcludeFields[(int)Field.Status] && ((Disk)rom).ItemStatus == ItemStatus.BadDump) - { - state += " BAD"; - } - - // If we have a nodump, write out the indicator - if (!ExcludeFields[(int)Field.Status] && ((Disk)rom).ItemStatus == ItemStatus.Nodump) - { - state += " NO GOOD DUMP KNOWN"; - } - // Otherwise, write out the SHA-1 hash - else if (!ExcludeFields[(int)Field.SHA1]) - { - state += " SHA1(" + ((Disk)rom).SHA1 + ")"; - } - - // If we have a baddump, put the second indicator - if (!ExcludeFields[(int)Field.Status] && ((Disk)rom).ItemStatus == ItemStatus.BadDump) - { - state += " BAD_DUMP"; - } - - state += "\n"; - break; - case ItemType.Rom: - // The name is padded out to a particular length - if (rom.Name.Length < 40) - { - state += rom.Name.PadRight(43 - (((Rom)rom).Size.ToString().Length), ' '); - } - else - { - state += rom.Name + " "; - } - - // If we don't have a nodump, write out the size - if (((Rom)rom).ItemStatus != ItemStatus.Nodump) - { - state += ((Rom)rom).Size; - } - - // If we have a baddump, put the first indicator - if (!ExcludeFields[(int)Field.Status] && ((Rom)rom).ItemStatus == ItemStatus.BadDump) - { - state += " BAD"; - } - - // If we have a nodump, write out the indicator - if (!ExcludeFields[(int)Field.Status] && ((Rom)rom).ItemStatus == ItemStatus.Nodump) - { - state += " NO GOOD DUMP KNOWN"; - } - // Otherwise, write out the CRC and SHA-1 hashes - else - { - state += (!ExcludeFields[(int)Field.CRC] ? " CRC(" + ((Rom)rom).CRC + ")" : ""); - state += (!ExcludeFields[(int)Field.SHA1] ? " SHA1(" + ((Rom)rom).SHA1 + ")" : ""); - } - - // If we have a baddump, put the second indicator - if (!ExcludeFields[(int)Field.Status] && ((Rom)rom).ItemStatus == ItemStatus.BadDump) - { - state += " BAD_DUMP"; - } - - state += "\n"; - break; - } - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - } + /// + /// Represents parsing and writing of a MAME Listrom DAT + /// + internal class Listrom : DatFile + { + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + public Listrom(DatFile datFile) + : base(datFile, cloneHeader: false) + { + } + + /// + /// Parse a MAME Listrom DAT and return all found games and roms within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + /// + /// In a new style MAME listrom DAT, each game has the following format: + /// + /// ROMs required for driver "005". + /// Name Size Checksum + /// 1346b.cpu-u25 2048 CRC(8e68533e) SHA1(a257c556d31691068ed5c991f1fb2b51da4826db) + /// 6331.sound-u8 32 BAD CRC(1d298cb0) SHA1(bb0bb62365402543e3154b9a77be9c75010e6abc) BAD_DUMP + /// 16v8h-blue.u24 279 NO GOOD DUMP KNOWN + /// + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Open a file reader + Encoding enc = Utilities.GetEncoding(filename); + StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc); + + string gamename = ""; + while (!sr.EndOfStream) + { + string line = sr.ReadLine().Trim(); + + // If we have a blank line, we just skip it + if (String.IsNullOrWhiteSpace(line)) + { + continue; + } + + // If we have the descriptor line, ignore it + else if (line == "Name Size Checksum") + { + continue; + } + + // If we have the beginning of a game, set the name of the game + else if (line.StartsWith("ROMs required for")) + { + gamename = Regex.Match(line, @"^ROMs required for \S*? ""(.*?)""\.").Groups[1].Value; + } + + // If we have a machine with no required roms (usually internal devices), skip it + else if (line.StartsWith("No ROMs required for")) + { + continue; + } + + // Otherwise, we assume we have a rom that we need to add + else + { + // First, we preprocess the line so that the rom name is consistently correct + string romname = ""; + string[] split = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); + + // If the line doesn't have the 4 spaces of padding, check for 3 + if (split.Length == 1) + { + split = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); + } + + // If the split is still unsuccessful, log it and skip + if (split.Length == 1) + { + Globals.Logger.Warning("Possibly malformed line: '{0}'", line); + } + + romname = split[0]; + line = line.Substring(romname.Length); + + // Next we separate the ROM into pieces + split = line.Split(new char[0], StringSplitOptions.RemoveEmptyEntries); + + // Standard Disks have 2 pieces (name, sha1) + if (split.Length == 1) + { + Disk disk = new Disk() + { + Name = romname, + SHA1 = Utilities.CleanListromHashData(split[0]), + + MachineName = gamename, + }; + + ParseAddHelper(disk, clean, remUnicode); + } + + // Baddump Disks have 4 pieces (name, BAD, sha1, BAD_DUMP) + else if (split.Length == 3 && line.EndsWith("BAD_DUMP")) + { + Disk disk = new Disk() + { + Name = romname, + SHA1 = Utilities.CleanListromHashData(split[1]), + ItemStatus = ItemStatus.BadDump, + + MachineName = gamename, + }; + + ParseAddHelper(disk, clean, remUnicode); + } + + // Standard ROMs have 4 pieces (name, size, crc, sha1) + else if (split.Length == 3) + { + if (!Int64.TryParse(split[0], out long size)) + { + size = 0; + } + + Rom rom = new Rom() + { + Name = romname, + Size = size, + CRC = Utilities.CleanListromHashData(split[1]), + SHA1 = Utilities.CleanListromHashData(split[2]), + + MachineName = gamename, + }; + + ParseAddHelper(rom, clean, remUnicode); + } + + // Nodump Disks have 5 pieces (name, NO, GOOD, DUMP, KNOWN) + else if (split.Length == 4 && line.EndsWith("NO GOOD DUMP KNOWN")) + { + Disk disk = new Disk() + { + Name = romname, + ItemStatus = ItemStatus.Nodump, + + MachineName = gamename, + }; + + ParseAddHelper(disk, clean, remUnicode); + } + + // Baddump ROMs have 6 pieces (name, size, BAD, crc, sha1, BAD_DUMP) + else if (split.Length == 5 && line.EndsWith("BAD_DUMP")) + { + if (!Int64.TryParse(split[0], out long size)) + { + size = 0; + } + + Rom rom = new Rom() + { + Name = romname, + Size = size, + CRC = Utilities.CleanListromHashData(split[2]), + SHA1 = Utilities.CleanListromHashData(split[3]), + ItemStatus = ItemStatus.BadDump, + + MachineName = gamename, + }; + + ParseAddHelper(rom, clean, remUnicode); + } + + // Nodump ROMs have 6 pieces (name, size, NO, GOOD, DUMP, KNOWN) + else if (split.Length == 5 && line.EndsWith("NO GOOD DUMP KNOWN")) + { + if (!Int64.TryParse(split[0], out long size)) + { + size = 0; + } + + Rom rom = new Rom() + { + Name = romname, + Size = size, + ItemStatus = ItemStatus.Nodump, + + MachineName = gamename, + }; + + ParseAddHelper(rom, clean, remUnicode); + } + + // If we have something else, it's invalid + else + { + Globals.Logger.Warning("Invalid line detected: '{0} {1}'", romname, line); + } + } + } + } + + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public override bool WriteToFile(string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); + + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } + + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = this[key]; + + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); + + for (int index = 0; index < roms.Count; index++) + { + DatItem rom = roms[index]; + + // There are apparently times when a null rom can skip by, skip them + if (rom.Name == null || rom.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } + + // If we have a different game and we're not at the start of the list, output the end of last item + if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteEndGame(sw); + } + + // If we have a new game, output the beginning of the new item + if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteStartGame(sw, rom); + } + + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.ItemType == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + + rom.Name = (rom.Name == "null" ? "-" : rom.Name); + ((Rom)rom).Size = Constants.SizeZero; + ((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null; + ((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null; + ((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null; + ((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null; + ((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null; + ((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null; + } + + // Now, output the rom data + WriteDatItem(sw, rom, ignoreblanks); + + // Set the new data to compare against + lastgame = rom.MachineName; + } + } + + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if the data was written, false on error + private bool WriteStartGame(StreamWriter sw, DatItem rom) + { + try + { + // No game should start with a path separator + if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) + { + rom.MachineName = rom.MachineName.Substring(1); + } + + string state = "ROMs required for driver \"" + (!ExcludeFields[(int)Field.MachineName] ? rom.MachineName : "") + "\".\n" + + "Name Size Checksum\n"; + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out Game end using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteEndGame(StreamWriter sw) + { + try + { + string state = "\n"; + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the data was written, false on error + private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.ItemType == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + + // Pre-process the item name + ProcessItemName(rom, true); + + switch (rom.ItemType) + { + case ItemType.Archive: + case ItemType.BiosSet: + case ItemType.Release: + case ItemType.Sample: + // We don't output these at all + break; + case ItemType.Disk: + // The name is padded out to a particular length + if (rom.Name.Length < 43) + { + state += rom.Name.PadRight(43, ' '); + } + else + { + state += rom.Name + " "; + } + + // If we have a baddump, put the first indicator + if (!ExcludeFields[(int)Field.Status] && ((Disk)rom).ItemStatus == ItemStatus.BadDump) + { + state += " BAD"; + } + + // If we have a nodump, write out the indicator + if (!ExcludeFields[(int)Field.Status] && ((Disk)rom).ItemStatus == ItemStatus.Nodump) + { + state += " NO GOOD DUMP KNOWN"; + } + // Otherwise, write out the SHA-1 hash + else if (!ExcludeFields[(int)Field.SHA1]) + { + state += " SHA1(" + ((Disk)rom).SHA1 + ")"; + } + + // If we have a baddump, put the second indicator + if (!ExcludeFields[(int)Field.Status] && ((Disk)rom).ItemStatus == ItemStatus.BadDump) + { + state += " BAD_DUMP"; + } + + state += "\n"; + break; + case ItemType.Rom: + // The name is padded out to a particular length + if (rom.Name.Length < 40) + { + state += rom.Name.PadRight(43 - (((Rom)rom).Size.ToString().Length), ' '); + } + else + { + state += rom.Name + " "; + } + + // If we don't have a nodump, write out the size + if (((Rom)rom).ItemStatus != ItemStatus.Nodump) + { + state += ((Rom)rom).Size; + } + + // If we have a baddump, put the first indicator + if (!ExcludeFields[(int)Field.Status] && ((Rom)rom).ItemStatus == ItemStatus.BadDump) + { + state += " BAD"; + } + + // If we have a nodump, write out the indicator + if (!ExcludeFields[(int)Field.Status] && ((Rom)rom).ItemStatus == ItemStatus.Nodump) + { + state += " NO GOOD DUMP KNOWN"; + } + // Otherwise, write out the CRC and SHA-1 hashes + else + { + state += (!ExcludeFields[(int)Field.CRC] ? " CRC(" + ((Rom)rom).CRC + ")" : ""); + state += (!ExcludeFields[(int)Field.SHA1] ? " SHA1(" + ((Rom)rom).SHA1 + ")" : ""); + } + + // If we have a baddump, put the second indicator + if (!ExcludeFields[(int)Field.Status] && ((Rom)rom).ItemStatus == ItemStatus.BadDump) + { + state += " BAD_DUMP"; + } + + state += "\n"; + break; + } + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + } } diff --git a/SabreTools.Library/DatFiles/Listxml.cs b/SabreTools.Library/DatFiles/Listxml.cs index 0675d740..d10014a3 100644 --- a/SabreTools.Library/DatFiles/Listxml.cs +++ b/SabreTools.Library/DatFiles/Listxml.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Text; using System.Web; using System.Xml; - using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -20,857 +19,857 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { - /// - /// Represents parsing and writing of a MAME XML DAT - /// - /// TODO: Verify that all write for this DatFile type is correct - internal class Listxml : DatFile - { - /// - /// Constructor designed for casting a base DatFile - /// - /// Parent DatFile to copy from - public Listxml(DatFile datFile) - : base(datFile, cloneHeader: false) - { - } - - /// - /// Parse a MAME XML DAT and return all found games and roms within - /// - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - /// - /// - public override void ParseFile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Prepare all internal variables - Encoding enc = Utilities.GetEncoding(filename); - XmlReader xtr = Utilities.GetXmlTextReader(filename); - - // If we got a null reader, just return - if (xtr == null) - { - return; - } - - // Otherwise, read the file to the end - try - { - xtr.MoveToContent(); - while (!xtr.EOF) - { - // We only want elements - if (xtr.NodeType != XmlNodeType.Element) - { - xtr.Read(); - continue; - } - - switch (xtr.Name) - { - case "mame": - Name = (String.IsNullOrWhiteSpace(Name) ? xtr.GetAttribute("build") : Name); - Description = (String.IsNullOrWhiteSpace(Description) ? Name : Name); - // string mame_debug = xtr.GetAttribute("debug"); // (yes|no) "no" - // string mame_mameconfig = xtr.GetAttribute("mameconfig"); CDATA - xtr.Read(); - break; - // Handle M1 DATs since they're 99% the same as a SL DAT - case "m1": - Name = (String.IsNullOrWhiteSpace(Name) ? "M1" : Name); - Description = (String.IsNullOrWhiteSpace(Description) ? "M1" : Description); - Version = (String.IsNullOrWhiteSpace(Version) ? xtr.GetAttribute("version") ?? "" : Version); - xtr.Read(); - break; - // We want to process the entire subtree of the machine - case "game": // Some older DATs still use "game" - case "machine": - ReadMachine(xtr.ReadSubtree(), filename, sysid, srcid, keep, clean, remUnicode); - - // Skip the machine now that we've processed it - xtr.Skip(); - break; - default: - xtr.Read(); - break; - } - } - } - catch (Exception ex) - { - Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); - - // For XML errors, just skip the affected node - xtr?.Read(); - } - - xtr.Dispose(); - } - - /// - /// Read machine information - /// - /// XmlReader representing a machine block - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private void ReadMachine( - XmlReader reader, - - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // If we have an empty machine, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - string key = ""; - string temptype = reader.Name; - bool containsItems = false; - - // Create a new machine - MachineType machineType = MachineType.NULL; - if (Utilities.GetYesNo(reader.GetAttribute("isbios")) == true) - { - machineType |= MachineType.Bios; - } - if (Utilities.GetYesNo(reader.GetAttribute("isdevice")) == true) - { - machineType |= MachineType.Device; - } - if (Utilities.GetYesNo(reader.GetAttribute("ismechanical")) == true) - { - machineType |= MachineType.Mechanical; - } - - Machine machine = new Machine - { - Name = reader.GetAttribute("name"), - Description = reader.GetAttribute("name"), - SourceFile = reader.GetAttribute("sourcefile"), - Runnable = Utilities.GetYesNo(reader.GetAttribute("runnable")), - - Comment = "", - - CloneOf = reader.GetAttribute("cloneof") ?? "", - RomOf = reader.GetAttribute("romof") ?? "", - SampleOf = reader.GetAttribute("sampleof") ?? "", - Devices = new List(), - SlotOptions = new List(), - - MachineType = (machineType == MachineType.NULL ? MachineType.None : machineType), - }; - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the roms from the machine - switch (reader.Name) - { - case "description": - machine.Description = reader.ReadElementContentAsString(); - break; - case "year": - machine.Year = reader.ReadElementContentAsString(); - break; - case "manufacturer": - machine.Manufacturer = reader.ReadElementContentAsString(); - break; - case "biosset": - containsItems = true; - - DatItem biosset = new BiosSet - { - Name = reader.GetAttribute("name"), - Description = reader.GetAttribute("description"), - Default = Utilities.GetYesNo(reader.GetAttribute("default")), - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - biosset.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(biosset, clean, remUnicode); - - reader.Read(); - break; - case "rom": - containsItems = true; - - DatItem rom = new Rom - { - Name = reader.GetAttribute("name"), - Bios = reader.GetAttribute("bios"), - Size = Utilities.GetSize(reader.GetAttribute("size")), - CRC = Utilities.CleanHashData(reader.GetAttribute("crc"), Constants.CRCLength), - MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), - SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), - SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), - SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), - SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), - MergeTag = reader.GetAttribute("merge"), - Region = reader.GetAttribute("region"), - Offset = reader.GetAttribute("offset"), - ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), - Optional = Utilities.GetYesNo(reader.GetAttribute("optional")), - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - rom.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(rom, clean, remUnicode); - - reader.Read(); - break; - case "disk": - containsItems = true; - - DatItem disk = new Disk - { - Name = reader.GetAttribute("name"), - MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), - SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), - SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), - SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), - SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), - MergeTag = reader.GetAttribute("merge"), - Region = reader.GetAttribute("region"), - Index = reader.GetAttribute("index"), - Writable = Utilities.GetYesNo(reader.GetAttribute("writable")), - ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), - Optional = Utilities.GetYesNo(reader.GetAttribute("optional")), - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - disk.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(disk, clean, remUnicode); - - reader.Read(); - break; - case "device_ref": - string device_ref_name = reader.GetAttribute("name"); - if (!machine.Devices.Contains(device_ref_name)) - { - machine.Devices.Add(device_ref_name); - } - - reader.Read(); - break; - case "sample": - containsItems = true; - - DatItem samplerom = new Sample - { - Name = reader.GetAttribute("name"), - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - samplerom.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(samplerom, clean, remUnicode); - - reader.Read(); - break; - case "chip": - // string chip_name = reader.GetAttribute("name"); - // string chip_tag = reader.GetAttribute("tag"); - // string chip_type = reader.GetAttribute("type"); // (cpu|audio) - // string chip_clock = reader.GetAttribute("clock"); - - reader.Read(); - break; - case "display": - // string display_tag = reader.GetAttribute("tag"); - // string display_type = reader.GetAttribute("type"); // (raster|vector|lcd|svg|unknown) - // string display_rotate = reader.GetAttribute("rotate"); // (0|90|180|270) - // bool? display_flipx = Utilities.GetYesNo(reader.GetAttribute("flipx")); - // string display_width = reader.GetAttribute("width"); - // string display_height = reader.GetAttribute("height"); - // string display_refresh = reader.GetAttribute("refresh"); - // string display_pixclock = reader.GetAttribute("pixclock"); - // string display_htotal = reader.GetAttribute("htotal"); - // string display_hbend = reader.GetAttribute("hbend"); - // string display_hbstart = reader.GetAttribute("hbstart"); - // string display_vtotal = reader.GetAttribute("vtotal"); - // string display_vbend = reader.GetAttribute("vbend"); - // string display_vbstart = reader.GetAttribute("vbstart"); - - reader.Read(); - break; - case "sound": - // string sound_channels = reader.GetAttribute("channels"); - - reader.Read(); - break; - case "condition": - // string condition_tag = reader.GetAttribute("tag"); - // string condition_mask = reader.GetAttribute("mask"); - // string condition_relation = reader.GetAttribute("relation"); // (eq|ne|gt|le|lt|ge) - // string condition_value = reader.GetAttribute("value"); - - reader.Read(); - break; - case "input": - // bool? input_service = Utilities.GetYesNo(reader.GetAttribute("service")); - // bool? input_tilt = Utilities.GetYesNo(reader.GetAttribute("tilt")); - // string input_players = reader.GetAttribute("players"); - // string input_coins = reader.GetAttribute("coins"); - - // // While the subtree contains elements... - // string control_type = reader.GetAttribute("type"); - // string control_player = reader.GetAttribute("player"); - // string control_buttons = reader.GetAttribute("buttons"); - // string control_regbuttons = reader.GetAttribute("regbuttons"); - // string control_minimum = reader.GetAttribute("minimum"); - // string control_maximum = reader.GetAttribute("maximum"); - // string control_sensitivity = reader.GetAttribute("sensitivity"); - // string control_keydelta = reader.GetAttribute("keydelta"); - // bool? control_reverse = Utilities.GetYesNo(reader.GetAttribute("reverse")); - // string control_ways = reader.GetAttribute("ways"); - // string control_ways2 = reader.GetAttribute("ways2"); - // string control_ways3 = reader.GetAttribute("ways3"); - - reader.Skip(); - break; - case "dipswitch": - // string dipswitch_name = reader.GetAttribute("name"); - // string dipswitch_tag = reader.GetAttribute("tag"); - // string dipswitch_mask = reader.GetAttribute("mask"); - - // // While the subtree contains elements... - // string diplocation_name = reader.GetAttribute("name"); - // string diplocation_number = reader.GetAttribute("number"); - // bool? diplocation_inverted = Utilities.GetYesNo(reader.GetAttribute("inverted")); - - // // While the subtree contains elements... - // string dipvalue_name = reader.GetAttribute("name"); - // string dipvalue_value = reader.GetAttribute("value"); - // bool? dipvalue_default = Utilities.GetYesNo(reader.GetAttribute("default")); - - reader.Skip(); - break; - case "configuration": - // string configuration_name = reader.GetAttribute("name"); - // string configuration_tag = reader.GetAttribute("tag"); - // string configuration_mask = reader.GetAttribute("mask"); - - // // While the subtree contains elements... - // string conflocation_name = reader.GetAttribute("name"); - // string conflocation_number = reader.GetAttribute("number"); - // bool? conflocation_inverted = Utilities.GetYesNo(reader.GetAttribute("inverted")); - - // // While the subtree contains elements... - // string confsetting_name = reader.GetAttribute("name"); - // string confsetting_value = reader.GetAttribute("value"); - // bool? confsetting_default = Utilities.GetYesNo(reader.GetAttribute("default")); - - reader.Skip(); - break; - case "port": - // string port_tag = reader.GetAttribute("tag"); - - // // While the subtree contains elements... - // string analog_mask = reader.GetAttribute("mask"); - - reader.Skip(); - break; - case "adjuster": - // string adjuster_name = reader.GetAttribute("name"); - // bool? adjuster_default = Utilities.GetYesNo(reader.GetAttribute("default")); - - // // For the one possible element... - // string condition_tag = reader.GetAttribute("tag"); - // string condition_mask = reader.GetAttribute("mask"); - // string condition_relation = reader.GetAttribute("relation"); // (eq|ne|gt|le|lt|ge) - // string condition_value = reader.GetAttribute("value"); - - reader.Skip(); - break; - case "driver": - // string driver_status = reader.GetAttribute("status"); // (good|imperfect|preliminary) - // string driver_emulation = reader.GetAttribute("emulation"); // (good|imperfect|preliminary) - // string driver_cocktail = reader.GetAttribute("cocktail"); // (good|imperfect|preliminary) - // string driver_savestate = reader.GetAttribute("savestate"); // (supported|unsupported) - - reader.Read(); - break; - case "feature": - // string feature_type = reader.GetAttribute("type"); // (protection|palette|graphics|sound|controls|keyboard|mouse|microphone|camera|disk|printer|lan|wan|timing) - // string feature_status = reader.GetAttribute("status"); // (unemulated|imperfect) - // string feature_overall = reader.GetAttribute("overall"); // (unemulated|imperfect) - - reader.Read(); - break; - case "device": - // string device_type = reader.GetAttribute("type"); - // string device_tag = reader.GetAttribute("tag"); - // string device_fixed_image = reader.GetAttribute("fixed_image"); - // string device_mandatory = reader.GetAttribute("mandatory"); - // string device_interface = reader.GetAttribute("interface"); - - // // For the one possible element... - // string instance_name = reader.GetAttribute("name"); - // string instance_briefname = reader.GetAttribute("briefname"); - - // // While the subtree contains elements... - // string extension_name = reader.GetAttribute("name"); - - reader.Skip(); - break; - case "slot": - // string slot_name = reader.GetAttribute("name"); - ReadSlot(reader.ReadSubtree(), machine); - - // Skip the slot now that we've processed it - reader.Skip(); - break; - case "softwarelist": - // string softwarelist_name = reader.GetAttribute("name"); - // string softwarelist_status = reader.GetAttribute("status"); // (original|compatible) - // string softwarelist_filter = reader.GetAttribute("filter"); - - reader.Read(); - break; - case "ramoption": - // string ramoption_default = reader.GetAttribute("default"); - - reader.Read(); - break; - default: - reader.Read(); - break; - } - } - - // If no items were found for this machine, add a Blank placeholder - if (!containsItems) - { - Blank blank = new Blank() - { - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - blank.CopyMachineInformation(machine); - - // Now process and add the rom - ParseAddHelper(blank, clean, remUnicode); - } - } - - /// - /// Read slot information - /// - /// XmlReader representing a machine block - /// Machine information to pass to contained items - private void ReadSlot(XmlReader reader, Machine machine) - { - // If we have an empty machine, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the roms from the machine - switch (reader.Name) - { - case "slotoption": - // string slotoption_name = reader.GetAttribute("name"); - string devname = reader.GetAttribute("devname"); - if (!machine.SlotOptions.Contains(devname)) - { - machine.SlotOptions.Add(devname); - } - // bool? slotoption_default = Utilities.GetYesNo(reader.GetAttribute("default")); - reader.Read(); - break; - default: - reader.Read(); - break; - } - } - } - - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the DAT was written correctly, false otherwise - public override bool WriteToFile(string outfile, bool ignoreblanks = false) - { - try - { - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = Utilities.TryCreate(outfile); - - // If we get back null for some reason, just log and return - if (fs == null) - { - Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); - return false; - } - - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); - - // Write out the header - WriteHeader(sw); - - // Write out each of the machines and roms - string lastgame = null; - - // Get a properly sorted set of keys - List keys = Keys; - keys.Sort(new NaturalComparer()); - - foreach (string key in keys) - { - List roms = this[key]; - - // Resolve the names in the block - roms = DatItem.ResolveNames(roms); - - for (int index = 0; index < roms.Count; index++) - { - DatItem rom = roms[index]; - - // There are apparently times when a null rom can skip by, skip them - if (rom.Name == null || rom.MachineName == null) - { - Globals.Logger.Warning("Null rom found!"); - continue; - } - - // If we have a different game and we're not at the start of the list, output the end of last item - if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteEndGame(sw); - } - - // If we have a new game, output the beginning of the new item - if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteStartGame(sw, rom); - } - - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (rom.ItemType == ItemType.Rom - && ((Rom)rom).Size == -1 - && ((Rom)rom).CRC == "null") - { - Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); - - lastgame = rom.MachineName; - continue; - } - - // Now, output the rom data - WriteDatItem(sw, rom, ignoreblanks); - - // Set the new data to compare against - lastgame = rom.MachineName; - } - } - - // Write the file footer out - WriteFooter(sw); - - Globals.Logger.Verbose("File written!" + Environment.NewLine); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteHeader(StreamWriter sw) - { - try - { - string header = "\n" + - "\n\n"; - - // Write the header out - sw.Write(header); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if the data was written, false on error - private bool WriteStartGame(StreamWriter sw, DatItem rom) - { - try - { - // No game should start with a path separator - if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) - { - rom.MachineName = rom.MachineName.Substring(1); - } - - string state = "\t\n" - + (!ExcludeFields[(int)Field.Description] ? "\t\t" + HttpUtility.HtmlEncode(rom.MachineDescription) + "\n" : "") - + (!ExcludeFields[(int)Field.Year] && rom.Year != null ? "\t\t" + HttpUtility.HtmlEncode(rom.Year) + "\n" : "") - + (!ExcludeFields[(int)Field.Publisher] && rom.Publisher != null ? "\t\t" + HttpUtility.HtmlEncode(rom.Publisher) + "\n" : ""); - - if (!ExcludeFields[(int)Field.Infos] && rom.Infos != null && rom.Infos.Count > 0) - { - foreach (Tuple kvp in rom.Infos) - { - state += "\t\t\n"; - } - } - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteEndGame(StreamWriter sw) - { - try - { - string state = "\t\n"; - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the data was written, false on error - private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) - { - // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip - if (ignoreblanks - && (rom.ItemType == ItemType.Rom - && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) - { - return true; - } - - try - { - string state = ""; - - // Pre-process the item name - ProcessItemName(rom, true); - - switch (rom.ItemType) - { - case ItemType.Archive: - break; - case ItemType.BiosSet: // TODO: Separate out MachineDescription from Description - state += "\t\t\n"; - break; - case ItemType.Disk: - state += "\t\t\n"; - break; - case ItemType.Release: - //TODO: Am I missing this? - break; - case ItemType.Rom: - state += "\t\t\n"; - break; - case ItemType.Sample: - state += "\t\t\n"; - break; - } - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DAT footer using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteFooter(StreamWriter sw) - { - try - { - string footer = "\t\n\n"; - - // Write the footer out - sw.Write(footer); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - } + /// + /// Represents parsing and writing of a MAME XML DAT + /// + /// TODO: Verify that all write for this DatFile type is correct + internal class Listxml : DatFile + { + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + public Listxml(DatFile datFile) + : base(datFile, cloneHeader: false) + { + } + + /// + /// Parse a MAME XML DAT and return all found games and roms within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + /// + /// + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Prepare all internal variables + Encoding enc = Utilities.GetEncoding(filename); + XmlReader xtr = Utilities.GetXmlTextReader(filename); + + // If we got a null reader, just return + if (xtr == null) + { + return; + } + + // Otherwise, read the file to the end + try + { + xtr.MoveToContent(); + while (!xtr.EOF) + { + // We only want elements + if (xtr.NodeType != XmlNodeType.Element) + { + xtr.Read(); + continue; + } + + switch (xtr.Name) + { + case "mame": + Name = (String.IsNullOrWhiteSpace(Name) ? xtr.GetAttribute("build") : Name); + Description = (String.IsNullOrWhiteSpace(Description) ? Name : Name); + // string mame_debug = xtr.GetAttribute("debug"); // (yes|no) "no" + // string mame_mameconfig = xtr.GetAttribute("mameconfig"); CDATA + xtr.Read(); + break; + // Handle M1 DATs since they're 99% the same as a SL DAT + case "m1": + Name = (String.IsNullOrWhiteSpace(Name) ? "M1" : Name); + Description = (String.IsNullOrWhiteSpace(Description) ? "M1" : Description); + Version = (String.IsNullOrWhiteSpace(Version) ? xtr.GetAttribute("version") ?? "" : Version); + xtr.Read(); + break; + // We want to process the entire subtree of the machine + case "game": // Some older DATs still use "game" + case "machine": + ReadMachine(xtr.ReadSubtree(), filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the machine now that we've processed it + xtr.Skip(); + break; + default: + xtr.Read(); + break; + } + } + } + catch (Exception ex) + { + Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); + + // For XML errors, just skip the affected node + xtr?.Read(); + } + + xtr.Dispose(); + } + + /// + /// Read machine information + /// + /// XmlReader representing a machine block + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private void ReadMachine( + XmlReader reader, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // If we have an empty machine, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + string key = ""; + string temptype = reader.Name; + bool containsItems = false; + + // Create a new machine + MachineType machineType = MachineType.NULL; + if (Utilities.GetYesNo(reader.GetAttribute("isbios")) == true) + { + machineType |= MachineType.Bios; + } + if (Utilities.GetYesNo(reader.GetAttribute("isdevice")) == true) + { + machineType |= MachineType.Device; + } + if (Utilities.GetYesNo(reader.GetAttribute("ismechanical")) == true) + { + machineType |= MachineType.Mechanical; + } + + Machine machine = new Machine + { + Name = reader.GetAttribute("name"), + Description = reader.GetAttribute("name"), + SourceFile = reader.GetAttribute("sourcefile"), + Runnable = Utilities.GetYesNo(reader.GetAttribute("runnable")), + + Comment = "", + + CloneOf = reader.GetAttribute("cloneof") ?? "", + RomOf = reader.GetAttribute("romof") ?? "", + SampleOf = reader.GetAttribute("sampleof") ?? "", + Devices = new List(), + SlotOptions = new List(), + + MachineType = (machineType == MachineType.NULL ? MachineType.None : machineType), + }; + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the roms from the machine + switch (reader.Name) + { + case "description": + machine.Description = reader.ReadElementContentAsString(); + break; + case "year": + machine.Year = reader.ReadElementContentAsString(); + break; + case "manufacturer": + machine.Manufacturer = reader.ReadElementContentAsString(); + break; + case "biosset": + containsItems = true; + + DatItem biosset = new BiosSet + { + Name = reader.GetAttribute("name"), + Description = reader.GetAttribute("description"), + Default = Utilities.GetYesNo(reader.GetAttribute("default")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + biosset.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(biosset, clean, remUnicode); + + reader.Read(); + break; + case "rom": + containsItems = true; + + DatItem rom = new Rom + { + Name = reader.GetAttribute("name"), + Bios = reader.GetAttribute("bios"), + Size = Utilities.GetSize(reader.GetAttribute("size")), + CRC = Utilities.CleanHashData(reader.GetAttribute("crc"), Constants.CRCLength), + MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), + SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), + SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), + SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), + SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), + MergeTag = reader.GetAttribute("merge"), + Region = reader.GetAttribute("region"), + Offset = reader.GetAttribute("offset"), + ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), + Optional = Utilities.GetYesNo(reader.GetAttribute("optional")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + rom.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(rom, clean, remUnicode); + + reader.Read(); + break; + case "disk": + containsItems = true; + + DatItem disk = new Disk + { + Name = reader.GetAttribute("name"), + MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), + SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), + SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), + SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), + SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), + MergeTag = reader.GetAttribute("merge"), + Region = reader.GetAttribute("region"), + Index = reader.GetAttribute("index"), + Writable = Utilities.GetYesNo(reader.GetAttribute("writable")), + ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), + Optional = Utilities.GetYesNo(reader.GetAttribute("optional")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + disk.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(disk, clean, remUnicode); + + reader.Read(); + break; + case "device_ref": + string device_ref_name = reader.GetAttribute("name"); + if (!machine.Devices.Contains(device_ref_name)) + { + machine.Devices.Add(device_ref_name); + } + + reader.Read(); + break; + case "sample": + containsItems = true; + + DatItem samplerom = new Sample + { + Name = reader.GetAttribute("name"), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + samplerom.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(samplerom, clean, remUnicode); + + reader.Read(); + break; + case "chip": + // string chip_name = reader.GetAttribute("name"); + // string chip_tag = reader.GetAttribute("tag"); + // string chip_type = reader.GetAttribute("type"); // (cpu|audio) + // string chip_clock = reader.GetAttribute("clock"); + + reader.Read(); + break; + case "display": + // string display_tag = reader.GetAttribute("tag"); + // string display_type = reader.GetAttribute("type"); // (raster|vector|lcd|svg|unknown) + // string display_rotate = reader.GetAttribute("rotate"); // (0|90|180|270) + // bool? display_flipx = Utilities.GetYesNo(reader.GetAttribute("flipx")); + // string display_width = reader.GetAttribute("width"); + // string display_height = reader.GetAttribute("height"); + // string display_refresh = reader.GetAttribute("refresh"); + // string display_pixclock = reader.GetAttribute("pixclock"); + // string display_htotal = reader.GetAttribute("htotal"); + // string display_hbend = reader.GetAttribute("hbend"); + // string display_hbstart = reader.GetAttribute("hbstart"); + // string display_vtotal = reader.GetAttribute("vtotal"); + // string display_vbend = reader.GetAttribute("vbend"); + // string display_vbstart = reader.GetAttribute("vbstart"); + + reader.Read(); + break; + case "sound": + // string sound_channels = reader.GetAttribute("channels"); + + reader.Read(); + break; + case "condition": + // string condition_tag = reader.GetAttribute("tag"); + // string condition_mask = reader.GetAttribute("mask"); + // string condition_relation = reader.GetAttribute("relation"); // (eq|ne|gt|le|lt|ge) + // string condition_value = reader.GetAttribute("value"); + + reader.Read(); + break; + case "input": + // bool? input_service = Utilities.GetYesNo(reader.GetAttribute("service")); + // bool? input_tilt = Utilities.GetYesNo(reader.GetAttribute("tilt")); + // string input_players = reader.GetAttribute("players"); + // string input_coins = reader.GetAttribute("coins"); + + // // While the subtree contains elements... + // string control_type = reader.GetAttribute("type"); + // string control_player = reader.GetAttribute("player"); + // string control_buttons = reader.GetAttribute("buttons"); + // string control_regbuttons = reader.GetAttribute("regbuttons"); + // string control_minimum = reader.GetAttribute("minimum"); + // string control_maximum = reader.GetAttribute("maximum"); + // string control_sensitivity = reader.GetAttribute("sensitivity"); + // string control_keydelta = reader.GetAttribute("keydelta"); + // bool? control_reverse = Utilities.GetYesNo(reader.GetAttribute("reverse")); + // string control_ways = reader.GetAttribute("ways"); + // string control_ways2 = reader.GetAttribute("ways2"); + // string control_ways3 = reader.GetAttribute("ways3"); + + reader.Skip(); + break; + case "dipswitch": + // string dipswitch_name = reader.GetAttribute("name"); + // string dipswitch_tag = reader.GetAttribute("tag"); + // string dipswitch_mask = reader.GetAttribute("mask"); + + // // While the subtree contains elements... + // string diplocation_name = reader.GetAttribute("name"); + // string diplocation_number = reader.GetAttribute("number"); + // bool? diplocation_inverted = Utilities.GetYesNo(reader.GetAttribute("inverted")); + + // // While the subtree contains elements... + // string dipvalue_name = reader.GetAttribute("name"); + // string dipvalue_value = reader.GetAttribute("value"); + // bool? dipvalue_default = Utilities.GetYesNo(reader.GetAttribute("default")); + + reader.Skip(); + break; + case "configuration": + // string configuration_name = reader.GetAttribute("name"); + // string configuration_tag = reader.GetAttribute("tag"); + // string configuration_mask = reader.GetAttribute("mask"); + + // // While the subtree contains elements... + // string conflocation_name = reader.GetAttribute("name"); + // string conflocation_number = reader.GetAttribute("number"); + // bool? conflocation_inverted = Utilities.GetYesNo(reader.GetAttribute("inverted")); + + // // While the subtree contains elements... + // string confsetting_name = reader.GetAttribute("name"); + // string confsetting_value = reader.GetAttribute("value"); + // bool? confsetting_default = Utilities.GetYesNo(reader.GetAttribute("default")); + + reader.Skip(); + break; + case "port": + // string port_tag = reader.GetAttribute("tag"); + + // // While the subtree contains elements... + // string analog_mask = reader.GetAttribute("mask"); + + reader.Skip(); + break; + case "adjuster": + // string adjuster_name = reader.GetAttribute("name"); + // bool? adjuster_default = Utilities.GetYesNo(reader.GetAttribute("default")); + + // // For the one possible element... + // string condition_tag = reader.GetAttribute("tag"); + // string condition_mask = reader.GetAttribute("mask"); + // string condition_relation = reader.GetAttribute("relation"); // (eq|ne|gt|le|lt|ge) + // string condition_value = reader.GetAttribute("value"); + + reader.Skip(); + break; + case "driver": + // string driver_status = reader.GetAttribute("status"); // (good|imperfect|preliminary) + // string driver_emulation = reader.GetAttribute("emulation"); // (good|imperfect|preliminary) + // string driver_cocktail = reader.GetAttribute("cocktail"); // (good|imperfect|preliminary) + // string driver_savestate = reader.GetAttribute("savestate"); // (supported|unsupported) + + reader.Read(); + break; + case "feature": + // string feature_type = reader.GetAttribute("type"); // (protection|palette|graphics|sound|controls|keyboard|mouse|microphone|camera|disk|printer|lan|wan|timing) + // string feature_status = reader.GetAttribute("status"); // (unemulated|imperfect) + // string feature_overall = reader.GetAttribute("overall"); // (unemulated|imperfect) + + reader.Read(); + break; + case "device": + // string device_type = reader.GetAttribute("type"); + // string device_tag = reader.GetAttribute("tag"); + // string device_fixed_image = reader.GetAttribute("fixed_image"); + // string device_mandatory = reader.GetAttribute("mandatory"); + // string device_interface = reader.GetAttribute("interface"); + + // // For the one possible element... + // string instance_name = reader.GetAttribute("name"); + // string instance_briefname = reader.GetAttribute("briefname"); + + // // While the subtree contains elements... + // string extension_name = reader.GetAttribute("name"); + + reader.Skip(); + break; + case "slot": + // string slot_name = reader.GetAttribute("name"); + ReadSlot(reader.ReadSubtree(), machine); + + // Skip the slot now that we've processed it + reader.Skip(); + break; + case "softwarelist": + // string softwarelist_name = reader.GetAttribute("name"); + // string softwarelist_status = reader.GetAttribute("status"); // (original|compatible) + // string softwarelist_filter = reader.GetAttribute("filter"); + + reader.Read(); + break; + case "ramoption": + // string ramoption_default = reader.GetAttribute("default"); + + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + + // If no items were found for this machine, add a Blank placeholder + if (!containsItems) + { + Blank blank = new Blank() + { + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + blank.CopyMachineInformation(machine); + + // Now process and add the rom + ParseAddHelper(blank, clean, remUnicode); + } + } + + /// + /// Read slot information + /// + /// XmlReader representing a machine block + /// Machine information to pass to contained items + private void ReadSlot(XmlReader reader, Machine machine) + { + // If we have an empty machine, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the roms from the machine + switch (reader.Name) + { + case "slotoption": + // string slotoption_name = reader.GetAttribute("name"); + string devname = reader.GetAttribute("devname"); + if (!machine.SlotOptions.Contains(devname)) + { + machine.SlotOptions.Add(devname); + } + // bool? slotoption_default = Utilities.GetYesNo(reader.GetAttribute("default")); + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public override bool WriteToFile(string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); + + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } + + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + + // Write out the header + WriteHeader(sw); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = this[key]; + + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); + + for (int index = 0; index < roms.Count; index++) + { + DatItem rom = roms[index]; + + // There are apparently times when a null rom can skip by, skip them + if (rom.Name == null || rom.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } + + // If we have a different game and we're not at the start of the list, output the end of last item + if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteEndGame(sw); + } + + // If we have a new game, output the beginning of the new item + if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteStartGame(sw, rom); + } + + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.ItemType == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + + lastgame = rom.MachineName; + continue; + } + + // Now, output the rom data + WriteDatItem(sw, rom, ignoreblanks); + + // Set the new data to compare against + lastgame = rom.MachineName; + } + } + + // Write the file footer out + WriteFooter(sw); + + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteHeader(StreamWriter sw) + { + try + { + string header = "\n" + + "\n\n"; + + // Write the header out + sw.Write(header); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if the data was written, false on error + private bool WriteStartGame(StreamWriter sw, DatItem rom) + { + try + { + // No game should start with a path separator + if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) + { + rom.MachineName = rom.MachineName.Substring(1); + } + + string state = "\t\n" + + (!ExcludeFields[(int)Field.Description] ? "\t\t" + HttpUtility.HtmlEncode(rom.MachineDescription) + "\n" : "") + + (!ExcludeFields[(int)Field.Year] && rom.Year != null ? "\t\t" + HttpUtility.HtmlEncode(rom.Year) + "\n" : "") + + (!ExcludeFields[(int)Field.Publisher] && rom.Publisher != null ? "\t\t" + HttpUtility.HtmlEncode(rom.Publisher) + "\n" : ""); + + if (!ExcludeFields[(int)Field.Infos] && rom.Infos != null && rom.Infos.Count > 0) + { + foreach (Tuple kvp in rom.Infos) + { + state += "\t\t\n"; + } + } + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteEndGame(StreamWriter sw) + { + try + { + string state = "\t\n"; + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the data was written, false on error + private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.ItemType == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + + // Pre-process the item name + ProcessItemName(rom, true); + + switch (rom.ItemType) + { + case ItemType.Archive: + break; + case ItemType.BiosSet: // TODO: Separate out MachineDescription from Description + state += "\t\t\n"; + break; + case ItemType.Disk: + state += "\t\t\n"; + break; + case ItemType.Release: + //TODO: Am I missing this? + break; + case ItemType.Rom: + state += "\t\t\n"; + break; + case ItemType.Sample: + state += "\t\t\n"; + break; + } + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT footer using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteFooter(StreamWriter sw) + { + try + { + string footer = "\t\n\n"; + + // Write the footer out + sw.Write(footer); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + } } diff --git a/SabreTools.Library/DatFiles/Logiqx.cs b/SabreTools.Library/DatFiles/Logiqx.cs index dde0801b..39de4829 100644 --- a/SabreTools.Library/DatFiles/Logiqx.cs +++ b/SabreTools.Library/DatFiles/Logiqx.cs @@ -5,7 +5,6 @@ using System.Text; using System.Text.RegularExpressions; using System.Web; using System.Xml; - using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -22,963 +21,963 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { - /// - /// Represents parsing and writing of a Logiqx-derived DAT - /// - /// TODO: Add XSD validation for all XML DAT types (maybe?) - /// TODO: Verify that all write for this DatFile type is correct - internal class Logiqx : DatFile - { - // Private instance variables specific to Logiqx DATs - bool _depreciated; - - /// - /// Constructor designed for casting a base DatFile - /// - /// Parent DatFile to copy from - /// True if the output uses "game", false if the output uses "machine" - public Logiqx(DatFile datFile, bool depreciated) - : base(datFile, cloneHeader: false) - { - _depreciated = depreciated; - } - - /// - /// Parse a Logiqx XML DAT and return all found games and roms within - /// - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - public override void ParseFile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Prepare all internal variables - Encoding enc = Utilities.GetEncoding(filename); - XmlReader xtr = Utilities.GetXmlTextReader(filename); - List dirs = new List(); - - // If we got a null reader, just return - if (xtr == null) - { - return; - } - - // Otherwise, read the file to the end - try - { - xtr.MoveToContent(); - while (!xtr.EOF) - { - // We only want elements - if (xtr.NodeType != XmlNodeType.Element) - { - // If we're ending a dir, remove the last item from the dirs list, if possible - if (xtr.Name == "dir" && dirs.Count > 0) - { - dirs.RemoveAt(dirs.Count - 1); - } - - xtr.Read(); - continue; - } - - switch (xtr.Name) - { - // The datafile tag can have some attributes - case "datafile": - // string build = xtr.GetAttribute("build"); - // string debug = xtr.GetAttribute("debug"); // (yes|no) "no" - xtr.Read(); - break; - // We want to process the entire subtree of the header - case "header": - ReadHeader(xtr.ReadSubtree(), keep); - - // Skip the header node now that we've processed it - xtr.Skip(); - break; - // Unique to RomVault-created DATs - case "dir": - Type = "SuperDAT"; - dirs.Add(xtr.GetAttribute("name") ?? ""); - xtr.Read(); - break; - // We want to process the entire subtree of the game - case "machine": // New-style Logiqx - case "game": // Old-style Logiqx - ReadMachine(xtr.ReadSubtree(), dirs, filename, sysid, srcid, keep, clean, remUnicode); - - // Skip the machine now that we've processed it - xtr.Skip(); - break; - default: - xtr.Read(); - break; - } - } - } - catch (Exception ex) - { - Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); - - // For XML errors, just skip the affected node - xtr?.Read(); - } - - xtr.Dispose(); - } - - /// - /// Read header information - /// - /// XmlReader to use to parse the header - /// True if full pathnames are to be kept, false otherwise (default) - private void ReadHeader(XmlReader reader, bool keep) - { - bool superdat = false; - - // If there's no subtree to the header, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element || reader.Name == "header") - { - reader.Read(); - continue; - } - - // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) - string content = ""; - switch (reader.Name) - { - case "name": - content = reader.ReadElementContentAsString(); ; - Name = (String.IsNullOrWhiteSpace(Name) ? content : Name); - superdat = superdat || content.Contains(" - SuperDAT"); - if (keep && superdat) - { - Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); - } - break; - case "description": - content = reader.ReadElementContentAsString(); - Description = (String.IsNullOrWhiteSpace(Description) ? content : Description); - break; - case "rootdir": // This is exclusive to TruRip XML - content = reader.ReadElementContentAsString(); - RootDir = (String.IsNullOrWhiteSpace(RootDir) ? content : RootDir); - break; - case "category": - content = reader.ReadElementContentAsString(); - Category = (String.IsNullOrWhiteSpace(Category) ? content : Category); - break; - case "version": - content = reader.ReadElementContentAsString(); - Version = (String.IsNullOrWhiteSpace(Version) ? content : Version); - break; - case "date": - content = reader.ReadElementContentAsString(); - Date = (String.IsNullOrWhiteSpace(Date) ? content.Replace(".", "/") : Date); - break; - case "author": - content = reader.ReadElementContentAsString(); - Author = (String.IsNullOrWhiteSpace(Author) ? content : Author); - break; - case "email": - content = reader.ReadElementContentAsString(); - Email = (String.IsNullOrWhiteSpace(Email) ? content : Email); - break; - case "homepage": - content = reader.ReadElementContentAsString(); - Homepage = (String.IsNullOrWhiteSpace(Homepage) ? content : Homepage); - break; - case "url": - content = reader.ReadElementContentAsString(); - Url = (String.IsNullOrWhiteSpace(Url) ? content : Url); - break; - case "comment": - content = reader.ReadElementContentAsString(); - Comment = (String.IsNullOrWhiteSpace(Comment) ? content : Comment); - break; - case "type": // This is exclusive to TruRip XML - content = reader.ReadElementContentAsString(); - Type = (String.IsNullOrWhiteSpace(Type) ? content : Type); - superdat = superdat || content.Contains("SuperDAT"); - break; - case "clrmamepro": - if (String.IsNullOrWhiteSpace(Header)) - { - Header = reader.GetAttribute("header"); - } - if (ForceMerging == ForceMerging.None) - { - ForceMerging = Utilities.GetForceMerging(reader.GetAttribute("forcemerging")); - } - if (ForceNodump == ForceNodump.None) - { - ForceNodump = Utilities.GetForceNodump(reader.GetAttribute("forcenodump")); - } - if (ForcePacking == ForcePacking.None) - { - ForcePacking = Utilities.GetForcePacking(reader.GetAttribute("forcepacking")); - } - reader.Read(); - break; - case "romcenter": - if (reader.GetAttribute("plugin") != null) - { - // CDATA - } - if (reader.GetAttribute("rommode") != null) - { - // (merged|split|unmerged) "split" - } - if (reader.GetAttribute("biosmode") != null) - { - // merged|split|unmerged) "split" - } - if (reader.GetAttribute("samplemode") != null) - { - // (merged|unmerged) "merged" - } - if (reader.GetAttribute("lockrommode") != null) - { - // (yes|no) "no" - } - if (reader.GetAttribute("lockbiosmode") != null) - { - // (yes|no) "no" - } - if (reader.GetAttribute("locksamplemode") != null) - { - // (yes|no) "no" - } - reader.Read(); - break; - default: - reader.Read(); - break; - } - } - } - - /// - /// Read game/machine information - /// - /// XmlReader to use to parse the machine - /// List of dirs to prepend to the game name - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private void ReadMachine( - XmlReader reader, - List dirs, - - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // If we have an empty machine, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - string key = ""; - string temptype = reader.Name; - bool containsItems = false; - - // Create a new machine - MachineType machineType = MachineType.NULL; - if (Utilities.GetYesNo(reader.GetAttribute("isbios")) == true) - { - machineType |= MachineType.Bios; - } - if (Utilities.GetYesNo(reader.GetAttribute("isdevice")) == true) // Listxml-specific, used by older DATs - { - machineType |= MachineType.Device; - } - if (Utilities.GetYesNo(reader.GetAttribute("ismechanical")) == true) // Listxml-specific, used by older DATs - { - machineType |= MachineType.Mechanical; - } - - string dirsString = (dirs != null && dirs.Count() > 0 ? string.Join("/", dirs) + "/" : ""); - Machine machine = new Machine - { - Name = dirsString + reader.GetAttribute("name"), - Description = dirsString + reader.GetAttribute("name"), - SourceFile = reader.GetAttribute("sourcefile"), - Board = reader.GetAttribute("board"), - RebuildTo = reader.GetAttribute("rebuildto"), - Runnable = Utilities.GetYesNo(reader.GetAttribute("runnable")), // Listxml-specific, used by older DATs - - Comment = "", - - CloneOf = reader.GetAttribute("cloneof") ?? "", - RomOf = reader.GetAttribute("romof") ?? "", - SampleOf = reader.GetAttribute("sampleof") ?? "", - - MachineType = (machineType == MachineType.NULL ? MachineType.None : machineType), - }; - - if (Type == "SuperDAT" && !keep) - { - string tempout = Regex.Match(machine.Name, @".*?\\(.*)").Groups[1].Value; - if (!String.IsNullOrWhiteSpace(tempout)) - { - machine.Name = tempout; - } - } - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the roms from the machine - switch (reader.Name) - { - case "comment": // There can be multiple comments by spec - machine.Comment += reader.ReadElementContentAsString(); - break; - case "description": - machine.Description = reader.ReadElementContentAsString(); - break; - case "year": - machine.Year = reader.ReadElementContentAsString(); - break; - case "manufacturer": - machine.Manufacturer = reader.ReadElementContentAsString(); - break; - case "publisher": // Not technically supported but used by some legacy DATs - machine.Publisher = reader.ReadElementContentAsString(); - break; - case "trurip": // This is special metadata unique to TruRip - ReadTruRip(reader.ReadSubtree(), machine); - - // Skip the trurip node now that we've processed it - reader.Skip(); - break; - case "release": - containsItems = true; - - DatItem release = new Release - { - Name = reader.GetAttribute("name"), - Region = reader.GetAttribute("region"), - Language = reader.GetAttribute("language"), - Date = reader.GetAttribute("date"), - Default = Utilities.GetYesNo(reader.GetAttribute("default")), - }; - - release.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(release, clean, remUnicode); - - reader.Read(); - break; - case "biosset": - containsItems = true; - - DatItem biosset = new BiosSet - { - Name = reader.GetAttribute("name"), - Description = reader.GetAttribute("description"), - Default = Utilities.GetYesNo(reader.GetAttribute("default")), - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - biosset.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(biosset, clean, remUnicode); - - reader.Read(); - break; - case "rom": - containsItems = true; - - DatItem rom = new Rom - { - Name = reader.GetAttribute("name"), - Size = Utilities.GetSize(reader.GetAttribute("size")), - CRC = Utilities.CleanHashData(reader.GetAttribute("crc"), Constants.CRCLength), - MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), - SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), - SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), - SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), - SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), - MergeTag = reader.GetAttribute("merge"), - ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), - Date = Utilities.GetDate(reader.GetAttribute("date")), - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - rom.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(rom, clean, remUnicode); - - reader.Read(); - break; - case "disk": - containsItems = true; - - DatItem disk = new Disk - { - Name = reader.GetAttribute("name"), - MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), - SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), - SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), - SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), - SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), - MergeTag = reader.GetAttribute("merge"), - ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - disk.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(disk, clean, remUnicode); - - reader.Read(); - break; - case "sample": - containsItems = true; - - DatItem samplerom = new Sample - { - Name = reader.GetAttribute("name"), - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - samplerom.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(samplerom, clean, remUnicode); - - reader.Read(); - break; - case "archive": - containsItems = true; - - DatItem archiverom = new Archive - { - Name = reader.GetAttribute("name"), - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - archiverom.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(archiverom, clean, remUnicode); - - reader.Read(); - break; - default: - reader.Read(); - break; - } - } - - // If no items were found for this machine, add a Blank placeholder - if (!containsItems) - { - Blank blank = new Blank() - { - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - blank.CopyMachineInformation(machine); - - // Now process and add the rom - ParseAddHelper(blank, clean, remUnicode); - } - } - - /// - /// Read TruRip information - /// - /// True if full pathnames are to be kept, false otherwise (default) - /// Machine information to pass to contained items - private void ReadTruRip(XmlReader reader, Machine machine) - { - // If we have an empty trurip, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the information from the trurip - string content = ""; - switch (reader.Name) - { - case "titleid": - content = reader.ReadElementContentAsString(); - // string titleid = content; - break; - case "publisher": - machine.Publisher = reader.ReadElementContentAsString(); - break; - case "developer": // Manufacturer is as close as this gets - machine.Manufacturer = reader.ReadElementContentAsString(); - break; - case "year": - machine.Year = reader.ReadElementContentAsString(); - break; - case "genre": - content = reader.ReadElementContentAsString(); - // string genre = content; - break; - case "subgenre": - content = reader.ReadElementContentAsString(); - // string subgenre = content; - break; - case "ratings": - content = reader.ReadElementContentAsString(); - // string ratings = content; - break; - case "score": - content = reader.ReadElementContentAsString(); - // string score = content; - break; - case "players": - content = reader.ReadElementContentAsString(); - // string players = content; - break; - case "enabled": - content = reader.ReadElementContentAsString(); - // string enabled = content; - break; - case "crc": - content = reader.ReadElementContentAsString(); - // string crc = Utilities.GetYesNo(content); - break; - case "source": - machine.SourceFile = reader.ReadElementContentAsString(); - break; - case "cloneof": - machine.CloneOf = reader.ReadElementContentAsString(); - break; - case "relatedto": - content = reader.ReadElementContentAsString(); - // string relatedto = content; - break; - default: - reader.Read(); - break; - } - } - } - - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the DAT was written correctly, false otherwise - public override bool WriteToFile(string outfile, bool ignoreblanks = false) - { - try - { - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = Utilities.TryCreate(outfile); - - // If we get back null for some reason, just log and return - if (fs == null) - { - Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); - return false; - } - - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); - - // Write out the header - WriteHeader(sw); - - // Write out each of the machines and roms - string lastgame = null; - - // Get a properly sorted set of keys - List keys = Keys; - keys.Sort(new NaturalComparer()); - - foreach (string key in keys) - { - List roms = this[key]; - - // Resolve the names in the block - roms = DatItem.ResolveNames(roms); - - for (int index = 0; index < roms.Count; index++) - { - DatItem rom = roms[index]; - - // There are apparently times when a null rom can skip by, skip them - if (rom.Name == null || rom.MachineName == null) - { - Globals.Logger.Warning("Null rom found!"); - continue; - } - - // If we have a different game and we're not at the start of the list, output the end of last item - if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteEndGame(sw); - } - - // If we have a new game, output the beginning of the new item - if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteStartGame(sw, rom); - } - - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (rom.ItemType == ItemType.Rom - && ((Rom)rom).Size == -1 - && ((Rom)rom).CRC == "null") - { - Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); - - rom.Name = (rom.Name == "null" ? "-" : rom.Name); - ((Rom)rom).Size = Constants.SizeZero; - ((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null; - ((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null; - ((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null; - ((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null; - ((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null; - ((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null; - } - - // Now, output the rom data - WriteDatItem(sw, rom, ignoreblanks); - - // Set the new data to compare against - lastgame = rom.MachineName; - } - } - - // Write the file footer out - WriteFooter(sw); - - Globals.Logger.Verbose("File written!" + Environment.NewLine); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteHeader(StreamWriter sw) - { - try - { - string header = "\n" + - "\n\n" + - "\n" + - "\t
\n" + - "\t\t" + HttpUtility.HtmlEncode(Name) + "\n" + - "\t\t" + HttpUtility.HtmlEncode(Description) + "\n" + - (!String.IsNullOrWhiteSpace(RootDir) ? "\t\t" + HttpUtility.HtmlEncode(RootDir) + "\n" : "") + - (!String.IsNullOrWhiteSpace(Category) ? "\t\t" + HttpUtility.HtmlEncode(Category) + "\n" : "") + - "\t\t" + HttpUtility.HtmlEncode(Version) + "\n" + - (!String.IsNullOrWhiteSpace(Date) ? "\t\t" + HttpUtility.HtmlEncode(Date) + "\n" : "") + - "\t\t" + HttpUtility.HtmlEncode(Author) + "\n" + - (!String.IsNullOrWhiteSpace(Email) ? "\t\t" + HttpUtility.HtmlEncode(Email) + "\n" : "") + - (!String.IsNullOrWhiteSpace(Homepage) ? "\t\t" + HttpUtility.HtmlEncode(Homepage) + "\n" : "") + - (!String.IsNullOrWhiteSpace(Url) ? "\t\t" + HttpUtility.HtmlEncode(Url) + "\n" : "") + - (!String.IsNullOrWhiteSpace(Comment) ? "\t\t" + HttpUtility.HtmlEncode(Comment) + "\n" : "") + - (!String.IsNullOrWhiteSpace(Type) ? "\t\t" + HttpUtility.HtmlEncode(Type) + "\n" : "") + - (ForcePacking != ForcePacking.None || ForceMerging != ForceMerging.None || ForceNodump != ForceNodump.None || !String.IsNullOrWhiteSpace(Header) ? - "\t\t\n" - : "") + - "\t
\n"; - - // Write the header out - sw.Write(header); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if the data was written, false on error - private bool WriteStartGame(StreamWriter sw, DatItem rom) - { - try - { - // No game should start with a path separator - if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) - { - rom.MachineName = rom.MachineName.Substring(1); - } - - string state = "\t<" + (_depreciated ? "game" : "machine") + " name=\"" + (!ExcludeFields[(int)Field.MachineName] ? HttpUtility.HtmlEncode(rom.MachineName) : "") + "\"" - + (!ExcludeFields[(int)Field.MachineType] && (rom.MachineType & MachineType.Bios) != 0 ? " isbios=\"yes\"" : "") - + (!ExcludeFields[(int)Field.MachineType] && (rom.MachineType & MachineType.Device) != 0 ? " isdevice=\"yes\"" : "") - + (!ExcludeFields[(int)Field.MachineType] && (rom.MachineType & MachineType.Mechanical) != 0 ? " ismechanical=\"yes\"" : "") - + (!ExcludeFields[(int)Field.Runnable] && rom.Runnable == true - ? " runnable=\"yes\"" - : (!ExcludeFields[(int)Field.Runnable] && rom.Runnable == false ? " runnable=\"no\"" : "")) - + (!ExcludeFields[(int)Field.CloneOf] && !String.IsNullOrWhiteSpace(rom.CloneOf) && (rom.MachineName.ToLowerInvariant() != rom.CloneOf.ToLowerInvariant()) - ? " cloneof=\"" + HttpUtility.HtmlEncode(rom.CloneOf) + "\"" - : "") - + (!ExcludeFields[(int)Field.RomOf] && !String.IsNullOrWhiteSpace(rom.RomOf) && (rom.MachineName.ToLowerInvariant() != rom.RomOf.ToLowerInvariant()) - ? " romof=\"" + HttpUtility.HtmlEncode(rom.RomOf) + "\"" - : "") - + (!ExcludeFields[(int)Field.SampleOf] && !String.IsNullOrWhiteSpace(rom.SampleOf) && (rom.MachineName.ToLowerInvariant() != rom.SampleOf.ToLowerInvariant()) - ? " sampleof=\"" + HttpUtility.HtmlEncode(rom.SampleOf) + "\"" - : "") - + ">\n" - + (!ExcludeFields[(int)Field.Comment] && !String.IsNullOrWhiteSpace(rom.Comment) ? "\t\t" + HttpUtility.HtmlEncode(rom.Comment) + "\n" : "") - + (!ExcludeFields[(int)Field.Description] ? "\t\t" + HttpUtility.HtmlEncode((String.IsNullOrWhiteSpace(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription)) + "\n" : "") - + (!ExcludeFields[(int)Field.Year] && !String.IsNullOrWhiteSpace(rom.Year) ? "\t\t" + HttpUtility.HtmlEncode(rom.Year) + "\n" : "") - + (!ExcludeFields[(int)Field.Publisher] && !String.IsNullOrWhiteSpace(rom.Publisher) ? "\t\t" + HttpUtility.HtmlEncode(rom.Publisher) + "\n" : "") - + (!ExcludeFields[(int)Field.Manufacturer] && !String.IsNullOrWhiteSpace(rom.Manufacturer) ? "\t\t" + HttpUtility.HtmlEncode(rom.Manufacturer) + "\n" : ""); - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out Game end using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteEndGame(StreamWriter sw) - { - try - { - string state = "\t\n"; - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the data was written, false on error - private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) - { - // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip - if (ignoreblanks - && (rom.ItemType == ItemType.Rom - && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) - { - return true; - } - - try - { - string state = ""; - - // Pre-process the item name - ProcessItemName(rom, true); - - switch (rom.ItemType) - { - case ItemType.Archive: - state += "\t\t\n"; - break; - case ItemType.BiosSet: - state += "\t\t\n"; - break; - case ItemType.Disk: - state += "\t\t\n"; - break; - case ItemType.Release: - state += "\t\t\n"; - break; - case ItemType.Rom: - state += "\t\t\n"; - break; - case ItemType.Sample: - state += "\t\t\n"; - break; - } - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DAT footer using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteFooter(StreamWriter sw) - { - try - { - string footer = "\t\n
\n"; - - // Write the footer out - sw.Write(footer); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - } + /// + /// Represents parsing and writing of a Logiqx-derived DAT + /// + /// TODO: Add XSD validation for all XML DAT types (maybe?) + /// TODO: Verify that all write for this DatFile type is correct + internal class Logiqx : DatFile + { + // Private instance variables specific to Logiqx DATs + bool _depreciated; + + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + /// True if the output uses "game", false if the output uses "machine" + public Logiqx(DatFile datFile, bool depreciated) + : base(datFile, cloneHeader: false) + { + _depreciated = depreciated; + } + + /// + /// Parse a Logiqx XML DAT and return all found games and roms within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Prepare all internal variables + Encoding enc = Utilities.GetEncoding(filename); + XmlReader xtr = Utilities.GetXmlTextReader(filename); + List dirs = new List(); + + // If we got a null reader, just return + if (xtr == null) + { + return; + } + + // Otherwise, read the file to the end + try + { + xtr.MoveToContent(); + while (!xtr.EOF) + { + // We only want elements + if (xtr.NodeType != XmlNodeType.Element) + { + // If we're ending a dir, remove the last item from the dirs list, if possible + if (xtr.Name == "dir" && dirs.Count > 0) + { + dirs.RemoveAt(dirs.Count - 1); + } + + xtr.Read(); + continue; + } + + switch (xtr.Name) + { + // The datafile tag can have some attributes + case "datafile": + // string build = xtr.GetAttribute("build"); + // string debug = xtr.GetAttribute("debug"); // (yes|no) "no" + xtr.Read(); + break; + // We want to process the entire subtree of the header + case "header": + ReadHeader(xtr.ReadSubtree(), keep); + + // Skip the header node now that we've processed it + xtr.Skip(); + break; + // Unique to RomVault-created DATs + case "dir": + Type = "SuperDAT"; + dirs.Add(xtr.GetAttribute("name") ?? ""); + xtr.Read(); + break; + // We want to process the entire subtree of the game + case "machine": // New-style Logiqx + case "game": // Old-style Logiqx + ReadMachine(xtr.ReadSubtree(), dirs, filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the machine now that we've processed it + xtr.Skip(); + break; + default: + xtr.Read(); + break; + } + } + } + catch (Exception ex) + { + Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); + + // For XML errors, just skip the affected node + xtr?.Read(); + } + + xtr.Dispose(); + } + + /// + /// Read header information + /// + /// XmlReader to use to parse the header + /// True if full pathnames are to be kept, false otherwise (default) + private void ReadHeader(XmlReader reader, bool keep) + { + bool superdat = false; + + // If there's no subtree to the header, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element || reader.Name == "header") + { + reader.Read(); + continue; + } + + // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) + string content = ""; + switch (reader.Name) + { + case "name": + content = reader.ReadElementContentAsString(); ; + Name = (String.IsNullOrWhiteSpace(Name) ? content : Name); + superdat = superdat || content.Contains(" - SuperDAT"); + if (keep && superdat) + { + Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); + } + break; + case "description": + content = reader.ReadElementContentAsString(); + Description = (String.IsNullOrWhiteSpace(Description) ? content : Description); + break; + case "rootdir": // This is exclusive to TruRip XML + content = reader.ReadElementContentAsString(); + RootDir = (String.IsNullOrWhiteSpace(RootDir) ? content : RootDir); + break; + case "category": + content = reader.ReadElementContentAsString(); + Category = (String.IsNullOrWhiteSpace(Category) ? content : Category); + break; + case "version": + content = reader.ReadElementContentAsString(); + Version = (String.IsNullOrWhiteSpace(Version) ? content : Version); + break; + case "date": + content = reader.ReadElementContentAsString(); + Date = (String.IsNullOrWhiteSpace(Date) ? content.Replace(".", "/") : Date); + break; + case "author": + content = reader.ReadElementContentAsString(); + Author = (String.IsNullOrWhiteSpace(Author) ? content : Author); + break; + case "email": + content = reader.ReadElementContentAsString(); + Email = (String.IsNullOrWhiteSpace(Email) ? content : Email); + break; + case "homepage": + content = reader.ReadElementContentAsString(); + Homepage = (String.IsNullOrWhiteSpace(Homepage) ? content : Homepage); + break; + case "url": + content = reader.ReadElementContentAsString(); + Url = (String.IsNullOrWhiteSpace(Url) ? content : Url); + break; + case "comment": + content = reader.ReadElementContentAsString(); + Comment = (String.IsNullOrWhiteSpace(Comment) ? content : Comment); + break; + case "type": // This is exclusive to TruRip XML + content = reader.ReadElementContentAsString(); + Type = (String.IsNullOrWhiteSpace(Type) ? content : Type); + superdat = superdat || content.Contains("SuperDAT"); + break; + case "clrmamepro": + if (String.IsNullOrWhiteSpace(Header)) + { + Header = reader.GetAttribute("header"); + } + if (ForceMerging == ForceMerging.None) + { + ForceMerging = Utilities.GetForceMerging(reader.GetAttribute("forcemerging")); + } + if (ForceNodump == ForceNodump.None) + { + ForceNodump = Utilities.GetForceNodump(reader.GetAttribute("forcenodump")); + } + if (ForcePacking == ForcePacking.None) + { + ForcePacking = Utilities.GetForcePacking(reader.GetAttribute("forcepacking")); + } + reader.Read(); + break; + case "romcenter": + if (reader.GetAttribute("plugin") != null) + { + // CDATA + } + if (reader.GetAttribute("rommode") != null) + { + // (merged|split|unmerged) "split" + } + if (reader.GetAttribute("biosmode") != null) + { + // merged|split|unmerged) "split" + } + if (reader.GetAttribute("samplemode") != null) + { + // (merged|unmerged) "merged" + } + if (reader.GetAttribute("lockrommode") != null) + { + // (yes|no) "no" + } + if (reader.GetAttribute("lockbiosmode") != null) + { + // (yes|no) "no" + } + if (reader.GetAttribute("locksamplemode") != null) + { + // (yes|no) "no" + } + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Read game/machine information + /// + /// XmlReader to use to parse the machine + /// List of dirs to prepend to the game name + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private void ReadMachine( + XmlReader reader, + List dirs, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // If we have an empty machine, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + string key = ""; + string temptype = reader.Name; + bool containsItems = false; + + // Create a new machine + MachineType machineType = MachineType.NULL; + if (Utilities.GetYesNo(reader.GetAttribute("isbios")) == true) + { + machineType |= MachineType.Bios; + } + if (Utilities.GetYesNo(reader.GetAttribute("isdevice")) == true) // Listxml-specific, used by older DATs + { + machineType |= MachineType.Device; + } + if (Utilities.GetYesNo(reader.GetAttribute("ismechanical")) == true) // Listxml-specific, used by older DATs + { + machineType |= MachineType.Mechanical; + } + + string dirsString = (dirs != null && dirs.Count() > 0 ? string.Join("/", dirs) + "/" : ""); + Machine machine = new Machine + { + Name = dirsString + reader.GetAttribute("name"), + Description = dirsString + reader.GetAttribute("name"), + SourceFile = reader.GetAttribute("sourcefile"), + Board = reader.GetAttribute("board"), + RebuildTo = reader.GetAttribute("rebuildto"), + Runnable = Utilities.GetYesNo(reader.GetAttribute("runnable")), // Listxml-specific, used by older DATs + + Comment = "", + + CloneOf = reader.GetAttribute("cloneof") ?? "", + RomOf = reader.GetAttribute("romof") ?? "", + SampleOf = reader.GetAttribute("sampleof") ?? "", + + MachineType = (machineType == MachineType.NULL ? MachineType.None : machineType), + }; + + if (Type == "SuperDAT" && !keep) + { + string tempout = Regex.Match(machine.Name, @".*?\\(.*)").Groups[1].Value; + if (!String.IsNullOrWhiteSpace(tempout)) + { + machine.Name = tempout; + } + } + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the roms from the machine + switch (reader.Name) + { + case "comment": // There can be multiple comments by spec + machine.Comment += reader.ReadElementContentAsString(); + break; + case "description": + machine.Description = reader.ReadElementContentAsString(); + break; + case "year": + machine.Year = reader.ReadElementContentAsString(); + break; + case "manufacturer": + machine.Manufacturer = reader.ReadElementContentAsString(); + break; + case "publisher": // Not technically supported but used by some legacy DATs + machine.Publisher = reader.ReadElementContentAsString(); + break; + case "trurip": // This is special metadata unique to TruRip + ReadTruRip(reader.ReadSubtree(), machine); + + // Skip the trurip node now that we've processed it + reader.Skip(); + break; + case "release": + containsItems = true; + + DatItem release = new Release + { + Name = reader.GetAttribute("name"), + Region = reader.GetAttribute("region"), + Language = reader.GetAttribute("language"), + Date = reader.GetAttribute("date"), + Default = Utilities.GetYesNo(reader.GetAttribute("default")), + }; + + release.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(release, clean, remUnicode); + + reader.Read(); + break; + case "biosset": + containsItems = true; + + DatItem biosset = new BiosSet + { + Name = reader.GetAttribute("name"), + Description = reader.GetAttribute("description"), + Default = Utilities.GetYesNo(reader.GetAttribute("default")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + biosset.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(biosset, clean, remUnicode); + + reader.Read(); + break; + case "rom": + containsItems = true; + + DatItem rom = new Rom + { + Name = reader.GetAttribute("name"), + Size = Utilities.GetSize(reader.GetAttribute("size")), + CRC = Utilities.CleanHashData(reader.GetAttribute("crc"), Constants.CRCLength), + MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), + SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), + SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), + SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), + SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), + MergeTag = reader.GetAttribute("merge"), + ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), + Date = Utilities.GetDate(reader.GetAttribute("date")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + rom.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(rom, clean, remUnicode); + + reader.Read(); + break; + case "disk": + containsItems = true; + + DatItem disk = new Disk + { + Name = reader.GetAttribute("name"), + MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), + SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), + SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), + SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), + SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), + MergeTag = reader.GetAttribute("merge"), + ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + disk.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(disk, clean, remUnicode); + + reader.Read(); + break; + case "sample": + containsItems = true; + + DatItem samplerom = new Sample + { + Name = reader.GetAttribute("name"), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + samplerom.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(samplerom, clean, remUnicode); + + reader.Read(); + break; + case "archive": + containsItems = true; + + DatItem archiverom = new Archive + { + Name = reader.GetAttribute("name"), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + archiverom.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(archiverom, clean, remUnicode); + + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + + // If no items were found for this machine, add a Blank placeholder + if (!containsItems) + { + Blank blank = new Blank() + { + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + blank.CopyMachineInformation(machine); + + // Now process and add the rom + ParseAddHelper(blank, clean, remUnicode); + } + } + + /// + /// Read TruRip information + /// + /// True if full pathnames are to be kept, false otherwise (default) + /// Machine information to pass to contained items + private void ReadTruRip(XmlReader reader, Machine machine) + { + // If we have an empty trurip, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the information from the trurip + string content = ""; + switch (reader.Name) + { + case "titleid": + content = reader.ReadElementContentAsString(); + // string titleid = content; + break; + case "publisher": + machine.Publisher = reader.ReadElementContentAsString(); + break; + case "developer": // Manufacturer is as close as this gets + machine.Manufacturer = reader.ReadElementContentAsString(); + break; + case "year": + machine.Year = reader.ReadElementContentAsString(); + break; + case "genre": + content = reader.ReadElementContentAsString(); + // string genre = content; + break; + case "subgenre": + content = reader.ReadElementContentAsString(); + // string subgenre = content; + break; + case "ratings": + content = reader.ReadElementContentAsString(); + // string ratings = content; + break; + case "score": + content = reader.ReadElementContentAsString(); + // string score = content; + break; + case "players": + content = reader.ReadElementContentAsString(); + // string players = content; + break; + case "enabled": + content = reader.ReadElementContentAsString(); + // string enabled = content; + break; + case "crc": + content = reader.ReadElementContentAsString(); + // string crc = Utilities.GetYesNo(content); + break; + case "source": + machine.SourceFile = reader.ReadElementContentAsString(); + break; + case "cloneof": + machine.CloneOf = reader.ReadElementContentAsString(); + break; + case "relatedto": + content = reader.ReadElementContentAsString(); + // string relatedto = content; + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public override bool WriteToFile(string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); + + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } + + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + + // Write out the header + WriteHeader(sw); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = this[key]; + + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); + + for (int index = 0; index < roms.Count; index++) + { + DatItem rom = roms[index]; + + // There are apparently times when a null rom can skip by, skip them + if (rom.Name == null || rom.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } + + // If we have a different game and we're not at the start of the list, output the end of last item + if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteEndGame(sw); + } + + // If we have a new game, output the beginning of the new item + if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteStartGame(sw, rom); + } + + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.ItemType == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + + rom.Name = (rom.Name == "null" ? "-" : rom.Name); + ((Rom)rom).Size = Constants.SizeZero; + ((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null; + ((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null; + ((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null; + ((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null; + ((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null; + ((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null; + } + + // Now, output the rom data + WriteDatItem(sw, rom, ignoreblanks); + + // Set the new data to compare against + lastgame = rom.MachineName; + } + } + + // Write the file footer out + WriteFooter(sw); + + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteHeader(StreamWriter sw) + { + try + { + string header = "\n" + + "\n\n" + + "\n" + + "\t
\n" + + "\t\t" + HttpUtility.HtmlEncode(Name) + "\n" + + "\t\t" + HttpUtility.HtmlEncode(Description) + "\n" + + (!String.IsNullOrWhiteSpace(RootDir) ? "\t\t" + HttpUtility.HtmlEncode(RootDir) + "\n" : "") + + (!String.IsNullOrWhiteSpace(Category) ? "\t\t" + HttpUtility.HtmlEncode(Category) + "\n" : "") + + "\t\t" + HttpUtility.HtmlEncode(Version) + "\n" + + (!String.IsNullOrWhiteSpace(Date) ? "\t\t" + HttpUtility.HtmlEncode(Date) + "\n" : "") + + "\t\t" + HttpUtility.HtmlEncode(Author) + "\n" + + (!String.IsNullOrWhiteSpace(Email) ? "\t\t" + HttpUtility.HtmlEncode(Email) + "\n" : "") + + (!String.IsNullOrWhiteSpace(Homepage) ? "\t\t" + HttpUtility.HtmlEncode(Homepage) + "\n" : "") + + (!String.IsNullOrWhiteSpace(Url) ? "\t\t" + HttpUtility.HtmlEncode(Url) + "\n" : "") + + (!String.IsNullOrWhiteSpace(Comment) ? "\t\t" + HttpUtility.HtmlEncode(Comment) + "\n" : "") + + (!String.IsNullOrWhiteSpace(Type) ? "\t\t" + HttpUtility.HtmlEncode(Type) + "\n" : "") + + (ForcePacking != ForcePacking.None || ForceMerging != ForceMerging.None || ForceNodump != ForceNodump.None || !String.IsNullOrWhiteSpace(Header) ? + "\t\t\n" + : "") + + "\t
\n"; + + // Write the header out + sw.Write(header); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if the data was written, false on error + private bool WriteStartGame(StreamWriter sw, DatItem rom) + { + try + { + // No game should start with a path separator + if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) + { + rom.MachineName = rom.MachineName.Substring(1); + } + + string state = "\t<" + (_depreciated ? "game" : "machine") + " name=\"" + (!ExcludeFields[(int)Field.MachineName] ? HttpUtility.HtmlEncode(rom.MachineName) : "") + "\"" + + (!ExcludeFields[(int)Field.MachineType] && (rom.MachineType & MachineType.Bios) != 0 ? " isbios=\"yes\"" : "") + + (!ExcludeFields[(int)Field.MachineType] && (rom.MachineType & MachineType.Device) != 0 ? " isdevice=\"yes\"" : "") + + (!ExcludeFields[(int)Field.MachineType] && (rom.MachineType & MachineType.Mechanical) != 0 ? " ismechanical=\"yes\"" : "") + + (!ExcludeFields[(int)Field.Runnable] && rom.Runnable == true + ? " runnable=\"yes\"" + : (!ExcludeFields[(int)Field.Runnable] && rom.Runnable == false ? " runnable=\"no\"" : "")) + + (!ExcludeFields[(int)Field.CloneOf] && !String.IsNullOrWhiteSpace(rom.CloneOf) && (rom.MachineName.ToLowerInvariant() != rom.CloneOf.ToLowerInvariant()) + ? " cloneof=\"" + HttpUtility.HtmlEncode(rom.CloneOf) + "\"" + : "") + + (!ExcludeFields[(int)Field.RomOf] && !String.IsNullOrWhiteSpace(rom.RomOf) && (rom.MachineName.ToLowerInvariant() != rom.RomOf.ToLowerInvariant()) + ? " romof=\"" + HttpUtility.HtmlEncode(rom.RomOf) + "\"" + : "") + + (!ExcludeFields[(int)Field.SampleOf] && !String.IsNullOrWhiteSpace(rom.SampleOf) && (rom.MachineName.ToLowerInvariant() != rom.SampleOf.ToLowerInvariant()) + ? " sampleof=\"" + HttpUtility.HtmlEncode(rom.SampleOf) + "\"" + : "") + + ">\n" + + (!ExcludeFields[(int)Field.Comment] && !String.IsNullOrWhiteSpace(rom.Comment) ? "\t\t" + HttpUtility.HtmlEncode(rom.Comment) + "\n" : "") + + (!ExcludeFields[(int)Field.Description] ? "\t\t" + HttpUtility.HtmlEncode((String.IsNullOrWhiteSpace(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription)) + "\n" : "") + + (!ExcludeFields[(int)Field.Year] && !String.IsNullOrWhiteSpace(rom.Year) ? "\t\t" + HttpUtility.HtmlEncode(rom.Year) + "\n" : "") + + (!ExcludeFields[(int)Field.Publisher] && !String.IsNullOrWhiteSpace(rom.Publisher) ? "\t\t" + HttpUtility.HtmlEncode(rom.Publisher) + "\n" : "") + + (!ExcludeFields[(int)Field.Manufacturer] && !String.IsNullOrWhiteSpace(rom.Manufacturer) ? "\t\t" + HttpUtility.HtmlEncode(rom.Manufacturer) + "\n" : ""); + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out Game end using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteEndGame(StreamWriter sw) + { + try + { + string state = "\t\n"; + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the data was written, false on error + private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.ItemType == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + + // Pre-process the item name + ProcessItemName(rom, true); + + switch (rom.ItemType) + { + case ItemType.Archive: + state += "\t\t\n"; + break; + case ItemType.BiosSet: + state += "\t\t\n"; + break; + case ItemType.Disk: + state += "\t\t\n"; + break; + case ItemType.Release: + state += "\t\t\n"; + break; + case ItemType.Rom: + state += "\t\t\n"; + break; + case ItemType.Sample: + state += "\t\t\n"; + break; + } + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT footer using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteFooter(StreamWriter sw) + { + try + { + string footer = "\t\n
\n"; + + // Write the footer out + sw.Write(footer); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + } } diff --git a/SabreTools.Library/DatFiles/Missfile.cs b/SabreTools.Library/DatFiles/Missfile.cs index 0e15b70b..778ddc46 100644 --- a/SabreTools.Library/DatFiles/Missfile.cs +++ b/SabreTools.Library/DatFiles/Missfile.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Text; - using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -18,176 +17,176 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { - /// - /// Represents parsing and writing of a Missfile - /// - internal class Missfile : DatFile - { - /// - /// Constructor designed for casting a base DatFile - /// - /// Parent DatFile to copy from - public Missfile(DatFile datFile) - : base(datFile, cloneHeader: false) - { - } + /// + /// Represents parsing and writing of a Missfile + /// + internal class Missfile : DatFile + { + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + public Missfile(DatFile datFile) + : base(datFile, cloneHeader: false) + { + } - /// - /// Parse a Missfile and return all found games and roms within - /// - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - public override void ParseFile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, + /// + /// Parse a Missfile and return all found games and roms within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // There is no consistent way to parse a missfile... - throw new NotImplementedException(); - } + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // There is no consistent way to parse a missfile... + throw new NotImplementedException(); + } - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the DAT was written correctly, false otherwise - public override bool WriteToFile(string outfile, bool ignoreblanks = false) - { - try - { - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = Utilities.TryCreate(outfile); + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public override bool WriteToFile(string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); - // If we get back null for some reason, just log and return - if (fs == null) - { - Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); - return false; - } + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); - // Write out each of the machines and roms - string lastgame = null; + // Write out each of the machines and roms + string lastgame = null; - // Get a properly sorted set of keys - List keys = Keys; - keys.Sort(new NaturalComparer()); + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); - foreach (string key in keys) - { - List roms = this[key]; + foreach (string key in keys) + { + List roms = this[key]; - // Resolve the names in the block - roms = DatItem.ResolveNames(roms); + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); - for (int index = 0; index < roms.Count; index++) - { - DatItem rom = roms[index]; + for (int index = 0; index < roms.Count; index++) + { + DatItem rom = roms[index]; - // There are apparently times when a null rom can skip by, skip them - if (rom.Name == null || rom.MachineName == null) - { - Globals.Logger.Warning("Null rom found!"); - continue; - } + // There are apparently times when a null rom can skip by, skip them + if (rom.Name == null || rom.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (rom.ItemType == ItemType.Rom - && ((Rom)rom).Size == -1 - && ((Rom)rom).CRC == "null") - { - Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); - lastgame = rom.MachineName; - continue; - } + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.ItemType == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + lastgame = rom.MachineName; + continue; + } - // Now, output the rom data - WriteDatItem(sw, rom, lastgame, ignoreblanks); + // Now, output the rom data + WriteDatItem(sw, rom, lastgame, ignoreblanks); - // Set the new data to compare against - lastgame = rom.MachineName; - } - } + // Set the new data to compare against + lastgame = rom.MachineName; + } + } - Globals.Logger.Verbose("File written!" + Environment.NewLine); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// The name of the last game to be output - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the data was written, false on error - private bool WriteDatItem(StreamWriter sw, DatItem rom, string lastgame, bool ignoreblanks = false) - { - // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip - if (ignoreblanks - && (rom.ItemType == ItemType.Rom - && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) - { - return true; - } + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// The name of the last game to be output + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the data was written, false on error + private bool WriteDatItem(StreamWriter sw, DatItem rom, string lastgame, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.ItemType == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } - try - { - string state = ""; + try + { + string state = ""; - // Process the item name - ProcessItemName(rom, false, forceRomName: false); + // Process the item name + ProcessItemName(rom, false, forceRomName: false); - // If we're in Romba mode, the state is consistent - if (Romba) - { - state += (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; - } - // Otherwise, use any flags - else - { - if (!UseRomName && rom.MachineName != lastgame) - { - state += (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; - lastgame = rom.MachineName; - } - else if (UseRomName) - { - state += (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; - } - } + // If we're in Romba mode, the state is consistent + if (Romba) + { + state += (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; + } + // Otherwise, use any flags + else + { + if (!UseRomName && rom.MachineName != lastgame) + { + state += (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; + lastgame = rom.MachineName; + } + else if (UseRomName) + { + state += (!ExcludeFields[(int)Field.Name] ? rom.Name : "") + "\n"; + } + } - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } - } + return true; + } + } } diff --git a/SabreTools.Library/DatFiles/OfflineList.cs b/SabreTools.Library/DatFiles/OfflineList.cs index fa0da19b..9730e1d2 100644 --- a/SabreTools.Library/DatFiles/OfflineList.cs +++ b/SabreTools.Library/DatFiles/OfflineList.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Text; -using System.Text.RegularExpressions; using System.Web; using System.Xml; - using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -21,1028 +19,1028 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { - /// - /// Represents parsing and writing of an OfflineList XML DAT - /// - /// TODO: Verify that all write for this DatFile type is correct - internal class OfflineList : DatFile - { - /// - /// Constructor designed for casting a base DatFile - /// - /// Parent DatFile to copy from - public OfflineList(DatFile datFile) - : base(datFile, cloneHeader: false) - { - } - - /// - /// Parse an OfflineList XML DAT and return all found games and roms within - /// - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - /// - /// - public override void ParseFile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - Encoding enc = Utilities.GetEncoding(filename); - XmlReader xtr = Utilities.GetXmlTextReader(filename); - - // If we got a null reader, just return - if (xtr == null) - { - return; - } - - // Otherwise, read the file to the end - try - { - xtr.MoveToContent(); - while (!xtr.EOF) - { - // We only want elements - if (xtr.NodeType != XmlNodeType.Element) - { - xtr.Read(); - continue; - } - - switch (xtr.Name) - { - case "configuration": - ReadConfiguration(xtr.ReadSubtree(), keep); - - // Skip the configuration node now that we've processed it - xtr.Skip(); - break; - case "games": - ReadGames(xtr.ReadSubtree(), keep, clean, remUnicode); - - // Skip the games node now that we've processed it - xtr.Skip(); - break; - default: - xtr.Read(); - break; - } - } - } - catch (Exception ex) - { - Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); - - // For XML errors, just skip the affected node - xtr?.Read(); - } - - xtr.Dispose(); - } - - /// - /// Read configuration information - /// - /// XmlReader to use to parse the header - /// True if full pathnames are to be kept, false otherwise (default) - private void ReadConfiguration(XmlReader reader, bool keep) - { - bool superdat = false; - - // If there's no subtree to the configuration, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all configuration items (ONLY OVERWRITE IF THERE'S NO DATA) - string content = ""; - switch (reader.Name.ToLowerInvariant()) - { - case "datname": - content = reader.ReadElementContentAsString(); - Name = (String.IsNullOrWhiteSpace(Name) ? content : Name); - superdat = superdat || content.Contains(" - SuperDAT"); - if (keep && superdat) - { - Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); - } - break; - case "datversion": - content = reader.ReadElementContentAsString(); - Version = (String.IsNullOrWhiteSpace(Version) ? content : Version); - break; - case "system": - content = reader.ReadElementContentAsString(); - // string system = content; - break; - case "screenshotswidth": - content = reader.ReadElementContentAsString(); - // string screenshotsWidth = content; // Int32? - break; - case "screenshotsheight": - content = reader.ReadElementContentAsString(); - // string screenshotsHeight = content; // Int32? - break; - case "infos": - ReadInfos(reader.ReadSubtree()); - - // Skip the infos node now that we've processed it - reader.Skip(); - break; - case "canopen": - ReadCanOpen(reader.ReadSubtree()); - - // Skip the canopen node now that we've processed it - reader.Skip(); - break; - case "newdat": - ReadNewDat(reader.ReadSubtree()); - - // Skip the newdat node now that we've processed it - reader.Skip(); - break; - case "search": - ReadSearch(reader.ReadSubtree()); - - // Skip the search node now that we've processed it - reader.Skip(); - break; - case "romtitle": - content = reader.ReadElementContentAsString(); - // string romtitle = content; - - break; - default: - reader.Read(); - break; - } - } - } - - /// - /// Read infos information - /// - /// XmlReader to use to parse the header - private void ReadInfos(XmlReader reader) - { - // If there's no subtree to the configuration, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all infos items - switch (reader.Name.ToLowerInvariant()) - { - case "title": - // string title_visible = reader.GetAttribute("visible"); // (true|false) - // string title_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) - // string title_default = reader.GetAttribute("default"); // (true|false) - reader.Read(); - break; - case "location": - // string location_visible = reader.GetAttribute("visible"); // (true|false) - // string location_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) - // string location_default = reader.GetAttribute("default"); // (true|false) - reader.Read(); - break; - case "publisher": - // string publisher_visible = reader.GetAttribute("visible"); // (true|false) - // string publisher_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) - // string publisher_default = reader.GetAttribute("default"); // (true|false) - reader.Read(); - break; - case "sourcerom": - // string sourceRom_visible = reader.GetAttribute("visible"); // (true|false) - // string sourceRom_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) - // string sourceRom_default = reader.GetAttribute("default"); // (true|false) - reader.Read(); - break; - case "savetype": - // string saveType_visible = reader.GetAttribute("visible"); // (true|false) - // string saveType_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) - // string saveType_default = reader.GetAttribute("default"); // (true|false) - reader.Read(); - break; - case "romsize": - // string romSize_visible = reader.GetAttribute("visible"); // (true|false) - // string romSize_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) - // string romSize_default = reader.GetAttribute("default"); // (true|false) - reader.Read(); - break; - case "releasenumber": - // string releaseNumber_visible = reader.GetAttribute("visible"); // (true|false) - // string releaseNumber_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) - // string releaseNumber_default = reader.GetAttribute("default"); // (true|false) - reader.Read(); - break; - case "languagenumber": - // string languageNumber_visible = reader.GetAttribute("visible"); // (true|false) - // string languageNumber_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) - // string languageNumber_default = reader.GetAttribute("default"); // (true|false) - reader.Read(); - break; - case "comment": - // string comment_visible = reader.GetAttribute("visible"); // (true|false) - // string comment_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) - // string comment_default = reader.GetAttribute("default"); // (true|false) - reader.Read(); - break; - case "romcrc": - // string romCRC_visible = reader.GetAttribute("visible"); // (true|false) - // string romCRC_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) - // string romCRC_default = reader.GetAttribute("default"); // (true|false) - reader.Read(); - break; - case "im1crc": - // string im1CRC_visible = reader.GetAttribute("visible"); // (true|false) - // string im1CRC_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) - // string im1CRC_default = reader.GetAttribute("default"); // (true|false) - reader.Read(); - break; - case "im2crc": - // string im2CRC_visible = reader.GetAttribute("visible"); // (true|false) - // string im2CRC_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) - // string im2CRC_default = reader.GetAttribute("default"); // (true|false) - reader.Read(); - break; - case "languages": - // string languages_visible = reader.GetAttribute("visible"); // (true|false) - // string languages_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) - // string languages_default = reader.GetAttribute("default"); // (true|false) - reader.Read(); - break; - default: - reader.Read(); - break; - } - } - } - - /// - /// Read canopen information - /// - /// XmlReader to use to parse the header - private void ReadCanOpen(XmlReader reader) - { - // Prepare all internal variables - List extensions = new List(); - - // If there's no subtree to the configuration, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all canopen items - switch (reader.Name.ToLowerInvariant()) - { - case "extension": - extensions.Add(reader.ReadElementContentAsString()); - break; - default: - reader.Read(); - break; - } - } - } - - /// - /// Read newdat information - /// - /// XmlReader to use to parse the header - private void ReadNewDat(XmlReader reader) - { - // If there's no subtree to the configuration, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all newdat items - string content = ""; - switch (reader.Name.ToLowerInvariant()) - { - case "datversionurl": - content = reader.ReadElementContentAsString(); - Url = (String.IsNullOrWhiteSpace(Name) ? content : Url); - break; - case "daturl": - // string fileName = reader.GetAttribute("fileName"); - content = reader.ReadElementContentAsString(); - // string url = content; - break; - case "imurl": - content = reader.ReadElementContentAsString(); - // string url = content; - break; - default: - reader.Read(); - break; - } - } - } - - /// - /// Read search information - /// - /// XmlReader to use to parse the header - private void ReadSearch(XmlReader reader) - { - // If there's no subtree to the configuration, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all search items - string content = ""; - switch (reader.Name.ToLowerInvariant()) - { - case "to": - // string value = reader.GetAttribute("value"); - // string default = reader.GetAttribute("default"); (true|false) - // string auto = reader.GetAttribute("auto"); (true|false) - - ReadTo(reader.ReadSubtree()); - - // Skip the to node now that we've processed it - reader.Skip(); - break; - default: - reader.Read(); - break; - } - } - } - - /// - /// Read to information - /// - /// XmlReader to use to parse the header - private void ReadTo(XmlReader reader) - { - // If there's no subtree to the configuration, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all search items - string content = ""; - switch (reader.Name.ToLowerInvariant()) - { - case "find": - // string operation = reader.GetAttribute("operation"); - // string value = reader.GetAttribute("value"); // Int32? - content = reader.ReadElementContentAsString(); - // string findValue = content; - break; - default: - reader.Read(); - break; - } - } - } - - /// - /// Read games information - /// - /// XmlReader to use to parse the header - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private void ReadGames(XmlReader reader, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // If there's no subtree to the configuration, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all games items (ONLY OVERWRITE IF THERE'S NO DATA) - switch (reader.Name.ToLowerInvariant()) - { - case "game": - ReadGame(reader.ReadSubtree(), keep, clean, remUnicode); - - // Skip the game node now that we've processed it - reader.Skip(); - break; - default: - reader.Read(); - break; - } - } - } - - /// - /// Read game information - /// - /// XmlReader to use to parse the header - /// True if full pathnames are to be kept, false otherwise (default) - /// 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(XmlReader reader, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Prepare all internal variables - string releaseNumber = "", key = "", publisher = "", duplicateid = ""; - long size = -1; - List roms = new List(); - Machine machine = new Machine(); - - // If there's no subtree to the configuration, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all games items - string content = ""; - switch (reader.Name.ToLowerInvariant()) - { - case "imagenumber": - content = reader.ReadElementContentAsString(); - // string imageNumber = content; - - break; - case "releasenumber": - releaseNumber = reader.ReadElementContentAsString(); - - break; - case "title": - content = reader.ReadElementContentAsString(); - machine.Name = content; - - break; - case "savetype": - content = reader.ReadElementContentAsString(); - // string saveType = content; - - break; - case "romsize": - if (!Int64.TryParse(reader.ReadElementContentAsString(), out size)) - { - size = -1; - } - - break; - case "publisher": - publisher = reader.ReadElementContentAsString(); - - break; - case "location": - content = reader.ReadElementContentAsString(); - // string location = content; - - break; - case "sourcerom": - content = reader.ReadElementContentAsString(); - // string sourceRom = content; - - break; - case "language": - content = reader.ReadElementContentAsString(); - // string language = content; - - break; - case "files": - roms = ReadFiles(reader.ReadSubtree(), releaseNumber, machine.Name, keep, clean, remUnicode); - - // Skip the files node now that we've processed it - reader.Skip(); - break; - case "im1crc": - content = reader.ReadElementContentAsString(); - // string im1crc = content; - - break; - case "im2crc": - content = reader.ReadElementContentAsString(); - // string im2crc = content; - - break; - case "comment": - machine.Comment = reader.ReadElementContentAsString(); - - break; - case "duplicateid": - duplicateid = reader.ReadElementContentAsString(); - if (duplicateid != "0") - { - machine.CloneOf = duplicateid; - } - - break; - default: - reader.Read(); - break; - } - } - - // Add information accordingly for each rom - for (int i = 0; i < roms.Count; i++) - { - roms[i].Size = size; - roms[i].Publisher = publisher; - roms[i].CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(roms[i], clean, remUnicode); - } - } - - /// - /// Read files information - /// - /// XmlReader to use to parse the header - /// Release number from the parent game - /// Name of the parent game to use - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private List ReadFiles(XmlReader reader, - string releaseNumber, - string machineName, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Prepare all internal variables - List> extensionToCrc = new List>(); - List roms = new List(); - - // If there's no subtree to the configuration, skip it - if (reader == null) - { - return roms; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all romCRC items - switch (reader.Name.ToLowerInvariant()) - { - case "romcrc": - extensionToCrc.Add( - new Tuple( - reader.GetAttribute("extension") ?? "", - reader.ReadElementContentAsString().ToLowerInvariant())); - break; - default: - reader.Read(); - break; - } - } - - // Now process the roms with the proper information - foreach (Tuple pair in extensionToCrc) - { - roms.Add(new Rom() - { - Name = (releaseNumber != "0" ? releaseNumber + " - " : "") + machineName + pair.Item1, - CRC = Utilities.CleanHashData(pair.Item2, Constants.CRCLength), - - ItemStatus = ItemStatus.None, - }); - } - - return roms; - } - - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the DAT was written correctly, false otherwise - public override bool WriteToFile(string outfile, bool ignoreblanks = false) - { - try - { - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = Utilities.TryCreate(outfile); - - // If we get back null for some reason, just log and return - if (fs == null) - { - Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); - return false; - } - - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); - - // Write out the header - WriteHeader(sw); - - // Write out each of the machines and roms - string lastgame = null; - - // Get a properly sorted set of keys - List keys = Keys; - keys.Sort(new NaturalComparer()); - - foreach (string key in keys) - { - List roms = this[key]; - - // Resolve the names in the block - roms = DatItem.ResolveNames(roms); - - for (int index = 0; index < roms.Count; index++) - { - DatItem rom = roms[index]; - - // There are apparently times when a null rom can skip by, skip them - if (rom.Name == null || rom.MachineName == null) - { - Globals.Logger.Warning("Null rom found!"); - continue; - } - - // If we have a different game and we're not at the start of the list, output the end of last item - if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteEndGame(sw); - } - - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (rom.ItemType == ItemType.Rom - && ((Rom)rom).Size == -1 - && ((Rom)rom).CRC == "null") - { - Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); - - rom.Name = (rom.Name == "null" ? "-" : rom.Name); - ((Rom)rom).Size = Constants.SizeZero; - ((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null; - ((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null; - ((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null; - ((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null; - ((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null; - ((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null; - } - - // Now, output the rom data - WriteDatItem(sw, rom, ignoreblanks); - - // Set the new data to compare against - lastgame = rom.MachineName; - } - } - - // Write the file footer out - WriteFooter(sw); - - Globals.Logger.Verbose("File written!" + Environment.NewLine); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteHeader(StreamWriter sw) - { - try - { - string header = "\n" - + "\n" - + "\t\n" - + "\t\t" + HttpUtility.HtmlEncode(Name) + "\n" - + "\t\t" + Count + "\n" - + "\t\tnone\n" - + "\t\t240\n" - + "\t\t160\n" - + "\t\t\n" - + "\t\t\t\n" - + "\t\t\t<location visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n" - + "\t\t\t<publisher visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n" - + "\t\t\t<sourceRom visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n" - + "\t\t\t<saveType visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n" - + "\t\t\t<romSize visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n" - + "\t\t\t<releaseNumber visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n" - + "\t\t\t<languageNumber visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n" - + "\t\t\t<comment visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n" - + "\t\t\t<romCRC visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n" - + "\t\t\t<im1CRC visible=\"false\" inNamingOption=\"false\" default=\"false\"/>\n" - + "\t\t\t<im2CRC visible=\"false\" inNamingOption=\"false\" default=\"false\"/>\n" - + "\t\t\t<languages visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n" - + "\t\t</infos>\n" - + "\t\t<canOpen>\n" - + "\t\t\t<extension>.bin</extension>\n" - + "\t\t</canOpen>\n" - + "\t\t<newDat>\n" - + "\t\t\t<datVersionURL>" + HttpUtility.HtmlEncode(Url) + "</datVersionURL>\n" - + "\t\t\t<datURL fileName=\"" + HttpUtility.HtmlEncode(FileName) + ".zip\">" + HttpUtility.HtmlEncode(Url) + "</datURL>\n" - + "\t\t\t<imURL>" + HttpUtility.HtmlEncode(Url) + "</imURL>\n" - + "\t\t</newDat>\n" - + "\t\t<search>\n" - + "\t\t\t<to value=\"location\" default=\"true\" auto=\"true\"/>\n" - + "\t\t\t<to value=\"romSize\" default=\"true\" auto=\"false\"/>\n" - + "\t\t\t<to value=\"languages\" default=\"true\" auto=\"true\"/>\n" - + "\t\t\t<to value=\"saveType\" default=\"false\" auto=\"false\"/>\n" - + "\t\t\t<to value=\"publisher\" default=\"false\" auto=\"true\"/>\n" - + "\t\t\t<to value=\"sourceRom\" default=\"false\" auto=\"true\"/>\n" - + "\t\t</search>\n" - + "\t\t<romTitle >%u - %n</romTitle>\n" - + "\t</configuration>\n" - + "\t<games>\n"; - - // Write the header out - sw.Write(header); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// <summary> - /// Write out Game start using the supplied StreamWriter - /// </summary> - /// <param name="sw">StreamWriter to output to</param> - /// <returns>True if the data was written, false on error</returns> - private bool WriteEndGame(StreamWriter sw) - { - try - { - string state = "\t\t</game>\n"; - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// <summary> - /// Write out DatItem using the supplied StreamWriter - /// </summary> - /// <param name="sw">StreamWriter to output to</param> - /// <param name="rom">DatItem object to be output</param> - /// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param> - /// <returns>True if the data was written, false on error</returns> - private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) - { - // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip - if (ignoreblanks - && (rom.ItemType == ItemType.Rom - && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) - { - return true; - } - - try - { - string state = ""; - - // Pre-process the item name - ProcessItemName(rom, true); - - state += "\t\t<game>\n" - + "\t\t\t<imageNumber>1</imageNumber>\n" - + "\t\t\t<releaseNumber>1</releaseNumber>\n" - + "\t\t\t<title>" + (!ExcludeFields[(int)Field.Name] ? HttpUtility.HtmlEncode(rom.Name) : "") + "\n" - + "\t\t\tNone\n"; - - if (rom.ItemType == ItemType.Rom) - { - state += "\t\t\t" + (!ExcludeFields[(int)Field.Size] ? ((Rom)rom).Size.ToString() : "") + "\n"; - } - - state += "\t\t\tNone\n" - + "\t\t\t0\n" - + "\t\t\tNone\n" - + "\t\t\t0\n"; - - if (rom.ItemType == ItemType.Disk) - { - state += "\t\t\t\n" - + (((Disk)rom).MD5 != null - ? "\t\t\t\t" + (!ExcludeFields[(int)Field.MD5] ? ((Disk)rom).MD5.ToUpperInvariant() : "") + "\n" - : "\t\t\t\t" + (!ExcludeFields[(int)Field.SHA1] ? ((Disk)rom).SHA1.ToUpperInvariant() : "") + "\n") - + "\t\t\t\n"; - } - else if (rom.ItemType == ItemType.Rom) - { - string tempext = "." + Utilities.GetExtension(((Rom)rom).Name); - - state += "\t\t\t\n" - + (((Rom)rom).CRC != null - ? "\t\t\t\t" + (!ExcludeFields[(int)Field.CRC] ? ((Rom)rom).CRC.ToUpperInvariant() : "") + "\n" - : ((Rom)rom).MD5 != null - ? "\t\t\t\t" + (!ExcludeFields[(int)Field.MD5] ? ((Rom)rom).MD5.ToUpperInvariant() : "") + "\n" - : "\t\t\t\t" + (!ExcludeFields[(int)Field.SHA1] ? ((Rom)rom).SHA1.ToUpperInvariant() : "") + "\n") - + "\t\t\t\n"; - } - - state += "\t\t\t00000000\n" - + "\t\t\t00000000\n" - + "\t\t\t\n" - + "\t\t\t0\n" - + "\t\t\n"; - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DAT footer using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteFooter(StreamWriter sw) - { - try - { - string footer = "\t\t" - + "\t\n" - + "\t\n" - + "\t\t\n" - + "\t\t\t\n" - + "\t\t\t\n" - + "\t\t\n" - + "\t\n" - + ""; - - // Write the footer out - sw.Write(footer); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - } + /// + /// Represents parsing and writing of an OfflineList XML DAT + /// + /// TODO: Verify that all write for this DatFile type is correct + internal class OfflineList : DatFile + { + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + public OfflineList(DatFile datFile) + : base(datFile, cloneHeader: false) + { + } + + /// + /// Parse an OfflineList XML DAT and return all found games and roms within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + /// + /// + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + Encoding enc = Utilities.GetEncoding(filename); + XmlReader xtr = Utilities.GetXmlTextReader(filename); + + // If we got a null reader, just return + if (xtr == null) + { + return; + } + + // Otherwise, read the file to the end + try + { + xtr.MoveToContent(); + while (!xtr.EOF) + { + // We only want elements + if (xtr.NodeType != XmlNodeType.Element) + { + xtr.Read(); + continue; + } + + switch (xtr.Name) + { + case "configuration": + ReadConfiguration(xtr.ReadSubtree(), keep); + + // Skip the configuration node now that we've processed it + xtr.Skip(); + break; + case "games": + ReadGames(xtr.ReadSubtree(), keep, clean, remUnicode); + + // Skip the games node now that we've processed it + xtr.Skip(); + break; + default: + xtr.Read(); + break; + } + } + } + catch (Exception ex) + { + Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); + + // For XML errors, just skip the affected node + xtr?.Read(); + } + + xtr.Dispose(); + } + + /// + /// Read configuration information + /// + /// XmlReader to use to parse the header + /// True if full pathnames are to be kept, false otherwise (default) + private void ReadConfiguration(XmlReader reader, bool keep) + { + bool superdat = false; + + // If there's no subtree to the configuration, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + // Otherwise, read what we can from the header + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get all configuration items (ONLY OVERWRITE IF THERE'S NO DATA) + string content = ""; + switch (reader.Name.ToLowerInvariant()) + { + case "datname": + content = reader.ReadElementContentAsString(); + Name = (String.IsNullOrWhiteSpace(Name) ? content : Name); + superdat = superdat || content.Contains(" - SuperDAT"); + if (keep && superdat) + { + Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); + } + break; + case "datversion": + content = reader.ReadElementContentAsString(); + Version = (String.IsNullOrWhiteSpace(Version) ? content : Version); + break; + case "system": + content = reader.ReadElementContentAsString(); + // string system = content; + break; + case "screenshotswidth": + content = reader.ReadElementContentAsString(); + // string screenshotsWidth = content; // Int32? + break; + case "screenshotsheight": + content = reader.ReadElementContentAsString(); + // string screenshotsHeight = content; // Int32? + break; + case "infos": + ReadInfos(reader.ReadSubtree()); + + // Skip the infos node now that we've processed it + reader.Skip(); + break; + case "canopen": + ReadCanOpen(reader.ReadSubtree()); + + // Skip the canopen node now that we've processed it + reader.Skip(); + break; + case "newdat": + ReadNewDat(reader.ReadSubtree()); + + // Skip the newdat node now that we've processed it + reader.Skip(); + break; + case "search": + ReadSearch(reader.ReadSubtree()); + + // Skip the search node now that we've processed it + reader.Skip(); + break; + case "romtitle": + content = reader.ReadElementContentAsString(); + // string romtitle = content; + + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Read infos information + /// + /// XmlReader to use to parse the header + private void ReadInfos(XmlReader reader) + { + // If there's no subtree to the configuration, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + // Otherwise, read what we can from the header + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get all infos items + switch (reader.Name.ToLowerInvariant()) + { + case "title": + // string title_visible = reader.GetAttribute("visible"); // (true|false) + // string title_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) + // string title_default = reader.GetAttribute("default"); // (true|false) + reader.Read(); + break; + case "location": + // string location_visible = reader.GetAttribute("visible"); // (true|false) + // string location_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) + // string location_default = reader.GetAttribute("default"); // (true|false) + reader.Read(); + break; + case "publisher": + // string publisher_visible = reader.GetAttribute("visible"); // (true|false) + // string publisher_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) + // string publisher_default = reader.GetAttribute("default"); // (true|false) + reader.Read(); + break; + case "sourcerom": + // string sourceRom_visible = reader.GetAttribute("visible"); // (true|false) + // string sourceRom_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) + // string sourceRom_default = reader.GetAttribute("default"); // (true|false) + reader.Read(); + break; + case "savetype": + // string saveType_visible = reader.GetAttribute("visible"); // (true|false) + // string saveType_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) + // string saveType_default = reader.GetAttribute("default"); // (true|false) + reader.Read(); + break; + case "romsize": + // string romSize_visible = reader.GetAttribute("visible"); // (true|false) + // string romSize_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) + // string romSize_default = reader.GetAttribute("default"); // (true|false) + reader.Read(); + break; + case "releasenumber": + // string releaseNumber_visible = reader.GetAttribute("visible"); // (true|false) + // string releaseNumber_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) + // string releaseNumber_default = reader.GetAttribute("default"); // (true|false) + reader.Read(); + break; + case "languagenumber": + // string languageNumber_visible = reader.GetAttribute("visible"); // (true|false) + // string languageNumber_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) + // string languageNumber_default = reader.GetAttribute("default"); // (true|false) + reader.Read(); + break; + case "comment": + // string comment_visible = reader.GetAttribute("visible"); // (true|false) + // string comment_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) + // string comment_default = reader.GetAttribute("default"); // (true|false) + reader.Read(); + break; + case "romcrc": + // string romCRC_visible = reader.GetAttribute("visible"); // (true|false) + // string romCRC_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) + // string romCRC_default = reader.GetAttribute("default"); // (true|false) + reader.Read(); + break; + case "im1crc": + // string im1CRC_visible = reader.GetAttribute("visible"); // (true|false) + // string im1CRC_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) + // string im1CRC_default = reader.GetAttribute("default"); // (true|false) + reader.Read(); + break; + case "im2crc": + // string im2CRC_visible = reader.GetAttribute("visible"); // (true|false) + // string im2CRC_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) + // string im2CRC_default = reader.GetAttribute("default"); // (true|false) + reader.Read(); + break; + case "languages": + // string languages_visible = reader.GetAttribute("visible"); // (true|false) + // string languages_inNamingOption = reader.GetAttribute("inNamingOption"); // (true|false) + // string languages_default = reader.GetAttribute("default"); // (true|false) + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Read canopen information + /// + /// XmlReader to use to parse the header + private void ReadCanOpen(XmlReader reader) + { + // Prepare all internal variables + List extensions = new List(); + + // If there's no subtree to the configuration, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + // Otherwise, read what we can from the header + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get all canopen items + switch (reader.Name.ToLowerInvariant()) + { + case "extension": + extensions.Add(reader.ReadElementContentAsString()); + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Read newdat information + /// + /// XmlReader to use to parse the header + private void ReadNewDat(XmlReader reader) + { + // If there's no subtree to the configuration, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + // Otherwise, read what we can from the header + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get all newdat items + string content = ""; + switch (reader.Name.ToLowerInvariant()) + { + case "datversionurl": + content = reader.ReadElementContentAsString(); + Url = (String.IsNullOrWhiteSpace(Name) ? content : Url); + break; + case "daturl": + // string fileName = reader.GetAttribute("fileName"); + content = reader.ReadElementContentAsString(); + // string url = content; + break; + case "imurl": + content = reader.ReadElementContentAsString(); + // string url = content; + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Read search information + /// + /// XmlReader to use to parse the header + private void ReadSearch(XmlReader reader) + { + // If there's no subtree to the configuration, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + // Otherwise, read what we can from the header + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get all search items + string content = ""; + switch (reader.Name.ToLowerInvariant()) + { + case "to": + // string value = reader.GetAttribute("value"); + // string default = reader.GetAttribute("default"); (true|false) + // string auto = reader.GetAttribute("auto"); (true|false) + + ReadTo(reader.ReadSubtree()); + + // Skip the to node now that we've processed it + reader.Skip(); + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Read to information + /// + /// XmlReader to use to parse the header + private void ReadTo(XmlReader reader) + { + // If there's no subtree to the configuration, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + // Otherwise, read what we can from the header + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get all search items + string content = ""; + switch (reader.Name.ToLowerInvariant()) + { + case "find": + // string operation = reader.GetAttribute("operation"); + // string value = reader.GetAttribute("value"); // Int32? + content = reader.ReadElementContentAsString(); + // string findValue = content; + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Read games information + /// + /// XmlReader to use to parse the header + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private void ReadGames(XmlReader reader, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // If there's no subtree to the configuration, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + // Otherwise, read what we can from the header + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get all games items (ONLY OVERWRITE IF THERE'S NO DATA) + switch (reader.Name.ToLowerInvariant()) + { + case "game": + ReadGame(reader.ReadSubtree(), keep, clean, remUnicode); + + // Skip the game node now that we've processed it + reader.Skip(); + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Read game information + /// + /// XmlReader to use to parse the header + /// True if full pathnames are to be kept, false otherwise (default) + /// 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(XmlReader reader, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Prepare all internal variables + string releaseNumber = "", key = "", publisher = "", duplicateid = ""; + long size = -1; + List roms = new List(); + Machine machine = new Machine(); + + // If there's no subtree to the configuration, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + // Otherwise, read what we can from the header + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get all games items + string content = ""; + switch (reader.Name.ToLowerInvariant()) + { + case "imagenumber": + content = reader.ReadElementContentAsString(); + // string imageNumber = content; + + break; + case "releasenumber": + releaseNumber = reader.ReadElementContentAsString(); + + break; + case "title": + content = reader.ReadElementContentAsString(); + machine.Name = content; + + break; + case "savetype": + content = reader.ReadElementContentAsString(); + // string saveType = content; + + break; + case "romsize": + if (!Int64.TryParse(reader.ReadElementContentAsString(), out size)) + { + size = -1; + } + + break; + case "publisher": + publisher = reader.ReadElementContentAsString(); + + break; + case "location": + content = reader.ReadElementContentAsString(); + // string location = content; + + break; + case "sourcerom": + content = reader.ReadElementContentAsString(); + // string sourceRom = content; + + break; + case "language": + content = reader.ReadElementContentAsString(); + // string language = content; + + break; + case "files": + roms = ReadFiles(reader.ReadSubtree(), releaseNumber, machine.Name, keep, clean, remUnicode); + + // Skip the files node now that we've processed it + reader.Skip(); + break; + case "im1crc": + content = reader.ReadElementContentAsString(); + // string im1crc = content; + + break; + case "im2crc": + content = reader.ReadElementContentAsString(); + // string im2crc = content; + + break; + case "comment": + machine.Comment = reader.ReadElementContentAsString(); + + break; + case "duplicateid": + duplicateid = reader.ReadElementContentAsString(); + if (duplicateid != "0") + { + machine.CloneOf = duplicateid; + } + + break; + default: + reader.Read(); + break; + } + } + + // Add information accordingly for each rom + for (int i = 0; i < roms.Count; i++) + { + roms[i].Size = size; + roms[i].Publisher = publisher; + roms[i].CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(roms[i], clean, remUnicode); + } + } + + /// + /// Read files information + /// + /// XmlReader to use to parse the header + /// Release number from the parent game + /// Name of the parent game to use + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private List ReadFiles(XmlReader reader, + string releaseNumber, + string machineName, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Prepare all internal variables + List> extensionToCrc = new List>(); + List roms = new List(); + + // If there's no subtree to the configuration, skip it + if (reader == null) + { + return roms; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + // Otherwise, read what we can from the header + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get all romCRC items + switch (reader.Name.ToLowerInvariant()) + { + case "romcrc": + extensionToCrc.Add( + new Tuple( + reader.GetAttribute("extension") ?? "", + reader.ReadElementContentAsString().ToLowerInvariant())); + break; + default: + reader.Read(); + break; + } + } + + // Now process the roms with the proper information + foreach (Tuple pair in extensionToCrc) + { + roms.Add(new Rom() + { + Name = (releaseNumber != "0" ? releaseNumber + " - " : "") + machineName + pair.Item1, + CRC = Utilities.CleanHashData(pair.Item2, Constants.CRCLength), + + ItemStatus = ItemStatus.None, + }); + } + + return roms; + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public override bool WriteToFile(string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); + + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } + + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + + // Write out the header + WriteHeader(sw); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = this[key]; + + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); + + for (int index = 0; index < roms.Count; index++) + { + DatItem rom = roms[index]; + + // There are apparently times when a null rom can skip by, skip them + if (rom.Name == null || rom.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } + + // If we have a different game and we're not at the start of the list, output the end of last item + if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteEndGame(sw); + } + + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.ItemType == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + + rom.Name = (rom.Name == "null" ? "-" : rom.Name); + ((Rom)rom).Size = Constants.SizeZero; + ((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null; + ((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null; + ((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null; + ((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null; + ((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null; + ((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null; + } + + // Now, output the rom data + WriteDatItem(sw, rom, ignoreblanks); + + // Set the new data to compare against + lastgame = rom.MachineName; + } + } + + // Write the file footer out + WriteFooter(sw); + + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteHeader(StreamWriter sw) + { + try + { + string header = "\n" + + "\n" + + "\t\n" + + "\t\t" + HttpUtility.HtmlEncode(Name) + "\n" + + "\t\t" + Count + "\n" + + "\t\tnone\n" + + "\t\t240\n" + + "\t\t160\n" + + "\t\t\n" + + "\t\t\t\n" + + "\t\t\t<location visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n" + + "\t\t\t<publisher visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n" + + "\t\t\t<sourceRom visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n" + + "\t\t\t<saveType visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n" + + "\t\t\t<romSize visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n" + + "\t\t\t<releaseNumber visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n" + + "\t\t\t<languageNumber visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n" + + "\t\t\t<comment visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n" + + "\t\t\t<romCRC visible=\"true\" inNamingOption=\"true\" default=\"false\"/>\n" + + "\t\t\t<im1CRC visible=\"false\" inNamingOption=\"false\" default=\"false\"/>\n" + + "\t\t\t<im2CRC visible=\"false\" inNamingOption=\"false\" default=\"false\"/>\n" + + "\t\t\t<languages visible=\"true\" inNamingOption=\"true\" default=\"true\"/>\n" + + "\t\t</infos>\n" + + "\t\t<canOpen>\n" + + "\t\t\t<extension>.bin</extension>\n" + + "\t\t</canOpen>\n" + + "\t\t<newDat>\n" + + "\t\t\t<datVersionURL>" + HttpUtility.HtmlEncode(Url) + "</datVersionURL>\n" + + "\t\t\t<datURL fileName=\"" + HttpUtility.HtmlEncode(FileName) + ".zip\">" + HttpUtility.HtmlEncode(Url) + "</datURL>\n" + + "\t\t\t<imURL>" + HttpUtility.HtmlEncode(Url) + "</imURL>\n" + + "\t\t</newDat>\n" + + "\t\t<search>\n" + + "\t\t\t<to value=\"location\" default=\"true\" auto=\"true\"/>\n" + + "\t\t\t<to value=\"romSize\" default=\"true\" auto=\"false\"/>\n" + + "\t\t\t<to value=\"languages\" default=\"true\" auto=\"true\"/>\n" + + "\t\t\t<to value=\"saveType\" default=\"false\" auto=\"false\"/>\n" + + "\t\t\t<to value=\"publisher\" default=\"false\" auto=\"true\"/>\n" + + "\t\t\t<to value=\"sourceRom\" default=\"false\" auto=\"true\"/>\n" + + "\t\t</search>\n" + + "\t\t<romTitle >%u - %n</romTitle>\n" + + "\t</configuration>\n" + + "\t<games>\n"; + + // Write the header out + sw.Write(header); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// <summary> + /// Write out Game start using the supplied StreamWriter + /// </summary> + /// <param name="sw">StreamWriter to output to</param> + /// <returns>True if the data was written, false on error</returns> + private bool WriteEndGame(StreamWriter sw) + { + try + { + string state = "\t\t</game>\n"; + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// <summary> + /// Write out DatItem using the supplied StreamWriter + /// </summary> + /// <param name="sw">StreamWriter to output to</param> + /// <param name="rom">DatItem object to be output</param> + /// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param> + /// <returns>True if the data was written, false on error</returns> + private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.ItemType == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + + // Pre-process the item name + ProcessItemName(rom, true); + + state += "\t\t<game>\n" + + "\t\t\t<imageNumber>1</imageNumber>\n" + + "\t\t\t<releaseNumber>1</releaseNumber>\n" + + "\t\t\t<title>" + (!ExcludeFields[(int)Field.Name] ? HttpUtility.HtmlEncode(rom.Name) : "") + "\n" + + "\t\t\tNone\n"; + + if (rom.ItemType == ItemType.Rom) + { + state += "\t\t\t" + (!ExcludeFields[(int)Field.Size] ? ((Rom)rom).Size.ToString() : "") + "\n"; + } + + state += "\t\t\tNone\n" + + "\t\t\t0\n" + + "\t\t\tNone\n" + + "\t\t\t0\n"; + + if (rom.ItemType == ItemType.Disk) + { + state += "\t\t\t\n" + + (((Disk)rom).MD5 != null + ? "\t\t\t\t" + (!ExcludeFields[(int)Field.MD5] ? ((Disk)rom).MD5.ToUpperInvariant() : "") + "\n" + : "\t\t\t\t" + (!ExcludeFields[(int)Field.SHA1] ? ((Disk)rom).SHA1.ToUpperInvariant() : "") + "\n") + + "\t\t\t\n"; + } + else if (rom.ItemType == ItemType.Rom) + { + string tempext = "." + Utilities.GetExtension(((Rom)rom).Name); + + state += "\t\t\t\n" + + (((Rom)rom).CRC != null + ? "\t\t\t\t" + (!ExcludeFields[(int)Field.CRC] ? ((Rom)rom).CRC.ToUpperInvariant() : "") + "\n" + : ((Rom)rom).MD5 != null + ? "\t\t\t\t" + (!ExcludeFields[(int)Field.MD5] ? ((Rom)rom).MD5.ToUpperInvariant() : "") + "\n" + : "\t\t\t\t" + (!ExcludeFields[(int)Field.SHA1] ? ((Rom)rom).SHA1.ToUpperInvariant() : "") + "\n") + + "\t\t\t\n"; + } + + state += "\t\t\t00000000\n" + + "\t\t\t00000000\n" + + "\t\t\t\n" + + "\t\t\t0\n" + + "\t\t\n"; + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT footer using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteFooter(StreamWriter sw) + { + try + { + string footer = "\t\t" + + "\t\n" + + "\t\n" + + "\t\t\n" + + "\t\t\t\n" + + "\t\t\t\n" + + "\t\t\n" + + "\t\n" + + ""; + + // Write the footer out + sw.Write(footer); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + } } diff --git a/SabreTools.Library/DatFiles/OpenMSX.cs b/SabreTools.Library/DatFiles/OpenMSX.cs index 3d0f30b5..ba2d176e 100644 --- a/SabreTools.Library/DatFiles/OpenMSX.cs +++ b/SabreTools.Library/DatFiles/OpenMSX.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Text; using System.Web; using System.Xml; - using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -20,607 +19,607 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { - /// - /// Represents parsing and writing of a openMSX softawre list XML DAT - /// - /// TODO: Verify that all write for this DatFile type is correct - internal class OpenMSX : DatFile - { - /// - /// Constructor designed for casting a base DatFile - /// - /// Parent DatFile to copy from - public OpenMSX(DatFile datFile) - : base(datFile, cloneHeader: false) - { - } - - /// - /// Parse a openMSX softawre list XML DAT and return all found games and roms within - /// - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - /// - /// - public override void ParseFile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Prepare all internal variables - Encoding enc = Utilities.GetEncoding(filename); - XmlReader xtr = Utilities.GetXmlTextReader(filename); - - // If we got a null reader, just return - if (xtr == null) - { - return; - } - - // Otherwise, read the file to the end - try - { - xtr.MoveToContent(); - while (!xtr.EOF) - { - // We only want elements - if (xtr.NodeType != XmlNodeType.Element) - { - xtr.Read(); - continue; - } - - switch (xtr.Name) - { - case "softwaredb": - Name = (String.IsNullOrWhiteSpace(Name) ? "openMSX Software List" : Name); - Description = (String.IsNullOrWhiteSpace(Description) ? Name : Name); - // string timestamp = xtr.GetAttribute("timestamp"); // CDATA - xtr.Read(); - break; - // We want to process the entire subtree of the software - case "software": - ReadSoftware(xtr.ReadSubtree(), filename, sysid, srcid, keep, clean, remUnicode); - - // Skip the software now that we've processed it - xtr.Skip(); - break; - default: - xtr.Read(); - break; - } - } - } - catch (Exception ex) - { - Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); - - // For XML errors, just skip the affected node - xtr?.Read(); - } - - xtr.Dispose(); - } - - /// - /// Read software information - /// - /// XmlReader representing a machine block - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private void ReadSoftware( - XmlReader reader, - - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // If we have an empty machine, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - int diskno = 0; - bool containsItems = false; - - // Create a new machine - Machine machine = new Machine(); - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the roms from the machine - switch (reader.Name) - { - case "title": - machine.Name = reader.ReadElementContentAsString(); - break; - case "genmsxid": - // string id = reader.ReadElementContentAsString(); - reader.Read(); - break; - case "system": - // string system = reader.ReadElementContentAsString(); - reader.Read(); - break; - case "company": - machine.Manufacturer = reader.ReadElementContentAsString(); - break; - case "year": - machine.Year = reader.ReadElementContentAsString(); - break; - case "country": - // string country = reader.ReadElementContentAsString(); - reader.Read(); - break; - case "dump": - containsItems = ReadDump(reader.ReadSubtree(), machine, diskno, filename, sysid, srcid, keep, clean, remUnicode); - diskno++; - - // Skip the dump now that we've processed it - reader.Skip(); - break; - default: - reader.Read(); - break; - } - } - - // If no items were found for this machine, add a Blank placeholder - if (!containsItems) - { - Blank blank = new Blank() - { - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - blank.CopyMachineInformation(machine); - - // Now process and add the rom - ParseAddHelper(blank, clean, remUnicode); - } - } - - /// - /// Read dump information - /// - /// XmlReader representing a part block - /// Machine information to pass to contained items - /// Disk number to use when outputting to other DAT formats - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private bool ReadDump( - XmlReader reader, - Machine machine, - int diskno, - - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - bool containsItems = false; - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the elements from the dump - switch (reader.Name) - { - case "rom": - containsItems = ReadRom(reader.ReadSubtree(), machine, diskno, filename, sysid, srcid, keep, clean, remUnicode); - - // Skip the rom now that we've processed it - reader.Skip(); - break; - case "megarom": - containsItems = ReadMegaRom(reader.ReadSubtree(), machine, diskno, filename, sysid, srcid, keep, clean, remUnicode); - - // Skip the megarom now that we've processed it - reader.Skip(); - break; - case "sccpluscart": - containsItems = ReadSccPlusCart(reader.ReadSubtree(), machine, diskno, filename, sysid, srcid, keep, clean, remUnicode); - - // Skip the sccpluscart now that we've processed it - reader.Skip(); - break; - case "original": - // bool value = Utilities.GetYesNo(reader.GetAttribute("value"); - // string original = reader.ReadElementContentAsString(); - reader.Read(); - break; - default: - reader.Read(); - break; - } - } - - return containsItems; - } - - /// - /// Read rom information - /// - /// XmlReader representing a rom block - /// Machine information to pass to contained items - /// Disk number to use when outputting to other DAT formats - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private bool ReadRom( - XmlReader reader, - Machine machine, - int diskno, - - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - string hash = "", offset = "", type = "", remark = ""; - bool containsItems = false; - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the elements from the rom - switch (reader.Name) - { - case "hash": - containsItems = true; - hash = reader.ReadElementContentAsString(); - break; - case "start": - offset = reader.ReadElementContentAsString(); - break; - case "type": - type = reader.ReadElementContentAsString(); - break; - case "remark": - remark = reader.ReadElementContentAsString(); - break; - default: - reader.Read(); - break; - } - } - - // Create and add the new rom - Rom rom = new Rom - { - Name = machine.Name + "_" + diskno + (!String.IsNullOrWhiteSpace(remark) ? " " + remark : ""), - Offset = offset, - Size = -1, - SHA1 = Utilities.CleanHashData(hash, Constants.SHA1Length), - }; - - rom.CopyMachineInformation(machine); - ParseAddHelper(rom, clean, remUnicode); - - return containsItems; - } - - /// - /// Read megarom information - /// - /// XmlReader representing a megarom block - /// Machine information to pass to contained items - /// Disk number to use when outputting to other DAT formats - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private bool ReadMegaRom( - XmlReader reader, - Machine machine, - int diskno, - - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - string hash = "", offset = "", type = "", remark = ""; - bool containsItems = false; - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the elements from the dump - switch (reader.Name) - { - case "hash": - containsItems = true; - hash = reader.ReadElementContentAsString(); - break; - case "start": - offset = reader.ReadElementContentAsString(); - break; - case "type": - type = reader.ReadElementContentAsString(); - break; - case "remark": - remark = reader.ReadElementContentAsString(); - break; - default: - reader.Read(); - break; - } - } - - // Create and add the new rom - Rom rom = new Rom - { - Name = machine.Name + "_" + diskno + (!String.IsNullOrWhiteSpace(remark) ? " " + remark : ""), - Offset = offset, - Size = -1, - SHA1 = Utilities.CleanHashData(hash, Constants.SHA1Length), - }; - - rom.CopyMachineInformation(machine); - ParseAddHelper(rom, clean, remUnicode); - - return containsItems; - } - - /// - /// Read sccpluscart information - /// - /// XmlReader representing a sccpluscart block - /// Machine information to pass to contained items - /// Disk number to use when outputting to other DAT formats - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private bool ReadSccPlusCart( - XmlReader reader, - Machine machine, - int diskno, - - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - string hash = "", boot = "", remark = ""; - bool containsItems = false; - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the elements from the dump - switch (reader.Name) - { - case "boot": - boot = reader.ReadElementContentAsString(); - break; - case "hash": - containsItems = true; - hash = reader.ReadElementContentAsString(); - break; - case "remark": - remark = reader.ReadElementContentAsString(); - break; - default: - reader.Read(); - break; - } - } - - // Create and add the new rom - Rom rom = new Rom - { - Name = machine.Name + "_" + diskno + (!String.IsNullOrWhiteSpace(remark) ? " " + remark : ""), - Size = -1, - SHA1 = Utilities.CleanHashData(hash, Constants.SHA1Length), - }; - - rom.CopyMachineInformation(machine); - ParseAddHelper(rom, clean, remUnicode); - - return containsItems; - } - - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the DAT was written correctly, false otherwise - public override bool WriteToFile(string outfile, bool ignoreblanks = false) - { - try - { - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = Utilities.TryCreate(outfile); - - // If we get back null for some reason, just log and return - if (fs == null) - { - Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); - return false; - } - - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); - - // Write out the header - WriteHeader(sw); - - // Write out each of the machines and roms - string lastgame = null; - - // Get a properly sorted set of keys - List keys = Keys; - keys.Sort(new NaturalComparer()); - - foreach (string key in keys) - { - List roms = this[key]; - - // Resolve the names in the block - roms = DatItem.ResolveNames(roms); - - for (int index = 0; index < roms.Count; index++) - { - DatItem rom = roms[index]; - - // There are apparently times when a null rom can skip by, skip them - if (rom.Name == null || rom.MachineName == null) - { - Globals.Logger.Warning("Null rom found!"); - continue; - } - - // If we have a different game and we're not at the start of the list, output the end of last item - if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteEndGame(sw); - } - - // If we have a new game, output the beginning of the new item - if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteStartGame(sw, rom); - } - - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (rom.ItemType == ItemType.Rom - && ((Rom)rom).Size == -1 - && ((Rom)rom).CRC == "null") - { - Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); - - lastgame = rom.MachineName; - continue; - } - - // Now, output the rom data - WriteDatItem(sw, rom, ignoreblanks); - - // Set the new data to compare against - lastgame = rom.MachineName; - } - } - - // Write the file footer out - WriteFooter(sw); - - Globals.Logger.Verbose("File written!" + Environment.NewLine); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteHeader(StreamWriter sw) - { - try - { - string header = "\n" + - "\n" + - "\n" + - @" + /// + /// Represents parsing and writing of a openMSX softawre list XML DAT + /// + /// TODO: Verify that all write for this DatFile type is correct + internal class OpenMSX : DatFile + { + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + public OpenMSX(DatFile datFile) + : base(datFile, cloneHeader: false) + { + } + + /// + /// Parse a openMSX softawre list XML DAT and return all found games and roms within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + /// + /// + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Prepare all internal variables + Encoding enc = Utilities.GetEncoding(filename); + XmlReader xtr = Utilities.GetXmlTextReader(filename); + + // If we got a null reader, just return + if (xtr == null) + { + return; + } + + // Otherwise, read the file to the end + try + { + xtr.MoveToContent(); + while (!xtr.EOF) + { + // We only want elements + if (xtr.NodeType != XmlNodeType.Element) + { + xtr.Read(); + continue; + } + + switch (xtr.Name) + { + case "softwaredb": + Name = (String.IsNullOrWhiteSpace(Name) ? "openMSX Software List" : Name); + Description = (String.IsNullOrWhiteSpace(Description) ? Name : Name); + // string timestamp = xtr.GetAttribute("timestamp"); // CDATA + xtr.Read(); + break; + // We want to process the entire subtree of the software + case "software": + ReadSoftware(xtr.ReadSubtree(), filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the software now that we've processed it + xtr.Skip(); + break; + default: + xtr.Read(); + break; + } + } + } + catch (Exception ex) + { + Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); + + // For XML errors, just skip the affected node + xtr?.Read(); + } + + xtr.Dispose(); + } + + /// + /// Read software information + /// + /// XmlReader representing a machine block + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private void ReadSoftware( + XmlReader reader, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // If we have an empty machine, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + int diskno = 0; + bool containsItems = false; + + // Create a new machine + Machine machine = new Machine(); + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the roms from the machine + switch (reader.Name) + { + case "title": + machine.Name = reader.ReadElementContentAsString(); + break; + case "genmsxid": + // string id = reader.ReadElementContentAsString(); + reader.Read(); + break; + case "system": + // string system = reader.ReadElementContentAsString(); + reader.Read(); + break; + case "company": + machine.Manufacturer = reader.ReadElementContentAsString(); + break; + case "year": + machine.Year = reader.ReadElementContentAsString(); + break; + case "country": + // string country = reader.ReadElementContentAsString(); + reader.Read(); + break; + case "dump": + containsItems = ReadDump(reader.ReadSubtree(), machine, diskno, filename, sysid, srcid, keep, clean, remUnicode); + diskno++; + + // Skip the dump now that we've processed it + reader.Skip(); + break; + default: + reader.Read(); + break; + } + } + + // If no items were found for this machine, add a Blank placeholder + if (!containsItems) + { + Blank blank = new Blank() + { + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + blank.CopyMachineInformation(machine); + + // Now process and add the rom + ParseAddHelper(blank, clean, remUnicode); + } + } + + /// + /// Read dump information + /// + /// XmlReader representing a part block + /// Machine information to pass to contained items + /// Disk number to use when outputting to other DAT formats + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private bool ReadDump( + XmlReader reader, + Machine machine, + int diskno, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + bool containsItems = false; + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the elements from the dump + switch (reader.Name) + { + case "rom": + containsItems = ReadRom(reader.ReadSubtree(), machine, diskno, filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the rom now that we've processed it + reader.Skip(); + break; + case "megarom": + containsItems = ReadMegaRom(reader.ReadSubtree(), machine, diskno, filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the megarom now that we've processed it + reader.Skip(); + break; + case "sccpluscart": + containsItems = ReadSccPlusCart(reader.ReadSubtree(), machine, diskno, filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the sccpluscart now that we've processed it + reader.Skip(); + break; + case "original": + // bool value = Utilities.GetYesNo(reader.GetAttribute("value"); + // string original = reader.ReadElementContentAsString(); + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + + return containsItems; + } + + /// + /// Read rom information + /// + /// XmlReader representing a rom block + /// Machine information to pass to contained items + /// Disk number to use when outputting to other DAT formats + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private bool ReadRom( + XmlReader reader, + Machine machine, + int diskno, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + string hash = "", offset = "", type = "", remark = ""; + bool containsItems = false; + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the elements from the rom + switch (reader.Name) + { + case "hash": + containsItems = true; + hash = reader.ReadElementContentAsString(); + break; + case "start": + offset = reader.ReadElementContentAsString(); + break; + case "type": + type = reader.ReadElementContentAsString(); + break; + case "remark": + remark = reader.ReadElementContentAsString(); + break; + default: + reader.Read(); + break; + } + } + + // Create and add the new rom + Rom rom = new Rom + { + Name = machine.Name + "_" + diskno + (!String.IsNullOrWhiteSpace(remark) ? " " + remark : ""), + Offset = offset, + Size = -1, + SHA1 = Utilities.CleanHashData(hash, Constants.SHA1Length), + }; + + rom.CopyMachineInformation(machine); + ParseAddHelper(rom, clean, remUnicode); + + return containsItems; + } + + /// + /// Read megarom information + /// + /// XmlReader representing a megarom block + /// Machine information to pass to contained items + /// Disk number to use when outputting to other DAT formats + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private bool ReadMegaRom( + XmlReader reader, + Machine machine, + int diskno, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + string hash = "", offset = "", type = "", remark = ""; + bool containsItems = false; + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the elements from the dump + switch (reader.Name) + { + case "hash": + containsItems = true; + hash = reader.ReadElementContentAsString(); + break; + case "start": + offset = reader.ReadElementContentAsString(); + break; + case "type": + type = reader.ReadElementContentAsString(); + break; + case "remark": + remark = reader.ReadElementContentAsString(); + break; + default: + reader.Read(); + break; + } + } + + // Create and add the new rom + Rom rom = new Rom + { + Name = machine.Name + "_" + diskno + (!String.IsNullOrWhiteSpace(remark) ? " " + remark : ""), + Offset = offset, + Size = -1, + SHA1 = Utilities.CleanHashData(hash, Constants.SHA1Length), + }; + + rom.CopyMachineInformation(machine); + ParseAddHelper(rom, clean, remUnicode); + + return containsItems; + } + + /// + /// Read sccpluscart information + /// + /// XmlReader representing a sccpluscart block + /// Machine information to pass to contained items + /// Disk number to use when outputting to other DAT formats + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private bool ReadSccPlusCart( + XmlReader reader, + Machine machine, + int diskno, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + string hash = "", boot = "", remark = ""; + bool containsItems = false; + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the elements from the dump + switch (reader.Name) + { + case "boot": + boot = reader.ReadElementContentAsString(); + break; + case "hash": + containsItems = true; + hash = reader.ReadElementContentAsString(); + break; + case "remark": + remark = reader.ReadElementContentAsString(); + break; + default: + reader.Read(); + break; + } + } + + // Create and add the new rom + Rom rom = new Rom + { + Name = machine.Name + "_" + diskno + (!String.IsNullOrWhiteSpace(remark) ? " " + remark : ""), + Size = -1, + SHA1 = Utilities.CleanHashData(hash, Constants.SHA1Length), + }; + + rom.CopyMachineInformation(machine); + ParseAddHelper(rom, clean, remUnicode); + + return containsItems; + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public override bool WriteToFile(string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); + + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } + + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + + // Write out the header + WriteHeader(sw); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = this[key]; + + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); + + for (int index = 0; index < roms.Count; index++) + { + DatItem rom = roms[index]; + + // There are apparently times when a null rom can skip by, skip them + if (rom.Name == null || rom.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } + + // If we have a different game and we're not at the start of the list, output the end of last item + if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteEndGame(sw); + } + + // If we have a new game, output the beginning of the new item + if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteStartGame(sw, rom); + } + + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.ItemType == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + + lastgame = rom.MachineName; + continue; + } + + // Now, output the rom data + WriteDatItem(sw, rom, ignoreblanks); + + // Set the new data to compare against + lastgame = rom.MachineName; + } + } + + // Write the file footer out + WriteFooter(sw); + + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteHeader(StreamWriter sw) + { + try + { + string header = "\n" + + "\n" + + "\n" + + @" "; - // Write the header out - sw.Write(header); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + // Write the header out + sw.Write(header); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out Game start using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if the data was written, false on error - private bool WriteStartGame(StreamWriter sw, DatItem rom) - { - try - { - // No game should start with a path separator - if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) - { - rom.MachineName = rom.MachineName.Substring(1); - } + /// + /// Write out Game start using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if the data was written, false on error + private bool WriteStartGame(StreamWriter sw, DatItem rom) + { + try + { + // No game should start with a path separator + if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) + { + rom.MachineName = rom.MachineName.Substring(1); + } - string state = "\n" - + "\t" + (!ExcludeFields[(int)Field.MachineName] ? HttpUtility.HtmlEncode(rom.MachineName) : "") + "\n" - // + "\t" + msxid + "\n" - // + "\t" + system + "\n" - + "\t" + (!ExcludeFields[(int)Field.Manufacturer] ? rom.Manufacturer : "") + "\n" - + "\t" + (!ExcludeFields[(int)Field.Year] ? rom.Year : "") + "\n"; - // + "\t" + country + "\n"; + string state = "\n" + + "\t" + (!ExcludeFields[(int)Field.MachineName] ? HttpUtility.HtmlEncode(rom.MachineName) : "") + "\n" + // + "\t" + msxid + "\n" + // + "\t" + system + "\n" + + "\t" + (!ExcludeFields[(int)Field.Manufacturer] ? rom.Manufacturer : "") + "\n" + + "\t" + (!ExcludeFields[(int)Field.Year] ? rom.Year : "") + "\n"; + // + "\t" + country + "\n"; - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out Game start using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteEndGame(StreamWriter sw) - { - try - { - string state = "\n"; + /// + /// Write out Game start using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteEndGame(StreamWriter sw) + { + try + { + string state = "\n"; - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the data was written, false on error - private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) - { - // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip - if (ignoreblanks - && (rom.ItemType == ItemType.Rom - && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) - { - return true; - } + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the data was written, false on error + private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.ItemType == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } - try - { - string state = ""; + try + { + string state = ""; - // Pre-process the item name - ProcessItemName(rom, true); + // Pre-process the item name + ProcessItemName(rom, true); - switch (rom.ItemType) - { - case ItemType.Archive: - break; - case ItemType.BiosSet: - break; - case ItemType.Disk: - break; - case ItemType.Release: - break; - case ItemType.Rom: // Currently this encapsulates rom, megarom, and sccpluscart - state += "\t\t" - // + "GoodMSX" - + "" - + (!ExcludeFields[(int)Field.Offset] && !String.IsNullOrWhiteSpace(((Rom)rom).Offset) ? "" + ((Rom)rom).Offset + "" : "") - // + "Normal" - + "" + (!ExcludeFields[(int)Field.SHA1] ? ((Rom)rom).SHA1 : "") + "" - // + "" - + "\n"; - break; - case ItemType.Sample: - break; - } + switch (rom.ItemType) + { + case ItemType.Archive: + break; + case ItemType.BiosSet: + break; + case ItemType.Disk: + break; + case ItemType.Release: + break; + case ItemType.Rom: // Currently this encapsulates rom, megarom, and sccpluscart + state += "\t\t" + // + "GoodMSX" + + "" + + (!ExcludeFields[(int)Field.Offset] && !String.IsNullOrWhiteSpace(((Rom)rom).Offset) ? "" + ((Rom)rom).Offset + "" : "") + // + "Normal" + + "" + (!ExcludeFields[(int)Field.SHA1] ? ((Rom)rom).SHA1 : "") + "" + // + "" + + "\n"; + break; + case ItemType.Sample: + break; + } - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out DAT footer using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteFooter(StreamWriter sw) - { - try - { - string footer = "\n\n"; + /// + /// Write out DAT footer using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteFooter(StreamWriter sw) + { + try + { + string footer = "\n\n"; - // Write the footer out - sw.Write(footer); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + // Write the footer out + sw.Write(footer); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } - } + return true; + } + } } diff --git a/SabreTools.Library/DatFiles/RomCenter.cs b/SabreTools.Library/DatFiles/RomCenter.cs index 107e2fd7..43f7afb5 100644 --- a/SabreTools.Library/DatFiles/RomCenter.cs +++ b/SabreTools.Library/DatFiles/RomCenter.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Text; using System.Web; - using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -20,359 +19,359 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { - /// - /// Represents parsing and writing of a RomCenter DAT - /// - internal class RomCenter : DatFile - { - /// - /// Constructor designed for casting a base DatFile - /// - /// Parent DatFile to copy from - public RomCenter(DatFile datFile) - : base(datFile, cloneHeader: false) - { - } + /// + /// Represents parsing and writing of a RomCenter DAT + /// + internal class RomCenter : DatFile + { + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + public RomCenter(DatFile datFile) + : base(datFile, cloneHeader: false) + { + } - /// - /// Parse a RomCenter DAT and return all found games and roms within - /// - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - public override void ParseFile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, + /// + /// Parse a RomCenter DAT and return all found games and roms within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Open a file reader - Encoding enc = Utilities.GetEncoding(filename); - StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc); + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Open a file reader + Encoding enc = Utilities.GetEncoding(filename); + StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc); - string blocktype = ""; - while (!sr.EndOfStream) - { - string line = sr.ReadLine(); + string blocktype = ""; + while (!sr.EndOfStream) + { + string line = sr.ReadLine(); - // If the line is the start of the credits section - if (line.ToLowerInvariant().StartsWith("[credits]")) - { - blocktype = "credits"; - } - // If the line is the start of the dat section - else if (line.ToLowerInvariant().StartsWith("[dat]")) - { - blocktype = "dat"; - } - // If the line is the start of the emulator section - else if (line.ToLowerInvariant().StartsWith("[emulator]")) - { - blocktype = "emulator"; - } - // If the line is the start of the game section - else if (line.ToLowerInvariant().StartsWith("[games]")) - { - blocktype = "games"; - } - // Otherwise, it's not a section and it's data, so get out all data - else - { - // If we have an author - if (line.ToLowerInvariant().StartsWith("author=")) - { - Author = (String.IsNullOrWhiteSpace(Author) ? line.Split('=')[1] : Author); - } - // If we have one of the three version tags - else if (line.ToLowerInvariant().StartsWith("version=")) - { - switch (blocktype) - { - case "credits": - Version = (String.IsNullOrWhiteSpace(Version) ? line.Split('=')[1] : Version); - break; - case "emulator": - Description = (String.IsNullOrWhiteSpace(Description) ? line.Split('=')[1] : Description); - break; - } - } - // If we have a URL - else if (line.ToLowerInvariant().StartsWith("url=")) - { - Url = (String.IsNullOrWhiteSpace(Url) ? line.Split('=')[1] : Url); - } - // If we have a comment - else if (line.ToLowerInvariant().StartsWith("comment=")) - { - Comment = (String.IsNullOrWhiteSpace(Comment) ? line.Split('=')[1] : Comment); - } - // If we have the split flag - else if (line.ToLowerInvariant().StartsWith("split=")) - { - if (Int32.TryParse(line.Split('=')[1], out int split)) - { - if (split == 1 && ForceMerging == ForceMerging.None) - { - ForceMerging = ForceMerging.Split; - } - } - } - // If we have the merge tag - else if (line.ToLowerInvariant().StartsWith("merge=")) - { - if (Int32.TryParse(line.Split('=')[1], out int merge)) - { - if (merge == 1 && ForceMerging == ForceMerging.None) - { - ForceMerging = ForceMerging.Full; - } - } - } - // If we have the refname tag - else if (line.ToLowerInvariant().StartsWith("refname=")) - { - Name = (String.IsNullOrWhiteSpace(Name) ? line.Split('=')[1] : Name); - } - // If we have a rom - else if (line.StartsWith("¬")) - { - // Some old RC DATs have this behavior - if (line.Contains("¬N¬O")) - { - line = line.Replace("¬N¬O", "") + "¬¬"; - } + // If the line is the start of the credits section + if (line.ToLowerInvariant().StartsWith("[credits]")) + { + blocktype = "credits"; + } + // If the line is the start of the dat section + else if (line.ToLowerInvariant().StartsWith("[dat]")) + { + blocktype = "dat"; + } + // If the line is the start of the emulator section + else if (line.ToLowerInvariant().StartsWith("[emulator]")) + { + blocktype = "emulator"; + } + // If the line is the start of the game section + else if (line.ToLowerInvariant().StartsWith("[games]")) + { + blocktype = "games"; + } + // Otherwise, it's not a section and it's data, so get out all data + else + { + // If we have an author + if (line.ToLowerInvariant().StartsWith("author=")) + { + Author = (String.IsNullOrWhiteSpace(Author) ? line.Split('=')[1] : Author); + } + // If we have one of the three version tags + else if (line.ToLowerInvariant().StartsWith("version=")) + { + switch (blocktype) + { + case "credits": + Version = (String.IsNullOrWhiteSpace(Version) ? line.Split('=')[1] : Version); + break; + case "emulator": + Description = (String.IsNullOrWhiteSpace(Description) ? line.Split('=')[1] : Description); + break; + } + } + // If we have a URL + else if (line.ToLowerInvariant().StartsWith("url=")) + { + Url = (String.IsNullOrWhiteSpace(Url) ? line.Split('=')[1] : Url); + } + // If we have a comment + else if (line.ToLowerInvariant().StartsWith("comment=")) + { + Comment = (String.IsNullOrWhiteSpace(Comment) ? line.Split('=')[1] : Comment); + } + // If we have the split flag + else if (line.ToLowerInvariant().StartsWith("split=")) + { + if (Int32.TryParse(line.Split('=')[1], out int split)) + { + if (split == 1 && ForceMerging == ForceMerging.None) + { + ForceMerging = ForceMerging.Split; + } + } + } + // If we have the merge tag + else if (line.ToLowerInvariant().StartsWith("merge=")) + { + if (Int32.TryParse(line.Split('=')[1], out int merge)) + { + if (merge == 1 && ForceMerging == ForceMerging.None) + { + ForceMerging = ForceMerging.Full; + } + } + } + // If we have the refname tag + else if (line.ToLowerInvariant().StartsWith("refname=")) + { + Name = (String.IsNullOrWhiteSpace(Name) ? line.Split('=')[1] : Name); + } + // If we have a rom + else if (line.StartsWith("¬")) + { + // Some old RC DATs have this behavior + if (line.Contains("¬N¬O")) + { + line = line.Replace("¬N¬O", "") + "¬¬"; + } - /* - The rominfo order is as follows: - 1 - parent name - 2 - parent description - 3 - game name - 4 - game description - 5 - rom name - 6 - rom crc - 7 - rom size - 8 - romof name - 9 - merge name - */ - string[] rominfo = line.Split('¬'); + /* + The rominfo order is as follows: + 1 - parent name + 2 - parent description + 3 - game name + 4 - game description + 5 - rom name + 6 - rom crc + 7 - rom size + 8 - romof name + 9 - merge name + */ + string[] rominfo = line.Split('¬'); - // Try getting the size separately - if (!Int64.TryParse(rominfo[7], out long size)) - { - size = 0; - } + // Try getting the size separately + if (!Int64.TryParse(rominfo[7], out long size)) + { + size = 0; + } - Rom rom = new Rom - { - Name = rominfo[5], - Size = size, - CRC = Utilities.CleanHashData(rominfo[6], Constants.CRCLength), - ItemStatus = ItemStatus.None, + Rom rom = new Rom + { + Name = rominfo[5], + Size = size, + CRC = Utilities.CleanHashData(rominfo[6], Constants.CRCLength), + ItemStatus = ItemStatus.None, - MachineName = rominfo[3], - MachineDescription = rominfo[4], - CloneOf = rominfo[1], - RomOf = rominfo[8], + MachineName = rominfo[3], + MachineDescription = rominfo[4], + CloneOf = rominfo[1], + RomOf = rominfo[8], - SystemID = sysid, - SourceID = srcid, - }; + SystemID = sysid, + SourceID = srcid, + }; - // Now process and add the rom - ParseAddHelper(rom, clean, remUnicode); - } - } - } + // Now process and add the rom + ParseAddHelper(rom, clean, remUnicode); + } + } + } - sr.Dispose(); - } + sr.Dispose(); + } - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the DAT was written correctly, false otherwise - public override bool WriteToFile(string outfile, bool ignoreblanks = false) - { - try - { - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = Utilities.TryCreate(outfile); + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public override bool WriteToFile(string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); - // If we get back null for some reason, just log and return - if (fs == null) - { - Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); - return false; - } + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); - // Write out the header - WriteHeader(sw); + // Write out the header + WriteHeader(sw); - // Write out each of the machines and roms - string lastgame = null; - List splitpath = new List(); + // Write out each of the machines and roms + string lastgame = null; + List splitpath = new List(); - // Get a properly sorted set of keys - List keys = Keys; - keys.Sort(new NaturalComparer()); + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); - foreach (string key in keys) - { - List roms = this[key]; + foreach (string key in keys) + { + List roms = this[key]; - // Resolve the names in the block - roms = DatItem.ResolveNames(roms); + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); - for (int index = 0; index < roms.Count; index++) - { - DatItem rom = roms[index]; + for (int index = 0; index < roms.Count; index++) + { + DatItem rom = roms[index]; - // There are apparently times when a null rom can skip by, skip them - if (rom.Name == null || rom.MachineName == null) - { - Globals.Logger.Warning("Null rom found!"); - continue; - } + // There are apparently times when a null rom can skip by, skip them + if (rom.Name == null || rom.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (rom.ItemType == ItemType.Rom - && ((Rom)rom).Size == -1 - && ((Rom)rom).CRC == "null") - { - Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.ItemType == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); - rom.Name = (rom.Name == "null" ? "-" : rom.Name); - ((Rom)rom).Size = Constants.SizeZero; - ((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null; - ((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null; - ((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null; - ((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null; - ((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null; - ((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null; - } + rom.Name = (rom.Name == "null" ? "-" : rom.Name); + ((Rom)rom).Size = Constants.SizeZero; + ((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null; + ((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null; + ((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null; + ((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null; + ((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null; + ((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null; + } - // Now, output the rom data - WriteDatItem(sw, rom, ignoreblanks); + // Now, output the rom data + WriteDatItem(sw, rom, ignoreblanks); - // Set the new data to compare against - lastgame = rom.MachineName; - } - } + // Set the new data to compare against + lastgame = rom.MachineName; + } + } - Globals.Logger.Verbose("File written!" + Environment.NewLine); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteHeader(StreamWriter sw) - { - try - { - string header = header = "[CREDITS]\n" + - "author=" + Author + "\n" + - "version=" + Version + "\n" + - "comment=" + Comment + "\n" + - "[DAT]\n" + - "version=2.50\n" + - "split=" + (ForceMerging == ForceMerging.Split ? "1" : "0") + "\n" + - "merge=" + (ForceMerging == ForceMerging.Full || ForceMerging == ForceMerging.Merged ? "1" : "0") + "\n" + - "[EMULATOR]\n" + - "refname=" + Name + "\n" + - "version=" + Description + "\n" + - "[GAMES]\n"; + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteHeader(StreamWriter sw) + { + try + { + string header = header = "[CREDITS]\n" + + "author=" + Author + "\n" + + "version=" + Version + "\n" + + "comment=" + Comment + "\n" + + "[DAT]\n" + + "version=2.50\n" + + "split=" + (ForceMerging == ForceMerging.Split ? "1" : "0") + "\n" + + "merge=" + (ForceMerging == ForceMerging.Full || ForceMerging == ForceMerging.Merged ? "1" : "0") + "\n" + + "[EMULATOR]\n" + + "refname=" + Name + "\n" + + "version=" + Description + "\n" + + "[GAMES]\n"; - // Write the header out - sw.Write(header); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + // Write the header out + sw.Write(header); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the data was written, false on error - private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) - { - // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip - if (ignoreblanks - && (rom.ItemType == ItemType.Rom - && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) - { - return true; - } + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the data was written, false on error + private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.ItemType == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } - try - { - string state = ""; + try + { + string state = ""; - // Pre-process the item name - ProcessItemName(rom, true); + // Pre-process the item name + ProcessItemName(rom, true); - if (rom.ItemType == ItemType.Rom) - { - state += "¬" + (!ExcludeFields[(int)Field.CloneOf] && String.IsNullOrWhiteSpace(rom.CloneOf) ? HttpUtility.HtmlEncode(rom.CloneOf) : "") + - "¬" + (!ExcludeFields[(int)Field.CloneOf] && String.IsNullOrWhiteSpace(rom.CloneOf) ? HttpUtility.HtmlEncode(rom.CloneOf) : "") + - "¬" + (!ExcludeFields[(int)Field.MachineName] ? HttpUtility.HtmlEncode(rom.MachineName) : "") + - "¬" + (!ExcludeFields[(int)Field.Description] ? HttpUtility.HtmlEncode((String.IsNullOrWhiteSpace(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription)) : "") + - "¬" + (!ExcludeFields[(int)Field.Name] ? HttpUtility.HtmlEncode(rom.Name) : "") + - "¬" + (!ExcludeFields[(int)Field.CRC] ? ((Rom)rom).CRC.ToLowerInvariant() : "") + - "¬" + (!ExcludeFields[(int)Field.Size] && ((Rom)rom).Size != -1 ? ((Rom)rom).Size.ToString() : "") + "¬¬¬\n"; - } - else if (rom.ItemType == ItemType.Disk) - { - state += "¬" + (!ExcludeFields[(int)Field.CloneOf] && String.IsNullOrWhiteSpace(rom.CloneOf) ? HttpUtility.HtmlEncode(rom.CloneOf) : "") + - "¬" + (!ExcludeFields[(int)Field.CloneOf] && String.IsNullOrWhiteSpace(rom.CloneOf) ? HttpUtility.HtmlEncode(rom.CloneOf) : "") + - "¬" + (!ExcludeFields[(int)Field.MachineName] ? HttpUtility.HtmlEncode(rom.MachineName) : "") + - "¬" + (!ExcludeFields[(int)Field.Description] ? HttpUtility.HtmlEncode((String.IsNullOrWhiteSpace(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription)) : "") + - "¬" + (!ExcludeFields[(int)Field.Name] ? HttpUtility.HtmlEncode(rom.Name) : "") + - "¬¬¬¬¬\n"; - } + if (rom.ItemType == ItemType.Rom) + { + state += "¬" + (!ExcludeFields[(int)Field.CloneOf] && String.IsNullOrWhiteSpace(rom.CloneOf) ? HttpUtility.HtmlEncode(rom.CloneOf) : "") + + "¬" + (!ExcludeFields[(int)Field.CloneOf] && String.IsNullOrWhiteSpace(rom.CloneOf) ? HttpUtility.HtmlEncode(rom.CloneOf) : "") + + "¬" + (!ExcludeFields[(int)Field.MachineName] ? HttpUtility.HtmlEncode(rom.MachineName) : "") + + "¬" + (!ExcludeFields[(int)Field.Description] ? HttpUtility.HtmlEncode((String.IsNullOrWhiteSpace(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription)) : "") + + "¬" + (!ExcludeFields[(int)Field.Name] ? HttpUtility.HtmlEncode(rom.Name) : "") + + "¬" + (!ExcludeFields[(int)Field.CRC] ? ((Rom)rom).CRC.ToLowerInvariant() : "") + + "¬" + (!ExcludeFields[(int)Field.Size] && ((Rom)rom).Size != -1 ? ((Rom)rom).Size.ToString() : "") + "¬¬¬\n"; + } + else if (rom.ItemType == ItemType.Disk) + { + state += "¬" + (!ExcludeFields[(int)Field.CloneOf] && String.IsNullOrWhiteSpace(rom.CloneOf) ? HttpUtility.HtmlEncode(rom.CloneOf) : "") + + "¬" + (!ExcludeFields[(int)Field.CloneOf] && String.IsNullOrWhiteSpace(rom.CloneOf) ? HttpUtility.HtmlEncode(rom.CloneOf) : "") + + "¬" + (!ExcludeFields[(int)Field.MachineName] ? HttpUtility.HtmlEncode(rom.MachineName) : "") + + "¬" + (!ExcludeFields[(int)Field.Description] ? HttpUtility.HtmlEncode((String.IsNullOrWhiteSpace(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription)) : "") + + "¬" + (!ExcludeFields[(int)Field.Name] ? HttpUtility.HtmlEncode(rom.Name) : "") + + "¬¬¬¬¬\n"; + } - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } - } + return true; + } + } } diff --git a/SabreTools.Library/DatFiles/SabreDat.cs b/SabreTools.Library/DatFiles/SabreDat.cs index 75f4189b..41f0f669 100644 --- a/SabreTools.Library/DatFiles/SabreDat.cs +++ b/SabreTools.Library/DatFiles/SabreDat.cs @@ -2,10 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using System.Text.RegularExpressions; using System.Web; using System.Xml; - using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -22,918 +20,918 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { - /// - /// Represents parsing and writing of an SabreDat XML DAT - /// - /// TODO: Verify that all write for this DatFile type is correct - internal class SabreDat : DatFile - { - /// - /// Constructor designed for casting a base DatFile - /// - /// Parent DatFile to copy from - public SabreDat(DatFile datFile) - : base(datFile, cloneHeader: false) - { - } - - /// - /// Parse an SabreDat XML DAT and return all found directories and files within - /// - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - public override void ParseFile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Prepare all internal variables - bool empty = true; - string key = ""; - List parent = new List(); - - Encoding enc = Utilities.GetEncoding(filename); - XmlReader xtr = Utilities.GetXmlTextReader(filename); - - // If we got a null reader, just return - if (xtr == null) - { - return; - } - - // Otherwise, read the file to the end - try - { - xtr.MoveToContent(); - while (!xtr.EOF) - { - // If we're ending a folder or game, take care of possibly empty games and removing from the parent - if (xtr.NodeType == XmlNodeType.EndElement && (xtr.Name == "directory" || xtr.Name == "dir")) - { - // If we didn't find any items in the folder, make sure to add the blank rom - if (empty) - { - string tempgame = String.Join("\\", parent); - Rom rom = new Rom("null", tempgame, omitFromScan: Hash.DeepHashes); // TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually - - // Now process and add the rom - key = ParseAddHelper(rom, clean, remUnicode); - } - - // Regardless, end the current folder - int parentcount = parent.Count; - if (parentcount == 0) - { - Globals.Logger.Verbose("Empty parent '{0}' found in '{1}'", String.Join("\\", parent), filename); - empty = true; - } - - // If we have an end folder element, remove one item from the parent, if possible - if (parentcount > 0) - { - parent.RemoveAt(parent.Count - 1); - if (keep && parentcount > 1) - { - Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); - } - } - } - - // We only want elements - if (xtr.NodeType != XmlNodeType.Element) - { - xtr.Read(); - continue; - } - - switch (xtr.Name) - { - // We want to process the entire subtree of the header - case "header": - ReadHeader(xtr.ReadSubtree(), keep); - - // Skip the header node now that we've processed it - xtr.Skip(); - break; - case "dir": - case "directory": - empty = ReadDirectory(xtr.ReadSubtree(), parent, filename, sysid, srcid, keep, clean, remUnicode); - - // Skip the directory node now that we've processed it - xtr.Read(); - break; - default: - xtr.Read(); - break; - } - } - } - catch (Exception ex) - { - Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); - - // For XML errors, just skip the affected node - xtr?.Read(); - } - - xtr.Dispose(); - } - - /// - /// Read header information - /// - /// XmlReader to use to parse the header - /// True if full pathnames are to be kept, false otherwise (default) - private void ReadHeader(XmlReader reader, bool keep) - { - bool superdat = false; - - // If there's no subtree to the header, skip it - if (reader == null) - { - return; - } - - // Otherwise, read what we can from the header - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element || reader.Name == "header") - { - reader.Read(); - continue; - } - - // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) - string content = ""; - switch (reader.Name) - { - case "name": - content = reader.ReadElementContentAsString(); ; - Name = (String.IsNullOrWhiteSpace(Name) ? content : Name); - superdat = superdat || content.Contains(" - SuperDAT"); - if (keep && superdat) - { - Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); - } - break; - case "description": - content = reader.ReadElementContentAsString(); - Description = (String.IsNullOrWhiteSpace(Description) ? content : Description); - break; - case "rootdir": - content = reader.ReadElementContentAsString(); - RootDir = (String.IsNullOrWhiteSpace(RootDir) ? content : RootDir); - break; - case "category": - content = reader.ReadElementContentAsString(); - Category = (String.IsNullOrWhiteSpace(Category) ? content : Category); - break; - case "version": - content = reader.ReadElementContentAsString(); - Version = (String.IsNullOrWhiteSpace(Version) ? content : Version); - break; - case "date": - content = reader.ReadElementContentAsString(); - Date = (String.IsNullOrWhiteSpace(Date) ? content.Replace(".", "/") : Date); - break; - case "author": - content = reader.ReadElementContentAsString(); - Author = (String.IsNullOrWhiteSpace(Author) ? content : Author); - Email = (String.IsNullOrWhiteSpace(Email) ? reader.GetAttribute("email") : Email); - Homepage = (String.IsNullOrWhiteSpace(Homepage) ? reader.GetAttribute("homepage") : Homepage); - Url = (String.IsNullOrWhiteSpace(Url) ? reader.GetAttribute("url") : Url); - break; - case "comment": - content = reader.ReadElementContentAsString(); - Comment = (String.IsNullOrWhiteSpace(Comment) ? content : Comment); - break; - case "flags": - ReadFlags(reader.ReadSubtree(), superdat); - - // Skip the flags node now that we've processed it - reader.Skip(); - break; - default: - reader.Read(); - break; - } - } - } - - /// - /// Read directory information - /// - /// XmlReader 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 full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private bool ReadDirectory(XmlReader reader, - List parent, - - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Prepare all internal variables - XmlReader flagreader; - bool empty = true; - string key = "", date = ""; - long size = -1; - ItemStatus its = ItemStatus.None; - - // If there's no subtree to the header, skip it - if (reader == null) - { - return empty; - } - - string foldername = (reader.GetAttribute("name") ?? ""); - if (!String.IsNullOrWhiteSpace(foldername)) - { - parent.Add(foldername); - } - - // Otherwise, read what we can from the directory - while (!reader.EOF) - { - // If we're ending a folder or game, take care of possibly empty games and removing from the parent - if (reader.NodeType == XmlNodeType.EndElement && (reader.Name == "directory" || reader.Name == "dir")) - { - // If we didn't find any items in the folder, make sure to add the blank rom - if (empty) - { - string tempgame = String.Join("\\", parent); - Rom rom = new Rom("null", tempgame, omitFromScan: Hash.DeepHashes); // TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually - - // Now process and add the rom - key = ParseAddHelper(rom, clean, remUnicode); - } - - // Regardless, end the current folder - int parentcount = parent.Count; - if (parentcount == 0) - { - Globals.Logger.Verbose("Empty parent '{0}' found in '{1}'", String.Join("\\", parent), filename); - empty = true; - } - - // If we have an end folder element, remove one item from the parent, if possible - if (parentcount > 0) - { - parent.RemoveAt(parent.Count - 1); - if (keep && parentcount > 1) - { - Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); - } - } - } - - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get all directory items - string content = ""; - switch (reader.Name) - { - // Directories can contain directories - case "dir": - case "directory": - ReadDirectory(reader.ReadSubtree(), parent, filename, sysid, srcid, keep, clean, remUnicode); - - // Skip the directory node now that we've processed it - reader.Read(); - break; - case "file": - empty = false; - - // If the rom is itemStatus, flag it - its = ItemStatus.None; - flagreader = reader.ReadSubtree(); - - // If the subtree is empty, skip it - if (flagreader == null) - { - reader.Skip(); - continue; - } - - while (!flagreader.EOF) - { - // We only want elements - if (flagreader.NodeType != XmlNodeType.Element || flagreader.Name == "flags") - { - flagreader.Read(); - continue; - } - - switch (flagreader.Name) - { - case "flag": - if (flagreader.GetAttribute("name") != null && flagreader.GetAttribute("value") != null) - { - content = flagreader.GetAttribute("value"); - its = Utilities.GetItemStatus(flagreader.GetAttribute("name")); - } - break; - } - - flagreader.Read(); - } - - // If the rom has a Date attached, read it in and then sanitize it - date = Utilities.GetDate(reader.GetAttribute("date")); - - // Take care of hex-sized files - size = Utilities.GetSize(reader.GetAttribute("size")); - - Machine dir = new Machine(); - - // Get the name of the game from the parent - dir.Name = String.Join("\\", parent); - dir.Description = dir.Name; - - DatItem datItem; - switch (reader.GetAttribute("type").ToLowerInvariant()) - { - case "archive": - datItem = new Archive - { - Name = reader.GetAttribute("name"), - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - break; - case "biosset": - datItem = new BiosSet - { - Name = reader.GetAttribute("name"), - Description = reader.GetAttribute("description"), - Default = Utilities.GetYesNo(reader.GetAttribute("default")), - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - break; - case "disk": - datItem = new Disk - { - Name = reader.GetAttribute("name"), - MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), - SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), - SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), - SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), - SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), - ItemStatus = its, - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - break; - case "release": - datItem = new Release - { - Name = reader.GetAttribute("name"), - Region = reader.GetAttribute("region"), - Language = reader.GetAttribute("language"), - Date = reader.GetAttribute("date"), - Default = Utilities.GetYesNo(reader.GetAttribute("default")), - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - break; - case "rom": - datItem = new Rom - { - Name = reader.GetAttribute("name"), - Size = size, - CRC = Utilities.CleanHashData(reader.GetAttribute("crc"), Constants.CRCLength), - MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), - SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), - SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), - SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), - SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), - ItemStatus = its, - Date = date, - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - break; - case "sample": - datItem = new Sample - { - Name = reader.GetAttribute("name"), - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - break; - default: - // By default, create a new Blank, just in case - datItem = new Blank(); - break; - } - - datItem?.CopyMachineInformation(dir); - - // Now process and add the rom - key = ParseAddHelper(datItem, clean, remUnicode); - - reader.Read(); - break; - } - } - - return empty; - } - - /// - /// Read flags information - /// - /// XmlReader to use to parse the header - /// True if superdat has already been set externally, false otherwise - private void ReadFlags(XmlReader reader, bool superdat) - { - // Prepare all internal variables - string content = ""; - - // If we somehow have a null flag section, skip it - if (reader == null) - { - return; - } - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element || reader.Name == "flags") - { - reader.Read(); - continue; - } - - switch (reader.Name) - { - case "flag": - if (reader.GetAttribute("name") != null && reader.GetAttribute("value") != null) - { - content = reader.GetAttribute("value"); - switch (reader.GetAttribute("name").ToLowerInvariant()) - { - case "type": - Type = (String.IsNullOrWhiteSpace(Type) ? content : Type); - superdat = superdat || content.Contains("SuperDAT"); - break; - case "forcemerging": - if (ForceMerging == ForceMerging.None) - { - ForceMerging = Utilities.GetForceMerging(content); - } - break; - case "forcenodump": - if (ForceNodump == ForceNodump.None) - { - ForceNodump = Utilities.GetForceNodump(content); - } - break; - case "forcepacking": - if (ForcePacking == ForcePacking.None) - { - ForcePacking = Utilities.GetForcePacking(content); - } - break; - } - } - reader.Read(); - break; - default: - reader.Read(); - break; - } - } - } - - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the DAT was written correctly, false otherwise - /// TODO: Fix writing out files that have a path in the name - public override bool WriteToFile(string outfile, bool ignoreblanks = false) - { - try - { - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = Utilities.TryCreate(outfile); - - // If we get back null for some reason, just log and return - if (fs == null) - { - Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); - return false; - } - - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); - - // Write out the header - WriteHeader(sw); - - // Write out each of the machines and roms - int depth = 2, last = -1; - string lastgame = null; - List splitpath = new List(); - - // Get a properly sorted set of keys - List keys = Keys; - keys.Sort(new NaturalComparer()); - - foreach (string key in keys) - { - List roms = this[key]; - - // Resolve the names in the block - roms = DatItem.ResolveNames(roms); - - for (int index = 0; index < roms.Count; index++) - { - DatItem rom = roms[index]; - - // There are apparently times when a null rom can skip by, skip them - if (rom.Name == null || rom.MachineName == null) - { - Globals.Logger.Warning("Null rom found!"); - continue; - } - - List newsplit = rom.MachineName.Split('\\').ToList(); - - // If we have a different game and we're not at the start of the list, output the end of last item - if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - depth = WriteEndGame(sw, splitpath, newsplit, depth, out last); - } - - // If we have a new game, output the beginning of the new item - if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - depth = WriteStartGame(sw, rom, newsplit, lastgame, depth, last); - } - - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (rom.ItemType == ItemType.Rom - && ((Rom)rom).Size == -1 - && ((Rom)rom).CRC == "null") - { - Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); - - splitpath = newsplit; - lastgame = rom.MachineName; - continue; - } - - // Now, output the rom data - WriteDatItem(sw, rom, depth, ignoreblanks); - - // Set the new data to compare against - splitpath = newsplit; - lastgame = rom.MachineName; - } - } - - // Write the file footer out - WriteFooter(sw, depth); - - Globals.Logger.Verbose("File written!" + Environment.NewLine); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteHeader(StreamWriter sw) - { - try - { - string header = "\n" + - "\n\n" + - "\n" + - "\t
\n" + - "\t\t" + HttpUtility.HtmlEncode(Name) + "\n" + - "\t\t" + HttpUtility.HtmlEncode(Description) + "\n" + - (!String.IsNullOrWhiteSpace(RootDir) ? "\t\t" + HttpUtility.HtmlEncode(RootDir) + "\n" : "") + - (!String.IsNullOrWhiteSpace(Category) ? "\t\t" + HttpUtility.HtmlEncode(Category) + "\n" : "") + - "\t\t" + HttpUtility.HtmlEncode(Version) + "\n" + - (!String.IsNullOrWhiteSpace(Date) ? "\t\t" + HttpUtility.HtmlEncode(Date) + "\n" : "") + - "\t\t" + HttpUtility.HtmlEncode(Author) + "\n" + - (!String.IsNullOrWhiteSpace(Comment) ? "\t\t" + HttpUtility.HtmlEncode(Comment) + "\n" : "") + - (!String.IsNullOrWhiteSpace(Type) || ForcePacking != ForcePacking.None || ForceMerging != ForceMerging.None || ForceNodump != ForceNodump.None ? - "\t\t\n" + - (!String.IsNullOrWhiteSpace(Type) ? "\t\t\t\n" : "") + - (ForcePacking == ForcePacking.Unzip ? "\t\t\t\n" : "") + - (ForcePacking == ForcePacking.Zip ? "\t\t\t\n" : "") + - (ForceMerging == ForceMerging.Full ? "\t\t\t\n" : "") + - (ForceMerging == ForceMerging.Split ? "\t\t\t\n" : "") + - (ForceMerging == ForceMerging.Merged ? "\t\t\t\n" : "") + - (ForceMerging == ForceMerging.NonMerged ? "\t\t\t\n" : "") + - (ForceNodump == ForceNodump.Ignore ? "\t\t\t\n" : "") + - (ForceNodump == ForceNodump.Obsolete ? "\t\t\t\n" : "") + - (ForceNodump == ForceNodump.Required ? "\t\t\t\n" : "") + - "\t\t\n" - : "") + - "\t
\n" + - "\t\n"; - - // Write the header out - sw.Write(header); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// Split path representing the parent game (SabreDAT only) - /// The name of the last game to be output - /// Current depth to output file at (SabreDAT only) - /// Last known depth to cycle back from (SabreDAT only) - /// The new depth of the tag - private int WriteStartGame(StreamWriter sw, DatItem rom, List newsplit, string lastgame, int depth, int last) - { - try - { - // No game should start with a path separator - if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) - { - rom.MachineName = rom.MachineName.Substring(1); - } - - string state = ""; - for (int i = (last == -1 ? 0 : last); i < newsplit.Count; i++) - { - for (int j = 0; j < depth - last + i - (lastgame == null ? 1 : 0); j++) - { - state += "\t"; - } - state += "\n"; - } - depth = depth - (last == -1 ? 0 : last) + newsplit.Count; - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return depth; - } - - return depth; - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// StreamWriter to output to - /// Split path representing last kwown parent game (SabreDAT only) - /// Split path representing the parent game (SabreDAT only) - /// Current depth to output file at (SabreDAT only) - /// Last known depth to cycle back from (SabreDAT only) - /// The new depth of the tag - private int WriteEndGame(StreamWriter sw, List splitpath, List newsplit, int depth, out int last) - { - last = 0; - - try - { - string state = ""; - if (splitpath != null) - { - for (int i = 0; i < newsplit.Count && i < splitpath.Count; i++) - { - // Always keep track of the last seen item - last = i; - - // If we find a difference, break - if (newsplit[i] != splitpath[i]) - { - break; - } - } - - // Now that we have the last known position, take down all open folders - for (int i = depth - 1; i > last + 1; i--) - { - // Print out the number of tabs and the end folder - for (int j = 0; j < i; j++) - { - state += "\t"; - } - state += "\n"; - } - - // Reset the current depth - depth = 2 + last; - } - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return depth; - } - - return depth; - } - - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// Current depth to output file at (SabreDAT only) - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the data was written, false on error - private bool WriteDatItem(StreamWriter sw, DatItem rom, int depth, bool ignoreblanks = false) - { - // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip - if (ignoreblanks - && (rom.ItemType == ItemType.Rom - && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) - { - return true; - } - - try - { - string state = "", prefix = ""; - - // Pre-process the item name - ProcessItemName(rom, true); - - for (int i = 0; i < depth; i++) - { - prefix += "\t"; - } - state += prefix; - - switch (rom.ItemType) - { - case ItemType.Archive: - state += "\n"; - break; - case ItemType.BiosSet: - state += "\n"; - break; - case ItemType.Disk: - state += "\n" + prefix + "\t\n" + - prefix + "\t\t\n" + - prefix + "\t\n" + - prefix + "\n" : "/>\n"); - break; - case ItemType.Release: - state += "\n"; - break; - case ItemType.Rom: - state += "\n" + prefix + "\t\n" + - prefix + "\t\t\n" + - prefix + "\t\n" + - prefix + "\n" : "/>\n"); - break; - case ItemType.Sample: - state += "\n"; - break; - } - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DAT footer using the supplied StreamWriter - /// - /// StreamWriter to output to - /// Current depth to output file at (SabreDAT only) - /// True if the data was written, false on error - private bool WriteFooter(StreamWriter sw, int depth) - { - try - { - string footer = ""; - for (int i = depth - 1; i >= 2; i--) - { - // Print out the number of tabs and the end folder - for (int j = 0; j < i; j++) - { - footer += "\t"; - } - footer += "\n"; - } - footer += "\t\n
\n"; - - // Write the footer out - sw.Write(footer); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - } + /// + /// Represents parsing and writing of an SabreDat XML DAT + /// + /// TODO: Verify that all write for this DatFile type is correct + internal class SabreDat : DatFile + { + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + public SabreDat(DatFile datFile) + : base(datFile, cloneHeader: false) + { + } + + /// + /// Parse an SabreDat XML DAT and return all found directories and files within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Prepare all internal variables + bool empty = true; + string key = ""; + List parent = new List(); + + Encoding enc = Utilities.GetEncoding(filename); + XmlReader xtr = Utilities.GetXmlTextReader(filename); + + // If we got a null reader, just return + if (xtr == null) + { + return; + } + + // Otherwise, read the file to the end + try + { + xtr.MoveToContent(); + while (!xtr.EOF) + { + // If we're ending a folder or game, take care of possibly empty games and removing from the parent + if (xtr.NodeType == XmlNodeType.EndElement && (xtr.Name == "directory" || xtr.Name == "dir")) + { + // If we didn't find any items in the folder, make sure to add the blank rom + if (empty) + { + string tempgame = String.Join("\\", parent); + Rom rom = new Rom("null", tempgame, omitFromScan: Hash.DeepHashes); // TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually + + // Now process and add the rom + key = ParseAddHelper(rom, clean, remUnicode); + } + + // Regardless, end the current folder + int parentcount = parent.Count; + if (parentcount == 0) + { + Globals.Logger.Verbose("Empty parent '{0}' found in '{1}'", String.Join("\\", parent), filename); + empty = true; + } + + // If we have an end folder element, remove one item from the parent, if possible + if (parentcount > 0) + { + parent.RemoveAt(parent.Count - 1); + if (keep && parentcount > 1) + { + Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); + } + } + } + + // We only want elements + if (xtr.NodeType != XmlNodeType.Element) + { + xtr.Read(); + continue; + } + + switch (xtr.Name) + { + // We want to process the entire subtree of the header + case "header": + ReadHeader(xtr.ReadSubtree(), keep); + + // Skip the header node now that we've processed it + xtr.Skip(); + break; + case "dir": + case "directory": + empty = ReadDirectory(xtr.ReadSubtree(), parent, filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the directory node now that we've processed it + xtr.Read(); + break; + default: + xtr.Read(); + break; + } + } + } + catch (Exception ex) + { + Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); + + // For XML errors, just skip the affected node + xtr?.Read(); + } + + xtr.Dispose(); + } + + /// + /// Read header information + /// + /// XmlReader to use to parse the header + /// True if full pathnames are to be kept, false otherwise (default) + private void ReadHeader(XmlReader reader, bool keep) + { + bool superdat = false; + + // If there's no subtree to the header, skip it + if (reader == null) + { + return; + } + + // Otherwise, read what we can from the header + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element || reader.Name == "header") + { + reader.Read(); + continue; + } + + // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) + string content = ""; + switch (reader.Name) + { + case "name": + content = reader.ReadElementContentAsString(); ; + Name = (String.IsNullOrWhiteSpace(Name) ? content : Name); + superdat = superdat || content.Contains(" - SuperDAT"); + if (keep && superdat) + { + Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); + } + break; + case "description": + content = reader.ReadElementContentAsString(); + Description = (String.IsNullOrWhiteSpace(Description) ? content : Description); + break; + case "rootdir": + content = reader.ReadElementContentAsString(); + RootDir = (String.IsNullOrWhiteSpace(RootDir) ? content : RootDir); + break; + case "category": + content = reader.ReadElementContentAsString(); + Category = (String.IsNullOrWhiteSpace(Category) ? content : Category); + break; + case "version": + content = reader.ReadElementContentAsString(); + Version = (String.IsNullOrWhiteSpace(Version) ? content : Version); + break; + case "date": + content = reader.ReadElementContentAsString(); + Date = (String.IsNullOrWhiteSpace(Date) ? content.Replace(".", "/") : Date); + break; + case "author": + content = reader.ReadElementContentAsString(); + Author = (String.IsNullOrWhiteSpace(Author) ? content : Author); + Email = (String.IsNullOrWhiteSpace(Email) ? reader.GetAttribute("email") : Email); + Homepage = (String.IsNullOrWhiteSpace(Homepage) ? reader.GetAttribute("homepage") : Homepage); + Url = (String.IsNullOrWhiteSpace(Url) ? reader.GetAttribute("url") : Url); + break; + case "comment": + content = reader.ReadElementContentAsString(); + Comment = (String.IsNullOrWhiteSpace(Comment) ? content : Comment); + break; + case "flags": + ReadFlags(reader.ReadSubtree(), superdat); + + // Skip the flags node now that we've processed it + reader.Skip(); + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Read directory information + /// + /// XmlReader 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 full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private bool ReadDirectory(XmlReader reader, + List parent, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Prepare all internal variables + XmlReader flagreader; + bool empty = true; + string key = "", date = ""; + long size = -1; + ItemStatus its = ItemStatus.None; + + // If there's no subtree to the header, skip it + if (reader == null) + { + return empty; + } + + string foldername = (reader.GetAttribute("name") ?? ""); + if (!String.IsNullOrWhiteSpace(foldername)) + { + parent.Add(foldername); + } + + // Otherwise, read what we can from the directory + while (!reader.EOF) + { + // If we're ending a folder or game, take care of possibly empty games and removing from the parent + if (reader.NodeType == XmlNodeType.EndElement && (reader.Name == "directory" || reader.Name == "dir")) + { + // If we didn't find any items in the folder, make sure to add the blank rom + if (empty) + { + string tempgame = String.Join("\\", parent); + Rom rom = new Rom("null", tempgame, omitFromScan: Hash.DeepHashes); // TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually + + // Now process and add the rom + key = ParseAddHelper(rom, clean, remUnicode); + } + + // Regardless, end the current folder + int parentcount = parent.Count; + if (parentcount == 0) + { + Globals.Logger.Verbose("Empty parent '{0}' found in '{1}'", String.Join("\\", parent), filename); + empty = true; + } + + // If we have an end folder element, remove one item from the parent, if possible + if (parentcount > 0) + { + parent.RemoveAt(parent.Count - 1); + if (keep && parentcount > 1) + { + Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); + } + } + } + + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get all directory items + string content = ""; + switch (reader.Name) + { + // Directories can contain directories + case "dir": + case "directory": + ReadDirectory(reader.ReadSubtree(), parent, filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the directory node now that we've processed it + reader.Read(); + break; + case "file": + empty = false; + + // If the rom is itemStatus, flag it + its = ItemStatus.None; + flagreader = reader.ReadSubtree(); + + // If the subtree is empty, skip it + if (flagreader == null) + { + reader.Skip(); + continue; + } + + while (!flagreader.EOF) + { + // We only want elements + if (flagreader.NodeType != XmlNodeType.Element || flagreader.Name == "flags") + { + flagreader.Read(); + continue; + } + + switch (flagreader.Name) + { + case "flag": + if (flagreader.GetAttribute("name") != null && flagreader.GetAttribute("value") != null) + { + content = flagreader.GetAttribute("value"); + its = Utilities.GetItemStatus(flagreader.GetAttribute("name")); + } + break; + } + + flagreader.Read(); + } + + // If the rom has a Date attached, read it in and then sanitize it + date = Utilities.GetDate(reader.GetAttribute("date")); + + // Take care of hex-sized files + size = Utilities.GetSize(reader.GetAttribute("size")); + + Machine dir = new Machine(); + + // Get the name of the game from the parent + dir.Name = String.Join("\\", parent); + dir.Description = dir.Name; + + DatItem datItem; + switch (reader.GetAttribute("type").ToLowerInvariant()) + { + case "archive": + datItem = new Archive + { + Name = reader.GetAttribute("name"), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + case "biosset": + datItem = new BiosSet + { + Name = reader.GetAttribute("name"), + Description = reader.GetAttribute("description"), + Default = Utilities.GetYesNo(reader.GetAttribute("default")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + case "disk": + datItem = new Disk + { + Name = reader.GetAttribute("name"), + MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), + SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), + SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), + SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), + SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), + ItemStatus = its, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + case "release": + datItem = new Release + { + Name = reader.GetAttribute("name"), + Region = reader.GetAttribute("region"), + Language = reader.GetAttribute("language"), + Date = reader.GetAttribute("date"), + Default = Utilities.GetYesNo(reader.GetAttribute("default")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + case "rom": + datItem = new Rom + { + Name = reader.GetAttribute("name"), + Size = size, + CRC = Utilities.CleanHashData(reader.GetAttribute("crc"), Constants.CRCLength), + MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), + SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), + SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), + SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), + SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), + ItemStatus = its, + Date = date, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + case "sample": + datItem = new Sample + { + Name = reader.GetAttribute("name"), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + default: + // By default, create a new Blank, just in case + datItem = new Blank(); + break; + } + + datItem?.CopyMachineInformation(dir); + + // Now process and add the rom + key = ParseAddHelper(datItem, clean, remUnicode); + + reader.Read(); + break; + } + } + + return empty; + } + + /// + /// Read flags information + /// + /// XmlReader to use to parse the header + /// True if superdat has already been set externally, false otherwise + private void ReadFlags(XmlReader reader, bool superdat) + { + // Prepare all internal variables + string content = ""; + + // If we somehow have a null flag section, skip it + if (reader == null) + { + return; + } + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element || reader.Name == "flags") + { + reader.Read(); + continue; + } + + switch (reader.Name) + { + case "flag": + if (reader.GetAttribute("name") != null && reader.GetAttribute("value") != null) + { + content = reader.GetAttribute("value"); + switch (reader.GetAttribute("name").ToLowerInvariant()) + { + case "type": + Type = (String.IsNullOrWhiteSpace(Type) ? content : Type); + superdat = superdat || content.Contains("SuperDAT"); + break; + case "forcemerging": + if (ForceMerging == ForceMerging.None) + { + ForceMerging = Utilities.GetForceMerging(content); + } + break; + case "forcenodump": + if (ForceNodump == ForceNodump.None) + { + ForceNodump = Utilities.GetForceNodump(content); + } + break; + case "forcepacking": + if (ForcePacking == ForcePacking.None) + { + ForcePacking = Utilities.GetForcePacking(content); + } + break; + } + } + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + /// TODO: Fix writing out files that have a path in the name + public override bool WriteToFile(string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); + + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } + + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + + // Write out the header + WriteHeader(sw); + + // Write out each of the machines and roms + int depth = 2, last = -1; + string lastgame = null; + List splitpath = new List(); + + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = this[key]; + + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); + + for (int index = 0; index < roms.Count; index++) + { + DatItem rom = roms[index]; + + // There are apparently times when a null rom can skip by, skip them + if (rom.Name == null || rom.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } + + List newsplit = rom.MachineName.Split('\\').ToList(); + + // If we have a different game and we're not at the start of the list, output the end of last item + if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + depth = WriteEndGame(sw, splitpath, newsplit, depth, out last); + } + + // If we have a new game, output the beginning of the new item + if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + depth = WriteStartGame(sw, rom, newsplit, lastgame, depth, last); + } + + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.ItemType == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + + splitpath = newsplit; + lastgame = rom.MachineName; + continue; + } + + // Now, output the rom data + WriteDatItem(sw, rom, depth, ignoreblanks); + + // Set the new data to compare against + splitpath = newsplit; + lastgame = rom.MachineName; + } + } + + // Write the file footer out + WriteFooter(sw, depth); + + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteHeader(StreamWriter sw) + { + try + { + string header = "\n" + + "\n\n" + + "\n" + + "\t
\n" + + "\t\t" + HttpUtility.HtmlEncode(Name) + "\n" + + "\t\t" + HttpUtility.HtmlEncode(Description) + "\n" + + (!String.IsNullOrWhiteSpace(RootDir) ? "\t\t" + HttpUtility.HtmlEncode(RootDir) + "\n" : "") + + (!String.IsNullOrWhiteSpace(Category) ? "\t\t" + HttpUtility.HtmlEncode(Category) + "\n" : "") + + "\t\t" + HttpUtility.HtmlEncode(Version) + "\n" + + (!String.IsNullOrWhiteSpace(Date) ? "\t\t" + HttpUtility.HtmlEncode(Date) + "\n" : "") + + "\t\t" + HttpUtility.HtmlEncode(Author) + "\n" + + (!String.IsNullOrWhiteSpace(Comment) ? "\t\t" + HttpUtility.HtmlEncode(Comment) + "\n" : "") + + (!String.IsNullOrWhiteSpace(Type) || ForcePacking != ForcePacking.None || ForceMerging != ForceMerging.None || ForceNodump != ForceNodump.None ? + "\t\t\n" + + (!String.IsNullOrWhiteSpace(Type) ? "\t\t\t\n" : "") + + (ForcePacking == ForcePacking.Unzip ? "\t\t\t\n" : "") + + (ForcePacking == ForcePacking.Zip ? "\t\t\t\n" : "") + + (ForceMerging == ForceMerging.Full ? "\t\t\t\n" : "") + + (ForceMerging == ForceMerging.Split ? "\t\t\t\n" : "") + + (ForceMerging == ForceMerging.Merged ? "\t\t\t\n" : "") + + (ForceMerging == ForceMerging.NonMerged ? "\t\t\t\n" : "") + + (ForceNodump == ForceNodump.Ignore ? "\t\t\t\n" : "") + + (ForceNodump == ForceNodump.Obsolete ? "\t\t\t\n" : "") + + (ForceNodump == ForceNodump.Required ? "\t\t\t\n" : "") + + "\t\t\n" + : "") + + "\t
\n" + + "\t\n"; + + // Write the header out + sw.Write(header); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// Split path representing the parent game (SabreDAT only) + /// The name of the last game to be output + /// Current depth to output file at (SabreDAT only) + /// Last known depth to cycle back from (SabreDAT only) + /// The new depth of the tag + private int WriteStartGame(StreamWriter sw, DatItem rom, List newsplit, string lastgame, int depth, int last) + { + try + { + // No game should start with a path separator + if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) + { + rom.MachineName = rom.MachineName.Substring(1); + } + + string state = ""; + for (int i = (last == -1 ? 0 : last); i < newsplit.Count; i++) + { + for (int j = 0; j < depth - last + i - (lastgame == null ? 1 : 0); j++) + { + state += "\t"; + } + state += "\n"; + } + depth = depth - (last == -1 ? 0 : last) + newsplit.Count; + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return depth; + } + + return depth; + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// StreamWriter to output to + /// Split path representing last kwown parent game (SabreDAT only) + /// Split path representing the parent game (SabreDAT only) + /// Current depth to output file at (SabreDAT only) + /// Last known depth to cycle back from (SabreDAT only) + /// The new depth of the tag + private int WriteEndGame(StreamWriter sw, List splitpath, List newsplit, int depth, out int last) + { + last = 0; + + try + { + string state = ""; + if (splitpath != null) + { + for (int i = 0; i < newsplit.Count && i < splitpath.Count; i++) + { + // Always keep track of the last seen item + last = i; + + // If we find a difference, break + if (newsplit[i] != splitpath[i]) + { + break; + } + } + + // Now that we have the last known position, take down all open folders + for (int i = depth - 1; i > last + 1; i--) + { + // Print out the number of tabs and the end folder + for (int j = 0; j < i; j++) + { + state += "\t"; + } + state += "\n"; + } + + // Reset the current depth + depth = 2 + last; + } + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return depth; + } + + return depth; + } + + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// Current depth to output file at (SabreDAT only) + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the data was written, false on error + private bool WriteDatItem(StreamWriter sw, DatItem rom, int depth, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.ItemType == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = "", prefix = ""; + + // Pre-process the item name + ProcessItemName(rom, true); + + for (int i = 0; i < depth; i++) + { + prefix += "\t"; + } + state += prefix; + + switch (rom.ItemType) + { + case ItemType.Archive: + state += "\n"; + break; + case ItemType.BiosSet: + state += "\n"; + break; + case ItemType.Disk: + state += "\n" + prefix + "\t\n" + + prefix + "\t\t\n" + + prefix + "\t\n" + + prefix + "\n" : "/>\n"); + break; + case ItemType.Release: + state += "\n"; + break; + case ItemType.Rom: + state += "\n" + prefix + "\t\n" + + prefix + "\t\t\n" + + prefix + "\t\n" + + prefix + "\n" : "/>\n"); + break; + case ItemType.Sample: + state += "\n"; + break; + } + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT footer using the supplied StreamWriter + /// + /// StreamWriter to output to + /// Current depth to output file at (SabreDAT only) + /// True if the data was written, false on error + private bool WriteFooter(StreamWriter sw, int depth) + { + try + { + string footer = ""; + for (int i = depth - 1; i >= 2; i--) + { + // Print out the number of tabs and the end folder + for (int j = 0; j < i; j++) + { + footer += "\t"; + } + footer += "\n"; + } + footer += "\t\n
\n"; + + // Write the footer out + sw.Write(footer); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + } } diff --git a/SabreTools.Library/DatFiles/SeparatedValue.cs b/SabreTools.Library/DatFiles/SeparatedValue.cs index 54c9f412..8c1519f7 100644 --- a/SabreTools.Library/DatFiles/SeparatedValue.cs +++ b/SabreTools.Library/DatFiles/SeparatedValue.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; - using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -18,515 +16,515 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { - /// - /// Represents parsing and writing of a value-separated DAT - /// - internal class SeparatedValue : DatFile - { - // Private instance variables specific to Separated Value DATs - char _delim; + /// + /// Represents parsing and writing of a value-separated DAT + /// + internal class SeparatedValue : DatFile + { + // Private instance variables specific to Separated Value DATs + char _delim; - /// - /// Constructor designed for casting a base DatFile - /// - /// Parent DatFile to copy from - /// Delimiter for parsing individual lines - public SeparatedValue(DatFile datFile, char delim) - : base(datFile, cloneHeader: false) - { - _delim = delim; - } + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + /// Delimiter for parsing individual lines + public SeparatedValue(DatFile datFile, char delim) + : base(datFile, cloneHeader: false) + { + _delim = delim; + } - /// - /// Parse a character-separated value DAT and return all found games and roms within - /// - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - public override void ParseFile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, + /// + /// Parse a character-separated value DAT and return all found games and roms within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Open a file reader - Encoding enc = Utilities.GetEncoding(filename); - StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc); + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Open a file reader + Encoding enc = Utilities.GetEncoding(filename); + StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), enc); - // Create an empty list of columns to parse though - List columns = new List(); + // Create an empty list of columns to parse though + List columns = new List(); - long linenum = -1; - while (!sr.EndOfStream) - { - string line = sr.ReadLine(); - linenum++; + long linenum = -1; + while (!sr.EndOfStream) + { + string line = sr.ReadLine(); + linenum++; - // Parse the first line, getting types from the column names - if (linenum == 0) - { - string[] parsedColumns = line.Split(_delim); - foreach (string parsed in parsedColumns) - { - switch (parsed.ToLowerInvariant().Trim('"')) - { - case "file": - case "filename": - case "file name": - columns.Add("DatFile.FileName"); - break; - case "internal name": - columns.Add("DatFile.Name"); - break; - case "description": - case "dat description": - columns.Add("DatFile.Description"); - break; - case "game name": - case "game": - case "machine": - columns.Add("Machine.Name"); - break; - case "game description": - columns.Add("Description"); - break; - case "type": - columns.Add("DatItem.Type"); - break; - case "rom": - case "romname": - case "rom name": - case "name": - columns.Add("Rom.Name"); - break; - case "disk": - case "diskname": - case "disk name": - columns.Add("Disk.Name"); - break; - case "size": - columns.Add("DatItem.Size"); - break; - case "crc": - case "crc hash": - columns.Add("DatItem.CRC"); - break; - case "md5": - case "md5 hash": - columns.Add("DatItem.MD5"); - break; - case "sha1": - case "sha-1": - case "sha1 hash": - case "sha-1 hash": - columns.Add("DatItem.SHA1"); - break; - case "sha256": - case "sha-256": - case "sha256 hash": - case "sha-256 hash": - columns.Add("DatItem.SHA256"); - break; - case "sha384": - case "sha-384": - case "sha384 hash": - case "sha-384 hash": - columns.Add("DatItem.SHA384"); - break; - case "sha512": - case "sha-512": - case "sha512 hash": - case "sha-512 hash": - columns.Add("DatItem.SHA512"); - break; - case "nodump": - case "no dump": - case "status": - case "item status": - columns.Add("DatItem.Nodump"); - break; - case "date": - columns.Add("DatItem.Date"); - break; - default: - columns.Add("INVALID"); - break; - } - } + // Parse the first line, getting types from the column names + if (linenum == 0) + { + string[] parsedColumns = line.Split(_delim); + foreach (string parsed in parsedColumns) + { + switch (parsed.ToLowerInvariant().Trim('"')) + { + case "file": + case "filename": + case "file name": + columns.Add("DatFile.FileName"); + break; + case "internal name": + columns.Add("DatFile.Name"); + break; + case "description": + case "dat description": + columns.Add("DatFile.Description"); + break; + case "game name": + case "game": + case "machine": + columns.Add("Machine.Name"); + break; + case "game description": + columns.Add("Description"); + break; + case "type": + columns.Add("DatItem.Type"); + break; + case "rom": + case "romname": + case "rom name": + case "name": + columns.Add("Rom.Name"); + break; + case "disk": + case "diskname": + case "disk name": + columns.Add("Disk.Name"); + break; + case "size": + columns.Add("DatItem.Size"); + break; + case "crc": + case "crc hash": + columns.Add("DatItem.CRC"); + break; + case "md5": + case "md5 hash": + columns.Add("DatItem.MD5"); + break; + case "sha1": + case "sha-1": + case "sha1 hash": + case "sha-1 hash": + columns.Add("DatItem.SHA1"); + break; + case "sha256": + case "sha-256": + case "sha256 hash": + case "sha-256 hash": + columns.Add("DatItem.SHA256"); + break; + case "sha384": + case "sha-384": + case "sha384 hash": + case "sha-384 hash": + columns.Add("DatItem.SHA384"); + break; + case "sha512": + case "sha-512": + case "sha512 hash": + case "sha-512 hash": + columns.Add("DatItem.SHA512"); + break; + case "nodump": + case "no dump": + case "status": + case "item status": + columns.Add("DatItem.Nodump"); + break; + case "date": + columns.Add("DatItem.Date"); + break; + default: + columns.Add("INVALID"); + break; + } + } - continue; - } + continue; + } - // Otherwise, we want to split the line and parse - string[] parsedLine = line.Split(_delim); + // Otherwise, we want to split the line and parse + string[] parsedLine = line.Split(_delim); - // If the line doesn't have the correct number of columns, we log and skip - if (parsedLine.Length != columns.Count) - { - Globals.Logger.Warning("Malformed line found in '{0}' at line {1}", filename, linenum); - continue; - } + // If the line doesn't have the correct number of columns, we log and skip + if (parsedLine.Length != columns.Count) + { + Globals.Logger.Warning("Malformed line found in '{0}' at line {1}", filename, linenum); + continue; + } - // Set the output item information - string machineName = null, machineDesc = null, name = null, crc = null, md5 = null, sha1 = null, - sha256 = null, sha384 = null, sha512 = null, date = null; - long size = -1; - ItemType itemType = ItemType.Rom; - ItemStatus status = ItemStatus.None; + // Set the output item information + string machineName = null, machineDesc = null, name = null, crc = null, md5 = null, sha1 = null, + sha256 = null, sha384 = null, sha512 = null, date = null; + long size = -1; + ItemType itemType = ItemType.Rom; + ItemStatus status = ItemStatus.None; - // Now we loop through and get values for everything - for (int i = 0; i < columns.Count; i++) - { - string value = parsedLine[i].Trim('"'); - switch (columns[i]) - { - case "DatFile.FileName": - FileName = (String.IsNullOrWhiteSpace(FileName) ? value : FileName); - break; - case "DatFile.Name": - Name = (String.IsNullOrWhiteSpace(Name) ? value : Name); - break; - case "DatFile.Description": - Description = (String.IsNullOrWhiteSpace(Description) ? value : Description); - break; - case "Machine.Name": - machineName = value; - break; - case "Description": - machineDesc = value; - break; - case "DatItem.Type": - itemType = Utilities.GetItemType(value) ?? ItemType.Rom; - break; - case "Rom.Name": - case "Disk.Name": - name = String.IsNullOrWhiteSpace(value) ? name : value; - break; - case "DatItem.Size": - if (!Int64.TryParse(value, out size)) - { - size = -1; - } - break; - case "DatItem.CRC": - crc = Utilities.CleanHashData(value, Constants.CRCLength); - break; - case "DatItem.MD5": - md5 = Utilities.CleanHashData(value, Constants.MD5Length); - break; - case "DatItem.SHA1": - sha1 = Utilities.CleanHashData(value, Constants.SHA1Length); - break; - case "DatItem.SHA256": - sha256 = Utilities.CleanHashData(value, Constants.SHA256Length); - break; - case "DatItem.SHA384": - sha384 = Utilities.CleanHashData(value, Constants.SHA384Length); - break; - case "DatItem.SHA512": - sha512 = Utilities.CleanHashData(value, Constants.SHA512Length); - break; - case "DatItem.Nodump": - status = Utilities.GetItemStatus(value); - break; - case "DatItem.Date": - date = value; - break; - } - } + // Now we loop through and get values for everything + for (int i = 0; i < columns.Count; i++) + { + string value = parsedLine[i].Trim('"'); + switch (columns[i]) + { + case "DatFile.FileName": + FileName = (String.IsNullOrWhiteSpace(FileName) ? value : FileName); + break; + case "DatFile.Name": + Name = (String.IsNullOrWhiteSpace(Name) ? value : Name); + break; + case "DatFile.Description": + Description = (String.IsNullOrWhiteSpace(Description) ? value : Description); + break; + case "Machine.Name": + machineName = value; + break; + case "Description": + machineDesc = value; + break; + case "DatItem.Type": + itemType = Utilities.GetItemType(value) ?? ItemType.Rom; + break; + case "Rom.Name": + case "Disk.Name": + name = String.IsNullOrWhiteSpace(value) ? name : value; + break; + case "DatItem.Size": + if (!Int64.TryParse(value, out size)) + { + size = -1; + } + break; + case "DatItem.CRC": + crc = Utilities.CleanHashData(value, Constants.CRCLength); + break; + case "DatItem.MD5": + md5 = Utilities.CleanHashData(value, Constants.MD5Length); + break; + case "DatItem.SHA1": + sha1 = Utilities.CleanHashData(value, Constants.SHA1Length); + break; + case "DatItem.SHA256": + sha256 = Utilities.CleanHashData(value, Constants.SHA256Length); + break; + case "DatItem.SHA384": + sha384 = Utilities.CleanHashData(value, Constants.SHA384Length); + break; + case "DatItem.SHA512": + sha512 = Utilities.CleanHashData(value, Constants.SHA512Length); + break; + case "DatItem.Nodump": + status = Utilities.GetItemStatus(value); + break; + case "DatItem.Date": + date = value; + break; + } + } - // And now we populate and add the new item - switch (itemType) - { - case ItemType.Archive: - Archive archive = new Archive() - { - Name = name, + // And now we populate and add the new item + switch (itemType) + { + case ItemType.Archive: + Archive archive = new Archive() + { + Name = name, - MachineName = machineName, - MachineDescription = machineDesc, - }; + MachineName = machineName, + MachineDescription = machineDesc, + }; - ParseAddHelper(archive, clean, remUnicode); - break; - case ItemType.BiosSet: - BiosSet biosset = new BiosSet() - { - Name = name, + ParseAddHelper(archive, clean, remUnicode); + break; + case ItemType.BiosSet: + BiosSet biosset = new BiosSet() + { + Name = name, - MachineName = machineName, - Description = machineDesc, - }; + MachineName = machineName, + Description = machineDesc, + }; - ParseAddHelper(biosset, clean, remUnicode); - break; - case ItemType.Disk: - Disk disk = new Disk() - { - Name = name, - MD5 = md5, - SHA1 = sha1, - SHA256 = sha256, - SHA384 = sha384, - SHA512 = sha512, + ParseAddHelper(biosset, clean, remUnicode); + break; + case ItemType.Disk: + Disk disk = new Disk() + { + Name = name, + MD5 = md5, + SHA1 = sha1, + SHA256 = sha256, + SHA384 = sha384, + SHA512 = sha512, - MachineName = machineName, - MachineDescription = machineDesc, + MachineName = machineName, + MachineDescription = machineDesc, - ItemStatus = status, - }; + ItemStatus = status, + }; - ParseAddHelper(disk, clean, remUnicode); - break; - case ItemType.Release: - Release release = new Release() - { - Name = name, + ParseAddHelper(disk, clean, remUnicode); + break; + case ItemType.Release: + Release release = new Release() + { + Name = name, - MachineName = machineName, - MachineDescription = machineDesc, - }; + MachineName = machineName, + MachineDescription = machineDesc, + }; - ParseAddHelper(release, clean, remUnicode); - break; - case ItemType.Rom: - Rom rom = new Rom() - { - Name = name, - Size = size, - CRC = crc, - MD5 = md5, - SHA1 = sha1, - SHA256 = sha256, - SHA384 = sha384, - SHA512 = sha512, - Date = date, + ParseAddHelper(release, clean, remUnicode); + break; + case ItemType.Rom: + Rom rom = new Rom() + { + Name = name, + Size = size, + CRC = crc, + MD5 = md5, + SHA1 = sha1, + SHA256 = sha256, + SHA384 = sha384, + SHA512 = sha512, + Date = date, - MachineName = machineName, - MachineDescription = machineDesc, + MachineName = machineName, + MachineDescription = machineDesc, - ItemStatus = status, - }; + ItemStatus = status, + }; - ParseAddHelper(rom, clean, remUnicode); - break; - case ItemType.Sample: - Sample sample = new Sample() - { - Name = name, + ParseAddHelper(rom, clean, remUnicode); + break; + case ItemType.Sample: + Sample sample = new Sample() + { + Name = name, - MachineName = machineName, - MachineDescription = machineDesc, - }; + MachineName = machineName, + MachineDescription = machineDesc, + }; - ParseAddHelper(sample, clean, remUnicode); - break; - } - } - } + ParseAddHelper(sample, clean, remUnicode); + break; + } + } + } - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the DAT was written correctly, false otherwise - public override bool WriteToFile(string outfile, bool ignoreblanks = false) - { - try - { - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = Utilities.TryCreate(outfile); + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public override bool WriteToFile(string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); - // If we get back null for some reason, just log and return - if (fs == null) - { - Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); - return false; - } + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); - // Write out the header - WriteHeader(sw); + // Write out the header + WriteHeader(sw); - // Get a properly sorted set of keys - List keys = Keys; - keys.Sort(new NaturalComparer()); + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); - foreach (string key in keys) - { - List roms = this[key]; + foreach (string key in keys) + { + List roms = this[key]; - // Resolve the names in the block - roms = DatItem.ResolveNames(roms); + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); - for (int index = 0; index < roms.Count; index++) - { - DatItem rom = roms[index]; + for (int index = 0; index < roms.Count; index++) + { + DatItem rom = roms[index]; - // There are apparently times when a null rom can skip by, skip them - if (rom.Name == null || rom.MachineName == null) - { - Globals.Logger.Warning("Null rom found!"); - continue; - } + // There are apparently times when a null rom can skip by, skip them + if (rom.Name == null || rom.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (rom.ItemType == ItemType.Rom - && ((Rom)rom).Size == -1 - && ((Rom)rom).CRC == "null") - { - Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); - } + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.ItemType == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + } - // Now, output the rom data - WriteDatItem(sw, rom, ignoreblanks); - } - } + // Now, output the rom data + WriteDatItem(sw, rom, ignoreblanks); + } + } - Globals.Logger.Verbose("File written!" + Environment.NewLine); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteHeader(StreamWriter sw) - { - try - { - string header = string.Format("\"File Name\"{0}\"Internal Name\"{0}\"Description\"{0}\"Game Name\"{0}\"Game Description\"{0}\"Type\"{0}\"" + - "Rom Name\"{0}\"Disk Name\"{0}\"Size\"{0}\"CRC\"{0}\"MD5\"{0}\"SHA1\"{0}\"SHA256\"{0}\"Nodump\"\n", _delim); + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteHeader(StreamWriter sw) + { + try + { + string header = string.Format("\"File Name\"{0}\"Internal Name\"{0}\"Description\"{0}\"Game Name\"{0}\"Game Description\"{0}\"Type\"{0}\"" + + "Rom Name\"{0}\"Disk Name\"{0}\"Size\"{0}\"CRC\"{0}\"MD5\"{0}\"SHA1\"{0}\"SHA256\"{0}\"Nodump\"\n", _delim); - // Write the header out - sw.Write(header); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + // Write the header out + sw.Write(header); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } + return true; + } - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the data was written, false on error - private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) - { - // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip - if (ignoreblanks - && (rom.ItemType == ItemType.Rom - && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) - { - return true; - } + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the data was written, false on error + private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.ItemType == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } - try - { - // Initialize all strings - string state = "", - pre = "", - post = "", - type = "", - romname = "", - diskname = "", - size = "", - crc = "", - md5 = "", - sha1 = "", - sha256 = "", - sha384 = "", - sha512 = "", - status = ""; + try + { + // Initialize all strings + string state = "", + pre = "", + post = "", + type = "", + romname = "", + diskname = "", + size = "", + crc = "", + md5 = "", + sha1 = "", + sha256 = "", + sha384 = "", + sha512 = "", + status = ""; - // Separated values should only output Rom and Disk - if (rom.ItemType != ItemType.Disk && rom.ItemType != ItemType.Rom) - { - return true; - } + // Separated values should only output Rom and Disk + if (rom.ItemType != ItemType.Disk && rom.ItemType != ItemType.Rom) + { + return true; + } - if (rom.ItemType == ItemType.Rom) - { - type = "rom"; - romname = rom.Name; - size = ((Rom)rom).Size.ToString(); - crc = ((Rom)rom).CRC; - md5 = ((Rom)rom).MD5; - sha1 = ((Rom)rom).SHA1; - sha256 = ((Rom)rom).SHA256; - sha384 = ((Rom)rom).SHA384; - sha512 = ((Rom)rom).SHA512; - status = (((Rom)rom).ItemStatus != ItemStatus.None ? "\"" + ((Rom)rom).ItemStatus.ToString() + "\"" : "\"\""); - } - else if (rom.ItemType == ItemType.Disk) - { - type = "disk"; - diskname = rom.Name; - md5 = ((Disk)rom).MD5; - sha1 = ((Disk)rom).SHA1; - sha256 = ((Disk)rom).SHA256; - sha384 = ((Disk)rom).SHA384; - sha512 = ((Disk)rom).SHA512; - status = (((Disk)rom).ItemStatus != ItemStatus.None ? "\"" + ((Disk)rom).ItemStatus.ToString() + "\"" : "\"\""); - } + if (rom.ItemType == ItemType.Rom) + { + type = "rom"; + romname = rom.Name; + size = ((Rom)rom).Size.ToString(); + crc = ((Rom)rom).CRC; + md5 = ((Rom)rom).MD5; + sha1 = ((Rom)rom).SHA1; + sha256 = ((Rom)rom).SHA256; + sha384 = ((Rom)rom).SHA384; + sha512 = ((Rom)rom).SHA512; + status = (((Rom)rom).ItemStatus != ItemStatus.None ? "\"" + ((Rom)rom).ItemStatus.ToString() + "\"" : "\"\""); + } + else if (rom.ItemType == ItemType.Disk) + { + type = "disk"; + diskname = rom.Name; + md5 = ((Disk)rom).MD5; + sha1 = ((Disk)rom).SHA1; + sha256 = ((Disk)rom).SHA256; + sha384 = ((Disk)rom).SHA384; + sha512 = ((Disk)rom).SHA512; + status = (((Disk)rom).ItemStatus != ItemStatus.None ? "\"" + ((Disk)rom).ItemStatus.ToString() + "\"" : "\"\""); + } - pre = CreatePrefixPostfix(rom, true); - post = CreatePrefixPostfix(rom, false); - string inline = string.Format("\"" + FileName + "\"" - + "{0}\"" + Name + "\"" - + "{0}\"" + Description + "\"" - + "{0}\"" + (!ExcludeFields[(int)Field.MachineName] ? rom.MachineName : "") + "\"" - + "{0}\"" + (!ExcludeFields[(int)Field.Description] ? rom.MachineDescription : "") + "\"" - + "{0}\"" + type + "\"" - + "{0}\"" + (!ExcludeFields[(int)Field.Name] ? romname : "") + "\"" - + "{0}\"" + (!ExcludeFields[(int)Field.Name] ? diskname : "") + "\"" - + "{0}\"" + (!ExcludeFields[(int)Field.Size] ? size : "") + "\"" - + "{0}\"" + (!ExcludeFields[(int)Field.CRC] ? crc : "") + "\"" - + "{0}\"" + (!ExcludeFields[(int)Field.MD5] ? md5 : "") + "\"" - + "{0}\"" + (!ExcludeFields[(int)Field.SHA1] ? sha1 : "") + "\"" - + "{0}\"" + (!ExcludeFields[(int)Field.SHA256] ? sha256 : "") + "\"" - // + "{0}\"" + (!ExcludeFields[(int)Field.SHA384] ? sha384 : "") + "\"" - // + "{0}\"" + (!ExcludeFields[(int)Field.SHA512] ? sha512 : "") + "\"" - + "{0}" + status, _delim); - state += pre + inline + post + "\n"; + pre = CreatePrefixPostfix(rom, true); + post = CreatePrefixPostfix(rom, false); + string inline = string.Format("\"" + FileName + "\"" + + "{0}\"" + Name + "\"" + + "{0}\"" + Description + "\"" + + "{0}\"" + (!ExcludeFields[(int)Field.MachineName] ? rom.MachineName : "") + "\"" + + "{0}\"" + (!ExcludeFields[(int)Field.Description] ? rom.MachineDescription : "") + "\"" + + "{0}\"" + type + "\"" + + "{0}\"" + (!ExcludeFields[(int)Field.Name] ? romname : "") + "\"" + + "{0}\"" + (!ExcludeFields[(int)Field.Name] ? diskname : "") + "\"" + + "{0}\"" + (!ExcludeFields[(int)Field.Size] ? size : "") + "\"" + + "{0}\"" + (!ExcludeFields[(int)Field.CRC] ? crc : "") + "\"" + + "{0}\"" + (!ExcludeFields[(int)Field.MD5] ? md5 : "") + "\"" + + "{0}\"" + (!ExcludeFields[(int)Field.SHA1] ? sha1 : "") + "\"" + + "{0}\"" + (!ExcludeFields[(int)Field.SHA256] ? sha256 : "") + "\"" + // + "{0}\"" + (!ExcludeFields[(int)Field.SHA384] ? sha384 : "") + "\"" + // + "{0}\"" + (!ExcludeFields[(int)Field.SHA512] ? sha512 : "") + "\"" + + "{0}" + status, _delim); + state += pre + inline + post + "\n"; - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } - return true; - } - } + return true; + } + } } diff --git a/SabreTools.Library/DatFiles/SoftwareList.cs b/SabreTools.Library/DatFiles/SoftwareList.cs index 3d42d8b2..0fca4983 100644 --- a/SabreTools.Library/DatFiles/SoftwareList.cs +++ b/SabreTools.Library/DatFiles/SoftwareList.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Text; using System.Web; using System.Xml; - using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -20,855 +19,855 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { - /// - /// Represents parsing and writing of a SofwareList, M1, or MAME XML DAT - /// - /// TODO: Verify that all write for this DatFile type is correct - internal class SoftwareList : DatFile - { - /// - /// Constructor designed for casting a base DatFile - /// - /// Parent DatFile to copy from - public SoftwareList(DatFile datFile) - : base(datFile, cloneHeader: false) - { - } - - /// - /// Parse an SofwareList XML DAT and return all found games and roms within - /// - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - public override void ParseFile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Prepare all internal variables - Encoding enc = Utilities.GetEncoding(filename); - XmlReader xtr = Utilities.GetXmlTextReader(filename); - - // If we got a null reader, just return - if (xtr == null) - { - return; - } - - // Otherwise, read the file to the end - try - { - xtr.MoveToContent(); - while (!xtr.EOF) - { - // We only want elements - if (xtr.NodeType != XmlNodeType.Element) - { - xtr.Read(); - continue; - } - - switch (xtr.Name) - { - case "softwarelist": - Name = (String.IsNullOrWhiteSpace(Name) ? xtr.GetAttribute("name") ?? "" : Name); - Description = (String.IsNullOrWhiteSpace(Description) ? xtr.GetAttribute("description") ?? "" : Description); - if (ForceMerging == ForceMerging.None) - { - ForceMerging = Utilities.GetForceMerging(xtr.GetAttribute("forcemerging")); - } - if (ForceNodump == ForceNodump.None) - { - ForceNodump = Utilities.GetForceNodump(xtr.GetAttribute("forcenodump")); - } - if (ForcePacking == ForcePacking.None) - { - ForcePacking = Utilities.GetForcePacking(xtr.GetAttribute("forcepacking")); - } - xtr.Read(); - break; - // We want to process the entire subtree of the machine - case "software": - ReadSoftware(xtr.ReadSubtree(), filename, sysid, srcid, keep, clean, remUnicode); - - // Skip the software now that we've processed it - xtr.Skip(); - break; - default: - xtr.Read(); - break; - } - } - } - catch (Exception ex) - { - Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); - - // For XML errors, just skip the affected node - xtr?.Read(); - } - - xtr.Dispose(); - } - - /// - /// Read software information - /// - /// XmlReader representing a software block - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private void ReadSoftware( - XmlReader reader, - - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // If we have an empty software, skip it - if (reader == null) - { - return; - } - - // Otherwise, add what is possible - reader.MoveToContent(); - - string key = ""; - string temptype = reader.Name; - bool containsItems = false; - - // Create a new machine - MachineType machineType = MachineType.NULL; - if (Utilities.GetYesNo(reader.GetAttribute("isbios")) == true) - { - machineType |= MachineType.Bios; - } - if (Utilities.GetYesNo(reader.GetAttribute("isdevice")) == true) - { - machineType |= MachineType.Device; - } - if (Utilities.GetYesNo(reader.GetAttribute("ismechanical")) == true) - { - machineType |= MachineType.Mechanical; - } - - Machine machine = new Machine - { - Name = reader.GetAttribute("name"), - Description = reader.GetAttribute("name"), - Supported = Utilities.GetYesNo(reader.GetAttribute("supported")), // (yes|partial|no) "yes" - - CloneOf = reader.GetAttribute("cloneof") ?? "", - Infos = new List>(), - - MachineType = (machineType == MachineType.NULL ? MachineType.None : machineType), - }; - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the elements from the software - switch (reader.Name) - { - case "description": - machine.Description = reader.ReadElementContentAsString(); - break; - case "year": - machine.Year = reader.ReadElementContentAsString(); - break; - case "publisher": - machine.Publisher = reader.ReadElementContentAsString(); - break; - case "info": - machine.Infos.Add(new Tuple(reader.GetAttribute("name"), reader.GetAttribute("value"))); - - reader.Read(); - break; - case "sharedfeat": - // string sharedfeat_name = reader.GetAttribute("name"); - // string sharedfeat_value = reader.GetAttribute("value"); - - reader.Read(); - break; - case "part": // Contains all rom and disk information - containsItems = ReadPart(reader.ReadSubtree(), machine, filename, sysid, srcid, keep, clean, remUnicode); - - // Skip the part now that we've processed it - reader.Skip(); - break; - default: - reader.Read(); - break; - } - } - - // If no items were found for this machine, add a Blank placeholder - if (!containsItems) - { - Blank blank = new Blank() - { - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - blank.CopyMachineInformation(machine); - - // Now process and add the rom - ParseAddHelper(blank, clean, remUnicode); - } - } - - /// - /// Read part information - /// - /// XmlReader representing a part block - /// Machine information to pass to contained items - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private bool ReadPart( - XmlReader reader, - Machine machine, - - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - string key = "", areaname = "", partname = "", partinterface = ""; - string temptype = reader.Name; - long? areasize = null; - List> features = new List>(); - bool containsItems = false; - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "part") - { - partname = ""; - partinterface = ""; - features = new List>(); - } - if (reader.NodeType == XmlNodeType.EndElement && (reader.Name == "dataarea" || reader.Name == "diskarea")) - { - areaname = ""; - areasize = null; - } - - reader.Read(); - continue; - } - - // Get the elements from the software - switch (reader.Name) - { - case "part": - partname = reader.GetAttribute("name"); - partinterface = reader.GetAttribute("interface"); - - reader.Read(); - break; - case "feature": - features.Add(new Tuple(reader.GetAttribute("name"), reader.GetAttribute("feature"))); - - reader.Read(); - break; - case "dataarea": - areaname = reader.GetAttribute("name"); - if (reader.GetAttribute("size") != null) - { - if (Int64.TryParse(reader.GetAttribute("size"), out long tempas)) - { - areasize = tempas; - } - } - // string dataarea_width = reader.GetAttribute("width"); // (8|16|32|64) "8" - // string dataarea_endianness = reader.GetAttribute("endianness"); // endianness (big|little) "little" - - containsItems = ReadDataArea(reader.ReadSubtree(), machine, features, areaname, areasize, - partname, partinterface, filename, sysid, srcid, keep, clean, remUnicode); - - // Skip the dataarea now that we've processed it - reader.Skip(); - break; - case "diskarea": - areaname = reader.GetAttribute("name"); - - containsItems = ReadDiskArea(reader.ReadSubtree(), machine, features, areaname, areasize, - partname, partinterface, filename, sysid, srcid, keep, clean, remUnicode); - - // Skip the diskarea now that we've processed it - reader.Skip(); - break; - case "dipswitch": - // string dipswitch_name = reader.GetAttribute("name"); - // string dipswitch_tag = reader.GetAttribute("tag"); - // string dipswitch_mask = reader.GetAttribute("mask"); - - // For every element... - // string dipvalue_name = reader.GetAttribute("name"); - // string dipvalue_value = reader.GetAttribute("value"); - // bool? dipvalue_default = Utilities.GetYesNo(reader.GetAttribute("default")); // (yes|no) "no" - - reader.Skip(); - break; - default: - reader.Read(); - break; - } - } - - return containsItems; - } - - /// - /// Read dataarea information - /// - /// XmlReader representing a dataarea block - /// Machine information to pass to contained items - /// List of features from the parent part - /// Name of the containing area - /// Size of the containing area - /// Name of the containing part - /// Interface of the containing part - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private bool ReadDataArea( - XmlReader reader, - Machine machine, - List> features, - string areaname, - long? areasize, - string partname, - string partinterface, - - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - string key = ""; - string temptype = reader.Name; - bool containsItems = false; - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the elements from the software - switch (reader.Name) - { - case "rom": - containsItems = true; - - // If the rom is continue or ignore, add the size to the previous rom - if (reader.GetAttribute("loadflag") == "continue" || reader.GetAttribute("loadflag") == "ignore") - { - int index = this[key].Count - 1; - DatItem lastrom = this[key][index]; - if (lastrom.ItemType == ItemType.Rom) - { - ((Rom)lastrom).Size += Utilities.GetSize(reader.GetAttribute("size")); - } - this[key].RemoveAt(index); - this[key].Add(lastrom); - reader.Read(); - continue; - } - - DatItem rom = new Rom - { - Name = reader.GetAttribute("name"), - Size = Utilities.GetSize(reader.GetAttribute("size")), - CRC = Utilities.CleanHashData(reader.GetAttribute("crc"), Constants.CRCLength), - MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), - SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), - SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), - SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), - SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), - Offset = reader.GetAttribute("offset"), - // Value = reader.GetAttribute("value"); - ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), - // LoadFlag = reader.GetAttribute("loadflag"), // (load16_byte|load16_word|load16_word_swap|load32_byte|load32_word|load32_word_swap|load32_dword|load64_word|load64_word_swap|reload|fill|continue|reload_plain|ignore) - - AreaName = areaname, - AreaSize = areasize, - Features = features, - PartName = partname, - PartInterface = partinterface, - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - rom.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(rom, clean, remUnicode); - - reader.Read(); - break; - default: - reader.Read(); - break; - } - } - - return containsItems; - } - - /// - /// Read diskarea information - /// - /// XmlReader representing a diskarea block - /// Machine information to pass to contained items - /// List of features from the parent part - /// Name of the containing area - /// Size of the containing area - /// Name of the containing part - /// Interface of the containing part - /// Name of the file to be parsed - /// System ID for the DAT - /// Source ID for the DAT - /// True if full pathnames are to be kept, false otherwise (default) - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private bool ReadDiskArea( - XmlReader reader, - Machine machine, - List> features, - string areaname, - long? areasize, - string partname, - string partinterface, - - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - string key = ""; - string temptype = reader.Name; - bool containsItems = false; - - while (!reader.EOF) - { - // We only want elements - if (reader.NodeType != XmlNodeType.Element) - { - reader.Read(); - continue; - } - - // Get the elements from the software - switch (reader.Name) - { - case "disk": - containsItems = true; - - DatItem disk = new Disk - { - Name = reader.GetAttribute("name"), - MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), - SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), - SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), - SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), - SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), - ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), - Writable = Utilities.GetYesNo(reader.GetAttribute("writable")), - - AreaName = areaname, - AreaSize = areasize, - Features = features, - PartName = partname, - PartInterface = partinterface, - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - disk.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(disk, clean, remUnicode); - - reader.Read(); - break; - default: - reader.Read(); - break; - } - } - - return containsItems; - } - - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the DAT was written correctly, false otherwise - public override bool WriteToFile(string outfile, bool ignoreblanks = false) - { - try - { - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = Utilities.TryCreate(outfile); - - // If we get back null for some reason, just log and return - if (fs == null) - { - Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); - return false; - } - - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); - - // Write out the header - WriteHeader(sw); - - // Write out each of the machines and roms - string lastgame = null; - - // Get a properly sorted set of keys - List keys = Keys; - keys.Sort(new NaturalComparer()); - - foreach (string key in keys) - { - List roms = this[key]; - - // Resolve the names in the block - roms = DatItem.ResolveNames(roms); - - for (int index = 0; index < roms.Count; index++) - { - DatItem rom = roms[index]; - - // There are apparently times when a null rom can skip by, skip them - if (rom.Name == null || rom.MachineName == null) - { - Globals.Logger.Warning("Null rom found!"); - continue; - } - - // If we have a different game and we're not at the start of the list, output the end of last item - if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteEndGame(sw); - } - - // If we have a new game, output the beginning of the new item - if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) - { - WriteStartGame(sw, rom); - } - - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (rom.ItemType == ItemType.Rom - && ((Rom)rom).Size == -1 - && ((Rom)rom).CRC == "null") - { - Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); - - lastgame = rom.MachineName; - continue; - } - - // Now, output the rom data - WriteDatItem(sw, rom, ignoreblanks); - - // Set the new data to compare against - lastgame = rom.MachineName; - } - } - - // Write the file footer out - WriteFooter(sw); - - Globals.Logger.Verbose("File written!" + Environment.NewLine); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteHeader(StreamWriter sw) - { - try - { - string header = "\n" + - "\n\n" + - "\n\n"; - - // Write the header out - sw.Write(header); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if the data was written, false on error - private bool WriteStartGame(StreamWriter sw, DatItem rom) - { - try - { - // No game should start with a path separator - if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) - { - rom.MachineName = rom.MachineName.Substring(1); - } - - string state = "\t\n" : "") - + (!ExcludeFields[(int)Field.Description] ? "\t\t" + HttpUtility.HtmlEncode(rom.MachineDescription) + "\n" : "") - + (!ExcludeFields[(int)Field.Year] && rom.Year != null ? "\t\t" + HttpUtility.HtmlEncode(rom.Year) + "\n" : "") - + (!ExcludeFields[(int)Field.Publisher] && rom.Publisher != null ? "\t\t" + HttpUtility.HtmlEncode(rom.Publisher) + "\n" : ""); - - if (!ExcludeFields[(int)Field.Infos]) - { - foreach (Tuple kvp in rom.Infos) - { - state += "\t\t\n"; - } - } - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteEndGame(StreamWriter sw) - { - try - { - string state = "\t\n\n"; - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if the data was written, false on error - private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) - { - // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip - if (ignoreblanks - && (rom.ItemType == ItemType.Rom - && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) - { - return true; - } - - try - { - string state = ""; - - // Pre-process the item name - ProcessItemName(rom, true); - - state += "\t\t\n"; - - if (!ExcludeFields[(int)Field.Features]) - { - foreach (Tuple kvp in rom.Features) - { - state += "\t\t\t\n"; - } - } - - switch (rom.ItemType) - { - case ItemType.Disk: - state += "\t\t\t\n" - + "\t\t\t\t\n" - + "\t\t\t\n"; - break; - case ItemType.Rom: - state += "\t\t\t\n" - + "\t\t\t\t\n" - + "\t\t\t\n"; - break; - } - - state += "\t\t\n"; - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - /// - /// Write out DAT footer using the supplied StreamWriter - /// - /// StreamWriter to output to - /// True if the data was written, false on error - private bool WriteFooter(StreamWriter sw) - { - try - { - string footer = "\t\n\n\n"; - - // Write the footer out - sw.Write(footer); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - } + /// + /// Represents parsing and writing of a SofwareList, M1, or MAME XML DAT + /// + /// TODO: Verify that all write for this DatFile type is correct + internal class SoftwareList : DatFile + { + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + public SoftwareList(DatFile datFile) + : base(datFile, cloneHeader: false) + { + } + + /// + /// Parse an SofwareList XML DAT and return all found games and roms within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Prepare all internal variables + Encoding enc = Utilities.GetEncoding(filename); + XmlReader xtr = Utilities.GetXmlTextReader(filename); + + // If we got a null reader, just return + if (xtr == null) + { + return; + } + + // Otherwise, read the file to the end + try + { + xtr.MoveToContent(); + while (!xtr.EOF) + { + // We only want elements + if (xtr.NodeType != XmlNodeType.Element) + { + xtr.Read(); + continue; + } + + switch (xtr.Name) + { + case "softwarelist": + Name = (String.IsNullOrWhiteSpace(Name) ? xtr.GetAttribute("name") ?? "" : Name); + Description = (String.IsNullOrWhiteSpace(Description) ? xtr.GetAttribute("description") ?? "" : Description); + if (ForceMerging == ForceMerging.None) + { + ForceMerging = Utilities.GetForceMerging(xtr.GetAttribute("forcemerging")); + } + if (ForceNodump == ForceNodump.None) + { + ForceNodump = Utilities.GetForceNodump(xtr.GetAttribute("forcenodump")); + } + if (ForcePacking == ForcePacking.None) + { + ForcePacking = Utilities.GetForcePacking(xtr.GetAttribute("forcepacking")); + } + xtr.Read(); + break; + // We want to process the entire subtree of the machine + case "software": + ReadSoftware(xtr.ReadSubtree(), filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the software now that we've processed it + xtr.Skip(); + break; + default: + xtr.Read(); + break; + } + } + } + catch (Exception ex) + { + Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); + + // For XML errors, just skip the affected node + xtr?.Read(); + } + + xtr.Dispose(); + } + + /// + /// Read software information + /// + /// XmlReader representing a software block + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private void ReadSoftware( + XmlReader reader, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // If we have an empty software, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + string key = ""; + string temptype = reader.Name; + bool containsItems = false; + + // Create a new machine + MachineType machineType = MachineType.NULL; + if (Utilities.GetYesNo(reader.GetAttribute("isbios")) == true) + { + machineType |= MachineType.Bios; + } + if (Utilities.GetYesNo(reader.GetAttribute("isdevice")) == true) + { + machineType |= MachineType.Device; + } + if (Utilities.GetYesNo(reader.GetAttribute("ismechanical")) == true) + { + machineType |= MachineType.Mechanical; + } + + Machine machine = new Machine + { + Name = reader.GetAttribute("name"), + Description = reader.GetAttribute("name"), + Supported = Utilities.GetYesNo(reader.GetAttribute("supported")), // (yes|partial|no) "yes" + + CloneOf = reader.GetAttribute("cloneof") ?? "", + Infos = new List>(), + + MachineType = (machineType == MachineType.NULL ? MachineType.None : machineType), + }; + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the elements from the software + switch (reader.Name) + { + case "description": + machine.Description = reader.ReadElementContentAsString(); + break; + case "year": + machine.Year = reader.ReadElementContentAsString(); + break; + case "publisher": + machine.Publisher = reader.ReadElementContentAsString(); + break; + case "info": + machine.Infos.Add(new Tuple(reader.GetAttribute("name"), reader.GetAttribute("value"))); + + reader.Read(); + break; + case "sharedfeat": + // string sharedfeat_name = reader.GetAttribute("name"); + // string sharedfeat_value = reader.GetAttribute("value"); + + reader.Read(); + break; + case "part": // Contains all rom and disk information + containsItems = ReadPart(reader.ReadSubtree(), machine, filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the part now that we've processed it + reader.Skip(); + break; + default: + reader.Read(); + break; + } + } + + // If no items were found for this machine, add a Blank placeholder + if (!containsItems) + { + Blank blank = new Blank() + { + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + blank.CopyMachineInformation(machine); + + // Now process and add the rom + ParseAddHelper(blank, clean, remUnicode); + } + } + + /// + /// Read part information + /// + /// XmlReader representing a part block + /// Machine information to pass to contained items + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private bool ReadPart( + XmlReader reader, + Machine machine, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + string key = "", areaname = "", partname = "", partinterface = ""; + string temptype = reader.Name; + long? areasize = null; + List> features = new List>(); + bool containsItems = false; + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "part") + { + partname = ""; + partinterface = ""; + features = new List>(); + } + if (reader.NodeType == XmlNodeType.EndElement && (reader.Name == "dataarea" || reader.Name == "diskarea")) + { + areaname = ""; + areasize = null; + } + + reader.Read(); + continue; + } + + // Get the elements from the software + switch (reader.Name) + { + case "part": + partname = reader.GetAttribute("name"); + partinterface = reader.GetAttribute("interface"); + + reader.Read(); + break; + case "feature": + features.Add(new Tuple(reader.GetAttribute("name"), reader.GetAttribute("feature"))); + + reader.Read(); + break; + case "dataarea": + areaname = reader.GetAttribute("name"); + if (reader.GetAttribute("size") != null) + { + if (Int64.TryParse(reader.GetAttribute("size"), out long tempas)) + { + areasize = tempas; + } + } + // string dataarea_width = reader.GetAttribute("width"); // (8|16|32|64) "8" + // string dataarea_endianness = reader.GetAttribute("endianness"); // endianness (big|little) "little" + + containsItems = ReadDataArea(reader.ReadSubtree(), machine, features, areaname, areasize, + partname, partinterface, filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the dataarea now that we've processed it + reader.Skip(); + break; + case "diskarea": + areaname = reader.GetAttribute("name"); + + containsItems = ReadDiskArea(reader.ReadSubtree(), machine, features, areaname, areasize, + partname, partinterface, filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the diskarea now that we've processed it + reader.Skip(); + break; + case "dipswitch": + // string dipswitch_name = reader.GetAttribute("name"); + // string dipswitch_tag = reader.GetAttribute("tag"); + // string dipswitch_mask = reader.GetAttribute("mask"); + + // For every element... + // string dipvalue_name = reader.GetAttribute("name"); + // string dipvalue_value = reader.GetAttribute("value"); + // bool? dipvalue_default = Utilities.GetYesNo(reader.GetAttribute("default")); // (yes|no) "no" + + reader.Skip(); + break; + default: + reader.Read(); + break; + } + } + + return containsItems; + } + + /// + /// Read dataarea information + /// + /// XmlReader representing a dataarea block + /// Machine information to pass to contained items + /// List of features from the parent part + /// Name of the containing area + /// Size of the containing area + /// Name of the containing part + /// Interface of the containing part + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private bool ReadDataArea( + XmlReader reader, + Machine machine, + List> features, + string areaname, + long? areasize, + string partname, + string partinterface, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + string key = ""; + string temptype = reader.Name; + bool containsItems = false; + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the elements from the software + switch (reader.Name) + { + case "rom": + containsItems = true; + + // If the rom is continue or ignore, add the size to the previous rom + if (reader.GetAttribute("loadflag") == "continue" || reader.GetAttribute("loadflag") == "ignore") + { + int index = this[key].Count - 1; + DatItem lastrom = this[key][index]; + if (lastrom.ItemType == ItemType.Rom) + { + ((Rom)lastrom).Size += Utilities.GetSize(reader.GetAttribute("size")); + } + this[key].RemoveAt(index); + this[key].Add(lastrom); + reader.Read(); + continue; + } + + DatItem rom = new Rom + { + Name = reader.GetAttribute("name"), + Size = Utilities.GetSize(reader.GetAttribute("size")), + CRC = Utilities.CleanHashData(reader.GetAttribute("crc"), Constants.CRCLength), + MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), + SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), + SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), + SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), + SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), + Offset = reader.GetAttribute("offset"), + // Value = reader.GetAttribute("value"); + ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), + // LoadFlag = reader.GetAttribute("loadflag"), // (load16_byte|load16_word|load16_word_swap|load32_byte|load32_word|load32_word_swap|load32_dword|load64_word|load64_word_swap|reload|fill|continue|reload_plain|ignore) + + AreaName = areaname, + AreaSize = areasize, + Features = features, + PartName = partname, + PartInterface = partinterface, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + rom.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(rom, clean, remUnicode); + + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + + return containsItems; + } + + /// + /// Read diskarea information + /// + /// XmlReader representing a diskarea block + /// Machine information to pass to contained items + /// List of features from the parent part + /// Name of the containing area + /// Size of the containing area + /// Name of the containing part + /// Interface of the containing part + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private bool ReadDiskArea( + XmlReader reader, + Machine machine, + List> features, + string areaname, + long? areasize, + string partname, + string partinterface, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + string key = ""; + string temptype = reader.Name; + bool containsItems = false; + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the elements from the software + switch (reader.Name) + { + case "disk": + containsItems = true; + + DatItem disk = new Disk + { + Name = reader.GetAttribute("name"), + MD5 = Utilities.CleanHashData(reader.GetAttribute("md5"), Constants.MD5Length), + SHA1 = Utilities.CleanHashData(reader.GetAttribute("sha1"), Constants.SHA1Length), + SHA256 = Utilities.CleanHashData(reader.GetAttribute("sha256"), Constants.SHA256Length), + SHA384 = Utilities.CleanHashData(reader.GetAttribute("sha384"), Constants.SHA384Length), + SHA512 = Utilities.CleanHashData(reader.GetAttribute("sha512"), Constants.SHA512Length), + ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), + Writable = Utilities.GetYesNo(reader.GetAttribute("writable")), + + AreaName = areaname, + AreaSize = areasize, + Features = features, + PartName = partname, + PartInterface = partinterface, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + disk.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(disk, clean, remUnicode); + + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + + return containsItems; + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public override bool WriteToFile(string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); + + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } + + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + + // Write out the header + WriteHeader(sw); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = this[key]; + + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); + + for (int index = 0; index < roms.Count; index++) + { + DatItem rom = roms[index]; + + // There are apparently times when a null rom can skip by, skip them + if (rom.Name == null || rom.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } + + // If we have a different game and we're not at the start of the list, output the end of last item + if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteEndGame(sw); + } + + // If we have a new game, output the beginning of the new item + if (lastgame == null || lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteStartGame(sw, rom); + } + + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.ItemType == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + + lastgame = rom.MachineName; + continue; + } + + // Now, output the rom data + WriteDatItem(sw, rom, ignoreblanks); + + // Set the new data to compare against + lastgame = rom.MachineName; + } + } + + // Write the file footer out + WriteFooter(sw); + + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteHeader(StreamWriter sw) + { + try + { + string header = "\n" + + "\n\n" + + "\n\n"; + + // Write the header out + sw.Write(header); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if the data was written, false on error + private bool WriteStartGame(StreamWriter sw, DatItem rom) + { + try + { + // No game should start with a path separator + if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) + { + rom.MachineName = rom.MachineName.Substring(1); + } + + string state = "\t\n" : "") + + (!ExcludeFields[(int)Field.Description] ? "\t\t" + HttpUtility.HtmlEncode(rom.MachineDescription) + "\n" : "") + + (!ExcludeFields[(int)Field.Year] && rom.Year != null ? "\t\t" + HttpUtility.HtmlEncode(rom.Year) + "\n" : "") + + (!ExcludeFields[(int)Field.Publisher] && rom.Publisher != null ? "\t\t" + HttpUtility.HtmlEncode(rom.Publisher) + "\n" : ""); + + if (!ExcludeFields[(int)Field.Infos]) + { + foreach (Tuple kvp in rom.Infos) + { + state += "\t\t\n"; + } + } + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteEndGame(StreamWriter sw) + { + try + { + string state = "\t\n\n"; + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the data was written, false on error + private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.ItemType == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + + // Pre-process the item name + ProcessItemName(rom, true); + + state += "\t\t\n"; + + if (!ExcludeFields[(int)Field.Features]) + { + foreach (Tuple kvp in rom.Features) + { + state += "\t\t\t\n"; + } + } + + switch (rom.ItemType) + { + case ItemType.Disk: + state += "\t\t\t\n" + + "\t\t\t\t\n" + + "\t\t\t\n"; + break; + case ItemType.Rom: + state += "\t\t\t\n" + + "\t\t\t\t\n" + + "\t\t\t\n"; + break; + } + + state += "\t\t\n"; + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT footer using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteFooter(StreamWriter sw) + { + try + { + string footer = "\t\n\n\n"; + + // Write the footer out + sw.Write(footer); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + } }