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; } }