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