/* Copyright (C) 2014-2019 de4dot@gmail.com This file is part of dnSpy dnSpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. dnSpy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ using System; namespace dnSpy.Contracts.Hex.Operations { /// /// Creates instances that can search for bytes or strings /// public abstract class HexSearchServiceProvider { /// /// Constructor /// protected HexSearchServiceProvider() { } /// /// The character that matches any nibble in a byte /// const char wildcardCharacter = '?'; /// /// Creates a that can search for values /// /// Value to search for /// public HexSearchService CreateByteSearchService(byte value) => CreateByteSearchService(new byte[1] { value }); /// /// Creates a that can search for values /// /// Value to search for /// public HexSearchService CreateByteSearchService(sbyte value) => CreateByteSearchService(new byte[1] { (byte)value }); /// /// Creates a that can search for values /// /// Value to search for /// true if big-endian, false if little-endian /// public HexSearchService CreateByteSearchService(short value, bool isBigEndian = false) => CreateByteSearchService(GetBytes((ushort)value, isBigEndian)); /// /// Creates a that can search for values /// /// Value to search for /// true if big-endian, false if little-endian /// public HexSearchService CreateByteSearchService(ushort value, bool isBigEndian = false) => CreateByteSearchService(GetBytes(value, isBigEndian)); /// /// Creates a that can search for values /// /// Value to search for /// true if big-endian, false if little-endian /// public HexSearchService CreateByteSearchService(int value, bool isBigEndian = false) => CreateByteSearchService(GetBytes((uint)value, isBigEndian)); /// /// Creates a that can search for values /// /// Value to search for /// true if big-endian, false if little-endian /// public HexSearchService CreateByteSearchService(uint value, bool isBigEndian = false) => CreateByteSearchService(GetBytes(value, isBigEndian)); /// /// Creates a that can search for values /// /// Value to search for /// true if big-endian, false if little-endian /// public HexSearchService CreateByteSearchService(long value, bool isBigEndian = false) => CreateByteSearchService(GetBytes((ulong)value, isBigEndian)); /// /// Creates a that can search for values /// /// Value to search for /// true if big-endian, false if little-endian /// public HexSearchService CreateByteSearchService(ulong value, bool isBigEndian = false) => CreateByteSearchService(GetBytes(value, isBigEndian)); /// /// Creates a that can search for values /// /// Value to search for /// true if big-endian, false if little-endian /// public HexSearchService CreateByteSearchService(float value, bool isBigEndian = false) => CreateByteSearchService(GetBytes(value, isBigEndian)); /// /// Creates a that can search for values /// /// Value to search for /// true if big-endian, false if little-endian /// public HexSearchService CreateByteSearchService(double value, bool isBigEndian = false) => CreateByteSearchService(GetBytes(value, isBigEndian)); /// /// Creates a that can search for bytes. The wildcard character ? matches any nibble (upper or lower 4 bits) in a byte. /// Use to validate the input before calling this method. /// /// Pattern. Supported characters: whitespace, hex digits and the wildcard character '?' /// public HexSearchService CreateByteSearchService(string pattern) { if (pattern is null) throw new ArgumentNullException(nameof(pattern)); int byteLength = GetByteLength(pattern); if (byteLength <= 0) throw new ArgumentOutOfRangeException(nameof(pattern)); var bytes = new byte[byteLength]; var mask = new byte[byteLength]; int bytesIndex = 0; for (int i = 0; i < pattern.Length;) { i = SkipWhitespace(pattern, i); if (i >= pattern.Length) break; int b = 0, m = 0; var c = pattern[i++]; if (c != wildcardCharacter) { m |= 0xF0; var v = HexToBin(c); if (v < 0) throw new ArgumentOutOfRangeException(nameof(pattern)); b |= v << 4; } i = SkipWhitespace(pattern, i); if (i < pattern.Length) { c = pattern[i++]; if (c != wildcardCharacter) { m |= 0x0F; var v = HexToBin(c); if (v < 0) throw new ArgumentOutOfRangeException(nameof(pattern)); b |= v; } } bytes[bytesIndex] = (byte)b; mask[bytesIndex] = (byte)m; bytesIndex++; } if (bytesIndex != bytes.Length) throw new InvalidOperationException(); return CreateByteSearchService(bytes, mask); } static int SkipWhitespace(string pattern, int index) { while (index < pattern.Length) { if (!char.IsWhiteSpace(pattern[index])) break; index++; } return index; } static int GetByteLength(string pattern) { int nibbles = 0; foreach (var c in pattern) { if (char.IsWhiteSpace(c)) continue; if (c != wildcardCharacter && HexToBin(c) < 0) return -1; nibbles++; } return (nibbles + 1) / 2; } static int HexToBin(char c) { if ('0' <= c && c <= '9') return c - '0'; if ('a' <= c && c <= 'f') return c - 'a' + 10; if ('A' <= c && c <= 'F') return c - 'A' + 10; return -1; } /// /// Checks whether only contains valid characters and at least one valid character /// /// Pattern /// public bool IsValidByteSearchString(string pattern) { if (pattern is null) throw new ArgumentNullException(nameof(pattern)); return GetByteLength(pattern) > 0; } /// /// Creates a that can search for bytes /// /// Bytes to search for /// public HexSearchService CreateByteSearchService(byte[] pattern) { if (pattern is null) throw new ArgumentNullException(nameof(pattern)); var mask = new byte[pattern.Length]; for (int i = 0; i < mask.Length; i++) mask[i] = 0xFF; return CreateByteSearchService(pattern, mask); } /// /// Creates a that can search for bytes /// /// Bytes to search for /// Mask used when comparing values. This array must have the same length as /// public abstract HexSearchService CreateByteSearchService(byte[] pattern, byte[] mask); /// /// Creates a that can search for UTF-8 strings /// /// Pattern to search for /// true if it's case sensitive, false if it's case insensitive /// public abstract HexSearchService CreateUtf8StringSearchService(string pattern, bool isCaseSensitive); /// /// Creates a that can search for UTF-16 strings /// /// Pattern to search for /// true if it's case sensitive, false if it's case insensitive /// true if big-endian, false if little-endian /// public abstract HexSearchService CreateUtf16StringSearchService(string pattern, bool isCaseSensitive, bool isBigEndian = false); static byte[] GetBytes(ushort value, bool isBigEndian) { if (isBigEndian) { return new byte[2] { (byte)(value >> 8), (byte)value, }; } return BitConverter.GetBytes(value); } static byte[] GetBytes(uint value, bool isBigEndian) { if (isBigEndian) { return new byte[4] { (byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)value, }; } return BitConverter.GetBytes(value); } static byte[] GetBytes(ulong value, bool isBigEndian) { if (isBigEndian) { return new byte[8] { (byte)(value >> 56), (byte)(value >> 48), (byte)(value >> 40), (byte)(value >> 32), (byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)value, }; } return BitConverter.GetBytes(value); } static byte[] GetBytes(float value, bool isBigEndian) { var bytes = BitConverter.GetBytes(value); if (isBigEndian) return GetBytes(BitConverter.ToUInt32(bytes, 0), isBigEndian); return bytes; } static byte[] GetBytes(double value, bool isBigEndian) { var bytes = BitConverter.GetBytes(value); if (isBigEndian) return GetBytes(BitConverter.ToUInt64(bytes, 0), isBigEndian); return bytes; } } }