CommonLibVR
Pattern.h
Go to the documentation of this file.
1 #pragma once
2 
3 #include "REL/Module.h"
4 
5 namespace REL
6 {
7  namespace detail
8  {
9  namespace characters
10  {
11  [[nodiscard]] constexpr bool hexadecimal(char a_ch) noexcept
12  {
13  return ('0' <= a_ch && a_ch <= '9') ||
14  ('A' <= a_ch && a_ch <= 'F') ||
15  ('a' <= a_ch && a_ch <= 'f');
16  }
17 
18  [[nodiscard]] constexpr bool space(char a_ch) noexcept
19  {
20  return a_ch == ' ';
21  }
22 
23  [[nodiscard]] constexpr bool wildcard(char a_ch) noexcept
24  {
25  return a_ch == '?';
26  }
27  }
28 
29  namespace rules
30  {
31  namespace detail
32  {
33  [[nodiscard]] consteval std::byte hexacharacters_to_hexadecimal(char a_hi, char a_lo) noexcept
34  {
35  constexpr auto lut = []() noexcept {
37 
38  const auto iterate = [&](std::uint8_t a_iFirst, unsigned char a_cFirst, unsigned char a_cLast) noexcept {
39  for (; a_cFirst <= a_cLast; ++a_cFirst, ++a_iFirst) {
40  a[a_cFirst] = a_iFirst;
41  }
42  };
43 
44  iterate(0, '0', '9');
45  iterate(0xA, 'A', 'F');
46  iterate(0xa, 'a', 'f');
47 
48  return a;
49  }();
50 
51  return static_cast<std::byte>(
52  lut[static_cast<unsigned char>(a_hi)] * 0x10u +
53  lut[static_cast<unsigned char>(a_lo)]);
54  }
55  }
56 
57  template <char HI, char LO>
59  {
60  public:
61  [[nodiscard]] static constexpr bool match(std::byte a_byte) noexcept
62  {
63  constexpr auto expected = detail::hexacharacters_to_hexadecimal(HI, LO);
64  return a_byte == expected;
65  }
66  };
67 
68  static_assert(Hexadecimal<'5', '7'>::match(std::byte{ 0x57 }));
69  static_assert(Hexadecimal<'6', '5'>::match(std::byte{ 0x65 }));
70  static_assert(Hexadecimal<'B', 'D'>::match(std::byte{ 0xBD }));
71  static_assert(Hexadecimal<'1', 'C'>::match(std::byte{ 0x1C }));
72  static_assert(Hexadecimal<'F', '2'>::match(std::byte{ 0xF2 }));
73  static_assert(Hexadecimal<'9', 'f'>::match(std::byte{ 0x9f }));
74 
75  static_assert(!Hexadecimal<'D', '4'>::match(std::byte{ 0xF8 }));
76  static_assert(!Hexadecimal<'6', '7'>::match(std::byte{ 0xAA }));
77  static_assert(!Hexadecimal<'7', '8'>::match(std::byte{ 0xE3 }));
78  static_assert(!Hexadecimal<'6', 'E'>::match(std::byte{ 0x61 }));
79 
80  class Wildcard
81  {
82  public:
83  [[nodiscard]] static constexpr bool match(std::byte) noexcept
84  {
85  return true;
86  }
87  };
88 
89  static_assert(Wildcard::match(std::byte{ 0xB9 }));
90  static_assert(Wildcard::match(std::byte{ 0x96 }));
91  static_assert(Wildcard::match(std::byte{ 0x35 }));
92  static_assert(Wildcard::match(std::byte{ 0xE4 }));
93 
94  template <char, char>
95  void rule_for() noexcept;
96 
97  template <char C1, char C2>
98  Hexadecimal<C1, C2> rule_for() noexcept
99  requires(characters::hexadecimal(C1) && characters::hexadecimal(C2));
100 
101  template <char C1, char C2>
102  Wildcard rule_for() noexcept
103  requires(characters::wildcard(C1) && characters::wildcard(C2));
104  }
105 
106  template <class... Rules>
108  {
109  public:
110  static_assert(sizeof...(Rules) >= 1, "must provide at least 1 rule for the pattern matcher");
111 
112  [[nodiscard]] constexpr bool match(std::span<const std::byte, sizeof...(Rules)> a_bytes) const noexcept
113  {
114  std::size_t i = 0;
115  return (Rules::match(a_bytes[i++]) && ...);
116  }
117 
118  [[nodiscard]] bool match(std::uintptr_t a_address) const noexcept
119  {
120  return this->match(*reinterpret_cast<const std::byte(*)[sizeof...(Rules)]>(a_address));
121  }
122 
123  void match_or_fail(std::uintptr_t a_address, std::source_location a_loc = std::source_location::current()) const noexcept
124  {
125  if (!this->match(a_address)) {
126  const auto version = Module::get().version();
128  std::format(
129  "A pattern has failed to match.\n"
130  "This means the plugin is incompatible with the current version of the game ({}.{}.{}). "
131  "Head to the mod page of this plugin to see if an update is available."sv,
132  version[0],
133  version[1],
134  version[2]),
135  a_loc);
136  }
137  }
138  };
139 
140  void consteval_error(const char* a_error);
141 
142  template <stl::nttp::string S, class... Rules>
143  [[nodiscard]] constexpr auto do_make_pattern() noexcept
144  {
145  if constexpr (S.length() == 0) {
146  return PatternMatcher<Rules...>();
147  } else if constexpr (S.length() == 1) {
148  constexpr char c = S[0];
149  if constexpr (characters::hexadecimal(c) || characters::wildcard(c)) {
150  consteval_error("the given pattern has an unpaired rule (rules are required to be written in pairs of 2)");
151  } else {
152  consteval_error("the given pattern has trailing characters at the end (which is not allowed)");
153  }
154  } else {
155  using rule_t = decltype(rules::rule_for<S[0], S[1]>());
156  if constexpr (std::same_as<rule_t, void>) {
157  consteval_error("the given pattern failed to match any known rules");
158  } else {
159  if constexpr (S.length() <= 3) {
160  return do_make_pattern<S.template substr<2>(), Rules..., rule_t>();
161  } else if constexpr (characters::space(S[2])) {
162  return do_make_pattern<S.template substr<3>(), Rules..., rule_t>();
163  } else {
164  consteval_error("a space character is required to split byte patterns");
165  }
166  }
167  }
168  }
169 
170  template <class... Bytes>
171  [[nodiscard]] consteval auto make_byte_array(Bytes... a_bytes) noexcept
172  -> std::array<std::byte, sizeof...(Bytes)>
173  {
174  static_assert((std::integral<Bytes> && ...), "all bytes must be an integral type");
175  return { static_cast<std::byte>(a_bytes)... };
176  }
177  }
178 
179  template <stl::nttp::string S>
180  [[nodiscard]] constexpr auto make_pattern() noexcept
181  {
182  return detail::do_make_pattern<S>();
183  }
184 
185  static_assert(make_pattern<"40 10 F2 ??">().match(
186  detail::make_byte_array(0x40, 0x10, 0xF2, 0x41)));
187  static_assert(make_pattern<"B8 D0 ?? ?? D4 6E">().match(
188  detail::make_byte_array(0xB8, 0xD0, 0x35, 0x2A, 0xD4, 0x6E)));
189 }
static Module & get()
Definition: Module.h:54
Version version() const noexcept
Definition: Module.h:62
Definition: Pattern.h:108
constexpr bool match(std::span< const std::byte, sizeof...(Rules)> a_bytes) const noexcept
Definition: Pattern.h:112
void match_or_fail(std::uintptr_t a_address, std::source_location a_loc=std::source_location::current()) const noexcept
Definition: Pattern.h:123
bool match(std::uintptr_t a_address) const noexcept
Definition: Pattern.h:118
Definition: Pattern.h:59
static constexpr bool match(std::byte a_byte) noexcept
Definition: Pattern.h:61
Definition: Pattern.h:81
static constexpr bool match(std::byte) noexcept
Definition: Pattern.h:83
constexpr bool space(char a_ch) noexcept
Definition: Pattern.h:18
constexpr bool hexadecimal(char a_ch) noexcept
Definition: Pattern.h:11
constexpr bool wildcard(char a_ch) noexcept
Definition: Pattern.h:23
consteval std::byte hexacharacters_to_hexadecimal(char a_hi, char a_lo) noexcept
Definition: Pattern.h:33
void rule_for() noexcept
consteval auto make_byte_array(Bytes... a_bytes) noexcept -> std::array< std::byte, sizeof...(Bytes)>
Definition: Pattern.h:171
void consteval_error(const char *a_error)
constexpr auto do_make_pattern() noexcept
Definition: Pattern.h:143
Definition: ID.h:9
constexpr auto make_pattern() noexcept
Definition: Pattern.h:180
NiColor max(const NiColor &a_lhs, const NiColor &a_rhs)
Definition: ColorUtil.h:71
string(const CharT(&)[N]) -> string< CharT, N - 1 >
void report_and_fail(std::string_view a_msg, std::source_location a_loc=std::source_location::current())
Definition: PCH.h:588
requires(std::invocable< std::remove_reference_t< EF >>) class scope_exit
Definition: PCH.h:151