using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Text;
using ReClassNET.Extensions;
namespace ReClassNET.MemoryScanner
{
public enum PatternMaskFormat
{
///
/// Example: AA BB ?? D? ?E FF
///
Combined,
///
/// Example: \xAA\xBB\x00\x00\x00\xFF xx???x
///
Separated
}
public class BytePattern
{
private interface IPatternByte
{
///
/// Gets the byte value of the pattern byte if possible.
///
///
byte ToByte();
///
/// Compares the pattern byte with the given byte.
///
///
///
bool Equals(byte b);
///
/// Formats the pattern byte as string.
///
///
///
Tuple ToString(PatternMaskFormat format);
}
private class PatternByte : IPatternByte
{
private struct Nibble
{
public int Value;
public bool IsWildcard;
}
private Nibble nibble1;
private Nibble nibble2;
public bool HasWildcard => nibble1.IsWildcard || nibble2.IsWildcard;
public byte ToByte() => !HasWildcard ? (byte)((nibble1.Value << 4) + nibble2.Value) : throw new InvalidOperationException();
public static PatternByte NewWildcardByte()
{
var pb = new PatternByte
{
nibble1 = { IsWildcard = true },
nibble2 = { IsWildcard = true }
};
return pb;
}
private static bool IsHexValue(char c)
{
return '0' <= c && c <= '9'
|| 'A' <= c && c <= 'F'
|| 'a' <= c && c <= 'f';
}
private static int HexToInt(char c)
{
if ('0' <= c && c <= '9') return c - '0';
if ('A' <= c && c <= 'F') return c - 'A' + 10;
return c - 'a' + 10;
}
public bool TryRead(StringReader sr)
{
Contract.Requires(sr != null);
var temp = sr.ReadSkipWhitespaces();
if (temp == -1 || !IsHexValue((char)temp) && (char)temp != '?')
{
return false;
}
nibble1.Value = HexToInt((char)temp) & 0xF;
nibble1.IsWildcard = (char)temp == '?';
temp = sr.Read();
if (temp == -1 || char.IsWhiteSpace((char)temp) || (char)temp == '?')
{
nibble2.IsWildcard = true;
return true;
}
if (!IsHexValue((char)temp))
{
return false;
}
nibble2.Value = HexToInt((char)temp) & 0xF;
nibble2.IsWildcard = false;
return true;
}
public bool Equals(byte b)
{
if (nibble1.IsWildcard || ((b >> 4) & 0xF) == nibble1.Value)
{
if (nibble2.IsWildcard || (b & 0xF) == nibble2.Value)
{
return true;
}
}
return false;
}
public Tuple ToString(PatternMaskFormat format)
{
switch (format)
{
case PatternMaskFormat.Separated:
return HasWildcard ? Tuple.Create("\\x00", "?") : Tuple.Create($"\\x{ToByte():X02}", "x");
case PatternMaskFormat.Combined:
var sb = new StringBuilder();
if (nibble1.IsWildcard) sb.Append('?');
else sb.AppendFormat("{0:X}", nibble1.Value);
if (nibble2.IsWildcard) sb.Append('?');
else sb.AppendFormat("{0:X}", nibble2.Value);
return Tuple.Create(sb.ToString(), (string)null);
default:
throw new ArgumentOutOfRangeException(nameof(format), format, null);
}
}
public override string ToString() => ToString(PatternMaskFormat.Combined).Item1;
}
private class SimplePatternByte : IPatternByte
{
private readonly byte value;
public SimplePatternByte(byte value)
{
this.value = value;
}
public byte ToByte() => value;
public bool Equals(byte b) => value == b;
public Tuple ToString(PatternMaskFormat format)
{
switch (format)
{
case PatternMaskFormat.Separated:
return Tuple.Create($"\\x{ToByte():X02}", "x");
case PatternMaskFormat.Combined:
return Tuple.Create($"{ToByte():X02}", (string)null);
default:
throw new ArgumentOutOfRangeException(nameof(format), format, null);
}
}
}
private readonly List pattern = new List();
///
/// Gets the length of the pattern in byte.
///
public int Length => pattern.Count;
///
/// Gets if the pattern contains wildcards.
///
public bool HasWildcards => pattern.Any(pb => pb is PatternByte pb2 && pb2.HasWildcard);
private BytePattern()
{
}
///
/// Parses the provided string for a byte pattern. Wildcards are supported by nibble.
///
///
/// Valid patterns:
/// AA BB CC DD
/// AABBCCDD
/// aabb CCdd
/// A? ?B ?? DD
///
/// Thrown if the provided string doesn't contain a valid byte pattern.
/// The byte pattern in hex format.
/// The corresponding .
public static BytePattern Parse(string value)
{
Contract.Requires(!string.IsNullOrEmpty(value));
Contract.Ensures(Contract.Result() != null);
var pattern = new BytePattern();
using var sr = new StringReader(value);
while (true)
{
var pb = new PatternByte();
if (pb.TryRead(sr))
{
if (!pb.HasWildcard)
{
pattern.pattern.Add(new SimplePatternByte(pb.ToByte()));
}
else
{
pattern.pattern.Add(pb);
}
}
else
{
break;
}
}
// Check if we are not at the end of the stream
if (sr.Peek() != -1)
{
throw new ArgumentException($"'{value}' is not a valid byte pattern.");
}
return pattern;
}
///
/// Creates a byte pattern from the provided bytes.
///
/// The bytes to match.
///
public static BytePattern From(IEnumerable data)
{
var pattern = new BytePattern();
pattern.pattern.AddRange(data.Select(b => new SimplePatternByte(b)));
return pattern;
}
///
/// Creates a byte pattern with wildcard support from the provided bytes. The boolean tuple item signals a wildcard.
///
/// The byte data or the wildcard flag.
///
public static BytePattern From(IEnumerable> data)
{
var pattern = new BytePattern();
foreach (var (value, isWildcard) in data)
{
var pb = isWildcard ? (IPatternByte)PatternByte.NewWildcardByte() : new SimplePatternByte(value);
pattern.pattern.Add(pb);
}
return pattern;
}
///
/// Tests if the provided byte array matches the byte pattern at the provided index.
///
/// The byte array to be compared.
/// The index into the byte array.
/// True if the pattern matches, false if they are not.
public bool Equals(byte[] data, int index)
{
Contract.Requires(data != null);
for (var j = 0; j < pattern.Count; ++j)
{
if (!pattern[j].Equals(data[index + j]))
{
return false;
}
}
return true;
}
///
/// Converts this to a byte array.
///
/// Thrown if the pattern contains wildcards.
/// The bytes of the pattern.
///
public byte[] ToByteArray()
{
Contract.Ensures(Contract.Result() != null);
if (HasWildcards)
{
throw new InvalidOperationException();
}
return pattern.Select(pb => pb.ToByte()).ToArray();
}
///
/// Formats the in the specified .
///
/// The format of the pattern.
/// A tuple containing the format. If is not the second item is null.
public Tuple ToString(PatternMaskFormat format)
{
switch (format)
{
case PatternMaskFormat.Separated:
var sb1 = new StringBuilder();
var sb2 = new StringBuilder();
pattern
.Select(p => p.ToString(PatternMaskFormat.Separated))
.ForEach(t =>
{
sb1.Append(t.Item1);
sb2.Append(t.Item2);
});
return Tuple.Create(sb1.ToString(), sb2.ToString());
case PatternMaskFormat.Combined:
return Tuple.Create(string.Join(" ", pattern.Select(p => p.ToString(PatternMaskFormat.Combined).Item1)), null);
default:
throw new ArgumentOutOfRangeException(nameof(format), format, null);
}
}
public override string ToString() => ToString(PatternMaskFormat.Combined).Item1;
}
}