Better split matching code, fix UPX name

This commit is contained in:
Matt Nadareski
2021-03-22 00:41:18 -07:00
parent ea022de022
commit 7aca58a6c9
47 changed files with 251 additions and 200 deletions

View File

@@ -1,3 +1,5 @@
using System.Diagnostics.Eventing.Reader;
namespace BurnOutSharp.Matching
{
/// <summary>
@@ -26,5 +28,66 @@ namespace BurnOutSharp.Matching
Start = start;
End = end;
}
#region Matching
/// <summary>
/// Get if this match can be found in a stack
/// </summary>
/// <param name="stack">Array to search for the given content</param>
/// <param name="reverse">True to search from the end of the array, false from the start</param>
public (bool, int) Match(byte[] 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);
// If start or end are not set properly, set them to defaults
if (Start < 0)
Start = 0;
if (End < 0)
End = stack.Length - Needle.Length;
for (int i = reverse ? End : Start; reverse ? i > Start : i < End; i += reverse ? -1 : 1)
{
if (EqualAt(stack, i))
return (true, i);
}
return (false, -1);
}
/// <summary>
/// Get if a stack at a certain index is equal to a needle
/// </summary>
/// <param name="stack">Array to search for the given content</param>
/// <param name="index">Starting index to check equality</param>
private bool EqualAt(byte[] 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;
for (int i = 0; i < Needle.Length; i++)
{
// A null value is a wildcard
if (Needle[i] == null)
continue;
else if (stack[i + index] != Needle[i])
return false;
}
return true;
}
#endregion
}
}

View File

@@ -0,0 +1,105 @@
using System.Collections.Generic;
using System.Linq;
namespace BurnOutSharp.Matching
{
/// <summary>
/// Helper class for content matching
/// </summary>
internal static class MatchUtil
{
/// <summary>
/// Get all content matches for a given list of matchers
/// </summary>
/// <param name="file">File to check for matches</param>
/// <param name="fileContent">Byte array representing the file contents</param>
/// <param name="matchers">Enumerable of matchers to be run on the file</param>
/// <param name="includePosition">True to include positional data, false otherwise</param>
/// <returns>List of strings representing the matched protections, null or empty otherwise</returns>
public static List<string> GetAllContentMatches(
string file,
byte[] fileContent,
IEnumerable<Matcher> matchers,
bool includePosition = false)
{
return FindAllContentMatches(file, fileContent, matchers, includePosition, false);
}
/// <summary>
/// Get first content match for a given list of matchers
/// </summary>
/// <param name="file">File to check for matches</param>
/// <param name="fileContent">Byte array representing the file contents</param>
/// <param name="matchers">Enumerable of matchers to be run on the file</param>
/// <param name="includePosition">True to include positional data, false otherwise</param>
/// <returns>String representing the matched protection, null otherwise</returns>
public static string GetFirstContentMatch(
string file,
byte[] fileContent,
IEnumerable<Matcher> matchers,
bool includePosition = false)
{
var contentMatches = FindAllContentMatches(file, fileContent, matchers, includePosition, true);
if (contentMatches == null || !contentMatches.Any())
return null;
return contentMatches.First();
}
/// <summary>
/// Get the required set of content matches on a per Matcher basis
/// </summary>
/// <param name="file">File to check for matches</param>
/// <param name="fileContent">Byte array representing the file contents</param>
/// <param name="matchers">Enumerable of matchers to be run on the file</param>
/// <param name="includePosition">True to include positional data, false otherwise</param>
/// <param name="stopAfterFirst">True to stop after the first match, false otherwise</param>
/// <returns>List of strings representing the matched protections, null or empty otherwise</returns>
private static List<string> FindAllContentMatches(
string file,
byte[] fileContent,
IEnumerable<Matcher> matchers,
bool includePosition,
bool stopAfterFirst)
{
// If there's no mappings, we can't match
if (matchers == null || !matchers.Any())
return null;
// Initialize the list of matched protections
List<string> matchedProtections = new List<string>();
// Loop through and try everything otherwise
foreach (var matcher in matchers)
{
// Determine if the matcher passes
(bool passes, List<int> positions) = matcher.MatchesAll(fileContent);
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.GetVersion == null)
{
matchedProtections.Add((matcher.ProtectionName ?? "Unknown Protection") + (includePosition ? $" (Index {positionsString})" : string.Empty));
}
// Otherwise, invoke the version method
// TODO: Pass all positions to the version finding method
else
{
string version = matcher.GetVersion(file, fileContent, positions[0]) ?? "Unknown Version";
matchedProtections.Add($"{matcher.ProtectionName ?? "Unknown Protection"} {version}" + (includePosition ? $" (Index {positionsString})" : string.Empty));
}
// If we're stopping after the first protection, bail out here
if (stopAfterFirst)
return matchedProtections;
}
return matchedProtections;
}
}
}

View File

@@ -55,5 +55,36 @@ namespace BurnOutSharp.Matching
}
#endregion
#region Matching
/// <summary>
/// Determine wheter all content matches pass
/// </summary>
/// <param name="fileContent">Byte array representing the file contents</param>
/// <returns>Tuole of passing status and matching positions</returns>
public (bool, List<int>) MatchesAll(byte[] fileContent)
{
// If no content matches are defined, we fail out
if (ContentMatches == null || !ContentMatches.Any())
return (false, null);
// Initialize the position list
List<int> positions = new List<int>();
// Loop through all content matches and make sure all pass
foreach (var contentMatch in ContentMatches)
{
(bool match, int position) = contentMatch.Match(fileContent);
if (!match)
return (false, null);
else
positions.Add(position);
}
return (true, positions);
}
#endregion
}
}

View File

@@ -22,7 +22,7 @@ namespace BurnOutSharp.PackerType
}, "Caphyon Advanced Installer"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -17,7 +17,7 @@ namespace BurnOutSharp.PackerType
new Matcher(new byte?[] { 0x41, 0x52, 0x4D, 0x44, 0x45, 0x42, 0x55, 0x47 }, "Armadillo"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -29,7 +29,7 @@ namespace BurnOutSharp.PackerType
}, end: 200), "CExe"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -21,7 +21,7 @@ namespace BurnOutSharp.PackerType
}, "EXE Stealth"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -23,7 +23,7 @@ namespace BurnOutSharp.PackerType
"Inno Setup"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -29,7 +29,7 @@ namespace BurnOutSharp.PackerType
}, "NSIS"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
public static string GetVersion(string file, byte[] fileContent, int index)

View File

@@ -27,7 +27,7 @@ namespace BurnOutSharp.PackerType
}, "PE Compact 2"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
public static string GetVersion(string file, byte[] fileContent, int position)

View File

@@ -44,7 +44,7 @@ namespace BurnOutSharp.PackerType
// }, GetVersion, "Setup Factory"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -12,7 +12,7 @@ namespace BurnOutSharp.PackerType
var matchers = new List<Matcher>
{
// UPX!
new Matcher(new byte?[] { 0x55, 0x50, 0x58, 0x21 }, GetVersion, "Inno Setup"),
new Matcher(new byte?[] { 0x55, 0x50, 0x58, 0x21 }, GetVersion, "UPX"),
// NOS
new Matcher(new byte?[] { 0x4E, 0x4F, 0x53, 0x20 }, GetVersion, "UPX (NOS Variant)"),
@@ -42,7 +42,7 @@ namespace BurnOutSharp.PackerType
),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
public static string GetVersion(string file, byte[] fileContent, int index)

View File

@@ -26,7 +26,7 @@ namespace BurnOutSharp.PackerType
}, "WinRAR SFX"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
public Dictionary<string, List<string>> Scan(Scanner scanner, string file)

View File

@@ -29,7 +29,7 @@ namespace BurnOutSharp.PackerType
new Matcher(new byte?[] { 0x5F, 0x77, 0x69, 0x6E, 0x7A, 0x69, 0x70, 0x5F }, GetVersion, "WinZip SFX"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -20,7 +20,7 @@ namespace BurnOutSharp.PackerType
new Matcher(new byte?[] { 0x57, 0x69, 0x73, 0x65, 0x4D, 0x61, 0x69, 0x6E }, "Wise Installation Wizard Module"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -19,7 +19,7 @@ namespace BurnOutSharp.PackerType
}, "dotFuscator"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -22,7 +22,7 @@ namespace BurnOutSharp.ProtectionType
}, "ActiveMARK 5"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -14,7 +14,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x53, 0x45, 0x54, 0x54, 0x45, 0x43 }, "Alpha-ROM"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -21,7 +21,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x43, 0x44, 0x43, 0x68, 0x65, 0x63, 0x6B }, "Executable-Based CD Check"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
// These content checks are too broad to be useful
@@ -45,7 +45,7 @@ namespace BurnOutSharp.ProtectionType
}, "Executable-Based CD Check"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -24,7 +24,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x2E, 0x67, 0x72, 0x61, 0x6E, 0x64, 0x00 }, "CD-Cops"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -23,7 +23,7 @@ namespace BurnOutSharp.ProtectionType
}, "CD-Lock"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -14,7 +14,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x7E, 0x30, 0x30, 0x31, 0x37, 0x2E, 0x74, 0x6D, 0x70 }, "CDSHiELD SE"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -24,7 +24,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x43, 0x44, 0x53, 0x50, 0x6C, 0x61, 0x79, 0x65, 0x72 }, "Cactus Data Shield 200"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -14,7 +14,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x2E, 0x63, 0x65, 0x6E, 0x65, 0x67, 0x61 }, "Cenega ProtectDVD"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -25,7 +25,7 @@ namespace BurnOutSharp.ProtectionType
}, "Code Lock"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -21,7 +21,7 @@ namespace BurnOutSharp.ProtectionType
}, "CopyKiller"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -20,7 +20,7 @@ namespace BurnOutSharp.ProtectionType
}, GetVersion, "DVD-Cops"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
public static string GetVersion(string file, byte[] fileContent, int position)

View File

@@ -106,7 +106,7 @@ namespace BurnOutSharp.ProtectionType
}, "EA DRM Protection"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -17,7 +17,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x78, 0x6C, 0x69, 0x76, 0x65, 0x2E, 0x64, 0x6C, 0x6C }, "Games for Windows - Live"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -30,7 +30,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x54, 0x72, 0x69, 0x61, 0x6C, 0x00, 0x50 }, "INTENIUM Trial & Buy Protection"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -18,7 +18,7 @@ namespace BurnOutSharp.ProtectionType
}, "Key-Lock (Dongle)"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -24,7 +24,7 @@ namespace BurnOutSharp.ProtectionType
}, "MediaMax CD-3"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -21,7 +21,7 @@ namespace BurnOutSharp.ProtectionType
}, Utilities.GetFileVersion, "Executable-Based Online Registration"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -17,7 +17,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x4F, 0x00, 0x72, 0x00, 0x69, 0x00, 0x67, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x53, 0x00, 0x65, 0x00, 0x74, 0x00, 0x75, 0x00, 0x70, 0x00, 0x2E, 0x00, 0x65, 0x00, 0x78, 0x00, 0x65, 0x00 }, "Origin"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -42,7 +42,7 @@ namespace BurnOutSharp.ProtectionType
}, "PlayStation Anti-modchip (Japanese)"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -19,7 +19,7 @@ namespace BurnOutSharp.ProtectionType
}, "Ring PROTECH [Check disc for physical ring]"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -14,7 +14,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x3F, 0x53, 0x56, 0x4B, 0x50, 0x00, 0x00 }, "SVK Protector"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -17,7 +17,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x53, 0x61, 0x66, 0x65, 0x4C, 0x6F, 0x63, 0x6B }, "SafeLock"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -46,7 +46,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x2E, 0x63, 0x6D, 0x73, 0x5F, 0x64, 0x00 }, "SecuROM 1-3"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -17,7 +17,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x42, 0x49, 0x54, 0x41, 0x52, 0x54, 0x53 }, "SmartE"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -25,7 +25,7 @@ namespace BurnOutSharp.ProtectionType
}, GetVersion, "Sysiphus"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
public static string GetVersion(string file, byte[] fileContent, int position)

View File

@@ -28,7 +28,7 @@ namespace BurnOutSharp.ProtectionType
// }, "3PLock"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -22,7 +22,7 @@ namespace BurnOutSharp.ProtectionType
}, "321Studios Online Activation"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -17,7 +17,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x57, 0x54, 0x4D, 0x37, 0x36, 0x35, 0x34, 0x35 }, "WTM CD Protect"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -32,7 +32,7 @@ namespace BurnOutSharp.ProtectionType
}, "XCP"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
/// <inheritdoc/>

View File

@@ -14,7 +14,7 @@ namespace BurnOutSharp.ProtectionType
new Matcher(new byte?[] { 0x58, 0x50, 0x52, 0x4F, 0x54, 0x20, 0x20, 0x20 }, "Xtreme-Protector"),
};
return Utilities.GetFirstContentMatch(file, fileContent, matchers, includePosition);
return MatchUtil.GetFirstContentMatch(file, fileContent, matchers, includePosition);
}
}
}

View File

@@ -168,11 +168,13 @@ namespace BurnOutSharp
// Initialize the loop variables
bool found = true;
int lastPosition = start;
var matcher = new ContentMatch(needle, end: end);
// Loop over and get all positions
while (found)
{
(found, lastPosition) = FindPosition(stack, needle, lastPosition, end, false);
matcher.Start = lastPosition;
(found, lastPosition) = matcher.Match(stack, false);
if (found)
positions.Add(lastPosition);
}
@@ -185,7 +187,8 @@ namespace BurnOutSharp
/// </summary>
public static bool FirstPosition(this byte[] stack, byte?[] needle, out int position, int start = 0, int end = -1)
{
(bool found, int foundPosition) = FindPosition(stack, needle, start, end, false);
var matcher = new ContentMatch(needle, start, end);
(bool found, int foundPosition) = matcher.Match(stack, false);
position = foundPosition;
return found;
}
@@ -195,7 +198,8 @@ namespace BurnOutSharp
/// </summary>
public static bool LastPosition(this byte[] stack, byte?[] needle, out int position, int start = 0, int end = -1)
{
(bool found, int foundPosition) = FindPosition(stack, needle, start, end, true);
var matcher = new ContentMatch(needle, start, end);
(bool found, int foundPosition) = matcher.Match(stack, true);
position = foundPosition;
return found;
}
@@ -216,89 +220,10 @@ namespace BurnOutSharp
return stack.FirstPosition(needle, out int _, start: stack.Length - needle.Length);
}
/// <summary>
/// Find the position of one array in another, if possible
/// </summary>
private static (bool, int) FindPosition(byte[] stack, byte?[] needle, int start, int end, bool reverse)
{
// 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);
// If start or end are not set properly, set them to defaults
if (start < 0)
start = 0;
if (end < 0)
end = stack.Length - needle.Length;
for (int i = reverse ? end : start; reverse ? i > start : i < end; i += reverse ? -1 : 1)
{
if (stack.EqualAt(needle, i))
return (true, i);
}
return (false, -1);
}
/// <summary>
/// Get if a stack at a certain index is equal to a needle
/// </summary>
private static bool EqualAt(this byte[] stack, byte?[] needle, int index)
{
// If we're too close to the end of the stack, return false
if (needle.Length >= stack.Length - index)
return false;
for (int i = 0; i < needle.Length; i++)
{
// A null value is a wildcard
if (needle[i] == null)
continue;
else if (stack[i + index] != needle[i])
return false;
}
return true;
}
#endregion
#region Protection
/// <summary>
/// Get all content matches for a given list of matchers
/// </summary>
/// <param name="file">File to check for matches</param>
/// <param name="fileContent">Byte array representing the file contents</param>
/// <param name="matchers">Enumerable of matchers to be run on the file</param>
/// <param name="includePosition">True to include positional data, false otherwise</param>
/// <returns>List of strings representing the matched protections, null or empty otherwise</returns>
public static List<string> GetAllContentMatches(string file, byte[] fileContent, IEnumerable<Matcher> matchers, bool includePosition = false)
{
return FindAllContentMatches(file, fileContent, matchers, includePosition, false);
}
/// <summary>
/// Get first content match for a given list of matchers
/// </summary>
/// <param name="file">File to check for matches</param>
/// <param name="fileContent">Byte array representing the file contents</param>
/// <param name="matchers">Enumerable of matchers to be run on the file</param>
/// <param name="includePosition">True to include positional data, false otherwise</param>
/// <returns>String representing the matched protection, null otherwise</returns>
public static string GetFirstContentMatch(string file, byte[] fileContent, IEnumerable<Matcher> matchers, bool includePosition = false)
{
var contentMatches = FindAllContentMatches(file, fileContent, matchers, includePosition, false);
if (contentMatches == null || !contentMatches.Any())
return null;
return contentMatches.First();
}
/// <summary>
/// Get the file version as reported by the filesystem
/// </summary>
@@ -382,79 +307,6 @@ namespace BurnOutSharp
}
}
/// <summary>
/// Get the required set of content matches on a per Matcher basis
/// </summary>
/// <param name="file">File to check for matches</param>
/// <param name="fileContent">Byte array representing the file contents</param>
/// <param name="matchers">Enumerable of matchers to be run on the file</param>
/// <param name="includePosition">True to include positional data, false otherwise</param>
/// <param name="stopAfterFirst">True to stop after the first match, false otherwise</param>
/// <returns>List of strings representing the matched protections, null or empty otherwise</returns>
private static List<string> FindAllContentMatches(
string file,
byte[] fileContent,
IEnumerable<Matcher> matchers,
bool includePosition,
bool stopAfterFirst)
{
// If there's no mappings, we can't match
if (matchers == null || !matchers.Any())
return null;
// Initialize the list of matched protections
List<string> matchedProtections = new List<string>();
// Loop through and try everything otherwise
foreach (var matcher in matchers)
{
// Setup for a single matcher
bool allMatches = true;
List<int> positions = new List<int>();
// Loop through all content matches and make sure all pass
foreach (var contentMatch in matcher.ContentMatches)
{
if (!fileContent.FirstPosition(contentMatch.Needle, out int position, contentMatch.Start, contentMatch.End))
{
allMatches = false;
break;
}
else
{
positions.Add(position);
}
}
// If not all matches pass, then we continue
if (!allMatches)
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.GetVersion == null)
{
matchedProtections.Add((matcher.ProtectionName ?? "Unknown Protection") + (includePosition ? $" (Index {positionsString})" : string.Empty));
}
// Otherwise, invoke the version method
// TODO: Pass all positions to the version finding method
else
{
string version = matcher.GetVersion(file, fileContent, positions[0]) ?? "Unknown Version";
matchedProtections.Add($"{matcher.ProtectionName} {version}" + (includePosition ? $" (Index {positionsString})" : string.Empty));
}
// If we're stopping after the first protection, bail out here
if (stopAfterFirst)
return matchedProtections;
}
return matchedProtections;
}
#endregion
}
}