diff --git a/SabreTools.Library/DatFiles/SabreDat.cs b/SabreTools.Library/DatFiles/SabreDat.cs index 78e787b4..a349637b 100644 --- a/SabreTools.Library/DatFiles/SabreDat.cs +++ b/SabreTools.Library/DatFiles/SabreDat.cs @@ -37,7 +37,7 @@ namespace SabreTools.Library.DatFiles } /// - /// Parse an SabreDat XML DAT and return all found games and roms within + /// Parse an SabreDat XML DAT and return all found directories and files within /// /// Name of the file to be parsed /// System ID for the DAT @@ -45,8 +45,6 @@ namespace SabreTools.Library.DatFiles /// 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, @@ -64,11 +62,8 @@ namespace SabreTools.Library.DatFiles return; // Prepare all internal variables - XmlReader subreader, headreader, flagreader; - bool superdat = false, empty = true; - string key = "", date = ""; - long size = -1; - ItemStatus its = ItemStatus.None; + bool empty = true; + string key = ""; List parent = new List(); Encoding enc = Utilities.GetEncoding(filename); @@ -114,7 +109,6 @@ namespace SabreTools.Library.DatFiles if (keep && parentcount > 1) { Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); - superdat = true; } } } @@ -128,916 +122,18 @@ namespace SabreTools.Library.DatFiles 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.IsNullOrWhiteSpace(Name) ? xtr.GetAttribute("build") : Name); - Description = (String.IsNullOrWhiteSpace(Description) ? Name : Name); - } - xtr.Read(); - break; - // New software lists have this behavior - case "softwarelist": - if (xtr.GetAttribute("name") != null) - { - Name = (String.IsNullOrWhiteSpace(Name) ? xtr.GetAttribute("name") : Name); - } - if (xtr.GetAttribute("description") != null) - { - 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; - // 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); - if (xtr.GetAttribute("version") != null) - { - Version = (String.IsNullOrWhiteSpace(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.IsNullOrWhiteSpace(Name) ? content : Name); - superdat = superdat || content.Contains(" - SuperDAT"); - if (keep && superdat) - { - Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); - } - break; - case "datversionurl": - content = headreader.ReadElementContentAsString(); ; - Url = (String.IsNullOrWhiteSpace(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.IsNullOrWhiteSpace(Name) ? content : Name); - superdat = superdat || content.Contains(" - SuperDAT"); - if (keep && superdat) - { - Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); - } - break; - case "description": - content = headreader.ReadElementContentAsString(); - Description = (String.IsNullOrWhiteSpace(Description) ? content : Description); - break; - case "rootdir": - content = headreader.ReadElementContentAsString(); - RootDir = (String.IsNullOrWhiteSpace(RootDir) ? content : RootDir); - break; - case "category": - content = headreader.ReadElementContentAsString(); - Category = (String.IsNullOrWhiteSpace(Category) ? content : Category); - break; - case "version": - content = headreader.ReadElementContentAsString(); - Version = (String.IsNullOrWhiteSpace(Version) ? content : Version); - break; - case "date": - content = headreader.ReadElementContentAsString(); - Date = (String.IsNullOrWhiteSpace(Date) ? content.Replace(".", "/") : Date); - break; - case "author": - content = headreader.ReadElementContentAsString(); - Author = (String.IsNullOrWhiteSpace(Author) ? content : Author); - - // Special cases for SabreDAT - Email = (String.IsNullOrWhiteSpace(Email) && !String.IsNullOrWhiteSpace(headreader.GetAttribute("email")) ? - headreader.GetAttribute("email") : Email); - Homepage = (String.IsNullOrWhiteSpace(Homepage) && !String.IsNullOrWhiteSpace(headreader.GetAttribute("homepage")) ? - headreader.GetAttribute("homepage") : Homepage); - Url = (String.IsNullOrWhiteSpace(Url) && !String.IsNullOrWhiteSpace(headreader.GetAttribute("url")) ? - headreader.GetAttribute("url") : Url); - break; - case "email": - content = headreader.ReadElementContentAsString(); - Email = (String.IsNullOrWhiteSpace(Email) ? content : Email); - break; - case "homepage": - content = headreader.ReadElementContentAsString(); - Homepage = (String.IsNullOrWhiteSpace(Homepage) ? content : Homepage); - break; - case "url": - content = headreader.ReadElementContentAsString(); - Url = (String.IsNullOrWhiteSpace(Url) ? content : Url); - break; - case "comment": - content = headreader.ReadElementContentAsString(); - Comment = (String.IsNullOrWhiteSpace(Comment) ? content : Comment); - break; - case "type": - content = headreader.ReadElementContentAsString(); - Type = (String.IsNullOrWhiteSpace(Type) ? content : Type); - superdat = superdat || content.Contains("SuperDAT"); - break; - case "clrmamepro": - case "romcenter": - if (headreader.GetAttribute("header") != null) - { - Header = (String.IsNullOrWhiteSpace(Header) ? headreader.GetAttribute("header") : Header); - } - if (headreader.GetAttribute("plugin") != null) - { - Header = (String.IsNullOrWhiteSpace(Header) ? headreader.GetAttribute("plugin") : Header); - } - if (ForceMerging == ForceMerging.None) - { - ForceMerging = Utilities.GetForceMerging(headreader.GetAttribute("forcemerging")); - } - if (ForceNodump == ForceNodump.None) - { - ForceNodump = Utilities.GetForceNodump(headreader.GetAttribute("forcenodump")); - } - if (ForcePacking == ForcePacking.None) - { - ForcePacking = Utilities.GetForcePacking(headreader.GetAttribute("forcepacking")); - } - 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.IsNullOrWhiteSpace(Type) ? content : Type); - superdat = superdat || content.Contains("SuperDAT"); - break; - case "forcemerging": - if (ForceMerging == ForceMerging.None) - { - ForceMerging = Utilities.GetForceMerging(content); - } - break; - case "forcenodump": - if (ForceNodump == ForceNodump.None) - { - ForceNodump = Utilities.GetForceNodump(content); - } - break; - case "forcepacking": - if (ForcePacking == ForcePacking.None) - { - ForcePacking = Utilities.GetForcePacking(content); - } - break; - } - } - flagreader.Read(); - break; - default: - flagreader.Read(); - break; - } - } - headreader.Skip(); - break; - default: - headreader.Read(); - break; - } - } + ReadHeader(xtr.ReadSubtree(), keep); // 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>(); - bool containsItems = false; - - // 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 - 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"), - Description = xtr.GetAttribute("name"), - - RomOf = xtr.GetAttribute("romof") ?? "", - CloneOf = xtr.GetAttribute("cloneof") ?? "", - SampleOf = xtr.GetAttribute("sampleof") ?? "", - - Devices = new List(), - MachineType = (machineType == MachineType.NULL ? MachineType.None : machineType), - }; - - // Get the supported value from the reader - if (subreader.GetAttribute("supported") != null) - { - supported = Utilities.GetYesNo(subreader.GetAttribute("supported")); - } - - // Get the runnable value from the reader - if (subreader.GetAttribute("runnable") != null) - { - machine.Runnable = Utilities.GetYesNo(subreader.GetAttribute("runnable")); - } - - if (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 (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; - containsItems = true; - - 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; - containsItems = true; - - bool? defaultrel = null; - if (subreader.GetAttribute("default") != null) - { - defaultrel = Utilities.GetYesNo(subreader.GetAttribute("default")); - } - - 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; - containsItems = true; - - bool? defaultbios = null; - if (subreader.GetAttribute("default") != null) - { - defaultbios = Utilities.GetYesNo(subreader.GetAttribute("default")); - } - - 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; - containsItems = true; - - 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; - containsItems = true; - - 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; - containsItems = true; - - // If the rom has a merge tag, add it - string merge = subreader.GetAttribute("merge"); - - // If the rom has a status, flag it - its = Utilities.GetItemStatus(subreader.GetAttribute("status")); - if (its == ItemStatus.None) - { - its = Utilities.GetItemStatus(subreader.GetAttribute("flags")); - } - - // 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 = Utilities.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; - } - } - - // If no items were found for this machine, add a Blank placeholder - if (!containsItems) - { - Blank blank = new Blank() - { - Supported = supported, - Publisher = publisher, - Infos = infos, - PartName = partname, - PartInterface = partinterface, - Features = features, - AreaName = areaname, - AreaSize = areasize, - - SystemID = sysid, - System = filename, - SourceID = srcid, - }; - - blank.CopyMachineInformation(machine); - - // Now process and add the rom - key = ParseAddHelper(blank, clean, remUnicode); - } - xtr.Skip(); break; case "dir": case "directory": - // Set SuperDAT flag for all SabreDAT inputs, regardless of depth - superdat = true; - if (keep) - { - Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); - } - - string foldername = (xtr.GetAttribute("name") ?? ""); - if (!String.IsNullOrWhiteSpace(foldername)) - { - parent.Add(foldername); - } - - xtr.Read(); - break; - case "file": - empty = false; - containsItems = true; - - // 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"); - its = Utilities.GetItemStatus(flagreader.GetAttribute("name")); - } - 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 (!String.IsNullOrWhiteSpace(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); + empty = ReadDirectory(xtr.ReadSubtree(), parent, filename, sysid, srcid, keep, clean, remUnicode); + // Skip the directory node now that we've processed it xtr.Read(); break; default: @@ -1057,6 +153,402 @@ namespace SabreTools.Library.DatFiles xtr.Dispose(); } + /// + /// Read header information + /// + /// XmlReader to use to parse the header + /// True if full pathnames are to be kept, false otherwise (default) + private void ReadHeader(XmlReader reader, bool keep) + { + bool superdat = false; + + // If there's no subtree to the header, skip it + if (reader == null) + { + return; + } + + // Otherwise, read what we can from the header + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element || reader.Name == "header") + { + reader.Read(); + continue; + } + + // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA) + string content = ""; + switch (reader.Name) + { + case "name": + content = reader.ReadElementContentAsString(); ; + Name = (String.IsNullOrWhiteSpace(Name) ? content : Name); + superdat = superdat || content.Contains(" - SuperDAT"); + if (keep && superdat) + { + Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); + } + break; + case "description": + content = reader.ReadElementContentAsString(); + Description = (String.IsNullOrWhiteSpace(Description) ? content : Description); + break; + case "rootdir": + content = reader.ReadElementContentAsString(); + RootDir = (String.IsNullOrWhiteSpace(RootDir) ? content : RootDir); + break; + case "category": + content = reader.ReadElementContentAsString(); + Category = (String.IsNullOrWhiteSpace(Category) ? content : Category); + break; + case "version": + content = reader.ReadElementContentAsString(); + Version = (String.IsNullOrWhiteSpace(Version) ? content : Version); + break; + case "date": + content = reader.ReadElementContentAsString(); + Date = (String.IsNullOrWhiteSpace(Date) ? content.Replace(".", "/") : Date); + break; + case "author": + content = reader.ReadElementContentAsString(); + Author = (String.IsNullOrWhiteSpace(Author) ? content : Author); + Email = (String.IsNullOrWhiteSpace(Email) ? reader.GetAttribute("email") : Email); + Homepage = (String.IsNullOrWhiteSpace(Homepage) ? reader.GetAttribute("homepage") : Homepage); + Url = (String.IsNullOrWhiteSpace(Url) ? reader.GetAttribute("url") : Url); + break; + case "comment": + content = reader.ReadElementContentAsString(); + Comment = (String.IsNullOrWhiteSpace(Comment) ? content : Comment); + break; + case "flags": + ReadFlags(reader.ReadSubtree(), superdat); + + // Skip the flags node now that we've processed it + reader.Skip(); + break; + default: + reader.Read(); + break; + } + } + } + + /// + /// Read directory information + /// + /// XmlReader to use to parse the header + /// Name of the file to be parsed + /// System ID for the DAT + /// Source ID for the DAT + /// True if full pathnames are to be kept, false otherwise (default) + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private bool ReadDirectory(XmlReader reader, + List parent, + + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Prepare all internal variables + XmlReader flagreader; + bool empty = true; + string key = "", date = ""; + long size = -1; + ItemStatus its = ItemStatus.None; + + // If there's no subtree to the header, skip it + if (reader == null) + { + return empty; + } + + string foldername = (reader.GetAttribute("name") ?? ""); + if (!String.IsNullOrWhiteSpace(foldername)) + { + parent.Add(foldername); + } + + // Otherwise, read what we can from the directory + while (!reader.EOF) + { + // If we're ending a folder or game, take care of possibly empty games and removing from the parent + if (reader.NodeType == XmlNodeType.EndElement && (reader.Name == "directory" || reader.Name == "dir")) + { + // If we didn't find any items in the folder, make sure to add the blank rom + if (empty) + { + string tempgame = String.Join("\\", parent); + Rom rom = new Rom("null", tempgame, omitFromScan: Hash.DeepHashes); // TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually + + // Now process and add the rom + key = ParseAddHelper(rom, clean, remUnicode); + } + + // Regardless, end the current folder + int parentcount = parent.Count; + if (parentcount == 0) + { + Globals.Logger.Verbose("Empty parent '{0}' found in '{1}'", String.Join("\\", parent), filename); + empty = true; + } + + // If we have an end folder element, remove one item from the parent, if possible + if (parentcount > 0) + { + parent.RemoveAt(parent.Count - 1); + if (keep && parentcount > 1) + { + Type = (String.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type); + } + } + } + + // We only want elements + if (reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + continue; + } + + // Get all directory items + string content = ""; + switch (reader.Name) + { + // Directories can contain directories + case "dir": + case "directory": + ReadDirectory(reader.ReadSubtree(), parent, filename, sysid, srcid, keep, clean, remUnicode); + + // Skip the directory node now that we've processed it + reader.Read(); + break; + case "file": + empty = false; + + // If the rom is itemStatus, flag it + its = ItemStatus.None; + flagreader = reader.ReadSubtree(); + + // If the subtree is empty, skip it + if (flagreader == null) + { + reader.Skip(); + continue; + } + + while (!flagreader.EOF) + { + // We only want elements + if (flagreader.NodeType != XmlNodeType.Element || flagreader.Name == "flags") + { + flagreader.Read(); + continue; + } + + switch (flagreader.Name) + { + case "flag": + if (flagreader.GetAttribute("name") != null && flagreader.GetAttribute("value") != null) + { + content = flagreader.GetAttribute("value"); + its = Utilities.GetItemStatus(flagreader.GetAttribute("name")); + } + break; + } + + flagreader.Read(); + } + + // If the rom has a Date attached, read it in and then sanitize it + date = Utilities.GetDate(reader.GetAttribute("date")); + + // Take care of hex-sized files + size = Utilities.GetSize(reader.GetAttribute("size")); + + Machine dir = new Machine(); + + // Get the name of the game from the parent + dir.Name = String.Join("\\", parent); + dir.Description = dir.Name; + + DatItem datItem; + switch (reader.GetAttribute("type").ToLowerInvariant()) + { + case "archive": + datItem = new Archive + { + Name = reader.GetAttribute("name"), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + case "biosset": + datItem = new BiosSet + { + Name = reader.GetAttribute("name"), + Description = reader.GetAttribute("description"), + Default = Utilities.GetYesNo(reader.GetAttribute("default")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + case "disk": + datItem = new Disk + { + Name = reader.GetAttribute("name"), + MD5 = 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 = its, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + case "release": + datItem = new Release + { + Name = reader.GetAttribute("name"), + Region = reader.GetAttribute("region"), + Language = reader.GetAttribute("language"), + Date = reader.GetAttribute("date"), + Default = Utilities.GetYesNo(reader.GetAttribute("default")), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + case "rom": + datItem = new Rom + { + Name = reader.GetAttribute("name"), + Size = size, + CRC = 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(), + ItemStatus = its, + Date = date, + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + case "sample": + datItem = new Sample + { + Name = reader.GetAttribute("name"), + + SystemID = sysid, + System = filename, + SourceID = srcid, + }; + break; + default: + // By default, create a new Blank, just in case + datItem = new Blank(); + break; + } + + datItem.CopyMachineInformation(dir); + + // Now process and add the rom + key = ParseAddHelper(datItem, clean, remUnicode); + + reader.Read(); + break; + } + } + + return empty; + } + + /// + /// Read flags information + /// + /// XmlReader to use to parse the header + /// True if superdat has already been set externally, false otherwise + private void ReadFlags(XmlReader reader, bool superdat) + { + // Prepare all internal variables + string content = ""; + + // If we somehow have a null flag section, skip it + if (reader == null) + { + return; + } + + while (!reader.EOF) + { + // We only want elements + if (reader.NodeType != XmlNodeType.Element || reader.Name == "flags") + { + reader.Read(); + continue; + } + + switch (reader.Name) + { + case "flag": + if (reader.GetAttribute("name") != null && reader.GetAttribute("value") != null) + { + content = reader.GetAttribute("value"); + switch (reader.GetAttribute("name").ToLowerInvariant()) + { + case "type": + Type = (String.IsNullOrWhiteSpace(Type) ? content : Type); + superdat = superdat || content.Contains("SuperDAT"); + break; + case "forcemerging": + if (ForceMerging == ForceMerging.None) + { + ForceMerging = Utilities.GetForceMerging(content); + } + break; + case "forcenodump": + if (ForceNodump == ForceNodump.None) + { + ForceNodump = Utilities.GetForceNodump(content); + } + break; + case "forcepacking": + if (ForcePacking == ForcePacking.None) + { + ForcePacking = Utilities.GetForcePacking(content); + } + break; + } + } + reader.Read(); + break; + default: + reader.Read(); + break; + } + } + } + /// /// Create and open an output file for writing direct from a dictionary ///