From 5e303cde49860c97965f0ab764ad811e35f2c9ba Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Sat, 13 Jan 2018 22:42:42 -0800 Subject: [PATCH] [ALL] Fix device merging, add new parsing prototypes --- SabreTools.Library/DatFiles/DatFile.cs | 44 +- SabreTools.Library/DatFiles/Logiqx.cs | 514 +++++++++++- SabreTools.Library/DatFiles/MameListxml.cs | 819 +++++++++++++++++++ SabreTools.Library/DatFiles/OfflineList.cs | 1 + SabreTools.Library/DatFiles/SoftwareList.cs | 439 ++++++++-- SabreTools.Library/DatItems/DatItem.cs | 22 +- SabreTools.Library/DatItems/Disk.cs | 30 + SabreTools.Library/DatItems/Machine.cs | 9 + SabreTools.Library/DatItems/Rom.cs | 30 + SabreTools.Library/Data/Constants.cs | 326 ++++++++ SabreTools.Library/SabreTools.Library.csproj | 1 + SabreTools.Library/Tools/Utilities.cs | 51 +- 12 files changed, 2200 insertions(+), 86 deletions(-) create mode 100644 SabreTools.Library/DatFiles/MameListxml.cs diff --git a/SabreTools.Library/DatFiles/DatFile.cs b/SabreTools.Library/DatFiles/DatFile.cs index e533ff5f..23c82f47 100644 --- a/SabreTools.Library/DatFiles/DatFile.cs +++ b/SabreTools.Library/DatFiles/DatFile.cs @@ -1429,6 +1429,12 @@ namespace SabreTools.Library.DatFiles i--; // This make sure that the pointer stays on the correct since one was removed } } + + // If the key is now empty, remove it + if (this[key].Count == 0) + { + Remove(key); + } } } @@ -1460,6 +1466,19 @@ namespace SabreTools.Library.DatFiles AddRange(key, sortedlist); }); } + // If the merge type is the same, we want to sort the dictionary to be consistent + else + { + List keys = Keys; + Parallel.ForEach(keys, Globals.ParallelOptions, key => + { + // Get the possibly unsorted list + List sortedlist = this[key]; + + // Sort the list of items to be consistent + DatItem.Sort(ref sortedlist, false); + }); + } // Now clean up all empty keys CleanEmptyKeys(); @@ -2659,7 +2678,8 @@ namespace SabreTools.Library.DatFiles BucketBy(SortedBy.Game, mergeroms, norename: true); // Now we want to loop through all of the games and set the correct information - AddRomsFromDevices(); + AddRomsFromDevices(true); + AddRomsFromDevices(false); // Then, remove the romof and cloneof tags so it's not picked up by the manager RemoveTagsFromChild(); @@ -2680,7 +2700,8 @@ namespace SabreTools.Library.DatFiles BucketBy(SortedBy.Game, mergeroms, norename: true); // Now we want to loop through all of the games and set the correct information - AddRomsFromDevices(); + AddRomsFromDevices(true); + AddRomsFromDevices(false); AddRomsFromParent(); // Now that we have looped through the cloneof tags, we loop through the romof tags @@ -2808,9 +2829,10 @@ namespace SabreTools.Library.DatFiles } /// - /// Use device_ref tags to add roms to the children + /// Use device_ref and slotoption tags to add roms to the children /// - private void AddRomsFromDevices() + /// True if only child device sets are touched, false for non-device sets (default) + private void AddRomsFromDevices(bool dev = false) { List games = Keys; foreach (string game in games) @@ -2821,6 +2843,12 @@ namespace SabreTools.Library.DatFiles continue; } + // If the game (is/is not) a bios, we want to continue + if (dev ^ (this[game][0].MachineType & MachineType.Device) != 0) + { + continue; + } + // If the game has no devices, we continue if (this[game][0].Devices == null || this[game][0].Devices.Count == 0) { @@ -2942,7 +2970,7 @@ namespace SabreTools.Library.DatFiles foreach (DatItem item in items) { // If the disk doesn't have a valid merge tag OR the merged file doesn't exist in the parent, then add it - if (item.Type == ItemType.Disk && (item.MergeTag == null || !this[parent].Select(i => i.Name).Contains(item.MergeTag))) + if (item.Type == ItemType.Disk && (((Disk)item).MergeTag == null || !this[parent].Select(i => i.Name).Contains(((Disk)item).MergeTag))) { item.CopyMachineInformation(copyFrom); Add(parent, item); @@ -2976,8 +3004,8 @@ namespace SabreTools.Library.DatFiles foreach (string game in games) { if (this[game].Count > 0 - && (this[game][0].MachineType == MachineType.Bios - || this[game][0].MachineType == MachineType.Device)) + && ((this[game][0].MachineType & MachineType.Bios) != 0 + || (this[game][0].MachineType & MachineType.Device) != 0)) { Remove(game); } @@ -3001,7 +3029,7 @@ namespace SabreTools.Library.DatFiles } // If the game (is/is not) a bios, we want to continue - if (bios ^ this[game][0].MachineType == MachineType.Bios) + if (bios ^ (this[game][0].MachineType & MachineType.Bios) != 0) { continue; } diff --git a/SabreTools.Library/DatFiles/Logiqx.cs b/SabreTools.Library/DatFiles/Logiqx.cs index d50314fe..0d6c708b 100644 --- a/SabreTools.Library/DatFiles/Logiqx.cs +++ b/SabreTools.Library/DatFiles/Logiqx.cs @@ -25,6 +25,8 @@ namespace SabreTools.Library.DatFiles /// /// Represents parsing and writing of a Logiqx-derived DAT /// + /// TODO: Separate out reading of other DAT types into their own classes + /// TODO: Add XSD validation for all XML DAT types internal class Logiqx : DatFile { // Private instance variables specific to Logiqx DATs @@ -423,6 +425,20 @@ namespace SabreTools.Library.DatFiles subreader.MoveToContent(); // Create a new machine + MachineType machineType = MachineType.NULL; + if (Utilities.GetYesNo(xtr.GetAttribute("isbios")) == true) + { + machineType |= MachineType.Bios; + } + if (Utilities.GetYesNo(xtr.GetAttribute("isdevice")) == true) + { + machineType |= MachineType.Device; + } + if (Utilities.GetYesNo(xtr.GetAttribute("ismechanical")) == true) + { + machineType |= MachineType.Mechanical; + } + Machine machine = new Machine { Name = xtr.GetAttribute("name"), @@ -433,11 +449,7 @@ namespace SabreTools.Library.DatFiles 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, + MachineType = (machineType == MachineType.NULL ? MachineType.None : machineType), }; // Get the supported value from the reader @@ -1047,6 +1059,492 @@ namespace SabreTools.Library.DatFiles xtr.Dispose(); } + /// + /// Parse a Logiqx XML DAT and return all found games and roms within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + public void ParseFileStripped( + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Prepare all internal variables + Encoding enc = Utilities.GetEncoding(filename); + XmlReader xtr = Utilities.GetXmlTextReader(filename); + + // If we got a null reader, just return + if (xtr == null) + { + return; + } + + // Otherwise, read the file to the end + try + { + xtr.MoveToContent(); + while (!xtr.EOF) + { + // We only want elements + if (xtr.NodeType != XmlNodeType.Element) + { + xtr.Read(); + continue; + } + + switch (xtr.Name) + { + // The datafile tag can have some attributes + case "datafile": + // string build = xtr.GetAttribute("build"); + // string debug = xtr.GetAttribute("debug"); // (yes|no) "no" + xtr.Read(); + break; + // We want to process the entire subtree of the header + case "header": + ReadHeader(xtr.ReadSubtree(), keep); + + // Skip the header node now that we've processed it + xtr.Skip(); + break; + // We want to process the entire subtree of the game + case "machine": // New-style Logiqx + case "game": // Old-style Logiqx + ReadMachine(xtr.ReadSubtree(), filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the machine now that we've processed it + xtr.Skip(); + break; + default: + xtr.Read(); + break; + } + } + } + catch (Exception ex) + { + Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); + + // For XML errors, just skip the affected node + xtr?.Read(); + } + + xtr.Dispose(); + } + + /// + /// Read header information + /// + /// XmlReader to use to parse the header + /// True if full pathnames are to be kept, false otherwise (default) + private void ReadHeader(XmlReader reader, bool keep) + { + bool superdat = false; + + // If there's no subtree to the header, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element || reader.Name == "header") + { + reader.Read(); + continue; + } + + // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) + string content = ""; + switch (reader.Name) + { + case "name": + content = reader.ReadElementContentAsString(); ; + Name = (String.IsNullOrWhiteSpace(Name) ? content : Name); + superdat = superdat || content.Contains(" - SuperDAT"); + if (keep && superdat) + { + Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); + } + break; + case "description": + content = reader.ReadElementContentAsString(); + Description = (String.IsNullOrWhiteSpace(Description) ? content : Description); + break; + case "rootdir": // TODO: Is this Logiqx? + content = reader.ReadElementContentAsString(); + RootDir = (String.IsNullOrWhiteSpace(RootDir) ? content : RootDir); + break; + case "category": + content = reader.ReadElementContentAsString(); + Category = (String.IsNullOrWhiteSpace(Category) ? content : Category); + break; + case "version": + content = reader.ReadElementContentAsString(); + Version = (String.IsNullOrWhiteSpace(Version) ? content : Version); + break; + case "date": + content = reader.ReadElementContentAsString(); + Date = (String.IsNullOrWhiteSpace(Date) ? content.Replace(".", "/") : Date); + break; + case "author": + content = reader.ReadElementContentAsString(); + Author = (String.IsNullOrWhiteSpace(Author) ? content : Author); + break; + case "email": + content = reader.ReadElementContentAsString(); + Email = (String.IsNullOrWhiteSpace(Email) ? content : Email); + break; + case "homepage": + content = reader.ReadElementContentAsString(); + Homepage = (String.IsNullOrWhiteSpace(Homepage) ? content : Homepage); + break; + case "url": + content = reader.ReadElementContentAsString(); + Url = (String.IsNullOrWhiteSpace(Url) ? content : Url); + break; + case "comment": + content = reader.ReadElementContentAsString(); + Comment = (String.IsNullOrWhiteSpace(Comment) ? content : Comment); + break; + case "type": // TODO: Is this Logiqx? + content = reader.ReadElementContentAsString(); + Type = (String.IsNullOrWhiteSpace(Type) ? content : Type); + superdat = superdat || content.Contains("SuperDAT"); + break; + case "clrmamepro": + if (String.IsNullOrWhiteSpace(Header)) + { + Header = reader.GetAttribute("header"); + } + if (ForceMerging == ForceMerging.None) + { + ForceMerging = Utilities.GetForceMerging(reader.GetAttribute("forcemerging")); + } + if (ForceNodump == ForceNodump.None) + { + ForceNodump = Utilities.GetForceNodump(reader.GetAttribute("forcenodump")); + } + if (ForcePacking == ForcePacking.None) + { + ForcePacking = Utilities.GetForcePacking(reader.GetAttribute("forcepacking")); + } + break; + case "romcenter": + if (reader.GetAttribute("plugin") != null) + { + // CDATA + } + if (reader.GetAttribute("rommode") != null) + { + // (merged|split|unmerged) "split" + } + if (reader.GetAttribute("biosmode") != null) + { + // merged|split|unmerged) "split" + } + if (reader.GetAttribute("samplemode") != null) + { + // (merged|unmerged) "merged" + } + if (reader.GetAttribute("lockrommode") != null) + { + // (yes|no) "no" + } + if (reader.GetAttribute("lockbiosmode") != null) + { + // (yes|no) "no" + } + if (reader.GetAttribute("locksamplemode") != null) + { + // (yes|no) "no" + } + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Read game/machine information + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private void ReadMachine( + XmlReader reader, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // If we have an empty machine, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + string key = ""; + string temptype = reader.Name; + bool containsItems = false; + List parent = new List(); + + // Create a new machine + MachineType machineType = MachineType.NULL; + if (Utilities.GetYesNo(reader.GetAttribute("isbios")) == true) + { + machineType |= MachineType.Bios; + } + if (Utilities.GetYesNo(reader.GetAttribute("isdevice")) == true) + { + machineType |= MachineType.Device; + } + if (Utilities.GetYesNo(reader.GetAttribute("ismechanical")) == true) + { + machineType |= MachineType.Mechanical; + } + + Machine machine = new Machine + { + Name = reader.GetAttribute("name"), + Description = reader.GetAttribute("name"), + SourceFile = reader.GetAttribute("sourcefile"), + Board = reader.GetAttribute("board"), + RebuildTo = reader.GetAttribute("rebuildto"), + + Comment = "", + + CloneOf = reader.GetAttribute("cloneof") ?? "", + RomOf = reader.GetAttribute("romof") ?? "", + SampleOf = reader.GetAttribute("sampleof") ?? "", + + MachineType = (machineType == MachineType.NULL ? MachineType.None : machineType), + }; + + if (Type == "SuperDAT" && !keep) + { + string tempout = Regex.Match(machine.Name, @".*?\\(.*)").Groups[1].Value; + if (!String.IsNullOrWhiteSpace(tempout)) + { + machine.Name = tempout; + } + } + // Get the name of the game from the parent + else if (Type == "SuperDAT" && keep && parent.Count > 0) + { + machine.Name = String.Join("\\", parent) + "\\" + machine.Name; + } + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the roms from the machine + switch (reader.Name) + { + case "comment": // There can be multiple comments by spec + machine.Comment += reader.ReadElementContentAsString(); + break; + case "description": + machine.Description = reader.ReadElementContentAsString(); + break; + case "year": + machine.Year = reader.ReadElementContentAsString(); + break; + case "manufacturer": + machine.Manufacturer = reader.ReadElementContentAsString(); + break; + case "release": + containsItems = true; + + DatItem release = new Release + { + Name = reader.GetAttribute("name"), + Region = reader.GetAttribute("region"), + Language = reader.GetAttribute("language"), + Date = reader.GetAttribute("date"), + Default = Utilities.GetYesNo(reader.GetAttribute("default")), + }; + + release.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(release, clean, remUnicode); + + reader.Read(); + break; + case "biosset": + containsItems = true; + + DatItem biosset = new BiosSet + { + Name = reader.GetAttribute("name"), + Description = reader.GetAttribute("description"), + Default = Utilities.GetYesNo(reader.GetAttribute("default")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + biosset.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(biosset, clean, remUnicode); + + reader.Read(); + break; + case "rom": + containsItems = true; + + DatItem rom = new Rom + { + Name = reader.GetAttribute("name"), + Size = Utilities.GetSize(reader.GetAttribute("size")), + CRC = reader.GetAttribute("crc")?.ToLowerInvariant(), + MD5 = reader.GetAttribute("md5")?.ToLowerInvariant(), + SHA1 = reader.GetAttribute("sha1")?.ToLowerInvariant(), + SHA256 = reader.GetAttribute("sha256")?.ToLowerInvariant(), + SHA384 = reader.GetAttribute("sha384")?.ToLowerInvariant(), + SHA512 = reader.GetAttribute("sha512")?.ToLowerInvariant(), + MergeTag = reader.GetAttribute("merge"), + ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), + Date = Utilities.GetDate(reader.GetAttribute("date")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + rom.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(rom, clean, remUnicode); + + reader.Read(); + break; + case "disk": + containsItems = true; + + DatItem disk = new Disk + { + Name = reader.GetAttribute("name"), + MD5 = reader.GetAttribute("md5")?.ToLowerInvariant(), + SHA1 = reader.GetAttribute("sha1")?.ToLowerInvariant(), + SHA256 = reader.GetAttribute("sha256")?.ToLowerInvariant(), + SHA384 = reader.GetAttribute("sha384")?.ToLowerInvariant(), + SHA512 = reader.GetAttribute("sha512")?.ToLowerInvariant(), + MergeTag = reader.GetAttribute("merge"), + ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + disk.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(disk, clean, remUnicode); + + reader.Read(); + break; + case "sample": + containsItems = true; + + DatItem samplerom = new Sample + { + Name = reader.GetAttribute("name"), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + samplerom.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(samplerom, clean, remUnicode); + + reader.Read(); + break; + case "archive": + containsItems = true; + + DatItem archiverom = new Archive + { + Name = reader.GetAttribute("name"), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + archiverom.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(archiverom, clean, remUnicode); + + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + + // If no items were found for this machine, add a Blank placeholder + if (!containsItems) + { + Blank blank = new Blank() + { + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + blank.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(blank, clean, remUnicode); + } + } + /// /// Create and open an output file for writing direct from a dictionary /// @@ -1221,9 +1719,9 @@ namespace SabreTools.Library.DatFiles string state = "\t<" + (_depreciated ? "game" : "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.MachineType & MachineType.Bios) != 0 ? " isbios=\"yes\"" : "") + + ((rom.MachineType & MachineType.Device) != 0 ? " isdevice=\"yes\"" : "") + + ((rom.MachineType & MachineType.Mechanical) != 0 ? " ismechanical=\"yes\"" : "") + (rom.Runnable == true ? " runnable=\"yes\"" : "") + (String.IsNullOrWhiteSpace(rom.CloneOf) || (rom.MachineName.ToLowerInvariant() == rom.CloneOf.ToLowerInvariant()) ? "" diff --git a/SabreTools.Library/DatFiles/MameListxml.cs b/SabreTools.Library/DatFiles/MameListxml.cs new file mode 100644 index 00000000..d2b9ce14 --- /dev/null +++ b/SabreTools.Library/DatFiles/MameListxml.cs @@ -0,0 +1,819 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Web; +using System.Xml; + +using SabreTools.Library.Data; +using SabreTools.Library.DatItems; +using SabreTools.Library.Tools; + +#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 MAME XML DAT + /// + /// TODO: Verify that all read/write for this DatFile type is correct + internal class MameListxml : DatFile + { + /// + /// Constructor designed for casting a base DatFile + /// + /// Parent DatFile to copy from + public MameListxml(DatFile datFile) + : base(datFile, cloneHeader: false) + { + } + + /// + /// Parse a MAME XML DAT and return all found games and roms within + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + /// + /// + public override void ParseFile( + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // All XML-derived DATs share a lot in common so it just calls one implementation + // new Logiqx(this, false).ParseFile(filename, sysid, srcid, keep, clean, remUnicode); + + // Prepare all internal variables + Encoding enc = Utilities.GetEncoding(filename); + XmlReader xtr = Utilities.GetXmlTextReader(filename); + + // If we got a null reader, just return + if (xtr == null) + { + return; + } + + // Otherwise, read the file to the end + try + { + xtr.MoveToContent(); + while (!xtr.EOF) + { + // We only want elements + if (xtr.NodeType != XmlNodeType.Element) + { + xtr.Read(); + continue; + } + + switch (xtr.Name) + { + case "mame": + Name = (String.IsNullOrWhiteSpace(Name) ? xtr.GetAttribute("build") : Name); + Description = (String.IsNullOrWhiteSpace(Description) ? Name : Name); + // string debug = xtr.GetAttribute("debug"); // (yes|no) "no" + // string mameconfig = xtr.GetAttribute("mameconfig"); CDATA + xtr.Read(); + break; + // Handle M1 DATs since they're 99% the same as a SL DAT + case "m1": + Name = (String.IsNullOrWhiteSpace(Name) ? "M1" : Name); + Description = (String.IsNullOrWhiteSpace(Description) ? "M1" : Description); + Version = (String.IsNullOrWhiteSpace(Version) ? xtr.GetAttribute("version") ?? "" : Version); + xtr.Read(); + break; + // We want to process the entire subtree of the machine + case "machine": + ReadMachine(xtr.ReadSubtree(), filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the machine now that we've processed it + xtr.Skip(); + break; + default: + xtr.Read(); + break; + } + } + } + catch (Exception ex) + { + Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); + + // For XML errors, just skip the affected node + xtr?.Read(); + } + + xtr.Dispose(); + } + + /// + /// Read machine information + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private void ReadMachine( + XmlReader reader, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // If we have an empty machine, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + string key = ""; + string temptype = reader.Name; + bool containsItems = false; + + // Create a new machine + MachineType machineType = MachineType.NULL; + if (Utilities.GetYesNo(reader.GetAttribute("isbios")) == true) + { + machineType |= MachineType.Bios; + } + if (Utilities.GetYesNo(reader.GetAttribute("isdevice")) == true) + { + machineType |= MachineType.Device; + } + if (Utilities.GetYesNo(reader.GetAttribute("ismechanical")) == true) + { + machineType |= MachineType.Mechanical; + } + + Machine machine = new Machine + { + Name = reader.GetAttribute("name"), + Description = reader.GetAttribute("name"), + SourceFile = reader.GetAttribute("sourcefile"), + Runnable = Utilities.GetYesNo(reader.GetAttribute("runnable")), + + Comment = "", + + CloneOf = reader.GetAttribute("cloneof") ?? "", + RomOf = reader.GetAttribute("romof") ?? "", + SampleOf = reader.GetAttribute("sampleof") ?? "", + + MachineType = (machineType == MachineType.NULL ? MachineType.None : machineType), + }; + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the roms from the machine + switch (reader.Name) + { + case "description": + machine.Description = reader.ReadElementContentAsString(); + break; + case "year": + machine.Year = reader.ReadElementContentAsString(); + break; + case "manufacturer": + machine.Manufacturer = reader.ReadElementContentAsString(); + break; + case "biosset": + containsItems = true; + + DatItem biosset = new BiosSet + { + Name = reader.GetAttribute("name"), + Description = reader.GetAttribute("description"), + Default = Utilities.GetYesNo(reader.GetAttribute("default")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + biosset.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(biosset, clean, remUnicode); + + reader.Read(); + break; + case "rom": + containsItems = true; + + DatItem rom = new Rom + { + Name = reader.GetAttribute("name"), + Bios = reader.GetAttribute("bios"), + Size = Utilities.GetSize(reader.GetAttribute("size")), + CRC = reader.GetAttribute("crc")?.ToLowerInvariant(), + MD5 = reader.GetAttribute("md5")?.ToLowerInvariant(), + SHA1 = reader.GetAttribute("sha1")?.ToLowerInvariant(), + SHA256 = reader.GetAttribute("sha256")?.ToLowerInvariant(), + SHA384 = reader.GetAttribute("sha384")?.ToLowerInvariant(), + SHA512 = reader.GetAttribute("sha512")?.ToLowerInvariant(), + MergeTag = reader.GetAttribute("merge"), + Region = reader.GetAttribute("region"), + Offset = reader.GetAttribute("offset"), + ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), + Optional = Utilities.GetYesNo(reader.GetAttribute("optional")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + rom.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(rom, clean, remUnicode); + + reader.Read(); + break; + case "disk": + containsItems = true; + + DatItem disk = new Disk + { + Name = reader.GetAttribute("name"), + MD5 = reader.GetAttribute("md5")?.ToLowerInvariant(), + SHA1 = reader.GetAttribute("sha1")?.ToLowerInvariant(), + SHA256 = reader.GetAttribute("sha256")?.ToLowerInvariant(), + SHA384 = reader.GetAttribute("sha384")?.ToLowerInvariant(), + SHA512 = reader.GetAttribute("sha512")?.ToLowerInvariant(), + MergeTag = reader.GetAttribute("merge"), + Region = reader.GetAttribute("region"), + Index = reader.GetAttribute("index"), + Writable = Utilities.GetYesNo(reader.GetAttribute("writable")), + ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), + Optional = Utilities.GetYesNo(reader.GetAttribute("optional")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + disk.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(disk, clean, remUnicode); + + reader.Read(); + break; + case "device_ref": + machine.Devices.Add(reader.ReadElementContentAsString()); + + reader.Read(); + break; + case "sample": + containsItems = true; + + DatItem samplerom = new Sample + { + Name = reader.GetAttribute("name"), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + samplerom.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(samplerom, clean, remUnicode); + + reader.Read(); + break; + case "chip": + // string name = reader.GetAttribute("name"); + // string tag = reader.GetAttribute("tag"); + // string type = reader.GetAttribute("type"); // (cpu|audio) + // string clock = reader.GetAttribute("clock"); + + reader.Read(); + break; + case "display": + // string tag = reader.GetAttribute("tag"); + // string type = reader.GetAttribute("type"); // (raster|vector|lcd|svg|unknown) + // string rotate = reader.GetAttribute("rotate"); // (0|90|180|270) + // bool? flipx = Utilities.GetYesNo(reader.GetAttribute("flipx")); + // string width = reader.GetAttribute("width"); + // string height = reader.GetAttribute("height"); + // string refresh = reader.GetAttribute("refresh"); + // string pixclock = reader.GetAttribute("pixclock"); + // string htotal = reader.GetAttribute("htotal"); + // string hbend = reader.GetAttribute("hbend"); + // string hbstart = reader.GetAttribute("hbstart"); + // string vtotal = reader.GetAttribute("vtotal"); + // string vbend = reader.GetAttribute("vbend"); + // string vbstart = reader.GetAttribute("vbstart"); + + reader.Read(); + break; + case "sound": + // string channels = reader.GetAttribute("channels"); + + reader.Read(); + break; + case "condition": + // string tag = reader.GetAttribute("tag"); + // string mask = reader.GetAttribute("mask"); + // string relation = reader.GetAttribute("relation"); // (eq|ne|gt|le|lt|ge) + // string value = reader.GetAttribute("value"); + + reader.Read(); + break; + case "input": + // bool? service = Utilities.GetYesNo(reader.GetAttribute("service")); + // bool? tilt = Utilities.GetYesNo(reader.GetAttribute("tilt")); + // string players = reader.GetAttribute("players"); + // string coins = reader.GetAttribute("coins"); + + // // While the subtree contains elements... + // string type = reader.GetAttribute("type"); + // string player = reader.GetAttribute("player"); + // string buttons = reader.GetAttribute("buttons"); + // string regbuttons = reader.GetAttribute("regbuttons"); + // string minimum = reader.GetAttribute("minimum"); + // string maximum = reader.GetAttribute("maximum"); + // string sensitivity = reader.GetAttribute("sensitivity"); + // string keydelta = reader.GetAttribute("keydelta"); + // bool? reverse = Utilities.GetYesNo(reader.GetAttribute("reverse")); + // string ways = reader.GetAttribute("ways"); + // string ways2 = reader.GetAttribute("ways2"); + // string ways3 = reader.GetAttribute("ways3"); + + reader.Read(); + break; + case "dipswitch": + // string name = reader.GetAttribute("name"); + // string tag = reader.GetAttribute("tag"); + // string mask = reader.GetAttribute("mask"); + + // // While the subtree contains elements... + // string name = reader.GetAttribute("name"); + // string number = reader.GetAttribute("number"); + // bool? inverted = Utilities.GetYesNo(reader.GetAttribute("inverted")); + + // // While the subtree contains elements... + // string name = reader.GetAttribute("name"); + // string value = reader.GetAttribute("value"); + // bool? default = Utilities.GetYesNo(reader.GetAttribute("default")); + + reader.Read(); + break; + case "configuration": + // string name = reader.GetAttribute("name"); + // string tag = reader.GetAttribute("tag"); + // string mask = reader.GetAttribute("mask"); + + // // While the subtree contains elements... + // string name = reader.GetAttribute("name"); + // string number = reader.GetAttribute("number"); + // bool? inverted = Utilities.GetYesNo(reader.GetAttribute("inverted")); + + // // While the subtree contains elements... + // string name = reader.GetAttribute("name"); + // string value = reader.GetAttribute("value"); + // bool? default = Utilities.GetYesNo(reader.GetAttribute("default")); + + reader.Read(); + break; + case "port": + // string tag = reader.GetAttribute("tag"); + + // // While the subtree contains elements... + // string mask = reader.GetAttribute("mask"); + + reader.Read(); + break; + case "adjuster": + // string name = reader.GetAttribute("name"); + // bool? default = Utilities.GetYesNo(reader.GetAttribute("default")); + + // // For the one possible element... + // string tag = reader.GetAttribute("tag"); + // string mask = reader.GetAttribute("mask"); + // string relation = reader.GetAttribute("relation"); // (eq|ne|gt|le|lt|ge) + // string value = reader.GetAttribute("value"); + + reader.Read(); + break; + case "driver": + // string status = reader.GetAttribute("status"); // (good|imperfect|preliminary) + // string emulation = reader.GetAttribute("emulation"); // (good|imperfect|preliminary) + // string cocktail = reader.GetAttribute("cocktail"); // (good|imperfect|preliminary) + // string savestate = reader.GetAttribute("savestate"); // (supported|unsupported) + + reader.Read(); + break; + case "feature": + // string type = reader.GetAttribute("type"); // (protection|palette|graphics|sound|controls|keyboard|mouse|microphone|camera|disk|printer|lan|wan|timing) + // string status = reader.GetAttribute("status"); // (unemulated|imperfect) + // string overall = reader.GetAttribute("overall"); // (unemulated|imperfect) + + reader.Read(); + break; + case "device": + // string type = reader.GetAttribute("type"); + // string tag = reader.GetAttribute("tag"); + // string fixed_image = reader.GetAttribute("fixed_image"); + // string mandatory = reader.GetAttribute("mandatory"); + // string interface = reader.GetAttribute("interface"); + + // // For the one possible element... + // string name = reader.GetAttribute("name"); + // string briefname = reader.GetAttribute("briefname"); + + // // While the subtree contains elements... + // string name = reader.GetAttribute("name"); + + reader.Read(); + break; + case "slot": + // string name = reader.GetAttribute("name"); + + // // While the subtree contains elements... (These get added as devices currently) + // string name = reader.GetAttribute("name"); + // string devname = reader.GetAttribute("devname"); + // bool? default = Utilities.GetYesNo(reader.GetAttribute("default")); + + reader.Read(); + break; + case "softwarelist": + // string name = reader.GetAttribute("name"); + // string status = reader.GetAttribute("status"); // (original|compatible) + // string filter = reader.GetAttribute("filter"); + + reader.Read(); + break; + case "ramoption": + // string default = reader.GetAttribute("default"); + + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + + // If no items were found for this machine, add a Blank placeholder + if (!containsItems) + { + Blank blank = new Blank() + { + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + blank.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(blank, clean, remUnicode); + } + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the DAT was written correctly, false otherwise + public override bool WriteToFile(string outfile, bool ignoreblanks = false) + { + try + { + Globals.Logger.User("Opening file for writing: {0}", outfile); + FileStream fs = Utilities.TryCreate(outfile); + + // If we get back null for some reason, just log and return + if (fs == null) + { + Globals.Logger.Warning("File '{0}' could not be created for writing! Please check to see if the file is writable", outfile); + return false; + } + + StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false)); + + // Write out the header + WriteHeader(sw); + + // Write out each of the machines and roms + string lastgame = null; + + // Get a properly sorted set of keys + List keys = Keys; + keys.Sort(new NaturalComparer()); + + foreach (string key in keys) + { + List roms = this[key]; + + // Resolve the names in the block + roms = DatItem.ResolveNames(roms); + + for (int index = 0; index < roms.Count; index++) + { + DatItem rom = roms[index]; + + // There are apparently times when a null rom can skip by, skip them + if (rom.Name == null || rom.MachineName == null) + { + Globals.Logger.Warning("Null rom found!"); + continue; + } + + // If we have a different game and we're not at the start of the list, output the end of last item + if (lastgame != null && lastgame.ToLowerInvariant() != rom.MachineName.ToLowerInvariant()) + { + WriteEndGame(sw); + } + + // 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); + + lastgame = rom.MachineName; + continue; + } + + // Now, output the rom data + WriteDatItem(sw, rom, ignoreblanks); + + // Set the new data to compare against + lastgame = rom.MachineName; + } + } + + // Write the file footer out + WriteFooter(sw); + + Globals.Logger.Verbose("File written!" + Environment.NewLine); + sw.Dispose(); + fs.Dispose(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT header using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteHeader(StreamWriter sw) + { + try + { + string header = "\n" + + "\n\n"; + + // Write the header out + sw.Write(header); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out Game start using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if the data was written, false on error + private bool WriteStartGame(StreamWriter sw, DatItem rom) + { + try + { + // No game should start with a path separator + if (rom.MachineName.StartsWith(Path.DirectorySeparatorChar.ToString())) + { + rom.MachineName = rom.MachineName.Substring(1); + } + + string state = "\t\n" + + "\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 bool WriteEndGame(StreamWriter sw) + { + try + { + string state = "\t\n"; + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DatItem using the supplied StreamWriter + /// + /// StreamWriter to output to + /// DatItem object to be output + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if the data was written, false on error + private bool WriteDatItem(StreamWriter sw, DatItem rom, bool ignoreblanks = false) + { + // If we are in ignore blanks mode AND we have a blank (0-size) rom, skip + if (ignoreblanks + && (rom.Type == ItemType.Rom + && (((Rom)rom).Size == 0 || ((Rom)rom).Size == -1))) + { + return true; + } + + try + { + string state = ""; + switch (rom.Type) + { + case ItemType.Archive: + break; + case ItemType.BiosSet: + state += "\t\t\n"; + break; + case ItemType.Disk: + state += "\t\t\n"; + break; + case ItemType.Rom: + state += "\t\t\n"; + break; + case ItemType.Sample: + state += "\t\t\n"; + break; + } + + sw.Write(state); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + + /// + /// Write out DAT footer using the supplied StreamWriter + /// + /// StreamWriter to output to + /// True if the data was written, false on error + private bool WriteFooter(StreamWriter sw) + { + try + { + string footer = "\t\n\n"; + + // Write the footer out + sw.Write(footer); + sw.Flush(); + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + + return true; + } + } +} diff --git a/SabreTools.Library/DatFiles/OfflineList.cs b/SabreTools.Library/DatFiles/OfflineList.cs index 98e39edb..0f9c1677 100644 --- a/SabreTools.Library/DatFiles/OfflineList.cs +++ b/SabreTools.Library/DatFiles/OfflineList.cs @@ -22,6 +22,7 @@ namespace SabreTools.Library.DatFiles /// /// Represents parsing and writing of an OfflineList XML DAT /// + /// TODO: Verify that all read/write for this DatFile type is correct internal class OfflineList : DatFile { /// diff --git a/SabreTools.Library/DatFiles/SoftwareList.cs b/SabreTools.Library/DatFiles/SoftwareList.cs index ecbff682..2c2f43a2 100644 --- a/SabreTools.Library/DatFiles/SoftwareList.cs +++ b/SabreTools.Library/DatFiles/SoftwareList.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using System.Web; +using System.Xml; using SabreTools.Library.Data; using SabreTools.Library.DatItems; @@ -20,8 +21,9 @@ using NaturalSort; namespace SabreTools.Library.DatFiles { /// - /// Represents parsing and writing of an SabreDat XML DAT + /// Represents parsing and writing of a SofwareList, M1, or MAME XML DAT /// + /// TODO: Verify that all read/write for this DatFile type is correct internal class SoftwareList : DatFile { /// @@ -34,7 +36,7 @@ namespace SabreTools.Library.DatFiles } /// - /// Parse an SabreDat XML DAT and return all found games and roms within + /// Parse an SofwareList XML DAT and return all found games and roms within /// /// Name of the file to be parsed /// System ID for the DAT @@ -56,7 +58,381 @@ namespace SabreTools.Library.DatFiles bool remUnicode) { // All XML-derived DATs share a lot in common so it just calls one implementation - new Logiqx(this, false).ParseFile(filename, sysid, srcid, keep, clean, remUnicode); + // new Logiqx(this, false).ParseFile(filename, sysid, srcid, keep, clean, remUnicode); + + // Prepare all internal variables + Encoding enc = Utilities.GetEncoding(filename); + XmlReader xtr = Utilities.GetXmlTextReader(filename); + + // If we got a null reader, just return + if (xtr == null) + { + return; + } + + // Otherwise, read the file to the end + try + { + xtr.MoveToContent(); + while (!xtr.EOF) + { + // We only want elements + if (xtr.NodeType != XmlNodeType.Element) + { + xtr.Read(); + continue; + } + + switch (xtr.Name) + { + case "softwarelist": + Name = (String.IsNullOrWhiteSpace(Name) ? xtr.GetAttribute("name") ?? "" : Name); + Description = (String.IsNullOrWhiteSpace(Description) ? xtr.GetAttribute("description") ?? "" : Description); + if (ForceMerging == ForceMerging.None) + { + ForceMerging = Utilities.GetForceMerging(xtr.GetAttribute("forcemerging")); + } + if (ForceNodump == ForceNodump.None) + { + ForceNodump = Utilities.GetForceNodump(xtr.GetAttribute("forcenodump")); + } + if (ForcePacking == ForcePacking.None) + { + ForcePacking = Utilities.GetForcePacking(xtr.GetAttribute("forcepacking")); + } + xtr.Read(); + break; + // We want to process the entire subtree of the machine + case "software": + ReadSoftware(xtr.ReadSubtree(), filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the software now that we've processed it + xtr.Skip(); + break; + default: + xtr.Read(); + break; + } + } + } + catch (Exception ex) + { + Globals.Logger.Warning("Exception found while parsing '{0}': {1}", filename, ex); + + // For XML errors, just skip the affected node + xtr?.Read(); + } + + xtr.Dispose(); + } + + /// + /// Read software information + /// + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private void ReadSoftware( + XmlReader reader, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // If we have an empty software, skip it + if (reader == null) + { + return; + } + + // Otherwise, add what is possible + reader.MoveToContent(); + + string key = ""; + string temptype = reader.Name; + bool containsItems = false; + + // Create a new machine + MachineType machineType = MachineType.NULL; + if (Utilities.GetYesNo(reader.GetAttribute("isbios")) == true) + { + machineType |= MachineType.Bios; + } + if (Utilities.GetYesNo(reader.GetAttribute("isdevice")) == true) + { + machineType |= MachineType.Device; + } + if (Utilities.GetYesNo(reader.GetAttribute("ismechanical")) == true) + { + machineType |= MachineType.Mechanical; + } + + Machine machine = new Machine + { + Name = reader.GetAttribute("name"), + Description = reader.GetAttribute("name"), + Supported = Utilities.GetYesNo(reader.GetAttribute("supported")), // (yes|partial|no) "yes" + + CloneOf = reader.GetAttribute("cloneof") ?? "", + + MachineType = (machineType == MachineType.NULL ? MachineType.None : machineType), + }; + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get the elements from the software + switch (reader.Name) + { + case "description": + machine.Description = reader.ReadElementContentAsString(); + break; + case "year": + machine.Year = reader.ReadElementContentAsString(); + break; + case "publisher": + machine.Publisher = reader.ReadElementContentAsString(); + break; + case "info": + machine.Infos.Add(new Tuple(reader.GetAttribute("name"), reader.GetAttribute("value"))); + + reader.Read(); + break; + case "sharedfeat": + // string name = reader.GetAttribute("name"); + // string value = reader.GetAttribute("value"); + + reader.Read(); + break; + case "part": // Contains all rom and disk information + containsItems = ReadPart(reader.ReadSubtree(), machine, filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the part now that we've processed it + reader.Skip(); + break; + default: + reader.Read(); + break; + } + } + + // If no items were found for this machine, add a Blank placeholder + if (!containsItems) + { + Blank blank = new Blank() + { + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + blank.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(blank, clean, remUnicode); + } + } + + /// + /// Read part 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) + private bool ReadPart( + XmlReader reader, + Machine machine, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + string key = "", areaname = "", partname = "", partinterface = ""; + string temptype = reader.Name; + long? areasize = null; + List> features = new List>(); + bool containsItems = false; + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "part") + { + partname = ""; + partinterface = ""; + features = new List>(); + } + if (reader.NodeType == XmlNodeType.EndElement && (reader.Name == "dataarea" || reader.Name == "diskarea")) + { + areaname = ""; + areasize = null; + } + + reader.Read(); + continue; + } + + // Get the elements from the software + switch (reader.Name) + { + case "part": + partname = reader.GetAttribute("name"); + partinterface = reader.GetAttribute("interface"); + + reader.Read(); + break; + case "feature": + features.Add(new Tuple(reader.GetAttribute("name"), reader.GetAttribute("feature"))); + + reader.Read(); + break; + case "dataarea": + areaname = reader.GetAttribute("name"); + if (reader.GetAttribute("size") != null) + { + if (Int64.TryParse(reader.GetAttribute("size"), out long tempas)) + { + areasize = tempas; + } + } + // string width = reader.GetAttribute("width"); // (8|16|32|64) "8" + // string endianness = reader.GetAttribute("endianness"); // endianness (big|little) "little" + + reader.Read(); + break; + case "rom": // TODO: Put this in a proper subreader under dataarea + containsItems = true; + + // If the rom is continue or ignore, add the size to the previous rom + if (reader.GetAttribute("loadflag") == "continue" || reader.GetAttribute("loadflag") == "ignore") + { + int index = this[key].Count - 1; + DatItem lastrom = this[key][index]; + if (lastrom.Type == ItemType.Rom) + { + ((Rom)lastrom).Size += Utilities.GetSize(reader.GetAttribute("size")); + } + this[key].RemoveAt(index); + this[key].Add(lastrom); + reader.Read(); + continue; + } + + DatItem rom = new Rom + { + Name = reader.GetAttribute("name"), + Size = Utilities.GetSize(reader.GetAttribute("size")), + CRC = reader.GetAttribute("crc")?.ToLowerInvariant(), + MD5 = reader.GetAttribute("md5")?.ToLowerInvariant(), + SHA1 = reader.GetAttribute("sha1")?.ToLowerInvariant(), + SHA256 = reader.GetAttribute("sha256")?.ToLowerInvariant(), + SHA384 = reader.GetAttribute("sha384")?.ToLowerInvariant(), + SHA512 = reader.GetAttribute("sha512")?.ToLowerInvariant(), + Offset = reader.GetAttribute("offset"), + // Value = reader.GetAttribute("value"); + ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), + // LoadFlag = reader.GetAttribute("loadflag"), // (load16_byte|load16_word|load16_word_swap|load32_byte|load32_word|load32_word_swap|load32_dword|load64_word|load64_word_swap|reload|fill|continue|reload_plain|ignore) + + AreaName = areaname, + AreaSize = areasize, + Features = features, + PartName = partname, + PartInterface = partinterface, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + rom.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(rom, clean, remUnicode); + + reader.Read(); + break; + case "diskarea": + areaname = reader.GetAttribute("name"); + + reader.Read(); + break; + case "disk": // TODO: Put this in a proper subreader under diskarea + containsItems = true; + + DatItem disk = new Disk + { + Name = reader.GetAttribute("name"), + MD5 = reader.GetAttribute("md5")?.ToLowerInvariant(), + SHA1 = reader.GetAttribute("sha1")?.ToLowerInvariant(), + SHA256 = reader.GetAttribute("sha256")?.ToLowerInvariant(), + SHA384 = reader.GetAttribute("sha384")?.ToLowerInvariant(), + SHA512 = reader.GetAttribute("sha512")?.ToLowerInvariant(), + ItemStatus = Utilities.GetItemStatus(reader.GetAttribute("status")), + Writable = Utilities.GetYesNo(reader.GetAttribute("writable")), + + AreaName = areaname, + AreaSize = areasize, + Features = features, + PartName = partname, + PartInterface = partinterface, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + + disk.CopyMachineInformation(machine); + + // Now process and add the rom + key = ParseAddHelper(disk, clean, remUnicode); + + reader.Read(); + break; + case "dipswitch": + // string name = reader.GetAttribute("name"); + // string tag = reader.GetAttribute("tag"); + // string mask = reader.GetAttribute("mask"); + + // For every element... + // string name = reader.GetAttribute("name"); + // string value = reader.GetAttribute("value"); + // bool? default = Utilities.GetYesNo(reader.GetAttribute("default")); // (yes|no) "no" + + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + + return containsItems; } /// @@ -210,18 +586,12 @@ namespace SabreTools.Library.DatFiles } string state = "\t\n" + : " cloneof=\"" + HttpUtility.HtmlEncode(rom.CloneOf) + "\"") + ) + + " supported=\"" + (rom.Supported == true ? "yes" : rom.Supported == false ? "no" : "partial") + "\">\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" : ""); @@ -295,24 +665,6 @@ namespace SabreTools.Library.DatFiles 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" @@ -323,22 +675,10 @@ namespace SabreTools.Library.DatFiles + (!String.IsNullOrWhiteSpace(((Disk)rom).SHA384) ? " sha384=\"" + ((Disk)rom).SHA384.ToLowerInvariant() + "\"" : "") + (!String.IsNullOrWhiteSpace(((Disk)rom).SHA512) ? " sha512=\"" + ((Disk)rom).SHA512.ToLowerInvariant() + "\"" : "") + (((Disk)rom).ItemStatus != ItemStatus.None ? " status=\"" + ((Disk)rom).ItemStatus.ToString().ToLowerInvariant() + "\"" : "") + + (((Disk)rom).Writable != null ? " writable=\"" + (((Disk)rom).Writable == true ? "yes" : "no") + "\"" : "") + "/>\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" @@ -350,15 +690,10 @@ namespace SabreTools.Library.DatFiles + (!String.IsNullOrWhiteSpace(((Rom)rom).SHA256) ? " sha256=\"" + ((Rom)rom).SHA256.ToLowerInvariant() + "\"" : "") + (!String.IsNullOrWhiteSpace(((Rom)rom).SHA384) ? " sha384=\"" + ((Rom)rom).SHA384.ToLowerInvariant() + "\"" : "") + (!String.IsNullOrWhiteSpace(((Rom)rom).SHA512) ? " sha512=\"" + ((Rom)rom).SHA512.ToLowerInvariant() + "\"" : "") - + (!String.IsNullOrWhiteSpace(((Rom)rom).Date) ? " date=\"" + ((Rom)rom).Date + "\"" : "") + + (!String.IsNullOrWhiteSpace(((Rom)rom).Offset) ? " offset=\"" + ((Rom)rom).Offset + "\"" : "") + // + (!String.IsNullOrWhiteSpace(((Rom)rom).Value) ? " value=\"" + ((Rom)rom).Value + "\"" : "") + (((Rom)rom).ItemStatus != ItemStatus.None ? " status=\"" + ((Rom)rom).ItemStatus.ToString().ToLowerInvariant() + "\"" : "") - + "/>\n" - + "\t\t\t\n"; - break; - case ItemType.Sample: - state += "\t\t\t\n" - + "\t\t\t\t\n" + "\t\t\t\n"; break; diff --git a/SabreTools.Library/DatItems/DatItem.cs b/SabreTools.Library/DatItems/DatItem.cs index fe0b92ac..cb601bcf 100644 --- a/SabreTools.Library/DatItems/DatItem.cs +++ b/SabreTools.Library/DatItems/DatItem.cs @@ -23,7 +23,6 @@ namespace SabreTools.Library.DatItems // Standard item information protected string _name; - protected string _merge; protected ItemType _itemType; protected DupeType _dupeType; @@ -32,8 +31,6 @@ namespace SabreTools.Library.DatItems // Software list information protected bool? _supported; - protected string _publisher; - protected List> _infos; protected string _partName; protected string _partInterface; protected List> _features; @@ -57,11 +54,6 @@ namespace SabreTools.Library.DatItems get { return _name; } set { _name = value; } } - public string MergeTag - { - get { return _merge; } - set { _merge = value; } - } public ItemType Type { get { return _itemType; } @@ -370,20 +362,20 @@ namespace SabreTools.Library.DatItems } // Software list information - public bool? Supported + public bool? Supported // yes = true, partial = null, no = false { - get { return _supported; } - set { _supported = value; } + get { return _machine.Supported; } + set { _machine.Supported = value; } } public string Publisher { - get { return _publisher; } - set { _publisher = value; } + get { return _machine.Publisher; } + set { _machine.Publisher = value; } } public List> Infos { - get { return _infos; } - set { _infos = value; } + get { return _machine.Infos; } + set { _machine.Infos = value; } } public string PartName { diff --git a/SabreTools.Library/DatItems/Disk.cs b/SabreTools.Library/DatItems/Disk.cs index 062809e1..2ac4301c 100644 --- a/SabreTools.Library/DatItems/Disk.cs +++ b/SabreTools.Library/DatItems/Disk.cs @@ -18,7 +18,12 @@ namespace SabreTools.Library.DatItems private byte[] _sha256; // 32 bytes private byte[] _sha384; // 48 bytes private byte[] _sha512; // 64 bytes + private string _merge; + private string _region; + private string _index; + private bool? _writable; private ItemStatus _itemStatus; + private bool? _optional; #endregion @@ -50,11 +55,36 @@ namespace SabreTools.Library.DatItems get { return _sha512.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_sha512); } set { _sha512 = Utilities.StringToByteArray(value); } } + public string MergeTag + { + get { return _merge; } + set { _merge = value; } + } + public string Region + { + get { return _region; } + set { _region = value; } + } + public string Index + { + get { return _index; } + set { _index = value; } + } + public bool? Writable + { + get { return _writable; } + set { _writable = value; } + } public ItemStatus ItemStatus { get { return _itemStatus; } set { _itemStatus = value; } } + public bool? Optional + { + get { return _optional; } + set { _optional = value; } + } #endregion diff --git a/SabreTools.Library/DatItems/Machine.cs b/SabreTools.Library/DatItems/Machine.cs index 0eb166e8..1a4890fc 100644 --- a/SabreTools.Library/DatItems/Machine.cs +++ b/SabreTools.Library/DatItems/Machine.cs @@ -18,14 +18,17 @@ namespace SabreTools.Library.DatItems public string Description; public string Year; public string Manufacturer; + public string Publisher; public string RomOf; public string CloneOf; public string SampleOf; + public bool? Supported; public string SourceFile; public bool? Runnable; public string Board; public string RebuildTo; public List Devices; + public List> Infos; public MachineType MachineType; #endregion @@ -42,9 +45,11 @@ namespace SabreTools.Library.DatItems Description = null; Year = null; Manufacturer = null; + Publisher = null; RomOf = null; CloneOf = null; SampleOf = null; + Supported = true; SourceFile = null; Runnable = null; Board = null; @@ -65,9 +70,11 @@ namespace SabreTools.Library.DatItems Description = description; Year = null; Manufacturer = null; + Publisher = null; RomOf = null; CloneOf = null; SampleOf = null; + Supported = true; SourceFile = null; Runnable = null; Board = null; @@ -93,9 +100,11 @@ namespace SabreTools.Library.DatItems Description = this.Description, Year = this.Year, Manufacturer = this.Manufacturer, + Publisher = this.Publisher, RomOf = this.RomOf, CloneOf = this.CloneOf, SampleOf = this.SampleOf, + Supported = this.Supported, SourceFile = this.SourceFile, Runnable = this.Runnable, Board = this.Board, diff --git a/SabreTools.Library/DatItems/Rom.cs b/SabreTools.Library/DatItems/Rom.cs index 51bb5cca..5c7c8ebc 100644 --- a/SabreTools.Library/DatItems/Rom.cs +++ b/SabreTools.Library/DatItems/Rom.cs @@ -13,6 +13,7 @@ namespace SabreTools.Library.DatItems #region Private instance variables // Rom information + private string _bios; private long _size; private byte[] _crc; // 8 bytes private byte[] _md5; // 16 bytes @@ -20,14 +21,23 @@ namespace SabreTools.Library.DatItems private byte[] _sha256; // 32 bytes private byte[] _sha384; // 48 bytes private byte[] _sha512; // 64 bytes + private string _merge; + private string _region; + private string _offset; private string _date; private ItemStatus _itemStatus; + private bool? _optional; #endregion #region Publicly facing variables // Rom information + public string Bios + { + get { return _bios; } + set { _bios = value; } + } public long Size { get { return _size; } @@ -63,6 +73,21 @@ namespace SabreTools.Library.DatItems get { return _sha512.IsNullOrWhiteSpace() ? null : Utilities.ByteArrayToString(_sha512); } set { _sha512 = Utilities.StringToByteArray(value); } } + public string MergeTag + { + get { return _merge; } + set { _merge = value; } + } + public string Region + { + get { return _region; } + set { _region = value; } + } + public string Offset + { + get { return _offset; } + set { _offset = value; } + } public string Date { get { return _date; } @@ -73,6 +98,11 @@ namespace SabreTools.Library.DatItems get { return _itemStatus; } set { _itemStatus = value; } } + public bool? Optional + { + get { return _optional; } + set { _optional = value; } + } #endregion diff --git a/SabreTools.Library/Data/Constants.cs b/SabreTools.Library/Data/Constants.cs index e5b3220b..3534857b 100644 --- a/SabreTools.Library/Data/Constants.cs +++ b/SabreTools.Library/Data/Constants.cs @@ -97,6 +97,332 @@ namespace SabreTools.Library.Data #endregion + #region DTDs + + public const string LogiqxDTD = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + public const string MAMEDTD = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + public const string SoftwareListDTD = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + + #endregion + #region Hash string length constants public const int CRCLength = 8; diff --git a/SabreTools.Library/SabreTools.Library.csproj b/SabreTools.Library/SabreTools.Library.csproj index f426a312..6a54806d 100644 --- a/SabreTools.Library/SabreTools.Library.csproj +++ b/SabreTools.Library/SabreTools.Library.csproj @@ -124,6 +124,7 @@ + diff --git a/SabreTools.Library/Tools/Utilities.cs b/SabreTools.Library/Tools/Utilities.cs index d9d18e81..57d56dab 100644 --- a/SabreTools.Library/Tools/Utilities.cs +++ b/SabreTools.Library/Tools/Utilities.cs @@ -563,6 +563,30 @@ namespace SabreTools.Library.Tools return null; } + /// + /// Get a sanitized Date from an input string + /// + /// String to get value from + /// Date as a string, if possible + public static string GetDate(string input) + { + string date = ""; + if (input != null) + { + DateTime dateTime = DateTime.Now; + if (DateTime.TryParse(input, out dateTime)) + { + date = dateTime.ToString(); + } + else + { + date = input; + } + } + + return date; + } + /// /// Create a specific type of DatFile to be used based on an input file and a base DAT /// @@ -794,6 +818,26 @@ namespace SabreTools.Library.Tools } } + /// + /// Get a sanitized size from an input string + /// + /// String to get value from + /// Size as a long, if possible + public static long GetSize(string input) + { + long size = -1; + if (input != null && input.Contains("0x")) + { + size = Convert.ToInt64(input, 16); + } + else if (input != null) + { + Int64.TryParse(input, out size); + } + + return size; + } + /// /// Get SplitType value from input ForceMerging /// @@ -822,15 +866,16 @@ namespace SabreTools.Library.Tools /// /// String to get value from /// Bool corresponding to the string - public static bool GetYesNo(string yesno) + public static bool? GetYesNo(string yesno) { switch (yesno?.ToLowerInvariant()) { case "yes": return true; - default: case "no": - return false; + return false; + default: + return null; } }