Skip to content

Commit

Permalink
Implement pattern search without wildcards
Browse files Browse the repository at this point in the history
  • Loading branch information
danielkrupinski committed Dec 5, 2023
1 parent 8026875 commit 5d37b05
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 48 deletions.
8 changes: 4 additions & 4 deletions Source/Helpers/PatternNotFoundLogger.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ struct PatternNotFoundLogger {
builder.put("Failed to find pattern ");

bool printedFirst = false;
for (const auto byte : pattern.get()) {
const auto wildcardChar{pattern.getWildcardChar()};
for (const auto byte : pattern.raw()) {
if (printedFirst)
builder.put(' ');

if (byte != BytePattern::wildcardChar) {
if (byte != wildcardChar) {
if ((byte & 0xF0) == 0)
builder.put('0');
builder.putHex(static_cast<unsigned char>(byte));
} else {
builder.put(BytePattern::wildcardChar);
builder.put(byte);
}

printedFirst = true;
Expand Down
27 changes: 13 additions & 14 deletions Source/MemorySearch/BytePattern.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,23 @@

#include <cassert>
#include <cstddef>
#include <optional>
#include <span>
#include <string_view>

#include "BytePatternStorage.h"

class BytePattern {
public:
static constexpr auto wildcardChar = '?';

template <std::size_t StorageCapacity>
explicit(false) constexpr BytePattern(const BytePatternStorage<StorageCapacity>& patternStorage)
: pattern{ patternStorage.pattern.data(), patternStorage.size }
constexpr BytePattern(std::string_view pattern, std::optional<char> wildcardChar = {}) noexcept
: pattern{pattern}
, wildcardChar{wildcardChar}
{
assert(!pattern.empty());
}

[[nodiscard]] BytePattern withoutFirstAndLastChar() const noexcept
{
if (pattern.size() > 2)
return BytePattern{ std::string_view{ pattern.data() + 1, pattern.size() - 2 } };
return BytePattern{ std::string_view{ pattern.data() + 1, pattern.size() - 2 }, wildcardChar };
return {};
}

Expand All @@ -39,7 +37,7 @@ class BytePattern {
return pattern.back();
}

[[nodiscard]] std::string_view get() const noexcept
[[nodiscard]] std::string_view raw() const noexcept
{
return pattern;
}
Expand All @@ -55,13 +53,14 @@ class BytePattern {
return true;
}

private:
BytePattern() = default;

explicit BytePattern(std::string_view pattern)
: pattern{ pattern }
[[nodiscard]] std::optional<char> getWildcardChar() const noexcept
{
return wildcardChar;
}

private:
BytePattern() = default;

std::string_view pattern;
std::optional<char> wildcardChar;
};
16 changes: 7 additions & 9 deletions Source/MemorySearch/BytePatternConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <string_view>
#include <utility>

#include "PatternStringWildcard.h"
#include <Utils/HexChars.h>

enum class BytePatternConverterError {
Expand All @@ -17,14 +18,11 @@ enum class BytePatternConverterError {
};

template <std::size_t N>
requires (N > 0)
struct BytePatternConverter {
static constexpr auto wildcardChar = '?';

static_assert(N > 0);

explicit constexpr BytePatternConverter(const char(&pattern)[N])
explicit constexpr BytePatternConverter(const char(&patternString)[N])
{
std::ranges::copy(pattern, buffer.begin());
std::ranges::copy(patternString, buffer.begin());
}

using Error = BytePatternConverterError;
Expand Down Expand Up @@ -75,12 +73,12 @@ struct BytePatternConverter {

[[nodiscard]] static constexpr bool isWildcardChar(char c) noexcept
{
return c == wildcardChar;
return c == kPatternStringWildcard;
}

[[nodiscard]] constexpr bool isNextCharWildcard() const
{
return peekNextChar() == wildcardChar;
return isWildcardChar(peekNextChar());
}

[[nodiscard]] constexpr bool isNextCharSpace() const
Expand Down Expand Up @@ -140,7 +138,7 @@ struct BytePatternConverter {
error = Error::EndsWithWildcard;
} else {
advanceReadPosition();
putChar(wildcardChar);
putChar(kPatternStringWildcard);
}
}

Expand Down
13 changes: 10 additions & 3 deletions Source/MemorySearch/BytePatternStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
#include <string_view>

#include "BytePatternConverter.h"
#include "BytePattern.h"
#include "PatternStringWildcard.h"

template <std::size_t Capacity>
struct BytePatternStorage {
explicit(false) consteval BytePatternStorage(const char (&patternToConvert)[Capacity])
explicit(false) consteval BytePatternStorage(const char (&patternString)[Capacity])
{
BytePatternConverter converter{ patternToConvert };
BytePatternConverter converter{patternString};
const auto [convertedPattern, error] = converter();
if (error == BytePatternConverterError::NoError) {
std::ranges::copy(convertedPattern, pattern.begin());
Expand All @@ -28,8 +30,13 @@ struct BytePatternStorage {
std::copy_n(storage.pattern.begin(), storage.size, pattern.begin());
}

[[nodiscard]] explicit(false) operator BytePattern() const noexcept
{
return BytePattern{{pattern.data(), size}, kPatternStringWildcard};
}

std::array<char, Capacity> pattern{};
std::size_t size = 0;
std::size_t size{0};

private:
void errorOccured();
Expand Down
3 changes: 3 additions & 0 deletions Source/MemorySearch/PatternStringWildcard.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once

constexpr auto kPatternStringWildcard{'?'};
1 change: 1 addition & 0 deletions Source/Osiris.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
<ClInclude Include="MemorySearch\PatternFinder.h" />
<ClInclude Include="MemorySearch\PatternFinderScalar.h" />
<ClInclude Include="MemorySearch\PatternFinderSIMD.h" />
<ClInclude Include="MemorySearch\PatternStringWildcard.h" />
<ClInclude Include="Platform\DynamicLibrary.h" />
<ClInclude Include="Platform\Linux\LinuxDynamicLibrary.h" />
<ClInclude Include="Platform\Linux\LinuxMessageBox.h" />
Expand Down
3 changes: 3 additions & 0 deletions Source/Osiris.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,9 @@
<ClInclude Include="CS2\Constants\AspectRatio.h">
<Filter>CS2\Constants</Filter>
</ClInclude>
<ClInclude Include="MemorySearch\PatternStringWildcard.h">
<Filter>MemorySearch</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="UI\Panorama\CreateGUI.js">
Expand Down
3 changes: 2 additions & 1 deletion Tests/MemorySearchTests/BytePatternConverterTests.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <gtest/gtest.h>

#include <MemorySearch/BytePatternConverter.h>
#include <MemorySearch/PatternStringWildcard.h>

namespace
{
Expand Down Expand Up @@ -41,7 +42,7 @@ TEST(BytePatternConverterTest, PatternCannotEndWithWildcard) {
}

TEST(BytePatternConverterTest, NumericValueOfWildcardCharCannotBeUsed) {
static_assert(BytePatternConverter<1>::wildcardChar == 0x3F);
static_assert(kPatternStringWildcard == 0x3F);

BytePatternConverter converter{ "AA BB 3F CC" };
const auto [converted, error] = converter();
Expand Down
54 changes: 37 additions & 17 deletions Tests/MemorySearchTests/BytePatternTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,62 @@
#include <gtest/gtest.h>

#include <MemorySearch/BytePattern.h>
#include <MemorySearch/BytePatternLiteral.h>
#include <MemorySearch/PatternStringWildcard.h>

namespace
{

using namespace std::string_view_literals;

template <std::size_t N>
[[nodiscard]] constexpr auto createByteArray(const unsigned char(&bytes)[N])
{
std::array<std::byte, N> arr;
std::ranges::transform(bytes, arr.begin(), [](unsigned char c) { return std::byte{ c }; });
std::ranges::transform(bytes, arr.begin(), [](unsigned char c) { return std::byte{c}; });
return arr;
}

TEST(BytePatternTest, PatternMatchesMemoryWhenBytesAreTheSame) {
constexpr auto pattern = "AB CD EF"_pat;
EXPECT_TRUE(BytePattern{ pattern }.matches(createByteArray({ 0xAB, 0xCD, 0xEF })));
TEST(BytePatternWithoutWildcardTest, PatternMatchesMemoryWhenBytesAreTheSame) {
EXPECT_TRUE(BytePattern{"\xAB\xCD\xEF"sv}.matches(createByteArray({ 0xAB, 0xCD, 0xEF })));
}

TEST(BytePatternTest, PatternDoesNotMatchMemoryWhenBytesAreDifferent) {
constexpr auto pattern = "AB CD AB"_pat;
EXPECT_FALSE(BytePattern{ pattern }.matches(createByteArray({ 0xAB, 0xCD, 0xEF })));
TEST(BytePatternWithoutWildcardTest, PatternDoesNotMatchMemoryWhenBytesAreDifferent) {
EXPECT_FALSE(BytePattern{"\xAB\xCD\xAB"sv}.matches(createByteArray({ 0xAB, 0xCD, 0xEF })));
}

TEST(BytePatternTest, NullCharsInPatternDoesNotTerminateComparison) {
constexpr auto pattern = "AB 00 EF 00 13"_pat;
EXPECT_FALSE(BytePattern{ pattern }.matches(createByteArray({ 0xAB, 0x00, 0xEF, 0x00, 0x12 })));
TEST(BytePatternWithoutWildcardTest, NullCharsInPatternDoesNotTerminateComparison) {
EXPECT_FALSE(BytePattern{"\xAB\x00\xEF\x00\x13"sv}.matches(createByteArray({ 0xAB, 0x00, 0xEF, 0x00, 0x12 })));
}

TEST(BytePatternTest, BytesOnWildcardPositionsAreIgnored) {
constexpr auto pattern = "AB ? EF ? FF"_pat;
EXPECT_TRUE(BytePattern{ pattern }.matches(createByteArray({ 0xAB, 0xCC, 0xEF, 0xDD, 0xFF })));
class BytePatternWithWildcardTest : public testing::TestWithParam<char> {
protected:
[[nodiscard]] auto makePattern(std::string_view pattern) noexcept
{
return BytePattern{pattern, GetParam()};
}
};

TEST_P(BytePatternWithWildcardTest, PatternMatchesMemoryWhenBytesAreTheSame) {
EXPECT_TRUE(makePattern("\xAB\xCD\xEF"sv).matches(createByteArray({ 0xAB, 0xCD, 0xEF })));
}

TEST(BytePatternTest, WildcardCharInMemoryDoesNotMatchEveryPatternChar) {
constexpr auto pattern = "AB CC EF DD FF"_pat;
EXPECT_FALSE(BytePattern{ pattern }.matches(createByteArray({ 0xAB, BytePattern::wildcardChar, 0xEF, BytePattern::wildcardChar, 0xFF })));
TEST_P(BytePatternWithWildcardTest, PatternDoesNotMatchMemoryWhenBytesAreDifferent) {
EXPECT_FALSE(makePattern("\xAB\xCD\xAB"sv).matches(createByteArray({ 0xAB, 0xCD, 0xEF })));
}

TEST_P(BytePatternWithWildcardTest, NullCharsInPatternDoesNotTerminateComparison) {
EXPECT_FALSE(makePattern("\xAB\x00\xEF\x00\x13"sv).matches(createByteArray({ 0xAB, 0x00, 0xEF, 0x00, 0x12 })));
}

TEST_P(BytePatternWithWildcardTest, BytesOnWildcardPositionsAreIgnored) {
const auto pattern = std::string{"\xAB"} + GetParam() + "\xEF" + GetParam() + "\xFF";
EXPECT_TRUE(makePattern(pattern).matches(createByteArray({ 0xAB, 0xCC, 0xEF, 0xDD, 0xFF })));
}

TEST_P(BytePatternWithWildcardTest, WildcardCharInMemoryDoesNotMatchEveryPatternChar) {
EXPECT_FALSE(makePattern("\xAB\xCC\xEF\xDD\xFF"sv).matches(createByteArray({ 0xAB, static_cast<unsigned char>(GetParam()), 0xEF, static_cast<unsigned char>(GetParam()), 0xFF })));
}

INSTANTIATE_TEST_SUITE_P(, BytePatternWithWildcardTest, testing::Values('?', '.'));

}

0 comments on commit 5d37b05

Please sign in to comment.