using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using System.Xml; namespace SabreTools.Helper { public class RomManipulation { /// /// Parse a DAT and return all found games and roms within /// /// Name of the file to be parsed /// System ID for the DAT /// Source ID for the DAT /// Logger object for console and/or file output /// List of RomData objects representing the found data public static List Parse(string filename, int sysid, int srcid, Logger logger) { List roms = new List(); bool superdat = false; XmlDocument doc = new XmlDocument(); try { doc.LoadXml(File.ReadAllText(filename)); } catch (XmlException) { doc.LoadXml(Converters.RomVaultToXML(File.ReadAllLines(filename)).ToString()); } // Experimental looping using only XML parsing XmlNode node = doc.FirstChild; if (node != null && node.Name == "xml") { // Skip over everything that's not an element while (node.NodeType != XmlNodeType.Element) { node = node.NextSibling; } } // Once we find the main body, enter it if (node != null && (node.Name == "datafile" || node.Name == "softwarelist")) { node = node.FirstChild; } // Skip the header if it exists if (node != null && node.Name == "header") { // Check for SuperDAT mode if (node.SelectSingleNode("name").InnerText.Contains(" - SuperDAT")) { superdat = true; } // Skip over anything that's not an element while (node.NodeType != XmlNodeType.Element) { node = node.NextSibling; } } // Loop over the document until the end while (node != null) { if (node.NodeType == XmlNodeType.Element && (node.Name == "machine" || node.Name == "game" || node.Name == "software")) { string tempname = ""; if (node.Name == "software") { tempname = node.SelectSingleNode("description").InnerText; } else { // There are rare cases where a malformed XML will not have the required attributes. We can only skip them. if (node.Attributes.Count == 0) { logger.Error(@"A node with malformed XML has been found! For RV DATs, please make sure that all names and descriptions are quoted. For XML DATs, make sure that the DAT has all required information."); node = node.NextSibling; continue; } tempname = node.Attributes["name"].Value; } if (superdat) { tempname = Regex.Match(tempname, @".*?\\(.*)").Groups[1].Value; } // Get the roms from the machine if (node.HasChildNodes) { // If this node has children, traverse the children foreach (XmlNode child in node.ChildNodes) { // If we find a rom or disk, add it if (child.NodeType == XmlNodeType.Element && (child.Name == "rom" || child.Name == "disk")) { // Take care of hex-sized files long size = -1; if (child.Attributes["size"] != null && child.Attributes["size"].Value.Contains("0x")) { size = Convert.ToInt64(child.Attributes["size"].Value, 16); } else if (child.Attributes["size"] != null) { size = Int64.Parse(child.Attributes["size"].Value); } roms.Add(new RomData { Game = tempname, Name = child.Attributes["name"].Value, Type = child.Name, SystemID = sysid, SourceID = srcid, Size = size, CRC = (child.Attributes["crc"] != null ? child.Attributes["crc"].Value.ToLowerInvariant().Trim() : ""), MD5 = (child.Attributes["md5"] != null ? child.Attributes["md5"].Value.ToLowerInvariant().Trim() : ""), SHA1 = (child.Attributes["sha1"] != null ? child.Attributes["sha1"].Value.ToLowerInvariant().Trim() : ""), }); } // If we find the signs of a software list, traverse the children else if (child.NodeType == XmlNodeType.Element && child.Name == "part" && child.HasChildNodes) { foreach (XmlNode part in child.ChildNodes) { // If we find a dataarea, traverse the children if (part.NodeType == XmlNodeType.Element && part.Name == "dataarea") { foreach (XmlNode data in part.ChildNodes) { // If we find a rom or disk, add it if (data.NodeType == XmlNodeType.Element && (data.Name == "rom" || data.Name == "disk") && data.Attributes["name"] != null) { // Take care of hex-sized files long size = -1; if (data.Attributes["size"] != null && data.Attributes["size"].Value.Contains("0x")) { size = Convert.ToInt64(data.Attributes["size"].Value, 16); } else if (data.Attributes["size"] != null) { size = Int64.Parse(data.Attributes["size"].Value); } roms.Add(new RomData { Game = tempname, Name = data.Attributes["name"].Value, Type = data.Name, SystemID = sysid, SourceID = srcid, Size = size, CRC = (data.Attributes["crc"] != null ? data.Attributes["crc"].Value.ToLowerInvariant().Trim() : ""), MD5 = (data.Attributes["md5"] != null ? data.Attributes["md5"].Value.ToLowerInvariant().Trim() : ""), SHA1 = (data.Attributes["sha1"] != null ? data.Attributes["sha1"].Value.ToLowerInvariant().Trim() : ""), }); } } } } } } } } node = node.NextSibling; } return roms; } /// /// Merge an arbitrary set of ROMs based on the supplied information /// /// List of RomData objects representing the roms to be merged /// True if the list should be considered pre-sorted (default false) /// A List of RomData objects representing the merged roms public static List Merge(List inroms, bool presorted = false) { List outroms = new List(); // First sort the roms by size, crc, sysid, srcid, md5, and sha1 (in order), if not sorted already if (!presorted) { inroms.Sort(delegate (RomData x, RomData y) { if (x.Size == y.Size) { if (x.CRC == y.CRC) { if (x.SystemID == y.SystemID) { if (x.SourceID == y.SourceID) { if (x.MD5 == y.MD5) { return String.Compare(x.SHA1, y.SHA1); } return String.Compare(x.MD5, y.MD5); } return x.SourceID - y.SourceID; } return x.SystemID - y.SystemID; } return String.Compare(x.CRC, y.CRC); } return (int)(x.Size - y.Size); }); } // Then, deduplicate them by checking to see if data matches foreach (RomData rom in inroms) { // If it's the first rom in the list, don't touch it if (outroms.Count != 0) { // Check if the rom is a duplicate RomData last = outroms[outroms.Count - 1]; bool shouldcont = false; if (rom.Type == "rom" && last.Type == "rom") { shouldcont = ((rom.Size != -1 && rom.Size == last.Size) && ( (rom.CRC != "" && last.CRC != "" && rom.CRC == last.CRC) || (rom.MD5 != "" && last.MD5 != "" && rom.MD5 == last.MD5) || (rom.SHA1 != "" && last.SHA1 != "" && rom.SHA1 == last.SHA1) ) ); } else if (rom.Type == "disk" && last.Type == "disk") { shouldcont = ((rom.MD5 != "" && last.MD5 != "" && rom.MD5 == last.MD5) || (rom.SHA1 != "" && last.SHA1 != "" && rom.SHA1 == last.SHA1) ); } // If it's a duplicate, skip adding it to the output but add any missing information if (shouldcont) { last.CRC = (last.CRC == "" && rom.CRC != "" ? rom.CRC : last.CRC); last.MD5 = (last.MD5 == "" && rom.MD5 != "" ? rom.MD5 : last.MD5); last.SHA1 = (last.SHA1 == "" && rom.SHA1 != "" ? rom.SHA1 : last.SHA1); outroms.RemoveAt(outroms.Count - 1); outroms.Insert(outroms.Count, last); continue; } else { outroms.Add(rom); } } else { outroms.Add(rom); } } // Then return the result return outroms; } /// /// Sort a list of RomData objects by SystemID, SourceID, Game, and Name (in order) /// /// List of RomData objects representing the roms to be sorted /// True if files are not renamed, false otherwise public static void Sort(List roms, bool norename) { roms.Sort(delegate (RomData x, RomData y) { if (x.SystemID == y.SystemID) { if (x.SourceID == y.SourceID) { if (x.Game == y.Game) { return String.Compare(x.Name, y.Name); } return String.Compare(x.Game, y.Game); } return (norename ? String.Compare(x.Game, y.Game) : x.SourceID - y.SourceID); } return (norename ? String.Compare(x.Game, y.Game) : x.SystemID - y.SystemID); }); } } }