GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 2 / 0 / 2
Functions: 100.0% 2 / 0 / 2
Branches: -% 0 / 0 / 0

include/DetourModKit/scanner.hpp
Line Branch Exec Source
1 #ifndef DETOURMODKIT_SCANNER_HPP
2 #define DETOURMODKIT_SCANNER_HPP
3
4 #include <vector>
5 #include <string>
6 #include <cstddef>
7 #include <optional>
8 #include <cstdint>
9 #include <span>
10
11 namespace DetourModKit
12 {
13 namespace Scanner
14 {
15 /**
16 * @struct CompiledPattern
17 * @brief A pre-compiled AOB pattern with separate bytes and mask.
18 * @details Stores the pattern bytes and a bitmask indicating which bytes
19 * are wildcards (mask=false) vs. literal values to match (mask=true).
20 * This design avoids sentinel byte conflicts (e.g., 0xCC is a valid byte).
21 */
22 struct CompiledPattern
23 {
24 std::vector<std::byte> bytes; ///< Pattern bytes (wildcard positions contain arbitrary values)
25 std::vector<std::byte> mask; ///< 0xFF = match this byte, 0x00 = wildcard (skip)
26 size_t offset = 0; ///< Byte offset from pattern start to the point of interest.
27 ///< Set by `|` marker in the AOB string, or 0 if absent.
28 ///< May equal bytes.size() when `|` appears at the end.
29
30 /**
31 * @brief Returns the size of the pattern.
32 * @return size_t The number of bytes in the pattern.
33 */
34 503 size_t size() const noexcept { return bytes.size(); }
35
36 /**
37 * @brief Checks if the pattern is empty.
38 * @return true if the pattern has no bytes.
39 */
40 77 bool empty() const noexcept { return bytes.empty(); }
41 };
42
43 /**
44 * @brief Parses a space-separated AOB string into a compiled pattern.
45 * @details Converts hexadecimal strings to byte values and wildcard tokens
46 * ('??' or '?') into mask=false entries. An optional `|` token marks
47 * the offset within the pattern (stored in CompiledPattern::offset).
48 * This lets wider patterns precisely target a specific instruction:
49 * e.g., "48 8B 88 B8 00 00 00 | 48 89 4C 24 68" sets offset=7.
50 * @param aob_str The AOB pattern string.
51 * @return std::optional<CompiledPattern> The compiled pattern, or std::nullopt on parse failure.
52 */
53 std::optional<CompiledPattern> parse_aob(std::string_view aob_str);
54
55 /**
56 * @brief Scans a specified memory region for a given byte pattern.
57 * @details Uses an optimized search algorithm that finds the first non-wildcard
58 * byte and uses memchr for fast skipping, then verifies the full pattern.
59 * @param start_address Pointer to the beginning of the memory region to scan.
60 * @param region_size The size (in bytes) of the memory region to scan.
61 * @param pattern The compiled pattern to search for.
62 * @return const std::byte* Pointer to the first occurrence of the pattern within
63 * the specified region. Returns nullptr if pattern not found.
64 */
65 const std::byte *find_pattern(const std::byte *start_address, size_t region_size,
66 const CompiledPattern &pattern);
67
68 /**
69 * @brief Scans a memory region for the Nth occurrence of a byte pattern.
70 * @param start_address Pointer to the beginning of the memory region to scan.
71 * @param region_size The size (in bytes) of the memory region to scan.
72 * @param pattern The compiled pattern to search for.
73 * @param occurrence Which occurrence to return (1-based). 1 = first match.
74 * Passing 0 returns nullptr.
75 * @return const std::byte* Pointer to the Nth occurrence, or nullptr if fewer
76 * than N matches exist.
77 */
78 const std::byte *find_pattern(const std::byte *start_address, size_t region_size,
79 const CompiledPattern &pattern, size_t occurrence);
80 // Common x86-64 RIP-relative opcode prefixes (bytes preceding the disp32 field)
81 inline constexpr std::byte PREFIX_MOV_RAX_RIP[] = {std::byte{0x48}, std::byte{0x8B}, std::byte{0x05}};
82 inline constexpr std::byte PREFIX_MOV_RCX_RIP[] = {std::byte{0x48}, std::byte{0x8B}, std::byte{0x0D}};
83 inline constexpr std::byte PREFIX_MOV_RDX_RIP[] = {std::byte{0x48}, std::byte{0x8B}, std::byte{0x15}};
84 inline constexpr std::byte PREFIX_MOV_RBX_RIP[] = {std::byte{0x48}, std::byte{0x8B}, std::byte{0x1D}};
85 inline constexpr std::byte PREFIX_LEA_RAX_RIP[] = {std::byte{0x48}, std::byte{0x8D}, std::byte{0x05}};
86 inline constexpr std::byte PREFIX_LEA_RCX_RIP[] = {std::byte{0x48}, std::byte{0x8D}, std::byte{0x0D}};
87 inline constexpr std::byte PREFIX_LEA_RDX_RIP[] = {std::byte{0x48}, std::byte{0x8D}, std::byte{0x15}};
88 inline constexpr std::byte PREFIX_CALL_REL32[] = {std::byte{0xE8}};
89 inline constexpr std::byte PREFIX_JMP_REL32[] = {std::byte{0xE9}};
90
91 /**
92 * @brief Resolves an absolute address from an x86-64 RIP-relative instruction.
93 * @details Extracts the int32 displacement at the given offset within the instruction
94 * and computes the absolute target: instruction_address + instruction_length + displacement.
95 * @param instruction_address Pointer to the first byte of the instruction.
96 * @param displacement_offset Byte offset from instruction_address to the disp32 field.
97 * @param instruction_length Total length of the instruction in bytes.
98 * @return The resolved absolute address, or std::nullopt on null input or unreadable displacement.
99 */
100 std::optional<uintptr_t> resolve_rip_relative(const std::byte *instruction_address,
101 size_t displacement_offset,
102 size_t instruction_length);
103
104 /**
105 * @brief Scans forward from a starting address for an opcode prefix, then resolves the RIP-relative target.
106 * @details Searches up to search_length bytes for the given opcode prefix. Once found,
107 * the displacement is assumed to immediately follow the prefix. The absolute address
108 * is computed as: found_address + instruction_length + displacement.
109 * @param search_start Pointer to the beginning of the search region.
110 * @param search_length Maximum number of bytes to search forward.
111 * @param opcode_prefix The opcode byte sequence to search for (disp32 must follow immediately).
112 * @param instruction_length Total length of the instruction in bytes.
113 * @return The resolved absolute address, or std::nullopt if prefix not found or displacement unreadable.
114 */
115 std::optional<uintptr_t> find_and_resolve_rip_relative(const std::byte *search_start,
116 size_t search_length,
117 std::span<const std::byte> opcode_prefix,
118 size_t instruction_length);
119
120 /**
121 * @brief Scans all committed executable memory regions for a byte pattern.
122 * @details Walks the process address space via VirtualQuery, scanning each
123 * committed region with execute permission. Useful for games with
124 * packed or protected binaries that unpack code into anonymous pages
125 * outside any loaded module's address range.
126 * @param pattern The compiled pattern to search for.
127 * @param occurrence Which occurrence to return (1-based). 1 = first match.
128 * @return Pointer to the match (adjusted by pattern offset), or nullptr if not found.
129 */
130 const std::byte *scan_executable_regions(const CompiledPattern &pattern, size_t occurrence = 1);
131
132 } // namespace Scanner
133 } // namespace DetourModKit
134
135 #endif // DETOURMODKIT_SCANNER_HPP
136