2020-12-08 14:53:49 -08:00
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
2023-04-04 18:31:19 -04:00
|
|
|
using System.Xml;
|
|
|
|
|
using System.Xml.Schema;
|
|
|
|
|
using System.Xml.Serialization;
|
2020-12-11 22:52:28 -08:00
|
|
|
using SabreTools.IO;
|
2020-12-08 14:53:49 -08:00
|
|
|
using SabreTools.Logging;
|
|
|
|
|
|
|
|
|
|
namespace SabreTools.Skippers
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Class for matching existing Skippers
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Skippers, in general, are distributed as XML files by some projects
|
|
|
|
|
/// in order to denote a way of transforming a file so that it will match
|
|
|
|
|
/// the hashes included in their DATs. Each skipper file can contain multiple
|
|
|
|
|
/// skipper rules, each of which denote a type of header/transformation. In
|
|
|
|
|
/// turn, each of those rules can contain multiple tests that denote that
|
|
|
|
|
/// a file should be processed using that rule. Transformations can include
|
|
|
|
|
/// simply skipping over a portion of the file all the way to byteswapping
|
|
|
|
|
/// the entire file. For the purposes of this library, Skippers also denote
|
|
|
|
|
/// a way of changing files directly in order to produce a file whose external
|
|
|
|
|
/// hash would match those same DATs.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
public static class SkipperMatch
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
2023-04-04 18:31:19 -04:00
|
|
|
/// Header detectors represented by a list of detector objects
|
2020-12-08 14:53:49 -08:00
|
|
|
/// </summary>
|
2023-04-04 18:31:19 -04:00
|
|
|
private static List<Detector>? Skippers = null;
|
2020-12-08 14:53:49 -08:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Local paths
|
|
|
|
|
/// </summary>
|
2020-12-11 22:52:28 -08:00
|
|
|
private static readonly string LocalPath = Path.Combine(PathTool.GetRuntimeDirectory(), "Skippers") + Path.DirectorySeparatorChar;
|
2020-12-08 14:53:49 -08:00
|
|
|
|
|
|
|
|
#region Logging
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Logging object
|
|
|
|
|
/// </summary>
|
2023-04-04 18:31:19 -04:00
|
|
|
private static readonly Logger logger = new();
|
2020-12-08 14:53:49 -08:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initialize static fields
|
|
|
|
|
/// </summary>
|
2020-12-10 21:29:17 -08:00
|
|
|
/// <param name="experimental">True to enable internal header skipper generation, false to use file-based generation (default)</param>
|
|
|
|
|
public static void Init(bool experimental = false)
|
2020-12-08 14:53:49 -08:00
|
|
|
{
|
2020-12-10 21:29:17 -08:00
|
|
|
// If the list is populated, don't add to it
|
|
|
|
|
if (Skippers != null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// If we're using internal skipper generation
|
|
|
|
|
if (experimental)
|
|
|
|
|
PopulateSkippersInternal();
|
|
|
|
|
|
|
|
|
|
// If we're using file-based skipper generation
|
|
|
|
|
else
|
|
|
|
|
PopulateSkippers();
|
2020-12-08 14:53:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2020-12-10 21:29:17 -08:00
|
|
|
/// Populate the entire list of header skippers from physical files
|
2020-12-08 14:53:49 -08:00
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// http://mamedev.emulab.it/clrmamepro/docs/xmlheaders.txt
|
|
|
|
|
/// http://www.emulab.it/forum/index.php?topic=127.0
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private static void PopulateSkippers()
|
|
|
|
|
{
|
2020-12-09 14:07:46 -08:00
|
|
|
// Ensure the list exists
|
2023-04-04 18:31:19 -04:00
|
|
|
Skippers ??= new List<Detector>();
|
|
|
|
|
|
|
|
|
|
// Create the XML serializer
|
|
|
|
|
var xts = new XmlSerializer(typeof(Detector));
|
2020-12-08 14:53:49 -08:00
|
|
|
|
2020-12-09 14:07:46 -08:00
|
|
|
// Get skippers for each known header type
|
2023-04-04 18:31:19 -04:00
|
|
|
foreach (string skipperPath in Directory.EnumerateFiles(LocalPath, "*", SearchOption.AllDirectories))
|
2020-12-08 14:53:49 -08:00
|
|
|
{
|
2023-04-04 18:31:19 -04:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// Create the XML reader
|
|
|
|
|
var xtr = XmlReader.Create(skipperPath, new XmlReaderSettings
|
|
|
|
|
{
|
|
|
|
|
CheckCharacters = false,
|
|
|
|
|
DtdProcessing = DtdProcessing.Ignore,
|
|
|
|
|
IgnoreComments = true,
|
|
|
|
|
IgnoreWhitespace = true,
|
|
|
|
|
ValidationFlags = XmlSchemaValidationFlags.None,
|
|
|
|
|
ValidationType = ValidationType.None,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Deserialize the detector, if possible
|
2023-04-19 16:39:58 -04:00
|
|
|
if (xts.Deserialize(xtr) is not Detector detector || detector == null)
|
2023-04-04 18:31:19 -04:00
|
|
|
continue;
|
|
|
|
|
|
2023-04-19 16:39:58 -04:00
|
|
|
// Set the source file on the detector
|
2023-04-04 18:31:19 -04:00
|
|
|
string sourceFile = Path.GetFileNameWithoutExtension(skipperPath);
|
|
|
|
|
detector.SourceFile = sourceFile;
|
2023-04-19 16:39:58 -04:00
|
|
|
|
|
|
|
|
// Set the source file on the rules
|
|
|
|
|
if (detector.Rules != null)
|
2023-04-04 18:31:19 -04:00
|
|
|
{
|
2023-04-19 16:39:58 -04:00
|
|
|
for (int i = 0; i < detector.Rules.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
if (detector.Rules[i] == null)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
detector.Rules[i].SourceFile = sourceFile;
|
|
|
|
|
}
|
2023-04-04 18:31:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add the skipper to the set
|
|
|
|
|
Skippers.Add(detector);
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
2020-12-08 14:53:49 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-09 14:07:46 -08:00
|
|
|
/// <summary>
|
2020-12-10 21:29:17 -08:00
|
|
|
/// Populate the entire list of header skippers from generated objects
|
2020-12-09 14:07:46 -08:00
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// http://mamedev.emulab.it/clrmamepro/docs/xmlheaders.txt
|
|
|
|
|
/// http://www.emulab.it/forum/index.php?topic=127.0
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private static void PopulateSkippersInternal()
|
|
|
|
|
{
|
|
|
|
|
// Ensure the list exists
|
2023-04-04 18:31:19 -04:00
|
|
|
Skippers ??= new List<Detector>();
|
2020-12-09 14:07:46 -08:00
|
|
|
|
|
|
|
|
// Get skippers for each known header type
|
2023-04-04 18:31:19 -04:00
|
|
|
Skippers.Add(new Detectors.Atari7800());
|
|
|
|
|
Skippers.Add(new Detectors.AtariLynx());
|
|
|
|
|
Skippers.Add(new Detectors.CommodorePSID());
|
|
|
|
|
Skippers.Add(new Detectors.NECPCEngine());
|
|
|
|
|
Skippers.Add(new Detectors.Nintendo64());
|
|
|
|
|
Skippers.Add(new Detectors.NintendoEntertainmentSystem());
|
|
|
|
|
Skippers.Add(new Detectors.NintendoFamicomDiskSystem());
|
|
|
|
|
Skippers.Add(new Detectors.SuperNintendoEntertainmentSystem());
|
|
|
|
|
Skippers.Add(new Detectors.SuperFamicomSPC());
|
2020-12-09 14:07:46 -08:00
|
|
|
}
|
|
|
|
|
|
2020-12-08 14:53:49 -08:00
|
|
|
/// <summary>
|
2023-04-04 18:31:19 -04:00
|
|
|
/// Get the Rule associated with a given file
|
2020-12-08 14:53:49 -08:00
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="input">Name of the file to be checked</param>
|
|
|
|
|
/// <param name="skipperName">Name of the skipper to be used, blank to find a matching skipper</param>
|
|
|
|
|
/// <param name="logger">Logger object for file and console output</param>
|
2023-04-04 18:31:19 -04:00
|
|
|
/// <returns>The Rule that matched the file</returns>
|
|
|
|
|
public static Rule GetMatchingRule(string input, string skipperName)
|
2020-12-08 14:53:49 -08:00
|
|
|
{
|
|
|
|
|
// If the file doesn't exist, return a blank skipper rule
|
|
|
|
|
if (!File.Exists(input))
|
|
|
|
|
{
|
|
|
|
|
logger.Error($"The file '{input}' does not exist so it cannot be tested");
|
2023-04-04 18:31:19 -04:00
|
|
|
return new Rule();
|
2020-12-08 14:53:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return GetMatchingRule(File.OpenRead(input), skipperName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2023-04-04 18:31:19 -04:00
|
|
|
/// Get the Rule associated with a given stream
|
2020-12-08 14:53:49 -08:00
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="input">Name of the file to be checked</param>
|
|
|
|
|
/// <param name="skipperName">Name of the skipper to be used, blank to find a matching skipper</param>
|
|
|
|
|
/// <param name="keepOpen">True if the underlying stream should be kept open, false otherwise</param>
|
2023-04-04 18:31:19 -04:00
|
|
|
/// <returns>The Rule that matched the file</returns>
|
|
|
|
|
public static Rule GetMatchingRule(Stream input, string skipperName, bool keepOpen = false)
|
2020-12-08 14:53:49 -08:00
|
|
|
{
|
2023-04-04 18:31:19 -04:00
|
|
|
var skipperRule = new Rule();
|
2020-12-08 14:53:49 -08:00
|
|
|
|
2023-04-04 18:31:19 -04:00
|
|
|
// If we have an invalid set of skippers or skipper name
|
|
|
|
|
if (Skippers == null || skipperName == null)
|
2020-12-08 14:53:49 -08:00
|
|
|
return skipperRule;
|
|
|
|
|
|
|
|
|
|
// Loop through and find a Skipper that has the right name
|
|
|
|
|
logger.Verbose("Beginning search for matching header skip rules");
|
|
|
|
|
|
2023-04-04 18:31:19 -04:00
|
|
|
// Loop through all known Detectors
|
|
|
|
|
foreach (Detector? skipper in Skippers)
|
2020-12-08 14:53:49 -08:00
|
|
|
{
|
2023-04-04 18:31:19 -04:00
|
|
|
// This should not happen
|
|
|
|
|
if (skipper == null)
|
|
|
|
|
continue;
|
|
|
|
|
|
2020-12-08 14:53:49 -08:00
|
|
|
skipperRule = skipper.GetMatchingRule(input, skipperName);
|
|
|
|
|
if (skipperRule != null)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we're not keeping the stream open, dispose of the binary reader
|
|
|
|
|
if (!keepOpen)
|
|
|
|
|
input.Dispose();
|
|
|
|
|
|
2023-04-04 18:31:19 -04:00
|
|
|
// If the Rule is null, make it empty
|
|
|
|
|
skipperRule ??= new Rule();
|
2020-12-08 14:53:49 -08:00
|
|
|
|
|
|
|
|
// If we have a blank rule, inform the user
|
|
|
|
|
if (skipperRule.Tests == null)
|
|
|
|
|
logger.Verbose("No matching rule found!");
|
|
|
|
|
else
|
|
|
|
|
logger.User("Matching rule found!");
|
|
|
|
|
|
|
|
|
|
return skipperRule;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|