VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
BytePattern.cpp
Go to the documentation of this file.
2
4#include <charconv>
5#include <cstring>
6#include <sstream>
7
8namespace VoltMod
9{
10
11std::vector<PatternByte> ParsePattern(const std::string& pattern)
12{
13 std::vector<PatternByte> bytes;
14 std::istringstream stream(pattern);
15 std::string token;
16
17 while (stream >> token)
18 {
19 if (token == "?" || token == "??")
20 {
21 bytes.push_back({.Wildcard = true});
22 continue;
23 }
24
25 // from_chars keeps a bad token local to this signature.
26 unsigned value = 0;
27 const char* end = token.data() + token.size();
28 auto [stop, ec] = std::from_chars(token.data(), end, value, 16);
29 if (ec != std::errc{} || stop != end || value > 0xFF)
30 {
31 Log::Error("Signature pattern has an invalid byte '{}'; ignoring the pattern.", token);
32 return {};
33 }
34 bytes.push_back({.Value = static_cast<uint8_t>(value)});
35 }
36 return bytes;
37}
38
39void CountBytes(const uint8_t* base, size_t size, ByteHistogram& counts)
40{
41 for (size_t i = 0; i < size; ++i)
42 {
43 ++counts[base[i]];
44 }
45}
46
47size_t AnchorOf(const std::vector<PatternByte>& pattern, const ByteHistogram& frequencies)
48{
49 size_t anchor = pattern.size();
50 size_t rarest = SIZE_MAX;
51
52 for (size_t i = 0; i < pattern.size(); ++i)
53 {
54 if (pattern[i].Wildcard)
55 {
56 continue;
57 }
58 if (const size_t count = frequencies[pattern[i].Value]; count < rarest)
59 {
60 rarest = count;
61 anchor = i;
62 }
63 }
64 return anchor;
65}
66
67/** Whether @p pattern matches at @p at, which must have room for all of it. */
68static bool Matches(const uint8_t* at, const std::vector<PatternByte>& pattern)
69{
70 for (size_t i = 0; i < pattern.size(); ++i)
71 {
72 if (!pattern[i].Wildcard && at[i] != pattern[i].Value)
73 {
74 return false;
75 }
76 }
77 return true;
78}
79
80const uint8_t* FindFirst(const uint8_t* base, size_t size, const std::vector<PatternByte>& pattern, size_t anchor)
81{
82 if (!base || pattern.empty() || size < pattern.size())
83 {
84 return nullptr;
85 }
86
87 const size_t lastStart = size - pattern.size();
88
89 // Nothing to anchor on: an all-wildcard pattern matches at the first offset.
90 if (anchor >= pattern.size())
91 {
92 return base;
93 }
94
95 const uint8_t wanted = pattern[anchor].Value;
96 for (size_t at = 0; at <= lastStart;)
97 {
98 // The anchor byte sits at `at + anchor` and the last start worth testing is lastStart, so
99 // the window ends at lastStart + anchor. Whatever memchr skips cannot match.
100 const auto* found = static_cast<const uint8_t*>(std::memchr(base + at + anchor, wanted, lastStart - at + 1));
101 if (!found)
102 {
103 return nullptr;
104 }
105
106 at = static_cast<size_t>(found - base) - anchor;
107 if (Matches(base + at, pattern))
108 {
109 return base + at;
110 }
111 ++at;
112 }
113 return nullptr;
114}
115
116} // namespace VoltMod
Parsing and searching byte patterns, with no notion of a loaded module.
void Error(std::format_string< Args... > fmt, Args &&... args)
Definition Log.hpp:97
std::vector< PatternByte > ParsePattern(const std::string &pattern)
const uint8_t * FindFirst(const uint8_t *base, size_t size, const std::vector< PatternByte > &pattern, size_t anchor)
static bool Matches(const uint8_t *at, const std::vector< PatternByte > &pattern)
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
size_t AnchorOf(const std::vector< PatternByte > &pattern, const ByteHistogram &frequencies)
std::array< size_t, 256 > ByteHistogram
void CountBytes(const uint8_t *base, size_t size, ByteHistogram &counts)