mirror of
https://github.com/SabreTools/SabreTools.Skippers.git
synced 2026-07-08 18:06:10 +00:00
Port code from SabreTools
This commit is contained in:
24
.vscode/tasks.json
vendored
Normal file
24
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "shell",
|
||||
"args": [
|
||||
"build",
|
||||
// Ask dotnet build to generate full paths for file names.
|
||||
"/property:GenerateFullPaths=true",
|
||||
// Do not generate summary otherwise it leads to duplicate errors in Problems panel
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
99
Detector.cs
Normal file
99
Detector.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
33
Detectors/Atari7800.cs
Normal file
33
Detectors/Atari7800.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Detectors/AtariLynx.cs
Normal file
33
Detectors/AtariLynx.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Detectors/CommodorePSID.cs
Normal file
42
Detectors/CommodorePSID.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Detectors/NECPCEngine.cs
Normal file
30
Detectors/NECPCEngine.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Detectors/Nintendo64.cs
Normal file
36
Detectors/Nintendo64.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Detectors/NintendoEntertainmentSystem.cs
Normal file
30
Detectors/NintendoEntertainmentSystem.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Detectors/NintendoFamicomDiskSystem.cs
Normal file
39
Detectors/NintendoFamicomDiskSystem.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Detectors/SuperFamicomSPC.cs
Normal file
30
Detectors/SuperFamicomSPC.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Detectors/SuperNintendoEntertainmentSystem.cs
Normal file
36
Detectors/SuperNintendoEntertainmentSystem.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Enums.cs
Normal file
40
Enums.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
21
LICENSE
21
LICENSE
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 SabreTools
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
5
README.MD
Normal file
5
README.MD
Normal file
@@ -0,0 +1,5 @@
|
||||
# SabreTools.Skippers
|
||||
|
||||
This library comprises of code to perform copier header operations such as matching, manipulation, and removal. These are used for many older console-based systems and helps define known header skippers for immediate use.
|
||||
|
||||
Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTools.Skippers).
|
||||
286
Rule.cs
Normal file
286
Rule.cs
Normal file
@@ -0,0 +1,286 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
SabreTools.Skippers.csproj
Normal file
30
SabreTools.Skippers.csproj
Normal file
@@ -0,0 +1,30 @@
|
||||
<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>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/SabreTools/SabreTools.Skippers</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>skippers headers game</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath=""/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Skippers", "SabreTools.Skippers.csproj", "{D71725DB-0DA3-4D92-9ADC-9601D136126C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -11,4 +13,10 @@ Global
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{D71725DB-0DA3-4D92-9ADC-9601D136126C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D71725DB-0DA3-4D92-9ADC-9601D136126C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D71725DB-0DA3-4D92-9ADC-9601D136126C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D71725DB-0DA3-4D92-9ADC-9601D136126C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
121
SkipperMatch.cs
Normal file
121
SkipperMatch.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Test.cs
Normal file
73
Test.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
141
Tests/AndTest.cs
Normal file
141
Tests/AndTest.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
111
Tests/DataTest.cs
Normal file
111
Tests/DataTest.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
97
Tests/FileTest.cs
Normal file
97
Tests/FileTest.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
141
Tests/OrTest.cs
Normal file
141
Tests/OrTest.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
141
Tests/XorTest.cs
Normal file
141
Tests/XorTest.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user