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-05-06 12:56:02 -07:00
|
|
|
|
// 0-byte file constants
|
2016-05-06 13:12:00 -07:00
|
|
|
|
public static long SizeZero = 0;
|
|
|
|
|
|
public static string CRCZero = "00000000";
|
|
|
|
|
|
public static string MD5Zero = "d41d8cd98f00b204e9800998ecf8427e";
|
|
|
|
|
|
public static string SHA1Zero = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
|
2016-05-06 12:56:02 -07:00
|
|
|
|
|
2016-04-19 15:41:06 -07:00
|
|
|
|
/// <summary>
|
2016-05-16 20:51:05 -07:00
|
|
|
|
/// Get what type of DAT the input file is
|
2016-04-19 15:41:06 -07:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="filename">Name of the file to be parsed</param>
|
2016-05-16 20:51:05 -07:00
|
|
|
|
/// <returns>The OutputFormat corresponding to the DAT</returns>
|
|
|
|
|
|
public static OutputFormat GetOutputFormat(string filename)
|
2016-04-19 15:41:06 -07:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2016-04-25 00:25:08 -07:00
|
|
|
|
StreamReader sr = new StreamReader(File.OpenRead(filename));
|
|
|
|
|
|
string first = sr.ReadLine();
|
|
|
|
|
|
sr.Close();
|
2016-05-16 20:51:05 -07:00
|
|
|
|
if (first.Contains("<") && first.Contains(">"))
|
2016-04-20 15:30:56 -07:00
|
|
|
|
{
|
2016-05-16 20:51:05 -07:00
|
|
|
|
return OutputFormat.Xml;
|
2016-04-20 15:30:56 -07:00
|
|
|
|
}
|
2016-05-16 20:51:05 -07:00
|
|
|
|
else if (first.Contains("[") && first.Contains("]"))
|
2016-04-20 15:30:56 -07:00
|
|
|
|
{
|
2016-05-16 20:51:05 -07:00
|
|
|
|
return OutputFormat.RomCenter;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
return OutputFormat.ClrMamePro;
|
2016-04-20 15:30:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-05-16 20:51:05 -07:00
|
|
|
|
catch (Exception)
|
2016-04-20 15:30:56 -07:00
|
|
|
|
{
|
2016-05-16 20:51:05 -07:00
|
|
|
|
return OutputFormat.ClrMamePro;
|
2016-04-20 15:37:49 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2016-05-18 16:37:39 -07:00
|
|
|
|
logger.Log("Attempting to read file: \"" + filename + "\"");
|
2016-04-25 00:26:19 -07:00
|
|
|
|
|
|
|
|
|
|
// Check if file exists
|
|
|
|
|
|
if (!File.Exists(filename))
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.Warning("File '" + filename + "' could not read from!");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-16 20:51:05 -07:00
|
|
|
|
XmlTextReader xtr;
|
|
|
|
|
|
StringReader sr;
|
|
|
|
|
|
switch (GetOutputFormat(filename))
|
|
|
|
|
|
{
|
|
|
|
|
|
case OutputFormat.Xml:
|
|
|
|
|
|
logger.Log("XML DAT detected");
|
|
|
|
|
|
xtr = new XmlTextReader(filename);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case OutputFormat.RomCenter:
|
|
|
|
|
|
logger.Log("RomCenter DAT detected");
|
|
|
|
|
|
sr = new StringReader(Converters.RomCenterToXML(File.ReadAllLines(filename)).ToString());
|
|
|
|
|
|
xtr = new XmlTextReader(sr);
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
logger.Log("ClrMamePro DAT detected");
|
|
|
|
|
|
sr = new StringReader(Converters.ClrMameProToXML(File.ReadAllLines(filename)).ToString());
|
|
|
|
|
|
xtr = new XmlTextReader(sr);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
xtr.WhitespaceHandling = WhitespaceHandling.None;
|
|
|
|
|
|
xtr.DtdProcessing = DtdProcessing.Ignore;
|
|
|
|
|
|
return xtr;
|
2016-04-21 14:35:11 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-27 00:53:15 -07:00
|
|
|
|
/// <summary>
|
2016-05-16 13:42:21 -07:00
|
|
|
|
/// 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="datdata">The DatData object representing found roms to this point</param>
|
|
|
|
|
|
/// <param name="logger">Logger object for console and/or file output</param>
|
|
|
|
|
|
/// <returns>DatData object representing the read-in data</returns>
|
2016-05-18 22:22:49 -07:00
|
|
|
|
public static DatData Parse(string filename, int sysid, int srcid, DatData datdata, Logger logger, bool keep = false)
|
2016-04-28 10:37:33 -07:00
|
|
|
|
{
|
|
|
|
|
|
XmlTextReader xtr = GetXmlTextReader(filename, logger);
|
|
|
|
|
|
bool superdat = false, shouldbreak = false;
|
|
|
|
|
|
string parent = "";
|
|
|
|
|
|
if (xtr != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
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":
|
2016-05-16 14:28:23 -07:00
|
|
|
|
// We want to process the entire subtree of the header
|
|
|
|
|
|
XmlReader headreader = xtr.ReadSubtree();
|
|
|
|
|
|
|
|
|
|
|
|
if (headreader != null)
|
2016-04-28 10:37:33 -07:00
|
|
|
|
{
|
2016-05-16 15:17:11 -07:00
|
|
|
|
while (!headreader.EOF)
|
2016-05-16 14:28:23 -07:00
|
|
|
|
{
|
|
|
|
|
|
// We only want elements
|
2016-05-16 15:17:11 -07:00
|
|
|
|
if (headreader.NodeType != XmlNodeType.Element || headreader.Name == "header")
|
2016-05-16 14:28:23 -07:00
|
|
|
|
{
|
2016-05-16 15:17:11 -07:00
|
|
|
|
headreader.Read();
|
2016-05-16 14:28:23 -07:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Get all header items (ONLY OVERWRITE IF THERE'S NO DATA)
|
2016-05-16 15:17:11 -07:00
|
|
|
|
string content = "";
|
|
|
|
|
|
switch (headreader.Name)
|
2016-05-16 14:28:23 -07:00
|
|
|
|
{
|
|
|
|
|
|
case "name":
|
2016-05-17 16:53:02 -07:00
|
|
|
|
content = headreader.ReadElementContentAsString(); ;
|
|
|
|
|
|
datdata.Name = (datdata.Name == "" ? content : datdata.Name);
|
|
|
|
|
|
superdat = superdat || content.Contains(" - SuperDAT");
|
2016-05-16 14:28:23 -07:00
|
|
|
|
break;
|
|
|
|
|
|
case "description":
|
2016-05-16 15:17:11 -07:00
|
|
|
|
content = headreader.ReadElementContentAsString();
|
|
|
|
|
|
datdata.Description = (datdata.Description == "" ? content : datdata.Description);
|
2016-05-16 14:28:23 -07:00
|
|
|
|
break;
|
|
|
|
|
|
case "category":
|
2016-05-16 15:17:11 -07:00
|
|
|
|
content = headreader.ReadElementContentAsString();
|
|
|
|
|
|
datdata.Category = (datdata.Category == "" ? content : datdata.Category);
|
2016-05-16 14:28:23 -07:00
|
|
|
|
break;
|
|
|
|
|
|
case "version":
|
2016-05-16 15:17:11 -07:00
|
|
|
|
content = headreader.ReadElementContentAsString();
|
|
|
|
|
|
datdata.Version = (datdata.Version == "" ? content : datdata.Version);
|
2016-05-16 14:28:23 -07:00
|
|
|
|
break;
|
|
|
|
|
|
case "date":
|
2016-05-16 15:17:11 -07:00
|
|
|
|
content = headreader.ReadElementContentAsString();
|
|
|
|
|
|
datdata.Date = (datdata.Date == "" ? content : datdata.Date);
|
2016-05-16 14:28:23 -07:00
|
|
|
|
break;
|
|
|
|
|
|
case "author":
|
2016-05-16 15:17:11 -07:00
|
|
|
|
content = headreader.ReadElementContentAsString();
|
|
|
|
|
|
datdata.Author = (datdata.Author == "" ? content : datdata.Author);
|
2016-05-16 14:28:23 -07:00
|
|
|
|
break;
|
|
|
|
|
|
case "email":
|
2016-05-16 15:17:11 -07:00
|
|
|
|
content = headreader.ReadElementContentAsString();
|
|
|
|
|
|
datdata.Email = (datdata.Email == "" ? content : datdata.Email);
|
2016-05-16 14:28:23 -07:00
|
|
|
|
break;
|
|
|
|
|
|
case "homepage":
|
2016-05-16 15:17:11 -07:00
|
|
|
|
content = headreader.ReadElementContentAsString();
|
|
|
|
|
|
datdata.Homepage = (datdata.Homepage == "" ? content : datdata.Homepage);
|
2016-05-16 14:28:23 -07:00
|
|
|
|
break;
|
|
|
|
|
|
case "url":
|
2016-05-16 15:17:11 -07:00
|
|
|
|
content = headreader.ReadElementContentAsString();
|
|
|
|
|
|
datdata.Url = (datdata.Url == "" ? content : datdata.Url);
|
2016-05-16 14:28:23 -07:00
|
|
|
|
break;
|
|
|
|
|
|
case "comment":
|
2016-05-16 15:17:11 -07:00
|
|
|
|
content = headreader.ReadElementContentAsString();
|
|
|
|
|
|
datdata.Comment = (datdata.Comment == "" ? content : datdata.Comment);
|
2016-05-16 14:28:23 -07:00
|
|
|
|
break;
|
2016-05-17 16:53:02 -07:00
|
|
|
|
case "type":
|
|
|
|
|
|
content = headreader.ReadElementContentAsString();
|
|
|
|
|
|
datdata.Type = (datdata.Type == "" ? content : datdata.Type);
|
|
|
|
|
|
superdat = superdat || content.Contains("SuperDAT");
|
|
|
|
|
|
break;
|
2016-05-16 14:28:23 -07:00
|
|
|
|
case "clrmamepro":
|
|
|
|
|
|
if (headreader.GetAttribute("forcemerging") != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (headreader.GetAttribute("forcemerging"))
|
|
|
|
|
|
{
|
|
|
|
|
|
case "split":
|
|
|
|
|
|
datdata.ForceMerging = ForceMerging.Split;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "none":
|
|
|
|
|
|
datdata.ForceMerging = ForceMerging.None;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "full":
|
|
|
|
|
|
datdata.ForceMerging = ForceMerging.Full;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (headreader.GetAttribute("forcenodump") != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (headreader.GetAttribute("forcenodump"))
|
|
|
|
|
|
{
|
|
|
|
|
|
case "obsolete":
|
|
|
|
|
|
datdata.ForceNodump = ForceNodump.Obsolete;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "required":
|
|
|
|
|
|
datdata.ForceNodump = ForceNodump.Required;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "ignore":
|
|
|
|
|
|
datdata.ForceNodump = ForceNodump.Ignore;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (headreader.GetAttribute("forcepacking") != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (headreader.GetAttribute("forcepacking"))
|
|
|
|
|
|
{
|
|
|
|
|
|
case "zip":
|
|
|
|
|
|
datdata.ForcePacking = ForcePacking.Zip;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "unzip":
|
|
|
|
|
|
datdata.ForcePacking = ForcePacking.Unzip;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-05-16 15:17:11 -07:00
|
|
|
|
headreader.Read();
|
2016-05-16 14:28:23 -07:00
|
|
|
|
break;
|
2016-05-16 22:25:03 -07:00
|
|
|
|
default:
|
|
|
|
|
|
headreader.Read();
|
|
|
|
|
|
break;
|
2016-05-16 14:28:23 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-04-28 10:37:33 -07:00
|
|
|
|
}
|
2016-05-16 14:28:23 -07:00
|
|
|
|
|
|
|
|
|
|
// Skip the header node now that we've processed it
|
|
|
|
|
|
xtr.Skip();
|
2016-04-28 10:37:33 -07:00
|
|
|
|
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();
|
2016-05-18 17:10:20 -07:00
|
|
|
|
tempname = tempname.Replace('/', '_');
|
2016-04-28 10:37:33 -07:00
|
|
|
|
}
|
|
|
|
|
|
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");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-18 22:22:49 -07:00
|
|
|
|
if (superdat && !keep)
|
2016-04-28 10:37:33 -07:00
|
|
|
|
{
|
|
|
|
|
|
tempname = Regex.Match(tempname, @".*?\\(.*)").Groups[1].Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-15 13:45:41 -07:00
|
|
|
|
string key = "";
|
2016-04-28 10:37:33 -07:00
|
|
|
|
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":
|
2016-05-18 16:59:34 -07:00
|
|
|
|
// If the rom is nodump, flag it
|
|
|
|
|
|
bool nodump = false;
|
2016-05-06 10:59:05 -07:00
|
|
|
|
if (xtr.GetAttribute("flags") == "nodump" || xtr.GetAttribute("status") == "nodump")
|
|
|
|
|
|
{
|
2016-05-18 16:37:39 -07:00
|
|
|
|
logger.Log("Nodump detected: " +
|
2016-05-18 16:59:34 -07:00
|
|
|
|
(xtr.GetAttribute("name") != null && xtr.GetAttribute("name") != "" ? "\"" + xtr.GetAttribute("name") + "\"" : "ROM NAME NOT FOUND"));
|
|
|
|
|
|
nodump = true;
|
2016-05-06 10:59:05 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-14 19:41:07 -07:00
|
|
|
|
// Take care of hex-sized files
|
2016-04-28 10:37:33 -07:00
|
|
|
|
long size = -1;
|
2016-05-14 19:41:07 -07:00
|
|
|
|
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-05-15 13:23:29 -07:00
|
|
|
|
// If the rom is continue or ignore, add the size to the previous rom
|
|
|
|
|
|
if (xtr.GetAttribute("loadflag") == "continue" || xtr.GetAttribute("loadflag") == "ignore")
|
2016-05-14 19:41:07 -07:00
|
|
|
|
{
|
2016-05-16 14:28:23 -07:00
|
|
|
|
int index = datdata.Roms[key].Count() - 1;
|
|
|
|
|
|
RomData lastrom = datdata.Roms[key][index];
|
2016-05-15 13:23:29 -07:00
|
|
|
|
lastrom.Size += size;
|
2016-05-16 14:28:23 -07:00
|
|
|
|
datdata.Roms[key].RemoveAt(index);
|
|
|
|
|
|
datdata.Roms[key].Add(lastrom);
|
2016-05-15 13:23:29 -07:00
|
|
|
|
continue;
|
2016-05-14 19:41:07 -07:00
|
|
|
|
}
|
2016-04-28 10:37:33 -07:00
|
|
|
|
|
2016-05-06 10:53:42 -07:00
|
|
|
|
// Sanitize the hashes from null, hex sizes, and "true blank" strings
|
2016-05-02 14:08:50 -07:00
|
|
|
|
string crc = (xtr.GetAttribute("crc") != null ? xtr.GetAttribute("crc").ToLowerInvariant().Trim() : "");
|
|
|
|
|
|
crc = (crc.StartsWith("0x") ? crc.Remove(0, 2) : crc);
|
2016-05-06 10:53:42 -07:00
|
|
|
|
crc = (crc == "-" ? "" : crc);
|
2016-05-10 18:20:27 -07:00
|
|
|
|
crc = (crc == "" ? "" : crc.PadLeft(8, '0'));
|
2016-05-02 14:08:50 -07:00
|
|
|
|
string md5 = (xtr.GetAttribute("md5") != null ? xtr.GetAttribute("md5").ToLowerInvariant().Trim() : "");
|
|
|
|
|
|
md5 = (md5.StartsWith("0x") ? md5.Remove(0, 2) : md5);
|
2016-05-06 10:53:42 -07:00
|
|
|
|
md5 = (md5 == "-" ? "" : md5);
|
2016-05-10 18:20:27 -07:00
|
|
|
|
md5 = (md5 == "" ? "" : md5.PadLeft(32, '0'));
|
2016-05-02 14:08:50 -07:00
|
|
|
|
string sha1 = (xtr.GetAttribute("sha1") != null ? xtr.GetAttribute("sha1").ToLowerInvariant().Trim() : "");
|
|
|
|
|
|
sha1 = (sha1.StartsWith("0x") ? sha1.Remove(0, 2) : sha1);
|
2016-05-06 10:53:42 -07:00
|
|
|
|
sha1 = (sha1 == "-" ? "" : sha1);
|
2016-05-10 18:20:27 -07:00
|
|
|
|
sha1 = (sha1 == "" ? "" : sha1.PadLeft(40, '0'));
|
2016-05-02 14:08:50 -07:00
|
|
|
|
|
2016-05-06 12:56:02 -07:00
|
|
|
|
// If we have a rom and it's missing size AND the hashes match a 0-byte file, fill in the rest of the info
|
2016-05-18 15:34:49 -07:00
|
|
|
|
if (subreader.Name == "rom" && (size == 0 || size == -1) && ((crc == CRCZero || crc == "") || md5 == MD5Zero || sha1 == SHA1Zero))
|
2016-05-06 12:56:02 -07:00
|
|
|
|
{
|
2016-05-06 13:12:00 -07:00
|
|
|
|
size = SizeZero;
|
|
|
|
|
|
crc = CRCZero;
|
|
|
|
|
|
md5 = MD5Zero;
|
|
|
|
|
|
sha1 = SHA1Zero;
|
2016-05-06 12:56:02 -07:00
|
|
|
|
}
|
|
|
|
|
|
// If the file has no size and it's not the above case, skip and log
|
2016-05-06 13:12:00 -07:00
|
|
|
|
else if (subreader.Name == "rom" && (size == 0 || size == -1))
|
2016-05-06 12:56:02 -07:00
|
|
|
|
{
|
2016-05-18 20:12:58 -07:00
|
|
|
|
logger.Warning("Incomplete entry for \"" + xtr.GetAttribute("name") + "\" will be output as nodump");
|
2016-05-18 16:59:34 -07:00
|
|
|
|
nodump = true;
|
2016-05-06 12:56:02 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-06 10:59:05 -07:00
|
|
|
|
// Only add the rom if there's useful information in it
|
2016-05-18 16:59:34 -07:00
|
|
|
|
if (!(crc == "" && md5 == "" && sha1 == "") || nodump)
|
2016-04-28 10:37:33 -07:00
|
|
|
|
{
|
2016-05-18 17:06:13 -07:00
|
|
|
|
// If we got to this point and it's a disk, log it because some tools don't like disks
|
|
|
|
|
|
if (xtr.Name == "disk")
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.Log("Disk found: \"" + xtr.GetAttribute("name") + "\"");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-06 10:59:05 -07:00
|
|
|
|
// Get the new values to add
|
2016-05-15 13:45:41 -07:00
|
|
|
|
key = size + "-" + crc;
|
2016-05-11 15:02:33 -07:00
|
|
|
|
|
2016-05-06 10:59:05 -07:00
|
|
|
|
RomData value = new RomData
|
|
|
|
|
|
{
|
|
|
|
|
|
Game = tempname,
|
|
|
|
|
|
Name = xtr.GetAttribute("name"),
|
|
|
|
|
|
Type = xtr.Name,
|
|
|
|
|
|
SystemID = sysid,
|
|
|
|
|
|
SourceID = srcid,
|
|
|
|
|
|
Size = size,
|
|
|
|
|
|
CRC = crc,
|
|
|
|
|
|
MD5 = md5,
|
|
|
|
|
|
SHA1 = sha1,
|
|
|
|
|
|
System = filename,
|
2016-05-18 16:59:34 -07:00
|
|
|
|
Nodump = nodump,
|
2016-05-06 10:59:05 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
2016-05-16 14:28:23 -07:00
|
|
|
|
if (datdata.Roms.ContainsKey(key))
|
2016-05-06 10:59:05 -07:00
|
|
|
|
{
|
2016-05-16 14:28:23 -07:00
|
|
|
|
datdata.Roms[key].Add(value);
|
2016-05-06 10:59:05 -07:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
List<RomData> newvalue = new List<RomData>();
|
|
|
|
|
|
newvalue.Add(value);
|
2016-05-16 14:28:23 -07:00
|
|
|
|
datdata.Roms.Add(key, newvalue);
|
2016-05-06 10:59:05 -07:00
|
|
|
|
}
|
2016-04-28 10:37:33 -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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-16 14:28:23 -07:00
|
|
|
|
return datdata;
|
2016-04-28 10:37:33 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
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>();
|
|
|
|
|
|
|
2016-05-13 16:55:58 -07:00
|
|
|
|
// First sort the roms by size, crc, md5, sha1 (in order), if not sorted already
|
2016-05-13 16:18:50 -07:00
|
|
|
|
if (!presorted)
|
|
|
|
|
|
{
|
|
|
|
|
|
inroms.Sort(delegate (RomData x, RomData y)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (x.Size == y.Size)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (x.CRC == y.CRC)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (x.MD5 == y.MD5)
|
|
|
|
|
|
{
|
|
|
|
|
|
return String.Compare(x.SHA1, y.SHA1);
|
|
|
|
|
|
}
|
|
|
|
|
|
return String.Compare(x.MD5, y.MD5);
|
|
|
|
|
|
}
|
|
|
|
|
|
return String.Compare(x.CRC, y.CRC);
|
|
|
|
|
|
}
|
|
|
|
|
|
return (int)(x.Size - y.Size);
|
2016-04-19 01:11:23 -07:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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
|
2016-05-17 12:55:29 -07:00
|
|
|
|
bool dupefound = false;
|
|
|
|
|
|
RomData savedrom = new RomData();
|
|
|
|
|
|
int pos = -1;
|
|
|
|
|
|
for (int i = 0; i < outroms.Count; i++)
|
2016-04-19 01:11:23 -07:00
|
|
|
|
{
|
2016-05-17 12:55:29 -07:00
|
|
|
|
RomData lastrom = outroms[i];
|
2016-04-19 01:11:23 -07:00
|
|
|
|
|
2016-05-17 12:55:29 -07:00
|
|
|
|
if (rom.Type == "rom" && lastrom.Type == "rom")
|
|
|
|
|
|
{
|
|
|
|
|
|
dupefound = ((rom.Size == lastrom.Size) &&
|
|
|
|
|
|
((rom.CRC == "" || lastrom.CRC == "") || rom.CRC == lastrom.CRC) &&
|
|
|
|
|
|
((rom.MD5 == "" || lastrom.MD5 == "") || rom.MD5 == lastrom.MD5) &&
|
|
|
|
|
|
((rom.SHA1 == "" || lastrom.SHA1 == "") || rom.SHA1 == lastrom.SHA1)
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (rom.Type == "disk" && lastrom.Type == "disk")
|
|
|
|
|
|
{
|
|
|
|
|
|
dupefound = (((rom.MD5 == "" || lastrom.MD5 == "") || rom.MD5 == lastrom.MD5) &&
|
|
|
|
|
|
((rom.SHA1 == "" || lastrom.SHA1 == "") || rom.SHA1 == lastrom.SHA1)
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2016-05-10 20:55:51 -07:00
|
|
|
|
|
2016-05-17 12:55:29 -07:00
|
|
|
|
// If it's a duplicate, skip adding it to the output but add any missing information
|
|
|
|
|
|
if (dupefound)
|
2016-05-10 20:55:51 -07:00
|
|
|
|
{
|
2016-05-17 12:55:29 -07:00
|
|
|
|
savedrom = lastrom;
|
|
|
|
|
|
pos = i;
|
|
|
|
|
|
|
|
|
|
|
|
savedrom.CRC = (savedrom.CRC == "" && rom.CRC != "" ? rom.CRC : savedrom.CRC);
|
|
|
|
|
|
savedrom.MD5 = (savedrom.MD5 == "" && rom.MD5 != "" ? rom.MD5 : savedrom.MD5);
|
|
|
|
|
|
savedrom.SHA1 = (savedrom.SHA1 == "" && rom.SHA1 != "" ? rom.SHA1 : savedrom.SHA1);
|
|
|
|
|
|
|
|
|
|
|
|
// If the duplicate is external already or should be, set it
|
|
|
|
|
|
if (savedrom.Dupe >= DupeType.ExternalHash || savedrom.SystemID != rom.SystemID || savedrom.SourceID != rom.SourceID)
|
2016-05-10 20:55:51 -07:00
|
|
|
|
{
|
2016-05-17 12:55:29 -07:00
|
|
|
|
if (savedrom.Game == rom.Game && savedrom.Name == rom.Name)
|
|
|
|
|
|
{
|
|
|
|
|
|
savedrom.Dupe = DupeType.ExternalAll;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
savedrom.Dupe = DupeType.ExternalHash;
|
|
|
|
|
|
}
|
2016-05-10 20:55:51 -07:00
|
|
|
|
}
|
2016-05-17 12:55:29 -07:00
|
|
|
|
|
|
|
|
|
|
// Otherwise, it's considered an internal dupe
|
2016-05-10 20:55:51 -07:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-05-17 12:55:29 -07:00
|
|
|
|
if (savedrom.Game == rom.Game && savedrom.Name == rom.Name)
|
|
|
|
|
|
{
|
|
|
|
|
|
savedrom.Dupe = DupeType.InternalAll;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
savedrom.Dupe = DupeType.InternalHash;
|
|
|
|
|
|
}
|
2016-05-10 20:55:51 -07:00
|
|
|
|
}
|
2016-05-11 12:08:17 -07:00
|
|
|
|
|
2016-05-17 12:55:29 -07:00
|
|
|
|
// If the current system has a lower ID than the previous, set the system accordingly
|
|
|
|
|
|
if (rom.SystemID < savedrom.SystemID)
|
2016-05-10 20:55:51 -07:00
|
|
|
|
{
|
2016-05-17 12:55:29 -07:00
|
|
|
|
savedrom.SystemID = rom.SystemID;
|
|
|
|
|
|
savedrom.System = rom.System;
|
|
|
|
|
|
savedrom.Game = rom.Game;
|
|
|
|
|
|
savedrom.Name = rom.Name;
|
2016-05-10 20:55:51 -07:00
|
|
|
|
}
|
2016-05-17 12:55:29 -07:00
|
|
|
|
|
|
|
|
|
|
// If the current source has a lower ID than the previous, set the source accordingly
|
|
|
|
|
|
if (rom.SourceID < savedrom.SourceID)
|
2016-05-10 20:55:51 -07:00
|
|
|
|
{
|
2016-05-17 12:55:29 -07:00
|
|
|
|
savedrom.SourceID = rom.SourceID;
|
|
|
|
|
|
savedrom.Source = rom.Source;
|
|
|
|
|
|
savedrom.Game = rom.Game;
|
|
|
|
|
|
savedrom.Name = rom.Name;
|
2016-05-10 20:55:51 -07:00
|
|
|
|
}
|
2016-04-19 01:11:23 -07:00
|
|
|
|
|
2016-05-17 12:55:29 -07:00
|
|
|
|
break;
|
2016-05-17 11:49:09 -07:00
|
|
|
|
}
|
2016-05-17 12:55:29 -07:00
|
|
|
|
}
|
2016-05-17 11:49:09 -07:00
|
|
|
|
|
2016-05-17 12:55:29 -07:00
|
|
|
|
// If no duplicate is found, add it to the list
|
|
|
|
|
|
if (!dupefound)
|
|
|
|
|
|
{
|
|
|
|
|
|
outroms.Add(rom);
|
2016-04-19 01:11:23 -07:00
|
|
|
|
}
|
2016-05-17 12:55:29 -07:00
|
|
|
|
// Otherwise, if a new rom information is found, add that
|
2016-04-19 10:38:01 -07:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-05-17 12:55:29 -07:00
|
|
|
|
outroms.RemoveAt(pos);
|
|
|
|
|
|
outroms.Insert(pos, savedrom);
|
2016-04-19 10:38:01 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
outroms.Add(rom);
|
2016-04-19 01:11:23 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Then return the result
|
|
|
|
|
|
return outroms;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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>
|
2016-04-29 11:49:23 -07:00
|
|
|
|
/// <returns>True if it sorted correctly, false otherwise</returns>
|
|
|
|
|
|
public static bool Sort(List<RomData> roms, bool norename)
|
2016-04-19 01:11:23 -07:00
|
|
|
|
{
|
|
|
|
|
|
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-29 11:49:23 -07:00
|
|
|
|
return true;
|
2016-04-19 01:11:23 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|