mirror of
https://github.com/claunia/SabreTools.git
synced 2025-12-16 19:14:27 +00:00
Complete overhaul on Skippers
This change involves safety updates for serialization, better definitions of various classes, renames of some classes for accuracy, missing enum decoration, and various fixes.
This commit is contained in:
@@ -421,10 +421,10 @@ namespace SabreTools.DatTools
|
|||||||
{
|
{
|
||||||
// Check to see if we have a matching header first
|
// Check to see if we have a matching header first
|
||||||
SkipperMatch.Init();
|
SkipperMatch.Init();
|
||||||
SkipperRule rule = SkipperMatch.GetMatchingRule(fileStream, Path.GetFileNameWithoutExtension(datFile.Header.HeaderSkipper));
|
Rule rule = SkipperMatch.GetMatchingRule(fileStream, Path.GetFileNameWithoutExtension(datFile.Header.HeaderSkipper));
|
||||||
|
|
||||||
// If there's a match, create the new file to write
|
// If there's a match, create the new file to write
|
||||||
if (rule.Tests != null && rule.Tests.Count != 0)
|
if (rule.Tests != null && rule.Tests.Length != 0)
|
||||||
{
|
{
|
||||||
// If the file could be transformed correctly
|
// If the file could be transformed correctly
|
||||||
MemoryStream transformStream = new MemoryStream();
|
MemoryStream transformStream = new MemoryStream();
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ namespace SabreTools.FileTypes
|
|||||||
var rule = SkipperMatch.GetMatchingRule(input, Path.GetFileNameWithoutExtension(header));
|
var rule = SkipperMatch.GetMatchingRule(input, Path.GetFileNameWithoutExtension(header));
|
||||||
|
|
||||||
// If there's a match, transform the stream before getting info
|
// If there's a match, transform the stream before getting info
|
||||||
if (rule.Tests != null && rule.Tests.Count != 0)
|
if (rule.Tests != null && rule.Tests.Length != 0)
|
||||||
{
|
{
|
||||||
// Create the output stream
|
// Create the output stream
|
||||||
MemoryStream outputStream = new MemoryStream();
|
MemoryStream outputStream = new MemoryStream();
|
||||||
|
|||||||
99
SabreTools.Skippers/Detector.cs
Normal file
99
SabreTools.Skippers/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 class Detector
|
||||||
|
{
|
||||||
|
#region Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detector name
|
||||||
|
/// </summary>
|
||||||
|
[XmlElement("name")]
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Author names
|
||||||
|
/// </summary>
|
||||||
|
[XmlElement("author")]
|
||||||
|
public string? Author { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// File version
|
||||||
|
/// </summary>
|
||||||
|
[XmlElement("version")]
|
||||||
|
public string? Version { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set of all rules in the skipper
|
||||||
|
/// </summary>
|
||||||
|
[XmlElement("rule")]
|
||||||
|
public Rule[]? Rules { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filename the skipper lives in
|
||||||
|
/// </summary>
|
||||||
|
[XmlIgnore]
|
||||||
|
public string? SourceFile { get; 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.IsNullOrWhiteSpace(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
|
||||||
|
}
|
||||||
|
}
|
||||||
63
SabreTools.Skippers/Detectors/Atari7800.cs
Normal file
63
SabreTools.Skippers/Detectors/Atari7800.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
Offset = "1",
|
||||||
|
Value = "415441524937383030",
|
||||||
|
Result = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule2Test1 = new DataTest
|
||||||
|
{
|
||||||
|
Offset = "64",
|
||||||
|
Value = "41435455414C20434152542044415441205354415254532048455245",
|
||||||
|
Result = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create rules
|
||||||
|
var rule1 = new Rule
|
||||||
|
{
|
||||||
|
StartOffset = "80",
|
||||||
|
EndOffset = "EOF",
|
||||||
|
Operation = HeaderSkipOperation.None,
|
||||||
|
Tests = new Test[]
|
||||||
|
{
|
||||||
|
rule1Test1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule2 = new Rule
|
||||||
|
{
|
||||||
|
StartOffset = "80",
|
||||||
|
EndOffset = "EOF",
|
||||||
|
Operation = HeaderSkipOperation.None,
|
||||||
|
Tests = new Test[]
|
||||||
|
{
|
||||||
|
rule2Test1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create file
|
||||||
|
Name = "Atari 7800";
|
||||||
|
Author = "Roman Scherzer";
|
||||||
|
Version = "1.0";
|
||||||
|
SourceFile = "a7800";
|
||||||
|
Rules = new Rule[]
|
||||||
|
{
|
||||||
|
rule1,
|
||||||
|
rule2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,48 +1,48 @@
|
|||||||
using System.Collections.Generic;
|
using SabreTools.Skippers.Tests;
|
||||||
|
|
||||||
namespace SabreTools.Skippers.SkipperFiles
|
namespace SabreTools.Skippers.Detectors
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SkipperFile for Atari Lynx headers
|
/// Detector for Atari Lynx headers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Originally from lynx.xml</remarks>
|
/// <remarks>Originally from lynx.xml</remarks>
|
||||||
internal class AtariLynx : SkipperFile
|
internal class AtariLynx : Detector
|
||||||
{
|
{
|
||||||
public AtariLynx()
|
public AtariLynx()
|
||||||
{
|
{
|
||||||
// Create tests
|
// Create tests
|
||||||
var rule1Test1 = new DataSkipperTest
|
var rule1Test1 = new DataTest
|
||||||
{
|
{
|
||||||
Offset = 0x00,
|
Offset = "0",
|
||||||
Value = new byte[] { 0x4C, 0x59, 0x4E, 0x58 },
|
Value = "4C594E58",
|
||||||
Result = true,
|
Result = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
var rule2Test1 = new DataSkipperTest
|
var rule2Test1 = new DataTest
|
||||||
{
|
{
|
||||||
Offset = 0x06,
|
Offset = "6",
|
||||||
Value = new byte[] { 0x42, 0x53, 0x39 },
|
Value = "425339",
|
||||||
Result = true,
|
Result = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create rules
|
// Create rules
|
||||||
var rule1 = new SkipperRule
|
var rule1 = new Rule
|
||||||
{
|
{
|
||||||
StartOffset = 0x40,
|
StartOffset = "40",
|
||||||
EndOffset = null,
|
EndOffset = "EOF",
|
||||||
Operation = HeaderSkipOperation.None,
|
Operation = HeaderSkipOperation.None,
|
||||||
Tests = new List<SkipperTest>
|
Tests = new Test[]
|
||||||
{
|
{
|
||||||
rule1Test1,
|
rule1Test1,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var rule2 = new SkipperRule
|
var rule2 = new Rule
|
||||||
{
|
{
|
||||||
StartOffset = 0x40,
|
StartOffset = "40",
|
||||||
EndOffset = null,
|
EndOffset = "EOF",
|
||||||
Operation = HeaderSkipOperation.None,
|
Operation = HeaderSkipOperation.None,
|
||||||
Tests = new List<SkipperTest>
|
Tests = new Test[]
|
||||||
{
|
{
|
||||||
rule2Test1,
|
rule2Test1,
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ namespace SabreTools.Skippers.SkipperFiles
|
|||||||
Author = "Roman Scherzer";
|
Author = "Roman Scherzer";
|
||||||
Version = "1.0";
|
Version = "1.0";
|
||||||
SourceFile = "lynx";
|
SourceFile = "lynx";
|
||||||
Rules = new List<SkipperRule>
|
Rules = new Rule[]
|
||||||
{
|
{
|
||||||
rule1,
|
rule1,
|
||||||
rule2,
|
rule2,
|
||||||
120
SabreTools.Skippers/Detectors/CommodorePSID.cs
Normal file
120
SabreTools.Skippers/Detectors/CommodorePSID.cs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
Offset = "0",
|
||||||
|
Value = "5053494400010076",
|
||||||
|
Result = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule2Test1 = new DataTest
|
||||||
|
{
|
||||||
|
Offset = "0",
|
||||||
|
Value = "505349440003007c",
|
||||||
|
Result = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule3Test1 = new DataTest
|
||||||
|
{
|
||||||
|
Offset = "0",
|
||||||
|
Value = "505349440002007c",
|
||||||
|
Result = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule4Test1 = new DataTest
|
||||||
|
{
|
||||||
|
Offset = "0",
|
||||||
|
Value = "505349440001007c",
|
||||||
|
Result = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule5Test1 = new DataTest
|
||||||
|
{
|
||||||
|
Offset = "0",
|
||||||
|
Value = "525349440002007c",
|
||||||
|
Result = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create rules
|
||||||
|
var rule1 = new Rule
|
||||||
|
{
|
||||||
|
StartOffset = "76",
|
||||||
|
EndOffset = "EOF",
|
||||||
|
Operation = HeaderSkipOperation.None,
|
||||||
|
Tests = new Test[]
|
||||||
|
{
|
||||||
|
rule1Test1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule2 = new Rule
|
||||||
|
{
|
||||||
|
StartOffset = "76",
|
||||||
|
EndOffset = "EOF",
|
||||||
|
Operation = HeaderSkipOperation.None,
|
||||||
|
Tests = new Test[]
|
||||||
|
{
|
||||||
|
rule2Test1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule3 = new Rule
|
||||||
|
{
|
||||||
|
StartOffset = "7c",
|
||||||
|
EndOffset = "EOF",
|
||||||
|
Operation = HeaderSkipOperation.None,
|
||||||
|
Tests = new Test[]
|
||||||
|
{
|
||||||
|
rule3Test1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule4 = new Rule
|
||||||
|
{
|
||||||
|
StartOffset = "7c",
|
||||||
|
EndOffset = "EOF",
|
||||||
|
Operation = HeaderSkipOperation.None,
|
||||||
|
Tests = new Test[]
|
||||||
|
{
|
||||||
|
rule4Test1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule5 = new Rule
|
||||||
|
{
|
||||||
|
StartOffset = "7c",
|
||||||
|
EndOffset = "EOF",
|
||||||
|
Operation = HeaderSkipOperation.None,
|
||||||
|
Tests = new Test[]
|
||||||
|
{
|
||||||
|
rule5Test1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create file
|
||||||
|
Name = "psid";
|
||||||
|
Author = "Yori Yoshizuki";
|
||||||
|
Version = "1.2";
|
||||||
|
SourceFile = "psid";
|
||||||
|
Rules = new Rule[]
|
||||||
|
{
|
||||||
|
rule1,
|
||||||
|
rule2,
|
||||||
|
rule3,
|
||||||
|
rule4,
|
||||||
|
rule5,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,29 @@
|
|||||||
using System.Collections.Generic;
|
using SabreTools.Skippers.Tests;
|
||||||
|
|
||||||
namespace SabreTools.Skippers.SkipperFiles
|
namespace SabreTools.Skippers.Detectors
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SkipperFile for NEC PC-Engine / TurboGrafx 16 headers
|
/// Detector for NEC PC-Engine / TurboGrafx 16 headers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Originally from pce.xml</remarks>
|
/// <remarks>Originally from pce.xml</remarks>
|
||||||
internal class NECPCEngine : SkipperFile
|
internal class NECPCEngine : Detector
|
||||||
{
|
{
|
||||||
public NECPCEngine()
|
public NECPCEngine()
|
||||||
{
|
{
|
||||||
// Create tests
|
// Create tests
|
||||||
var rule1Test1 = new DataSkipperTest
|
var rule1Test1 = new DataTest
|
||||||
{
|
{
|
||||||
Offset = 0x00,
|
Offset = "0",
|
||||||
Value = new byte[] { 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xBB, 0x02 },
|
Value = "4000000000000000AABB02",
|
||||||
Result = true,
|
Result = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create rules
|
// Create rules
|
||||||
var rule1 = new SkipperRule
|
var rule1 = new Rule
|
||||||
{
|
{
|
||||||
StartOffset = 0x200,
|
StartOffset = "200",
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
Operation = HeaderSkipOperation.None,
|
||||||
Tests = new List<SkipperTest>
|
Tests = new Test[]
|
||||||
{
|
{
|
||||||
rule1Test1,
|
rule1Test1,
|
||||||
}
|
}
|
||||||
@@ -35,7 +34,7 @@ namespace SabreTools.Skippers.SkipperFiles
|
|||||||
Author = "Matt Nadareski (darksabre76)";
|
Author = "Matt Nadareski (darksabre76)";
|
||||||
Version = "1.0";
|
Version = "1.0";
|
||||||
SourceFile = "pce";
|
SourceFile = "pce";
|
||||||
Rules = new List<SkipperRule>
|
Rules = new Rule[]
|
||||||
{
|
{
|
||||||
rule1,
|
rule1,
|
||||||
};
|
};
|
||||||
@@ -1,66 +1,66 @@
|
|||||||
using System.Collections.Generic;
|
using SabreTools.Skippers.Tests;
|
||||||
|
|
||||||
namespace SabreTools.Skippers.SkipperFiles
|
namespace SabreTools.Skippers.Detectors
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SkipperFile for Nintendo 64 headers
|
/// Detector for Nintendo 64 headers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Originally from n64.xml</remarks>
|
/// <remarks>Originally from n64.xml</remarks>
|
||||||
internal class Nintendo64 : SkipperFile
|
internal class Nintendo64 : Detector
|
||||||
{
|
{
|
||||||
public Nintendo64()
|
public Nintendo64()
|
||||||
{
|
{
|
||||||
// Create tests
|
// Create tests
|
||||||
var rule1Test1 = new DataSkipperTest
|
var rule1Test1 = new DataTest
|
||||||
{
|
{
|
||||||
Offset = 0x00,
|
Offset = "0",
|
||||||
Value = new byte[] { 0x80, 0x37, 0x12, 0x40 },
|
Value = "80371240",
|
||||||
Result = true,
|
Result = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
var rule2Test1 = new DataSkipperTest
|
var rule2Test1 = new DataTest
|
||||||
{
|
{
|
||||||
Offset = 0x00,
|
Offset = "0",
|
||||||
Value = new byte[] { 0x37, 0x80, 0x40, 0x12 },
|
Value = "37804012",
|
||||||
Result = true,
|
Result = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
var rule3Test1 = new DataSkipperTest
|
var rule3Test1 = new DataTest
|
||||||
{
|
{
|
||||||
Offset = 0x00,
|
Offset = "0",
|
||||||
Value = new byte[] { 0x40, 0x12, 0x37, 0x80 },
|
Value = "40123780",
|
||||||
Result = true,
|
Result = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create rules
|
// Create rules
|
||||||
var rule1 = new SkipperRule
|
var rule1 = new Rule
|
||||||
{
|
{
|
||||||
StartOffset = 0x00,
|
StartOffset = "0",
|
||||||
EndOffset = null,
|
EndOffset = "EOF",
|
||||||
Operation = HeaderSkipOperation.None,
|
Operation = HeaderSkipOperation.None,
|
||||||
Tests = new List<SkipperTest>
|
Tests = new Test[]
|
||||||
{
|
{
|
||||||
rule1Test1,
|
rule1Test1,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var rule2 = new SkipperRule
|
var rule2 = new Rule
|
||||||
{
|
{
|
||||||
StartOffset = 0x00,
|
StartOffset = "0",
|
||||||
EndOffset = null,
|
EndOffset = "EOF",
|
||||||
Operation = HeaderSkipOperation.Byteswap,
|
Operation = HeaderSkipOperation.Byteswap,
|
||||||
Tests = new List<SkipperTest>
|
Tests = new Test[]
|
||||||
{
|
{
|
||||||
rule2Test1,
|
rule2Test1,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var rule3 = new SkipperRule
|
var rule3 = new Rule
|
||||||
{
|
{
|
||||||
StartOffset = 0x00,
|
StartOffset = "0",
|
||||||
EndOffset = null,
|
EndOffset = "EOF",
|
||||||
Operation = HeaderSkipOperation.Wordswap,
|
Operation = HeaderSkipOperation.Wordswap,
|
||||||
Tests = new List<SkipperTest>
|
Tests = new Test[]
|
||||||
{
|
{
|
||||||
rule3Test1,
|
rule3Test1,
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ namespace SabreTools.Skippers.SkipperFiles
|
|||||||
Author = "CUE";
|
Author = "CUE";
|
||||||
Version = "1.1";
|
Version = "1.1";
|
||||||
SourceFile = "n64";
|
SourceFile = "n64";
|
||||||
Rules = new List<SkipperRule>
|
Rules = new Rule[]
|
||||||
{
|
{
|
||||||
rule1, // V64
|
rule1, // V64
|
||||||
rule2, // Z64
|
rule2, // Z64
|
||||||
@@ -1,30 +1,30 @@
|
|||||||
using System.Collections.Generic;
|
using SabreTools.Skippers.Tests;
|
||||||
|
|
||||||
namespace SabreTools.Skippers.SkipperFiles
|
namespace SabreTools.Skippers.Detectors
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SkipperFile for Nintendo Entertainment System headers
|
/// Detector for Nintendo Entertainment System headers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Originally from nes.xml</remarks>
|
/// <remarks>Originally from nes.xml</remarks>
|
||||||
internal class NintendoEntertainmentSystem : SkipperFile
|
internal class NintendoEntertainmentSystem : Detector
|
||||||
{
|
{
|
||||||
public NintendoEntertainmentSystem()
|
public NintendoEntertainmentSystem()
|
||||||
{
|
{
|
||||||
// Create tests
|
// Create tests
|
||||||
var rule1Test1 = new DataSkipperTest
|
var rule1Test1 = new DataTest
|
||||||
{
|
{
|
||||||
Offset = 0x00,
|
Offset = "0",
|
||||||
Value = new byte[] { 0x4E, 0x45, 0x53, 0x1A },
|
Value = "4E45531A",
|
||||||
Result = true,
|
Result = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create rules
|
// Create rules
|
||||||
var rule1 = new SkipperRule
|
var rule1 = new Rule
|
||||||
{
|
{
|
||||||
StartOffset = 0x10,
|
StartOffset = "10",
|
||||||
EndOffset = null,
|
EndOffset = "EOF",
|
||||||
Operation = HeaderSkipOperation.None,
|
Operation = HeaderSkipOperation.None,
|
||||||
Tests = new List<SkipperTest>
|
Tests = new Test[]
|
||||||
{
|
{
|
||||||
rule1Test1,
|
rule1Test1,
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ namespace SabreTools.Skippers.SkipperFiles
|
|||||||
Author = "Roman Scherzer";
|
Author = "Roman Scherzer";
|
||||||
Version = "1.1";
|
Version = "1.1";
|
||||||
SourceFile = "nes";
|
SourceFile = "nes";
|
||||||
Rules = new List<SkipperRule>
|
Rules = new Rule[]
|
||||||
{
|
{
|
||||||
rule1,
|
rule1,
|
||||||
};
|
};
|
||||||
93
SabreTools.Skippers/Detectors/NintendoFamicomDiskSystem.cs
Normal file
93
SabreTools.Skippers/Detectors/NintendoFamicomDiskSystem.cs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
Offset = "0",
|
||||||
|
Value = "4644531A010000000000000000000000",
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule2Test1 = new DataTest
|
||||||
|
{
|
||||||
|
Offset = "0",
|
||||||
|
Value = "4644531A020000000000000000000000",
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule3Test1 = new DataTest
|
||||||
|
{
|
||||||
|
Offset = "0",
|
||||||
|
Value = "4644531A030000000000000000000000",
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule4Test1 = new DataTest
|
||||||
|
{
|
||||||
|
Offset = "0",
|
||||||
|
Value = "4644531A040000000000000000000000",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create rules
|
||||||
|
var rule1 = new Rule
|
||||||
|
{
|
||||||
|
StartOffset = "10",
|
||||||
|
Operation = HeaderSkipOperation.None,
|
||||||
|
Tests = new Test[]
|
||||||
|
{
|
||||||
|
rule1Test1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule2 = new Rule
|
||||||
|
{
|
||||||
|
StartOffset = "10",
|
||||||
|
Operation = HeaderSkipOperation.None,
|
||||||
|
Tests = new Test[]
|
||||||
|
{
|
||||||
|
rule2Test1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule3 = new Rule
|
||||||
|
{
|
||||||
|
StartOffset = "10",
|
||||||
|
Operation = HeaderSkipOperation.None,
|
||||||
|
Tests = new Test[]
|
||||||
|
{
|
||||||
|
rule3Test1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule4 = new Rule
|
||||||
|
{
|
||||||
|
StartOffset = "10",
|
||||||
|
Operation = HeaderSkipOperation.None,
|
||||||
|
Tests = new Test[]
|
||||||
|
{
|
||||||
|
rule4Test1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create file
|
||||||
|
Name = "fds";
|
||||||
|
Author = "Yori Yoshizuki";
|
||||||
|
Version = "1.0";
|
||||||
|
SourceFile = "fds";
|
||||||
|
Rules = new Rule[]
|
||||||
|
{
|
||||||
|
rule1,
|
||||||
|
rule2,
|
||||||
|
rule3,
|
||||||
|
rule4,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,30 @@
|
|||||||
using System.Collections.Generic;
|
using SabreTools.Skippers.Tests;
|
||||||
|
|
||||||
namespace SabreTools.Skippers.SkipperFiles
|
namespace SabreTools.Skippers.Detectors
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SkipperFile for Super Famicom SPC headers
|
/// Detector for Super Famicom SPC headers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Originally from spc.xml</remarks>
|
/// <remarks>Originally from spc.xml</remarks>
|
||||||
internal class SuperFamicomSPC : SkipperFile
|
internal class SuperFamicomSPC : Detector
|
||||||
{
|
{
|
||||||
public SuperFamicomSPC()
|
public SuperFamicomSPC()
|
||||||
{
|
{
|
||||||
// Create tests
|
// Create tests
|
||||||
var rule1Test1 = new DataSkipperTest
|
var rule1Test1 = new DataTest
|
||||||
{
|
{
|
||||||
Offset = 0x00,
|
Offset = "0",
|
||||||
Value = new byte[] { 0x53, 0x4E, 0x45, 0x53, 0x2D, 0x53, 0x50, 0x43 },
|
Value = "534E45532D535043",
|
||||||
Result = true,
|
Result = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create rules
|
// Create rules
|
||||||
var rule1 = new SkipperRule
|
var rule1 = new Rule
|
||||||
{
|
{
|
||||||
StartOffset = 0x100,
|
StartOffset = "00100",
|
||||||
EndOffset = null,
|
EndOffset = "EOF",
|
||||||
Operation = HeaderSkipOperation.None,
|
Operation = HeaderSkipOperation.None,
|
||||||
Tests = new List<SkipperTest>
|
Tests = new Test[]
|
||||||
{
|
{
|
||||||
rule1Test1,
|
rule1Test1,
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ namespace SabreTools.Skippers.SkipperFiles
|
|||||||
Author = "Yori Yoshizuki";
|
Author = "Yori Yoshizuki";
|
||||||
Version = "1.0";
|
Version = "1.0";
|
||||||
SourceFile = "spc";
|
SourceFile = "spc";
|
||||||
Rules = new List<SkipperRule>
|
Rules = new Rule[]
|
||||||
{
|
{
|
||||||
rule1,
|
rule1,
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
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 rule1Test1 = new DataTest
|
||||||
|
{
|
||||||
|
Offset = "16",
|
||||||
|
Value = "0000000000000000",
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule2Test1 = new DataTest
|
||||||
|
{
|
||||||
|
Offset = "16",
|
||||||
|
Value = "AABB040000000000",
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule3Test1 = new DataTest
|
||||||
|
{
|
||||||
|
Offset = "16",
|
||||||
|
Value = "535550455255464F",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create rules
|
||||||
|
var rule1 = new Rule
|
||||||
|
{
|
||||||
|
StartOffset = "200",
|
||||||
|
Operation = HeaderSkipOperation.None,
|
||||||
|
Tests = new Test[]
|
||||||
|
{
|
||||||
|
rule1Test1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule2 = new Rule
|
||||||
|
{
|
||||||
|
StartOffset = "200",
|
||||||
|
Operation = HeaderSkipOperation.None,
|
||||||
|
Tests = new Test[]
|
||||||
|
{
|
||||||
|
rule2Test1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var rule3 = new Rule
|
||||||
|
{
|
||||||
|
StartOffset = "200",
|
||||||
|
Operation = HeaderSkipOperation.None,
|
||||||
|
Tests = new Test[]
|
||||||
|
{
|
||||||
|
rule3Test1,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create file
|
||||||
|
Name = "Nintendo Super Famicom/SNES";
|
||||||
|
Author = "Matt Nadareski (darksabre76)";
|
||||||
|
Version = "1.0";
|
||||||
|
SourceFile = "snes";
|
||||||
|
Rules = new Rule[]
|
||||||
|
{
|
||||||
|
rule1, // FIG
|
||||||
|
rule2, // SMC
|
||||||
|
rule3, // UFO
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ namespace SabreTools.Skippers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public enum HeaderSkipOperation
|
public enum HeaderSkipOperation
|
||||||
{
|
{
|
||||||
|
[XmlEnum("none")]
|
||||||
None = 0,
|
None = 0,
|
||||||
|
|
||||||
[XmlEnum("bitswap")]
|
[XmlEnum("bitswap")]
|
||||||
|
|||||||
@@ -1,27 +1,49 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
using SabreTools.Logging;
|
using SabreTools.Logging;
|
||||||
|
using SabreTools.Skippers.Tests;
|
||||||
|
|
||||||
namespace SabreTools.Skippers
|
namespace SabreTools.Skippers
|
||||||
{
|
{
|
||||||
public class SkipperRule
|
[XmlType("rule")]
|
||||||
|
public class Rule
|
||||||
{
|
{
|
||||||
#region Fields
|
#region Fields
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starting offset for applying rule
|
/// Starting offset for applying rule
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>Either numeric or the literal "EOF"</remarks>
|
||||||
[XmlAttribute("start_offset")]
|
[XmlAttribute("start_offset")]
|
||||||
public long? StartOffset { get; set; } // null is EOF
|
public string? StartOffset
|
||||||
|
{
|
||||||
|
get => _startOffset == null ? "EOF" : _startOffset.Value.ToString();
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null || value.ToLowerInvariant() == "eof")
|
||||||
|
_startOffset = null;
|
||||||
|
else
|
||||||
|
_startOffset = Convert.ToInt64(value, fromBase: 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ending offset for applying rule
|
/// Ending offset for applying rule
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>Either numeric or the literal "EOF"</remarks>
|
||||||
[XmlAttribute("end_offset")]
|
[XmlAttribute("end_offset")]
|
||||||
public long? EndOffset { get; set; } // null if EOF
|
public string? EndOffset
|
||||||
|
{
|
||||||
|
get => _endOffset == null ? "EOF" : _endOffset.Value.ToString();
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null || value.ToLowerInvariant() == "eof")
|
||||||
|
_endOffset = null;
|
||||||
|
else
|
||||||
|
_endOffset = Convert.ToInt64(value, fromBase: 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Byte manipulation operation
|
/// Byte manipulation operation
|
||||||
@@ -32,19 +54,34 @@ namespace SabreTools.Skippers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of matching tests in a rule
|
/// List of matching tests in a rule
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[XmlArray]
|
[XmlElement("and", typeof(AndTest))]
|
||||||
[XmlArrayItem("data")]
|
[XmlElement("data", typeof(DataTest))]
|
||||||
[XmlArrayItem("or")]
|
[XmlElement("file", typeof(FileTest))]
|
||||||
[XmlArrayItem("xor")]
|
[XmlElement("or", typeof(OrTest))]
|
||||||
[XmlArrayItem("and")]
|
[XmlElement("xor", typeof(XorTest))]
|
||||||
[XmlArrayItem("file")]
|
public Test[]? Tests { get; set; }
|
||||||
public List<SkipperTest> Tests { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Filename the skipper rule lives in
|
/// Filename the skipper rule lives in
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[XmlIgnore]
|
[XmlIgnore]
|
||||||
public string SourceFile { get; set; }
|
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
|
#endregion
|
||||||
|
|
||||||
@@ -57,27 +94,25 @@ namespace SabreTools.Skippers
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
public Rule()
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public SkipperRule()
|
|
||||||
{
|
{
|
||||||
logger = new Logger(this);
|
logger = new Logger(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if a Stream passes all tests in the SkipperRule
|
/// Check if a Stream passes all tests in the Rule
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">Stream to check</param>
|
/// <param name="input">Stream to check</param>
|
||||||
/// <returns>True if all tests passed, false otherwise</returns>
|
/// <returns>True if all tests passed, false otherwise</returns>
|
||||||
public bool PassesAllTests(Stream input)
|
public bool PassesAllTests(Stream input)
|
||||||
{
|
{
|
||||||
bool success = true;
|
bool success = true;
|
||||||
foreach (SkipperTest test in Tests)
|
|
||||||
|
// If there are no tests
|
||||||
|
if (Tests == null || Tests.Length == 0)
|
||||||
|
return success;
|
||||||
|
|
||||||
|
foreach (Test test in Tests)
|
||||||
{
|
{
|
||||||
bool result = test.Passes(input);
|
bool result = test.Passes(input);
|
||||||
success &= result;
|
success &= result;
|
||||||
@@ -94,16 +129,23 @@ namespace SabreTools.Skippers
|
|||||||
/// <returns>True if the file was transformed properly, false otherwise</returns>
|
/// <returns>True if the file was transformed properly, false otherwise</returns>
|
||||||
public bool TransformFile(string input, string output)
|
public bool TransformFile(string input, string output)
|
||||||
{
|
{
|
||||||
// If the input file doesn't exist, fail
|
// If the input file doesn't exist
|
||||||
if (!File.Exists(input))
|
if (string.IsNullOrWhiteSpace(input) || !File.Exists(input))
|
||||||
{
|
{
|
||||||
logger.Error($"I'm sorry but '{input}' doesn't exist!");
|
logger.Error($"'{input}' doesn't exist and cannot be transformed!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have an invalid output directory name
|
||||||
|
if (string.IsNullOrWhiteSpace(output))
|
||||||
|
{
|
||||||
|
logger.Error($"Output path was null or empty, cannot write transformed file!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the output directory if it doesn't already
|
// Create the output directory if it doesn't already
|
||||||
if (!Directory.Exists(Path.GetDirectoryName(output)))
|
string parentDirectory = Path.GetDirectoryName(output) ?? string.Empty;
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(output));
|
Directory.CreateDirectory(parentDirectory);
|
||||||
|
|
||||||
//logger.User($"Attempting to apply rule to '{input}'");
|
//logger.User($"Attempting to apply rule to '{input}'");
|
||||||
bool success = TransformStream(File.OpenRead(input), File.Create(output));
|
bool success = TransformStream(File.OpenRead(input), File.Create(output));
|
||||||
@@ -134,15 +176,15 @@ namespace SabreTools.Skippers
|
|||||||
long extsize = input.Length;
|
long extsize = input.Length;
|
||||||
if ((Operation > HeaderSkipOperation.Bitswap && (extsize % 2) != 0)
|
if ((Operation > HeaderSkipOperation.Bitswap && (extsize % 2) != 0)
|
||||||
|| (Operation > HeaderSkipOperation.Byteswap && (extsize % 4) != 0)
|
|| (Operation > HeaderSkipOperation.Byteswap && (extsize % 4) != 0)
|
||||||
|| (Operation > HeaderSkipOperation.Bitswap && (StartOffset == null || StartOffset % 2 != 0)))
|
|| (Operation > HeaderSkipOperation.Bitswap && (_startOffset == null || _startOffset % 2 != 0)))
|
||||||
{
|
{
|
||||||
logger.Error("The stream did not have the correct size to be transformed!");
|
logger.Error("The stream did not have the correct size to be transformed!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now read the proper part of the file and apply the rule
|
// Now read the proper part of the file and apply the rule
|
||||||
BinaryWriter bw = null;
|
BinaryWriter? bw = null;
|
||||||
BinaryReader br = null;
|
BinaryReader? br = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
logger.User("Applying found rule to input stream");
|
logger.User("Applying found rule to input stream");
|
||||||
@@ -150,24 +192,24 @@ namespace SabreTools.Skippers
|
|||||||
br = new BinaryReader(input);
|
br = new BinaryReader(input);
|
||||||
|
|
||||||
// Seek to the beginning offset
|
// Seek to the beginning offset
|
||||||
if (StartOffset == null)
|
if (_startOffset == null)
|
||||||
success = false;
|
success = false;
|
||||||
|
|
||||||
else if (Math.Abs((long)StartOffset) > input.Length)
|
else if (Math.Abs((long)_startOffset) > input.Length)
|
||||||
success = false;
|
success = false;
|
||||||
|
|
||||||
else if (StartOffset > 0)
|
else if (_startOffset > 0)
|
||||||
input.Seek((long)StartOffset, SeekOrigin.Begin);
|
input.Seek((long)_startOffset, SeekOrigin.Begin);
|
||||||
|
|
||||||
else if (StartOffset < 0)
|
else if (_startOffset < 0)
|
||||||
input.Seek((long)StartOffset, SeekOrigin.End);
|
input.Seek((long)_startOffset, SeekOrigin.End);
|
||||||
|
|
||||||
// Then read and apply the operation as you go
|
// Then read and apply the operation as you go
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
byte[] buffer = new byte[4];
|
byte[] buffer = new byte[4];
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
while (input.Position < (EndOffset ?? input.Length)
|
while (input.Position < (_endOffset ?? input.Length)
|
||||||
&& input.Position < input.Length)
|
&& input.Position < input.Length)
|
||||||
{
|
{
|
||||||
byte b = br.ReadByte();
|
byte b = br.ReadByte();
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,416 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Xml;
|
|
||||||
using System.Xml.Schema;
|
|
||||||
using System.Xml.Serialization;
|
|
||||||
|
|
||||||
namespace SabreTools.Skippers
|
|
||||||
{
|
|
||||||
/// <remarks>
|
|
||||||
/// It is well worth considering just moving the XML files to code, similar to how RV does it
|
|
||||||
/// if only because nobody really has any skippers outside of this. It would also make the
|
|
||||||
/// output directory cleaner and less prone to user error in case something didn't get copied
|
|
||||||
/// correctly. The contents of these files should still be added to the wiki, in that case.
|
|
||||||
/// </remarks>
|
|
||||||
public class SkipperFile
|
|
||||||
{
|
|
||||||
#region Fields
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Skipper name
|
|
||||||
/// </summary>
|
|
||||||
[XmlElement("name")]
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Author names
|
|
||||||
/// </summary>
|
|
||||||
[XmlElement("author")]
|
|
||||||
public string Author { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// File version
|
|
||||||
/// </summary>
|
|
||||||
[XmlElement("version")]
|
|
||||||
public string Version { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set of all rules in the skipper
|
|
||||||
/// </summary>
|
|
||||||
[XmlArray("rule")]
|
|
||||||
public List<SkipperRule> Rules { get; set; } = new List<SkipperRule>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Filename the skipper lives in
|
|
||||||
/// </summary>
|
|
||||||
[XmlIgnore]
|
|
||||||
public string SourceFile { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create an empty SkipperFile object
|
|
||||||
/// </summary>
|
|
||||||
public SkipperFile() { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a SkipperFile object parsed from an input file
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filename">Name of the file to parse</param>
|
|
||||||
public SkipperFile(string filename)
|
|
||||||
{
|
|
||||||
Rules = new List<SkipperRule>();
|
|
||||||
SourceFile = Path.GetFileNameWithoutExtension(filename);
|
|
||||||
|
|
||||||
XmlReader xtr = XmlReader.Create(filename, new XmlReaderSettings
|
|
||||||
{
|
|
||||||
CheckCharacters = false,
|
|
||||||
DtdProcessing = DtdProcessing.Ignore,
|
|
||||||
IgnoreComments = true,
|
|
||||||
IgnoreWhitespace = true,
|
|
||||||
ValidationFlags = XmlSchemaValidationFlags.None,
|
|
||||||
ValidationType = ValidationType.None,
|
|
||||||
});
|
|
||||||
|
|
||||||
bool valid = Parse(xtr);
|
|
||||||
|
|
||||||
// If we somehow have an invalid file, zero out the fields
|
|
||||||
if (!valid)
|
|
||||||
{
|
|
||||||
Name = null;
|
|
||||||
Author = null;
|
|
||||||
Version = null;
|
|
||||||
Rules = null;
|
|
||||||
SourceFile = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Parsing Helpers
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parse an XML document in as a SkipperFile
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="xtr">XmlReader representing the document</param>
|
|
||||||
/// <returns>True if the file could be parsed, false otherwise</returns>
|
|
||||||
private bool Parse(XmlReader xtr)
|
|
||||||
{
|
|
||||||
if (xtr == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bool valid = false;
|
|
||||||
xtr.MoveToContent();
|
|
||||||
while (!xtr.EOF)
|
|
||||||
{
|
|
||||||
if (xtr.NodeType != XmlNodeType.Element)
|
|
||||||
xtr.Read();
|
|
||||||
|
|
||||||
switch (xtr.Name.ToLowerInvariant())
|
|
||||||
{
|
|
||||||
case "detector":
|
|
||||||
valid = true;
|
|
||||||
xtr.Read();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "name":
|
|
||||||
Name = xtr.ReadElementContentAsString();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "author":
|
|
||||||
Author = xtr.ReadElementContentAsString();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "version":
|
|
||||||
Version = xtr.ReadElementContentAsString();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "rule":
|
|
||||||
SkipperRule rule = ParseRule(xtr);
|
|
||||||
if (rule != null)
|
|
||||||
Rules.Add(rule);
|
|
||||||
|
|
||||||
xtr.Read();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
xtr.Read();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return valid;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parse an XML document in as a SkipperRule
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="xtr">XmlReader representing the document</param>
|
|
||||||
/// <returns>Filled SkipperRule on success, null otherwise</returns>
|
|
||||||
private SkipperRule ParseRule(XmlReader xtr)
|
|
||||||
{
|
|
||||||
if (xtr == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Get the information from the rule first
|
|
||||||
SkipperRule rule = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = null,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>(),
|
|
||||||
SourceFile = this.SourceFile,
|
|
||||||
};
|
|
||||||
|
|
||||||
string startOffset = xtr.GetAttribute("start_offset");
|
|
||||||
if (startOffset != null)
|
|
||||||
{
|
|
||||||
if (startOffset.ToLowerInvariant() == "eof")
|
|
||||||
rule.StartOffset = null;
|
|
||||||
else
|
|
||||||
rule.StartOffset = Convert.ToInt64(startOffset, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
string endOffset = xtr.GetAttribute("end_offset");
|
|
||||||
if (endOffset != null)
|
|
||||||
{
|
|
||||||
if (endOffset.ToLowerInvariant() == "eof")
|
|
||||||
rule.EndOffset = null;
|
|
||||||
else
|
|
||||||
rule.EndOffset = Convert.ToInt64(endOffset, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
string operation = xtr.GetAttribute("operation");
|
|
||||||
if (operation != null)
|
|
||||||
{
|
|
||||||
switch (operation.ToLowerInvariant())
|
|
||||||
{
|
|
||||||
case "bitswap":
|
|
||||||
rule.Operation = HeaderSkipOperation.Bitswap;
|
|
||||||
break;
|
|
||||||
case "byteswap":
|
|
||||||
rule.Operation = HeaderSkipOperation.Byteswap;
|
|
||||||
break;
|
|
||||||
case "wordswap":
|
|
||||||
rule.Operation = HeaderSkipOperation.Wordswap;
|
|
||||||
break;
|
|
||||||
case "wordbyteswap":
|
|
||||||
rule.Operation = HeaderSkipOperation.WordByteswap;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now read the individual tests into the Rule
|
|
||||||
XmlReader subreader = xtr.ReadSubtree();
|
|
||||||
if (subreader != null)
|
|
||||||
{
|
|
||||||
subreader.MoveToContent();
|
|
||||||
while (!subreader.EOF)
|
|
||||||
{
|
|
||||||
if (subreader.NodeType != XmlNodeType.Element)
|
|
||||||
subreader.Read();
|
|
||||||
|
|
||||||
switch (xtr.Name.ToLowerInvariant())
|
|
||||||
{
|
|
||||||
case "data":
|
|
||||||
case "or":
|
|
||||||
case "xor":
|
|
||||||
case "and":
|
|
||||||
case "file":
|
|
||||||
SkipperTest test = ParseTest(subreader);
|
|
||||||
if (test != null)
|
|
||||||
rule.Tests.Add(test);
|
|
||||||
|
|
||||||
subreader.Read();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
subreader.Read();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rule;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parse an XML document in as a SkipperTest
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="xtr">XmlReader representing the document</param>
|
|
||||||
/// <returns>Filled SkipperTest on success, null otherwise</returns>
|
|
||||||
private SkipperTest ParseTest(XmlReader xtr)
|
|
||||||
{
|
|
||||||
if (xtr == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Get the test type
|
|
||||||
SkipperTest test = xtr.Name.ToLowerInvariant() switch
|
|
||||||
{
|
|
||||||
"and" => new AndSkipperTest(),
|
|
||||||
"data" => new DataSkipperTest(),
|
|
||||||
"file" => new FileSkipperTest(),
|
|
||||||
"or" => new OrSkipperTest(),
|
|
||||||
"xor" => new XorSkipperTest(),
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
// If we had an invalid test type
|
|
||||||
if (test == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Set the default values
|
|
||||||
test.Offset = 0;
|
|
||||||
test.Value = Array.Empty<byte>();
|
|
||||||
test.Result = true;
|
|
||||||
test.Mask = Array.Empty<byte>();
|
|
||||||
test.Size = 0;
|
|
||||||
test.Operator = HeaderSkipTestFileOperator.Equal;
|
|
||||||
|
|
||||||
// Now populate all the parts that we can
|
|
||||||
if (xtr.GetAttribute("offset") != null)
|
|
||||||
{
|
|
||||||
string offset = xtr.GetAttribute("offset");
|
|
||||||
if (offset.ToLowerInvariant() == "eof")
|
|
||||||
test.Offset = null;
|
|
||||||
else
|
|
||||||
test.Offset = Convert.ToInt64(offset, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xtr.GetAttribute("value") != null)
|
|
||||||
{
|
|
||||||
string value = xtr.GetAttribute("value");
|
|
||||||
|
|
||||||
// http://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array
|
|
||||||
test.Value = new byte[value.Length / 2];
|
|
||||||
for (int index = 0; index < test.Value.Length; index++)
|
|
||||||
{
|
|
||||||
string byteValue = value.Substring(index * 2, 2);
|
|
||||||
test.Value[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xtr.GetAttribute("result") != null)
|
|
||||||
{
|
|
||||||
string result = xtr.GetAttribute("result");
|
|
||||||
if (!bool.TryParse(result, out bool resultBool))
|
|
||||||
resultBool = true;
|
|
||||||
|
|
||||||
test.Result = resultBool;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xtr.GetAttribute("mask") != null)
|
|
||||||
{
|
|
||||||
string mask = xtr.GetAttribute("mask");
|
|
||||||
|
|
||||||
// http://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array
|
|
||||||
test.Mask = new byte[mask.Length / 2];
|
|
||||||
for (int index = 0; index < test.Mask.Length; index++)
|
|
||||||
{
|
|
||||||
string byteValue = mask.Substring(index * 2, 2);
|
|
||||||
test.Mask[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xtr.GetAttribute("size") != null)
|
|
||||||
{
|
|
||||||
string size = xtr.GetAttribute("size");
|
|
||||||
if (size.ToLowerInvariant() == "po2")
|
|
||||||
test.Size = null;
|
|
||||||
else
|
|
||||||
test.Size = Convert.ToInt64(size, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xtr.GetAttribute("operator") != null)
|
|
||||||
{
|
|
||||||
string oper = xtr.GetAttribute("operator");
|
|
||||||
test.Operator = oper.ToLowerInvariant() switch
|
|
||||||
{
|
|
||||||
"less" => HeaderSkipTestFileOperator.Less,
|
|
||||||
"greater" => HeaderSkipTestFileOperator.Greater,
|
|
||||||
"equal" => HeaderSkipTestFileOperator.Equal,
|
|
||||||
_ => HeaderSkipTestFileOperator.Equal,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return test;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Matching
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the SkipperRule 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 SkipperRule that matched the stream, null otherwise</returns>
|
|
||||||
public SkipperRule GetMatchingRule(Stream input, string skipperName)
|
|
||||||
{
|
|
||||||
// If we have no name supplied, try to blindly match
|
|
||||||
if (string.IsNullOrWhiteSpace(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 SkipperRule from all Rules, if possible
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">Stream to be checked</param>
|
|
||||||
/// <returns>The SkipperRule that matched the stream, null otherwise</returns>
|
|
||||||
private SkipperRule GetMatchingRule(Stream input)
|
|
||||||
{
|
|
||||||
// Loop through the rules until one is found that works
|
|
||||||
foreach (SkipperRule rule in Rules)
|
|
||||||
{
|
|
||||||
// Always reset the stream back to the original place
|
|
||||||
input.Seek(0, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
// If all tests in the rule pass, we return this rule
|
|
||||||
if (rule.PassesAllTests(input))
|
|
||||||
return rule;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If nothing passed, we return null by default
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace SabreTools.Skippers.SkipperFiles
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// SkipperFile for Atari 7800 headers
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Originally from a7800.xml</remarks>
|
|
||||||
internal class Atari7800 : SkipperFile
|
|
||||||
{
|
|
||||||
public Atari7800()
|
|
||||||
{
|
|
||||||
// Create tests
|
|
||||||
var rule1Test1 = new DataSkipperTest
|
|
||||||
{
|
|
||||||
Offset = 0x01,
|
|
||||||
Value = new byte[] { 0x41, 0x54, 0x41, 0x52, 0x49, 0x37, 0x38, 0x30, 0x30 },
|
|
||||||
Result = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule2Test1 = new DataSkipperTest
|
|
||||||
{
|
|
||||||
Offset = 0x64,
|
|
||||||
Value = new byte[] { 0x41, 0x43, 0x54, 0x55, 0x41, 0x4C, 0x20, 0x43, 0x41, 0x52, 0x54, 0x20, 0x44, 0x41, 0x54, 0x41, 0x20, 0x53, 0x54, 0x41, 0x52, 0x54, 0x53, 0x20, 0x48, 0x45, 0x52, 0x45 },
|
|
||||||
Result = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create rules
|
|
||||||
var rule1 = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = 0x80,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>
|
|
||||||
{
|
|
||||||
rule1Test1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule2 = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = 0x80,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>
|
|
||||||
{
|
|
||||||
rule2Test1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create file
|
|
||||||
Name = "Atari 7800";
|
|
||||||
Author = "Roman Scherzer";
|
|
||||||
Version = "1.0";
|
|
||||||
SourceFile = "a7800";
|
|
||||||
Rules = new List<SkipperRule>
|
|
||||||
{
|
|
||||||
rule1,
|
|
||||||
rule2,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace SabreTools.Skippers.SkipperFiles
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// SkipperFile for Commodore PSID headers
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Originally from psid.xml</remarks>
|
|
||||||
internal class CommodorePSID : SkipperFile
|
|
||||||
{
|
|
||||||
public CommodorePSID()
|
|
||||||
{
|
|
||||||
// Create tests
|
|
||||||
var rule1Test1 = new DataSkipperTest
|
|
||||||
{
|
|
||||||
Offset = 0x00,
|
|
||||||
Value = new byte[] { 0x50, 0x53, 0x49, 0x44, 0x00, 0x01, 0x00, 0x76 },
|
|
||||||
Result = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule2Test1 = new DataSkipperTest
|
|
||||||
{
|
|
||||||
Offset = 0x00,
|
|
||||||
Value = new byte[] { 0x50, 0x53, 0x49, 0x44, 0x00, 0x03, 0x00, 0x7c },
|
|
||||||
Result = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule3Test1 = new DataSkipperTest
|
|
||||||
{
|
|
||||||
Offset = 0x00,
|
|
||||||
Value = new byte[] { 0x50, 0x53, 0x49, 0x44, 0x00, 0x02, 0x00, 0x7c },
|
|
||||||
Result = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule4Test1 = new DataSkipperTest
|
|
||||||
{
|
|
||||||
Offset = 0x00,
|
|
||||||
Value = new byte[] { 0x50, 0x53, 0x49, 0x44, 0x00, 0x01, 0x00, 0x7c },
|
|
||||||
Result = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule5Test1 = new DataSkipperTest
|
|
||||||
{
|
|
||||||
Offset = 0x00,
|
|
||||||
Value = new byte[] { 0x52, 0x53, 0x49, 0x44, 0x00, 0x02, 0x00, 0x7c },
|
|
||||||
Result = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create rules
|
|
||||||
var rule1 = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = 0x76,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>
|
|
||||||
{
|
|
||||||
rule1Test1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule2 = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = 0x76,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>
|
|
||||||
{
|
|
||||||
rule2Test1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule3 = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = 0x7c,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>
|
|
||||||
{
|
|
||||||
rule3Test1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule4 = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = 0x7c,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>
|
|
||||||
{
|
|
||||||
rule4Test1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule5 = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = 0x7c,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>
|
|
||||||
{
|
|
||||||
rule5Test1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create file
|
|
||||||
Name = "psid";
|
|
||||||
Author = "Yori Yoshizuki";
|
|
||||||
Version = "1.2";
|
|
||||||
SourceFile = "psid";
|
|
||||||
Rules = new List<SkipperRule>
|
|
||||||
{
|
|
||||||
rule1,
|
|
||||||
rule2,
|
|
||||||
rule3,
|
|
||||||
rule4,
|
|
||||||
rule5,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace SabreTools.Skippers.SkipperFiles
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// SkipperFile for Nintendo Famicom Disk System headers
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Originally from fds.xml</remarks>
|
|
||||||
internal class NintendoFamicomDiskSystem : SkipperFile
|
|
||||||
{
|
|
||||||
public NintendoFamicomDiskSystem()
|
|
||||||
{
|
|
||||||
// Create tests
|
|
||||||
var rule1Test1 = new DataSkipperTest
|
|
||||||
{
|
|
||||||
Offset = 0x00,
|
|
||||||
Value = new byte[] { 0x46, 0x44, 0x53, 0x1A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
|
|
||||||
Result = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule2Test1 = new DataSkipperTest
|
|
||||||
{
|
|
||||||
Offset = 0x00,
|
|
||||||
Value = new byte[] { 0x46, 0x44, 0x53, 0x1A, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
|
|
||||||
Result = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule3Test1 = new DataSkipperTest
|
|
||||||
{
|
|
||||||
Offset = 0x00,
|
|
||||||
Value = new byte[] { 0x46, 0x44, 0x53, 0x1A, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
|
|
||||||
Result = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule4Test1 = new DataSkipperTest
|
|
||||||
{
|
|
||||||
Offset = 0x00,
|
|
||||||
Value = new byte[] { 0x46, 0x44, 0x53, 0x1A, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
|
|
||||||
Result = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create rules
|
|
||||||
var rule1 = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = 0x10,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>
|
|
||||||
{
|
|
||||||
rule1Test1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule2 = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = 0x10,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>
|
|
||||||
{
|
|
||||||
rule2Test1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule3 = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = 0x10,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>
|
|
||||||
{
|
|
||||||
rule3Test1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule4 = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = 0x10,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>
|
|
||||||
{
|
|
||||||
rule4Test1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create file
|
|
||||||
Name = "fds";
|
|
||||||
Author = "Yori Yoshizuki";
|
|
||||||
Version = "1.0";
|
|
||||||
SourceFile = "fds";
|
|
||||||
Rules = new List<SkipperRule>
|
|
||||||
{
|
|
||||||
rule1,
|
|
||||||
rule2,
|
|
||||||
rule3,
|
|
||||||
rule4,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace SabreTools.Skippers.SkipperFiles
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// SkipperFile for Super Nintendo Entertainment System headers
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Originally from snes.xml</remarks>
|
|
||||||
internal class SuperNintendoEntertainmentSystem : SkipperFile
|
|
||||||
{
|
|
||||||
public SuperNintendoEntertainmentSystem()
|
|
||||||
{
|
|
||||||
// Create tests
|
|
||||||
var rule1Test1 = new DataSkipperTest
|
|
||||||
{
|
|
||||||
Offset = 0x16,
|
|
||||||
Value = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
|
|
||||||
Result = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule2Test1 = new DataSkipperTest
|
|
||||||
{
|
|
||||||
Offset = 0x16,
|
|
||||||
Value = new byte[] { 0xAA, 0xBB, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 },
|
|
||||||
Result = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule3Test1 = new DataSkipperTest
|
|
||||||
{
|
|
||||||
Offset = 0x16,
|
|
||||||
Value = new byte[] { 0x53, 0x55, 0x50, 0x45, 0x52, 0x55, 0x46, 0x4F },
|
|
||||||
Result = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create rules
|
|
||||||
var rule1 = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = 0x200,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>
|
|
||||||
{
|
|
||||||
rule1Test1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule2 = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = 0x200,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>
|
|
||||||
{
|
|
||||||
rule2Test1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var rule3 = new SkipperRule
|
|
||||||
{
|
|
||||||
StartOffset = 0x200,
|
|
||||||
EndOffset = null,
|
|
||||||
Operation = HeaderSkipOperation.None,
|
|
||||||
Tests = new List<SkipperTest>
|
|
||||||
{
|
|
||||||
rule3Test1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create file
|
|
||||||
Name = "Nintendo Super Famicom/SNES";
|
|
||||||
Author = "Matt Nadareski (darksabre76)";
|
|
||||||
Version = "1.0";
|
|
||||||
SourceFile = "snes";
|
|
||||||
Rules = new List<SkipperRule>
|
|
||||||
{
|
|
||||||
rule1, // FIG
|
|
||||||
rule2, // SMC
|
|
||||||
rule3, // UFO
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Schema;
|
||||||
|
using System.Xml.Serialization;
|
||||||
using SabreTools.IO;
|
using SabreTools.IO;
|
||||||
using SabreTools.Logging;
|
using SabreTools.Logging;
|
||||||
|
|
||||||
@@ -23,9 +26,9 @@ namespace SabreTools.Skippers
|
|||||||
public static class SkipperMatch
|
public static class SkipperMatch
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Header skippers represented by a list of skipper objects
|
/// Header detectors represented by a list of detector objects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static List<SkipperFile> Skippers = null;
|
private static List<Detector>? Skippers = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Local paths
|
/// Local paths
|
||||||
@@ -37,7 +40,7 @@ namespace SabreTools.Skippers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Logging object
|
/// Logging object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Logger logger = new Logger();
|
private static readonly Logger logger = new();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -70,13 +73,43 @@ namespace SabreTools.Skippers
|
|||||||
private static void PopulateSkippers()
|
private static void PopulateSkippers()
|
||||||
{
|
{
|
||||||
// Ensure the list exists
|
// Ensure the list exists
|
||||||
if (Skippers == null)
|
Skippers ??= new List<Detector>();
|
||||||
Skippers = new List<SkipperFile>();
|
|
||||||
|
// Create the XML serializer
|
||||||
|
var xts = new XmlSerializer(typeof(Detector));
|
||||||
|
|
||||||
// Get skippers for each known header type
|
// Get skippers for each known header type
|
||||||
foreach (string skipperFile in Directory.EnumerateFiles(LocalPath, "*", SearchOption.AllDirectories))
|
foreach (string skipperPath in Directory.EnumerateFiles(LocalPath, "*", SearchOption.AllDirectories))
|
||||||
{
|
{
|
||||||
Skippers.Add(new SkipperFile(Path.GetFullPath(skipperFile)));
|
try
|
||||||
|
{
|
||||||
|
// Create the XML reader
|
||||||
|
var xtr = XmlReader.Create(skipperPath, new XmlReaderSettings
|
||||||
|
{
|
||||||
|
CheckCharacters = false,
|
||||||
|
DtdProcessing = DtdProcessing.Ignore,
|
||||||
|
IgnoreComments = true,
|
||||||
|
IgnoreWhitespace = true,
|
||||||
|
ValidationFlags = XmlSchemaValidationFlags.None,
|
||||||
|
ValidationType = ValidationType.None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Deserialize the detector, if possible
|
||||||
|
if (xts.Deserialize(xtr) is not Detector detector)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Set the source file
|
||||||
|
string sourceFile = Path.GetFileNameWithoutExtension(skipperPath);
|
||||||
|
detector.SourceFile = sourceFile;
|
||||||
|
for (int i = 0; i < (detector.Rules?.Length ?? 0); i++)
|
||||||
|
{
|
||||||
|
detector.Rules[i].SourceFile = sourceFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the skipper to the set
|
||||||
|
Skippers.Add(detector);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,63 +123,64 @@ namespace SabreTools.Skippers
|
|||||||
private static void PopulateSkippersInternal()
|
private static void PopulateSkippersInternal()
|
||||||
{
|
{
|
||||||
// Ensure the list exists
|
// Ensure the list exists
|
||||||
if (Skippers == null)
|
Skippers ??= new List<Detector>();
|
||||||
Skippers = new List<SkipperFile>();
|
|
||||||
|
|
||||||
// Get skippers for each known header type
|
// Get skippers for each known header type
|
||||||
Skippers.Add(new SkipperFiles.Atari7800());
|
Skippers.Add(new Detectors.Atari7800());
|
||||||
Skippers.Add(new SkipperFiles.AtariLynx());
|
Skippers.Add(new Detectors.AtariLynx());
|
||||||
Skippers.Add(new SkipperFiles.CommodorePSID());
|
Skippers.Add(new Detectors.CommodorePSID());
|
||||||
Skippers.Add(new SkipperFiles.NECPCEngine());
|
Skippers.Add(new Detectors.NECPCEngine());
|
||||||
Skippers.Add(new SkipperFiles.Nintendo64());
|
Skippers.Add(new Detectors.Nintendo64());
|
||||||
Skippers.Add(new SkipperFiles.NintendoEntertainmentSystem());
|
Skippers.Add(new Detectors.NintendoEntertainmentSystem());
|
||||||
Skippers.Add(new SkipperFiles.NintendoFamicomDiskSystem());
|
Skippers.Add(new Detectors.NintendoFamicomDiskSystem());
|
||||||
Skippers.Add(new SkipperFiles.SuperNintendoEntertainmentSystem());
|
Skippers.Add(new Detectors.SuperNintendoEntertainmentSystem());
|
||||||
Skippers.Add(new SkipperFiles.SuperFamicomSPC());
|
Skippers.Add(new Detectors.SuperFamicomSPC());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the SkipperRule associated with a given file
|
/// Get the Rule associated with a given file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">Name of the file to be checked</param>
|
/// <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="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>
|
/// <param name="logger">Logger object for file and console output</param>
|
||||||
/// <returns>The SkipperRule that matched the file</returns>
|
/// <returns>The Rule that matched the file</returns>
|
||||||
public static SkipperRule GetMatchingRule(string input, string skipperName)
|
public static Rule GetMatchingRule(string input, string skipperName)
|
||||||
{
|
{
|
||||||
// If the file doesn't exist, return a blank skipper rule
|
// If the file doesn't exist, return a blank skipper rule
|
||||||
if (!File.Exists(input))
|
if (!File.Exists(input))
|
||||||
{
|
{
|
||||||
logger.Error($"The file '{input}' does not exist so it cannot be tested");
|
logger.Error($"The file '{input}' does not exist so it cannot be tested");
|
||||||
return new SkipperRule();
|
return new Rule();
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetMatchingRule(File.OpenRead(input), skipperName);
|
return GetMatchingRule(File.OpenRead(input), skipperName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the SkipperRule associated with a given stream
|
/// Get the Rule associated with a given stream
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">Name of the file to be checked</param>
|
/// <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="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>
|
/// <param name="keepOpen">True if the underlying stream should be kept open, false otherwise</param>
|
||||||
/// <returns>The SkipperRule that matched the file</returns>
|
/// <returns>The Rule that matched the file</returns>
|
||||||
public static SkipperRule GetMatchingRule(Stream input, string skipperName, bool keepOpen = false)
|
public static Rule GetMatchingRule(Stream input, string skipperName, bool keepOpen = false)
|
||||||
{
|
{
|
||||||
SkipperRule skipperRule = new SkipperRule();
|
var skipperRule = new Rule();
|
||||||
|
|
||||||
// If we have a null skipper name, we return since we're not matching skippers
|
// If we have an invalid set of skippers or skipper name
|
||||||
if (skipperName == null)
|
if (Skippers == null || skipperName == null)
|
||||||
return skipperRule;
|
return skipperRule;
|
||||||
|
|
||||||
// Loop through and find a Skipper that has the right name
|
// Loop through and find a Skipper that has the right name
|
||||||
logger.Verbose("Beginning search for matching header skip rules");
|
logger.Verbose("Beginning search for matching header skip rules");
|
||||||
List<SkipperFile> tempList = new List<SkipperFile>();
|
|
||||||
tempList.AddRange(Skippers);
|
|
||||||
|
|
||||||
// Loop through all known SkipperFiles
|
// Loop through all known Detectors
|
||||||
foreach (SkipperFile skipper in tempList)
|
foreach (Detector? skipper in Skippers)
|
||||||
{
|
{
|
||||||
|
// This should not happen
|
||||||
|
if (skipper == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
skipperRule = skipper.GetMatchingRule(input, skipperName);
|
skipperRule = skipper.GetMatchingRule(input, skipperName);
|
||||||
if (skipperRule != null)
|
if (skipperRule != null)
|
||||||
break;
|
break;
|
||||||
@@ -156,9 +190,8 @@ namespace SabreTools.Skippers
|
|||||||
if (!keepOpen)
|
if (!keepOpen)
|
||||||
input.Dispose();
|
input.Dispose();
|
||||||
|
|
||||||
// If the SkipperRule is null, make it empty
|
// If the Rule is null, make it empty
|
||||||
if (skipperRule == null)
|
skipperRule ??= new Rule();
|
||||||
skipperRule = new SkipperRule();
|
|
||||||
|
|
||||||
// If we have a blank rule, inform the user
|
// If we have a blank rule, inform the user
|
||||||
if (skipperRule.Tests == null)
|
if (skipperRule.Tests == null)
|
||||||
|
|||||||
@@ -1,303 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Xml;
|
|
||||||
using System.Xml.Serialization;
|
|
||||||
|
|
||||||
namespace SabreTools.Skippers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Individual test that applies to a SkipperRule
|
|
||||||
/// </summary>
|
|
||||||
public abstract class SkipperTest
|
|
||||||
{
|
|
||||||
#region Fields
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// File offset to run the test
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>null is EOF</remarks>
|
|
||||||
[XmlAttribute("offset")]
|
|
||||||
public long? Offset { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Static value to be checked at the offset
|
|
||||||
/// </summary>
|
|
||||||
[XmlAttribute("value")]
|
|
||||||
public byte[] Value { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether a pass or failure is expected
|
|
||||||
/// </summary>
|
|
||||||
[XmlAttribute("result")]
|
|
||||||
public bool Result { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Byte mask to be applied to the tested bytes
|
|
||||||
/// </summary>
|
|
||||||
[XmlAttribute("mask")]
|
|
||||||
public byte[] Mask { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Expected size of the input byte array, used with the Operator
|
|
||||||
/// </summary>
|
|
||||||
[XmlAttribute("size")]
|
|
||||||
public long? Size { get; set; } // null is PO2, "power of 2" filesize
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Expected range value for the input byte array size, used with Size
|
|
||||||
/// </summary>
|
|
||||||
[XmlAttribute("operator")]
|
|
||||||
public HeaderSkipTestFileOperator Operator { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <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 Checking Helpers
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Seek an input stream based on the test value
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">Stream to seek</param>
|
|
||||||
/// <returns>True if the stream could seek, false on error</returns>
|
|
||||||
protected bool Seek(Stream input)
|
|
||||||
{
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Skipper test using AND
|
|
||||||
/// </summary>
|
|
||||||
[XmlType("and")]
|
|
||||||
public class AndSkipperTest : SkipperTest
|
|
||||||
{
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override bool Passes(Stream input)
|
|
||||||
{
|
|
||||||
// First seek to the correct position
|
|
||||||
Seek(input);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Skipper test using DATA
|
|
||||||
/// </summary>
|
|
||||||
[XmlType("data")]
|
|
||||||
public class DataSkipperTest : SkipperTest
|
|
||||||
{
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override bool Passes(Stream input)
|
|
||||||
{
|
|
||||||
// First seek to the correct position
|
|
||||||
if (!Seek(input))
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Skipper test using FILE
|
|
||||||
/// </summary>
|
|
||||||
[XmlType("file")]
|
|
||||||
public class FileSkipperTest : SkipperTest
|
|
||||||
{
|
|
||||||
/// <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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Skipper test using OR
|
|
||||||
/// </summary>
|
|
||||||
[XmlType("or")]
|
|
||||||
public class OrSkipperTest : SkipperTest
|
|
||||||
{
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override bool Passes(Stream input)
|
|
||||||
{
|
|
||||||
// First seek to the correct position
|
|
||||||
Seek(input);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Skipper test using XOR
|
|
||||||
/// </summary>
|
|
||||||
[XmlType("xor")]
|
|
||||||
public class XorSkipperTest : SkipperTest
|
|
||||||
{
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override bool Passes(Stream input)
|
|
||||||
{
|
|
||||||
// First seek to the correct position
|
|
||||||
Seek(input);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
73
SabreTools.Skippers/Test.cs
Normal file
73
SabreTools.Skippers/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.IsNullOrWhiteSpace(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
|
||||||
|
}
|
||||||
|
}
|
||||||
130
SabreTools.Skippers/Tests/AndTest.cs
Normal file
130
SabreTools.Skippers/Tests/AndTest.cs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
SabreTools.Skippers/Tests/DataTest.cs
Normal file
101
SabreTools.Skippers/Tests/DataTest.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
87
SabreTools.Skippers/Tests/FileTest.cs
Normal file
87
SabreTools.Skippers/Tests/FileTest.cs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
130
SabreTools.Skippers/Tests/OrTest.cs
Normal file
130
SabreTools.Skippers/Tests/OrTest.cs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
130
SabreTools.Skippers/Tests/XorTest.cs
Normal file
130
SabreTools.Skippers/Tests/XorTest.cs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,9 +6,9 @@ using Xunit;
|
|||||||
namespace SabreTools.Test.Skippers
|
namespace SabreTools.Test.Skippers
|
||||||
{
|
{
|
||||||
[Collection("SkipperMatch")]
|
[Collection("SkipperMatch")]
|
||||||
public class SkipperRuleTransformTests
|
public class RuleTransformTests
|
||||||
{
|
{
|
||||||
public SkipperRuleTransformTests()
|
public RuleTransformTests()
|
||||||
{
|
{
|
||||||
SkipperMatch.Init(false);
|
SkipperMatch.Init(false);
|
||||||
}
|
}
|
||||||
@@ -231,7 +231,7 @@ namespace SabreTools.Test.Skippers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pad the stream to 1KiB then seek to beginning
|
/// Pad the stream to 1KiB then seek to beginning
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void PadAndReset(MemoryStream ms)
|
private static void PadAndReset(MemoryStream ms)
|
||||||
{
|
{
|
||||||
for (long i = ms.Length; i < 1024; i++)
|
for (long i = ms.Length; i < 1024; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -77,10 +77,10 @@ The following systems have headers that this program can work with:
|
|||||||
|
|
||||||
// Get the skipper rule that matches the file, if any
|
// Get the skipper rule that matches the file, if any
|
||||||
SkipperMatch.Init();
|
SkipperMatch.Init();
|
||||||
SkipperRule rule = SkipperMatch.GetMatchingRule(file, string.Empty);
|
Rule rule = SkipperMatch.GetMatchingRule(file, string.Empty);
|
||||||
|
|
||||||
// If we have an empty rule, return false
|
// If we have an empty rule, return false
|
||||||
if (rule.Tests == null || rule.Tests.Count == 0 || rule.Operation != HeaderSkipOperation.None)
|
if (rule.Tests == null || rule.Tests.Length == 0 || rule.Operation != HeaderSkipOperation.None)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
logger.User("File has a valid copier header");
|
logger.User("File has a valid copier header");
|
||||||
@@ -91,8 +91,8 @@ The following systems have headers that this program can work with:
|
|||||||
{
|
{
|
||||||
// Extract the header as a string for the database
|
// Extract the header as a string for the database
|
||||||
using var fs = File.OpenRead(file);
|
using var fs = File.OpenRead(file);
|
||||||
byte[] hbin = new byte[(int)rule.StartOffset];
|
byte[] hbin = new byte[int.Parse(rule.StartOffset)];
|
||||||
fs.Read(hbin, 0, (int)rule.StartOffset);
|
fs.Read(hbin, 0, int.Parse(rule.StartOffset));
|
||||||
hstr = Utilities.ByteArrayToString(hbin);
|
hstr = Utilities.ByteArrayToString(hbin);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
Reference in New Issue
Block a user