using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Xml; namespace SabreTools.Helper { public class RomManipulation { /// /// Return if the file is XML or not /// /// Name of the file to be parsed /// True if the file is XML, false otherwise public static bool IsXmlDat(string filename) { XmlDocument doc = new XmlDocument(); try { doc.LoadXml(File.ReadAllText(filename)); } catch (XmlException) { return false; } return true; } /// /// 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) { try { doc.LoadXml(Converters.RomVaultToXML(File.ReadAllLines(filename)).ToString()); } catch (Exception ex) { logger.Error(ex.ToString()); return roms; } } catch (Exception ex) { logger.Error(ex.ToString()); return roms; } // 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("No attributes were found"); 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); }); } /// /// Get differences between two lists of RomData objects /// /// First RomData list /// Second RomData list /// Any rom that's not in both lists /// Adapted from http://stackoverflow.com/questions/5620266/the-opposite-of-intersect public static List Diff(List A, List B) { List AString = Output.RomDataToString(A); List BString = Output.RomDataToString(B); List CString = AString.Except(BString).Union(BString.Except(AString)).ToList(); return Output.StringToRomData(CString); } } }