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