From 2209d0a13bfb0d4e849fe18d625e64d2de545f1a Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 9 Oct 2017 18:04:49 -0700 Subject: [PATCH] [DatFiles/] Create separate classes for each type of DAT This DOES group some kinds of DAT due to their inherently similar nature. TSV and CSV are under the same "SeparatedValue" umbrella, and all of the SFV, MD5, SHA1, etc are under Hashfile because they're nearly identical. This is just the first stage change, making everything static and making them reference the DatFile separately. --- SabreTools.Library/DatFiles/AttractMode.cs | 269 ++ SabreTools.Library/DatFiles/ClrMamePro.cs | 939 ++++++ .../DatFiles/DatFile.Parsers.cs | 2927 ----------------- .../DatFiles/DatFile.Writers.cs | 1664 ---------- SabreTools.Library/DatFiles/DatFile.cs | 546 ++- SabreTools.Library/DatFiles/DosCenter.cs | 322 ++ SabreTools.Library/DatFiles/Hashfile.cs | 270 ++ SabreTools.Library/DatFiles/Listroms.cs | 508 +++ SabreTools.Library/DatFiles/Logiqx.cs | 1508 +++++++++ SabreTools.Library/DatFiles/Missfile.cs | 311 ++ SabreTools.Library/DatFiles/OfflineList.cs | 357 ++ SabreTools.Library/DatFiles/RomCenter.cs | 371 +++ SabreTools.Library/DatFiles/SabreDat.cs | 451 +++ SabreTools.Library/DatFiles/SeparatedValue.cs | 612 ++++ SabreTools.Library/DatFiles/SoftwareList.cs | 403 +++ SabreTools.Library/SabreTools.Library.csproj | 14 +- 16 files changed, 6878 insertions(+), 4594 deletions(-) create mode 100644 SabreTools.Library/DatFiles/AttractMode.cs create mode 100644 SabreTools.Library/DatFiles/ClrMamePro.cs delete mode 100644 SabreTools.Library/DatFiles/DatFile.Parsers.cs delete mode 100644 SabreTools.Library/DatFiles/DatFile.Writers.cs create mode 100644 SabreTools.Library/DatFiles/DosCenter.cs create mode 100644 SabreTools.Library/DatFiles/Hashfile.cs create mode 100644 SabreTools.Library/DatFiles/Listroms.cs create mode 100644 SabreTools.Library/DatFiles/Logiqx.cs create mode 100644 SabreTools.Library/DatFiles/Missfile.cs create mode 100644 SabreTools.Library/DatFiles/OfflineList.cs create mode 100644 SabreTools.Library/DatFiles/RomCenter.cs create mode 100644 SabreTools.Library/DatFiles/SabreDat.cs create mode 100644 SabreTools.Library/DatFiles/SeparatedValue.cs create mode 100644 SabreTools.Library/DatFiles/SoftwareList.cs diff --git a/SabreTools.Library/DatFiles/AttractMode.cs b/SabreTools.Library/DatFiles/AttractMode.cs new file mode 100644 index 00000000..4a609dec --- /dev/null +++ b/SabreTools.Library/DatFiles/AttractMode.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using SabreTools.Library.Data; +using SabreTools.Library.Items; +using SabreTools.Library.Tools; + +#if MONO +using System.IO; +#else +using Alphaleonis.Win32.Filesystem; + +using FileStream = System.IO.FileStream; +using StreamReader = System.IO.StreamReader; +using StreamWriter = System.IO.StreamWriter; +#endif +using NaturalSort; + +namespace SabreTools.Library.DatFiles +{ + /// + /// Represents parsing and writing of an AttractMode DAT + /// + public class AttractMode + { + /// + /// Parse an AttractMode DAT and return all found games within + /// + /// DatFile to populate with the read information + /// 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 static void Parse( + DatFile datFile, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Open a file reader + Encoding enc = Style.GetEncoding(filename); + StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc); + + 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 + */ + + string[] gameinfo = line.Split(';'); + + 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], + }; + + // Now process and add the rom + datFile.ParseAddHelper(rom, clean, remUnicode); + } + + sr.Dispose(); + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// DatFile to write out from + /// Name of the file to write to + /// True if the DAT was written correctly, false otherwise + public static bool WriteToFile(DatFile datFile, string outfile) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = FileTools.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(true)); + + // 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 = datFile.Keys.ToList(); + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = datFile[key]; + + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); + + 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; + } + + // If we have a new game, output the beginning of the new item + if (lastgame == null || lastgame.ToLowerInvariant() != item.MachineName.ToLowerInvariant()) + { + WriteStartGame(datFile, sw, item); + } + + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (item.Type == 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; + ((Rom)item).CRC = ((Rom)item).CRC == "null" ? Constants.CRCZero : null; + ((Rom)item).MD5 = ((Rom)item).MD5 == "null" ? Constants.MD5Zero : null; + ((Rom)item).SHA1 = ((Rom)item).SHA1 == "null" ? Constants.SHA1Zero : null; + ((Rom)item).SHA256 = ((Rom)item).SHA256 == "null" ? Constants.SHA256Zero : null; + ((Rom)item).SHA384 = ((Rom)item).SHA384 == "null" ? Constants.SHA384Zero : null; + ((Rom)item).SHA512 = ((Rom)item).SHA512 == "null" ? Constants.SHA512Zero : null; + } + + // 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; + } + + return true; + } + + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private static 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; + } + + return true; + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// DatFile to write out from + /// StreamWriter to output to + /// RomData object to be output + /// True if the data was written, false on error + private static bool WriteStartGame(DatFile datFile, 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.MachineName + ";" + + rom.MachineDescription + ";" + + datFile.FileName + ";" + + rom.CloneOf + ";" + + rom.Year + ";" + + rom.Manufacturer + ";" + /* + rom.Category */ + ";" + /* + rom.Players */ + ";" + /* + rom.Rotation */ + ";" + /* + rom.Control */ + ";" + /* + rom.Status */ + ";" + /* + rom.DisplayCount */ + ";" + /* + rom.DisplayType */ + ";" + /* + rom.AltRomname */ + ";" + /* + rom.AltTitle */ + ";" + + rom.Comment + ";" + /* + rom.Buttons */ + "\n"; + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + } +} diff --git a/SabreTools.Library/DatFiles/ClrMamePro.cs b/SabreTools.Library/DatFiles/ClrMamePro.cs new file mode 100644 index 00000000..f26752d7 --- /dev/null +++ b/SabreTools.Library/DatFiles/ClrMamePro.cs @@ -0,0 +1,939 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +using SabreTools.Library.Data; +using SabreTools.Library.Items; +using SabreTools.Library.Tools; + +#if MONO +using System.IO; +#else +using Alphaleonis.Win32.Filesystem; + +using FileStream = System.IO.FileStream; +using StreamReader = System.IO.StreamReader; +using StreamWriter = System.IO.StreamWriter; +#endif +using NaturalSort; + +namespace SabreTools.Library.DatFiles +{ + /// + /// Represents parsing and writing of a ClrMamePro DAT + /// + public class ClrMamePro + { + /// + /// Parse a ClrMamePro DAT and return all found games and roms within + /// + /// DatFile to populate with the read information + /// 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 static void Parse( + DatFile datFile, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Open a file reader + Encoding enc = Style.GetEncoding(filename); + StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc); + + bool block = false, superdat = false; + string blockname = "", tempgamename = "", gamedesc = "", cloneof = "", + romof = "", sampleof = "", year = "", manufacturer = ""; + 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; + + if (gc[1].Value == "clrmamepro" || gc[1].Value == "romvault" || gc[1].Value.ToLowerInvariant() == "doscenter") + { + blockname = "header"; + } + + block = true; + } + + // If the line is a rom-like item and we're in a block + else if ((line.Trim().StartsWith("rom (") + || line.Trim().StartsWith("disk (") + || line.Trim().StartsWith("file (") + || (line.Trim().StartsWith("sample") && !line.Trim().StartsWith("sampleof")) + ) && block) + { + 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; + switch (temptype) + { + case ItemType.Archive: + item = new Archive(); + break; + case ItemType.BiosSet: + item = new BiosSet(); + break; + case ItemType.Disk: + item = new Disk(); + break; + case ItemType.Release: + item = new Release(); + break; + case ItemType.Sample: + item = new Sample(); + break; + case ItemType.Rom: + default: + item = new Rom(); + break; + } + + // Then populate it with information + item.MachineName = tempgamename; + item.MachineDescription = gamedesc; + item.CloneOf = cloneof; + item.RomOf = romof; + item.SampleOf = sampleof; + item.Manufacturer = manufacturer; + item.Year = year; + + item.SystemID = sysid; + 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 + datFile.ParseAddHelper(item, clean, remUnicode); + + continue; + } + + // Get the line split by spaces and quotes + string[] gc = Style.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 < gc.Length; i++) + { + // Names are not quoted, for some stupid reason + if (gc[i] == "name") + { + // Get the name in order until we find the next flag + while (++i < gc.Length && gc[i] != "size" && gc[i] != "date" && gc[i] != "crc" && gc[i] != "md5" + && gc[i] != "sha1" && gc[i] != "sha256" && gc[i] != "sha384" && gc[i] != "sha512") + { + item.Name += " " + gc[i]; + } + + // Perform correction + item.Name = item.Name.TrimStart(); + i--; + } + + // Get the size from the next part + else if (gc[i] == "size") + { + long tempsize = -1; + if (!Int64.TryParse(gc[++i], out tempsize)) + { + tempsize = 0; + } + ((Rom)item).Size = tempsize; + } + + // Get the date from the next part + else if (gc[i] == "date") + { + ((Rom)item).Date = gc[++i].Replace("\"", "") + " " + gc[++i].Replace("\"", ""); + } + + // Get the CRC from the next part + else if (gc[i] == "crc") + { + ((Rom)item).CRC = gc[++i].Replace("\"", "").ToLowerInvariant(); + } + + // Get the MD5 from the next part + else if (gc[i] == "md5") + { + ((Rom)item).MD5 = gc[++i].Replace("\"", "").ToLowerInvariant(); + } + + // Get the SHA1 from the next part + else if (gc[i] == "sha1") + { + ((Rom)item).SHA1 = gc[++i].Replace("\"", "").ToLowerInvariant(); + } + + // Get the SHA256 from the next part + else if (gc[i] == "sha256") + { + ((Rom)item).SHA256 = gc[++i].Replace("\"", "").ToLowerInvariant(); + } + + // Get the SHA384 from the next part + else if (gc[i] == "sha384") + { + ((Rom)item).SHA384 = gc[++i].Replace("\"", "").ToLowerInvariant(); + } + + // Get the SHA512 from the next part + else if (gc[i] == "sha512") + { + ((Rom)item).SHA512 = gc[++i].Replace("\"", "").ToLowerInvariant(); + } + } + + // Now process and add the rom + datFile.ParseAddHelper(item, clean, remUnicode); + continue; + } + + // Loop over all attributes normally and add them if possible + for (int i = 0; i < gc.Length; i++) + { + // Look at the current item and use it if possible + string quoteless = gc[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": + if (item.Type == ItemType.Rom) + { + ((Rom)item).ItemStatus = ItemStatus.BadDump; + } + else if (item.Type == ItemType.Disk) + { + ((Disk)item).ItemStatus = ItemStatus.BadDump; + } + break; + case "good": + if (item.Type == ItemType.Rom) + { + ((Rom)item).ItemStatus = ItemStatus.Good; + } + else if (item.Type == ItemType.Disk) + { + ((Disk)item).ItemStatus = ItemStatus.Good; + } + break; + case "nodump": + if (item.Type == ItemType.Rom) + { + ((Rom)item).ItemStatus = ItemStatus.Nodump; + } + else if (item.Type == ItemType.Disk) + { + ((Disk)item).ItemStatus = ItemStatus.Nodump; + } + break; + case "verified": + if (item.Type == ItemType.Rom) + { + ((Rom)item).ItemStatus = ItemStatus.Verified; + } + else if (item.Type == ItemType.Disk) + { + ((Disk)item).ItemStatus = ItemStatus.Verified; + } + break; + + // Regular attributes + case "name": + quoteless = gc[++i].Replace("\"", ""); + item.Name = quoteless; + break; + case "size": + if (item.Type == ItemType.Rom) + { + quoteless = gc[++i].Replace("\"", ""); + if (Int64.TryParse(quoteless, out long size)) + { + ((Rom)item).Size = size; + } + else + { + ((Rom)item).Size = -1; + } + } + break; + case "crc": + if (item.Type == ItemType.Rom) + { + quoteless = gc[++i].Replace("\"", ""); + ((Rom)item).CRC = quoteless.ToLowerInvariant(); + } + break; + case "md5": + if (item.Type == ItemType.Rom) + { + quoteless = gc[++i].Replace("\"", ""); + ((Rom)item).MD5 = quoteless.ToLowerInvariant(); + } + else if (item.Type == ItemType.Disk) + { + i++; + quoteless = gc[i].Replace("\"", ""); + ((Disk)item).MD5 = quoteless.ToLowerInvariant(); + } + break; + case "sha1": + if (item.Type == ItemType.Rom) + { + quoteless = gc[++i].Replace("\"", ""); + ((Rom)item).SHA1 = quoteless.ToLowerInvariant(); + } + else if (item.Type == ItemType.Disk) + { + quoteless = gc[++i].Replace("\"", ""); + ((Disk)item).SHA1 = quoteless.ToLowerInvariant(); + } + break; + case "sha256": + if (item.Type == ItemType.Rom) + { + quoteless = gc[++i].Replace("\"", ""); + ((Rom)item).SHA256 = quoteless.ToLowerInvariant(); + } + else if (item.Type == ItemType.Disk) + { + quoteless = gc[++i].Replace("\"", ""); + ((Disk)item).SHA256 = quoteless.ToLowerInvariant(); + } + break; + case "sha384": + if (item.Type == ItemType.Rom) + { + quoteless = gc[++i].Replace("\"", ""); + ((Rom)item).SHA384 = quoteless.ToLowerInvariant(); + } + else if (item.Type == ItemType.Disk) + { + quoteless = gc[++i].Replace("\"", ""); + ((Disk)item).SHA384 = quoteless.ToLowerInvariant(); + } + break; + case "sha512": + if (item.Type == ItemType.Rom) + { + quoteless = gc[++i].Replace("\"", ""); + ((Rom)item).SHA512 = quoteless.ToLowerInvariant(); + } + else if (item.Type == ItemType.Disk) + { + quoteless = gc[++i].Replace("\"", ""); + ((Disk)item).SHA512 = quoteless.ToLowerInvariant(); + } + break; + case "status": + case "flags": + quoteless = gc[++i].Replace("\"", ""); + if (quoteless.ToLowerInvariant() == "good") + { + if (item.Type == ItemType.Rom) + { + ((Rom)item).ItemStatus = ItemStatus.Good; + } + else if (item.Type == ItemType.Disk) + { + ((Disk)item).ItemStatus = ItemStatus.Good; + } + } + else if (quoteless.ToLowerInvariant() == "baddump") + { + if (item.Type == ItemType.Rom) + { + ((Rom)item).ItemStatus = ItemStatus.BadDump; + } + else if (item.Type == ItemType.Disk) + { + ((Disk)item).ItemStatus = ItemStatus.BadDump; + } + } + else if (quoteless.ToLowerInvariant() == "nodump") + { + if (item.Type == ItemType.Rom) + { + ((Rom)item).ItemStatus = ItemStatus.Nodump; + } + else if (item.Type == ItemType.Disk) + { + ((Disk)item).ItemStatus = ItemStatus.Nodump; + } + } + else if (quoteless.ToLowerInvariant() == "verified") + { + if (item.Type == ItemType.Rom) + { + ((Rom)item).ItemStatus = ItemStatus.Verified; + } + else if (item.Type == ItemType.Disk) + { + ((Disk)item).ItemStatus = ItemStatus.Verified; + } + } + break; + case "date": + if (item.Type == ItemType.Rom) + { + // If we have quotes in the next item, assume only one item + if (gc[i + 1].Contains("\"")) + { + quoteless = gc[++i].Replace("\"", ""); + } + // Otherwise, we assume we need to read the next two items + else + { + quoteless = gc[++i].Replace("\"", "") + " " + gc[++i].Replace("\"", ""); + } + ((Rom)item).Date = quoteless; + } + break; + } + } + + // Now process and add the rom + datFile.ParseAddHelper(item, clean, remUnicode); + } + + // If the line is anything but a rom or disk and we're in a block + else if (Regex.IsMatch(line, Constants.ItemPatternCMP) && block) + { + GroupCollection gc = Regex.Match(line, Constants.ItemPatternCMP).Groups; + + if (blockname != "header") + { + string itemval = gc[2].Value.Replace("\"", ""); + switch (gc[1].Value) + { + case "name": + tempgamename = (itemval.ToLowerInvariant().EndsWith(".zip") ? itemval.Remove(itemval.Length - 4) : itemval); + break; + case "description": + gamedesc = itemval; + break; + case "romof": + romof = itemval; + break; + case "cloneof": + cloneof = itemval; + break; + case "year": + year = itemval; + break; + case "manufacturer": + manufacturer = itemval; + break; + case "sampleof": + sampleof = itemval; + break; + } + } + else + { + string itemval = gc[2].Value.Replace("\"", ""); + + if (line.Trim().StartsWith("Name:")) + { + datFile.Name = (String.IsNullOrEmpty(datFile.Name) ? line.Substring(6) : datFile.Name); + superdat = superdat || itemval.Contains(" - SuperDAT"); + if (keep && superdat) + { + datFile.Type = (String.IsNullOrEmpty(datFile.Type) ? "SuperDAT" : datFile.Type); + } + continue; + } + + switch (gc[1].Value) + { + case "name": + case "Name:": + datFile.Name = (String.IsNullOrEmpty(datFile.Name) ? itemval : datFile.Name); + superdat = superdat || itemval.Contains(" - SuperDAT"); + if (keep && superdat) + { + datFile.Type = (String.IsNullOrEmpty(datFile.Type) ? "SuperDAT" : datFile.Type); + } + break; + case "description": + case "Description:": + datFile.Description = (String.IsNullOrEmpty(datFile.Description) ? itemval : datFile.Description); + break; + case "rootdir": + datFile.RootDir = (String.IsNullOrEmpty(datFile.RootDir) ? itemval : datFile.RootDir); + break; + case "category": + datFile.Category = (String.IsNullOrEmpty(datFile.Category) ? itemval : datFile.Category); + break; + case "version": + case "Version:": + datFile.Version = (String.IsNullOrEmpty(datFile.Version) ? itemval : datFile.Version); + break; + case "date": + case "Date:": + datFile.Date = (String.IsNullOrEmpty(datFile.Date) ? itemval : datFile.Date); + break; + case "author": + case "Author:": + datFile.Author = (String.IsNullOrEmpty(datFile.Author) ? itemval : datFile.Author); + break; + case "email": + datFile.Email = (String.IsNullOrEmpty(datFile.Email) ? itemval : datFile.Email); + break; + case "homepage": + case "Homepage:": + datFile.Homepage = (String.IsNullOrEmpty(datFile.Homepage) ? itemval : datFile.Homepage); + break; + case "url": + datFile.Url = (String.IsNullOrEmpty(datFile.Url) ? itemval : datFile.Url); + break; + case "comment": + case "Comment:": + datFile.Comment = (String.IsNullOrEmpty(datFile.Comment) ? itemval : datFile.Comment); + break; + case "header": + datFile.Header = (String.IsNullOrEmpty(datFile.Header) ? itemval : datFile.Header); + break; + case "type": + datFile.Type = (String.IsNullOrEmpty(datFile.Type) ? itemval : datFile.Type); + superdat = superdat || itemval.Contains("SuperDAT"); + break; + case "forcemerging": + if (datFile.ForceMerging == ForceMerging.None) + { + switch (itemval) + { + case "none": + datFile.ForceMerging = ForceMerging.None; + break; + case "split": + datFile.ForceMerging = ForceMerging.Split; + break; + case "merged": + datFile.ForceMerging = ForceMerging.Merged; + break; + case "nonmerged": + datFile.ForceMerging = ForceMerging.NonMerged; + break; + case "full": + datFile.ForceMerging = ForceMerging.Full; + break; + } + } + break; + case "forcezipping": + if (datFile.ForcePacking == ForcePacking.None) + { + switch (itemval) + { + case "yes": + datFile.ForcePacking = ForcePacking.Zip; + break; + case "no": + datFile.ForcePacking = ForcePacking.Unzip; + break; + } + } + break; + case "forcepacking": + if (datFile.ForcePacking == ForcePacking.None) + { + switch (itemval) + { + case "zip": + datFile.ForcePacking = ForcePacking.Zip; + break; + case "unzip": + datFile.ForcePacking = ForcePacking.Unzip; + break; + } + } + break; + } + } + } + + // If we find an end bracket that's not associated with anything else, the block is done + else if (Regex.IsMatch(line, Constants.EndPatternCMP) && block) + { + block = false; + blockname = ""; tempgamename = ""; gamedesc = ""; cloneof = ""; + romof = ""; sampleof = ""; year = ""; manufacturer = ""; + } + } + + sr.Dispose(); + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// DatFile to write out from + /// 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 static bool WriteToFile(DatFile datFile, string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = FileTools.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(true)); + + // Write out the header + WriteHeader(datFile, sw); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = datFile.Keys.ToList(); + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = datFile[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(datFile, sw, rom); + } + + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.Type == 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 + WriteRomData(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 + /// + /// DatFile to write out from + /// StreamWriter to output to + /// True if the data was written, false on error + private static bool WriteHeader(DatFile datFile, StreamWriter sw) + { + try + { + string header = "clrmamepro (\n" + + "\tname \"" + datFile.Name + "\"\n" + + "\tdescription \"" + datFile.Description + "\"\n" + + (!String.IsNullOrEmpty(datFile.Category) ? "\tcategory \"" + datFile.Category + "\"\n" : "") + + "\tversion \"" + datFile.Version + "\"\n" + + (!String.IsNullOrEmpty(datFile.Date) ? "\tdate \"" + datFile.Date + "\"\n" : "") + + "\tauthor \"" + datFile.Author + "\"\n" + + (!String.IsNullOrEmpty(datFile.Email) ? "\temail \"" + datFile.Email + "\"\n" : "") + + (!String.IsNullOrEmpty(datFile.Homepage) ? "\thomepage \"" + datFile.Homepage + "\"\n" : "") + + (!String.IsNullOrEmpty(datFile.Url) ? "\turl \"" + datFile.Url + "\"\n" : "") + + (!String.IsNullOrEmpty(datFile.Comment) ? "\tcomment \"" + datFile.Comment + "\"\n" : "") + + (datFile.ForcePacking == ForcePacking.Unzip ? "\tforcezipping no\n" : "") + + (datFile.ForcePacking == ForcePacking.Zip ? "\tforcezipping yes\n" : "") + + (datFile.ForceMerging == ForceMerging.Full ? "\tforcemerging full\n" : "") + + (datFile.ForceMerging == ForceMerging.Split ? "\tforcemerging split\n" : "") + + (datFile.ForceMerging == ForceMerging.Merged ? "\tforcemerging merged\n" : "") + + (datFile.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 + /// + /// DatFile to write out from + /// StreamWriter to output to + /// RomData object to be output + /// True if the data was written, false on error + private static bool WriteStartGame(DatFile datFile, 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 \"" + rom.MachineName + "\"\n" + + (datFile.ExcludeOf ? "" : + (String.IsNullOrEmpty(rom.RomOf) ? "" : "\tromof \"" + rom.RomOf + "\"\n") + + (String.IsNullOrEmpty(rom.CloneOf) ? "" : "\tcloneof \"" + rom.CloneOf + "\"\n") + + (String.IsNullOrEmpty(rom.SampleOf) ? "" : "\tsampleof \"" + rom.SampleOf + "\"\n") + ) + + "\tdescription \"" + (String.IsNullOrEmpty(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription) + "\"\n" + + (String.IsNullOrEmpty(rom.Year) ? "" : "\tyear " + rom.Year + "\n") + + (String.IsNullOrEmpty(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 + /// RomData object to be output + /// True if the data was written, false on error + private static bool WriteEndGame(StreamWriter sw, DatItem rom) + { + try + { + string state = (String.IsNullOrEmpty(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 RomData using the supplied StreamWriter + /// + /// DatFile to write out from + /// StreamWriter to output to + /// RomData 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 static bool WriteRomData(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.Type == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + switch (rom.Type) + { + case ItemType.Archive: + state += "\tarchive ( name\"" + rom.Name + "\"" + + " )\n"; + break; + case ItemType.BiosSet: + state += "\tbiosset ( name\"" + rom.Name + "\"" + + (!String.IsNullOrEmpty(((BiosSet)rom).Description) ? " description \"" + ((BiosSet)rom).Description + "\"" : "") + + (((BiosSet)rom).Default != null + ? "default " + ((BiosSet)rom).Default.ToString().ToLowerInvariant() + : "") + + " )\n"; + break; + case ItemType.Disk: + state += "\tdisk ( name \"" + rom.Name + "\"" + + (!String.IsNullOrEmpty(((Disk)rom).MD5) ? " md5 " + ((Disk)rom).MD5.ToLowerInvariant() : "") + + (!String.IsNullOrEmpty(((Disk)rom).SHA1) ? " sha1 " + ((Disk)rom).SHA1.ToLowerInvariant() : "") + + (!String.IsNullOrEmpty(((Disk)rom).SHA256) ? " sha256 " + ((Disk)rom).SHA256.ToLowerInvariant() : "") + + (!String.IsNullOrEmpty(((Disk)rom).SHA384) ? " sha384 " + ((Disk)rom).SHA384.ToLowerInvariant() : "") + + (!String.IsNullOrEmpty(((Disk)rom).SHA512) ? " sha512 " + ((Disk)rom).SHA512.ToLowerInvariant() : "") + + (((Disk)rom).ItemStatus != ItemStatus.None ? " flags " + ((Disk)rom).ItemStatus.ToString().ToLowerInvariant() : "") + + " )\n"; + break; + case ItemType.Release: + state += "\trelease ( name\"" + rom.Name + "\"" + + (!String.IsNullOrEmpty(((Release)rom).Region) ? " region \"" + ((Release)rom).Region + "\"" : "") + + (!String.IsNullOrEmpty(((Release)rom).Language) ? " language \"" + ((Release)rom).Language + "\"" : "") + + (!String.IsNullOrEmpty(((Release)rom).Date) ? " date \"" + ((Release)rom).Date + "\"" : "") + + (((Release)rom).Default != null + ? "default " + ((Release)rom).Default.ToString().ToLowerInvariant() + : "") + + " )\n"; + break; + case ItemType.Rom: + state += "\trom ( name \"" + rom.Name + "\"" + + (((Rom)rom).Size != -1 ? " size " + ((Rom)rom).Size : "") + + (!String.IsNullOrEmpty(((Rom)rom).CRC) ? " crc " + ((Rom)rom).CRC.ToLowerInvariant() : "") + + (!String.IsNullOrEmpty(((Rom)rom).MD5) ? " md5 " + ((Rom)rom).MD5.ToLowerInvariant() : "") + + (!String.IsNullOrEmpty(((Rom)rom).SHA1) ? " sha1 " + ((Rom)rom).SHA1.ToLowerInvariant() : "") + + (!String.IsNullOrEmpty(((Rom)rom).SHA256) ? " sha256 " + ((Rom)rom).SHA256.ToLowerInvariant() : "") + + (!String.IsNullOrEmpty(((Rom)rom).SHA384) ? " sha384 " + ((Rom)rom).SHA384.ToLowerInvariant() : "") + + (!String.IsNullOrEmpty(((Rom)rom).SHA512) ? " sha512 " + ((Rom)rom).SHA512.ToLowerInvariant() : "") + + (!String.IsNullOrEmpty(((Rom)rom).Date) ? " date \"" + ((Rom)rom).Date + "\"" : "") + + (((Rom)rom).ItemStatus != ItemStatus.None ? " flags " + ((Rom)rom).ItemStatus.ToString().ToLowerInvariant() : "") + + " )\n"; + break; + case ItemType.Sample: + state += "\tsample ( 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 static 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/DatFile.Parsers.cs b/SabreTools.Library/DatFiles/DatFile.Parsers.cs deleted file mode 100644 index be845247..00000000 --- a/SabreTools.Library/DatFiles/DatFile.Parsers.cs +++ /dev/null @@ -1,2927 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Xml; - -using SabreTools.Library.Data; -using SabreTools.Library.Items; -using SabreTools.Library.Tools; - -#if MONO -using System.IO; -#else -using Alphaleonis.Win32.Filesystem; - -using StreamReader = System.IO.StreamReader; -#endif - -namespace SabreTools.Library.DatFiles -{ - /// - /// Represents a format-agnostic DAT - /// - public partial class DatFile - { - #region Parsing - - /// - /// Parse a 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 - /// The DatData object representing found roms to this point - /// 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) - /// True if descriptions should be used as names, false otherwise (default) - /// True if original extension should be kept, false otherwise (default) - /// True if tags from the DAT should be used to merge the output, false otherwise (default) - public void Parse(string filename, int sysid, int srcid, bool keep = false, bool clean = false, - bool remUnicode = false, bool descAsName = false, bool keepext = false, bool useTags = false) - { - Parse(filename, sysid, srcid, SplitType.None, keep: keep, clean: clean, - remUnicode: remUnicode, descAsName: descAsName, keepext: keepext, useTags: useTags); - } - - /// - /// Parse a 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> - /// Type of the split that should be performed (split, merged, fully merged) - /// 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) - /// True if descriptions should be used as names, false otherwise (default) - /// True if original extension should be kept, false otherwise (default) - /// True if tags from the DAT should be used to merge the output, false otherwise (default) - public void Parse( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Rom renaming - SplitType splitType, - - // Miscellaneous - bool keep = false, - bool clean = false, - bool remUnicode = false, - bool descAsName = false, - bool keepext = false, - bool useTags = false) - { - // Check the file extension first as a safeguard - string ext = Path.GetExtension(filename).ToLowerInvariant(); - if (ext.StartsWith(".")) - { - ext = ext.Substring(1); - } - if (ext != "dat" && ext != "csv" && ext != "md5" && ext != "sfv" && ext != "sha1" && ext != "sha256" - && ext != "sha384" && ext != "sha512" && ext != "tsv" && ext != "txt" && ext != "xml") - { - return; - } - - // If the output filename isn't set already, get the internal filename - FileName = (String.IsNullOrEmpty(FileName) ? (keepext ? Path.GetFileName(filename) : Path.GetFileNameWithoutExtension(filename)) : FileName); - - // If the output type isn't set already, get the internal output type - DatFormat = (DatFormat == 0 ? FileTools.GetDatFormat(filename) : DatFormat); - - // Now parse the correct type of DAT - try - { - switch (FileTools.GetDatFormat(filename)) - { - case DatFormat.AttractMode: - ParseAttractMode(filename, sysid, srcid, keep, clean, remUnicode); - break; - case DatFormat.ClrMamePro: - case DatFormat.DOSCenter: - ParseClrMamePro(filename, sysid, srcid, keep, clean, remUnicode); - break; - case DatFormat.CSV: - ParseSeparatedValue(filename, sysid, srcid, ',', keep, clean, remUnicode); - break; - case DatFormat.Listroms: - ParseListroms(filename, sysid, srcid, keep, clean, remUnicode); - break; - case DatFormat.Logiqx: - case DatFormat.OfflineList: - case DatFormat.SabreDat: - case DatFormat.SoftwareList: - ParseXML(filename, sysid, srcid, keep, clean, remUnicode); - break; - case DatFormat.RedumpMD5: - ParseHashfile(filename, sysid, srcid, Hash.MD5, clean, remUnicode); - break; - case DatFormat.RedumpSFV: - ParseHashfile(filename, sysid, srcid, Hash.CRC, clean, remUnicode); - break; - case DatFormat.RedumpSHA1: - ParseHashfile(filename, sysid, srcid, Hash.SHA1, clean, remUnicode); - break; - case DatFormat.RedumpSHA256: - ParseHashfile(filename, sysid, srcid, Hash.SHA256, clean, remUnicode); - break; - case DatFormat.RedumpSHA384: - ParseHashfile(filename, sysid, srcid, Hash.SHA384, clean, remUnicode); - break; - case DatFormat.RedumpSHA512: - ParseHashfile(filename, sysid, srcid, Hash.SHA512, clean, remUnicode); - break; - case DatFormat.RomCenter: - ParseRomCenter(filename, sysid, srcid, clean, remUnicode); - break; - case DatFormat.TSV: - ParseSeparatedValue(filename, sysid, srcid, '\t', keep, clean, remUnicode); - break; - default: - return; - } - } - catch (Exception ex) - { - Globals.Logger.Error("Error with file '{0}': {1}", filename, ex); - } - - // If we want to use descriptions as names, update everything - if (descAsName) - { - MachineDescriptionToName(); - } - - // If we are using tags from the DAT, set the proper input for split type unless overridden - if (useTags && splitType == SplitType.None) - { - switch (ForceMerging) - { - case ForceMerging.None: - // No-op - break; - case ForceMerging.Split: - splitType = SplitType.Split; - break; - case ForceMerging.Merged: - splitType = SplitType.Merged; - break; - case ForceMerging.NonMerged: - splitType = SplitType.NonMerged; - break; - case ForceMerging.Full: - splitType = SplitType.FullNonMerged; - break; - } - } - - // Now we pre-process the DAT with the splitting/merging mode - switch (splitType) - { - case SplitType.None: - // No-op - break; - case SplitType.DeviceNonMerged: - CreateDeviceNonMergedSets(DedupeType.None); - break; - case SplitType.FullNonMerged: - CreateFullyNonMergedSets(DedupeType.None); - break; - case SplitType.NonMerged: - CreateNonMergedSets(DedupeType.None); - break; - case SplitType.Merged: - CreateMergedSets(DedupeType.None); - break; - case SplitType.Split: - CreateSplitSets(DedupeType.None); - break; - } - } - - /// - /// 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) - private void ParseAttractMode( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Open a file reader - Encoding enc = Style.GetEncoding(filename); - StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc); - - 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 - */ - - string[] gameinfo = line.Split(';'); - - 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], - }; - - // Now process and add the rom - ParseAddHelper(rom, clean, remUnicode); - } - - sr.Dispose(); - } - - /// - /// 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) - private void ParseClrMamePro( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Open a file reader - Encoding enc = Style.GetEncoding(filename); - StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc); - - bool block = false, superdat = false; - string blockname = "", tempgamename = "", gamedesc = "", cloneof = "", - romof = "", sampleof = "", year = "", manufacturer = ""; - 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; - - if (gc[1].Value == "clrmamepro" || gc[1].Value == "romvault" || gc[1].Value.ToLowerInvariant() == "doscenter") - { - blockname = "header"; - } - - block = true; - } - - // If the line is a rom-like item and we're in a block - else if ((line.Trim().StartsWith("rom (") - || line.Trim().StartsWith("disk (") - || line.Trim().StartsWith("file (") - || (line.Trim().StartsWith("sample") && !line.Trim().StartsWith("sampleof")) - ) && block) - { - 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; - switch (temptype) - { - case ItemType.Archive: - item = new Archive(); - break; - case ItemType.BiosSet: - item = new BiosSet(); - break; - case ItemType.Disk: - item = new Disk(); - break; - case ItemType.Release: - item = new Release(); - break; - case ItemType.Sample: - item = new Sample(); - break; - case ItemType.Rom: - default: - item = new Rom(); - break; - } - - // Then populate it with information - item.MachineName = tempgamename; - item.MachineDescription = gamedesc; - item.CloneOf = cloneof; - item.RomOf = romof; - item.SampleOf = sampleof; - item.Manufacturer = manufacturer; - item.Year = year; - - item.SystemID = sysid; - 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); - - continue; - } - - // Get the line split by spaces and quotes - string[] gc = Style.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 < gc.Length; i++) - { - // Names are not quoted, for some stupid reason - if (gc[i] == "name") - { - // Get the name in order until we find the next flag - while (++i < gc.Length && gc[i] != "size" && gc[i] != "date" && gc[i] != "crc" && gc[i] != "md5" - && gc[i] != "sha1" && gc[i] != "sha256" && gc[i] != "sha384" && gc[i] != "sha512") - { - item.Name += " " + gc[i]; - } - - // Perform correction - item.Name = item.Name.TrimStart(); - i--; - } - - // Get the size from the next part - else if (gc[i] == "size") - { - long tempsize = -1; - if (!Int64.TryParse(gc[++i], out tempsize)) - { - tempsize = 0; - } - ((Rom)item).Size = tempsize; - } - - // Get the date from the next part - else if (gc[i] == "date") - { - ((Rom)item).Date = gc[++i].Replace("\"", "") + " " + gc[++i].Replace("\"", ""); - } - - // Get the CRC from the next part - else if (gc[i] == "crc") - { - ((Rom)item).CRC = gc[++i].Replace("\"", "").ToLowerInvariant(); - } - - // Get the MD5 from the next part - else if (gc[i] == "md5") - { - ((Rom)item).MD5 = gc[++i].Replace("\"", "").ToLowerInvariant(); - } - - // Get the SHA1 from the next part - else if (gc[i] == "sha1") - { - ((Rom)item).SHA1 = gc[++i].Replace("\"", "").ToLowerInvariant(); - } - - // Get the SHA256 from the next part - else if (gc[i] == "sha256") - { - ((Rom)item).SHA256 = gc[++i].Replace("\"", "").ToLowerInvariant(); - } - - // Get the SHA384 from the next part - else if (gc[i] == "sha384") - { - ((Rom)item).SHA384 = gc[++i].Replace("\"", "").ToLowerInvariant(); - } - - // Get the SHA512 from the next part - else if (gc[i] == "sha512") - { - ((Rom)item).SHA512 = gc[++i].Replace("\"", "").ToLowerInvariant(); - } - } - - // Now process and add the rom - ParseAddHelper(item, clean, remUnicode); - continue; - } - - // Loop over all attributes normally and add them if possible - for (int i = 0; i < gc.Length; i++) - { - // Look at the current item and use it if possible - string quoteless = gc[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": - if (item.Type == ItemType.Rom) - { - ((Rom)item).ItemStatus = ItemStatus.BadDump; - } - else if (item.Type == ItemType.Disk) - { - ((Disk)item).ItemStatus = ItemStatus.BadDump; - } - break; - case "good": - if (item.Type == ItemType.Rom) - { - ((Rom)item).ItemStatus = ItemStatus.Good; - } - else if (item.Type == ItemType.Disk) - { - ((Disk)item).ItemStatus = ItemStatus.Good; - } - break; - case "nodump": - if (item.Type == ItemType.Rom) - { - ((Rom)item).ItemStatus = ItemStatus.Nodump; - } - else if (item.Type == ItemType.Disk) - { - ((Disk)item).ItemStatus = ItemStatus.Nodump; - } - break; - case "verified": - if (item.Type == ItemType.Rom) - { - ((Rom)item).ItemStatus = ItemStatus.Verified; - } - else if (item.Type == ItemType.Disk) - { - ((Disk)item).ItemStatus = ItemStatus.Verified; - } - break; - - // Regular attributes - case "name": - quoteless = gc[++i].Replace("\"", ""); - item.Name = quoteless; - break; - case "size": - if (item.Type == ItemType.Rom) - { - quoteless = gc[++i].Replace("\"", ""); - if (Int64.TryParse(quoteless, out long size)) - { - ((Rom)item).Size = size; - } - else - { - ((Rom)item).Size = -1; - } - } - break; - case "crc": - if (item.Type == ItemType.Rom) - { - quoteless = gc[++i].Replace("\"", ""); - ((Rom)item).CRC = quoteless.ToLowerInvariant(); - } - break; - case "md5": - if (item.Type == ItemType.Rom) - { - quoteless = gc[++i].Replace("\"", ""); - ((Rom)item).MD5 = quoteless.ToLowerInvariant(); - } - else if (item.Type == ItemType.Disk) - { - i++; - quoteless = gc[i].Replace("\"", ""); - ((Disk)item).MD5 = quoteless.ToLowerInvariant(); - } - break; - case "sha1": - if (item.Type == ItemType.Rom) - { - quoteless = gc[++i].Replace("\"", ""); - ((Rom)item).SHA1 = quoteless.ToLowerInvariant(); - } - else if (item.Type == ItemType.Disk) - { - quoteless = gc[++i].Replace("\"", ""); - ((Disk)item).SHA1 = quoteless.ToLowerInvariant(); - } - break; - case "sha256": - if (item.Type == ItemType.Rom) - { - quoteless = gc[++i].Replace("\"", ""); - ((Rom)item).SHA256 = quoteless.ToLowerInvariant(); - } - else if (item.Type == ItemType.Disk) - { - quoteless = gc[++i].Replace("\"", ""); - ((Disk)item).SHA256 = quoteless.ToLowerInvariant(); - } - break; - case "sha384": - if (item.Type == ItemType.Rom) - { - quoteless = gc[++i].Replace("\"", ""); - ((Rom)item).SHA384 = quoteless.ToLowerInvariant(); - } - else if (item.Type == ItemType.Disk) - { - quoteless = gc[++i].Replace("\"", ""); - ((Disk)item).SHA384 = quoteless.ToLowerInvariant(); - } - break; - case "sha512": - if (item.Type == ItemType.Rom) - { - quoteless = gc[++i].Replace("\"", ""); - ((Rom)item).SHA512 = quoteless.ToLowerInvariant(); - } - else if (item.Type == ItemType.Disk) - { - quoteless = gc[++i].Replace("\"", ""); - ((Disk)item).SHA512 = quoteless.ToLowerInvariant(); - } - break; - case "status": - case "flags": - quoteless = gc[++i].Replace("\"", ""); - if (quoteless.ToLowerInvariant() == "good") - { - if (item.Type == ItemType.Rom) - { - ((Rom)item).ItemStatus = ItemStatus.Good; - } - else if (item.Type == ItemType.Disk) - { - ((Disk)item).ItemStatus = ItemStatus.Good; - } - } - else if (quoteless.ToLowerInvariant() == "baddump") - { - if (item.Type == ItemType.Rom) - { - ((Rom)item).ItemStatus = ItemStatus.BadDump; - } - else if (item.Type == ItemType.Disk) - { - ((Disk)item).ItemStatus = ItemStatus.BadDump; - } - } - else if (quoteless.ToLowerInvariant() == "nodump") - { - if (item.Type == ItemType.Rom) - { - ((Rom)item).ItemStatus = ItemStatus.Nodump; - } - else if (item.Type == ItemType.Disk) - { - ((Disk)item).ItemStatus = ItemStatus.Nodump; - } - } - else if (quoteless.ToLowerInvariant() == "verified") - { - if (item.Type == ItemType.Rom) - { - ((Rom)item).ItemStatus = ItemStatus.Verified; - } - else if (item.Type == ItemType.Disk) - { - ((Disk)item).ItemStatus = ItemStatus.Verified; - } - } - break; - case "date": - if (item.Type == ItemType.Rom) - { - // If we have quotes in the next item, assume only one item - if (gc[i + 1].Contains("\"")) - { - quoteless = gc[++i].Replace("\"", ""); - } - // Otherwise, we assume we need to read the next two items - else - { - quoteless = gc[++i].Replace("\"", "") + " " + gc[++i].Replace("\"", ""); - } - ((Rom)item).Date = quoteless; - } - break; - } - } - - // Now process and add the rom - ParseAddHelper(item, clean, remUnicode); - } - - // If the line is anything but a rom or disk and we're in a block - else if (Regex.IsMatch(line, Constants.ItemPatternCMP) && block) - { - GroupCollection gc = Regex.Match(line, Constants.ItemPatternCMP).Groups; - - if (blockname != "header") - { - string itemval = gc[2].Value.Replace("\"", ""); - switch (gc[1].Value) - { - case "name": - tempgamename = (itemval.ToLowerInvariant().EndsWith(".zip") ? itemval.Remove(itemval.Length - 4) : itemval); - break; - case "description": - gamedesc = itemval; - break; - case "romof": - romof = itemval; - break; - case "cloneof": - cloneof = itemval; - break; - case "year": - year = itemval; - break; - case "manufacturer": - manufacturer = itemval; - break; - case "sampleof": - sampleof = itemval; - break; - } - } - else - { - string itemval = gc[2].Value.Replace("\"", ""); - - if (line.Trim().StartsWith("Name:")) - { - Name = (String.IsNullOrEmpty(Name) ? line.Substring(6) : Name); - superdat = superdat || itemval.Contains(" - SuperDAT"); - if (keep && superdat) - { - Type = (String.IsNullOrEmpty(Type) ? "SuperDAT" : Type); - } - continue; - } - - switch (gc[1].Value) - { - case "name": - case "Name:": - Name = (String.IsNullOrEmpty(Name) ? itemval : Name); - superdat = superdat || itemval.Contains(" - SuperDAT"); - if (keep && superdat) - { - Type = (String.IsNullOrEmpty(Type) ? "SuperDAT" : Type); - } - break; - case "description": - case "Description:": - Description = (String.IsNullOrEmpty(Description) ? itemval : Description); - break; - case "rootdir": - RootDir = (String.IsNullOrEmpty(RootDir) ? itemval : RootDir); - break; - case "category": - Category = (String.IsNullOrEmpty(Category) ? itemval : Category); - break; - case "version": - case "Version:": - Version = (String.IsNullOrEmpty(Version) ? itemval : Version); - break; - case "date": - case "Date:": - Date = (String.IsNullOrEmpty(Date) ? itemval : Date); - break; - case "author": - case "Author:": - Author = (String.IsNullOrEmpty(Author) ? itemval : Author); - break; - case "email": - Email = (String.IsNullOrEmpty(Email) ? itemval : Email); - break; - case "homepage": - case "Homepage:": - Homepage = (String.IsNullOrEmpty(Homepage) ? itemval : Homepage); - break; - case "url": - Url = (String.IsNullOrEmpty(Url) ? itemval : Url); - break; - case "comment": - case "Comment:": - Comment = (String.IsNullOrEmpty(Comment) ? itemval : Comment); - break; - case "header": - Header = (String.IsNullOrEmpty(Header) ? itemval : Header); - break; - case "type": - Type = (String.IsNullOrEmpty(Type) ? itemval : Type); - superdat = superdat || itemval.Contains("SuperDAT"); - break; - case "forcemerging": - if (ForceMerging == ForceMerging.None) - { - switch (itemval) - { - case "none": - ForceMerging = ForceMerging.None; - break; - case "split": - ForceMerging = ForceMerging.Split; - break; - case "merged": - ForceMerging = ForceMerging.Merged; - break; - case "nonmerged": - ForceMerging = ForceMerging.NonMerged; - break; - case "full": - ForceMerging = ForceMerging.Full; - break; - } - } - break; - case "forcezipping": - if (ForcePacking == ForcePacking.None) - { - switch (itemval) - { - case "yes": - ForcePacking = ForcePacking.Zip; - break; - case "no": - ForcePacking = ForcePacking.Unzip; - break; - } - } - break; - case "forcepacking": - if (ForcePacking == ForcePacking.None) - { - switch (itemval) - { - case "zip": - ForcePacking = ForcePacking.Zip; - break; - case "unzip": - ForcePacking = ForcePacking.Unzip; - break; - } - } - break; - } - } - } - - // If we find an end bracket that's not associated with anything else, the block is done - else if (Regex.IsMatch(line, Constants.EndPatternCMP) && block) - { - block = false; - blockname = ""; tempgamename = ""; gamedesc = ""; cloneof = ""; - romof = ""; sampleof = ""; year = ""; manufacturer = ""; - } - } - - sr.Dispose(); - } - - /// - /// 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 - /// Hash type that should be assumed - /// True if game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private void ParseHashfile( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Specific to hash files - Hash hashtype, - - // Miscellaneous - bool clean, - bool remUnicode) - { - // Open a file reader - Encoding enc = Style.GetEncoding(filename); - StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc); - - while (!sr.EndOfStream) - { - string line = sr.ReadLine(); - - // 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 ((hashtype & 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 = ((hashtype & Hash.CRC) != 0 ? hash : null), - MD5 = ((hashtype & Hash.MD5) != 0 ? hash : null), - SHA1 = ((hashtype & Hash.SHA1) != 0 ? hash : null), - SHA256 = ((hashtype & Hash.SHA256) != 0 ? hash : null), - SHA384 = ((hashtype & Hash.SHA384) != 0 ? hash : null), - SHA512 = ((hashtype & Hash.SHA512) != 0 ? hash : null), - ItemStatus = ItemStatus.None, - - MachineName = Path.GetFileNameWithoutExtension(filename), - - SystemID = sysid, - SourceID = srcid, - }; - - // Now process and add the rom - ParseAddHelper(rom, clean, remUnicode); - } - - sr.Dispose(); - } - - - /// - /// Parse a MAME Listroms 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 listroms 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 - /// - private void ParseListroms( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Open a file reader - Encoding enc = Style.GetEncoding(filename); - StreamReader sr = new StreamReader(FileTools.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.IsNullOrEmpty(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 = Style.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 = Style.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 = Style.CleanListromHashData(split[1]), - SHA1 = Style.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 = Style.CleanListromHashData(split[2]), - SHA1 = Style.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); - } - } - } - } - - /// - /// 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 game names are sanitized, false otherwise (default) - /// True if we should remove non-ASCII characters from output, false otherwise (default) - private void ParseRomCenter( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool clean, - bool remUnicode) - { - // Open a file reader - Encoding enc = Style.GetEncoding(filename); - StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc); - - 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.IsNullOrEmpty(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.IsNullOrEmpty(Version) ? line.Split('=')[1] : Version); - break; - case "emulator": - Description = (String.IsNullOrEmpty(Description) ? line.Split('=')[1] : Description); - break; - } - } - // If we have a URL - else if (line.ToLowerInvariant().StartsWith("url=")) - { - Url = (String.IsNullOrEmpty(Url) ? line.Split('=')[1] : Url); - } - // If we have a comment - else if (line.ToLowerInvariant().StartsWith("comment=")) - { - Comment = (String.IsNullOrEmpty(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 && ForcePacking == ForcePacking.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.IsNullOrEmpty(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('¬'); - - // 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 = rominfo[6], - ItemStatus = ItemStatus.None, - - MachineName = rominfo[3], - MachineDescription = rominfo[4], - CloneOf = rominfo[1], - RomOf = rominfo[8], - - SystemID = sysid, - SourceID = srcid, - }; - - // Now process and add the rom - ParseAddHelper(rom, clean, remUnicode); - } - } - } - - sr.Dispose(); - } - - /// - /// 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 - /// Delimiter for parsing individual lines - /// 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 ParseSeparatedValue( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - char delim, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Open a file reader - Encoding enc = Style.GetEncoding(filename); - StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc); - - // Create an empty list of columns to parse though - List columns = new List(); - - 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; - default: - columns.Add("INVALID"); - break; - } - } - - continue; - } - - // 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; - } - - // Set the output item information - string machineName = null, machineDesc = null, name = null, crc = null, md5 = null, sha1 = null, - sha256 = null, sha384 = null, sha512 = 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.IsNullOrEmpty(FileName) ? value : FileName); - break; - case "DatFile.Name": - Name = (String.IsNullOrEmpty(Name) ? value : Name); - break; - case "DatFile.Description": - Description = (String.IsNullOrEmpty(Description) ? value : Description); - break; - case "Machine.Name": - machineName = value; - break; - case "Description": - machineDesc = value; - break; - case "DatItem.Type": - switch (value.ToLowerInvariant()) - { - case "archive": - itemType = ItemType.Archive; - break; - case "biosset": - itemType = ItemType.BiosSet; - break; - case "disk": - itemType = ItemType.Disk; - break; - case "release": - itemType = ItemType.Release; - break; - case "rom": - itemType = ItemType.Rom; - break; - case "sample": - itemType = ItemType.Sample; - break; - } - break; - case "Rom.Name": - case "Disk.Name": - name = value == "" ? name : value; - break; - case "DatItem.Size": - if (!Int64.TryParse(value, out size)) - { - size = -1; - } - break; - case "DatItem.CRC": - crc = value; - break; - case "DatItem.MD5": - md5 = value; - break; - case "DatItem.SHA1": - sha1 = value; - break; - case "DatItem.SHA256": - sha256 = value; - break; - case "DatItem.SHA384": - sha384 = value; - break; - case "DatItem.SHA512": - sha512 = value; - break; - case "DatItem.Nodump": - switch (value.ToLowerInvariant()) - { - case "baddump": - status = ItemStatus.BadDump; - break; - case "good": - status = ItemStatus.Good; - break; - case "no": - case "none": - status = ItemStatus.None; - break; - case "nodump": - case "yes": - status = ItemStatus.Nodump; - break; - case "verified": - status = ItemStatus.Verified; - break; - } - break; - } - } - - // And now we populate and add the new item - switch (itemType) - { - case ItemType.Archive: - Archive archive = new Archive() - { - Name = name, - - MachineName = machineName, - MachineDescription = machineDesc, - }; - - ParseAddHelper(archive, clean, remUnicode); - break; - case ItemType.BiosSet: - BiosSet biosset = new BiosSet() - { - Name = name, - - 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, - - MachineName = machineName, - MachineDescription = machineDesc, - - ItemStatus = status, - }; - - ParseAddHelper(disk, clean, remUnicode); - break; - case ItemType.Release: - Release release = new Release() - { - Name = name, - - 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, - - MachineName = machineName, - MachineDescription = machineDesc, - - ItemStatus = status, - }; - - ParseAddHelper(rom, clean, remUnicode); - break; - case ItemType.Sample: - Sample sample = new Sample() - { - Name = name, - - MachineName = machineName, - MachineDescription = machineDesc, - }; - - ParseAddHelper(sample, clean, remUnicode); - break; - } - } - } - - - /// - /// Parse an XML DAT (Logiqx, OfflineList, SabreDAT, and Software List) 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: Software Lists - sharedfeat tag (read-in, write-out) - /// - private void ParseXML( - // Standard Dat parsing - string filename, - int sysid, - int srcid, - - // Miscellaneous - bool keep, - bool clean, - bool remUnicode) - { - // Prepare all internal variables - XmlReader subreader, headreader, flagreader; - bool superdat = false, empty = true; - string key = "", date = ""; - long size = -1; - ItemStatus its = ItemStatus.None; - List parent = new List(); - - Encoding enc = Style.GetEncoding(filename); - XmlReader xtr = FileTools.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.IsNullOrEmpty(Type) ? "SuperDAT" : Type); - superdat = true; - } - } - } - - // We only want elements - if (xtr.NodeType != XmlNodeType.Element) - { - xtr.Read(); - continue; - } - - switch (xtr.Name) - { - // Handle MAME listxml since they're halfway between a SL and a Logiqx XML - case "mame": - if (xtr.GetAttribute("build") != null) - { - Name = (String.IsNullOrEmpty(Name) ? xtr.GetAttribute("build") : Name); - Description = (String.IsNullOrEmpty(Description) ? Name : Name); - } - xtr.Read(); - break; - // New software lists have this behavior - case "softwarelist": - if (xtr.GetAttribute("name") != null) - { - Name = (String.IsNullOrEmpty(Name) ? xtr.GetAttribute("name") : Name); - } - if (xtr.GetAttribute("description") != null) - { - Description = (String.IsNullOrEmpty(Description) ? xtr.GetAttribute("description") : Description); - } - if (xtr.GetAttribute("forcemerging") != null && ForceMerging == ForceMerging.None) - { - switch (xtr.GetAttribute("forcemerging")) - { - case "none": - ForceMerging = ForceMerging.None; - break; - case "split": - ForceMerging = ForceMerging.Split; - break; - case "merged": - ForceMerging = ForceMerging.Merged; - break; - case "nonmerged": - ForceMerging = ForceMerging.NonMerged; - break; - case "full": - ForceMerging = ForceMerging.Full; - break; - } - } - if (xtr.GetAttribute("forcenodump") != null && ForceNodump == ForceNodump.None) - { - switch (xtr.GetAttribute("forcenodump")) - { - case "obsolete": - ForceNodump = ForceNodump.Obsolete; - break; - case "required": - ForceNodump = ForceNodump.Required; - break; - case "ignore": - ForceNodump = ForceNodump.Ignore; - break; - } - } - if (xtr.GetAttribute("forcepacking") != null && ForcePacking == ForcePacking.None) - { - switch (xtr.GetAttribute("forcepacking")) - { - case "zip": - ForcePacking = ForcePacking.Zip; - break; - case "unzip": - ForcePacking = ForcePacking.Unzip; - break; - } - } - xtr.Read(); - break; - // Handle M1 DATs since they're 99% the same as a SL DAT - case "m1": - Name = (String.IsNullOrEmpty(Name) ? "M1" : Name); - Description = (String.IsNullOrEmpty(Description) ? "M1" : Description); - if (xtr.GetAttribute("version") != null) - { - Version = (String.IsNullOrEmpty(Version) ? xtr.GetAttribute("version") : Version); - } - xtr.Read(); - break; - // OfflineList has a different header format - case "configuration": - headreader = xtr.ReadSubtree(); - - // If there's no subtree to the header, skip it - if (headreader == null) - { - xtr.Skip(); - continue; - } - - // Otherwise, read what we can from the header - while (!headreader.EOF) - { - // We only want elements - if (headreader.NodeType != XmlNodeType.Element || headreader.Name == "configuration") - { - headreader.Read(); - continue; - } - - // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) - string content = ""; - switch (headreader.Name.ToLowerInvariant()) - { - case "datname": - content = headreader.ReadElementContentAsString(); ; - Name = (String.IsNullOrEmpty(Name) ? content : Name); - superdat = superdat || content.Contains(" - SuperDAT"); - if (keep && superdat) - { - Type = (String.IsNullOrEmpty(Type) ? "SuperDAT" : Type); - } - break; - case "datversionurl": - content = headreader.ReadElementContentAsString(); ; - Url = (String.IsNullOrEmpty(Name) ? content : Url); - break; - default: - headreader.Read(); - break; - } - } - - break; - // We want to process the entire subtree of the header - case "header": - headreader = xtr.ReadSubtree(); - - // If there's no subtree to the header, skip it - if (headreader == null) - { - xtr.Skip(); - continue; - } - - // Otherwise, read what we can from the header - while (!headreader.EOF) - { - // We only want elements - if (headreader.NodeType != XmlNodeType.Element || headreader.Name == "header") - { - headreader.Read(); - continue; - } - - // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) - string content = ""; - switch (headreader.Name) - { - case "name": - content = headreader.ReadElementContentAsString(); ; - Name = (String.IsNullOrEmpty(Name) ? content : Name); - superdat = superdat || content.Contains(" - SuperDAT"); - if (keep && superdat) - { - Type = (String.IsNullOrEmpty(Type) ? "SuperDAT" : Type); - } - break; - case "description": - content = headreader.ReadElementContentAsString(); - Description = (String.IsNullOrEmpty(Description) ? content : Description); - break; - case "rootdir": - content = headreader.ReadElementContentAsString(); - RootDir = (String.IsNullOrEmpty(RootDir) ? content : RootDir); - break; - case "category": - content = headreader.ReadElementContentAsString(); - Category = (String.IsNullOrEmpty(Category) ? content : Category); - break; - case "version": - content = headreader.ReadElementContentAsString(); - Version = (String.IsNullOrEmpty(Version) ? content : Version); - break; - case "date": - content = headreader.ReadElementContentAsString(); - Date = (String.IsNullOrEmpty(Date) ? content.Replace(".", "/") : Date); - break; - case "author": - content = headreader.ReadElementContentAsString(); - Author = (String.IsNullOrEmpty(Author) ? content : Author); - - // Special cases for SabreDAT - Email = (String.IsNullOrEmpty(Email) && !String.IsNullOrEmpty(headreader.GetAttribute("email")) ? - headreader.GetAttribute("email") : Email); - Homepage = (String.IsNullOrEmpty(Homepage) && !String.IsNullOrEmpty(headreader.GetAttribute("homepage")) ? - headreader.GetAttribute("homepage") : Email); - Url = (String.IsNullOrEmpty(Url) && !String.IsNullOrEmpty(headreader.GetAttribute("url")) ? - headreader.GetAttribute("url") : Email); - break; - case "email": - content = headreader.ReadElementContentAsString(); - Email = (String.IsNullOrEmpty(Email) ? content : Email); - break; - case "homepage": - content = headreader.ReadElementContentAsString(); - Homepage = (String.IsNullOrEmpty(Homepage) ? content : Homepage); - break; - case "url": - content = headreader.ReadElementContentAsString(); - Url = (String.IsNullOrEmpty(Url) ? content : Url); - break; - case "comment": - content = headreader.ReadElementContentAsString(); - Comment = (String.IsNullOrEmpty(Comment) ? content : Comment); - break; - case "type": - content = headreader.ReadElementContentAsString(); - Type = (String.IsNullOrEmpty(Type) ? content : Type); - superdat = superdat || content.Contains("SuperDAT"); - break; - case "clrmamepro": - case "romcenter": - if (headreader.GetAttribute("header") != null) - { - Header = (String.IsNullOrEmpty(Header) ? headreader.GetAttribute("header") : Header); - } - if (headreader.GetAttribute("plugin") != null) - { - Header = (String.IsNullOrEmpty(Header) ? headreader.GetAttribute("plugin") : Header); - } - if (headreader.GetAttribute("forcemerging") != null && ForceMerging == ForceMerging.None) - { - switch (headreader.GetAttribute("forcemerging")) - { - case "none": - ForceMerging = ForceMerging.None; - break; - case "split": - ForceMerging = ForceMerging.Split; - break; - case "merged": - ForceMerging = ForceMerging.Merged; - break; - case "nonmerged": - ForceMerging = ForceMerging.NonMerged; - break; - case "full": - ForceMerging = ForceMerging.Full; - break; - } - } - if (headreader.GetAttribute("forcenodump") != null && ForceNodump == ForceNodump.None) - { - switch (headreader.GetAttribute("forcenodump")) - { - case "obsolete": - ForceNodump = ForceNodump.Obsolete; - break; - case "required": - ForceNodump = ForceNodump.Required; - break; - case "ignore": - ForceNodump = ForceNodump.Ignore; - break; - } - } - if (headreader.GetAttribute("forcepacking") != null && ForcePacking == ForcePacking.None) - { - switch (headreader.GetAttribute("forcepacking")) - { - case "zip": - ForcePacking = ForcePacking.Zip; - break; - case "unzip": - ForcePacking = ForcePacking.Unzip; - break; - } - } - headreader.Read(); - break; - case "flags": - flagreader = xtr.ReadSubtree(); - - // If we somehow have a null flag section, skip it - if (flagreader == null) - { - xtr.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"); - switch (flagreader.GetAttribute("name")) - { - case "type": - Type = (String.IsNullOrEmpty(Type) ? content : Type); - superdat = superdat || content.Contains("SuperDAT"); - break; - case "forcemerging": - if (ForceMerging == ForceMerging.None) - { - switch (content) - { - case "split": - ForceMerging = ForceMerging.Split; - break; - case "none": - ForceMerging = ForceMerging.None; - break; - case "full": - ForceMerging = ForceMerging.Full; - break; - } - } - break; - case "forcenodump": - if (ForceNodump == ForceNodump.None) - { - switch (content) - { - case "obsolete": - ForceNodump = ForceNodump.Obsolete; - break; - case "required": - ForceNodump = ForceNodump.Required; - break; - case "ignore": - ForceNodump = ForceNodump.Ignore; - break; - } - } - break; - case "forcepacking": - if (ForcePacking == ForcePacking.None) - { - switch (content) - { - case "zip": - ForcePacking = ForcePacking.Zip; - break; - case "unzip": - ForcePacking = ForcePacking.Unzip; - break; - } - } - break; - } - } - flagreader.Read(); - break; - default: - flagreader.Read(); - break; - } - } - headreader.Skip(); - break; - default: - headreader.Read(); - break; - } - } - - // Skip the header node now that we've processed it - xtr.Skip(); - break; - case "machine": - case "game": - case "software": - string temptype = xtr.Name, publisher = "", partname = "", partinterface = "", areaname = ""; - bool? supported = null; - long? areasize = null; - List> infos = new List>(); - List> features = new List>(); - - // We want to process the entire subtree of the game - subreader = xtr.ReadSubtree(); - - // Safeguard for interesting case of "software" without anything except roms - bool software = false; - - // If we have an empty machine, skip it - if (subreader == null) - { - xtr.Skip(); - continue; - } - - // Otherwise, add what is possible - subreader.MoveToContent(); - - // Create a new machine - Machine machine = new Machine - { - Name = xtr.GetAttribute("name"), - Description = xtr.GetAttribute("name"), - - RomOf = xtr.GetAttribute("romof") ?? "", - CloneOf = xtr.GetAttribute("cloneof") ?? "", - SampleOf = xtr.GetAttribute("sampleof") ?? "", - - Devices = new List(), - MachineType = - xtr.GetAttribute("isbios") == "yes" ? MachineType.Bios : - xtr.GetAttribute("isdevice") == "yes" ? MachineType.Device : - xtr.GetAttribute("ismechanical") == "yes" ? MachineType.Mechanical : - MachineType.None, - }; - - // Get the supported value from the reader - if (subreader.GetAttribute("supported") != null) - { - switch (subreader.GetAttribute("supported")) - { - case "no": - supported = false; - break; - case "yes": - supported = true; - break; - } - } - - // Get the runnable value from the reader - if (subreader.GetAttribute("runnable") != null) - { - switch (subreader.GetAttribute("runnable")) - { - case "no": - machine.Runnable = false; - break; - case "yes": - machine.Runnable = true; - break; - } - } - - if (superdat && !keep) - { - string tempout = Regex.Match(machine.Name, @".*?\\(.*)").Groups[1].Value; - if (tempout != "") - { - machine.Name = tempout; - } - } - // Get the name of the game from the parent - else if (superdat && keep && parent.Count > 0) - { - machine.Name = String.Join("\\", parent) + "\\" + machine.Name; - } - - // Special offline list parts - string ext = ""; - string releaseNumber = ""; - - while (software || !subreader.EOF) - { - software = false; - - // We only want elements - if (subreader.NodeType != XmlNodeType.Element) - { - if (subreader.NodeType == XmlNodeType.EndElement && subreader.Name == "part") - { - partname = ""; - partinterface = ""; - features = new List>(); - } - if (subreader.NodeType == XmlNodeType.EndElement && (subreader.Name == "dataarea" || subreader.Name == "diskarea")) - { - areaname = ""; - areasize = null; - } - - subreader.Read(); - continue; - } - - // Get the roms from the machine - switch (subreader.Name) - { - // For OfflineList only - case "title": - machine.Name = subreader.ReadElementContentAsString(); - break; - case "releaseNumber": - releaseNumber = subreader.ReadElementContentAsString(); - break; - case "romSize": - if (!Int64.TryParse(subreader.ReadElementContentAsString(), out size)) - { - size = -1; - } - break; - case "romCRC": - empty = false; - - ext = (subreader.GetAttribute("extension") ?? ""); - - DatItem olrom = new Rom - { - Name = releaseNumber + " - " + machine.Name + ext, - Size = size, - CRC = subreader.ReadElementContentAsString(), - ItemStatus = ItemStatus.None, - }; - - olrom.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(olrom, clean, remUnicode); - break; - - // For Software List and MAME listxml only - case "device_ref": - string device = subreader.GetAttribute("name"); - if (!machine.Devices.Contains(device)) - { - machine.Devices.Add(device); - } - - subreader.Read(); - break; - case "slotoption": - string slotoption = subreader.GetAttribute("devname"); - if (!machine.Devices.Contains(slotoption)) - { - machine.Devices.Add(slotoption); - } - - subreader.Read(); - break; - case "publisher": - publisher = subreader.ReadElementContentAsString(); - break; - case "info": - infos.Add(Tuple.Create(subreader.GetAttribute("name"), subreader.GetAttribute("value"))); - subreader.Read(); - break; - case "part": - partname = subreader.GetAttribute("name"); - partinterface = subreader.GetAttribute("interface"); - subreader.Read(); - break; - case "feature": - features.Add(Tuple.Create(subreader.GetAttribute("name"), subreader.GetAttribute("value"))); - subreader.Read(); - break; - case "dataarea": - case "diskarea": - areaname = subreader.GetAttribute("name"); - long areasizetemp = -1; - if (Int64.TryParse(subreader.GetAttribute("size"), out areasizetemp)) - { - areasize = areasizetemp; - } - subreader.Read(); - break; - - // For Logiqx, SabreDAT, and Software List - case "description": - machine.Description = subreader.ReadElementContentAsString(); - break; - case "year": - machine.Year = subreader.ReadElementContentAsString(); - break; - case "manufacturer": - machine.Manufacturer = subreader.ReadElementContentAsString(); - break; - case "release": - empty = false; - - bool? defaultrel = null; - if (subreader.GetAttribute("default") != null) - { - if (subreader.GetAttribute("default") == "yes") - { - defaultrel = true; - } - else if (subreader.GetAttribute("default") == "no") - { - defaultrel = false; - } - } - - DatItem relrom = new Release - { - Name = subreader.GetAttribute("name"), - Region = subreader.GetAttribute("region"), - Language = subreader.GetAttribute("language"), - Date = date, - Default = defaultrel, - - Supported = supported, - Publisher = publisher, - Infos = infos, - PartName = partname, - PartInterface = partinterface, - Features = features, - AreaName = areaname, - AreaSize = areasize, - }; - - relrom.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(relrom, clean, remUnicode); - - subreader.Read(); - break; - case "biosset": - empty = false; - - bool? defaultbios = null; - if (subreader.GetAttribute("default") != null) - { - if (subreader.GetAttribute("default") == "yes") - { - defaultbios = true; - } - else if (subreader.GetAttribute("default") == "no") - { - defaultbios = false; - } - } - - DatItem biosrom = new BiosSet - { - Name = subreader.GetAttribute("name"), - Description = subreader.GetAttribute("description"), - Default = defaultbios, - - Supported = supported, - Publisher = publisher, - Infos = infos, - PartName = partname, - PartInterface = partinterface, - Features = features, - AreaName = areaname, - AreaSize = areasize, - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - biosrom.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(biosrom, clean, remUnicode); - - subreader.Read(); - break; - case "archive": - empty = false; - - DatItem archiverom = new Archive - { - Name = subreader.GetAttribute("name"), - - Supported = supported, - Publisher = publisher, - Infos = infos, - PartName = partname, - PartInterface = partinterface, - Features = features, - AreaName = areaname, - AreaSize = areasize, - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - archiverom.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(archiverom, clean, remUnicode); - - subreader.Read(); - break; - case "sample": - empty = false; - - DatItem samplerom = new Sample - { - Name = subreader.GetAttribute("name"), - - Supported = supported, - Publisher = publisher, - Infos = infos, - PartName = partname, - PartInterface = partinterface, - Features = features, - AreaName = areaname, - AreaSize = areasize, - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - samplerom.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(samplerom, clean, remUnicode); - - subreader.Read(); - break; - case "rom": - case "disk": - empty = false; - - // If the rom has a merge tag, add it - string merge = subreader.GetAttribute("merge"); - - // If the rom has a status, flag it - its = ItemStatus.None; - if (subreader.GetAttribute("flags") == "good" || subreader.GetAttribute("status") == "good") - { - its = ItemStatus.Good; - } - if (subreader.GetAttribute("flags") == "baddump" || subreader.GetAttribute("status") == "baddump") - { - its = ItemStatus.BadDump; - } - if (subreader.GetAttribute("flags") == "nodump" || subreader.GetAttribute("status") == "nodump") - { - its = ItemStatus.Nodump; - } - if (subreader.GetAttribute("flags") == "verified" || subreader.GetAttribute("status") == "verified") - { - its = ItemStatus.Verified; - } - - // If the rom has a Date attached, read it in and then sanitize it - date = ""; - if (subreader.GetAttribute("date") != null) - { - DateTime dateTime = DateTime.Now; - if (DateTime.TryParse(subreader.GetAttribute("date"), out dateTime)) - { - date = dateTime.ToString(); - } - else - { - date = subreader.GetAttribute("date"); - } - } - - // Take care of hex-sized files - size = -1; - if (subreader.GetAttribute("size") != null && subreader.GetAttribute("size").Contains("0x")) - { - size = Convert.ToInt64(subreader.GetAttribute("size"), 16); - } - else if (subreader.GetAttribute("size") != null) - { - Int64.TryParse(subreader.GetAttribute("size"), out size); - } - - // If the rom is continue or ignore, add the size to the previous rom - if (subreader.GetAttribute("loadflag") == "continue" || subreader.GetAttribute("loadflag") == "ignore") - { - int index = this[key].Count() - 1; - DatItem lastrom = this[key][index]; - if (lastrom.Type == ItemType.Rom) - { - ((Rom)lastrom).Size += size; - } - this[key].RemoveAt(index); - this[key].Add(lastrom); - subreader.Read(); - continue; - } - - // If we're in clean mode, sanitize the game name - if (clean) - { - machine.Name = Style.CleanGameName(machine.Name.Split(Path.DirectorySeparatorChar)); - } - - DatItem inrom; - switch (subreader.Name.ToLowerInvariant()) - { - case "disk": - inrom = new Disk - { - Name = subreader.GetAttribute("name"), - MD5 = subreader.GetAttribute("md5")?.ToLowerInvariant(), - SHA1 = subreader.GetAttribute("sha1")?.ToLowerInvariant(), - SHA256 = subreader.GetAttribute("sha256")?.ToLowerInvariant(), - SHA384 = subreader.GetAttribute("sha384")?.ToLowerInvariant(), - SHA512 = subreader.GetAttribute("sha512")?.ToLowerInvariant(), - MergeTag = merge, - ItemStatus = its, - - Supported = supported, - Publisher = publisher, - Infos = infos, - PartName = partname, - PartInterface = partinterface, - Features = features, - AreaName = areaname, - AreaSize = areasize, - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - break; - case "rom": - default: - inrom = new Rom - { - Name = subreader.GetAttribute("name"), - Size = size, - CRC = subreader.GetAttribute("crc"), - MD5 = subreader.GetAttribute("md5")?.ToLowerInvariant(), - SHA1 = subreader.GetAttribute("sha1")?.ToLowerInvariant(), - SHA256 = subreader.GetAttribute("sha256")?.ToLowerInvariant(), - SHA384 = subreader.GetAttribute("sha384")?.ToLowerInvariant(), - SHA512 = subreader.GetAttribute("sha512")?.ToLowerInvariant(), - ItemStatus = its, - MergeTag = merge, - Date = date, - - Supported = supported, - Publisher = publisher, - Infos = infos, - PartName = partname, - PartInterface = partinterface, - Features = features, - AreaName = areaname, - AreaSize = areasize, - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - break; - } - - inrom.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(inrom, clean, remUnicode); - - subreader.Read(); - break; - default: - subreader.Read(); - break; - } - } - - xtr.Skip(); - break; - case "dir": - case "directory": - // Set SuperDAT flag for all SabreDAT inputs, regardless of depth - superdat = true; - if (keep) - { - Type = (Type == "" ? "SuperDAT" : Type); - } - - string foldername = (xtr.GetAttribute("name") ?? ""); - if (foldername != "") - { - parent.Add(foldername); - } - - xtr.Read(); - break; - case "file": - empty = false; - - // If the rom is itemStatus, flag it - its = ItemStatus.None; - flagreader = xtr.ReadSubtree(); - - // If the subtree is empty, skip it - if (flagreader == null) - { - xtr.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": - case "status": - if (flagreader.GetAttribute("name") != null && flagreader.GetAttribute("value") != null) - { - string content = flagreader.GetAttribute("value"); - switch (flagreader.GetAttribute("name")) - { - case "good": - its = ItemStatus.Good; - break; - case "baddump": - its = ItemStatus.BadDump; - break; - case "nodump": - its = ItemStatus.Nodump; - break; - case "verified": - its = ItemStatus.Verified; - break; - } - } - break; - } - - flagreader.Read(); - } - - // If the rom has a Date attached, read it in and then sanitize it - date = ""; - if (xtr.GetAttribute("date") != null) - { - date = DateTime.Parse(xtr.GetAttribute("date")).ToString(); - } - - // Take care of hex-sized files - size = -1; - if (xtr.GetAttribute("size") != null && xtr.GetAttribute("size").Contains("0x")) - { - size = Convert.ToInt64(xtr.GetAttribute("size"), 16); - } - else if (xtr.GetAttribute("size") != null) - { - Int64.TryParse(xtr.GetAttribute("size"), out size); - } - - // If the rom is continue or ignore, add the size to the previous rom - if (xtr.GetAttribute("loadflag") == "continue" || xtr.GetAttribute("loadflag") == "ignore") - { - int index = this[key].Count() - 1; - DatItem lastrom = this[key][index]; - if (lastrom.Type == ItemType.Rom) - { - ((Rom)lastrom).Size += size; - } - this[key].RemoveAt(index); - this[key].Add(lastrom); - continue; - } - - Machine dir = new Machine(); - - // Get the name of the game from the parent - dir.Name = String.Join("\\", parent); - dir.Description = dir.Name; - - // If we aren't keeping names, trim out the path - if (!keep || !superdat) - { - string tempout = Regex.Match(dir.Name, @".*?\\(.*)").Groups[1].Value; - if (tempout != "") - { - dir.Name = tempout; - } - } - - DatItem rom; - switch (xtr.GetAttribute("type").ToLowerInvariant()) - { - case "disk": - rom = new Disk - { - Name = xtr.GetAttribute("name"), - MD5 = xtr.GetAttribute("md5")?.ToLowerInvariant(), - SHA1 = xtr.GetAttribute("sha1")?.ToLowerInvariant(), - SHA256 = xtr.GetAttribute("sha256")?.ToLowerInvariant(), - SHA384 = xtr.GetAttribute("sha384")?.ToLowerInvariant(), - SHA512 = xtr.GetAttribute("sha512")?.ToLowerInvariant(), - ItemStatus = its, - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - break; - case "rom": - default: - rom = new Rom - { - Name = xtr.GetAttribute("name"), - Size = size, - CRC = xtr.GetAttribute("crc")?.ToLowerInvariant(), - MD5 = xtr.GetAttribute("md5")?.ToLowerInvariant(), - SHA1 = xtr.GetAttribute("sha1")?.ToLowerInvariant(), - SHA256 = xtr.GetAttribute("sha256")?.ToLowerInvariant(), - SHA384 = xtr.GetAttribute("sha384")?.ToLowerInvariant(), - SHA512 = xtr.GetAttribute("sha512")?.ToLowerInvariant(), - ItemStatus = its, - Date = date, - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - break; - } - - rom.CopyMachineInformation(dir); - - // Now process and add the rom - key = ParseAddHelper(rom, clean, remUnicode); - - 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(); - } - - /// - /// Add a rom to the Dat after checking - /// - /// Item data to check against - /// True if the names should be cleaned to WoD standards, false otherwise - /// True if we should remove non-ASCII characters from output, false otherwise (default) - /// The key for the item - private string ParseAddHelper(DatItem item, bool clean, bool remUnicode) - { - string key = ""; - - // If there's no name in the rom, we log and skip it - if (item.Name == null) - { - Globals.Logger.Warning("{0}: Rom with no name found! Skipping...", FileName); - return key; - } - - // If the name ends with a directory separator, we log and skip it (DOSCenter only?) - if (item.Name.EndsWith("/") || item.Name.EndsWith("\\")) - { - Globals.Logger.Warning("{0}: Rom ending with directory separator found: '{1}'. Skipping...", FileName, item.Name); - return key; - } - - // If we're in cleaning mode, sanitize the game name - item.MachineName = (clean ? Style.CleanGameName(item.MachineName) : item.MachineName); - - // If we're stripping unicode characters, do so from all relevant things - if (remUnicode) - { - item.Name = Style.RemoveUnicodeCharacters(item.Name); - item.MachineName = Style.RemoveUnicodeCharacters(item.MachineName); - item.MachineDescription = Style.RemoveUnicodeCharacters(item.MachineDescription); - } - - // If we have a Rom or a Disk, clean the hash data - if (item.Type == ItemType.Rom) - { - Rom itemRom = (Rom)item; - - // Sanitize the hashes from null, hex sizes, and "true blank" strings - itemRom.CRC = Style.CleanHashData(itemRom.CRC, Constants.CRCLength); - itemRom.MD5 = Style.CleanHashData(itemRom.MD5, Constants.MD5Length); - itemRom.SHA1 = Style.CleanHashData(itemRom.SHA1, Constants.SHA1Length); - itemRom.SHA256 = Style.CleanHashData(itemRom.SHA256, Constants.SHA256Length); - itemRom.SHA384 = Style.CleanHashData(itemRom.SHA384, Constants.SHA384Length); - itemRom.SHA512 = Style.CleanHashData(itemRom.SHA512, Constants.SHA512Length); - - // If we have a rom and it's missing size AND the hashes match a 0-byte file, fill in the rest of the info - if ((itemRom.Size == 0 || itemRom.Size == -1) - && ((itemRom.CRC == Constants.CRCZero || String.IsNullOrEmpty(itemRom.CRC)) - || itemRom.MD5 == Constants.MD5Zero - || itemRom.SHA1 == Constants.SHA1Zero - || itemRom.SHA256 == Constants.SHA256Zero - || itemRom.SHA384 == Constants.SHA384Zero - || itemRom.SHA512 == Constants.SHA512Zero)) - { - // TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually - itemRom.Size = Constants.SizeZero; - itemRom.CRC = Constants.CRCZero; - itemRom.MD5 = Constants.MD5Zero; - itemRom.SHA1 = Constants.SHA1Zero; - itemRom.SHA256 = null; - itemRom.SHA384 = null; - itemRom.SHA512 = null; - //itemRom.SHA256 = Constants.SHA256Zero; - //itemRom.SHA384 = Constants.SHA384Zero; - //itemRom.SHA512 = Constants.SHA512Zero; - } - // If the file has no size and it's not the above case, skip and log - else if (itemRom.ItemStatus != ItemStatus.Nodump && (itemRom.Size == 0 || itemRom.Size == -1)) - { - Globals.Logger.Verbose("{0}: Incomplete entry for '{1}' will be output as nodump", FileName, itemRom.Name); - itemRom.ItemStatus = ItemStatus.Nodump; - } - // If the file has a size but aboslutely no hashes, skip and log - else if (itemRom.ItemStatus != ItemStatus.Nodump - && itemRom.Size > 0 - && String.IsNullOrEmpty(itemRom.CRC) - && String.IsNullOrEmpty(itemRom.MD5) - && String.IsNullOrEmpty(itemRom.SHA1) - && String.IsNullOrEmpty(itemRom.SHA256) - && String.IsNullOrEmpty(itemRom.SHA384) - && String.IsNullOrEmpty(itemRom.SHA512)) - { - Globals.Logger.Verbose("{0}: Incomplete entry for '{1}' will be output as nodump", FileName, itemRom.Name); - itemRom.ItemStatus = ItemStatus.Nodump; - } - - item = itemRom; - } - else if (item.Type == ItemType.Disk) - { - Disk itemDisk = (Disk)item; - - // Sanitize the hashes from null, hex sizes, and "true blank" strings - itemDisk.MD5 = Style.CleanHashData(itemDisk.MD5, Constants.MD5Length); - itemDisk.SHA1 = Style.CleanHashData(itemDisk.SHA1, Constants.SHA1Length); - itemDisk.SHA256 = Style.CleanHashData(itemDisk.SHA256, Constants.SHA256Length); - itemDisk.SHA384 = Style.CleanHashData(itemDisk.SHA384, Constants.SHA384Length); - itemDisk.SHA512 = Style.CleanHashData(itemDisk.SHA512, Constants.SHA512Length); - - // If the file has aboslutely no hashes, skip and log - if (itemDisk.ItemStatus != ItemStatus.Nodump - && String.IsNullOrEmpty(itemDisk.MD5) - && String.IsNullOrEmpty(itemDisk.SHA1) - && String.IsNullOrEmpty(itemDisk.SHA256) - && String.IsNullOrEmpty(itemDisk.SHA384) - && String.IsNullOrEmpty(itemDisk.SHA512)) - { - Globals.Logger.Verbose("Incomplete entry for '{0}' will be output as nodump", itemDisk.Name); - itemDisk.ItemStatus = ItemStatus.Nodump; - } - - item = itemDisk; - } - - // Get the key and add statistical data - switch (item.Type) - { - case ItemType.Archive: - case ItemType.BiosSet: - case ItemType.Release: - case ItemType.Sample: - key = item.Type.ToString(); - break; - case ItemType.Disk: - key = ((Disk)item).MD5; - break; - case ItemType.Rom: - key = ((Rom)item).Size + "-" + ((Rom)item).CRC; - break; - default: - key = "default"; - break; - } - - // Add the item to the DAT - Add(key, item); - - return key; - } - - /// - /// Add a rom to the Dat after checking - /// - /// Item data to check against - /// True if the names should be cleaned to WoD standards, false otherwise - /// True if we should remove non-ASCII characters from output, false otherwise (default) - /// The key for the item - private async Task ParseAddHelperAsync(DatItem item, bool clean, bool remUnicode) - { - return await Task.Run(() => ParseAddHelper(item, clean, remUnicode)); - } - - #endregion - } -} diff --git a/SabreTools.Library/DatFiles/DatFile.Writers.cs b/SabreTools.Library/DatFiles/DatFile.Writers.cs deleted file mode 100644 index bfcf1211..00000000 --- a/SabreTools.Library/DatFiles/DatFile.Writers.cs +++ /dev/null @@ -1,1664 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Web; - -using SabreTools.Library.Data; -using SabreTools.Library.Items; -using SabreTools.Library.Tools; - -#if MONO -using System.IO; -#else -using Alphaleonis.Win32.Filesystem; - -using FileStream = System.IO.FileStream; -using MemoryStream = System.IO.MemoryStream; -using StreamWriter = System.IO.StreamWriter; -#endif -using NaturalSort; - -namespace SabreTools.Library.DatFiles -{ - /// - /// Represents a format-agnostic DAT - /// - public partial class DatFile - { - #region Writing - - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// All information for creating the datfile header - /// Set the output directory - /// True if games should only be compared on game and file name (default), false if system and source are counted - /// True if DAT statistics should be output on write, false otherwise (default) - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if files should be overwritten (default), false if they should be renamed instead - /// True if the DAT was written correctly, false otherwise - public bool WriteToFile(string outDir, bool norename = true, bool stats = false, bool ignoreblanks = false, bool overwrite = true) - { - // If there's nothing there, abort - if (Count == 0) - { - Globals.Logger.User("There were no items to write out!"); - return false; - } - - // If output directory is empty, use the current folder - if (outDir == null || outDir.Trim() == "") - { - Globals.Logger.Verbose("No output directory defined, defaulting to curent folder"); - outDir = Environment.CurrentDirectory; - } - - // Create the output directory if it doesn't already exist - if (!Directory.Exists(outDir)) - { - Directory.CreateDirectory(outDir); - } - - // If the DAT has no output format, default to XML - if (DatFormat == 0) - { - Globals.Logger.Verbose("No DAT format defined, defaulting to XML"); - DatFormat = DatFormat.Logiqx; - } - - // Make sure that the three essential fields are filled in - if (String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description)) - { - FileName = Name = Description = "Default"; - } - else if (String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description)) - { - FileName = Name = Description; - } - else if (String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description)) - { - FileName = Description = Name; - } - else if (String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description)) - { - FileName = Description; - } - else if (!String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description)) - { - Name = Description = FileName; - } - else if (!String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description)) - { - Name = Description; - } - else if (!String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description)) - { - Description = Name; - } - else if (!String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description)) - { - // Nothing is needed - } - - // Output initial statistics, for kicks - if (stats) - { - OutputStats(new Dictionary(), StatDatFormat.None, - recalculate: (RomCount + DiskCount == 0), baddumpCol: true, nodumpCol: true); - } - - // Bucket and dedupe according to the flag - if (DedupeRoms == DedupeType.Full) - { - BucketBy(SortedBy.CRC, DedupeRoms, norename: norename); - } - else if (DedupeRoms == DedupeType.Game) - { - BucketBy(SortedBy.Game, DedupeRoms, norename: norename); - } - - // Bucket roms by game name, if not already - BucketBy(SortedBy.Game, DedupeType.None, norename: norename); - - // Output the number of items we're going to be writing - Globals.Logger.User("A total of {0} items will be written out to '{1}'", Count, FileName); - - // Filter the DAT by 1G1R rules, if we're supposed to - // TODO: Create 1G1R logic before write - - // If we are removing hashes, do that now - if (StripHash != 0x0) - { - StripHashesFromItems(); - } - - // Get the outfile names - Dictionary outfiles = Style.CreateOutfileNames(outDir, this, overwrite); - - try - { - // Get a properly sorted set of keys - List keys = Keys.ToList(); - keys.Sort(new NaturalComparer()); - - // Write out all required formats - Parallel.ForEach(outfiles.Keys, Globals.ParallelOptions, datFormat => - { - string outfile = outfiles[datFormat]; - - Globals.Logger.User("Opening file for writing: {0}", outfile); - FileStream fs = FileTools.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; - } - - StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(true)); - - // Write out the header - WriteHeader(sw, datFormat); - - // Write out each of the machines and roms - int depth = 2, last = -1; - string lastgame = null; - List splitpath = new List(); - - 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, datFormat, rom, splitpath, newsplit, lastgame, 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, datFormat, rom, newsplit, lastgame, depth, last); - } - - // If we have a "null" game (created by DATFromDir or something similar), log it to file - if (rom.Type == 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 - if (datFormat != DatFormat.CSV - && datFormat != DatFormat.MissFile - && datFormat != DatFormat.SabreDat - && datFormat != DatFormat.TSV) - { - 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; - } - - // Otherwise, set the new path and such, write out, and continue - else - { - splitpath = newsplit; - lastgame = rom.MachineName; - continue; - } - } - - // Now, output the rom data - WriteRomData(sw, datFormat, rom, lastgame, depth, ignoreblanks); - - // Set the new data to compare against - splitpath = newsplit; - lastgame = rom.MachineName; - } - } - - // Write the file footer out - WriteFooter(sw, datFormat, 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 - /// Output format to write to - /// True if the data was written, false on error - private bool WriteHeader(StreamWriter sw, DatFormat datFormat) - { - try - { - string header = ""; - switch (datFormat) - { - case DatFormat.AttractMode: - header = "#Title;Name;Emulator;CloneOf;Year;Manufacturer;Category;Players;Rotation;Control;Status;DisplayCount;DisplayType;AltRomname;AltTitle;Extra;Buttons\n"; - break; - case DatFormat.ClrMamePro: - header = "clrmamepro (\n" + - "\tname \"" + Name + "\"\n" + - "\tdescription \"" + Description + "\"\n" + - (!String.IsNullOrEmpty(Category) ? "\tcategory \"" + Category + "\"\n" : "") + - "\tversion \"" + Version + "\"\n" + - (!String.IsNullOrEmpty(Date) ? "\tdate \"" + Date + "\"\n" : "") + - "\tauthor \"" + Author + "\"\n" + - (!String.IsNullOrEmpty(Email) ? "\temail \"" + Email + "\"\n" : "") + - (!String.IsNullOrEmpty(Homepage) ? "\thomepage \"" + Homepage + "\"\n" : "") + - (!String.IsNullOrEmpty(Url) ? "\turl \"" + Url + "\"\n" : "") + - (!String.IsNullOrEmpty(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"; - break; - case DatFormat.CSV: - header = "\"File Name\",\"Internal Name\",\"Description\",\"Game Name\",\"Game Description\",\"Type\",\"" + - "Rom Name\",\"Disk Name\",\"Size\",\"CRC\",\"MD5\",\"SHA1\",\"SHA256\",\"Nodump\"\n"; - break; - case DatFormat.DOSCenter: - 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"; - break; - case DatFormat.Logiqx: - header = "\n" + - "\n\n" + - "\n" + - "\t
\n" + - "\t\t" + HttpUtility.HtmlEncode(Name) + "\n" + - "\t\t" + HttpUtility.HtmlEncode(Description) + "\n" + - (!String.IsNullOrEmpty(RootDir) ? "\t\t" + HttpUtility.HtmlEncode(RootDir) + "\n" : "") + - (!String.IsNullOrEmpty(Category) ? "\t\t" + HttpUtility.HtmlEncode(Category) + "\n" : "") + - "\t\t" + HttpUtility.HtmlEncode(Version) + "\n" + - (!String.IsNullOrEmpty(Date) ? "\t\t" + HttpUtility.HtmlEncode(Date) + "\n" : "") + - "\t\t" + HttpUtility.HtmlEncode(Author) + "\n" + - (!String.IsNullOrEmpty(Email) ? "\t\t" + HttpUtility.HtmlEncode(Email) + "\n" : "") + - (!String.IsNullOrEmpty(Homepage) ? "\t\t" + HttpUtility.HtmlEncode(Homepage) + "\n" : "") + - (!String.IsNullOrEmpty(Url) ? "\t\t" + HttpUtility.HtmlEncode(Url) + "\n" : "") + - (!String.IsNullOrEmpty(Comment) ? "\t\t" + HttpUtility.HtmlEncode(Comment) + "\n" : "") + - (!String.IsNullOrEmpty(Type) ? "\t\t" + HttpUtility.HtmlEncode(Type) + "\n" : "") + - (ForcePacking != ForcePacking.None || ForceMerging != ForceMerging.None || ForceNodump != ForceNodump.None ? - "\t\t\n" - : "") + - "\t
\n"; - break; - case DatFormat.TSV: - header = "\"File Name\"\t\"Internal Name\"\t\"Description\"\t\"Game Name\"\t\"Game Description\"\t\"Type\"\t\"" + - "Rom Name\"\t\"Disk Name\"\t\"Size\"\t\"CRC\"\t\"MD5\"\t\"SHA1\"\t\"SHA256\"\t\"Nodump\"\n"; - break; - case DatFormat.OfflineList: - 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"; - break; - case DatFormat.RomCenter: - 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"; - break; - case DatFormat.SabreDat: - header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + - "<!DOCTYPE sabredat SYSTEM \"newdat.xsd\">\n\n" + - "<datafile>\n" + - "\t<header>\n" + - "\t\t<name>" + HttpUtility.HtmlEncode(Name) + "</name>\n" + - "\t\t<description>" + HttpUtility.HtmlEncode(Description) + "</description>\n" + - (!String.IsNullOrEmpty(RootDir) ? "\t\t<rootdir>" + HttpUtility.HtmlEncode(RootDir) + "</rootdir>\n" : "") + - (!String.IsNullOrEmpty(Category) ? "\t\t<category>" + HttpUtility.HtmlEncode(Category) + "</category>\n" : "") + - "\t\t<version>" + HttpUtility.HtmlEncode(Version) + "</version>\n" + - (!String.IsNullOrEmpty(Date) ? "\t\t<date>" + HttpUtility.HtmlEncode(Date) + "</date>\n" : "") + - "\t\t<author>" + HttpUtility.HtmlEncode(Author) + "</author>\n" + - (!String.IsNullOrEmpty(Comment) ? "\t\t<comment>" + HttpUtility.HtmlEncode(Comment) + "</comment>\n" : "") + - (!String.IsNullOrEmpty(Type) || ForcePacking != ForcePacking.None || ForceMerging != ForceMerging.None || ForceNodump != ForceNodump.None ? - "\t\t<flags>\n" + - (!String.IsNullOrEmpty(Type) ? "\t\t\t<flag name=\"type\" value=\"" + HttpUtility.HtmlEncode(Type) + "\"/>\n" : "") + - (ForcePacking == ForcePacking.Unzip ? "\t\t\t<flag name=\"forcepacking\" value=\"unzip\"/>\n" : "") + - (ForcePacking == ForcePacking.Zip ? "\t\t\t<flag name=\"forcepacking\" value=\"zip\"/>\n" : "") + - (ForceMerging == ForceMerging.Full ? "\t\t\t<flag name=\"forcemerging\" value=\"full\"/>\n" : "") + - (ForceMerging == ForceMerging.Split ? "\t\t\t<flag name=\"forcemerging\" value=\"split\"/>\n" : "") + - (ForceMerging == ForceMerging.Merged ? "\t\t\t<flag name=\"forcemerging\" value=\"merged\"/>\n" : "") + - (ForceMerging == ForceMerging.NonMerged ? "\t\t\t<flag name=\"forcemerging\" value=\"nonmerged\"/>\n" : "") + - (ForceNodump == ForceNodump.Ignore ? "\t\t\t<flag name=\"forcenodump\" value=\"ignore\"/>\n" : "") + - (ForceNodump == ForceNodump.Obsolete ? "\t\t\t<flag name=\"forcenodump\" value=\"obsolete\"/>\n" : "") + - (ForceNodump == ForceNodump.Required ? "\t\t\t<flag name=\"forcenodump\" value=\"required\"/>\n" : "") + - "\t\t</flags>\n" - : "") + - "\t</header>\n" + - "\t<data>\n"; - break; - case DatFormat.SoftwareList: - header = "<?xml version=\"1.0\"?>\n" + - "<!DOCTYPE softwarelist SYSTEM \"softwarelist.dtd\">\n\n" + - "<softwarelist name=\"" + HttpUtility.HtmlEncode(Name) + "\"" + - " description=\"" + HttpUtility.HtmlEncode(Description) + "\"" + - (ForcePacking == ForcePacking.Unzip ? " forcepacking=\"unzip\"" : "") + - (ForcePacking == ForcePacking.Zip ? " forcepacking=\"zip\"" : "") + - (ForceMerging == ForceMerging.Full ? " forcemerging=\"full\"" : "") + - (ForceMerging == ForceMerging.Split ? " forcemerging=\"split\"" : "") + - (ForceMerging == ForceMerging.Merged ? " forcemerging=\"merged\"" : "") + - (ForceMerging == ForceMerging.NonMerged ? " forcemerging=\"nonmerged\"" : "") + - (ForceNodump == ForceNodump.Ignore ? " forcenodump=\"ignore\"" : "") + - (ForceNodump == ForceNodump.Obsolete ? " forcenodump=\"obsolete\"" : "") + - (ForceNodump == ForceNodump.Required ? " forcenodump=\"required\"" : "") + - ">\n\n"; - break; - } - - // 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> - /// <param name="datFormat">Output format to write to</param> - /// <param name="rom">RomData object to be output</param> - /// <param name="newsplit">Split path representing the parent game (SabreDAT only)</param> - /// <param name="lastgame">The name of the last game to be output</param> - /// <param name="depth">Current depth to output file at (SabreDAT only)</param> - /// <param name="last">Last known depth to cycle back from (SabreDAT only)</param> - /// <returns>The new depth of the tag</returns> - private int WriteStartGame(StreamWriter sw, DatFormat datFormat, DatItem rom, List<string> 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 = ""; - switch (datFormat) - { - case DatFormat.AttractMode: - state += rom.MachineName + ";" - + rom.MachineDescription + ";" - + FileName + ";" - + rom.CloneOf + ";" - + rom.Year + ";" - + rom.Manufacturer + ";" - /* + rom.Category */ + ";" - /* + rom.Players */ + ";" - /* + rom.Rotation */ + ";" - /* + rom.Control */ + ";" - /* + rom.Status */ + ";" - /* + rom.DisplayCount */ + ";" - /* + rom.DisplayType */ + ";" - /* + rom.AltRomname */ + ";" - /* + rom.AltTitle */ + ";" - + rom.Comment + ";" - /* + rom.Buttons */ + "\n"; - break; - case DatFormat.ClrMamePro: - state += "game (\n\tname \"" + rom.MachineName + "\"\n" + - (ExcludeOf ? "" : - (String.IsNullOrEmpty(rom.RomOf) ? "" : "\tromof \"" + rom.RomOf + "\"\n") + - (String.IsNullOrEmpty(rom.CloneOf) ? "" : "\tcloneof \"" + rom.CloneOf + "\"\n") + - (String.IsNullOrEmpty(rom.SampleOf) ? "" : "\tsampleof \"" + rom.SampleOf + "\"\n") - ) + - "\tdescription \"" + (String.IsNullOrEmpty(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription) + "\"\n" + - (String.IsNullOrEmpty(rom.Year) ? "" : "\tyear " + rom.Year + "\n") + - (String.IsNullOrEmpty(rom.Manufacturer) ? "" : "\tmanufacturer \"" + rom.Manufacturer + "\"\n"); - break; - case DatFormat.DOSCenter: - state += "game (\n\tname \"" + rom.MachineName + ".zip\"\n"; - break; - case DatFormat.Listroms: - state += "ROMs required for driver \"" + rom.MachineName + "\".\n" + - "Name Size Checksum\n"; - break; - case DatFormat.Logiqx: - state += "\t<machine name=\"" + HttpUtility.HtmlEncode(rom.MachineName) + "\"" + - (ExcludeOf ? "" : - (rom.MachineType == MachineType.Bios ? " isbios=\"yes\"" : "") + - (rom.MachineType == MachineType.Device ? " isdevice=\"yes\"" : "") + - (rom.MachineType == MachineType.Mechanical ? " ismechanical=\"yes\"" : "") + - (rom.Runnable == true ? " runnable=\"yes\"" : "") + - (String.IsNullOrEmpty(rom.CloneOf) || (rom.MachineName.ToLowerInvariant() == rom.CloneOf.ToLowerInvariant()) - ? "" - : " cloneof=\"" + HttpUtility.HtmlEncode(rom.CloneOf) + "\"") + - (String.IsNullOrEmpty(rom.RomOf) || (rom.MachineName.ToLowerInvariant() == rom.RomOf.ToLowerInvariant()) - ? "" - : " romof=\"" + HttpUtility.HtmlEncode(rom.RomOf) + "\"") + - (String.IsNullOrEmpty(rom.SampleOf) || (rom.MachineName.ToLowerInvariant() == rom.SampleOf.ToLowerInvariant()) - ? "" - : " sampleof=\"" + HttpUtility.HtmlEncode(rom.SampleOf) + "\"") - ) + - ">\n" + - (String.IsNullOrEmpty(rom.Comment) ? "" : "\t\t<comment>" + HttpUtility.HtmlEncode(rom.Comment) + "</comment>\n") + - "\t\t<description>" + HttpUtility.HtmlEncode((String.IsNullOrEmpty(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription)) + "</description>\n" + - (String.IsNullOrEmpty(rom.Year) ? "" : "\t\t<year>" + HttpUtility.HtmlEncode(rom.Year) + "</year>\n") + - (String.IsNullOrEmpty(rom.Manufacturer) ? "" : "\t\t<manufacturer>" + HttpUtility.HtmlEncode(rom.Manufacturer) + "</manufacturer>\n"); - break; - case DatFormat.SabreDat: - 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 += "<directory name=\"" + HttpUtility.HtmlEncode(newsplit[i]) + "\" description=\"" + - HttpUtility.HtmlEncode(newsplit[i]) + "\">\n"; - } - depth = depth - (last == -1 ? 0 : last) + newsplit.Count; - break; - case DatFormat.SoftwareList: - state += "\t<software name=\"" + HttpUtility.HtmlEncode(rom.MachineName) + "\"" - + (rom.Supported != null ? " supported=\"" + (rom.Supported == true ? "yes" : "no") + "\"" : "") + - (ExcludeOf ? "" : - (String.IsNullOrEmpty(rom.CloneOf) || (rom.MachineName.ToLowerInvariant() == rom.CloneOf.ToLowerInvariant()) - ? "" - : " cloneof=\"" + HttpUtility.HtmlEncode(rom.CloneOf) + "\"") + - (String.IsNullOrEmpty(rom.RomOf) || (rom.MachineName.ToLowerInvariant() == rom.RomOf.ToLowerInvariant()) - ? "" - : " romof=\"" + HttpUtility.HtmlEncode(rom.RomOf) + "\"") + - (String.IsNullOrEmpty(rom.SampleOf) || (rom.MachineName.ToLowerInvariant() == rom.SampleOf.ToLowerInvariant()) - ? "" - : " sampleof=\"" + HttpUtility.HtmlEncode(rom.SampleOf) + "\"") - ) + ">\n" - + "\t\t<description>" + HttpUtility.HtmlEncode(rom.MachineDescription) + "</description>\n" - + (rom.Year != null ? "\t\t<year>" + HttpUtility.HtmlEncode(rom.Year) + "</year>\n" : "") - + (rom.Publisher != null ? "\t\t<publisher>" + HttpUtility.HtmlEncode(rom.Publisher) + "</publisher>\n" : ""); - - foreach (Tuple<string, string> kvp in rom.Infos) - { - state += "\t\t<info name=\"" + HttpUtility.HtmlEncode(kvp.Item1) + "\" value=\"" + HttpUtility.HtmlEncode(kvp.Item2) + "\" />\n"; - } - break; - } - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return depth; - } - - return depth; - } - - /// <summary> - /// Write out Game start using the supplied StreamWriter - /// </summary> - /// <param name="sw">StreamWriter to output to</param> - /// <param name="datFormat">Output format to write to</param> - /// <param name="rom">RomData object to be output</param> - /// <param name="splitpath">Split path representing last kwown parent game (SabreDAT only)</param> - /// <param name="newsplit">Split path representing the parent game (SabreDAT only)</param> - /// <param name="lastgame">The name of the last game to be output</param> - /// <param name="depth">Current depth to output file at (SabreDAT only)</param> - /// <param name="last">Last known depth to cycle back from (SabreDAT only)</param> - /// <returns>The new depth of the tag</returns> - private int WriteEndGame(StreamWriter sw, DatFormat datFormat, DatItem rom, List<string> splitpath, List<string> newsplit, string lastgame, int depth, out int last) - { - last = 0; - - try - { - string state = ""; - - switch (datFormat) - { - case DatFormat.ClrMamePro: - case DatFormat.DOSCenter: - state += (String.IsNullOrEmpty(rom.SampleOf) ? "" : "\tsampleof \"" + rom.SampleOf + "\"\n") + ")\n"; - break; - case DatFormat.Listroms: - state += "\n"; - break; - case DatFormat.Logiqx: - state += "\t</machine>\n"; - break; - case DatFormat.OfflineList: - state += "\t\t</game>\n"; - break; - case DatFormat.SabreDat: - 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 += "</directory>\n"; - } - - // Reset the current depth - depth = 2 + last; - } - break; - case DatFormat.SoftwareList: - state += "\t</software>\n\n"; - break; - } - - sw.Write(state); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return depth; - } - - return depth; - } - - /// <summary> - /// Write out RomData using the supplied StreamWriter - /// </summary> - /// <param name="sw">StreamWriter to output to</param> - /// <param name="datFormat">Output format to write to</param> - /// <param name="rom">RomData object to be output</param> - /// <param name="lastgame">The name of the last game to be output</param> - /// <param name="depth">Current depth to output file at (SabreDAT only)</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 WriteRomData(StreamWriter sw, DatFormat datFormat, DatItem rom, string lastgame, int depth, bool ignoreblanks = false) - { - // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip - if (ignoreblanks - && (rom.Type == ItemType.Rom - && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) - { - return true; - } - - try - { - string state = "", name = "", pre = "", post = ""; - switch (datFormat) - { - case DatFormat.ClrMamePro: - switch (rom.Type) - { - case ItemType.Archive: - state += "\tarchive ( name\"" + rom.Name + "\"" - + " )\n"; - break; - case ItemType.BiosSet: - state += "\tbiosset ( name\"" + rom.Name + "\"" - + (!String.IsNullOrEmpty(((BiosSet)rom).Description) ? " description \"" + ((BiosSet)rom).Description + "\"" : "") - + (((BiosSet)rom).Default != null - ? "default " + ((BiosSet)rom).Default.ToString().ToLowerInvariant() - : "") - + " )\n"; - break; - case ItemType.Disk: - state += "\tdisk ( name \"" + rom.Name + "\"" - + (!String.IsNullOrEmpty(((Disk)rom).MD5) ? " md5 " + ((Disk)rom).MD5.ToLowerInvariant() : "") - + (!String.IsNullOrEmpty(((Disk)rom).SHA1) ? " sha1 " + ((Disk)rom).SHA1.ToLowerInvariant() : "") - + (!String.IsNullOrEmpty(((Disk)rom).SHA256) ? " sha256 " + ((Disk)rom).SHA256.ToLowerInvariant() : "") - + (!String.IsNullOrEmpty(((Disk)rom).SHA384) ? " sha384 " + ((Disk)rom).SHA384.ToLowerInvariant() : "") - + (!String.IsNullOrEmpty(((Disk)rom).SHA512) ? " sha512 " + ((Disk)rom).SHA512.ToLowerInvariant() : "") - + (((Disk)rom).ItemStatus != ItemStatus.None ? " flags " + ((Disk)rom).ItemStatus.ToString().ToLowerInvariant() : "") - + " )\n"; - break; - case ItemType.Release: - state += "\trelease ( name\"" + rom.Name + "\"" - + (!String.IsNullOrEmpty(((Release)rom).Region) ? " region \"" + ((Release)rom).Region + "\"" : "") - + (!String.IsNullOrEmpty(((Release)rom).Language) ? " language \"" + ((Release)rom).Language + "\"" : "") - + (!String.IsNullOrEmpty(((Release)rom).Date) ? " date \"" + ((Release)rom).Date + "\"" : "") - + (((Release)rom).Default != null - ? "default " + ((Release)rom).Default.ToString().ToLowerInvariant() - : "") - + " )\n"; - break; - case ItemType.Rom: - state += "\trom ( name \"" + rom.Name + "\"" - + (((Rom)rom).Size != -1 ? " size " + ((Rom)rom).Size : "") - + (!String.IsNullOrEmpty(((Rom)rom).CRC) ? " crc " + ((Rom)rom).CRC.ToLowerInvariant() : "") - + (!String.IsNullOrEmpty(((Rom)rom).MD5) ? " md5 " + ((Rom)rom).MD5.ToLowerInvariant() : "") - + (!String.IsNullOrEmpty(((Rom)rom).SHA1) ? " sha1 " + ((Rom)rom).SHA1.ToLowerInvariant() : "") - + (!String.IsNullOrEmpty(((Rom)rom).SHA256) ? " sha256 " + ((Rom)rom).SHA256.ToLowerInvariant() : "") - + (!String.IsNullOrEmpty(((Rom)rom).SHA384) ? " sha384 " + ((Rom)rom).SHA384.ToLowerInvariant() : "") - + (!String.IsNullOrEmpty(((Rom)rom).SHA512) ? " sha512 " + ((Rom)rom).SHA512.ToLowerInvariant() : "") - + (!String.IsNullOrEmpty(((Rom)rom).Date) ? " date \"" + ((Rom)rom).Date + "\"" : "") - + (((Rom)rom).ItemStatus != ItemStatus.None ? " flags " + ((Rom)rom).ItemStatus.ToString().ToLowerInvariant() : "") - + " )\n"; - break; - case ItemType.Sample: - state += "\tsample ( name\"" + rom.Name + "\"" - + " )\n"; - break; - } - - break; - case DatFormat.CSV: - // CSV should only output Rom and Disk - if (rom.Type != ItemType.Disk && rom.Type != ItemType.Rom) - { - return true; - } - - pre = Prefix + (Quotes ? "\"" : ""); - post = (Quotes ? "\"" : "") + Postfix; - - if (rom.Type == ItemType.Rom) - { - // Check for special strings in prefix and postfix - pre = pre - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", ((Rom)rom).CRC) - .Replace("%md5%", ((Rom)rom).MD5) - .Replace("%sha1%", ((Rom)rom).SHA1) - .Replace("%sha256%", ((Rom)rom).SHA256) - .Replace("%sha384%", ((Rom)rom).SHA384) - .Replace("%sha512%", ((Rom)rom).SHA512) - .Replace("%size%", ((Rom)rom).Size.ToString()); - post = post - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", ((Rom)rom).CRC) - .Replace("%md5%", ((Rom)rom).MD5) - .Replace("%sha1%", ((Rom)rom).SHA1) - .Replace("%sha256%", ((Rom)rom).SHA256) - .Replace("%sha384%", ((Rom)rom).SHA384) - .Replace("%sha512%", ((Rom)rom).SHA512) - .Replace("%size%", ((Rom)rom).Size.ToString()); - } - else if (rom.Type == ItemType.Disk) - { - // Check for special strings in prefix and postfix - pre = pre - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", string.Empty) - .Replace("%md5%", ((Disk)rom).MD5) - .Replace("%sha1%", ((Disk)rom).SHA1) - .Replace("%sha256%", ((Disk)rom).SHA256) - .Replace("%sha384%", ((Disk)rom).SHA384) - .Replace("%sha512%", ((Disk)rom).SHA512) - .Replace("%size%", string.Empty);; - post = post - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", string.Empty) - .Replace("%md5%", ((Disk)rom).MD5) - .Replace("%sha1%", ((Disk)rom).SHA1) - .Replace("%sha256%", ((Disk)rom).SHA256) - .Replace("%sha384%", ((Disk)rom).SHA384) - .Replace("%sha512%", ((Disk)rom).SHA512) - .Replace("%size%", string.Empty);; - } - else - { - // Check for special strings in prefix and postfix - pre = pre - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", string.Empty) - .Replace("%md5%", string.Empty) - .Replace("%sha1%", string.Empty) - .Replace("%size%", string.Empty); - post = post - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", string.Empty) - .Replace("%md5%", string.Empty) - .Replace("%sha1%", string.Empty) - .Replace("%size%", string.Empty); - } - - if (rom.Type == ItemType.Rom) - { - string inline = "\"" + FileName + "\"" - + ",\"" + Name + "\"" - + ",\"" + Description + "\"" - + ",\"" + rom.MachineName + "\"" - + ",\"" + rom.MachineDescription + "\"" - + "," + "\"rom\"" - + ",\"" + rom.Name + "\"" - + "," + "\"\"" - + ",\"" + ((Rom)rom).Size + "\"" - + ",\"" + ((Rom)rom).CRC + "\"" - + ",\"" + ((Rom)rom).MD5 + "\"" - + ",\"" + ((Rom)rom).SHA1 + "\"" - + ",\"" + ((Rom)rom).SHA256 + "\"" - // + ",\"" + ((Rom)rom).SHA384 + "\"" - // + ",\"" + ((Rom)rom).SHA512 + "\"" - + "," + (((Rom)rom).ItemStatus != ItemStatus.None ? "\"" + ((Rom)rom).ItemStatus.ToString() + "\"" : "\"\""); - state += pre + inline + post + "\n"; - } - else if (rom.Type == ItemType.Disk) - { - string inline = "\"" + FileName + "\"" - + ",\"" + Name + "\"" - + ",\"" + Description + "\"" - + ",\"" + rom.MachineName + "\"" - + ",\"" + rom.MachineDescription + "\"" - + "," + "\"disk\"" - + "," + "\"\"" - + ",\"" + rom.Name + "\"" - + "," + "\"\"" - + "," + "\"\"" - + ",\"" + ((Disk)rom).MD5 + "\"" - + ",\"" + ((Disk)rom).SHA1 + "\"" - + ",\"" + ((Disk)rom).SHA256 + "\"" - // + ",\"" + ((Rom)rom).SHA384 + "\"" - // + ",\"" + ((Rom)rom).SHA512 + "\"" - + "," + (((Disk)rom).ItemStatus != ItemStatus.None ? "\"" + ((Disk)rom).ItemStatus.ToString() + "\"" : "\"\""); - state += pre + inline + post + "\n"; - } - break; - case DatFormat.DOSCenter: - switch (rom.Type) - { - case ItemType.Archive: - case ItemType.BiosSet: - case ItemType.Disk: - case ItemType.Release: - case ItemType.Sample: - // We don't output these at all - break; - case ItemType.Rom: - state += "\tfile ( name " + ((Rom)rom).Name - + (((Rom)rom).Size != -1 ? " size " + ((Rom)rom).Size : "") - + (!String.IsNullOrEmpty(((Rom)rom).Date) ? " date " + ((Rom)rom).Date : "") - + (!String.IsNullOrEmpty(((Rom)rom).CRC) ? " crc " + ((Rom)rom).CRC.ToLowerInvariant() : "") - + " )\n"; - break; - } - break; - case DatFormat.Listroms: - switch (rom.Type) - { - 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 (((Disk)rom).ItemStatus == ItemStatus.BadDump) - { - state += " BAD"; - } - - // If we have a nodump, write out the indicator - if (((Disk)rom).ItemStatus == ItemStatus.Nodump) - { - state += " NO GOOD DUMP KNOWN"; - } - // Otherwise, write out the SHA-1 hash - else - { - state += " SHA1(" + ((Disk)rom).SHA1 + ")"; - } - - // If we have a baddump, put the second indicator - if (((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 (((Rom)rom).ItemStatus == ItemStatus.BadDump) - { - state += " BAD"; - } - - // If we have a nodump, write out the indicator - if (((Rom)rom).ItemStatus == ItemStatus.Nodump) - { - state += " NO GOOD DUMP KNOWN"; - } - // Otherwise, write out the CRC and SHA-1 hashes - else - { - state += " CRC(" + ((Rom)rom).CRC + ")"; - state += " SHA1(" + ((Rom)rom).SHA1 + ")"; - } - - // If we have a baddump, put the second indicator - if (((Rom)rom).ItemStatus == ItemStatus.BadDump) - { - state += " BAD_DUMP"; - } - - state += "\n"; - break; - } - break; - case DatFormat.Logiqx: - switch (rom.Type) - { - case ItemType.Archive: - state += "\t\t<archive name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\"" - + "/>\n"; - break; - case ItemType.BiosSet: - state += "\t\t<biosset name\"" + HttpUtility.HtmlEncode(rom.Name) + "\"" - + (!String.IsNullOrEmpty(((BiosSet)rom).Description) ? " description=\"" + HttpUtility.HtmlEncode(((BiosSet)rom).Description) + "\"" : "") - + (((BiosSet)rom).Default != null - ? ((BiosSet)rom).Default.ToString().ToLowerInvariant() - : "") - + "/>\n"; - break; - case ItemType.Disk: - state += "\t\t<disk name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\"" - + (!String.IsNullOrEmpty(((Disk)rom).MD5) ? " md5=\"" + ((Disk)rom).MD5.ToLowerInvariant() + "\"" : "") - + (!String.IsNullOrEmpty(((Disk)rom).SHA1) ? " sha1=\"" + ((Disk)rom).SHA1.ToLowerInvariant() + "\"" : "") - + (!String.IsNullOrEmpty(((Disk)rom).SHA256) ? " sha256=\"" + ((Disk)rom).SHA256.ToLowerInvariant() + "\"" : "") - + (!String.IsNullOrEmpty(((Disk)rom).SHA384) ? " sha384=\"" + ((Disk)rom).SHA384.ToLowerInvariant() + "\"" : "") - + (!String.IsNullOrEmpty(((Disk)rom).SHA512) ? " sha512=\"" + ((Disk)rom).SHA512.ToLowerInvariant() + "\"" : "") - + (((Disk)rom).ItemStatus != ItemStatus.None ? " status=\"" + ((Disk)rom).ItemStatus.ToString().ToLowerInvariant() + "\"" : "") - + "/>\n"; - break; - case ItemType.Release: - state += "\t\t<release name\"" + HttpUtility.HtmlEncode(rom.Name) + "\"" - + (!String.IsNullOrEmpty(((Release)rom).Region) ? " region=\"" + HttpUtility.HtmlEncode(((Release)rom).Region) + "\"" : "") - + (!String.IsNullOrEmpty(((Release)rom).Language) ? " language=\"" + HttpUtility.HtmlEncode(((Release)rom).Language) + "\"" : "") - + (!String.IsNullOrEmpty(((Release)rom).Date) ? " date=\"" + HttpUtility.HtmlEncode(((Release)rom).Date) + "\"" : "") - + (((Release)rom).Default != null - ? ((Release)rom).Default.ToString().ToLowerInvariant() - : "") - + "/>\n"; - break; - case ItemType.Rom: - state += "\t\t<rom name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\"" - + (((Rom)rom).Size != -1 ? " size=\"" + ((Rom)rom).Size + "\"" : "") - + (!String.IsNullOrEmpty(((Rom)rom).CRC) ? " crc=\"" + ((Rom)rom).CRC.ToLowerInvariant() + "\"" : "") - + (!String.IsNullOrEmpty(((Rom)rom).MD5) ? " md5=\"" + ((Rom)rom).MD5.ToLowerInvariant() + "\"" : "") - + (!String.IsNullOrEmpty(((Rom)rom).SHA1) ? " sha1=\"" + ((Rom)rom).SHA1.ToLowerInvariant() + "\"" : "") - + (!String.IsNullOrEmpty(((Rom)rom).SHA256) ? " sha256=\"" + ((Rom)rom).SHA256.ToLowerInvariant() + "\"" : "") - + (!String.IsNullOrEmpty(((Rom)rom).SHA384) ? " sha384=\"" + ((Rom)rom).SHA384.ToLowerInvariant() + "\"" : "") - + (!String.IsNullOrEmpty(((Rom)rom).SHA512) ? " sha512=\"" + ((Rom)rom).SHA512.ToLowerInvariant() + "\"" : "") - + (!String.IsNullOrEmpty(((Rom)rom).Date) ? " date=\"" + ((Rom)rom).Date + "\"" : "") - + (((Rom)rom).ItemStatus != ItemStatus.None ? " status=\"" + ((Rom)rom).ItemStatus.ToString().ToLowerInvariant() + "\"" : "") - + "/>\n"; - break; - case ItemType.Sample: - state += "\t\t<sample name=\"" + HttpUtility.HtmlEncode(rom.Name) + "\"" - + "/>\n"; - break; - } - break; - case DatFormat.MissFile: - pre = Prefix + (Quotes ? "\"" : ""); - post = (Quotes ? "\"" : "") + Postfix; - - if (rom.Type == ItemType.Rom) - { - // Check for special strings in prefix and postfix - pre = pre - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", ((Rom)rom).CRC) - .Replace("%md5%", ((Rom)rom).MD5) - .Replace("%sha1%", ((Rom)rom).SHA1) - .Replace("%sha256%", ((Rom)rom).SHA256) - .Replace("%sha384%", ((Rom)rom).SHA384) - .Replace("%sha512%", ((Rom)rom).SHA512) - .Replace("%size%", ((Rom)rom).Size.ToString()); - post = post - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", ((Rom)rom).CRC) - .Replace("%md5%", ((Rom)rom).MD5) - .Replace("%sha1%", ((Rom)rom).SHA1) - .Replace("%sha256%", ((Rom)rom).SHA256) - .Replace("%sha384%", ((Rom)rom).SHA384) - .Replace("%sha512%", ((Rom)rom).SHA512) - .Replace("%size%", ((Rom)rom).Size.ToString()); - } - else if (rom.Type == ItemType.Disk) - { - // Check for special strings in prefix and postfix - pre = pre - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", string.Empty) - .Replace("%md5%", ((Disk)rom).MD5) - .Replace("%sha1%", ((Disk)rom).SHA1) - .Replace("%sha256%", ((Disk)rom).SHA256) - .Replace("%sha384%", ((Disk)rom).SHA384) - .Replace("%sha512%", ((Disk)rom).SHA512) - .Replace("%size%", string.Empty); - post = post - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", string.Empty) - .Replace("%md5%", ((Disk)rom).MD5) - .Replace("%sha1%", ((Disk)rom).SHA1) - .Replace("%sha256%", ((Disk)rom).SHA256) - .Replace("%sha384%", ((Disk)rom).SHA384) - .Replace("%sha512%", ((Disk)rom).SHA512) - .Replace("%size%", string.Empty); - } - else - { - // Check for special strings in prefix and postfix - pre = pre - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", string.Empty) - .Replace("%md5%", string.Empty) - .Replace("%sha1%", string.Empty) - .Replace("%sha256%", string.Empty) - .Replace("%sha384%", string.Empty) - .Replace("%sha512%", string.Empty) - .Replace("%size%", string.Empty); - post = post - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", string.Empty) - .Replace("%md5%", string.Empty) - .Replace("%sha1%", string.Empty) - .Replace("%sha256%", string.Empty) - .Replace("%sha384%", string.Empty) - .Replace("%sha512%", string.Empty) - .Replace("%size%", string.Empty); - } - - // If we're in Romba mode, the state is consistent - if (Romba) - { - if (rom.Type == ItemType.Rom) - { - // We can only write out if there's a SHA-1 - if (((Rom)rom).SHA1 != "") - { - name = ((Rom)rom).SHA1.Substring(0, 2) - + "/" + ((Rom)rom).SHA1.Substring(2, 2) - + "/" + ((Rom)rom).SHA1.Substring(4, 2) - + "/" + ((Rom)rom).SHA1.Substring(6, 2) - + "/" + ((Rom)rom).SHA1 + ".gz"; - state += pre + name + post + "\n"; - } - } - else if (rom.Type == ItemType.Disk) - { - // We can only write out if there's a SHA-1 - if (((Disk)rom).SHA1 != "") - { - name = ((Disk)rom).SHA1.Substring(0, 2) - + "/" + ((Disk)rom).SHA1.Substring(2, 2) - + "/" + ((Disk)rom).SHA1.Substring(4, 2) - + "/" + ((Disk)rom).SHA1.Substring(6, 2) - + "/" + ((Disk)rom).SHA1 + ".gz"; - state += pre + name + post + "\n"; - } - } - } - - // Otherwise, use any flags - else - { - name = (UseGame ? rom.MachineName : rom.Name); - if (RepExt != "" || RemExt) - { - if (RemExt) - { - RepExt = ""; - } - - string dir = Path.GetDirectoryName(name); - dir = (dir.StartsWith(Path.DirectorySeparatorChar.ToString()) ? dir.Remove(0, 1) : dir); - name = Path.Combine(dir, Path.GetFileNameWithoutExtension(name) + RepExt); - } - if (AddExt != "") - { - name += AddExt; - } - if (!UseGame && GameName) - { - name = Path.Combine(rom.MachineName, name); - } - - if (UseGame && rom.MachineName != lastgame) - { - state += pre + name + post + "\n"; - lastgame = rom.MachineName; - } - else if (!UseGame) - { - state += pre + name + post + "\n"; - } - } - - break; - case DatFormat.OfflineList: - state += "\t\t<game>\n" - + "\t\t\t<imageNumber>1</imageNumber>\n" - + "\t\t\t<releaseNumber>1</releaseNumber>\n" - + "\t\t\t<title>" + HttpUtility.HtmlEncode(rom.Name) + "\n" - + "\t\t\tNone\n"; - - if (rom.Type == ItemType.Rom) - { - state += "\t\t\t" + ((Rom)rom).Size + "\n"; - } - - state += "\t\t\tNone\n" - + "\t\t\t0\n" - + "\t\t\tNone\n" - + "\t\t\t0\n"; - - if (rom.Type == ItemType.Disk) - { - state += "\t\t\t\n" - + (((Disk)rom).MD5 != null - ? "\t\t\t\t" + ((Disk)rom).MD5.ToUpperInvariant() + "\n" - : "\t\t\t\t" + ((Disk)rom).SHA1.ToUpperInvariant() + "\n") - + "\t\t\t\n"; - } - else if (rom.Type == ItemType.Rom) - { - string tempext = Path.GetExtension(((Rom)rom).Name); - if (!tempext.StartsWith(".")) - { - tempext = "." + tempext; - } - - state += "\t\t\t\n" - + (((Rom)rom).CRC != null - ? "\t\t\t\t" + ((Rom)rom).CRC.ToUpperInvariant() + "\n" - : ((Rom)rom).MD5 != null - ? "\t\t\t\t" + ((Rom)rom).MD5.ToUpperInvariant() + "\n" - : "\t\t\t\t" + ((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"; - break; - case DatFormat.RedumpMD5: - if (rom.Type == ItemType.Rom) - { - state += ((Rom)rom).MD5 + " *" + (GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; - } - else if (rom.Type == ItemType.Disk) - { - state += ((Disk)rom).MD5 + " *" + (GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; - } - break; - case DatFormat.RedumpSFV: - if (rom.Type == ItemType.Rom) - { - state += (GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + " " + ((Rom)rom).CRC + "\n"; - } - break; - case DatFormat.RedumpSHA1: - if (rom.Type == ItemType.Rom) - { - state += ((Rom)rom).SHA1 + " *" + (GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; - } - else if (rom.Type == ItemType.Disk) - { - state += ((Disk)rom).SHA1 + " *" + (GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; - } - break; - case DatFormat.RedumpSHA256: - if (rom.Type == ItemType.Rom) - { - state += ((Rom)rom).SHA256 + " *" + (GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; - } - else if (rom.Type == ItemType.Disk) - { - state += ((Disk)rom).SHA256 + " *" + (GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; - } - break; - case DatFormat.RedumpSHA384: - if (rom.Type == ItemType.Rom) - { - state += ((Rom)rom).SHA384 + " *" + (GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; - } - else if (rom.Type == ItemType.Disk) - { - state += ((Disk)rom).SHA384 + " *" + (GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; - } - break; - case DatFormat.RedumpSHA512: - if (rom.Type == ItemType.Rom) - { - state += ((Rom)rom).SHA512 + " *" + (GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; - } - else if (rom.Type == ItemType.Disk) - { - state += ((Disk)rom).SHA512 + " *" + (GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; - } - break; - case DatFormat.RomCenter: - if (rom.Type == ItemType.Rom) - { - state += "¬" + (String.IsNullOrEmpty(rom.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.CloneOf)) + - "¬" + (String.IsNullOrEmpty(rom.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.CloneOf)) + - "¬" + HttpUtility.HtmlEncode(rom.MachineName) + - "¬" + HttpUtility.HtmlEncode((String.IsNullOrEmpty(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription)) + - "¬" + HttpUtility.HtmlEncode(rom.Name) + - "¬" + ((Rom)rom).CRC.ToLowerInvariant() + - "¬" + (((Rom)rom).Size != -1 ? ((Rom)rom).Size.ToString() : "") + "¬¬¬\n"; - } - else if (rom.Type == ItemType.Disk) - { - state += "¬" + (String.IsNullOrEmpty(rom.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.CloneOf)) + - "¬" + (String.IsNullOrEmpty(rom.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.CloneOf)) + - "¬" + HttpUtility.HtmlEncode(rom.MachineName) + - "¬" + HttpUtility.HtmlEncode((String.IsNullOrEmpty(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription)) + - "¬" + HttpUtility.HtmlEncode(rom.Name) + - "¬¬¬¬¬\n"; - } - - break; - case DatFormat.SabreDat: - string prefix = ""; - for (int i = 0; i < depth; i++) - { - prefix += "\t"; - } - state += prefix; - - switch (rom.Type) - { - 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; - } - break; - case DatFormat.SoftwareList: - state += "\t\t\n"; - - foreach (Tuple kvp in rom.Features) - { - state += "\t\t\t\n"; - } - - switch (rom.Type) - { - case ItemType.Archive: - state += "\t\t\t\n" - + "\t\t\t\t\n" - + "\t\t\t\n"; - break; - case ItemType.BiosSet: - state += "\t\t\t\n" - + "\t\t\t\t\n" - + "\t\t\t\n"; - break; - case ItemType.Disk: - state += "\t\t\t\n" - + "\t\t\t\t\n" - + "\t\t\t\n"; - break; - case ItemType.Release: - 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; - case ItemType.Sample: - state += "\t\t\t\n" - + "\t\t\t\t\n" - + "\t\t\t\n"; - break; - } - - state += "\t\t\n"; - break; - case DatFormat.TSV: - // TSV should only output Rom and Disk - if (rom.Type != ItemType.Disk && rom.Type != ItemType.Rom) - { - return true; - } - - pre = Prefix + (Quotes ? "\"" : ""); - post = (Quotes ? "\"" : "") + Postfix; - - if (rom.Type == ItemType.Rom) - { - // Check for special strings in prefix and postfix - pre = pre - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", ((Rom)rom).CRC) - .Replace("%md5%", ((Rom)rom).MD5) - .Replace("%sha1%", ((Rom)rom).SHA1) - .Replace("%sha256%", ((Rom)rom).SHA256) - .Replace("%sha384%", ((Rom)rom).SHA384) - .Replace("%sha512%", ((Rom)rom).SHA512) - .Replace("%size%", ((Rom)rom).Size.ToString()); - post = post - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", ((Rom)rom).CRC) - .Replace("%md5%", ((Rom)rom).MD5) - .Replace("%sha1%", ((Rom)rom).SHA1) - .Replace("%sha256%", ((Rom)rom).SHA256) - .Replace("%sha384%", ((Rom)rom).SHA384) - .Replace("%sha512%", ((Rom)rom).SHA512) - .Replace("%size%", ((Rom)rom).Size.ToString()); - } - else if (rom.Type == ItemType.Disk) - { - // Check for special strings in prefix and postfix - pre = pre - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", string.Empty) - .Replace("%md5%", ((Disk)rom).MD5) - .Replace("%sha1%", ((Disk)rom).SHA1) - .Replace("%sha256%", ((Disk)rom).SHA256) - .Replace("%sha384%", ((Disk)rom).SHA384) - .Replace("%sha512%", ((Disk)rom).SHA512) - .Replace("%size%", string.Empty); - post = post - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", string.Empty) - .Replace("%md5%", ((Disk)rom).MD5) - .Replace("%sha1%", ((Disk)rom).SHA1) - .Replace("%sha256%", ((Disk)rom).SHA256) - .Replace("%sha384%", ((Disk)rom).SHA384) - .Replace("%sha512%", ((Disk)rom).SHA512) - .Replace("%size%", string.Empty);; - } - else - { - // Check for special strings in prefix and postfix - pre = pre - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", string.Empty) - .Replace("%md5%", string.Empty) - .Replace("%sha1%", string.Empty) - .Replace("%sha256%", string.Empty) - .Replace("%sha384%", string.Empty) - .Replace("%sha512%", string.Empty) - .Replace("%size%", string.Empty); - post = post - .Replace("%game%", rom.MachineName) - .Replace("%name%", rom.Name) - .Replace("%crc%", string.Empty) - .Replace("%md5%", string.Empty) - .Replace("%sha1%", string.Empty) - .Replace("%sha256%", string.Empty) - .Replace("%sha384%", string.Empty) - .Replace("%sha512%", string.Empty) - .Replace("%size%", string.Empty);; - } - - - if (rom.Type == ItemType.Rom) - { - string inline = "\"" + FileName + "\"" - + "\t\"" + Name + "\"" - + "\t\"" + Description + "\"" - + "\t\"" + rom.MachineName + "\"" - + "\t\"" + rom.MachineDescription + "\"" - + "\t" + "\"rom\"" - + "\t\"" + rom.Name + "\"" - + "\t" + "\"\"" - + "\t\"" + ((Rom)rom).Size + "\"" - + "\t\"" + ((Rom)rom).CRC + "\"" - + "\t\"" + ((Rom)rom).MD5 + "\"" - + "\t\"" + ((Rom)rom).SHA1 + "\"" - + "\t\"" + ((Rom)rom).SHA256 + "\"" - // + "\t\"" + ((Rom)rom).SHA384 + "\"" - // + "\t\"" + ((Rom)rom).SHA512 + "\"" - + "\t" + (((Rom)rom).ItemStatus != ItemStatus.None ? "\"" + ((Rom)rom).ItemStatus.ToString() + "\"" : "\"\""); - state += pre + inline + post + "\n"; - } - else if (rom.Type == ItemType.Disk) - { - string inline = "\"" + FileName + "\"" - + "\t\"" + Name + "\"" - + "\t\"" + Description + "\"" - + "\t\"" + rom.MachineName + "\"" - + "\t\"" + rom.MachineDescription + "\"" - + "\t" + "\"disk\"" - + "\t" + "\"\"" - + "\t\"" + rom.Name + "\"" - + "\t" + "\"\"" - + "\t" + "\"\"" - + "\t\"" + ((Disk)rom).MD5 + "\"" - + "\t\"" + ((Disk)rom).SHA1 + "\"" - + "\t\"" + ((Disk)rom).SHA256 + "\"" - // + "\t\"" + ((Disk)rom).SHA384 + "\"" - // + "\t\"" + ((Disk)rom).SHA512 + "\"" - + "\t" + (((Disk)rom).ItemStatus != ItemStatus.None ? "\"" + ((Disk)rom).ItemStatus.ToString() + "\"" : "\"\""); - state += pre + inline + post + "\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 - /// Output format to write to - /// Current depth to output file at (SabreDAT only) - /// True if the data was written, false on error - private bool WriteFooter(StreamWriter sw, DatFormat datFormat, int depth) - { - try - { - string footer = ""; - - // Output the proper footer - switch (datFormat) - { - case DatFormat.ClrMamePro: - case DatFormat.DOSCenter: - footer = ")\n"; - break; - case DatFormat.Logiqx: - footer = "\t\n
\n"; - break; - case DatFormat.OfflineList: - footer = "\t\t" - + "\t\n" - + "\t\n" - + "\t\t\n" - + "\t\t\t\n" - + "\t\t\t\n" - + "\t\t\n" - + "\t\n" - + ""; - break; - case DatFormat.SabreDat: - 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"; - break; - case DatFormat.SoftwareList: - footer = "\t\n\n\n"; - break; - } - - // Write the footer out - sw.Write(footer); - sw.Flush(); - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - - return true; - } - - #endregion - } -} diff --git a/SabreTools.Library/DatFiles/DatFile.cs b/SabreTools.Library/DatFiles/DatFile.cs index 91d574fd..0960070b 100644 --- a/SabreTools.Library/DatFiles/DatFile.cs +++ b/SabreTools.Library/DatFiles/DatFile.cs @@ -1,8 +1,20 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using SabreTools.Library.Data; using SabreTools.Library.Items; +using SabreTools.Library.Tools; + +#if MONO +using System.IO; +#else +using Alphaleonis.Win32.Filesystem; + +using FileStream = System.IO.FileStream; +using StreamWriter = System.IO.StreamWriter; +#endif namespace SabreTools.Library.DatFiles { @@ -1441,6 +1453,538 @@ namespace SabreTools.Library.DatFiles #endregion + #region Parsing + + /// + /// Parse a 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 + /// The DatData object representing found roms to this point + /// 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) + /// True if descriptions should be used as names, false otherwise (default) + /// True if original extension should be kept, false otherwise (default) + /// True if tags from the DAT should be used to merge the output, false otherwise (default) + public void Parse(string filename, int sysid, int srcid, bool keep = false, bool clean = false, + bool remUnicode = false, bool descAsName = false, bool keepext = false, bool useTags = false) + { + Parse(filename, sysid, srcid, SplitType.None, keep: keep, clean: clean, + remUnicode: remUnicode, descAsName: descAsName, keepext: keepext, useTags: useTags); + } + + /// + /// Parse a 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> + /// Type of the split that should be performed (split, merged, fully merged) + /// 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) + /// True if descriptions should be used as names, false otherwise (default) + /// True if original extension should be kept, false otherwise (default) + /// True if tags from the DAT should be used to merge the output, false otherwise (default) + public void Parse( + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Rom renaming + SplitType splitType, + + // Miscellaneous + bool keep = false, + bool clean = false, + bool remUnicode = false, + bool descAsName = false, + bool keepext = false, + bool useTags = false) + { + // Check the file extension first as a safeguard + string ext = Path.GetExtension(filename).ToLowerInvariant(); + if (ext.StartsWith(".")) + { + ext = ext.Substring(1); + } + if (ext != "dat" && ext != "csv" && ext != "md5" && ext != "sfv" && ext != "sha1" && ext != "sha256" + && ext != "sha384" && ext != "sha512" && ext != "tsv" && ext != "txt" && ext != "xml") + { + return; + } + + // If the output filename isn't set already, get the internal filename + FileName = (String.IsNullOrEmpty(FileName) ? (keepext ? Path.GetFileName(filename) : Path.GetFileNameWithoutExtension(filename)) : FileName); + + // If the output type isn't set already, get the internal output type + DatFormat = (DatFormat == 0 ? FileTools.GetDatFormat(filename) : DatFormat); + + // Now parse the correct type of DAT + try + { + switch (FileTools.GetDatFormat(filename)) + { + case DatFormat.AttractMode: + AttractMode.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); + break; + case DatFormat.ClrMamePro: + ClrMamePro.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); + break; + case DatFormat.CSV: + SeparatedValue.Parse(this, filename, sysid, srcid, ',', keep, clean, remUnicode); + break; + case DatFormat.DOSCenter: + DosCenter.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); + break; + case DatFormat.Listroms: + Listroms.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); + break; + case DatFormat.Logiqx: + Logiqx.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); + break; + case DatFormat.OfflineList: + OfflineList.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); + break; + case DatFormat.RedumpMD5: + Hashfile.Parse(this, filename, sysid, srcid, Hash.MD5, clean, remUnicode); + break; + case DatFormat.RedumpSFV: + Hashfile.Parse(this, filename, sysid, srcid, Hash.CRC, clean, remUnicode); + break; + case DatFormat.RedumpSHA1: + Hashfile.Parse(this, filename, sysid, srcid, Hash.SHA1, clean, remUnicode); + break; + case DatFormat.RedumpSHA256: + Hashfile.Parse(this, filename, sysid, srcid, Hash.SHA256, clean, remUnicode); + break; + case DatFormat.RedumpSHA384: + Hashfile.Parse(this, filename, sysid, srcid, Hash.SHA384, clean, remUnicode); + break; + case DatFormat.RedumpSHA512: + Hashfile.Parse(this, filename, sysid, srcid, Hash.SHA512, clean, remUnicode); + break; + case DatFormat.RomCenter: + RomCenter.Parse(this, filename, sysid, srcid, clean, remUnicode); + break; + case DatFormat.SabreDat: + SabreDat.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); + break; + case DatFormat.SoftwareList: + SoftwareList.Parse(this, filename, sysid, srcid, keep, clean, remUnicode); + break; + case DatFormat.TSV: + SeparatedValue.Parse(this, filename, sysid, srcid, '\t', keep, clean, remUnicode); + break; + default: + return; + } + } + catch (Exception ex) + { + Globals.Logger.Error("Error with file '{0}': {1}", filename, ex); + } + + // If we want to use descriptions as names, update everything + if (descAsName) + { + MachineDescriptionToName(); + } + + // If we are using tags from the DAT, set the proper input for split type unless overridden + if (useTags && splitType == SplitType.None) + { + switch (ForceMerging) + { + case ForceMerging.None: + // No-op + break; + case ForceMerging.Split: + splitType = SplitType.Split; + break; + case ForceMerging.Merged: + splitType = SplitType.Merged; + break; + case ForceMerging.NonMerged: + splitType = SplitType.NonMerged; + break; + case ForceMerging.Full: + splitType = SplitType.FullNonMerged; + break; + } + } + + // Now we pre-process the DAT with the splitting/merging mode + switch (splitType) + { + case SplitType.None: + // No-op + break; + case SplitType.DeviceNonMerged: + CreateDeviceNonMergedSets(DedupeType.None); + break; + case SplitType.FullNonMerged: + CreateFullyNonMergedSets(DedupeType.None); + break; + case SplitType.NonMerged: + CreateNonMergedSets(DedupeType.None); + break; + case SplitType.Merged: + CreateMergedSets(DedupeType.None); + break; + case SplitType.Split: + CreateSplitSets(DedupeType.None); + break; + } + } + + /// + /// Add a rom to the Dat after checking + /// + /// Item data to check against + /// True if the names should be cleaned to WoD standards, false otherwise + /// True if we should remove non-ASCII characters from output, false otherwise (default) + /// The key for the item + public string ParseAddHelper(DatItem item, bool clean, bool remUnicode) + { + string key = ""; + + // If there's no name in the rom, we log and skip it + if (item.Name == null) + { + Globals.Logger.Warning("{0}: Rom with no name found! Skipping...", FileName); + return key; + } + + // If the name ends with a directory separator, we log and skip it (DOSCenter only?) + if (item.Name.EndsWith("/") || item.Name.EndsWith("\\")) + { + Globals.Logger.Warning("{0}: Rom ending with directory separator found: '{1}'. Skipping...", FileName, item.Name); + return key; + } + + // If we're in cleaning mode, sanitize the game name + item.MachineName = (clean ? Style.CleanGameName(item.MachineName) : item.MachineName); + + // If we're stripping unicode characters, do so from all relevant things + if (remUnicode) + { + item.Name = Style.RemoveUnicodeCharacters(item.Name); + item.MachineName = Style.RemoveUnicodeCharacters(item.MachineName); + item.MachineDescription = Style.RemoveUnicodeCharacters(item.MachineDescription); + } + + // If we have a Rom or a Disk, clean the hash data + if (item.Type == ItemType.Rom) + { + Rom itemRom = (Rom)item; + + // Sanitize the hashes from null, hex sizes, and "true blank" strings + itemRom.CRC = Style.CleanHashData(itemRom.CRC, Constants.CRCLength); + itemRom.MD5 = Style.CleanHashData(itemRom.MD5, Constants.MD5Length); + itemRom.SHA1 = Style.CleanHashData(itemRom.SHA1, Constants.SHA1Length); + itemRom.SHA256 = Style.CleanHashData(itemRom.SHA256, Constants.SHA256Length); + itemRom.SHA384 = Style.CleanHashData(itemRom.SHA384, Constants.SHA384Length); + itemRom.SHA512 = Style.CleanHashData(itemRom.SHA512, Constants.SHA512Length); + + // If we have a rom and it's missing size AND the hashes match a 0-byte file, fill in the rest of the info + if ((itemRom.Size == 0 || itemRom.Size == -1) + && ((itemRom.CRC == Constants.CRCZero || String.IsNullOrEmpty(itemRom.CRC)) + || itemRom.MD5 == Constants.MD5Zero + || itemRom.SHA1 == Constants.SHA1Zero + || itemRom.SHA256 == Constants.SHA256Zero + || itemRom.SHA384 == Constants.SHA384Zero + || itemRom.SHA512 == Constants.SHA512Zero)) + { + // TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually + itemRom.Size = Constants.SizeZero; + itemRom.CRC = Constants.CRCZero; + itemRom.MD5 = Constants.MD5Zero; + itemRom.SHA1 = Constants.SHA1Zero; + itemRom.SHA256 = null; + itemRom.SHA384 = null; + itemRom.SHA512 = null; + //itemRom.SHA256 = Constants.SHA256Zero; + //itemRom.SHA384 = Constants.SHA384Zero; + //itemRom.SHA512 = Constants.SHA512Zero; + } + // If the file has no size and it's not the above case, skip and log + else if (itemRom.ItemStatus != ItemStatus.Nodump && (itemRom.Size == 0 || itemRom.Size == -1)) + { + Globals.Logger.Verbose("{0}: Incomplete entry for '{1}' will be output as nodump", FileName, itemRom.Name); + itemRom.ItemStatus = ItemStatus.Nodump; + } + // If the file has a size but aboslutely no hashes, skip and log + else if (itemRom.ItemStatus != ItemStatus.Nodump + && itemRom.Size > 0 + && String.IsNullOrEmpty(itemRom.CRC) + && String.IsNullOrEmpty(itemRom.MD5) + && String.IsNullOrEmpty(itemRom.SHA1) + && String.IsNullOrEmpty(itemRom.SHA256) + && String.IsNullOrEmpty(itemRom.SHA384) + && String.IsNullOrEmpty(itemRom.SHA512)) + { + Globals.Logger.Verbose("{0}: Incomplete entry for '{1}' will be output as nodump", FileName, itemRom.Name); + itemRom.ItemStatus = ItemStatus.Nodump; + } + + item = itemRom; + } + else if (item.Type == ItemType.Disk) + { + Disk itemDisk = (Disk)item; + + // Sanitize the hashes from null, hex sizes, and "true blank" strings + itemDisk.MD5 = Style.CleanHashData(itemDisk.MD5, Constants.MD5Length); + itemDisk.SHA1 = Style.CleanHashData(itemDisk.SHA1, Constants.SHA1Length); + itemDisk.SHA256 = Style.CleanHashData(itemDisk.SHA256, Constants.SHA256Length); + itemDisk.SHA384 = Style.CleanHashData(itemDisk.SHA384, Constants.SHA384Length); + itemDisk.SHA512 = Style.CleanHashData(itemDisk.SHA512, Constants.SHA512Length); + + // If the file has aboslutely no hashes, skip and log + if (itemDisk.ItemStatus != ItemStatus.Nodump + && String.IsNullOrEmpty(itemDisk.MD5) + && String.IsNullOrEmpty(itemDisk.SHA1) + && String.IsNullOrEmpty(itemDisk.SHA256) + && String.IsNullOrEmpty(itemDisk.SHA384) + && String.IsNullOrEmpty(itemDisk.SHA512)) + { + Globals.Logger.Verbose("Incomplete entry for '{0}' will be output as nodump", itemDisk.Name); + itemDisk.ItemStatus = ItemStatus.Nodump; + } + + item = itemDisk; + } + + // Get the key and add statistical data + switch (item.Type) + { + case ItemType.Archive: + case ItemType.BiosSet: + case ItemType.Release: + case ItemType.Sample: + key = item.Type.ToString(); + break; + case ItemType.Disk: + key = ((Disk)item).MD5; + break; + case ItemType.Rom: + key = ((Rom)item).Size + "-" + ((Rom)item).CRC; + break; + default: + key = "default"; + break; + } + + // Add the item to the DAT + Add(key, item); + + return key; + } + + /// + /// Add a rom to the Dat after checking + /// + /// Item data to check against + /// True if the names should be cleaned to WoD standards, false otherwise + /// True if we should remove non-ASCII characters from output, false otherwise (default) + /// The key for the item + public async Task ParseAddHelperAsync(DatItem item, bool clean, bool remUnicode) + { + return await Task.Run(() => ParseAddHelper(item, clean, remUnicode)); + } + + #endregion + + #region Writing + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// All information for creating the datfile header + /// Set the output directory + /// True if games should only be compared on game and file name (default), false if system and source are counted + /// True if DAT statistics should be output on write, false otherwise (default) + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if files should be overwritten (default), false if they should be renamed instead + /// True if the DAT was written correctly, false otherwise + public bool WriteToFile(string outDir, bool norename = true, bool stats = false, bool ignoreblanks = false, bool overwrite = true) + { + // If there's nothing there, abort + if (Count == 0) + { + Globals.Logger.User("There were no items to write out!"); + return false; + } + + // If output directory is empty, use the current folder + if (outDir == null || outDir.Trim() == "") + { + Globals.Logger.Verbose("No output directory defined, defaulting to curent folder"); + outDir = Environment.CurrentDirectory; + } + + // Create the output directory if it doesn't already exist + if (!Directory.Exists(outDir)) + { + Directory.CreateDirectory(outDir); + } + + // If the DAT has no output format, default to XML + if (DatFormat == 0) + { + Globals.Logger.Verbose("No DAT format defined, defaulting to XML"); + DatFormat = DatFormat.Logiqx; + } + + // Make sure that the three essential fields are filled in + if (String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description)) + { + FileName = Name = Description = "Default"; + } + else if (String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description)) + { + FileName = Name = Description; + } + else if (String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description)) + { + FileName = Description = Name; + } + else if (String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description)) + { + FileName = Description; + } + else if (!String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description)) + { + Name = Description = FileName; + } + else if (!String.IsNullOrEmpty(FileName) && String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description)) + { + Name = Description; + } + else if (!String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && String.IsNullOrEmpty(Description)) + { + Description = Name; + } + else if (!String.IsNullOrEmpty(FileName) && !String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Description)) + { + // Nothing is needed + } + + // Output initial statistics, for kicks + if (stats) + { + OutputStats(new Dictionary(), StatDatFormat.None, + recalculate: (RomCount + DiskCount == 0), baddumpCol: true, nodumpCol: true); + } + + // Bucket and dedupe according to the flag + if (DedupeRoms == DedupeType.Full) + { + BucketBy(SortedBy.CRC, DedupeRoms, norename: norename); + } + else if (DedupeRoms == DedupeType.Game) + { + BucketBy(SortedBy.Game, DedupeRoms, norename: norename); + } + + // Bucket roms by game name, if not already + BucketBy(SortedBy.Game, DedupeType.None, norename: norename); + + // Output the number of items we're going to be writing + Globals.Logger.User("A total of {0} items will be written out to '{1}'", Count, FileName); + + // Filter the DAT by 1G1R rules, if we're supposed to + // TODO: Create 1G1R logic before write + + // If we are removing hashes, do that now + if (StripHash != 0x0) + { + StripHashesFromItems(); + } + + // Get the outfile names + Dictionary outfiles = Style.CreateOutfileNames(outDir, this, overwrite); + + try + { + // Write out all required formats + Parallel.ForEach(outfiles.Keys, Globals.ParallelOptions, datFormat => + { + string outfile = outfiles[datFormat]; + switch (datFormat) + { + case DatFormat.AttractMode: + AttractMode.WriteToFile(this, outfile); + break; + case DatFormat.ClrMamePro: + ClrMamePro.WriteToFile(this, outfile, ignoreblanks); + break; + case DatFormat.CSV: + SeparatedValue.WriteToFile(this, outfile, ',', ignoreblanks); + break; + case DatFormat.DOSCenter: + DosCenter.WriteToFile(this, outfile, ignoreblanks); + break; + case DatFormat.Listroms: + Listroms.WriteToFile(this, outfile, ignoreblanks); + break; + case DatFormat.Logiqx: + Logiqx.WriteToFile(this, outfile, ignoreblanks); + break; + case DatFormat.MissFile: + Missfile.WriteToFile(this, outfile, ignoreblanks); + break; + case DatFormat.OfflineList: + OfflineList.WriteToFile(this, outfile, ignoreblanks); + break; + case DatFormat.RedumpMD5: + Hashfile.WriteToFile(this, outfile, Hash.MD5, ignoreblanks); + break; + case DatFormat.RedumpSFV: + Hashfile.WriteToFile(this, outfile, Hash.CRC, ignoreblanks); + break; + case DatFormat.RedumpSHA1: + Hashfile.WriteToFile(this, outfile, Hash.SHA1, ignoreblanks); + break; + case DatFormat.RedumpSHA256: + Hashfile.WriteToFile(this, outfile, Hash.SHA256, ignoreblanks); + break; + case DatFormat.RedumpSHA384: + Hashfile.WriteToFile(this, outfile, Hash.SHA384, ignoreblanks); + break; + case DatFormat.RedumpSHA512: + Hashfile.WriteToFile(this, outfile, Hash.SHA512, ignoreblanks); + break; + case DatFormat.RomCenter: + RomCenter.WriteToFile(this, outfile, ignoreblanks); + break; + case DatFormat.SabreDat: + SabreDat.WriteToFile(this, outfile, ignoreblanks); + break; + case DatFormat.SoftwareList: + SoftwareList.WriteToFile(this, outfile, ignoreblanks); + break; + case DatFormat.TSV: + SeparatedValue.WriteToFile(this, outfile, '\t', ignoreblanks); + break; + } + }); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + #endregion + #endregion // Instance Methods } } diff --git a/SabreTools.Library/DatFiles/DosCenter.cs b/SabreTools.Library/DatFiles/DosCenter.cs new file mode 100644 index 00000000..5cd5018a --- /dev/null +++ b/SabreTools.Library/DatFiles/DosCenter.cs @@ -0,0 +1,322 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using SabreTools.Library.Data; +using SabreTools.Library.Items; +using SabreTools.Library.Tools; + +#if MONO +using System.IO; +#else +using Alphaleonis.Win32.Filesystem; + +using FileStream = System.IO.FileStream; +using StreamWriter = System.IO.StreamWriter; +#endif +using NaturalSort; + +namespace SabreTools.Library.DatFiles +{ + /// + /// Represents parsing and writing of a DosCenter DAT + /// + public class DosCenter + { + /// + /// Parse a DosCenter DAT and return all found games and roms within + /// + /// DatFile to populate with the read information + /// 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 static void Parse( + DatFile datFile, + + // 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 + ClrMamePro.Parse(datFile, filename, sysid, srcid, keep, clean, remUnicode); + } + + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// DatFile to write out from + /// 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 static bool WriteToFile(DatFile datFile, string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = FileTools.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(true)); + + // Write out the header + WriteHeader(datFile, sw); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = datFile.Keys.ToList(); + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = datFile[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()) + { + 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.Type == 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 + WriteRomData(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 + /// + /// DatFile to write out from + /// StreamWriter to output to + /// True if the data was written, false on error + private static bool WriteHeader(DatFile datFile, StreamWriter sw) + { + try + { + string header = "DOSCenter (\n" + + "\tName: " + datFile.Name + "\n" + + "\tDescription: " + datFile.Description + "\n" + + "\tVersion: " + datFile.Version + "\n" + + "\tDate: " + datFile.Date + "\n" + + "\tAuthor: " + datFile.Author + "\n" + + "\tHomepage: " + datFile.Homepage + "\n" + + "\tComment: " + datFile.Comment + "\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 + /// RomData object to be output + /// True if the data was written, false on error + private static 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 \"" + rom.MachineName + ".zip\"\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 + /// RomData object to be output + /// True if the data was written, false on error + private static bool WriteEndGame(StreamWriter sw, DatItem rom) + { + try + { + string state = (String.IsNullOrEmpty(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 RomData using the supplied StreamWriter + /// + /// StreamWriter to output to + /// RomData 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 static bool WriteRomData(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.Type == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + switch (rom.Type) + { + 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 " + ((Rom)rom).Name + + (((Rom)rom).Size != -1 ? " size " + ((Rom)rom).Size : "") + + (!String.IsNullOrEmpty(((Rom)rom).Date) ? " date " + ((Rom)rom).Date : "") + + (!String.IsNullOrEmpty(((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; + } + + return true; + } + + /// + /// Write out DAT footer using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private static 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; + } + + return true; + } + } +} diff --git a/SabreTools.Library/DatFiles/Hashfile.cs b/SabreTools.Library/DatFiles/Hashfile.cs new file mode 100644 index 00000000..5f471f07 --- /dev/null +++ b/SabreTools.Library/DatFiles/Hashfile.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using SabreTools.Library.Data; +using SabreTools.Library.Items; +using SabreTools.Library.Tools; + +#if MONO +using System.IO; +#else +using Alphaleonis.Win32.Filesystem; + +using FileStream = System.IO.FileStream; +using StreamReader = System.IO.StreamReader; +using StreamWriter = System.IO.StreamWriter; +#endif +using NaturalSort; + +namespace SabreTools.Library.DatFiles +{ + /// + /// Represents parsing and writing of a hashfile such as an SFV, MD5, or SHA-1 file + /// + public class Hashfile + { + /// + /// Parse a hashfile or SFV and return all found games and roms within + /// + /// DatFile to populate with the read information + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// Hash type that should be assumed + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + public static void Parse( + DatFile datFile, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Specific to hash files + Hash hashtype, + + // Miscellaneous + bool clean, + bool remUnicode) + { + // Open a file reader + Encoding enc = Style.GetEncoding(filename); + StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc); + + while (!sr.EndOfStream) + { + string line = sr.ReadLine(); + + // 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 ((hashtype & 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 = ((hashtype & Hash.CRC) != 0 ? hash : null), + MD5 = ((hashtype & Hash.MD5) != 0 ? hash : null), + SHA1 = ((hashtype & Hash.SHA1) != 0 ? hash : null), + SHA256 = ((hashtype & Hash.SHA256) != 0 ? hash : null), + SHA384 = ((hashtype & Hash.SHA384) != 0 ? hash : null), + SHA512 = ((hashtype & Hash.SHA512) != 0 ? hash : null), + ItemStatus = ItemStatus.None, + + MachineName = Path.GetFileNameWithoutExtension(filename), + + SystemID = sysid, + SourceID = srcid, + }; + + // Now process and add the rom + datFile.ParseAddHelper(rom, clean, remUnicode); + } + + sr.Dispose(); + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// DatFile to write out from + /// Name of the file to write to + /// Hash that should be written out + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public static bool WriteToFile(DatFile datFile, string outfile, Hash hash, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = FileTools.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(true)); + + // Get a properly sorted set of keys + List keys = datFile.Keys.ToList(); + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = datFile[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 "null" game (created by DATFromDir or something similar), log it to file + if (rom.Type == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + } + + // Now, output the rom data + WriteRomData(datFile, sw, hash, rom, ignoreblanks); + } + } + + 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 RomData using the supplied StreamWriter + /// + /// DatFile to write out from + /// StreamWriter to output to + /// Hash that should be written out + /// RomData 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 static bool WriteRomData(DatFile datFile, StreamWriter sw, Hash hash, DatItem rom, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.Type == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + switch (hash) + { + case Hash.MD5: + if (rom.Type == ItemType.Rom) + { + state += ((Rom)rom).MD5 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; + } + else if (rom.Type == ItemType.Disk) + { + state += ((Disk)rom).MD5 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; + } + break; + case Hash.CRC: + if (rom.Type == ItemType.Rom) + { + state += (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + " " + ((Rom)rom).CRC + "\n"; + } + break; + case Hash.SHA1: + if (rom.Type == ItemType.Rom) + { + state += ((Rom)rom).SHA1 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; + } + else if (rom.Type == ItemType.Disk) + { + state += ((Disk)rom).SHA1 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; + } + break; + case Hash.SHA256: + if (rom.Type == ItemType.Rom) + { + state += ((Rom)rom).SHA256 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; + } + else if (rom.Type == ItemType.Disk) + { + state += ((Disk)rom).SHA256 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; + } + break; + case Hash.SHA384: + if (rom.Type == ItemType.Rom) + { + state += ((Rom)rom).SHA384 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; + } + else if (rom.Type == ItemType.Disk) + { + state += ((Disk)rom).SHA384 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; + } + break; + case Hash.SHA512: + if (rom.Type == ItemType.Rom) + { + state += ((Rom)rom).SHA512 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\n"; + } + else if (rom.Type == ItemType.Disk) + { + state += ((Disk)rom).SHA512 + " *" + (datFile.GameName ? rom.MachineName + Path.DirectorySeparatorChar : "") + rom.Name + "\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/Listroms.cs b/SabreTools.Library/DatFiles/Listroms.cs new file mode 100644 index 00000000..692c94d3 --- /dev/null +++ b/SabreTools.Library/DatFiles/Listroms.cs @@ -0,0 +1,508 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +using SabreTools.Library.Data; +using SabreTools.Library.Items; +using SabreTools.Library.Tools; + +#if MONO +using System.IO; +#else +using Alphaleonis.Win32.Filesystem; + +using FileStream = System.IO.FileStream; +using StreamReader = System.IO.StreamReader; +using StreamWriter = System.IO.StreamWriter; +#endif +using NaturalSort; + +namespace SabreTools.Library.DatFiles +{ + /// + /// Represents parsing and writing of a MAME Listroms DAT + /// + public class Listroms + { + /// + /// Parse a MAME Listroms DAT and return all found games and roms within + /// + /// DatFile to populate with the read information + /// 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 listroms 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 static void Parse( + DatFile datFile, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Open a file reader + Encoding enc = Style.GetEncoding(filename); + StreamReader sr = new StreamReader(FileTools.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.IsNullOrEmpty(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 = Style.CleanListromHashData(split[0]), + + MachineName = gamename, + }; + + datFile.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 = Style.CleanListromHashData(split[1]), + ItemStatus = ItemStatus.BadDump, + + MachineName = gamename, + }; + + datFile.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 = Style.CleanListromHashData(split[1]), + SHA1 = Style.CleanListromHashData(split[2]), + + MachineName = gamename, + }; + + datFile.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, + }; + + datFile.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 = Style.CleanListromHashData(split[2]), + SHA1 = Style.CleanListromHashData(split[3]), + ItemStatus = ItemStatus.BadDump, + + MachineName = gamename, + }; + + datFile.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, + }; + + datFile.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 + /// + /// DatFile to write out from + /// 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 static bool WriteToFile(DatFile datFile, string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = FileTools.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(true)); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = datFile.Keys.ToList(); + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = datFile[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.Type == 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 + WriteRomData(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 + /// RomData object to be output + /// True if the data was written, false on error + private static 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 \"" + 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 static 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 RomData using the supplied StreamWriter + /// + /// StreamWriter to output to + /// RomData 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 static bool WriteRomData(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.Type == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + switch (rom.Type) + { + 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 (((Disk)rom).ItemStatus == ItemStatus.BadDump) + { + state += " BAD"; + } + + // If we have a nodump, write out the indicator + if (((Disk)rom).ItemStatus == ItemStatus.Nodump) + { + state += " NO GOOD DUMP KNOWN"; + } + // Otherwise, write out the SHA-1 hash + else + { + state += " SHA1(" + ((Disk)rom).SHA1 + ")"; + } + + // If we have a baddump, put the second indicator + if (((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 (((Rom)rom).ItemStatus == ItemStatus.BadDump) + { + state += " BAD"; + } + + // If we have a nodump, write out the indicator + if (((Rom)rom).ItemStatus == ItemStatus.Nodump) + { + state += " NO GOOD DUMP KNOWN"; + } + // Otherwise, write out the CRC and SHA-1 hashes + else + { + state += " CRC(" + ((Rom)rom).CRC + ")"; + state += " SHA1(" + ((Rom)rom).SHA1 + ")"; + } + + // If we have a baddump, put the second indicator + if (((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/Logiqx.cs b/SabreTools.Library/DatFiles/Logiqx.cs new file mode 100644 index 00000000..dd46e3e9 --- /dev/null +++ b/SabreTools.Library/DatFiles/Logiqx.cs @@ -0,0 +1,1508 @@ +using System; +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.Items; +using SabreTools.Library.Tools; + +#if MONO +using System.IO; +#else +using Alphaleonis.Win32.Filesystem; + +using FileStream = System.IO.FileStream; +using StreamWriter = System.IO.StreamWriter; +#endif +using NaturalSort; + +namespace SabreTools.Library.DatFiles +{ + /// + /// Represents parsing and writing of a Logiqx-derived DAT + /// + public class Logiqx + { + /// + /// Parse a Logiqx XML DAT and return all found games and roms within + /// + /// DatFile to populate with the read information + /// 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 static void Parse( + DatFile datFile, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Prepare all internal variables + XmlReader subreader, headreader, flagreader; + bool superdat = false, empty = true; + string key = "", date = ""; + long size = -1; + ItemStatus its = ItemStatus.None; + List parent = new List(); + + Encoding enc = Style.GetEncoding(filename); + XmlReader xtr = FileTools.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 = datFile.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) + { + datFile.Type = (String.IsNullOrEmpty(datFile.Type) ? "SuperDAT" : datFile.Type); + superdat = true; + } + } + } + + // We only want elements + if (xtr.NodeType != XmlNodeType.Element) + { + xtr.Read(); + continue; + } + + switch (xtr.Name) + { + // Handle MAME listxml since they're halfway between a SL and a Logiqx XML + case "mame": + if (xtr.GetAttribute("build") != null) + { + datFile.Name = (String.IsNullOrEmpty(datFile.Name) ? xtr.GetAttribute("build") : datFile.Name); + datFile.Description = (String.IsNullOrEmpty(datFile.Description) ? datFile.Name : datFile.Name); + } + xtr.Read(); + break; + // New software lists have this behavior + case "softwarelist": + if (xtr.GetAttribute("name") != null) + { + datFile.Name = (String.IsNullOrEmpty(datFile.Name) ? xtr.GetAttribute("name") : datFile.Name); + } + if (xtr.GetAttribute("description") != null) + { + datFile.Description = (String.IsNullOrEmpty(datFile.Description) ? xtr.GetAttribute("description") : datFile.Description); + } + if (xtr.GetAttribute("forcemerging") != null && datFile.ForceMerging == ForceMerging.None) + { + switch (xtr.GetAttribute("forcemerging")) + { + case "none": + datFile.ForceMerging = ForceMerging.None; + break; + case "split": + datFile.ForceMerging = ForceMerging.Split; + break; + case "merged": + datFile.ForceMerging = ForceMerging.Merged; + break; + case "nonmerged": + datFile.ForceMerging = ForceMerging.NonMerged; + break; + case "full": + datFile.ForceMerging = ForceMerging.Full; + break; + } + } + if (xtr.GetAttribute("forcenodump") != null && datFile.ForceNodump == ForceNodump.None) + { + switch (xtr.GetAttribute("forcenodump")) + { + case "obsolete": + datFile.ForceNodump = ForceNodump.Obsolete; + break; + case "required": + datFile.ForceNodump = ForceNodump.Required; + break; + case "ignore": + datFile.ForceNodump = ForceNodump.Ignore; + break; + } + } + if (xtr.GetAttribute("forcepacking") != null && datFile.ForcePacking == ForcePacking.None) + { + switch (xtr.GetAttribute("forcepacking")) + { + case "zip": + datFile.ForcePacking = ForcePacking.Zip; + break; + case "unzip": + datFile.ForcePacking = ForcePacking.Unzip; + break; + } + } + xtr.Read(); + break; + // Handle M1 DATs since they're 99% the same as a SL DAT + case "m1": + datFile.Name = (String.IsNullOrEmpty(datFile.Name) ? "M1" : datFile.Name); + datFile.Description = (String.IsNullOrEmpty(datFile.Description) ? "M1" : datFile.Description); + if (xtr.GetAttribute("version") != null) + { + datFile.Version = (String.IsNullOrEmpty(datFile.Version) ? xtr.GetAttribute("version") : datFile.Version); + } + xtr.Read(); + break; + // OfflineList has a different header format + case "configuration": + headreader = xtr.ReadSubtree(); + + // If there's no subtree to the header, skip it + if (headreader == null) + { + xtr.Skip(); + continue; + } + + // Otherwise, read what we can from the header + while (!headreader.EOF) + { + // We only want elements + if (headreader.NodeType != XmlNodeType.Element || headreader.Name == "configuration") + { + headreader.Read(); + continue; + } + + // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) + string content = ""; + switch (headreader.Name.ToLowerInvariant()) + { + case "datname": + content = headreader.ReadElementContentAsString(); ; + datFile.Name = (String.IsNullOrEmpty(datFile.Name) ? content : datFile.Name); + superdat = superdat || content.Contains(" - SuperDAT"); + if (keep && superdat) + { + datFile.Type = (String.IsNullOrEmpty(datFile.Type) ? "SuperDAT" : datFile.Type); + } + break; + case "datversionurl": + content = headreader.ReadElementContentAsString(); ; + datFile.Url = (String.IsNullOrEmpty(datFile.Name) ? content : datFile.Url); + break; + default: + headreader.Read(); + break; + } + } + + break; + // We want to process the entire subtree of the header + case "header": + headreader = xtr.ReadSubtree(); + + // If there's no subtree to the header, skip it + if (headreader == null) + { + xtr.Skip(); + continue; + } + + // Otherwise, read what we can from the header + while (!headreader.EOF) + { + // We only want elements + if (headreader.NodeType != XmlNodeType.Element || headreader.Name == "header") + { + headreader.Read(); + continue; + } + + // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) + string content = ""; + switch (headreader.Name) + { + case "name": + content = headreader.ReadElementContentAsString(); ; + datFile.Name = (String.IsNullOrEmpty(datFile.Name) ? content : datFile.Name); + superdat = superdat || content.Contains(" - SuperDAT"); + if (keep && superdat) + { + datFile.Type = (String.IsNullOrEmpty(datFile.Type) ? "SuperDAT" : datFile.Type); + } + break; + case "description": + content = headreader.ReadElementContentAsString(); + datFile.Description = (String.IsNullOrEmpty(datFile.Description) ? content : datFile.Description); + break; + case "rootdir": + content = headreader.ReadElementContentAsString(); + datFile.RootDir = (String.IsNullOrEmpty(datFile.RootDir) ? content : datFile.RootDir); + break; + case "category": + content = headreader.ReadElementContentAsString(); + datFile.Category = (String.IsNullOrEmpty(datFile.Category) ? content : datFile.Category); + break; + case "version": + content = headreader.ReadElementContentAsString(); + datFile.Version = (String.IsNullOrEmpty(datFile.Version) ? content : datFile.Version); + break; + case "date": + content = headreader.ReadElementContentAsString(); + datFile.Date = (String.IsNullOrEmpty(datFile.Date) ? content.Replace(".", "/") : datFile.Date); + break; + case "author": + content = headreader.ReadElementContentAsString(); + datFile.Author = (String.IsNullOrEmpty(datFile.Author) ? content : datFile.Author); + + // Special cases for SabreDAT + datFile.Email = (String.IsNullOrEmpty(datFile.Email) && !String.IsNullOrEmpty(headreader.GetAttribute("email")) ? + headreader.GetAttribute("email") : datFile.Email); + datFile.Homepage = (String.IsNullOrEmpty(datFile.Homepage) && !String.IsNullOrEmpty(headreader.GetAttribute("homepage")) ? + headreader.GetAttribute("homepage") : datFile.Homepage); + datFile.Url = (String.IsNullOrEmpty(datFile.Url) && !String.IsNullOrEmpty(headreader.GetAttribute("url")) ? + headreader.GetAttribute("url") : datFile.Url); + break; + case "email": + content = headreader.ReadElementContentAsString(); + datFile.Email = (String.IsNullOrEmpty(datFile.Email) ? content : datFile.Email); + break; + case "homepage": + content = headreader.ReadElementContentAsString(); + datFile.Homepage = (String.IsNullOrEmpty(datFile.Homepage) ? content : datFile.Homepage); + break; + case "url": + content = headreader.ReadElementContentAsString(); + datFile.Url = (String.IsNullOrEmpty(datFile.Url) ? content : datFile.Url); + break; + case "comment": + content = headreader.ReadElementContentAsString(); + datFile.Comment = (String.IsNullOrEmpty(datFile.Comment) ? content : datFile.Comment); + break; + case "type": + content = headreader.ReadElementContentAsString(); + datFile.Type = (String.IsNullOrEmpty(datFile.Type) ? content : datFile.Type); + superdat = superdat || content.Contains("SuperDAT"); + break; + case "clrmamepro": + case "romcenter": + if (headreader.GetAttribute("header") != null) + { + datFile.Header = (String.IsNullOrEmpty(datFile.Header) ? headreader.GetAttribute("header") : datFile.Header); + } + if (headreader.GetAttribute("plugin") != null) + { + datFile.Header = (String.IsNullOrEmpty(datFile.Header) ? headreader.GetAttribute("plugin") : datFile.Header); + } + if (headreader.GetAttribute("forcemerging") != null && datFile.ForceMerging == ForceMerging.None) + { + switch (headreader.GetAttribute("forcemerging")) + { + case "none": + datFile.ForceMerging = ForceMerging.None; + break; + case "split": + datFile.ForceMerging = ForceMerging.Split; + break; + case "merged": + datFile.ForceMerging = ForceMerging.Merged; + break; + case "nonmerged": + datFile.ForceMerging = ForceMerging.NonMerged; + break; + case "full": + datFile.ForceMerging = ForceMerging.Full; + break; + } + } + if (headreader.GetAttribute("forcenodump") != null && datFile.ForceNodump == ForceNodump.None) + { + switch (headreader.GetAttribute("forcenodump")) + { + case "obsolete": + datFile.ForceNodump = ForceNodump.Obsolete; + break; + case "required": + datFile.ForceNodump = ForceNodump.Required; + break; + case "ignore": + datFile.ForceNodump = ForceNodump.Ignore; + break; + } + } + if (headreader.GetAttribute("forcepacking") != null && datFile.ForcePacking == ForcePacking.None) + { + switch (headreader.GetAttribute("forcepacking")) + { + case "zip": + datFile.ForcePacking = ForcePacking.Zip; + break; + case "unzip": + datFile.ForcePacking = ForcePacking.Unzip; + break; + } + } + headreader.Read(); + break; + case "flags": + flagreader = xtr.ReadSubtree(); + + // If we somehow have a null flag section, skip it + if (flagreader == null) + { + xtr.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"); + switch (flagreader.GetAttribute("name")) + { + case "type": + datFile.Type = (String.IsNullOrEmpty(datFile.Type) ? content : datFile.Type); + superdat = superdat || content.Contains("SuperDAT"); + break; + case "forcemerging": + if (datFile.ForceMerging == ForceMerging.None) + { + switch (content) + { + case "split": + datFile.ForceMerging = ForceMerging.Split; + break; + case "none": + datFile.ForceMerging = ForceMerging.None; + break; + case "full": + datFile.ForceMerging = ForceMerging.Full; + break; + } + } + break; + case "forcenodump": + if (datFile.ForceNodump == ForceNodump.None) + { + switch (content) + { + case "obsolete": + datFile.ForceNodump = ForceNodump.Obsolete; + break; + case "required": + datFile.ForceNodump = ForceNodump.Required; + break; + case "ignore": + datFile.ForceNodump = ForceNodump.Ignore; + break; + } + } + break; + case "forcepacking": + if (datFile.ForcePacking == ForcePacking.None) + { + switch (content) + { + case "zip": + datFile.ForcePacking = ForcePacking.Zip; + break; + case "unzip": + datFile.ForcePacking = ForcePacking.Unzip; + break; + } + } + break; + } + } + flagreader.Read(); + break; + default: + flagreader.Read(); + break; + } + } + headreader.Skip(); + break; + default: + headreader.Read(); + break; + } + } + + // Skip the header node now that we've processed it + xtr.Skip(); + break; + case "machine": + case "game": + case "software": + string temptype = xtr.Name, publisher = "", partname = "", partinterface = "", areaname = ""; + bool? supported = null; + long? areasize = null; + List> infos = new List>(); + List> features = new List>(); + + // We want to process the entire subtree of the game + subreader = xtr.ReadSubtree(); + + // Safeguard for interesting case of "software" without anything except roms + bool software = false; + + // If we have an empty machine, skip it + if (subreader == null) + { + xtr.Skip(); + continue; + } + + // Otherwise, add what is possible + subreader.MoveToContent(); + + // Create a new machine + Machine machine = new Machine + { + Name = xtr.GetAttribute("name"), + Description = xtr.GetAttribute("name"), + + RomOf = xtr.GetAttribute("romof") ?? "", + CloneOf = xtr.GetAttribute("cloneof") ?? "", + SampleOf = xtr.GetAttribute("sampleof") ?? "", + + Devices = new List(), + MachineType = + xtr.GetAttribute("isbios") == "yes" ? MachineType.Bios : + xtr.GetAttribute("isdevice") == "yes" ? MachineType.Device : + xtr.GetAttribute("ismechanical") == "yes" ? MachineType.Mechanical : + MachineType.None, + }; + + // Get the supported value from the reader + if (subreader.GetAttribute("supported") != null) + { + switch (subreader.GetAttribute("supported")) + { + case "no": + supported = false; + break; + case "yes": + supported = true; + break; + } + } + + // Get the runnable value from the reader + if (subreader.GetAttribute("runnable") != null) + { + switch (subreader.GetAttribute("runnable")) + { + case "no": + machine.Runnable = false; + break; + case "yes": + machine.Runnable = true; + break; + } + } + + if (superdat && !keep) + { + string tempout = Regex.Match(machine.Name, @".*?\\(.*)").Groups[1].Value; + if (tempout != "") + { + machine.Name = tempout; + } + } + // Get the name of the game from the parent + else if (superdat && keep && parent.Count > 0) + { + machine.Name = String.Join("\\", parent) + "\\" + machine.Name; + } + + // Special offline list parts + string ext = ""; + string releaseNumber = ""; + + while (software || !subreader.EOF) + { + software = false; + + // We only want elements + if (subreader.NodeType != XmlNodeType.Element) + { + if (subreader.NodeType == XmlNodeType.EndElement && subreader.Name == "part") + { + partname = ""; + partinterface = ""; + features = new List>(); + } + if (subreader.NodeType == XmlNodeType.EndElement && (subreader.Name == "dataarea" || subreader.Name == "diskarea")) + { + areaname = ""; + areasize = null; + } + + subreader.Read(); + continue; + } + + // Get the roms from the machine + switch (subreader.Name) + { + // For OfflineList only + case "title": + machine.Name = subreader.ReadElementContentAsString(); + break; + case "releaseNumber": + releaseNumber = subreader.ReadElementContentAsString(); + break; + case "romSize": + if (!Int64.TryParse(subreader.ReadElementContentAsString(), out size)) + { + size = -1; + } + break; + case "romCRC": + empty = false; + + ext = (subreader.GetAttribute("extension") ?? ""); + + DatItem olrom = new Rom + { + Name = releaseNumber + " - " + machine.Name + ext, + Size = size, + CRC = subreader.ReadElementContentAsString(), + ItemStatus = ItemStatus.None, + }; + + olrom.CopyMachineInformation(machine); + + // Now process and add the rom + key = datFile.ParseAddHelper(olrom, clean, remUnicode); + break; + + // For Software List and MAME listxml only + case "device_ref": + string device = subreader.GetAttribute("name"); + if (!machine.Devices.Contains(device)) + { + machine.Devices.Add(device); + } + + subreader.Read(); + break; + case "slotoption": + string slotoption = subreader.GetAttribute("devname"); + if (!machine.Devices.Contains(slotoption)) + { + machine.Devices.Add(slotoption); + } + + subreader.Read(); + break; + case "publisher": + publisher = subreader.ReadElementContentAsString(); + break; + case "info": + infos.Add(Tuple.Create(subreader.GetAttribute("name"), subreader.GetAttribute("value"))); + subreader.Read(); + break; + case "part": + partname = subreader.GetAttribute("name"); + partinterface = subreader.GetAttribute("interface"); + subreader.Read(); + break; + case "feature": + features.Add(Tuple.Create(subreader.GetAttribute("name"), subreader.GetAttribute("value"))); + subreader.Read(); + break; + case "dataarea": + case "diskarea": + areaname = subreader.GetAttribute("name"); + long areasizetemp = -1; + if (Int64.TryParse(subreader.GetAttribute("size"), out areasizetemp)) + { + areasize = areasizetemp; + } + subreader.Read(); + break; + + // For Logiqx, SabreDAT, and Software List + case "description": + machine.Description = subreader.ReadElementContentAsString(); + break; + case "year": + machine.Year = subreader.ReadElementContentAsString(); + break; + case "manufacturer": + machine.Manufacturer = subreader.ReadElementContentAsString(); + break; + case "release": + empty = false; + + bool? defaultrel = null; + if (subreader.GetAttribute("default") != null) + { + if (subreader.GetAttribute("default") == "yes") + { + defaultrel = true; + } + else if (subreader.GetAttribute("default") == "no") + { + defaultrel = false; + } + } + + DatItem relrom = new Release + { + Name = subreader.GetAttribute("name"), + Region = subreader.GetAttribute("region"), + Language = subreader.GetAttribute("language"), + Date = date, + Default = defaultrel, + + Supported = supported, + Publisher = publisher, + Infos = infos, + PartName = partname, + PartInterface = partinterface, + Features = features, + AreaName = areaname, + AreaSize = areasize, + }; + + relrom.CopyMachineInformation(machine); + + // Now process and add the rom + key = datFile.ParseAddHelper(relrom, clean, remUnicode); + + subreader.Read(); + break; + case "biosset": + empty = false; + + bool? defaultbios = null; + if (subreader.GetAttribute("default") != null) + { + if (subreader.GetAttribute("default") == "yes") + { + defaultbios = true; + } + else if (subreader.GetAttribute("default") == "no") + { + defaultbios = false; + } + } + + DatItem biosrom = new BiosSet + { + Name = subreader.GetAttribute("name"), + Description = subreader.GetAttribute("description"), + Default = defaultbios, + + Supported = supported, + Publisher = publisher, + Infos = infos, + PartName = partname, + PartInterface = partinterface, + Features = features, + AreaName = areaname, + AreaSize = areasize, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + biosrom.CopyMachineInformation(machine); + + // Now process and add the rom + key = datFile.ParseAddHelper(biosrom, clean, remUnicode); + + subreader.Read(); + break; + case "archive": + empty = false; + + DatItem archiverom = new Archive + { + Name = subreader.GetAttribute("name"), + + Supported = supported, + Publisher = publisher, + Infos = infos, + PartName = partname, + PartInterface = partinterface, + Features = features, + AreaName = areaname, + AreaSize = areasize, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + archiverom.CopyMachineInformation(machine); + + // Now process and add the rom + key = datFile.ParseAddHelper(archiverom, clean, remUnicode); + + subreader.Read(); + break; + case "sample": + empty = false; + + DatItem samplerom = new Sample + { + Name = subreader.GetAttribute("name"), + + Supported = supported, + Publisher = publisher, + Infos = infos, + PartName = partname, + PartInterface = partinterface, + Features = features, + AreaName = areaname, + AreaSize = areasize, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + samplerom.CopyMachineInformation(machine); + + // Now process and add the rom + key = datFile.ParseAddHelper(samplerom, clean, remUnicode); + + subreader.Read(); + break; + case "rom": + case "disk": + empty = false; + + // If the rom has a merge tag, add it + string merge = subreader.GetAttribute("merge"); + + // If the rom has a status, flag it + its = ItemStatus.None; + if (subreader.GetAttribute("flags") == "good" || subreader.GetAttribute("status") == "good") + { + its = ItemStatus.Good; + } + if (subreader.GetAttribute("flags") == "baddump" || subreader.GetAttribute("status") == "baddump") + { + its = ItemStatus.BadDump; + } + if (subreader.GetAttribute("flags") == "nodump" || subreader.GetAttribute("status") == "nodump") + { + its = ItemStatus.Nodump; + } + if (subreader.GetAttribute("flags") == "verified" || subreader.GetAttribute("status") == "verified") + { + its = ItemStatus.Verified; + } + + // If the rom has a Date attached, read it in and then sanitize it + date = ""; + if (subreader.GetAttribute("date") != null) + { + DateTime dateTime = DateTime.Now; + if (DateTime.TryParse(subreader.GetAttribute("date"), out dateTime)) + { + date = dateTime.ToString(); + } + else + { + date = subreader.GetAttribute("date"); + } + } + + // Take care of hex-sized files + size = -1; + if (subreader.GetAttribute("size") != null && subreader.GetAttribute("size").Contains("0x")) + { + size = Convert.ToInt64(subreader.GetAttribute("size"), 16); + } + else if (subreader.GetAttribute("size") != null) + { + Int64.TryParse(subreader.GetAttribute("size"), out size); + } + + // If the rom is continue or ignore, add the size to the previous rom + if (subreader.GetAttribute("loadflag") == "continue" || subreader.GetAttribute("loadflag") == "ignore") + { + int index = datFile[key].Count() - 1; + DatItem lastrom = datFile[key][index]; + if (lastrom.Type == ItemType.Rom) + { + ((Rom)lastrom).Size += size; + } + datFile[key].RemoveAt(index); + datFile[key].Add(lastrom); + subreader.Read(); + continue; + } + + // If we're in clean mode, sanitize the game name + if (clean) + { + machine.Name = Style.CleanGameName(machine.Name.Split(Path.DirectorySeparatorChar)); + } + + DatItem inrom; + switch (subreader.Name.ToLowerInvariant()) + { + case "disk": + inrom = new Disk + { + Name = subreader.GetAttribute("name"), + MD5 = subreader.GetAttribute("md5")?.ToLowerInvariant(), + SHA1 = subreader.GetAttribute("sha1")?.ToLowerInvariant(), + SHA256 = subreader.GetAttribute("sha256")?.ToLowerInvariant(), + SHA384 = subreader.GetAttribute("sha384")?.ToLowerInvariant(), + SHA512 = subreader.GetAttribute("sha512")?.ToLowerInvariant(), + MergeTag = merge, + ItemStatus = its, + + Supported = supported, + Publisher = publisher, + Infos = infos, + PartName = partname, + PartInterface = partinterface, + Features = features, + AreaName = areaname, + AreaSize = areasize, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + case "rom": + default: + inrom = new Rom + { + Name = subreader.GetAttribute("name"), + Size = size, + CRC = subreader.GetAttribute("crc"), + MD5 = subreader.GetAttribute("md5")?.ToLowerInvariant(), + SHA1 = subreader.GetAttribute("sha1")?.ToLowerInvariant(), + SHA256 = subreader.GetAttribute("sha256")?.ToLowerInvariant(), + SHA384 = subreader.GetAttribute("sha384")?.ToLowerInvariant(), + SHA512 = subreader.GetAttribute("sha512")?.ToLowerInvariant(), + ItemStatus = its, + MergeTag = merge, + Date = date, + + Supported = supported, + Publisher = publisher, + Infos = infos, + PartName = partname, + PartInterface = partinterface, + Features = features, + AreaName = areaname, + AreaSize = areasize, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + } + + inrom.CopyMachineInformation(machine); + + // Now process and add the rom + key = datFile.ParseAddHelper(inrom, clean, remUnicode); + + subreader.Read(); + break; + default: + subreader.Read(); + break; + } + } + + xtr.Skip(); + break; + case "dir": + case "directory": + // Set SuperDAT flag for all SabreDAT inputs, regardless of depth + superdat = true; + if (keep) + { + datFile.Type = (datFile.Type == "" ? "SuperDAT" : datFile.Type); + } + + string foldername = (xtr.GetAttribute("name") ?? ""); + if (foldername != "") + { + parent.Add(foldername); + } + + xtr.Read(); + break; + case "file": + empty = false; + + // If the rom is itemStatus, flag it + its = ItemStatus.None; + flagreader = xtr.ReadSubtree(); + + // If the subtree is empty, skip it + if (flagreader == null) + { + xtr.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": + case "status": + if (flagreader.GetAttribute("name") != null && flagreader.GetAttribute("value") != null) + { + string content = flagreader.GetAttribute("value"); + switch (flagreader.GetAttribute("name")) + { + case "good": + its = ItemStatus.Good; + break; + case "baddump": + its = ItemStatus.BadDump; + break; + case "nodump": + its = ItemStatus.Nodump; + break; + case "verified": + its = ItemStatus.Verified; + break; + } + } + break; + } + + flagreader.Read(); + } + + // If the rom has a Date attached, read it in and then sanitize it + date = ""; + if (xtr.GetAttribute("date") != null) + { + date = DateTime.Parse(xtr.GetAttribute("date")).ToString(); + } + + // Take care of hex-sized files + size = -1; + if (xtr.GetAttribute("size") != null && xtr.GetAttribute("size").Contains("0x")) + { + size = Convert.ToInt64(xtr.GetAttribute("size"), 16); + } + else if (xtr.GetAttribute("size") != null) + { + Int64.TryParse(xtr.GetAttribute("size"), out size); + } + + // If the rom is continue or ignore, add the size to the previous rom + if (xtr.GetAttribute("loadflag") == "continue" || xtr.GetAttribute("loadflag") == "ignore") + { + int index = datFile[key].Count() - 1; + DatItem lastrom = datFile[key][index]; + if (lastrom.Type == ItemType.Rom) + { + ((Rom)lastrom).Size += size; + } + datFile[key].RemoveAt(index); + datFile[key].Add(lastrom); + continue; + } + + Machine dir = new Machine(); + + // Get the name of the game from the parent + dir.Name = String.Join("\\", parent); + dir.Description = dir.Name; + + // If we aren't keeping names, trim out the path + if (!keep || !superdat) + { + string tempout = Regex.Match(dir.Name, @".*?\\(.*)").Groups[1].Value; + if (tempout != "") + { + dir.Name = tempout; + } + } + + DatItem rom; + switch (xtr.GetAttribute("type").ToLowerInvariant()) + { + case "disk": + rom = new Disk + { + Name = xtr.GetAttribute("name"), + MD5 = xtr.GetAttribute("md5")?.ToLowerInvariant(), + SHA1 = xtr.GetAttribute("sha1")?.ToLowerInvariant(), + SHA256 = xtr.GetAttribute("sha256")?.ToLowerInvariant(), + SHA384 = xtr.GetAttribute("sha384")?.ToLowerInvariant(), + SHA512 = xtr.GetAttribute("sha512")?.ToLowerInvariant(), + ItemStatus = its, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + case "rom": + default: + rom = new Rom + { + Name = xtr.GetAttribute("name"), + Size = size, + CRC = xtr.GetAttribute("crc")?.ToLowerInvariant(), + MD5 = xtr.GetAttribute("md5")?.ToLowerInvariant(), + SHA1 = xtr.GetAttribute("sha1")?.ToLowerInvariant(), + SHA256 = xtr.GetAttribute("sha256")?.ToLowerInvariant(), + SHA384 = xtr.GetAttribute("sha384")?.ToLowerInvariant(), + SHA512 = xtr.GetAttribute("sha512")?.ToLowerInvariant(), + ItemStatus = its, + Date = date, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + } + + rom.CopyMachineInformation(dir); + + // Now process and add the rom + key = datFile.ParseAddHelper(rom, clean, remUnicode); + + 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(); + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// DatFile to write out from + /// Name of the file to write to + /// True if games should only be compared on game and file name (default), false if system and source are counted + /// True if DAT statistics should be output on write, false otherwise (default) + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if files should be overwritten (default), false if they should be renamed instead + /// True if the DAT was written correctly, false otherwise + public static bool WriteToFile(DatFile datFile, string outfile, bool norename = true, bool stats = false, bool ignoreblanks = false, bool overwrite = true) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = FileTools.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(true)); + + // Write out the header + WriteHeader(datFile, sw); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = datFile.Keys.ToList(); + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = datFile[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(datFile, sw, rom); + } + + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.Type == 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 + WriteRomData(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 + /// + /// DatFile to write out from + /// StreamWriter to output to + /// True if the data was written, false on error + private static bool WriteHeader(DatFile datFile, StreamWriter sw) + { + try + { + string header = "\n" + + "\n\n" + + "\n" + + "\t
\n" + + "\t\t" + HttpUtility.HtmlEncode(datFile.Name) + "\n" + + "\t\t" + HttpUtility.HtmlEncode(datFile.Description) + "\n" + + (!String.IsNullOrEmpty(datFile.RootDir) ? "\t\t" + HttpUtility.HtmlEncode(datFile.RootDir) + "\n" : "") + + (!String.IsNullOrEmpty(datFile.Category) ? "\t\t" + HttpUtility.HtmlEncode(datFile.Category) + "\n" : "") + + "\t\t" + HttpUtility.HtmlEncode(datFile.Version) + "\n" + + (!String.IsNullOrEmpty(datFile.Date) ? "\t\t" + HttpUtility.HtmlEncode(datFile.Date) + "\n" : "") + + "\t\t" + HttpUtility.HtmlEncode(datFile.Author) + "\n" + + (!String.IsNullOrEmpty(datFile.Email) ? "\t\t" + HttpUtility.HtmlEncode(datFile.Email) + "\n" : "") + + (!String.IsNullOrEmpty(datFile.Homepage) ? "\t\t" + HttpUtility.HtmlEncode(datFile.Homepage) + "\n" : "") + + (!String.IsNullOrEmpty(datFile.Url) ? "\t\t" + HttpUtility.HtmlEncode(datFile.Url) + "\n" : "") + + (!String.IsNullOrEmpty(datFile.Comment) ? "\t\t" + HttpUtility.HtmlEncode(datFile.Comment) + "\n" : "") + + (!String.IsNullOrEmpty(datFile.Type) ? "\t\t" + HttpUtility.HtmlEncode(datFile.Type) + "\n" : "") + + (datFile.ForcePacking != ForcePacking.None || datFile.ForceMerging != ForceMerging.None || datFile.ForceNodump != ForceNodump.None ? + "\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 + /// + /// DatFile to write out from + /// StreamWriter to output to + /// RomData object to be output + /// True if the data was written, false on error + private static bool WriteStartGame(DatFile datFile, 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" + + (String.IsNullOrEmpty(rom.Comment) ? "" : "\t\t" + HttpUtility.HtmlEncode(rom.Comment) + "\n") + + "\t\t" + HttpUtility.HtmlEncode((String.IsNullOrEmpty(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription)) + "\n" + + (String.IsNullOrEmpty(rom.Year) ? "" : "\t\t" + HttpUtility.HtmlEncode(rom.Year) + "\n") + + (String.IsNullOrEmpty(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 static 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 RomData using the supplied StreamWriter + /// + /// StreamWriter to output to + /// RomData 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 static bool WriteRomData(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.Type == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + switch (rom.Type) + { + 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 static 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 new file mode 100644 index 00000000..77f93856 --- /dev/null +++ b/SabreTools.Library/DatFiles/Missfile.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using SabreTools.Library.Data; +using SabreTools.Library.Items; +using SabreTools.Library.Tools; + +#if MONO +using System.IO; +#else +using Alphaleonis.Win32.Filesystem; + +using FileStream = System.IO.FileStream; +using StreamWriter = System.IO.StreamWriter; +#endif +using NaturalSort; + +namespace SabreTools.Library.DatFiles +{ + /// + /// Represents parsing and writing of a Missfile + /// + public class Missfile + { + /// + /// Parse a Missfileand return all found games and roms within + /// + /// DatFile to populate with the read information + /// 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 static void Parse( + DatFile datFile, + + // 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(); + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// DatFile to write out from + /// 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 static bool WriteToFile(DatFile datFile, string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = FileTools.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(true)); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = datFile.Keys.ToList(); + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = datFile[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 "null" game (created by DATFromDir or something similar), log it to file + if (rom.Type == 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 + WriteRomData(datFile, sw, rom, lastgame, 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 RomData using the supplied StreamWriter + /// + /// DatFile to write out from + /// StreamWriter to output to + /// RomData 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 static bool WriteRomData(DatFile datFile, 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.Type == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = "", name = "", pre = "", post = ""; + pre = datFile.Prefix + (datFile.Quotes ? "\"" : ""); + post = (datFile.Quotes ? "\"" : "") + datFile.Postfix; + + if (rom.Type == ItemType.Rom) + { + // Check for special strings in prefix and postfix + pre = pre + .Replace("%game%", rom.MachineName) + .Replace("%name%", rom.Name) + .Replace("%crc%", ((Rom)rom).CRC) + .Replace("%md5%", ((Rom)rom).MD5) + .Replace("%sha1%", ((Rom)rom).SHA1) + .Replace("%sha256%", ((Rom)rom).SHA256) + .Replace("%sha384%", ((Rom)rom).SHA384) + .Replace("%sha512%", ((Rom)rom).SHA512) + .Replace("%size%", ((Rom)rom).Size.ToString()); + post = post + .Replace("%game%", rom.MachineName) + .Replace("%name%", rom.Name) + .Replace("%crc%", ((Rom)rom).CRC) + .Replace("%md5%", ((Rom)rom).MD5) + .Replace("%sha1%", ((Rom)rom).SHA1) + .Replace("%sha256%", ((Rom)rom).SHA256) + .Replace("%sha384%", ((Rom)rom).SHA384) + .Replace("%sha512%", ((Rom)rom).SHA512) + .Replace("%size%", ((Rom)rom).Size.ToString()); + } + else if (rom.Type == ItemType.Disk) + { + // Check for special strings in prefix and postfix + pre = pre + .Replace("%game%", rom.MachineName) + .Replace("%name%", rom.Name) + .Replace("%crc%", string.Empty) + .Replace("%md5%", ((Disk)rom).MD5) + .Replace("%sha1%", ((Disk)rom).SHA1) + .Replace("%sha256%", ((Disk)rom).SHA256) + .Replace("%sha384%", ((Disk)rom).SHA384) + .Replace("%sha512%", ((Disk)rom).SHA512) + .Replace("%size%", string.Empty); + post = post + .Replace("%game%", rom.MachineName) + .Replace("%name%", rom.Name) + .Replace("%crc%", string.Empty) + .Replace("%md5%", ((Disk)rom).MD5) + .Replace("%sha1%", ((Disk)rom).SHA1) + .Replace("%sha256%", ((Disk)rom).SHA256) + .Replace("%sha384%", ((Disk)rom).SHA384) + .Replace("%sha512%", ((Disk)rom).SHA512) + .Replace("%size%", string.Empty); + } + else + { + // Check for special strings in prefix and postfix + pre = pre + .Replace("%game%", rom.MachineName) + .Replace("%name%", rom.Name) + .Replace("%crc%", string.Empty) + .Replace("%md5%", string.Empty) + .Replace("%sha1%", string.Empty) + .Replace("%sha256%", string.Empty) + .Replace("%sha384%", string.Empty) + .Replace("%sha512%", string.Empty) + .Replace("%size%", string.Empty); + post = post + .Replace("%game%", rom.MachineName) + .Replace("%name%", rom.Name) + .Replace("%crc%", string.Empty) + .Replace("%md5%", string.Empty) + .Replace("%sha1%", string.Empty) + .Replace("%sha256%", string.Empty) + .Replace("%sha384%", string.Empty) + .Replace("%sha512%", string.Empty) + .Replace("%size%", string.Empty); + } + + // If we're in Romba mode, the state is consistent + if (datFile.Romba) + { + if (rom.Type == ItemType.Rom) + { + // We can only write out if there's a SHA-1 + if (((Rom)rom).SHA1 != "") + { + name = ((Rom)rom).SHA1.Substring(0, 2) + + "/" + ((Rom)rom).SHA1.Substring(2, 2) + + "/" + ((Rom)rom).SHA1.Substring(4, 2) + + "/" + ((Rom)rom).SHA1.Substring(6, 2) + + "/" + ((Rom)rom).SHA1 + ".gz"; + state += pre + name + post + "\n"; + } + } + else if (rom.Type == ItemType.Disk) + { + // We can only write out if there's a SHA-1 + if (((Disk)rom).SHA1 != "") + { + name = ((Disk)rom).SHA1.Substring(0, 2) + + "/" + ((Disk)rom).SHA1.Substring(2, 2) + + "/" + ((Disk)rom).SHA1.Substring(4, 2) + + "/" + ((Disk)rom).SHA1.Substring(6, 2) + + "/" + ((Disk)rom).SHA1 + ".gz"; + state += pre + name + post + "\n"; + } + } + } + + // Otherwise, use any flags + else + { + name = (datFile.UseGame ? rom.MachineName : rom.Name); + if (datFile.RepExt != "" || datFile.RemExt) + { + if (datFile.RemExt) + { + datFile.RepExt = ""; + } + + string dir = Path.GetDirectoryName(name); + dir = (dir.StartsWith(Path.DirectorySeparatorChar.ToString()) ? dir.Remove(0, 1) : dir); + name = Path.Combine(dir, Path.GetFileNameWithoutExtension(name) + datFile.RepExt); + } + if (datFile.AddExt != "") + { + name += datFile.AddExt; + } + if (!datFile.UseGame && datFile.GameName) + { + name = Path.Combine(rom.MachineName, name); + } + + if (datFile.UseGame && rom.MachineName != lastgame) + { + state += pre + name + post + "\n"; + lastgame = rom.MachineName; + } + else if (!datFile.UseGame) + { + state += pre + name + post + "\n"; + } + } + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + } +} diff --git a/SabreTools.Library/DatFiles/OfflineList.cs b/SabreTools.Library/DatFiles/OfflineList.cs new file mode 100644 index 00000000..6e0a6030 --- /dev/null +++ b/SabreTools.Library/DatFiles/OfflineList.cs @@ -0,0 +1,357 @@ +using System; +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.Items; +using SabreTools.Library.Tools; + +#if MONO +using System.IO; +#else +using Alphaleonis.Win32.Filesystem; + +using FileStream = System.IO.FileStream; +using StreamWriter = System.IO.StreamWriter; +#endif +using NaturalSort; + +namespace SabreTools.Library.DatFiles +{ + /// + /// Represents parsing and writing of an OfflineList XML DAT + /// + public class OfflineList + { + /// + /// Parse an OfflineList XML DAT and return all found games and roms within + /// + /// DatFile to populate with the read information + /// 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 static void Parse( + DatFile datFile, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // All XML-derived DATs share a lot in common so it just calls one implementation + Logiqx.Parse(datFile, filename, sysid, srcid, keep, clean, remUnicode); + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// DatFile to write out from + /// 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 static bool WriteToFile(DatFile datFile, string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = FileTools.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(true)); + + // Write out the header + WriteHeader(datFile, sw); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = datFile.Keys.ToList(); + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = datFile[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.Type == 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 + WriteRomData(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 + /// + /// DatFile to write out from + /// StreamWriter to output to + /// True if the data was written, false on error + private static bool WriteHeader(DatFile datFile, StreamWriter sw) + { + try + { + string header = "\n" + + "\n" + + "\t\n" + + "\t\t" + HttpUtility.HtmlEncode(datFile.Name) + "\n" + + "\t\t" + datFile.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(datFile.Url) + "</datVersionURL>\n" + + "\t\t\t<datURL fileName=\"" + HttpUtility.HtmlEncode(datFile.FileName) + ".zip\">" + HttpUtility.HtmlEncode(datFile.Url) + "</datURL>\n" + + "\t\t\t<imURL>" + HttpUtility.HtmlEncode(datFile.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 static 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 RomData using the supplied StreamWriter + /// </summary> + /// <param name="sw">StreamWriter to output to</param> + /// <param name="rom">RomData 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 static bool WriteRomData(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.Type == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + state += "\t\t<game>\n" + + "\t\t\t<imageNumber>1</imageNumber>\n" + + "\t\t\t<releaseNumber>1</releaseNumber>\n" + + "\t\t\t<title>" + HttpUtility.HtmlEncode(rom.Name) + "\n" + + "\t\t\tNone\n"; + + if (rom.Type == ItemType.Rom) + { + state += "\t\t\t" + ((Rom)rom).Size + "\n"; + } + + state += "\t\t\tNone\n" + + "\t\t\t0\n" + + "\t\t\tNone\n" + + "\t\t\t0\n"; + + if (rom.Type == ItemType.Disk) + { + state += "\t\t\t\n" + + (((Disk)rom).MD5 != null + ? "\t\t\t\t" + ((Disk)rom).MD5.ToUpperInvariant() + "\n" + : "\t\t\t\t" + ((Disk)rom).SHA1.ToUpperInvariant() + "\n") + + "\t\t\t\n"; + } + else if (rom.Type == ItemType.Rom) + { + string tempext = Path.GetExtension(((Rom)rom).Name); + if (!tempext.StartsWith(".")) + { + tempext = "." + tempext; + } + + state += "\t\t\t\n" + + (((Rom)rom).CRC != null + ? "\t\t\t\t" + ((Rom)rom).CRC.ToUpperInvariant() + "\n" + : ((Rom)rom).MD5 != null + ? "\t\t\t\t" + ((Rom)rom).MD5.ToUpperInvariant() + "\n" + : "\t\t\t\t" + ((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 static 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/RomCenter.cs b/SabreTools.Library/DatFiles/RomCenter.cs new file mode 100644 index 00000000..8358ec22 --- /dev/null +++ b/SabreTools.Library/DatFiles/RomCenter.cs @@ -0,0 +1,371 @@ +using System; +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.Items; +using SabreTools.Library.Tools; + +#if MONO +using System.IO; +#else +using Alphaleonis.Win32.Filesystem; + +using FileStream = System.IO.FileStream; +using StreamReader = System.IO.StreamReader; +using StreamWriter = System.IO.StreamWriter; +#endif +using NaturalSort; + +namespace SabreTools.Library.DatFiles +{ + /// + /// Represents parsing and writing of a RomCenter DAT + /// + public class RomCenter + { + /// + /// Parse a RomCenter DAT and return all found games and roms within + /// + /// DatFile to populate with the read information + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + public static void Parse( + DatFile datFile, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool clean, + bool remUnicode) + { + // Open a file reader + Encoding enc = Style.GetEncoding(filename); + StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc); + + 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=")) + { + datFile.Author = (String.IsNullOrEmpty(datFile.Author) ? line.Split('=')[1] : datFile.Author); + } + // If we have one of the three version tags + else if (line.ToLowerInvariant().StartsWith("version=")) + { + switch (blocktype) + { + case "credits": + datFile.Version = (String.IsNullOrEmpty(datFile.Version) ? line.Split('=')[1] : datFile.Version); + break; + case "emulator": + datFile.Description = (String.IsNullOrEmpty(datFile.Description) ? line.Split('=')[1] : datFile.Description); + break; + } + } + // If we have a URL + else if (line.ToLowerInvariant().StartsWith("url=")) + { + datFile.Url = (String.IsNullOrEmpty(datFile.Url) ? line.Split('=')[1] : datFile.Url); + } + // If we have a comment + else if (line.ToLowerInvariant().StartsWith("comment=")) + { + datFile.Comment = (String.IsNullOrEmpty(datFile.Comment) ? line.Split('=')[1] : datFile.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 && datFile.ForceMerging == ForceMerging.None) + { + datFile.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 && datFile.ForceMerging == ForceMerging.None) + { + datFile.ForceMerging = ForceMerging.Full; + } + } + } + // If we have the refname tag + else if (line.ToLowerInvariant().StartsWith("refname=")) + { + datFile.Name = (String.IsNullOrEmpty(datFile.Name) ? line.Split('=')[1] : datFile.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('¬'); + + // 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 = rominfo[6], + ItemStatus = ItemStatus.None, + + MachineName = rominfo[3], + MachineDescription = rominfo[4], + CloneOf = rominfo[1], + RomOf = rominfo[8], + + SystemID = sysid, + SourceID = srcid, + }; + + // Now process and add the rom + datFile.ParseAddHelper(rom, clean, remUnicode); + } + } + } + + sr.Dispose(); + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// DatFile to write out from + /// 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 static bool WriteToFile(DatFile datFile, string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = FileTools.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(true)); + + // Write out the header + WriteHeader(datFile, sw); + + // Write out each of the machines and roms + string lastgame = null; + List splitpath = new List(); + + // Get a properly sorted set of keys + List keys = datFile.Keys.ToList(); + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = datFile[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 "null" game (created by DATFromDir or something similar), log it to file + if (rom.Type == 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 + WriteRomData(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 DAT header using the supplied StreamWriter + /// + /// DatFile to write out from + /// StreamWriter to output to + /// True if the data was written, false on error + private static bool WriteHeader(DatFile datFile, StreamWriter sw) + { + try + { + string header = header = "[CREDITS]\n" + + "author=" + datFile.Author + "\n" + + "version=" + datFile.Version + "\n" + + "comment=" + datFile.Comment + "\n" + + "[DAT]\n" + + "version=2.50\n" + + "split=" + (datFile.ForceMerging == ForceMerging.Split ? "1" : "0") + "\n" + + "merge=" + (datFile.ForceMerging == ForceMerging.Full || datFile.ForceMerging == ForceMerging.Merged ? "1" : "0") + "\n" + + "[EMULATOR]\n" + + "refname=" + datFile.Name + "\n" + + "version=" + datFile.Description + "\n" + + "[GAMES]\n"; + + // Write the header out + sw.Write(header); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out RomData using the supplied StreamWriter + /// + /// StreamWriter to output to + /// RomData 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 static bool WriteRomData(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.Type == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + if (rom.Type == ItemType.Rom) + { + state += "¬" + (String.IsNullOrEmpty(rom.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.CloneOf)) + + "¬" + (String.IsNullOrEmpty(rom.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.CloneOf)) + + "¬" + HttpUtility.HtmlEncode(rom.MachineName) + + "¬" + HttpUtility.HtmlEncode((String.IsNullOrEmpty(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription)) + + "¬" + HttpUtility.HtmlEncode(rom.Name) + + "¬" + ((Rom)rom).CRC.ToLowerInvariant() + + "¬" + (((Rom)rom).Size != -1 ? ((Rom)rom).Size.ToString() : "") + "¬¬¬\n"; + } + else if (rom.Type == ItemType.Disk) + { + state += "¬" + (String.IsNullOrEmpty(rom.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.CloneOf)) + + "¬" + (String.IsNullOrEmpty(rom.CloneOf) ? "" : HttpUtility.HtmlEncode(rom.CloneOf)) + + "¬" + HttpUtility.HtmlEncode(rom.MachineName) + + "¬" + HttpUtility.HtmlEncode((String.IsNullOrEmpty(rom.MachineDescription) ? rom.MachineName : rom.MachineDescription)) + + "¬" + HttpUtility.HtmlEncode(rom.Name) + + "¬¬¬¬¬\n"; + } + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + } +} diff --git a/SabreTools.Library/DatFiles/SabreDat.cs b/SabreTools.Library/DatFiles/SabreDat.cs new file mode 100644 index 00000000..24b228a4 --- /dev/null +++ b/SabreTools.Library/DatFiles/SabreDat.cs @@ -0,0 +1,451 @@ +using System; +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.Items; +using SabreTools.Library.Tools; + +#if MONO +using System.IO; +#else +using Alphaleonis.Win32.Filesystem; + +using FileStream = System.IO.FileStream; +using StreamReader = System.IO.StreamReader; +using StreamWriter = System.IO.StreamWriter; +#endif +using NaturalSort; + +namespace SabreTools.Library.DatFiles +{ + /// + /// Represents parsing and writing of an SabreDat XML DAT + /// + public class SabreDat + { + /// + /// Parse an SabreDat XML DAT and return all found games and roms within + /// + /// DatFile to populate with the read information + /// 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 static void Parse( + DatFile datFile, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // All XML-derived DATs share a lot in common so it just calls one implementation + Logiqx.Parse(datFile, filename, sysid, srcid, keep, clean, remUnicode); + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// DatFile to write out from + /// Name of the file to write to + /// True if games should only be compared on game and file name (default), false if system and source are counted + /// True if DAT statistics should be output on write, false otherwise (default) + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if files should be overwritten (default), false if they should be renamed instead + /// True if the DAT was written correctly, false otherwise + public static bool WriteToFile(DatFile datFile, string outfile, bool norename = true, bool stats = false, bool ignoreblanks = false, bool overwrite = true) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = FileTools.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(true)); + + // Write out the header + WriteHeader(datFile, 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 = datFile.Keys.ToList(); + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = datFile[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.Type == 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 + WriteRomData(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 + /// + /// DatFile to write out from + /// StreamWriter to output to + /// True if the data was written, false on error + private static bool WriteHeader(DatFile datFile, StreamWriter sw) + { + try + { + string header = "\n" + + "\n\n" + + "\n" + + "\t
\n" + + "\t\t" + HttpUtility.HtmlEncode(datFile.Name) + "\n" + + "\t\t" + HttpUtility.HtmlEncode(datFile.Description) + "\n" + + (!String.IsNullOrEmpty(datFile.RootDir) ? "\t\t" + HttpUtility.HtmlEncode(datFile.RootDir) + "\n" : "") + + (!String.IsNullOrEmpty(datFile.Category) ? "\t\t" + HttpUtility.HtmlEncode(datFile.Category) + "\n" : "") + + "\t\t" + HttpUtility.HtmlEncode(datFile.Version) + "\n" + + (!String.IsNullOrEmpty(datFile.Date) ? "\t\t" + HttpUtility.HtmlEncode(datFile.Date) + "\n" : "") + + "\t\t" + HttpUtility.HtmlEncode(datFile.Author) + "\n" + + (!String.IsNullOrEmpty(datFile.Comment) ? "\t\t" + HttpUtility.HtmlEncode(datFile.Comment) + "\n" : "") + + (!String.IsNullOrEmpty(datFile.Type) || datFile.ForcePacking != ForcePacking.None || datFile.ForceMerging != ForceMerging.None || datFile.ForceNodump != ForceNodump.None ? + "\t\t\n" + + (!String.IsNullOrEmpty(datFile.Type) ? "\t\t\t\n" : "") + + (datFile.ForcePacking == ForcePacking.Unzip ? "\t\t\t\n" : "") + + (datFile.ForcePacking == ForcePacking.Zip ? "\t\t\t\n" : "") + + (datFile.ForceMerging == ForceMerging.Full ? "\t\t\t\n" : "") + + (datFile.ForceMerging == ForceMerging.Split ? "\t\t\t\n" : "") + + (datFile.ForceMerging == ForceMerging.Merged ? "\t\t\t\n" : "") + + (datFile.ForceMerging == ForceMerging.NonMerged ? "\t\t\t\n" : "") + + (datFile.ForceNodump == ForceNodump.Ignore ? "\t\t\t\n" : "") + + (datFile.ForceNodump == ForceNodump.Obsolete ? "\t\t\t\n" : "") + + (datFile.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 + /// RomData 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 static 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 static 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 RomData using the supplied StreamWriter + /// + /// StreamWriter to output to + /// RomData 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 static bool WriteRomData(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.Type == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = "", prefix = ""; + for (int i = 0; i < depth; i++) + { + prefix += "\t"; + } + state += prefix; + + switch (rom.Type) + { + 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 static 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 new file mode 100644 index 00000000..92cc109b --- /dev/null +++ b/SabreTools.Library/DatFiles/SeparatedValue.cs @@ -0,0 +1,612 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using SabreTools.Library.Data; +using SabreTools.Library.Items; +using SabreTools.Library.Tools; + +#if MONO +using System.IO; +#else +using FileStream = System.IO.FileStream; +using StreamReader = System.IO.StreamReader; +using StreamWriter = System.IO.StreamWriter; +#endif +using NaturalSort; + +namespace SabreTools.Library.DatFiles +{ + /// + /// Represents parsing and writing of a value-separated DAT + /// + public class SeparatedValue + { + /// + /// Parse a character-separated value DAT and return all found games and roms within + /// + /// DatFile to populate with the read information + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// Delimiter for parsing individual lines + /// 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 static void Parse( + DatFile datFile, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + char delim, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Open a file reader + Encoding enc = Style.GetEncoding(filename); + StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc); + + // Create an empty list of columns to parse though + List columns = new List(); + + 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; + default: + columns.Add("INVALID"); + break; + } + } + + continue; + } + + // 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; + } + + // Set the output item information + string machineName = null, machineDesc = null, name = null, crc = null, md5 = null, sha1 = null, + sha256 = null, sha384 = null, sha512 = 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": + datFile.FileName = (String.IsNullOrEmpty(datFile.FileName) ? value : datFile.FileName); + break; + case "DatFile.Name": + datFile.Name = (String.IsNullOrEmpty(datFile.Name) ? value : datFile.Name); + break; + case "DatFile.Description": + datFile.Description = (String.IsNullOrEmpty(datFile.Description) ? value : datFile.Description); + break; + case "Machine.Name": + machineName = value; + break; + case "Description": + machineDesc = value; + break; + case "DatItem.Type": + switch (value.ToLowerInvariant()) + { + case "archive": + itemType = ItemType.Archive; + break; + case "biosset": + itemType = ItemType.BiosSet; + break; + case "disk": + itemType = ItemType.Disk; + break; + case "release": + itemType = ItemType.Release; + break; + case "rom": + itemType = ItemType.Rom; + break; + case "sample": + itemType = ItemType.Sample; + break; + } + break; + case "Rom.Name": + case "Disk.Name": + name = value == "" ? name : value; + break; + case "DatItem.Size": + if (!Int64.TryParse(value, out size)) + { + size = -1; + } + break; + case "DatItem.CRC": + crc = value; + break; + case "DatItem.MD5": + md5 = value; + break; + case "DatItem.SHA1": + sha1 = value; + break; + case "DatItem.SHA256": + sha256 = value; + break; + case "DatItem.SHA384": + sha384 = value; + break; + case "DatItem.SHA512": + sha512 = value; + break; + case "DatItem.Nodump": + switch (value.ToLowerInvariant()) + { + case "baddump": + status = ItemStatus.BadDump; + break; + case "good": + status = ItemStatus.Good; + break; + case "no": + case "none": + status = ItemStatus.None; + break; + case "nodump": + case "yes": + status = ItemStatus.Nodump; + break; + case "verified": + status = ItemStatus.Verified; + break; + } + break; + } + } + + // And now we populate and add the new item + switch (itemType) + { + case ItemType.Archive: + Archive archive = new Archive() + { + Name = name, + + MachineName = machineName, + MachineDescription = machineDesc, + }; + + datFile.ParseAddHelper(archive, clean, remUnicode); + break; + case ItemType.BiosSet: + BiosSet biosset = new BiosSet() + { + Name = name, + + MachineName = machineName, + Description = machineDesc, + }; + + datFile.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, + + ItemStatus = status, + }; + + datFile.ParseAddHelper(disk, clean, remUnicode); + break; + case ItemType.Release: + Release release = new Release() + { + Name = name, + + MachineName = machineName, + MachineDescription = machineDesc, + }; + + datFile.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, + + MachineName = machineName, + MachineDescription = machineDesc, + + ItemStatus = status, + }; + + datFile.ParseAddHelper(rom, clean, remUnicode); + break; + case ItemType.Sample: + Sample sample = new Sample() + { + Name = name, + + MachineName = machineName, + MachineDescription = machineDesc, + }; + + datFile.ParseAddHelper(sample, clean, remUnicode); + break; + } + } + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// DatFile to write out from + /// Name of the file to write to + /// Delimiter for parsing individual lines + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public static bool WriteToFile(DatFile datFile, string outfile, char delim, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = FileTools.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(true)); + + // Write out the header + WriteHeader(sw, delim); + + // Get a properly sorted set of keys + List keys = datFile.Keys.ToList(); + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = datFile[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 "null" game (created by DATFromDir or something similar), log it to file + if (rom.Type == ItemType.Rom + && ((Rom)rom).Size == -1 + && ((Rom)rom).CRC == "null") + { + Globals.Logger.Verbose("Empty folder found: {0}", rom.MachineName); + } + + // Now, output the rom data + WriteRomData(datFile, sw, delim, rom, ignoreblanks); + } + } + + 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 + /// Delimiter for parsing individual lines + /// True if the data was written, false on error + private static bool WriteHeader(StreamWriter sw, char delim) + { + 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; + } + + return true; + } + + /// + /// Write out RomData using the supplied StreamWriter + /// + /// DatFile to write out from + /// StreamWriter to output to + /// Delimiter for parsing individual lines + /// RomData 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 static bool WriteRomData(DatFile datFile, StreamWriter sw, char delim, DatItem rom, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.Type == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = "", name = "", pre = "", post = ""; + + // Separated values should only output Rom and Disk + if (rom.Type != ItemType.Disk && rom.Type != ItemType.Rom) + { + return true; + } + + pre = datFile.Prefix + (datFile.Quotes ? "\"" : ""); + post = (datFile.Quotes ? "\"" : "") + datFile.Postfix; + + if (rom.Type == ItemType.Rom) + { + // Check for special strings in prefix and postfix + pre = pre + .Replace("%game%", rom.MachineName) + .Replace("%name%", rom.Name) + .Replace("%crc%", ((Rom)rom).CRC) + .Replace("%md5%", ((Rom)rom).MD5) + .Replace("%sha1%", ((Rom)rom).SHA1) + .Replace("%sha256%", ((Rom)rom).SHA256) + .Replace("%sha384%", ((Rom)rom).SHA384) + .Replace("%sha512%", ((Rom)rom).SHA512) + .Replace("%size%", ((Rom)rom).Size.ToString()); + post = post + .Replace("%game%", rom.MachineName) + .Replace("%name%", rom.Name) + .Replace("%crc%", ((Rom)rom).CRC) + .Replace("%md5%", ((Rom)rom).MD5) + .Replace("%sha1%", ((Rom)rom).SHA1) + .Replace("%sha256%", ((Rom)rom).SHA256) + .Replace("%sha384%", ((Rom)rom).SHA384) + .Replace("%sha512%", ((Rom)rom).SHA512) + .Replace("%size%", ((Rom)rom).Size.ToString()); + } + else if (rom.Type == ItemType.Disk) + { + // Check for special strings in prefix and postfix + pre = pre + .Replace("%game%", rom.MachineName) + .Replace("%name%", rom.Name) + .Replace("%crc%", string.Empty) + .Replace("%md5%", ((Disk)rom).MD5) + .Replace("%sha1%", ((Disk)rom).SHA1) + .Replace("%sha256%", ((Disk)rom).SHA256) + .Replace("%sha384%", ((Disk)rom).SHA384) + .Replace("%sha512%", ((Disk)rom).SHA512) + .Replace("%size%", string.Empty); ; + post = post + .Replace("%game%", rom.MachineName) + .Replace("%name%", rom.Name) + .Replace("%crc%", string.Empty) + .Replace("%md5%", ((Disk)rom).MD5) + .Replace("%sha1%", ((Disk)rom).SHA1) + .Replace("%sha256%", ((Disk)rom).SHA256) + .Replace("%sha384%", ((Disk)rom).SHA384) + .Replace("%sha512%", ((Disk)rom).SHA512) + .Replace("%size%", string.Empty); ; + } + else + { + // Check for special strings in prefix and postfix + pre = pre + .Replace("%game%", rom.MachineName) + .Replace("%name%", rom.Name) + .Replace("%crc%", string.Empty) + .Replace("%md5%", string.Empty) + .Replace("%sha1%", string.Empty) + .Replace("%size%", string.Empty); + post = post + .Replace("%game%", rom.MachineName) + .Replace("%name%", rom.Name) + .Replace("%crc%", string.Empty) + .Replace("%md5%", string.Empty) + .Replace("%sha1%", string.Empty) + .Replace("%size%", string.Empty); + } + + if (rom.Type == ItemType.Rom) + { + string inline = string.Format("\"" + datFile.FileName + "\"" + + "{0}\"" + datFile.Name + "\"" + + "{0}\"" + datFile.Description + "\"" + + "{0}\"" + rom.MachineName + "\"" + + "{0}\"" + rom.MachineDescription + "\"" + + "{0}" + "\"rom\"" + + "{0}\"" + rom.Name + "\"" + + "{0}" + "\"\"" + + "{0}\"" + ((Rom)rom).Size + "\"" + + "{0}\"" + ((Rom)rom).CRC + "\"" + + "{0}\"" + ((Rom)rom).MD5 + "\"" + + "{0}\"" + ((Rom)rom).SHA1 + "\"" + + "{0}\"" + ((Rom)rom).SHA256 + "\"" + // + "{0}\"" + ((Rom)rom).SHA384 + "\"" + // + "{0}\"" + ((Rom)rom).SHA512 + "\"" + + "{0}" + (((Rom)rom).ItemStatus != ItemStatus.None ? "\"" + ((Rom)rom).ItemStatus.ToString() + "\"" : "\"\""), delim); + state += pre + inline + post + "\n"; + } + else if (rom.Type == ItemType.Disk) + { + string inline = string.Format("\"" + datFile.FileName + "\"" + + "{0}\"" + datFile.Name + "\"" + + "{0}\"" + datFile.Description + "\"" + + "{0}\"" + rom.MachineName + "\"" + + "{0}\"" + rom.MachineDescription + "\"" + + "{0}" + "\"disk\"" + + "{0}" + "\"\"" + + "{0}\"" + rom.Name + "\"" + + "{0}" + "\"\"" + + "{0}" + "\"\"" + + "{0}\"" + ((Disk)rom).MD5 + "\"" + + "{0}\"" + ((Disk)rom).SHA1 + "\"" + + "{0}\"" + ((Disk)rom).SHA256 + "\"" + // + "{0}\"" + ((Rom)rom).SHA384 + "\"" + // + "{0}\"" + ((Rom)rom).SHA512 + "\"" + + "{0}" + (((Disk)rom).ItemStatus != ItemStatus.None ? "\"" + ((Disk)rom).ItemStatus.ToString() + "\"" : "\"\""), delim); + state += pre + inline + post + "\n"; + } + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + } +} diff --git a/SabreTools.Library/DatFiles/SoftwareList.cs b/SabreTools.Library/DatFiles/SoftwareList.cs new file mode 100644 index 00000000..bad449c8 --- /dev/null +++ b/SabreTools.Library/DatFiles/SoftwareList.cs @@ -0,0 +1,403 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; + +using SabreTools.Library.Data; +using SabreTools.Library.Items; +using SabreTools.Library.Tools; + +#if MONO +using System.IO; +#else +using Alphaleonis.Win32.Filesystem; + +using FileStream = System.IO.FileStream; +using StreamWriter = System.IO.StreamWriter; +#endif +using NaturalSort; + +namespace SabreTools.Library.DatFiles +{ + /// + /// Represents parsing and writing of an SabreDat XML DAT + /// + public class SoftwareList + { + /// + /// Parse an SabreDat XML DAT and return all found games and roms within + /// + /// DatFile to populate with the read information + /// 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 static void Parse( + DatFile datFile, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // All XML-derived DATs share a lot in common so it just calls one implementation + Logiqx.Parse(datFile, filename, sysid, srcid, keep, clean, remUnicode); + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// DatFile to write out from + /// 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 static bool WriteToFile(DatFile datFile, string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = FileTools.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(true)); + + // Write out the header + WriteHeader(datFile, sw); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = datFile.Keys.ToList(); + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = datFile[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(datFile, sw, rom); + } + + // If we have a "null" game (created by DATFromDir or something similar), log it to file + if (rom.Type == 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 + WriteRomData(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 + /// + /// DatFile to write out from + /// StreamWriter to output to + /// True if the data was written, false on error + private static bool WriteHeader(DatFile datFile, 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 + /// + /// DatFile to write out from + /// StreamWriter to output to + /// RomData object to be output + /// True if the data was written, false on error + private static bool WriteStartGame(DatFile datFile, 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" + + "\t\t" + HttpUtility.HtmlEncode(rom.MachineDescription) + "\n" + + (rom.Year != null ? "\t\t" + HttpUtility.HtmlEncode(rom.Year) + "\n" : "") + + (rom.Publisher != null ? "\t\t" + HttpUtility.HtmlEncode(rom.Publisher) + "\n" : ""); + + 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 static 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 RomData using the supplied StreamWriter + /// + /// StreamWriter to output to + /// RomData 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 static bool WriteRomData(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.Type == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + state += "\t\t\n"; + + foreach (Tuple kvp in rom.Features) + { + state += "\t\t\t\n"; + } + + switch (rom.Type) + { + case ItemType.Archive: + state += "\t\t\t\n" + + "\t\t\t\t\n" + + "\t\t\t\n"; + break; + case ItemType.BiosSet: + state += "\t\t\t\n" + + "\t\t\t\t\n" + + "\t\t\t\n"; + break; + case ItemType.Disk: + state += "\t\t\t\n" + + "\t\t\t\t\n" + + "\t\t\t\n"; + break; + case ItemType.Release: + 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; + case ItemType.Sample: + 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 static 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; + } + } +} diff --git a/SabreTools.Library/SabreTools.Library.csproj b/SabreTools.Library/SabreTools.Library.csproj index 5d461670..f8d1401c 100644 --- a/SabreTools.Library/SabreTools.Library.csproj +++ b/SabreTools.Library/SabreTools.Library.csproj @@ -115,17 +115,27 @@ + + - + - + + + + + + + + +