diff --git a/Source/Helpers/PatternNotFoundLogger.h b/Source/Helpers/PatternNotFoundLogger.h index d997cb5e8ba..b84499ae0c3 100644 --- a/Source/Helpers/PatternNotFoundLogger.h +++ b/Source/Helpers/PatternNotFoundLogger.h @@ -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(byte)); } else { - builder.put(BytePattern::wildcardChar); + builder.put(byte); } printedFirst = true; diff --git a/Source/MemorySearch/BytePattern.h b/Source/MemorySearch/BytePattern.h index d2ffb995b9b..20c7068f3d2 100644 --- a/Source/MemorySearch/BytePattern.h +++ b/Source/MemorySearch/BytePattern.h @@ -2,25 +2,23 @@ #include #include +#include #include #include -#include "BytePatternStorage.h" - class BytePattern { public: - static constexpr auto wildcardChar = '?'; - - template - explicit(false) constexpr BytePattern(const BytePatternStorage& patternStorage) - : pattern{ patternStorage.pattern.data(), patternStorage.size } + constexpr BytePattern(std::string_view pattern, std::optional 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 {}; } @@ -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; } @@ -55,13 +53,14 @@ class BytePattern { return true; } -private: - BytePattern() = default; - - explicit BytePattern(std::string_view pattern) - : pattern{ pattern } + [[nodiscard]] std::optional getWildcardChar() const noexcept { + return wildcardChar; } +private: + BytePattern() = default; + std::string_view pattern; + std::optional wildcardChar; }; diff --git a/Source/MemorySearch/BytePatternConverter.h b/Source/MemorySearch/BytePatternConverter.h index 5e8a8507155..d3b95553b9c 100644 --- a/Source/MemorySearch/BytePatternConverter.h +++ b/Source/MemorySearch/BytePatternConverter.h @@ -6,6 +6,7 @@ #include #include +#include "PatternStringWildcard.h" #include enum class BytePatternConverterError { @@ -17,14 +18,11 @@ enum class BytePatternConverterError { }; template + 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; @@ -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 @@ -140,7 +138,7 @@ struct BytePatternConverter { error = Error::EndsWithWildcard; } else { advanceReadPosition(); - putChar(wildcardChar); + putChar(kPatternStringWildcard); } } diff --git a/Source/MemorySearch/BytePatternStorage.h b/Source/MemorySearch/BytePatternStorage.h index 85d1e151482..bfe2cf82716 100644 --- a/Source/MemorySearch/BytePatternStorage.h +++ b/Source/MemorySearch/BytePatternStorage.h @@ -6,12 +6,14 @@ #include #include "BytePatternConverter.h" +#include "BytePattern.h" +#include "PatternStringWildcard.h" template 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()); @@ -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 pattern{}; - std::size_t size = 0; + std::size_t size{0}; private: void errorOccured(); diff --git a/Source/MemorySearch/PatternStringWildcard.h b/Source/MemorySearch/PatternStringWildcard.h new file mode 100644 index 00000000000..c9e9875142a --- /dev/null +++ b/Source/MemorySearch/PatternStringWildcard.h @@ -0,0 +1,3 @@ +#pragma once + +constexpr auto kPatternStringWildcard{'?'}; diff --git a/Source/Osiris.vcxproj b/Source/Osiris.vcxproj index 9a1560c4067..7930c62d558 100644 --- a/Source/Osiris.vcxproj +++ b/Source/Osiris.vcxproj @@ -172,6 +172,7 @@ + diff --git a/Source/Osiris.vcxproj.filters b/Source/Osiris.vcxproj.filters index b5bf4564352..f650d559ef0 100644 --- a/Source/Osiris.vcxproj.filters +++ b/Source/Osiris.vcxproj.filters @@ -735,6 +735,9 @@ CS2\Constants + + MemorySearch + diff --git a/Tests/MemorySearchTests/BytePatternConverterTests.cpp b/Tests/MemorySearchTests/BytePatternConverterTests.cpp index d4f0e29c581..90a72ac8e0f 100644 --- a/Tests/MemorySearchTests/BytePatternConverterTests.cpp +++ b/Tests/MemorySearchTests/BytePatternConverterTests.cpp @@ -1,6 +1,7 @@ #include #include +#include namespace { @@ -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(); diff --git a/Tests/MemorySearchTests/BytePatternTests.cpp b/Tests/MemorySearchTests/BytePatternTests.cpp index 44c72001820..538359ae8f1 100644 --- a/Tests/MemorySearchTests/BytePatternTests.cpp +++ b/Tests/MemorySearchTests/BytePatternTests.cpp @@ -6,42 +6,62 @@ #include #include -#include +#include namespace { +using namespace std::string_view_literals; + template [[nodiscard]] constexpr auto createByteArray(const unsigned char(&bytes)[N]) { std::array 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 { +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(GetParam()), 0xEF, static_cast(GetParam()), 0xFF }))); +} + +INSTANTIATE_TEST_SUITE_P(, BytePatternWithWildcardTest, testing::Values('?', '.')); + }