include/DetourModKit/region.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef DETOURMODKIT_REGION_HPP | ||
| 2 | #define DETOURMODKIT_REGION_HPP | ||
| 3 | |||
| 4 | /** | ||
| 5 | * @file region.hpp | ||
| 6 | * @brief The Region value type and the Prot protection flags, the shared range-of-memory vocabulary. | ||
| 7 | * @details A Region pairs a base Address with a byte size, so a memory range travels as one value. Each named | ||
| 8 | * factory yields a Region that the caller stores, passes to a scan, and narrows with `sub()`. Value | ||
| 9 | * operations (`end`, `contains`, `sub`) are pure arithmetic and allocate nothing. Every factory is | ||
| 10 | * Setup/control-plane only, and only `module_named()` can allocate among them. | ||
| 11 | * @warning `[B-100]` `host()`, `own()`, and `module_named()` query loader state: never call them under the Windows | ||
| 12 | * loader lock. `whole_process()` does not query loader state. `RegionLoaderBoundary.*` pins this boundary. | ||
| 13 | */ | ||
| 14 | |||
| 15 | #include "DetourModKit/address.hpp" | ||
| 16 | #include "DetourModKit/defines.hpp" | ||
| 17 | |||
| 18 | #include <cstddef> | ||
| 19 | #include <cstdint> | ||
| 20 | #include <string_view> | ||
| 21 | |||
| 22 | namespace DetourModKit | ||
| 23 | { | ||
| 24 | /** | ||
| 25 | * @struct Region | ||
| 26 | * @brief A half-open span of process memory: [base, base + size). | ||
| 27 | * @details A plain data aggregate. It maintains no invariant beyond what its fields hold, so it stays a POD-like | ||
| 28 | * struct with public fields and is freely copied. An empty Region (null base, zero size) is the | ||
| 29 | * fail-closed result every factory returns when its scope cannot be resolved, and `contains()` reports | ||
| 30 | * false for any address against it. | ||
| 31 | */ | ||
| 32 | struct Region | ||
| 33 | { | ||
| 34 | /// Inclusive start of the span. | ||
| 35 | Address base{}; | ||
| 36 | /// Length of the span in bytes; a size of 0 denotes an empty Region. | ||
| 37 | std::size_t size{0}; | ||
| 38 | |||
| 39 | /// Returns the exclusive end address (base advanced by size). | ||
| 40 | 2974 | [[nodiscard]] constexpr Address end() const noexcept { return base.offset(static_cast<std::ptrdiff_t>(size)); } | |
| 41 | |||
| 42 | /** | ||
| 43 | * @brief Tests whether @p address lies within the half-open span. | ||
| 44 | * @param address The address to test. | ||
| 45 | * @return True when base <= address < end(); false for any address against an empty Region. | ||
| 46 | */ | ||
| 47 | 19 | [[nodiscard]] constexpr bool contains(Address address) const noexcept | |
| 48 | { | ||
| 49 |
4/4✓ Branch 4 → 5 taken 17 times.
✓ Branch 4 → 10 taken 2 times.
✓ Branch 8 → 9 taken 12 times.
✓ Branch 8 → 10 taken 5 times.
|
19 | return address >= base && address < end(); |
| 50 | } | ||
| 51 | |||
| 52 | /** | ||
| 53 | * @brief Returns a sub-span starting @p offset bytes into this Region and running for @p length bytes. | ||
| 54 | * @param offset Byte offset from base at which the sub-span starts. | ||
| 55 | * @param length Length of the sub-span in bytes. | ||
| 56 | * @details Pure value arithmetic with no clamping: the caller owns keeping the sub-span inside the parent, | ||
| 57 | * matching how it is used to carve a known-good window out of an already-validated Region. | ||
| 58 | */ | ||
| 59 | 1 | [[nodiscard]] constexpr Region sub(std::size_t offset, std::size_t length) const noexcept | |
| 60 | { | ||
| 61 | 1 | return Region{base.offset(static_cast<std::ptrdiff_t>(offset)), length}; | |
| 62 | } | ||
| 63 | |||
| 64 | /** | ||
| 65 | * @brief Returns the Region spanning the host process image (the .exe the mod is injected into). | ||
| 66 | * @return The host module's mapped image span, or an empty Region if it cannot be resolved. | ||
| 67 | * @details The default scope for a cascade that carries no explicit range. | ||
| 68 | * @note Setup/control-plane only: loader-backed, see the file-level `[B-100]` warning. | ||
| 69 | */ | ||
| 70 | [[nodiscard]] static Region host() noexcept; | ||
| 71 | |||
| 72 | /** | ||
| 73 | * @brief Returns the Region spanning the module DetourModKit is linked into (the calling DLL, or the EXE when | ||
| 74 | * DMK is statically linked into the host process). | ||
| 75 | * @return The owning module's mapped image span, or an empty Region if the lookup or PE-header read failed. | ||
| 76 | * @details DetourModKit is a static library, so `own()` resolves to whichever DLL or EXE consumed it. That is | ||
| 77 | * distinct from @ref host(), which is always the process EXE. The lookup resolves the module that | ||
| 78 | * contains this function's own code, so it stays correct however the consumer packaged the library. | ||
| 79 | * @note Setup/control-plane only: loader-backed, see the file-level `[B-100]` warning. | ||
| 80 | */ | ||
| 81 | [[nodiscard]] static Region own() noexcept; | ||
| 82 | |||
| 83 | /** | ||
| 84 | * @brief Returns the Region spanning a named, already-loaded module. | ||
| 85 | * @param name UTF-8 module name as the loader knows it (e.g. "kernel32.dll"). | ||
| 86 | * @return The module's mapped image span, or an empty Region if @p name is empty or the module is not loaded. | ||
| 87 | * @note Setup/control-plane only: loader-backed, see the file-level `[B-100]` warning. The name conversion | ||
| 88 | * allocates. | ||
| 89 | */ | ||
| 90 | [[nodiscard]] static Region module_named(std::string_view name) noexcept; | ||
| 91 | |||
| 92 | /** | ||
| 93 | * @brief Returns the Region spanning this process's entire user-mode address window. | ||
| 94 | * @return The half-open span from the system minimum application address through the maximum (inclusive). | ||
| 95 | * @details The widest scope, for a scan that cannot assume which module holds the target. It reads the | ||
| 96 | * system's reported application-address window, never a hardcoded ceiling. | ||
| 97 | * @note Setup/control-plane only; a whole-process scan is a startup-time operation, never a per-frame one. | ||
| 98 | */ | ||
| 99 | [[nodiscard]] static Region whole_process() noexcept; | ||
| 100 | }; | ||
| 101 | |||
| 102 | /** | ||
| 103 | * @enum Prot | ||
| 104 | * @brief Page protection as composable read/write/execute flags. | ||
| 105 | * @details A backend-neutral spelling of memory protection: the scan and memory layers speak in `Prot::RW` rather | ||
| 106 | * than the platform's `PAGE_READWRITE` constants, so the public surface never leaks an OS protection | ||
| 107 | * value. The RW / RWX combinations are predefined for the common cases; arbitrary unions compose through | ||
| 108 | * the flag operators generated below. | ||
| 109 | */ | ||
| 110 | enum class Prot : std::uint32_t | ||
| 111 | { | ||
| 112 | None = 0, | ||
| 113 | R = 1, | ||
| 114 | W = 2, | ||
| 115 | X = 4, | ||
| 116 | // Inside an enum with a fixed underlying type, the enumerators have that integral type (std::uint32_t), not | ||
| 117 | // Prot, until the closing brace, so these `R | W` initializers are a plain integer OR evaluated at this point. | ||
| 118 | // They do not depend on (and predate) the DMK_FLAG_ENUM(Prot) operators below, which only apply to Prot values. | ||
| 119 | RW = R | W, | ||
| 120 | RWX = R | W | X | ||
| 121 | }; | ||
| 122 | |||
| 123 | // Emit Prot's bitwise/compound operators in this namespace (unqualified enum), so `Prot::R | Prot::W` composes and | ||
| 124 | // ADL finds the operators. See DMK_FLAG_ENUM in defines.hpp for why placement and the missing semicolon matter. | ||
| 125 | 3663 | DMK_FLAG_ENUM(Prot) | |
| 126 | |||
| 127 | } // namespace DetourModKit | ||
| 128 | |||
| 129 | #endif // DETOURMODKIT_REGION_HPP | ||
| 130 |