diff --git a/BurnOutSharp.Matching/ContentMatch.cs b/BurnOutSharp.Matching/ContentMatch.cs
index 36602a03..95e53431 100644
--- a/BurnOutSharp.Matching/ContentMatch.cs
+++ b/BurnOutSharp.Matching/ContentMatch.cs
@@ -1,3 +1,5 @@
+using System.IO;
+
namespace BurnOutSharp.Matching
{
///
@@ -33,7 +35,7 @@ namespace BurnOutSharp.Matching
End = end;
}
- #region Matching
+ #region Array Matching
///
/// Get if this match can be found in a stack
@@ -91,6 +93,7 @@ namespace BurnOutSharp.Matching
if (Needle.Length > stack.Length - index)
return false;
+ // Loop through and check the value
for (int i = 0; i < Needle.Length; i++)
{
// A null value is a wildcard
@@ -102,7 +105,96 @@ namespace BurnOutSharp.Matching
return true;
}
-
+
+ #endregion
+
+ #region Stream Matching
+
+ ///
+ /// Get if this match can be found in a stack
+ ///
+ /// Stream to search for the given content
+ /// True to search from the end of the array, false from the start
+ /// Tuple of success and found position
+ public (bool success, int position) Match(Stream stack, bool reverse = false)
+ {
+ // If either array is null or empty, we can't do anything
+ if (stack == null || stack.Length == 0 || Needle == null || Needle.Length == 0)
+ return (false, -1);
+
+ // If the needle array is larger than the stack array, it can't be contained within
+ if (Needle.Length > stack.Length)
+ return (false, -1);
+
+ // Set the default start and end values
+ int start = Start;
+ int end = End;
+
+ // If start or end are not set properly, set them to defaults
+ if (start < 0)
+ start = 0;
+ if (end < 0)
+ end = (int)(stack.Length - Needle.Length);
+
+ for (int i = reverse ? end : start; reverse ? i > start : i < end; i += reverse ? -1 : 1)
+ {
+ // If we somehow have an invalid end and we haven't matched, return
+ if (i > stack.Length)
+ return (false, -1);
+
+ // Check to see if the values are equal
+ if (EqualAt(stack, i))
+ return (true, i);
+ }
+
+ return (false, -1);
+ }
+
+ ///
+ /// Get if a stack at a certain index is equal to a needle
+ ///
+ /// Stream to search for the given content
+ /// Starting index to check equality
+ /// True if the needle matches the stack at a given index
+ private bool EqualAt(Stream stack, int index)
+ {
+ // If the index is invalid, we can't do anything
+ if (index < 0)
+ return false;
+
+ // If we're too close to the end of the stack, return false
+ if (Needle.Length > stack.Length - index)
+ return false;
+
+ // Save the current position and move to the index
+ long currentPosition = stack.Position;
+ stack.Seek(index, SeekOrigin.Begin);
+
+ // Set the return value
+ bool matched = true;
+
+ // Loop through and check the value
+ for (int i = 0; i < Needle.Length; i++)
+ {
+ byte stackValue = (byte)stack.ReadByte();
+
+ // A null value is a wildcard
+ if (Needle[i] == null)
+ {
+ continue;
+ }
+ else if (stackValue != Needle[i])
+ {
+ matched = false;
+ break;
+ }
+ }
+
+ // Reset the position and return the value
+ stack.Seek(currentPosition, SeekOrigin.Begin);
+ return matched;
+ }
+
#endregion
}
}
\ No newline at end of file
diff --git a/BurnOutSharp.Matching/ContentMatchSet.cs b/BurnOutSharp.Matching/ContentMatchSet.cs
index 8930852c..9b17427e 100644
--- a/BurnOutSharp.Matching/ContentMatchSet.cs
+++ b/BurnOutSharp.Matching/ContentMatchSet.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
namespace BurnOutSharp.Matching
@@ -19,48 +20,84 @@ namespace BurnOutSharp.Matching
/// to the protection name, or `null`, in which case it will cause
/// the protection to be omitted.
///
- public Func, string> GetVersion { get; set; }
+ public Func, string> GetArrayVersion { get; set; }
- #region Constructors
+ ///
+ /// Function to get a content version
+ ///
+ ///
+ /// A content version method takes the file path, the file contents,
+ /// and a list of found positions and returns a single string. That
+ /// string is either a version string, in which case it will be appended
+ /// to the protection name, or `null`, in which case it will cause
+ /// the protection to be omitted.
+ ///
+ public Func, string> GetStreamVersion { get; set; }
+
+ #region Generic Constructors
public ContentMatchSet(byte?[] needle, string protectionName)
- : this(new List { needle }, null, protectionName) { }
+ : this(new List { needle }, getArrayVersion: null, protectionName) { }
public ContentMatchSet(List needles, string protectionName)
- : this(needles, null, protectionName) { }
-
- public ContentMatchSet(byte?[] needle, Func, string> getVersion, string protectionName)
- : this(new List { needle }, getVersion, protectionName) { }
-
- public ContentMatchSet(List needles, Func, string> getVersion, string protectionName)
- : this(needles.Select(n => new ContentMatch(n)).ToList(), getVersion, protectionName) { }
+ : this(needles, getArrayVersion: null, protectionName) { }
public ContentMatchSet(ContentMatch needle, string protectionName)
- : this(new List() { needle }, null, protectionName) { }
+ : this(new List() { needle }, getArrayVersion: null, protectionName) { }
public ContentMatchSet(List needles, string protectionName)
- : this(needles, null, protectionName) { }
+ : this(needles, getArrayVersion: null, protectionName) { }
- public ContentMatchSet(ContentMatch needle, Func, string> getVersion, string protectionName)
- : this(new List() { needle }, getVersion, protectionName) { }
+ #endregion
- public ContentMatchSet(List needles, Func, string> getVersion, string protectionName)
+ #region Array Constructors
+
+ public ContentMatchSet(byte?[] needle, Func, string> getArrayVersion, string protectionName)
+ : this(new List { needle }, getArrayVersion, protectionName) { }
+
+ public ContentMatchSet(List needles, Func, string> getArrayVersion, string protectionName)
+ : this(needles.Select(n => new ContentMatch(n)).ToList(), getArrayVersion, protectionName) { }
+
+ public ContentMatchSet(ContentMatch needle, Func, string> getArrayVersion, string protectionName)
+ : this(new List() { needle }, getArrayVersion, protectionName) { }
+
+ public ContentMatchSet(List needles, Func, string> getArrayVersion, string protectionName)
{
Matchers = needles;
- GetVersion = getVersion;
+ GetArrayVersion = getArrayVersion;
ProtectionName = protectionName;
}
#endregion
- #region Matching
+ #region Stream Constructors
+
+ public ContentMatchSet(byte?[] needle, Func, string> getStreamVersion, string protectionName)
+ : this(new List { needle }, getStreamVersion, protectionName) { }
+
+ public ContentMatchSet(List needles, Func, string> getStreamVersion, string protectionName)
+ : this(needles.Select(n => new ContentMatch(n)).ToList(), getStreamVersion, protectionName) { }
+
+ public ContentMatchSet(ContentMatch needle, Func, string> getStreamVersion, string protectionName)
+ : this(new List() { needle }, getStreamVersion, protectionName) { }
+
+ public ContentMatchSet(List needles, Func, string> getStreamVersion, string protectionName)
+ {
+ Matchers = needles;
+ GetStreamVersion = getStreamVersion;
+ ProtectionName = protectionName;
+ }
+
+ #endregion
+
+ #region Array Matching
///
/// Determine whether all content matches pass
///
- /// Byte array representing the file contents
+ /// Array to search
/// Tuple of passing status and matching positions
- public (bool, List) MatchesAll(byte[] fileContent)
+ public (bool, List) MatchesAll(byte[] stack)
{
// If no content matches are defined, we fail out
if (Matchers == null || !Matchers.Any())
@@ -72,7 +109,7 @@ namespace BurnOutSharp.Matching
// Loop through all content matches and make sure all pass
foreach (var contentMatch in Matchers)
{
- (bool match, int position) = contentMatch.Match(fileContent);
+ (bool match, int position) = contentMatch.Match(stack);
if (!match)
return (false, new List());
else
@@ -85,9 +122,9 @@ namespace BurnOutSharp.Matching
///
/// Determine whether any content matches pass
///
- /// Byte array representing the file contents
+ /// Array to search
/// Tuple of passing status and first matching position
- public (bool, int) MatchesAny(byte[] fileContent)
+ public (bool, int) MatchesAny(byte[] stack)
{
// If no content matches are defined, we fail out
if (Matchers == null || !Matchers.Any())
@@ -96,7 +133,60 @@ namespace BurnOutSharp.Matching
// Loop through all content matches and make sure all pass
foreach (var contentMatch in Matchers)
{
- (bool match, int position) = contentMatch.Match(fileContent);
+ (bool match, int position) = contentMatch.Match(stack);
+ if (match)
+ return (true, position);
+ }
+
+ return (false, -1);
+ }
+
+ #endregion
+
+ #region Stream Matching
+
+ ///
+ /// Determine whether all content matches pass
+ ///
+ /// Stream to search
+ /// Tuple of passing status and matching positions
+ public (bool, List) MatchesAll(Stream stack)
+ {
+ // If no content matches are defined, we fail out
+ if (Matchers == null || !Matchers.Any())
+ return (false, new List());
+
+ // Initialize the position list
+ List positions = new List();
+
+ // Loop through all content matches and make sure all pass
+ foreach (var contentMatch in Matchers)
+ {
+ (bool match, int position) = contentMatch.Match(stack);
+ if (!match)
+ return (false, new List());
+ else
+ positions.Add(position);
+ }
+
+ return (true, positions);
+ }
+
+ ///
+ /// Determine whether any content matches pass
+ ///
+ /// Stream to search
+ /// Tuple of passing status and first matching position
+ public (bool, int) MatchesAny(Stream stack)
+ {
+ // If no content matches are defined, we fail out
+ if (Matchers == null || !Matchers.Any())
+ return (false, -1);
+
+ // Loop through all content matches and make sure all pass
+ foreach (var contentMatch in Matchers)
+ {
+ (bool match, int position) = contentMatch.Match(stack);
if (match)
return (true, position);
}
diff --git a/BurnOutSharp.Matching/MatchUtil.cs b/BurnOutSharp.Matching/MatchUtil.cs
index 9dc9e537..4f116688 100644
--- a/BurnOutSharp.Matching/MatchUtil.cs
+++ b/BurnOutSharp.Matching/MatchUtil.cs
@@ -1,5 +1,6 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
namespace BurnOutSharp.Matching
@@ -9,40 +10,40 @@ namespace BurnOutSharp.Matching
///
public static class MatchUtil
{
- #region Content Matching
+ #region Array Content Matching
///
/// Get all content matches for a given list of matchers
///
/// File to check for matches
- /// Byte array representing the file contents
+ /// Array to search
/// Enumerable of ContentMatchSets to be run on the file
/// True to include positional data, false otherwise
/// List of strings representing the matched protections, null or empty otherwise
public static ConcurrentQueue GetAllMatches(
string file,
- byte[] fileContent,
+ byte[] stack,
IEnumerable matchers,
bool includeDebug = false)
{
- return FindAllMatches(file, fileContent, matchers, includeDebug, false);
+ return FindAllMatches(file, stack, matchers, includeDebug, false);
}
///
/// Get first content match for a given list of matchers
///
/// File to check for matches
- /// Byte array representing the file contents
+ /// Array to search
/// Enumerable of ContentMatchSets to be run on the file
/// True to include positional data, false otherwise
/// String representing the matched protection, null otherwise
public static string GetFirstMatch(
string file,
- byte[] fileContent,
+ byte[] stack,
IEnumerable matchers,
bool includeDebug = false)
{
- var contentMatches = FindAllMatches(file, fileContent, matchers, includeDebug, true);
+ var contentMatches = FindAllMatches(file, stack, matchers, includeDebug, true);
if (contentMatches == null || !contentMatches.Any())
return null;
@@ -53,14 +54,14 @@ namespace BurnOutSharp.Matching
/// Get the required set of content matches on a per Matcher basis
///
/// File to check for matches
- /// Byte array representing the file contents
+ /// Array to search
/// Enumerable of ContentMatchSets to be run on the file
/// True to include positional data, false otherwise
/// True to stop after the first match, false otherwise
/// List of strings representing the matched protections, null or empty otherwise
private static ConcurrentQueue FindAllMatches(
string file,
- byte[] fileContent,
+ byte[] stack,
IEnumerable matchers,
bool includeDebug,
bool stopAfterFirst)
@@ -76,7 +77,7 @@ namespace BurnOutSharp.Matching
foreach (var matcher in matchers)
{
// Determine if the matcher passes
- (bool passes, List positions) = matcher.MatchesAll(fileContent);
+ (bool passes, List positions) = matcher.MatchesAll(stack);
if (!passes)
continue;
@@ -84,7 +85,7 @@ namespace BurnOutSharp.Matching
string positionsString = string.Join(", ", positions);
// If we there is no version method, just return the protection name
- if (matcher.GetVersion == null)
+ if (matcher.GetArrayVersion == null)
{
matchedProtections.Enqueue((matcher.ProtectionName ?? "Unknown Protection") + (includeDebug ? $" (Index {positionsString})" : string.Empty));
}
@@ -93,7 +94,108 @@ namespace BurnOutSharp.Matching
else
{
// A null version returned means the check didn't pass at the version step
- string version = matcher.GetVersion(file, fileContent, positions);
+ string version = matcher.GetArrayVersion(file, stack, positions);
+ if (version == null)
+ continue;
+
+ matchedProtections.Enqueue($"{matcher.ProtectionName ?? "Unknown Protection"} {version}".TrimEnd() + (includeDebug ? $" (Index {positionsString})" : string.Empty));
+ }
+
+ // If we're stopping after the first protection, bail out here
+ if (stopAfterFirst)
+ return matchedProtections;
+ }
+
+ return matchedProtections;
+ }
+
+ #endregion
+
+ #region Stream Content Matching
+
+ ///
+ /// Get all content matches for a given list of matchers
+ ///
+ /// File to check for matches
+ /// Stream to search
+ /// Enumerable of ContentMatchSets to be run on the file
+ /// True to include positional data, false otherwise
+ /// List of strings representing the matched protections, null or empty otherwise
+ public static ConcurrentQueue GetAllMatches(
+ string file,
+ Stream stack,
+ IEnumerable matchers,
+ bool includeDebug = false)
+ {
+ return FindAllMatches(file, stack, matchers, includeDebug, false);
+ }
+
+ ///
+ /// Get first content match for a given list of matchers
+ ///
+ /// File to check for matches
+ /// Stream to search
+ /// Enumerable of ContentMatchSets to be run on the file
+ /// True to include positional data, false otherwise
+ /// String representing the matched protection, null otherwise
+ public static string GetFirstMatch(
+ string file,
+ Stream stack,
+ IEnumerable matchers,
+ bool includeDebug = false)
+ {
+ var contentMatches = FindAllMatches(file, stack, matchers, includeDebug, true);
+ if (contentMatches == null || !contentMatches.Any())
+ return null;
+
+ return contentMatches.First();
+ }
+
+ ///
+ /// Get the required set of content matches on a per Matcher basis
+ ///
+ /// File to check for matches
+ /// Stream to search
+ /// Enumerable of ContentMatchSets to be run on the file
+ /// True to include positional data, false otherwise
+ /// True to stop after the first match, false otherwise
+ /// List of strings representing the matched protections, null or empty otherwise
+ private static ConcurrentQueue FindAllMatches(
+ string file,
+ Stream stack,
+ IEnumerable matchers,
+ bool includeDebug,
+ bool stopAfterFirst)
+ {
+ // If there's no mappings, we can't match
+ if (matchers == null || !matchers.Any())
+ return null;
+
+ // Initialize the queue of matched protections
+ var matchedProtections = new ConcurrentQueue();
+
+ // Loop through and try everything otherwise
+ foreach (var matcher in matchers)
+ {
+ // Determine if the matcher passes
+ (bool passes, List positions) = matcher.MatchesAll(stack);
+ if (!passes)
+ continue;
+
+ // Format the list of all positions found
+ string positionsString = string.Join(", ", positions);
+
+ // If we there is no version method, just return the protection name
+ if (matcher.GetStreamVersion == null)
+ {
+ matchedProtections.Enqueue((matcher.ProtectionName ?? "Unknown Protection") + (includeDebug ? $" (Index {positionsString})" : string.Empty));
+ }
+
+ // Otherwise, invoke the version method
+ else
+ {
+ // A null version returned means the check didn't pass at the version step
+ string version = matcher.GetStreamVersion(file, stack, positions);
if (version == null)
continue;