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\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\n"
- + "\t\t\n"
- + "\t\t\t.bin\n"
- + "\t\t\n"
- + "\t\t\n"
- + "\t\t\t" + HttpUtility.HtmlEncode(Url) + "\n"
- + "\t\t\t" + HttpUtility.HtmlEncode(Url) + "\n"
- + "\t\t\t" + HttpUtility.HtmlEncode(Url) + "\n"
- + "\t\t\n"
- + "\t\t\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\t\n"
- + "\t\t\n"
- + "\t\t%u - %n\n"
- + "\t\n"
- + "\t\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 = "\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(Comment) ? "\t\t" + HttpUtility.HtmlEncode(Comment) + "\n" : "") +
- (!String.IsNullOrEmpty(Type) || ForcePacking != ForcePacking.None || ForceMerging != ForceMerging.None || ForceNodump != ForceNodump.None ?
- "\t\t\n" +
- (!String.IsNullOrEmpty(Type) ? "\t\t\t\n" : "") +
- (ForcePacking == ForcePacking.Unzip ? "\t\t\t\n" : "") +
- (ForcePacking == ForcePacking.Zip ? "\t\t\t\n" : "") +
- (ForceMerging == ForceMerging.Full ? "\t\t\t\n" : "") +
- (ForceMerging == ForceMerging.Split ? "\t\t\t\n" : "") +
- (ForceMerging == ForceMerging.Merged ? "\t\t\t\n" : "") +
- (ForceMerging == ForceMerging.NonMerged ? "\t\t\t\n" : "") +
- (ForceNodump == ForceNodump.Ignore ? "\t\t\t\n" : "") +
- (ForceNodump == ForceNodump.Obsolete ? "\t\t\t\n" : "") +
- (ForceNodump == ForceNodump.Required ? "\t\t\t\n" : "") +
- "\t\t\n"
- : "") +
- "\t\n" +
- "\t\n";
- break;
- case DatFormat.SoftwareList:
- header = "\n" +
- "\n\n" +
- "\n\n";
- break;
- }
-
- // 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
- /// Output format to write 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 int WriteStartGame(StreamWriter sw, DatFormat datFormat, 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 = "";
- 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\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");
- 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 += "\n";
- }
- depth = depth - (last == -1 ? 0 : last) + newsplit.Count;
- break;
- case DatFormat.SoftwareList:
- 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";
- }
- break;
- }
-
- 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
- /// Output format to write to
- /// RomData object to be output
- /// Split path representing last kwown parent game (SabreDAT only)
- /// Split path representing the parent game (SabreDAT only)
- /// The name of the last game to be output
- /// Current depth to output file at (SabreDAT only)
- /// Last known depth to cycle back from (SabreDAT only)
- /// The new depth of the tag
- private int WriteEndGame(StreamWriter sw, DatFormat datFormat, DatItem rom, List splitpath, List 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\n";
- break;
- case DatFormat.OfflineList:
- state += "\t\t\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 += "\n";
- }
-
- // Reset the current depth
- depth = 2 + last;
- }
- break;
- case DatFormat.SoftwareList:
- state += "\t\n\n";
- break;
- }
-
- 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
- /// Output format to write to
- /// RomData object to be output
- /// The name of the last game to be output
- /// Current depth to output file at (SabreDAT only)
- /// True if blank roms should be skipped on output, false otherwise (default)
- /// True if the data was written, false on error
- private bool 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\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;
- }
- 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\n"
- + "\t\t\t1\n"
- + "\t\t\t1\n"
- + "\t\t\t" + 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\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\n"
+ + "\t\t\n"
+ + "\t\t\t.bin\n"
+ + "\t\t\n"
+ + "\t\t\n"
+ + "\t\t\t" + HttpUtility.HtmlEncode(datFile.Url) + "\n"
+ + "\t\t\t" + HttpUtility.HtmlEncode(datFile.Url) + "\n"
+ + "\t\t\t" + HttpUtility.HtmlEncode(datFile.Url) + "\n"
+ + "\t\t\n"
+ + "\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\t\n"
+ + "\t\t\n"
+ + "\t\t%u - %n\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
+ /// True if the data was written, false on error
+ private static bool WriteEndGame(StreamWriter sw)
+ {
+ try
+ {
+ string state = "\t\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 = "";
+ state += "\t\t\n"
+ + "\t\t\t1\n"
+ + "\t\t\t1\n"
+ + "\t\t\t" + 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 @@
+
+
-
+
-
+
+
+
+
+
+
+
+
+