2016-04-19 01:11:23 -07:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
2016-04-27 00:53:15 -07:00
|
|
|
|
using Mono.Data.Sqlite;
|
2016-04-19 01:11:23 -07:00
|
|
|
|
using System.IO;
|
2016-04-19 15:41:06 -07:00
|
|
|
|
using System.Linq;
|
2016-04-19 01:11:23 -07:00
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
|
using System.Xml;
|
|
|
|
|
|
|
|
|
|
|
|
namespace SabreTools.Helper
|
|
|
|
|
|
{
|
|
|
|
|
|
public class RomManipulation
|
|
|
|
|
|
{
|
2016-04-19 15:41:06 -07:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Return if the file is XML or not
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="filename">Name of the file to be parsed</param>
|
2016-04-25 00:25:08 -07:00
|
|
|
|
/// <returns>public static bool IsXmlDat(string filename)
|
2016-04-19 15:41:06 -07:00
|
|
|
|
public static bool IsXmlDat(string filename)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2016-04-25 00:25:08 -07:00
|
|
|
|
StreamReader sr = new StreamReader(File.OpenRead(filename));
|
|
|
|
|
|
string first = sr.ReadLine();
|
|
|
|
|
|
sr.Close();
|
|
|
|
|
|
return first.Contains("<") && first.Contains(">");
|
2016-04-19 15:41:06 -07:00
|
|
|
|
}
|
2016-04-25 00:25:08 -07:00
|
|
|
|
catch (Exception)
|
2016-04-19 15:41:06 -07:00
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-20 15:30:56 -07:00
|
|
|
|
/// <summary>
|
2016-04-20 15:37:49 -07:00
|
|
|
|
/// Get the XmlDocument associated with a file, if possible
|
2016-04-20 15:30:56 -07:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="filename">Name of the file to be parsed</param>
|
|
|
|
|
|
/// <param name="logger">Logger object for console and file output</param>
|
2016-04-20 15:37:49 -07:00
|
|
|
|
/// <returns>The XmlDocument representing the (possibly converted) file, null otherwise</returns>
|
|
|
|
|
|
public static XmlDocument GetXmlDocument(string filename, Logger logger)
|
2016-04-20 15:30:56 -07:00
|
|
|
|
{
|
2016-04-23 00:29:48 -07:00
|
|
|
|
logger.Log("Attempting to read file: " + filename);
|
|
|
|
|
|
|
|
|
|
|
|
// Check if file exists
|
|
|
|
|
|
if (!File.Exists(filename))
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.Warning("File '" + filename + "' could not read from!");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-20 15:30:56 -07:00
|
|
|
|
XmlDocument doc = new XmlDocument();
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2016-04-23 01:00:31 -07:00
|
|
|
|
doc.Load(filename);
|
2016-04-20 15:30:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
catch (XmlException)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2016-04-23 00:29:48 -07:00
|
|
|
|
doc.LoadXml(Converters.ClrMameProToXML(File.ReadAllLines(filename)).ToString());
|
2016-04-20 15:30:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.Error(ex.ToString());
|
2016-04-20 15:37:49 -07:00
|
|
|
|
return null;
|
2016-04-20 15:30:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-04-22 15:59:32 -07:00
|
|
|
|
catch (IOException)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.Error("File '" + filename + "' could not be open or read");
|
|
|
|
|
|
}
|
2016-04-23 01:00:31 -07:00
|
|
|
|
catch (OutOfMemoryException)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.Error("File '" + filename + "' is too large to be processed!");
|
|
|
|
|
|
}
|
2016-04-20 15:30:56 -07:00
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.Error(ex.ToString());
|
2016-04-20 15:37:49 -07:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return doc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-25 00:26:19 -07:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Get the XmlTextReader associated with a file, if possible
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="filename">Name of the file to be parsed</param>
|
|
|
|
|
|
/// <param name="logger">Logger object for console and file output</param>
|
|
|
|
|
|
/// <returns>The XmlTextReader representing the (possibly converted) file, null otherwise</returns>
|
|
|
|
|
|
public static XmlTextReader GetXmlTextReader(string filename, Logger logger)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.Log("Attempting to read file: " + filename);
|
|
|
|
|
|
|
|
|
|
|
|
// Check if file exists
|
|
|
|
|
|
if (!File.Exists(filename))
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.Warning("File '" + filename + "' could not read from!");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (IsXmlDat(filename))
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.Log("XML DAT detected");
|
|
|
|
|
|
return new XmlTextReader(filename);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.Log("Non-XML DAT detected");
|
|
|
|
|
|
StringReader sr = new StringReader(Converters.ClrMameProToXML(File.ReadAllLines(filename)).ToString());
|
|
|
|
|
|
return new XmlTextReader(sr);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-20 15:37:49 -07:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Get the name of the DAT for external use
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="filename">Name of the file to be parsed</param>
|
|
|
|
|
|
/// <param name="logger">Logger object for console and file output</param>
|
|
|
|
|
|
/// <returns>The internal name of the DAT on success, empty string otherwise</returns>
|
2016-04-25 00:27:27 -07:00
|
|
|
|
/// <remarks>Needs to be upgraded to XmlTextReader</remarks>
|
2016-04-20 15:37:49 -07:00
|
|
|
|
public static string GetDatName(string filename, Logger logger)
|
|
|
|
|
|
{
|
|
|
|
|
|
string name = "";
|
|
|
|
|
|
XmlDocument doc = GetXmlDocument(filename, logger);
|
|
|
|
|
|
|
|
|
|
|
|
// If the returned document is null, return the blank string
|
|
|
|
|
|
if (doc == null)
|
|
|
|
|
|
{
|
2016-04-20 15:30:56 -07:00
|
|
|
|
return name;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Get the name from the header
|
|
|
|
|
|
if (node != null && node.Name == "header")
|
|
|
|
|
|
{
|
2016-04-21 14:35:11 -07:00
|
|
|
|
XmlNode temp = node.SelectSingleNode("name");
|
|
|
|
|
|
if (temp != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
name = temp.InnerText;
|
|
|
|
|
|
}
|
2016-04-20 15:30:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return name;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-21 14:35:11 -07:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Get the description of the DAT for external use
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="filename">Name of the file to be parsed</param>
|
|
|
|
|
|
/// <param name="logger">Logger object for console and file output</param>
|
|
|
|
|
|
/// <returns>The internal name of the DAT on success, empty string otherwise</returns>
|
2016-04-25 00:27:27 -07:00
|
|
|
|
/// <remarks>Needs to be upgraded to XmlTextReader</remarks>
|
2016-04-21 14:35:11 -07:00
|
|
|
|
public static string GetDatDescription(string filename, Logger logger)
|
|
|
|
|
|
{
|
|
|
|
|
|
string desc = "";
|
|
|
|
|
|
XmlDocument doc = GetXmlDocument(filename, logger);
|
|
|
|
|
|
|
|
|
|
|
|
// If the returned document is null, return the blank string
|
|
|
|
|
|
if (doc == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return desc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Get the name from the header
|
|
|
|
|
|
if (node != null && node.Name == "header")
|
|
|
|
|
|
{
|
|
|
|
|
|
XmlNode temp = node.SelectSingleNode("description");
|
|
|
|
|
|
if (temp != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
desc = temp.InnerText;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return desc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-19 01:11:23 -07:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Parse a DAT and return all found games and roms within
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="filename">Name of the file to be parsed</param>
|
|
|
|
|
|
/// <param name="sysid">System ID for the DAT</param>
|
|
|
|
|
|
/// <param name="srcid">Source ID for the DAT</param>
|
|
|
|
|
|
/// <param name="logger">Logger object for console and/or file output</param>
|
|
|
|
|
|
/// <returns>List of RomData objects representing the found data</returns>
|
|
|
|
|
|
public static List<RomData> Parse(string filename, int sysid, int srcid, Logger logger)
|
2016-04-25 00:30:50 -07:00
|
|
|
|
{
|
|
|
|
|
|
List<RomData> roms = new List<RomData>();
|
|
|
|
|
|
XmlTextReader xtr = GetXmlTextReader(filename, logger);
|
|
|
|
|
|
xtr.WhitespaceHandling = WhitespaceHandling.None;
|
2016-04-25 01:59:09 -07:00
|
|
|
|
bool superdat = false, shouldbreak = false;
|
2016-04-25 00:30:50 -07:00
|
|
|
|
string parent = "";
|
|
|
|
|
|
if (xtr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
xtr.MoveToContent();
|
2016-04-25 01:59:09 -07:00
|
|
|
|
while (xtr.NodeType != XmlNodeType.None)
|
2016-04-25 00:30:50 -07:00
|
|
|
|
{
|
|
|
|
|
|
// We only want elements
|
2016-04-25 01:59:09 -07:00
|
|
|
|
if (xtr.NodeType != XmlNodeType.Element)
|
2016-04-25 00:30:50 -07:00
|
|
|
|
{
|
|
|
|
|
|
xtr.Read();
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-25 01:59:09 -07:00
|
|
|
|
switch (xtr.Name)
|
2016-04-25 00:30:50 -07:00
|
|
|
|
{
|
2016-04-25 01:59:09 -07:00
|
|
|
|
case "datafile":
|
|
|
|
|
|
case "softwarelist":
|
|
|
|
|
|
parent = xtr.Name;
|
|
|
|
|
|
xtr.Read();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "header":
|
|
|
|
|
|
xtr.ReadToDescendant("name");
|
|
|
|
|
|
superdat = (xtr.ReadElementContentAsString() != null ? xtr.ReadElementContentAsString().Contains(" - SuperDAT") : false);
|
|
|
|
|
|
while (xtr.Name != "header")
|
2016-04-25 00:30:50 -07:00
|
|
|
|
{
|
2016-04-25 01:59:09 -07:00
|
|
|
|
xtr.Read();
|
2016-04-25 00:30:50 -07:00
|
|
|
|
}
|
2016-04-25 01:59:09 -07:00
|
|
|
|
xtr.Read();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "machine":
|
|
|
|
|
|
case "game":
|
|
|
|
|
|
case "software":
|
|
|
|
|
|
string temptype = xtr.Name;
|
|
|
|
|
|
string tempname = "";
|
|
|
|
|
|
|
|
|
|
|
|
// We want to process the entire subtree of the game
|
|
|
|
|
|
XmlReader subreader = xtr.ReadSubtree();
|
|
|
|
|
|
|
|
|
|
|
|
if (subreader != null)
|
2016-04-25 00:30:50 -07:00
|
|
|
|
{
|
2016-04-25 01:59:09 -07:00
|
|
|
|
if (temptype == "software" && subreader.ReadToFollowing("description"))
|
2016-04-25 00:30:50 -07:00
|
|
|
|
{
|
2016-04-25 01:59:09 -07:00
|
|
|
|
tempname = subreader.ReadElementContentAsString();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// There are rare cases where a malformed XML will not have the required attributes. We can only skip them.
|
|
|
|
|
|
if (xtr.AttributeCount == 0)
|
2016-04-25 00:30:50 -07:00
|
|
|
|
{
|
2016-04-25 01:59:09 -07:00
|
|
|
|
logger.Error("No attributes were found");
|
|
|
|
|
|
xtr.ReadToNextSibling(xtr.Name);
|
|
|
|
|
|
continue;
|
2016-04-25 00:30:50 -07:00
|
|
|
|
}
|
2016-04-25 01:59:09 -07:00
|
|
|
|
tempname = xtr.GetAttribute("name");
|
|
|
|
|
|
}
|
2016-04-25 00:30:50 -07:00
|
|
|
|
|
2016-04-25 01:59:09 -07:00
|
|
|
|
if (superdat)
|
|
|
|
|
|
{
|
|
|
|
|
|
tempname = Regex.Match(tempname, @".*?\\(.*)").Groups[1].Value;
|
2016-04-25 00:30:50 -07:00
|
|
|
|
}
|
2016-04-25 01:59:09 -07:00
|
|
|
|
|
|
|
|
|
|
while (subreader.Read())
|
2016-04-25 00:30:50 -07:00
|
|
|
|
{
|
2016-04-25 01:59:09 -07:00
|
|
|
|
// We only want elements
|
|
|
|
|
|
if (subreader.NodeType != XmlNodeType.Element)
|
2016-04-25 00:30:50 -07:00
|
|
|
|
{
|
2016-04-25 01:59:09 -07:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Get the roms from the machine
|
|
|
|
|
|
switch (subreader.Name)
|
|
|
|
|
|
{
|
|
|
|
|
|
case "rom":
|
|
|
|
|
|
case "disk":
|
|
|
|
|
|
// Take care of hex-sized files
|
|
|
|
|
|
long size = -1;
|
|
|
|
|
|
if (xtr.GetAttribute("size") != null && xtr.GetAttribute("size").Contains("0x"))
|
2016-04-25 00:30:50 -07:00
|
|
|
|
{
|
2016-04-25 01:59:09 -07:00
|
|
|
|
size = Convert.ToInt64(xtr.GetAttribute("size"), 16);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (xtr.GetAttribute("size") != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Int64.TryParse(xtr.GetAttribute("size"), out size);
|
2016-04-25 00:30:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-25 01:59:09 -07:00
|
|
|
|
roms.Add(new RomData
|
|
|
|
|
|
{
|
|
|
|
|
|
Game = tempname,
|
|
|
|
|
|
Name = xtr.GetAttribute("name"),
|
|
|
|
|
|
Type = xtr.Name,
|
|
|
|
|
|
SystemID = sysid,
|
|
|
|
|
|
SourceID = srcid,
|
|
|
|
|
|
Size = size,
|
|
|
|
|
|
CRC = (xtr.GetAttribute("crc") != null ? xtr.GetAttribute("crc").ToLowerInvariant().Trim() : ""),
|
|
|
|
|
|
MD5 = (xtr.GetAttribute("md5") != null ? xtr.GetAttribute("md5").ToLowerInvariant().Trim() : ""),
|
|
|
|
|
|
SHA1 = (xtr.GetAttribute("sha1") != null ? xtr.GetAttribute("sha1").ToLowerInvariant().Trim() : ""),
|
|
|
|
|
|
});
|
2016-04-25 00:30:50 -07:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-25 01:59:09 -07:00
|
|
|
|
// Read to next game
|
|
|
|
|
|
if (!xtr.ReadToNextSibling(temptype))
|
|
|
|
|
|
{
|
|
|
|
|
|
shouldbreak = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
xtr.Read();
|
|
|
|
|
|
break;
|
2016-04-25 00:30:50 -07:00
|
|
|
|
}
|
2016-04-25 01:59:09 -07:00
|
|
|
|
|
|
|
|
|
|
// If we hit an endpoint, break out of the loop early
|
|
|
|
|
|
if (shouldbreak)
|
2016-04-25 00:30:50 -07:00
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return roms;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-27 00:53:15 -07:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Parse a DAT and return all found games and roms within
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="filename">Name of the file to be parsed</param>
|
|
|
|
|
|
/// <param name="sysid">System ID for the DAT</param>
|
|
|
|
|
|
/// <param name="srcid">Source ID for the DAT</param>
|
|
|
|
|
|
/// <param name="merge">True if files should be matched by hash alone, false otherwise</param>
|
|
|
|
|
|
/// <param name="dbc">Database connection for adding found ROMs</param>
|
|
|
|
|
|
/// <param name="logger">Logger object for console and/or file output</param>
|
|
|
|
|
|
/// <returns>True if no errors occur, false otherwise</returns>
|
2016-04-27 01:10:24 -07:00
|
|
|
|
/// <remarks>This doesn't have the same output as Parse + Merge OR even just Parse. Duplicates don't seem to be added either way, why?</remarks>
|
2016-04-27 20:57:05 -07:00
|
|
|
|
public static bool ParseDb(string filename, int sysid, int srcid, bool merge, SqliteConnection dbc, Logger logger)
|
2016-04-27 00:53:15 -07:00
|
|
|
|
{
|
|
|
|
|
|
XmlTextReader xtr = GetXmlTextReader(filename, logger);
|
|
|
|
|
|
xtr.WhitespaceHandling = WhitespaceHandling.None;
|
|
|
|
|
|
bool superdat = false, shouldbreak = false;
|
|
|
|
|
|
string parent = "";
|
|
|
|
|
|
|
|
|
|
|
|
// If the reader is null, return false
|
|
|
|
|
|
if (xtr == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
xtr.MoveToContent();
|
|
|
|
|
|
while (xtr.NodeType != XmlNodeType.None)
|
|
|
|
|
|
{
|
|
|
|
|
|
// We only want elements
|
|
|
|
|
|
if (xtr.NodeType != XmlNodeType.Element)
|
|
|
|
|
|
{
|
|
|
|
|
|
xtr.Read();
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (xtr.Name)
|
|
|
|
|
|
{
|
|
|
|
|
|
case "datafile":
|
|
|
|
|
|
case "softwarelist":
|
|
|
|
|
|
parent = xtr.Name;
|
|
|
|
|
|
xtr.Read();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "header":
|
|
|
|
|
|
xtr.ReadToDescendant("name");
|
2016-04-27 21:27:41 -07:00
|
|
|
|
string content = xtr.ReadElementContentAsString();
|
|
|
|
|
|
superdat = (content != null ? content.Contains(" - SuperDAT") : false);
|
2016-04-27 00:53:15 -07:00
|
|
|
|
while (xtr.Name != "header")
|
|
|
|
|
|
{
|
|
|
|
|
|
xtr.Read();
|
|
|
|
|
|
}
|
|
|
|
|
|
xtr.Read();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "machine":
|
|
|
|
|
|
case "game":
|
|
|
|
|
|
case "software":
|
|
|
|
|
|
string temptype = xtr.Name;
|
|
|
|
|
|
string tempname = "";
|
|
|
|
|
|
|
|
|
|
|
|
// We want to process the entire subtree of the game
|
|
|
|
|
|
XmlReader subreader = xtr.ReadSubtree();
|
|
|
|
|
|
|
|
|
|
|
|
if (subreader != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (temptype == "software" && subreader.ReadToFollowing("description"))
|
|
|
|
|
|
{
|
|
|
|
|
|
tempname = subreader.ReadElementContentAsString();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// There are rare cases where a malformed XML will not have the required attributes. We can only skip them.
|
|
|
|
|
|
if (xtr.AttributeCount == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.Error("No attributes were found");
|
|
|
|
|
|
xtr.ReadToNextSibling(xtr.Name);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
tempname = xtr.GetAttribute("name");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (superdat)
|
|
|
|
|
|
{
|
|
|
|
|
|
tempname = Regex.Match(tempname, @".*?\\(.*)").Groups[1].Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
while (subreader.Read())
|
|
|
|
|
|
{
|
|
|
|
|
|
// We only want elements
|
|
|
|
|
|
if (subreader.NodeType != XmlNodeType.Element)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Get the roms from the machine
|
|
|
|
|
|
switch (subreader.Name)
|
|
|
|
|
|
{
|
|
|
|
|
|
case "rom":
|
|
|
|
|
|
case "disk":
|
|
|
|
|
|
// Take care of hex-sized files
|
|
|
|
|
|
long 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-27 11:44:14 -07:00
|
|
|
|
// If we're in merged mode, check before adding
|
|
|
|
|
|
if (merge)
|
2016-04-27 00:53:15 -07:00
|
|
|
|
{
|
2016-04-27 11:44:14 -07:00
|
|
|
|
// If the rom doesn't exist, add it to the database
|
2016-04-27 11:54:27 -07:00
|
|
|
|
string query = @"SELECT id FROM roms WHERE size=" + size +
|
|
|
|
|
|
(xtr.GetAttribute("crc") != null ? " AND (crc='" + xtr.GetAttribute("crc").ToLowerInvariant().Trim() + "' OR crc='')" : "") +
|
|
|
|
|
|
(xtr.GetAttribute("md5") != null ? " AND (md5='" + xtr.GetAttribute("md5").ToLowerInvariant().Trim() + "' OR md5='')" : "") +
|
|
|
|
|
|
(xtr.GetAttribute("sha1") != null ? " AND (sha1='" + xtr.GetAttribute("sha1").ToLowerInvariant().Trim() + "' OR sha1='')" : "");
|
2016-04-27 11:44:14 -07:00
|
|
|
|
|
|
|
|
|
|
using (SqliteCommand slc = new SqliteCommand(query, dbc))
|
2016-04-27 00:53:15 -07:00
|
|
|
|
{
|
2016-04-27 11:44:14 -07:00
|
|
|
|
using (SqliteDataReader sldr = slc.ExecuteReader())
|
2016-04-27 00:53:15 -07:00
|
|
|
|
{
|
2016-04-27 11:44:14 -07:00
|
|
|
|
// If there's no returns, then add the file
|
|
|
|
|
|
if (!sldr.HasRows)
|
|
|
|
|
|
{
|
|
|
|
|
|
query = @"INSERT INTO roms
|
2016-04-27 16:57:03 -07:00
|
|
|
|
(game, name, type, sysid, srcid, size, crc, md5, sha1, dupe)
|
2016-04-27 00:53:15 -07:00
|
|
|
|
VALUES ('" + tempname.Replace("'", "''") + "', '" +
|
2016-04-27 11:44:14 -07:00
|
|
|
|
xtr.GetAttribute("name").Replace("'", "''") + "', '" +
|
|
|
|
|
|
xtr.Name + "', " +
|
|
|
|
|
|
sysid + ", " +
|
|
|
|
|
|
srcid + ", " +
|
|
|
|
|
|
size +
|
|
|
|
|
|
(xtr.GetAttribute("crc") != null ? ", '" + xtr.GetAttribute("crc").ToLowerInvariant().Trim() + "'" : ", ''") +
|
|
|
|
|
|
(xtr.GetAttribute("md5") != null ? ", '" + xtr.GetAttribute("md5").ToLowerInvariant().Trim() + "'" : ", ''") +
|
|
|
|
|
|
(xtr.GetAttribute("sha1") != null ? ", '" + xtr.GetAttribute("sha1").ToLowerInvariant().Trim() + "'" : ", ''") +
|
2016-04-27 16:57:03 -07:00
|
|
|
|
", '" + filename + "'" +
|
2016-04-27 11:44:14 -07:00
|
|
|
|
")";
|
|
|
|
|
|
using (SqliteCommand sslc = new SqliteCommand(query, dbc))
|
|
|
|
|
|
{
|
|
|
|
|
|
sslc.ExecuteNonQuery();
|
|
|
|
|
|
}
|
2016-04-27 00:53:15 -07:00
|
|
|
|
}
|
2016-04-27 11:44:14 -07:00
|
|
|
|
// Otherwise, set the dupe flag to true
|
|
|
|
|
|
else
|
2016-04-27 00:53:15 -07:00
|
|
|
|
{
|
2016-04-27 11:44:14 -07:00
|
|
|
|
query = @"UPDATE roms SET dupe='true' WHERE size=" + size +
|
|
|
|
|
|
(xtr.GetAttribute("crc") != null ? " AND crc='" + xtr.GetAttribute("crc").ToLowerInvariant().Trim() + "'" : "") +
|
|
|
|
|
|
(xtr.GetAttribute("md5") != null ? " AND md5='" + xtr.GetAttribute("md5").ToLowerInvariant().Trim() + "'" : "") +
|
2016-04-27 11:54:27 -07:00
|
|
|
|
(xtr.GetAttribute("sha1") != null ? " AND sha1='" + xtr.GetAttribute("sha1").ToLowerInvariant().Trim() + "'" : "");
|
2016-04-27 11:44:14 -07:00
|
|
|
|
|
|
|
|
|
|
using (SqliteCommand sslc = new SqliteCommand(query, dbc))
|
|
|
|
|
|
{
|
|
|
|
|
|
sslc.ExecuteNonQuery();
|
|
|
|
|
|
}
|
2016-04-27 00:53:15 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-04-27 11:44:14 -07:00
|
|
|
|
// If we're not in merged mode, just add it
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
string query = @"INSERT INTO roms
|
2016-04-27 21:53:54 -07:00
|
|
|
|
(game, name, type, sysid, srcid, size, crc, md5, sha1, filename)
|
2016-04-27 11:44:14 -07:00
|
|
|
|
VALUES ('" + tempname.Replace("'", "''") + "', '" +
|
|
|
|
|
|
xtr.GetAttribute("name").Replace("'", "''") + "', '" +
|
|
|
|
|
|
xtr.Name + "', " +
|
|
|
|
|
|
sysid + ", " +
|
|
|
|
|
|
srcid + ", " +
|
|
|
|
|
|
size +
|
|
|
|
|
|
(xtr.GetAttribute("crc") != null ? ", '" + xtr.GetAttribute("crc").ToLowerInvariant().Trim() + "'" : ", ''") +
|
|
|
|
|
|
(xtr.GetAttribute("md5") != null ? ", '" + xtr.GetAttribute("md5").ToLowerInvariant().Trim() + "'" : ", ''") +
|
|
|
|
|
|
(xtr.GetAttribute("sha1") != null ? ", '" + xtr.GetAttribute("sha1").ToLowerInvariant().Trim() + "'" : ", ''") +
|
2016-04-27 21:53:54 -07:00
|
|
|
|
", '" + filename + "'" +
|
2016-04-27 11:44:14 -07:00
|
|
|
|
")";
|
|
|
|
|
|
using (SqliteCommand sslc = new SqliteCommand(query, dbc))
|
|
|
|
|
|
{
|
|
|
|
|
|
sslc.ExecuteNonQuery();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-04-27 00:53:15 -07:00
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Read to next game
|
|
|
|
|
|
if (!xtr.ReadToNextSibling(temptype))
|
|
|
|
|
|
{
|
|
|
|
|
|
shouldbreak = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
xtr.Read();
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If we hit an endpoint, break out of the loop early
|
|
|
|
|
|
if (shouldbreak)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-19 01:11:23 -07:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Merge an arbitrary set of ROMs based on the supplied information
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="inroms">List of RomData objects representing the roms to be merged</param>
|
|
|
|
|
|
/// <param name="presorted">True if the list should be considered pre-sorted (default false)</param>
|
|
|
|
|
|
/// <returns>A List of RomData objects representing the merged roms</returns>
|
|
|
|
|
|
public static List<RomData> Merge(List<RomData> inroms, bool presorted = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
List<RomData> outroms = new List<RomData>();
|
|
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
2016-04-19 12:26:24 -07:00
|
|
|
|
outroms.RemoveAt(outroms.Count - 1);
|
|
|
|
|
|
outroms.Insert(outroms.Count, last);
|
2016-04-19 01:11:23 -07:00
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2016-04-19 10:38:01 -07:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
outroms.Add(rom);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
outroms.Add(rom);
|
2016-04-19 01:11:23 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Then return the result
|
|
|
|
|
|
return outroms;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-27 00:53:15 -07:00
|
|
|
|
|
2016-04-19 01:11:23 -07:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Sort a list of RomData objects by SystemID, SourceID, Game, and Name (in order)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="roms">List of RomData objects representing the roms to be sorted</param>
|
|
|
|
|
|
/// <param name="norename">True if files are not renamed, false otherwise</param>
|
|
|
|
|
|
public static void Sort(List<RomData> 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);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2016-04-19 13:11:11 -07:00
|
|
|
|
|
2016-04-19 15:41:06 -07:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Get differences between two lists of RomData objects
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="A">First RomData list</param>
|
|
|
|
|
|
/// <param name="B">Second RomData list</param>
|
|
|
|
|
|
/// <returns>Any rom that's not in both lists</returns>
|
|
|
|
|
|
/// <remarks>Adapted from http://stackoverflow.com/questions/5620266/the-opposite-of-intersect</remarks>
|
|
|
|
|
|
public static List<RomData> Diff(List<RomData> A, List<RomData> B)
|
2016-04-19 13:11:11 -07:00
|
|
|
|
{
|
2016-04-19 16:39:17 -07:00
|
|
|
|
List<String> AString = Output.RomDataToString(A);
|
|
|
|
|
|
List<String> BString = Output.RomDataToString(B);
|
|
|
|
|
|
List<String> CString = AString.Except(BString).Union(BString.Except(AString)).ToList();
|
|
|
|
|
|
return Output.StringToRomData(CString);
|
2016-04-19 13:11:11 -07:00
|
|
|
|
}
|
2016-04-22 13:51:37 -07:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Get all RomData objects that are in A but not in B
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="A">First RomData list</param>
|
|
|
|
|
|
/// <param name="B">Second RomData list</param>
|
|
|
|
|
|
/// <returns>Any rom that's only in the first list</returns>
|
|
|
|
|
|
/// <remarks>Adapted from http://stackoverflow.com/questions/5620266/the-opposite-of-intersect</remarks>
|
|
|
|
|
|
public static List<RomData> DiffOnlyInA(List<RomData> A, List<RomData> B)
|
|
|
|
|
|
{
|
|
|
|
|
|
List<String> AString = Output.RomDataToString(A);
|
|
|
|
|
|
List<String> BString = Output.RomDataToString(B);
|
|
|
|
|
|
List<String> CString = AString.Except(BString).ToList();
|
|
|
|
|
|
return Output.StringToRomData(CString);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Get all RomData objects that are in A and B
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="A">First RomData list</param>
|
|
|
|
|
|
/// <param name="B">Second RomData list</param>
|
|
|
|
|
|
/// <returns>Any rom that's in both lists</returns>
|
|
|
|
|
|
/// <remarks>Adapted from http://stackoverflow.com/questions/5620266/the-opposite-of-intersect</remarks>
|
|
|
|
|
|
public static List<RomData> DiffInAB(List<RomData> A, List<RomData> B)
|
|
|
|
|
|
{
|
|
|
|
|
|
List<String> AString = Output.RomDataToString(A);
|
|
|
|
|
|
List<String> BString = Output.RomDataToString(B);
|
|
|
|
|
|
List<String> CString = AString.Union(BString).Except(AString.Except(BString).Union(BString.Except(AString))).ToList();
|
|
|
|
|
|
return Output.StringToRomData(CString);
|
|
|
|
|
|
}
|
2016-04-19 01:11:23 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|