mirror of
https://github.com/claunia/SabreTools.git
synced 2025-12-16 19:14:27 +00:00
Use Nuget package for Skippers
This commit is contained in:
@@ -29,11 +29,11 @@
|
||||
<ProjectReference Include="..\SabreTools.Filtering\SabreTools.Filtering.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.Logging\SabreTools.Logging.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.Reports\SabreTools.Reports.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.Skippers\SabreTools.Skippers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.IO" Version="1.3.0" />
|
||||
<PackageReference Include="SabreTools.Skippers" Version="1.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
<ProjectReference Include="..\NaturalSort\NaturalSort.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.Core\SabreTools.Core.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.Logging\SabreTools.Logging.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.Skippers\SabreTools.Skippers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
@@ -36,6 +35,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.IO" Version="1.3.0" />
|
||||
<PackageReference Include="SabreTools.Skippers" Version="1.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace SabreTools.Skippers
|
||||
{
|
||||
[XmlRoot("detector")]
|
||||
public abstract class Detector
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Detector name
|
||||
/// </summary>
|
||||
[XmlElement("name")]
|
||||
public string? Name { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Author names
|
||||
/// </summary>
|
||||
[XmlElement("author")]
|
||||
public string? Author { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// File version
|
||||
/// </summary>
|
||||
[XmlElement("version")]
|
||||
public string? Version { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set of all rules in the skipper
|
||||
/// </summary>
|
||||
[XmlElement("rule")]
|
||||
public Rule[]? Rules { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Filename the skipper lives in
|
||||
/// </summary>
|
||||
[XmlIgnore]
|
||||
public string? SourceFile { get; protected set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Matching
|
||||
|
||||
/// <summary>
|
||||
/// Get the Rule associated with a given stream
|
||||
/// </summary>
|
||||
/// <param name="input">Stream to be checked</param>
|
||||
/// <param name="skipperName">Name of the skipper to be used, blank to find a matching skipper</param>
|
||||
/// <returns>The Rule that matched the stream, null otherwise</returns>
|
||||
public Rule? GetMatchingRule(Stream input, string skipperName)
|
||||
{
|
||||
// If we have no name supplied, try to blindly match
|
||||
if (string.IsNullOrEmpty(skipperName))
|
||||
return GetMatchingRule(input);
|
||||
|
||||
// If the name matches the internal name of the skipper
|
||||
else if (string.Equals(skipperName, Name, StringComparison.OrdinalIgnoreCase))
|
||||
return GetMatchingRule(input);
|
||||
|
||||
// If the name matches the source file name of the skipper
|
||||
else if (string.Equals(skipperName, SourceFile, StringComparison.OrdinalIgnoreCase))
|
||||
return GetMatchingRule(input);
|
||||
|
||||
// Otherwise, nothing matches by default
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the matching Rule from all Rules, if possible
|
||||
/// </summary>
|
||||
/// <param name="input">Stream to be checked</param>
|
||||
/// <returns>The Rule that matched the stream, null otherwise</returns>
|
||||
private Rule? GetMatchingRule(Stream input)
|
||||
{
|
||||
// If we have no rules
|
||||
if (Rules == null)
|
||||
return null;
|
||||
|
||||
// Loop through the rules until one is found that works
|
||||
foreach (Rule rule in Rules)
|
||||
{
|
||||
// Always reset the stream back to the original place
|
||||
input.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// If all tests in the rule pass
|
||||
if (rule.PassesAllTests(input))
|
||||
return rule;
|
||||
}
|
||||
|
||||
// If nothing passed
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using SabreTools.Skippers.Tests;
|
||||
|
||||
namespace SabreTools.Skippers.Detectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Detector for Atari 7800 headers
|
||||
/// </summary>
|
||||
/// <remarks>Originally from a7800.xml</remarks>
|
||||
internal class Atari7800 : Detector
|
||||
{
|
||||
public Atari7800()
|
||||
{
|
||||
// Create tests
|
||||
var rule1Test1 = new DataTest("1", "415441524937383030", true);
|
||||
var rule2Test1 = new DataTest("64", "41435455414C20434152542044415441205354415254532048455245", true);
|
||||
|
||||
// Create rules
|
||||
var rule1 = new Rule("80", "EOF", HeaderSkipOperation.None, [rule1Test1], "a7800");
|
||||
var rule2 = new Rule("80", "EOF", HeaderSkipOperation.None, [rule2Test1], "a7800");
|
||||
|
||||
// Create file
|
||||
Name = "Atari 7800";
|
||||
Author = "Roman Scherzer";
|
||||
Version = "1.0";
|
||||
SourceFile = "a7800";
|
||||
Rules =
|
||||
[
|
||||
rule1,
|
||||
rule2,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using SabreTools.Skippers.Tests;
|
||||
|
||||
namespace SabreTools.Skippers.Detectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Detector for Atari Lynx headers
|
||||
/// </summary>
|
||||
/// <remarks>Originally from lynx.xml</remarks>
|
||||
internal class AtariLynx : Detector
|
||||
{
|
||||
public AtariLynx()
|
||||
{
|
||||
// Create tests
|
||||
var rule1Test1 = new DataTest("0", "4C594E58", true);
|
||||
var rule2Test1 = new DataTest("6", "425339", true);
|
||||
|
||||
// Create rules
|
||||
var rule1 = new Rule("40", "EOF", HeaderSkipOperation.None, [rule1Test1], "lynx");
|
||||
var rule2 = new Rule("40", "EOF", HeaderSkipOperation.None, [rule2Test1], "lynx");
|
||||
|
||||
// Create file
|
||||
Name = "Atari Lynx";
|
||||
Author = "Roman Scherzer";
|
||||
Version = "1.0";
|
||||
SourceFile = "lynx";
|
||||
Rules =
|
||||
[
|
||||
rule1,
|
||||
rule2,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using SabreTools.Skippers.Tests;
|
||||
|
||||
namespace SabreTools.Skippers.Detectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Detector for Commodore PSID headers
|
||||
/// </summary>
|
||||
/// <remarks>Originally from psid.xml</remarks>
|
||||
internal class CommodorePSID : Detector
|
||||
{
|
||||
public CommodorePSID()
|
||||
{
|
||||
// Create tests
|
||||
var rule1Test1 = new DataTest("0", "5053494400010076", true);
|
||||
var rule2Test1 = new DataTest("0", "505349440003007c", true);
|
||||
var rule3Test1 = new DataTest("0", "505349440002007c", true);
|
||||
var rule4Test1 = new DataTest("0", "505349440001007c", true);
|
||||
var rule5Test1 = new DataTest("0", "525349440002007c", true);
|
||||
|
||||
// Create rules
|
||||
var rule1 = new Rule("76", "EOF", HeaderSkipOperation.None, [rule1Test1], "psid");
|
||||
var rule2 = new Rule("76", "EOF", HeaderSkipOperation.None, [rule2Test1], "psid");
|
||||
var rule3 = new Rule("7c", "EOF", HeaderSkipOperation.None, [rule3Test1], "psid");
|
||||
var rule4 = new Rule("7c", "EOF", HeaderSkipOperation.None, [rule4Test1], "psid");
|
||||
var rule5 = new Rule("7c", "EOF", HeaderSkipOperation.None, [rule5Test1], "psid");
|
||||
|
||||
// Create file
|
||||
Name = "psid";
|
||||
Author = "Yori Yoshizuki";
|
||||
Version = "1.2";
|
||||
SourceFile = "psid";
|
||||
Rules =
|
||||
[
|
||||
rule1,
|
||||
rule2,
|
||||
rule3,
|
||||
rule4,
|
||||
rule5,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using SabreTools.Skippers.Tests;
|
||||
|
||||
namespace SabreTools.Skippers.Detectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Detector for NEC PC-Engine / TurboGrafx 16 headers
|
||||
/// </summary>
|
||||
/// <remarks>Originally from pce.xml</remarks>
|
||||
internal class NECPCEngine : Detector
|
||||
{
|
||||
public NECPCEngine()
|
||||
{
|
||||
// Create tests
|
||||
var rule1Test1 = new DataTest("0", "4000000000000000AABB02", true);
|
||||
|
||||
// Create rules
|
||||
var rule1 = new Rule("200", null, HeaderSkipOperation.None, [rule1Test1], "pce");
|
||||
|
||||
// Create file
|
||||
Name = "NEC TurboGrafx-16/PC-Engine";
|
||||
Author = "Matt Nadareski (darksabre76)";
|
||||
Version = "1.0";
|
||||
SourceFile = "pce";
|
||||
Rules =
|
||||
[
|
||||
rule1,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using SabreTools.Skippers.Tests;
|
||||
|
||||
namespace SabreTools.Skippers.Detectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Detector for Nintendo 64 headers
|
||||
/// </summary>
|
||||
/// <remarks>Originally from n64.xml</remarks>
|
||||
internal class Nintendo64 : Detector
|
||||
{
|
||||
public Nintendo64()
|
||||
{
|
||||
// Create tests
|
||||
var v64Test = new DataTest("0", "80371240", true);
|
||||
var z64Test = new DataTest("0", "37804012", true);
|
||||
var n64Test = new DataTest("0", "40123780", true);
|
||||
|
||||
// Create rules
|
||||
var v64Rule = new Rule("0", "EOF", HeaderSkipOperation.None, [v64Test], "n64");
|
||||
var z64Rule = new Rule("0", "EOF", HeaderSkipOperation.Byteswap, [z64Test], "n64");
|
||||
var n64Rule = new Rule("0", "EOF", HeaderSkipOperation.Wordswap, [n64Test], "n64");
|
||||
|
||||
// Create file
|
||||
Name = "Nintendo 64 - ABCD";
|
||||
Author = "CUE";
|
||||
Version = "1.1";
|
||||
SourceFile = "n64";
|
||||
Rules =
|
||||
[
|
||||
v64Rule,
|
||||
z64Rule,
|
||||
n64Rule,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using SabreTools.Skippers.Tests;
|
||||
|
||||
namespace SabreTools.Skippers.Detectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Detector for Nintendo Entertainment System headers
|
||||
/// </summary>
|
||||
/// <remarks>Originally from nes.xml</remarks>
|
||||
internal class NintendoEntertainmentSystem : Detector
|
||||
{
|
||||
public NintendoEntertainmentSystem()
|
||||
{
|
||||
// Create tests
|
||||
var inesTest = new DataTest("0", "4E45531A", true);
|
||||
|
||||
// Create rules
|
||||
var inesRule = new Rule("10", "EOF", HeaderSkipOperation.None, [inesTest], "nes");
|
||||
|
||||
// Create file
|
||||
Name = "Nintendo Famicon/NES";
|
||||
Author = "Roman Scherzer";
|
||||
Version = "1.1";
|
||||
SourceFile = "nes";
|
||||
Rules =
|
||||
[
|
||||
inesRule,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using SabreTools.Skippers.Tests;
|
||||
|
||||
namespace SabreTools.Skippers.Detectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Detector for Nintendo Famicom Disk System headers
|
||||
/// </summary>
|
||||
/// <remarks>Originally from fds.xml</remarks>
|
||||
internal class NintendoFamicomDiskSystem : Detector
|
||||
{
|
||||
public NintendoFamicomDiskSystem()
|
||||
{
|
||||
// Create tests
|
||||
var rule1Test1 = new DataTest("0", "4644531A010000000000000000000000", true);
|
||||
var rule2Test1 = new DataTest("0", "4644531A020000000000000000000000", true);
|
||||
var rule3Test1 = new DataTest("0", "4644531A030000000000000000000000", true);
|
||||
var rule4Test1 = new DataTest("0", "4644531A040000000000000000000000", true);
|
||||
|
||||
// Create rules
|
||||
var rule1 = new Rule("10", null, HeaderSkipOperation.None, [rule1Test1], "fds");
|
||||
var rule2 = new Rule("10", null, HeaderSkipOperation.None, [rule2Test1], "fds");
|
||||
var rule3 = new Rule("10", null, HeaderSkipOperation.None, [rule3Test1], "fds");
|
||||
var rule4 = new Rule("10", null, HeaderSkipOperation.None, [rule4Test1], "fds");
|
||||
|
||||
// Create file
|
||||
Name = "fds";
|
||||
Author = "Yori Yoshizuki";
|
||||
Version = "1.0";
|
||||
SourceFile = "fds";
|
||||
Rules =
|
||||
[
|
||||
rule1,
|
||||
rule2,
|
||||
rule3,
|
||||
rule4,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using SabreTools.Skippers.Tests;
|
||||
|
||||
namespace SabreTools.Skippers.Detectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Detector for Super Famicom SPC headers
|
||||
/// </summary>
|
||||
/// <remarks>Originally from spc.xml</remarks>
|
||||
internal class SuperFamicomSPC : Detector
|
||||
{
|
||||
public SuperFamicomSPC()
|
||||
{
|
||||
// Create tests
|
||||
var rule1Test1 = new DataTest("0", "534E45532D535043", true);
|
||||
|
||||
// Create rules
|
||||
var rule1 = new Rule("00100", "EOF", HeaderSkipOperation.None, [rule1Test1], "spc");
|
||||
|
||||
// Create file
|
||||
Name = "Nintendo Super Famicon SPC";
|
||||
Author = "Yori Yoshizuki";
|
||||
Version = "1.0";
|
||||
SourceFile = "spc";
|
||||
Rules =
|
||||
[
|
||||
rule1,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using SabreTools.Skippers.Tests;
|
||||
|
||||
namespace SabreTools.Skippers.Detectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Detector for Super Nintendo Entertainment System headers
|
||||
/// </summary>
|
||||
/// <remarks>Originally from snes.xml</remarks>
|
||||
internal class SuperNintendoEntertainmentSystem : Detector
|
||||
{
|
||||
public SuperNintendoEntertainmentSystem()
|
||||
{
|
||||
// Create tests
|
||||
var figTest = new DataTest("16", "0000000000000000", true);
|
||||
var smcTest = new DataTest("16", "AABB040000000000", true);
|
||||
var ufoTest = new DataTest("16", "535550455255464F", true);
|
||||
|
||||
// Create rules
|
||||
var figRule = new Rule("200", null, HeaderSkipOperation.None, [figTest], "snes");
|
||||
var smcRule = new Rule("200", null, HeaderSkipOperation.None, [smcTest], "snes");
|
||||
var ufoRule = new Rule("200", null, HeaderSkipOperation.None, [ufoTest], "snes");
|
||||
|
||||
// Create file
|
||||
Name = "Nintendo Super Famicom/SNES";
|
||||
Author = "Matt Nadareski (darksabre76)";
|
||||
Version = "1.0";
|
||||
SourceFile = "snes";
|
||||
Rules =
|
||||
[
|
||||
figRule,
|
||||
smcRule,
|
||||
ufoRule,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace SabreTools.Skippers
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines the header skip operation
|
||||
/// </summary>
|
||||
public enum HeaderSkipOperation
|
||||
{
|
||||
[XmlEnum("none")]
|
||||
None = 0,
|
||||
|
||||
[XmlEnum("bitswap")]
|
||||
Bitswap,
|
||||
|
||||
[XmlEnum("byteswap")]
|
||||
Byteswap,
|
||||
|
||||
[XmlEnum("wordswap")]
|
||||
Wordswap,
|
||||
|
||||
[XmlEnum("wordbyteswap")]
|
||||
WordByteswap,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the operator to be used in a file test
|
||||
/// </summary>
|
||||
public enum HeaderSkipTestFileOperator
|
||||
{
|
||||
[XmlEnum("equal")]
|
||||
Equal = 0,
|
||||
|
||||
[XmlEnum("less")]
|
||||
Less,
|
||||
|
||||
[XmlEnum("greater")]
|
||||
Greater,
|
||||
}
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml.Serialization;
|
||||
using SabreTools.Skippers.Tests;
|
||||
|
||||
namespace SabreTools.Skippers
|
||||
{
|
||||
[XmlType("rule")]
|
||||
public class Rule
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Starting offset for applying rule
|
||||
/// </summary>
|
||||
/// <remarks>Either numeric or the literal "EOF"</remarks>
|
||||
[XmlAttribute("start_offset")]
|
||||
public string? StartOffset
|
||||
{
|
||||
get => _startOffset == null ? "EOF" : _startOffset.Value.ToString();
|
||||
set
|
||||
{
|
||||
if (value == null || value.Equals("eof", StringComparison.InvariantCultureIgnoreCase))
|
||||
_startOffset = null;
|
||||
else
|
||||
_startOffset = Convert.ToInt64(value, fromBase: 16);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ending offset for applying rule
|
||||
/// </summary>
|
||||
/// <remarks>Either numeric or the literal "EOF"</remarks>
|
||||
[XmlAttribute("end_offset")]
|
||||
public string? EndOffset
|
||||
{
|
||||
get => _endOffset == null ? "EOF" : _endOffset.Value.ToString();
|
||||
set
|
||||
{
|
||||
if (value == null || value.Equals("eof", StringComparison.InvariantCultureIgnoreCase))
|
||||
_endOffset = null;
|
||||
else
|
||||
_endOffset = Convert.ToInt64(value, fromBase: 16);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Byte manipulation operation
|
||||
/// </summary>
|
||||
[XmlAttribute("operation")]
|
||||
public HeaderSkipOperation Operation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of matching tests in a rule
|
||||
/// </summary>
|
||||
[XmlElement("and", typeof(AndTest))]
|
||||
[XmlElement("data", typeof(DataTest))]
|
||||
[XmlElement("file", typeof(FileTest))]
|
||||
[XmlElement("or", typeof(OrTest))]
|
||||
[XmlElement("xor", typeof(XorTest))]
|
||||
public Test[]? Tests { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Filename the skipper rule lives in
|
||||
/// </summary>
|
||||
[XmlIgnore]
|
||||
public string? SourceFile { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private instance variables
|
||||
|
||||
/// <summary>
|
||||
/// Starting offset for applying rule
|
||||
/// </summary>
|
||||
/// <remarks>null is EOF</remarks>
|
||||
private long? _startOffset = null;
|
||||
|
||||
/// <summary>
|
||||
/// Ending offset for applying rule
|
||||
/// </summary>
|
||||
/// <remarks>null is EOF</remarks>
|
||||
private long? _endOffset = null;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public Rule(string? startOffset, string? endOffset, HeaderSkipOperation operation, Test[]? tests, string? sourceFile)
|
||||
{
|
||||
StartOffset = startOffset;
|
||||
EndOffset = endOffset;
|
||||
Operation = operation;
|
||||
Tests = tests;
|
||||
SourceFile = sourceFile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a Stream passes all tests in the Rule
|
||||
/// </summary>
|
||||
/// <param name="input">Stream to check</param>
|
||||
/// <returns>True if all tests passed, false otherwise</returns>
|
||||
public bool PassesAllTests(Stream input)
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
// If there are no tests
|
||||
if (Tests == null || Tests.Length == 0)
|
||||
return success;
|
||||
|
||||
foreach (Test test in Tests)
|
||||
{
|
||||
bool result = test.Passes(input);
|
||||
success &= result;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform an input file using the given rule
|
||||
/// </summary>
|
||||
/// <param name="input">Input file name</param>
|
||||
/// <param name="output">Output file name</param>
|
||||
/// <returns>True if the file was transformed properly, false otherwise</returns>
|
||||
public bool TransformFile(string input, string output)
|
||||
{
|
||||
// If the input file doesn't exist
|
||||
if (string.IsNullOrEmpty(input) || !File.Exists(input))
|
||||
return false;
|
||||
|
||||
// If we have an invalid output directory name
|
||||
if (string.IsNullOrEmpty(output))
|
||||
return false;
|
||||
|
||||
// Create the output directory if it doesn't already
|
||||
string parentDirectory = Path.GetDirectoryName(output) ?? string.Empty;
|
||||
Directory.CreateDirectory(parentDirectory);
|
||||
|
||||
//logger.User($"Attempting to apply rule to '{input}'");
|
||||
bool success = TransformStream(File.OpenRead(input), File.Create(output));
|
||||
|
||||
// If the output file has size 0, delete it
|
||||
if (new FileInfo(output).Length == 0)
|
||||
{
|
||||
File.Delete(output);
|
||||
success = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform an input stream using the given rule
|
||||
/// </summary>
|
||||
/// <param name="input">Input stream</param>
|
||||
/// <param name="output">Output stream</param>
|
||||
/// <param name="keepReadOpen">True if the underlying read stream should be kept open, false otherwise</param>
|
||||
/// <param name="keepWriteOpen">True if the underlying write stream should be kept open, false otherwise</param>
|
||||
/// <returns>True if the file was transformed properly, false otherwise</returns>
|
||||
public bool TransformStream(Stream? input, Stream output, bool keepReadOpen = false, bool keepWriteOpen = false)
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
// If the input stream isn't valid
|
||||
if (input == null || !input.CanRead)
|
||||
return false;
|
||||
|
||||
// If the sizes are wrong for the values, fail
|
||||
long extsize = input.Length;
|
||||
if ((Operation > HeaderSkipOperation.Bitswap && (extsize % 2) != 0)
|
||||
|| (Operation > HeaderSkipOperation.Byteswap && (extsize % 4) != 0)
|
||||
|| (Operation > HeaderSkipOperation.Bitswap && (_startOffset == null || _startOffset % 2 != 0)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now read the proper part of the file and apply the rule
|
||||
BinaryWriter? bw = null;
|
||||
BinaryReader? br = null;
|
||||
try
|
||||
{
|
||||
bw = new BinaryWriter(output);
|
||||
br = new BinaryReader(input);
|
||||
|
||||
// Seek to the beginning offset
|
||||
if (_startOffset == null)
|
||||
success = false;
|
||||
|
||||
else if (Math.Abs((long)_startOffset) > input.Length)
|
||||
success = false;
|
||||
|
||||
else if (_startOffset > 0)
|
||||
input.Seek((long)_startOffset, SeekOrigin.Begin);
|
||||
|
||||
else if (_startOffset < 0)
|
||||
input.Seek((long)_startOffset, SeekOrigin.End);
|
||||
|
||||
// Then read and apply the operation as you go
|
||||
if (success)
|
||||
{
|
||||
byte[] buffer = new byte[4];
|
||||
int pos = 0;
|
||||
while (input.Position < (_endOffset ?? input.Length)
|
||||
&& input.Position < input.Length)
|
||||
{
|
||||
byte b = br.ReadByte();
|
||||
switch (Operation)
|
||||
{
|
||||
case HeaderSkipOperation.Bitswap:
|
||||
// http://stackoverflow.com/questions/3587826/is-there-a-built-in-function-to-reverse-bit-order
|
||||
uint r = b;
|
||||
int s = 7;
|
||||
for (b >>= 1; b != 0; b >>= 1)
|
||||
{
|
||||
r <<= 1;
|
||||
r |= (byte)(b & 1);
|
||||
s--;
|
||||
}
|
||||
r <<= s;
|
||||
buffer[pos] = (byte)r;
|
||||
break;
|
||||
|
||||
case HeaderSkipOperation.Byteswap:
|
||||
if (pos % 2 == 1)
|
||||
buffer[pos - 1] = b;
|
||||
else
|
||||
buffer[pos + 1] = b;
|
||||
|
||||
break;
|
||||
|
||||
case HeaderSkipOperation.Wordswap:
|
||||
buffer[3 - pos] = b;
|
||||
break;
|
||||
|
||||
case HeaderSkipOperation.WordByteswap:
|
||||
buffer[(pos + 2) % 4] = b;
|
||||
break;
|
||||
|
||||
case HeaderSkipOperation.None:
|
||||
default:
|
||||
buffer[pos] = b;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the buffer position to default write to
|
||||
pos = (pos + 1) % 4;
|
||||
|
||||
// If we filled a buffer, flush to the stream
|
||||
if (pos == 0)
|
||||
{
|
||||
bw.Write(buffer);
|
||||
bw.Flush();
|
||||
buffer = new byte[4];
|
||||
}
|
||||
}
|
||||
|
||||
// If there's anything more in the buffer, write only the left bits
|
||||
for (int i = 0; i < pos; i++)
|
||||
{
|
||||
bw.Write(buffer[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
#if NET40_OR_GREATER
|
||||
// If we're not keeping the read stream open, dispose of the binary reader
|
||||
if (!keepReadOpen)
|
||||
br?.Dispose();
|
||||
|
||||
// If we're not keeping the write stream open, dispose of the binary reader
|
||||
if (!keepWriteOpen)
|
||||
bw?.Dispose();
|
||||
#endif
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>1.1.2</Version>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Copyright>Copyright (c)2016-2024 Matt Nadareski</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/SabreTools/SabreTools</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,121 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
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>
|
||||
/// Header detectors represented by a list of detector objects
|
||||
/// </summary>
|
||||
private static List<Detector>? Skippers = null;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize static fields
|
||||
/// </summary>
|
||||
public static void Init()
|
||||
{
|
||||
// If the list is populated, don't add to it
|
||||
if (Skippers != null)
|
||||
return;
|
||||
|
||||
// Generate header skippers internally
|
||||
PopulateSkippers();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate the entire list of header skippers from generated objects
|
||||
/// </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()
|
||||
{
|
||||
// Ensure the list exists
|
||||
Skippers ??= [];
|
||||
|
||||
// Get skippers for each known header type
|
||||
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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Rule associated with a given file
|
||||
/// </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>
|
||||
/// <returns>The Rule that matched the file</returns>
|
||||
public static Rule GetMatchingRule(string input, string skipperName)
|
||||
{
|
||||
// If the file doesn't exist, return a blank skipper rule
|
||||
if (!File.Exists(input))
|
||||
return new Rule(null, null, HeaderSkipOperation.None, null, null);
|
||||
|
||||
return GetMatchingRule(File.OpenRead(input), skipperName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Rule associated with a given stream
|
||||
/// </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>
|
||||
/// <returns>The Rule that matched the file</returns>
|
||||
public static Rule GetMatchingRule(Stream? input, string skipperName, bool keepOpen = false)
|
||||
{
|
||||
var skipperRule = new Rule(null, null, HeaderSkipOperation.None, null, null);
|
||||
|
||||
// If we have an invalid input
|
||||
if (input == null || !input.CanRead)
|
||||
return skipperRule;
|
||||
|
||||
// If we have an invalid set of skippers or skipper name
|
||||
if (Skippers == null || skipperName == null)
|
||||
return skipperRule;
|
||||
|
||||
// Loop through all known Detectors
|
||||
foreach (Detector? skipper in Skippers)
|
||||
{
|
||||
// This should not happen
|
||||
if (skipper == null)
|
||||
continue;
|
||||
|
||||
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();
|
||||
|
||||
// If the Rule is null, make it empty
|
||||
skipperRule ??= new Rule(null, null, HeaderSkipOperation.None, null, null);
|
||||
return skipperRule;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.Skippers
|
||||
{
|
||||
/// <summary>
|
||||
/// Individual test that applies to a Rule
|
||||
/// </summary>
|
||||
public abstract class Test
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if a stream passes the test
|
||||
/// </summary>
|
||||
/// <param name="input">Stream to check rule against</param>
|
||||
/// <remarks>The Stream is assumed to be in the proper position for a given test</remarks>
|
||||
public abstract bool Passes(Stream input);
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Prase a hex string into a byte array
|
||||
/// </summary>
|
||||
/// <see href="http://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array"/>
|
||||
protected static byte[]? ParseByteArrayFromHex(string? hex)
|
||||
{
|
||||
// If we have an invalid string
|
||||
if (string.IsNullOrEmpty(hex))
|
||||
return null;
|
||||
|
||||
var ret = new byte[hex!.Length / 2];
|
||||
for (int index = 0; index < ret.Length; index++)
|
||||
{
|
||||
string byteValue = hex.Substring(index * 2, 2);
|
||||
ret[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seek an input stream based on the test value
|
||||
/// </summary>
|
||||
/// <param name="input">Stream to seek</param>
|
||||
/// <param name="offset">Offset to seek to</param>
|
||||
/// <returns>True if the stream could seek, false on error</returns>
|
||||
protected static bool Seek(Stream input, long? offset)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Null offset means EOF
|
||||
if (offset == null)
|
||||
input.Seek(0, SeekOrigin.End);
|
||||
|
||||
// Positive offset means from beginning
|
||||
else if (offset >= 0 && offset <= input.Length)
|
||||
input.Seek(offset.Value, SeekOrigin.Begin);
|
||||
|
||||
// Negative offset means from end
|
||||
else if (offset < 0 && Math.Abs(offset.Value) <= input.Length)
|
||||
input.Seek(offset.Value, SeekOrigin.End);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace SabreTools.Skippers.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test that uses a byte mask AND against data
|
||||
/// </summary>
|
||||
[XmlType("and")]
|
||||
public class AndTest : Test
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// File offset to run the test
|
||||
/// </summary>
|
||||
/// <remarks>Either numeric or the literal "EOF"</remarks>
|
||||
[XmlAttribute("offset")]
|
||||
public string? Offset
|
||||
{
|
||||
get => _offset == null ? "EOF" : _offset.Value.ToString();
|
||||
set
|
||||
{
|
||||
if (value == null || value.ToLowerInvariant() == "eof")
|
||||
_offset = null;
|
||||
else
|
||||
_offset = Convert.ToInt64(value, fromBase: 16);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static value to be checked at the offset
|
||||
/// </summary>
|
||||
/// <remarks>Hex string representation of a byte array</remarks>
|
||||
[XmlAttribute("value")]
|
||||
public string? Value
|
||||
{
|
||||
get => _value == null ? string.Empty : BitConverter.ToString(_value).Replace("-", string.Empty);
|
||||
set => _value = ParseByteArrayFromHex(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a pass or failure is expected
|
||||
/// </summary>
|
||||
[XmlAttribute("result")]
|
||||
public bool Result { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Byte mask to be applied to the tested bytes
|
||||
/// </summary>
|
||||
/// <remarks>Hex string representation of a byte array</remarks>
|
||||
[XmlAttribute("mask")]
|
||||
public string? Mask
|
||||
{
|
||||
get => _mask == null ? string.Empty : BitConverter.ToString(_mask).Replace("-", string.Empty);
|
||||
set => _mask = ParseByteArrayFromHex(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private instance variables
|
||||
|
||||
/// <summary>
|
||||
/// File offset to run the test
|
||||
/// </summary>
|
||||
/// <remarks>null is EOF</remarks>
|
||||
private long? _offset;
|
||||
|
||||
/// <summary>
|
||||
/// Static value to be checked at the offset
|
||||
/// </summary>
|
||||
private byte[]? _value;
|
||||
|
||||
/// <summary>
|
||||
/// Byte mask to be applied to the tested bytes
|
||||
/// </summary>
|
||||
private byte[]? _mask;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public AndTest(string? offset, string? value, bool result, string? mask)
|
||||
{
|
||||
Offset = offset;
|
||||
Value = value;
|
||||
Result = result;
|
||||
Mask = mask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Passes(Stream input)
|
||||
{
|
||||
// If we have an invalid mask
|
||||
if (_mask == null || _mask.Length == 0)
|
||||
return false;
|
||||
|
||||
// If we have an invalid value
|
||||
if (_value == null || _value.Length == 0)
|
||||
return false;
|
||||
|
||||
// Seek to the correct position, if possible
|
||||
if (!Seek(input, _offset))
|
||||
return false;
|
||||
|
||||
bool result = true;
|
||||
try
|
||||
{
|
||||
// Then apply the mask if it exists
|
||||
byte[] read = new byte[_mask.Length];
|
||||
input.Read(read, 0, _mask.Length);
|
||||
|
||||
byte[] masked = new byte[_mask.Length];
|
||||
for (int i = 0; i < read.Length; i++)
|
||||
{
|
||||
masked[i] = (byte)(read[i] & _mask[i]);
|
||||
}
|
||||
|
||||
// Finally, compare it against the value
|
||||
for (int i = 0; i < _value.Length; i++)
|
||||
{
|
||||
if (masked[i] != _value[i])
|
||||
{
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
|
||||
// Return if the expected and actual results match
|
||||
return result == Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace SabreTools.Skippers.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test that checks data matches
|
||||
/// </summary>
|
||||
[XmlType("data")]
|
||||
public class DataTest : Test
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// File offset to run the test
|
||||
/// </summary>
|
||||
/// <remarks>Either numeric or the literal "EOF"</remarks>
|
||||
[XmlAttribute("offset")]
|
||||
public string? Offset
|
||||
{
|
||||
get => _offset == null ? "EOF" : _offset.Value.ToString();
|
||||
set
|
||||
{
|
||||
if (value == null || value.ToLowerInvariant() == "eof")
|
||||
_offset = null;
|
||||
else
|
||||
_offset = Convert.ToInt64(value, fromBase: 16);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static value to be checked at the offset
|
||||
/// </summary>
|
||||
/// <remarks>Hex string representation of a byte array</remarks>
|
||||
[XmlAttribute("value")]
|
||||
public string? Value
|
||||
{
|
||||
get => _value == null ? string.Empty : BitConverter.ToString(_value).Replace("-", string.Empty);
|
||||
set => _value = ParseByteArrayFromHex(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a pass or failure is expected
|
||||
/// </summary>
|
||||
[XmlAttribute("result")]
|
||||
public bool Result { get; set; } = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private instance variables
|
||||
|
||||
/// <summary>
|
||||
/// File offset to run the test
|
||||
/// </summary>
|
||||
/// <remarks>null is EOF</remarks>
|
||||
private long? _offset;
|
||||
|
||||
/// <summary>
|
||||
/// Static value to be checked at the offset
|
||||
/// </summary>
|
||||
private byte[]? _value;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public DataTest(string? offset, string? value, bool result)
|
||||
{
|
||||
Offset = offset;
|
||||
Value = value;
|
||||
Result = result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Passes(Stream input)
|
||||
{
|
||||
// If we have an invalid value
|
||||
if (_value == null || _value.Length == 0)
|
||||
return false;
|
||||
|
||||
// Seek to the correct position, if possible
|
||||
if (!Seek(input, _offset))
|
||||
return false;
|
||||
|
||||
// Then read and compare bytewise
|
||||
bool result = true;
|
||||
for (int i = 0; i < _value.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (input.ReadByte() != _value[i])
|
||||
{
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Return if the expected and actual results match
|
||||
return result == Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace SabreTools.Skippers.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test that tests file size
|
||||
/// </summary>
|
||||
[XmlType("file")]
|
||||
public class FileTest : Test
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a pass or failure is expected
|
||||
/// </summary>
|
||||
[XmlAttribute("result")]
|
||||
public bool Result { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Expected size of the input byte array, used with the Operator
|
||||
/// </summary>
|
||||
/// <remarks>Either numeric or the literal "po2"</remarks>
|
||||
[XmlAttribute("size")]
|
||||
public string? Size
|
||||
{
|
||||
get => _size == null ? "po2" : _size.Value.ToString();
|
||||
set
|
||||
{
|
||||
if (value == null || value.ToLowerInvariant() == "po2")
|
||||
_size = null;
|
||||
else
|
||||
_size = Convert.ToInt64(value, fromBase: 16);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expected range value for the input byte array size, used with Size
|
||||
/// </summary>
|
||||
[XmlAttribute("operator")]
|
||||
public HeaderSkipTestFileOperator Operator { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private instance variables
|
||||
|
||||
/// <summary>
|
||||
/// File offset to run the test
|
||||
/// </summary>
|
||||
/// <remarks>null is PO2 ("power of 2" filesize)</remarks>
|
||||
private long? _size;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public FileTest(bool result, string? size, HeaderSkipTestFileOperator opr)
|
||||
{
|
||||
Result = result;
|
||||
Size = size;
|
||||
Operator = opr;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Passes(Stream input)
|
||||
{
|
||||
// First get the file size from stream
|
||||
long size = input.Length;
|
||||
|
||||
// If we have a null size, check that the size is a power of 2
|
||||
bool result = true;
|
||||
if (_size == null)
|
||||
{
|
||||
// http://stackoverflow.com/questions/600293/how-to-check-if-a-number-is-a-power-of-2
|
||||
result = (((ulong)size & ((ulong)size - 1)) == 0);
|
||||
}
|
||||
else if (Operator == HeaderSkipTestFileOperator.Less)
|
||||
{
|
||||
result = (size < _size);
|
||||
}
|
||||
else if (Operator == HeaderSkipTestFileOperator.Greater)
|
||||
{
|
||||
result = (size > _size);
|
||||
}
|
||||
else if (Operator == HeaderSkipTestFileOperator.Equal)
|
||||
{
|
||||
result = (size == _size);
|
||||
}
|
||||
|
||||
// Return if the expected and actual results match
|
||||
return result == Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace SabreTools.Skippers.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test that uses a byte mask OR against data
|
||||
/// </summary>
|
||||
[XmlType("or")]
|
||||
public class OrTest : Test
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// File offset to run the test
|
||||
/// </summary>
|
||||
/// <remarks>Either numeric or the literal "EOF"</remarks>
|
||||
[XmlAttribute("offset")]
|
||||
public string? Offset
|
||||
{
|
||||
get => _offset == null ? "EOF" : _offset.Value.ToString();
|
||||
set
|
||||
{
|
||||
if (value == null || value.ToLowerInvariant() == "eof")
|
||||
_offset = null;
|
||||
else
|
||||
_offset = Convert.ToInt64(value, fromBase: 16);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static value to be checked at the offset
|
||||
/// </summary>
|
||||
/// <remarks>Hex string representation of a byte array</remarks>
|
||||
[XmlAttribute("value")]
|
||||
public string? Value
|
||||
{
|
||||
get => _value == null ? string.Empty : BitConverter.ToString(_value).Replace("-", string.Empty);
|
||||
set => _value = ParseByteArrayFromHex(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a pass or failure is expected
|
||||
/// </summary>
|
||||
[XmlAttribute("result")]
|
||||
public bool Result { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Byte mask to be applied to the tested bytes
|
||||
/// </summary>
|
||||
/// <remarks>Hex string representation of a byte array</remarks>
|
||||
[XmlAttribute("mask")]
|
||||
public string? Mask
|
||||
{
|
||||
get => _mask == null ? string.Empty : BitConverter.ToString(_mask).Replace("-", string.Empty);
|
||||
set => _mask = ParseByteArrayFromHex(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private instance variables
|
||||
|
||||
/// <summary>
|
||||
/// File offset to run the test
|
||||
/// </summary>
|
||||
/// <remarks>null is EOF</remarks>
|
||||
private long? _offset;
|
||||
|
||||
/// <summary>
|
||||
/// Static value to be checked at the offset
|
||||
/// </summary>
|
||||
private byte[]? _value;
|
||||
|
||||
/// <summary>
|
||||
/// Byte mask to be applied to the tested bytes
|
||||
/// </summary>
|
||||
private byte[]? _mask;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public OrTest(string? offset, string? value, bool result, string? mask)
|
||||
{
|
||||
Offset = offset;
|
||||
Value = value;
|
||||
Result = result;
|
||||
Mask = mask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Passes(Stream input)
|
||||
{
|
||||
// If we have an invalid mask
|
||||
if (_mask == null || _mask.Length == 0)
|
||||
return false;
|
||||
|
||||
// If we have an invalid value
|
||||
if (_value == null || _value.Length == 0)
|
||||
return false;
|
||||
|
||||
// Seek to the correct position, if possible
|
||||
if (!Seek(input, _offset))
|
||||
return false;
|
||||
|
||||
bool result = true;
|
||||
try
|
||||
{
|
||||
// Then apply the mask if it exists
|
||||
byte[] read = new byte[_mask.Length];
|
||||
input.Read(read, 0, _mask.Length);
|
||||
|
||||
byte[] masked = new byte[_mask.Length];
|
||||
for (int i = 0; i < read.Length; i++)
|
||||
{
|
||||
masked[i] = (byte)(read[i] | _mask[i]);
|
||||
}
|
||||
|
||||
// Finally, compare it against the value
|
||||
for (int i = 0; i < _value.Length; i++)
|
||||
{
|
||||
if (masked[i] != _value[i])
|
||||
{
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
|
||||
// Return if the expected and actual results match
|
||||
return result == Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace SabreTools.Skippers.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test that uses a byte mask XOR against data
|
||||
/// </summary>
|
||||
[XmlType("xor")]
|
||||
public class XorTest : Test
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// File offset to run the test
|
||||
/// </summary>
|
||||
/// <remarks>Either numeric or the literal "EOF"</remarks>
|
||||
[XmlAttribute("offset")]
|
||||
public string? Offset
|
||||
{
|
||||
get => _offset == null ? "EOF" : _offset.Value.ToString();
|
||||
set
|
||||
{
|
||||
if (value == null || value.ToLowerInvariant() == "eof")
|
||||
_offset = null;
|
||||
else
|
||||
_offset = Convert.ToInt64(value, fromBase: 16);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static value to be checked at the offset
|
||||
/// </summary>
|
||||
/// <remarks>Hex string representation of a byte array</remarks>
|
||||
[XmlAttribute("value")]
|
||||
public string? Value
|
||||
{
|
||||
get => _value == null ? string.Empty : BitConverter.ToString(_value).Replace("-", string.Empty);
|
||||
set => _value = ParseByteArrayFromHex(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a pass or failure is expected
|
||||
/// </summary>
|
||||
[XmlAttribute("result")]
|
||||
public bool Result { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Byte mask to be applied to the tested bytes
|
||||
/// </summary>
|
||||
/// <remarks>Hex string representation of a byte array</remarks>
|
||||
[XmlAttribute("mask")]
|
||||
public string? Mask
|
||||
{
|
||||
get => _mask == null ? string.Empty : BitConverter.ToString(_mask).Replace("-", string.Empty);
|
||||
set => _mask = ParseByteArrayFromHex(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private instance variables
|
||||
|
||||
/// <summary>
|
||||
/// File offset to run the test
|
||||
/// </summary>
|
||||
/// <remarks>null is EOF</remarks>
|
||||
private long? _offset;
|
||||
|
||||
/// <summary>
|
||||
/// Static value to be checked at the offset
|
||||
/// </summary>
|
||||
private byte[]? _value;
|
||||
|
||||
/// <summary>
|
||||
/// Byte mask to be applied to the tested bytes
|
||||
/// </summary>
|
||||
private byte[]? _mask;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public XorTest(string? offset, string? value, bool result, string? mask)
|
||||
{
|
||||
Offset = offset;
|
||||
Value = value;
|
||||
Result = result;
|
||||
Mask = mask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Passes(Stream input)
|
||||
{
|
||||
// If we have an invalid mask
|
||||
if (_mask == null || _mask.Length == 0)
|
||||
return false;
|
||||
|
||||
// If we have an invalid value
|
||||
if (_value == null || _value.Length == 0)
|
||||
return false;
|
||||
|
||||
// Seek to the correct position, if possible
|
||||
if (!Seek(input, _offset))
|
||||
return false;
|
||||
|
||||
bool result = true;
|
||||
try
|
||||
{
|
||||
// Then apply the mask if it exists
|
||||
byte[] read = new byte[_mask.Length];
|
||||
input.Read(read, 0, _mask.Length);
|
||||
|
||||
byte[] masked = new byte[_mask.Length];
|
||||
for (int i = 0; i < read.Length; i++)
|
||||
{
|
||||
masked[i] = (byte)(read[i] ^ _mask[i]);
|
||||
}
|
||||
|
||||
// Finally, compare it against the value
|
||||
for (int i = 0; i < _value.Length; i++)
|
||||
{
|
||||
if (masked[i] != _value[i])
|
||||
{
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
|
||||
// Return if the expected and actual results match
|
||||
return result == Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@
|
||||
<ProjectReference Include="..\SabreTools.DatTools\SabreTools.DatTools.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.FileTypes\SabreTools.FileTypes.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.Filtering\SabreTools.Filtering.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.Skippers\SabreTools.Skippers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -34,6 +33,7 @@
|
||||
<PackageReference Include="SabreTools.IO" Version="1.3.0" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.3.0" />
|
||||
<PackageReference Include="SabreTools.Serialization" Version="1.3.0" />
|
||||
<PackageReference Include="SabreTools.Skippers" Version="1.1.2" />
|
||||
<PackageReference Include="xunit" Version="2.6.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -18,8 +18,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SabreTools.Core", "SabreTools.Core\SabreTools.Core.csproj", "{66E2FB10-77C0-4589-9DCD-3CA48702C18A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Skippers", "SabreTools.Skippers\SabreTools.Skippers.csproj", "{D8665F27-75E6-4E3F-9F0A-286433831C69}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Help", "SabreTools.Help\SabreTools.Help.csproj", "{55364167-844F-4B58-8280-F5327FA3D8E7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Logging", "SabreTools.Logging\SabreTools.Logging.csproj", "{3D54D896-19F0-4723-B1E3-E40FAFE5A078}"
|
||||
@@ -80,14 +78,6 @@ Global
|
||||
{66E2FB10-77C0-4589-9DCD-3CA48702C18A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{66E2FB10-77C0-4589-9DCD-3CA48702C18A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{66E2FB10-77C0-4589-9DCD-3CA48702C18A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D8665F27-75E6-4E3F-9F0A-286433831C69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D8665F27-75E6-4E3F-9F0A-286433831C69}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D8665F27-75E6-4E3F-9F0A-286433831C69}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{D8665F27-75E6-4E3F-9F0A-286433831C69}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{D8665F27-75E6-4E3F-9F0A-286433831C69}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D8665F27-75E6-4E3F-9F0A-286433831C69}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D8665F27-75E6-4E3F-9F0A-286433831C69}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{D8665F27-75E6-4E3F-9F0A-286433831C69}.Release|x64.Build.0 = Release|Any CPU
|
||||
{55364167-844F-4B58-8280-F5327FA3D8E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{55364167-844F-4B58-8280-F5327FA3D8E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{55364167-844F-4B58-8280-F5327FA3D8E7}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
||||
Reference in New Issue
Block a user