mirror of
https://github.com/orange-cpp/omath.git
synced 2026-02-13 07:03:25 +00:00
Documents pattern scanning API
Adds comprehensive documentation for the pattern scanning API. Details parsing behavior, complexity, and usage examples. Includes troubleshooting tips and minimal test sketches. Clarifies edge-case handling and implementation notes.
This commit is contained in:
118
docs/3d_primitives/box.md
Normal file
118
docs/3d_primitives/box.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# `omath::primitives::create_box` — Build an oriented box as 12 triangles
|
||||
|
||||
> Header: your project’s `primitives/box.hpp` (declares `create_box`)
|
||||
> Namespace: `omath::primitives`
|
||||
> Depends on: `omath::Triangle<omath::Vector3<float>>`, `omath::Vector3<float>`
|
||||
|
||||
```cpp
|
||||
[[nodiscard]]
|
||||
std::array<Triangle<Vector3<float>>, 12>
|
||||
create_box(const Vector3<float>& top,
|
||||
const Vector3<float>& bottom,
|
||||
const Vector3<float>& dir_forward,
|
||||
const Vector3<float>& dir_right,
|
||||
float ratio = 4.f) noexcept;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What it does
|
||||
|
||||
Constructs a **rectangular cuboid (“box”)** oriented in 3D space and returns its surface as **12 triangles** (2 per face × 6 faces). The box’s central axis runs from `bottom` → `top`. The **up** direction is inferred from that segment; the **forward** and **right** directions define the box’s orientation around that axis.
|
||||
|
||||
The lateral half-extents are derived from the axis length and `ratio`:
|
||||
|
||||
> Let `H = |top - bottom|`. Lateral half-size ≈ `H / ratio` along both `dir_forward` and `dir_right`
|
||||
> (i.e., the cross-section is a square of side `2H/ratio`).
|
||||
|
||||
> **Note:** This describes the intended behavior from the interface. If you rely on a different sizing rule, document it next to your implementation.
|
||||
|
||||
---
|
||||
|
||||
## Parameters
|
||||
|
||||
* `top`
|
||||
Center of the **top face**.
|
||||
|
||||
* `bottom`
|
||||
Center of the **bottom face**.
|
||||
|
||||
* `dir_forward`
|
||||
A direction that orients the box around its up axis. Should be **non-zero** and **not collinear** with `top - bottom`.
|
||||
|
||||
* `dir_right`
|
||||
A direction roughly orthogonal to both `dir_forward` and `top - bottom`. Used to fully fix orientation.
|
||||
|
||||
* `ratio` (default `4.0f`)
|
||||
Controls thickness relative to height. Larger values → thinner box.
|
||||
With the default rule above, half-extent = `|top-bottom|/ratio`.
|
||||
|
||||
---
|
||||
|
||||
## Return value
|
||||
|
||||
`std::array<Triangle<Vector3<float>>, 12>` — the six faces of the box, triangulated.
|
||||
Winding is intended to be **outward-facing** (right-handed coordinates). Do not rely on a specific **face ordering**; treat the array as opaque unless your implementation guarantees an order.
|
||||
|
||||
---
|
||||
|
||||
## Expected math & robustness
|
||||
|
||||
* Define `u = normalize(top - bottom)`.
|
||||
* Re-orthonormalize the basis to avoid skew:
|
||||
|
||||
```cpp
|
||||
f = normalize(dir_forward - u * u.dot(dir_forward)); // drop any up component
|
||||
r = normalize(u.cross(f)); // right-handed basis
|
||||
// (Optionally recompute f = r.cross(u) for orthogonality)
|
||||
```
|
||||
* Half-extents: `h = length(top - bottom) / ratio; hf = h * f; hr = h * r`.
|
||||
* Corners (top): `t±r±f = top ± hr ± hf`; (bottom): `b±r±f = bottom ± hr ± hf`.
|
||||
* Triangulate each face with consistent CCW winding when viewed from outside.
|
||||
|
||||
---
|
||||
|
||||
## Example
|
||||
|
||||
```cpp
|
||||
using omath::Vector3;
|
||||
using omath::Triangle;
|
||||
using omath::primitives::create_box;
|
||||
|
||||
// Axis from bottom to top (height 2)
|
||||
Vector3<float> bottom{0, 0, 0};
|
||||
Vector3<float> top {0, 2, 0};
|
||||
|
||||
// Orientation around the axis
|
||||
Vector3<float> forward{0, 0, 1};
|
||||
Vector3<float> right {1, 0, 0};
|
||||
|
||||
// Ratio 4 → lateral half-size = height/4 = 0.5
|
||||
auto tris = create_box(top, bottom, forward, right, 4.0f);
|
||||
|
||||
// Use the triangles (normals, rendering, collision, etc.)
|
||||
for (const auto& tri : tris) {
|
||||
auto n = tri.calculate_normal();
|
||||
(void)n;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage notes & pitfalls
|
||||
|
||||
* **Degenerate axis**: If `top == bottom`, the box is undefined (zero height). Guard against this.
|
||||
* **Directions**: Provide **non-zero**, **reasonably orthogonal** `dir_forward`/`dir_right`. A robust implementation should project/normalize internally, but callers should still pass sensible inputs.
|
||||
* **Winding**: If your renderer or collision expects a specific winding, verify with a unit test and flip vertex order per face if necessary.
|
||||
* **Thickness policy**: This doc assumes both lateral half-extents equal `|top-bottom|/ratio`. If your implementation diverges (e.g., separate forward/right ratios), document it.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
* `omath::Triangle` (vertex utilities: normals, centroid, etc.)
|
||||
* `omath::Vector3` (geometry operations used by the construction)
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 31 Oct 2025*
|
||||
98
docs/3d_primitives/plane.md
Normal file
98
docs/3d_primitives/plane.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# `omath::primitives::create_plane` — Build an oriented quad (2 triangles)
|
||||
|
||||
> Header: your project’s `primitives/plane.hpp`
|
||||
> Namespace: `omath::primitives`
|
||||
> Depends on: `omath::Triangle<omath::Vector3<float>>`, `omath::Vector3<float>`
|
||||
|
||||
```cpp
|
||||
[[nodiscard]]
|
||||
std::array<Triangle<Vector3<float>>, 2>
|
||||
create_plane(const Vector3<float>& vertex_a,
|
||||
const Vector3<float>& vertex_b,
|
||||
const Vector3<float>& direction,
|
||||
float size) noexcept;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What it does
|
||||
|
||||
Creates a **rectangle (quad)** in 3D oriented by the edge **A→B** and a second in-plane **direction**. The quad is returned as **two triangles** suitable for rendering or collision.
|
||||
|
||||
* Edge axis: `e = vertex_b - vertex_a`
|
||||
* Width axis: “direction”, **projected to be perpendicular to `e`** so the quad is planar and well-formed.
|
||||
* Normal (by right-hand rule): `n ∝ e × width`.
|
||||
|
||||
> **Sizing convention**
|
||||
> Typical construction uses **half-width = `size`** along the (normalized, orthogonalized) *direction*, i.e. the total width is `2*size`.
|
||||
> If your implementation interprets `size` as full width, adjust your expectations accordingly.
|
||||
|
||||
---
|
||||
|
||||
## Parameters
|
||||
|
||||
* `vertex_a`, `vertex_b` — two adjacent quad vertices defining the **long edge** of the plane.
|
||||
* `direction` — a vector indicating the **cross-edge direction** within the plane (does not need to be orthogonal or normalized).
|
||||
* `size` — **half-width** of the quad along the (processed) `direction`.
|
||||
|
||||
---
|
||||
|
||||
## Return
|
||||
|
||||
`std::array<Triangle<Vector3<float>>, 2>` — the quad triangulated (consistent CCW winding, outward normal per `e × width`).
|
||||
|
||||
---
|
||||
|
||||
## Robust construction (expected math)
|
||||
|
||||
1. `e = vertex_b - vertex_a`
|
||||
2. Make `d` perpendicular to `e`:
|
||||
|
||||
```
|
||||
d = direction - e * (e.dot(direction) / e.length_sqr());
|
||||
if (d.length_sqr() == 0) pick an arbitrary perpendicular to e
|
||||
d = d.normalized();
|
||||
```
|
||||
3. Offsets: `w = d * size`
|
||||
4. Four corners:
|
||||
|
||||
```
|
||||
A0 = vertex_a - w; A1 = vertex_a + w;
|
||||
B0 = vertex_b - w; B1 = vertex_b + w;
|
||||
```
|
||||
5. Triangles (CCW when viewed from +normal):
|
||||
|
||||
```
|
||||
T0 = Triangle{ A0, A1, B1 }
|
||||
T1 = Triangle{ A0, B1, B0 }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example
|
||||
|
||||
```cpp
|
||||
using omath::Vector3;
|
||||
using omath::Triangle;
|
||||
using omath::primitives::create_plane;
|
||||
|
||||
Vector3<float> a{ -1, 0, -1 }; // edge start
|
||||
Vector3<float> b{ 1, 0, -1 }; // edge end
|
||||
Vector3<float> dir{ 0, 0, 1 }; // cross-edge direction within the plane (roughly +Z)
|
||||
float half_width = 2.0f;
|
||||
|
||||
auto quad = create_plane(a, b, dir, half_width);
|
||||
|
||||
// e.g., compute normals
|
||||
for (const auto& tri : quad) {
|
||||
auto n = tri.calculate_normal(); (void)n;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes & edge cases
|
||||
|
||||
* **Degenerate edge**: if `vertex_a == vertex_b`, the plane is undefined.
|
||||
* **Collinearity**: if `direction` is parallel to `vertex_b - vertex_a`, the function must choose an alternate perpendicular; expect a fallback.
|
||||
* **Winding**: If your renderer expects a specific face order, verify and swap the two vertices in each triangle as needed.
|
||||
190
docs/utility/color.md
Normal file
190
docs/utility/color.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# `omath::Color` — RGBA color with HSV helpers (C++20/23)
|
||||
|
||||
> Header: your project’s `color.hpp`
|
||||
> Namespace: `omath`
|
||||
> Inherits: `Vector4<float>` (`x=r`, `y=g`, `z=b`, `w=a`)
|
||||
> Depends on: `<cstdint>`, `Vector4`, optionally ImGui (`OMATH_IMGUI_INTEGRATION`)
|
||||
> Formatting: provides `std::formatter<omath::Color>`
|
||||
|
||||
`Color` is a tiny RGBA utility on top of `Vector4<float>`. It offers sRGB-style channel construction, HSV↔RGB conversion, in-place HSV setters, linear blending, and string/formatter helpers.
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```cpp
|
||||
#include "color.hpp"
|
||||
using omath::Color;
|
||||
|
||||
// RGBA in [0,1] (r,g,b clamped to [0,1] on construction)
|
||||
Color c{0.2f, 0.4f, 0.8f, 0.5f};
|
||||
|
||||
// From 8-bit channels
|
||||
auto red = Color::from_rgba(255, 0, 0, 255);
|
||||
auto green = Color::from_rgba(0, 255, 0, 160);
|
||||
|
||||
// From HSV (h ∈ [0,1], s ∈ [0,1], v ∈ [0,1])
|
||||
auto cyan = Color::from_hsv(0.5f, 1.0f, 1.0f); // a = 1
|
||||
|
||||
// Read/modify via HSV
|
||||
auto hsv = cyan.to_hsv(); // hue ∈ [0,1], saturation ∈ [0,1], value ∈ [0,1]
|
||||
cyan.set_value(0.6f); // converts back to RGB (alpha becomes 1)
|
||||
|
||||
// Blend linearly (lerp)
|
||||
auto mid = red.blend(green, 0.5f);
|
||||
|
||||
// Printable (0–255 per channel)
|
||||
std::string s = std::format("{}", mid); // "[r:128, g:128, b:0, a:207]" for example
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data model
|
||||
|
||||
* Inherits `Vector4<float>`:
|
||||
|
||||
* `x` = **red**, `y` = **green**, `z` = **blue**, `w` = **alpha**.
|
||||
* Construction clamps **RGB** to `[0,1]` (via `Vector4::clamp(0,1)`), **alpha is not clamped** by that call (see notes).
|
||||
|
||||
---
|
||||
|
||||
## Construction & factories
|
||||
|
||||
```cpp
|
||||
// RGBA in [0,1] (RGB clamped to [0,1]; alpha untouched by clamp)
|
||||
constexpr Color(float r, float g, float b, float a) noexcept;
|
||||
|
||||
// Default
|
||||
constexpr Color() noexcept;
|
||||
|
||||
// From 8-bit RGBA (0–255) → normalized to [0,1]
|
||||
constexpr static Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept;
|
||||
|
||||
// From HSV where hue ∈ [0,1], saturation ∈ [0,1], value ∈ [0,1]
|
||||
struct Hsv { float hue{}, saturation{}, value{}; };
|
||||
|
||||
constexpr static Color from_hsv(float hue, float saturation, float value) noexcept;
|
||||
constexpr static Color from_hsv(const Hsv& hsv) noexcept; // delegates to the above
|
||||
|
||||
// Construct from a Vector4 (RGB clamped, alpha not clamped)
|
||||
constexpr explicit Color(const Vector4& vec) noexcept;
|
||||
```
|
||||
|
||||
**HSV details**
|
||||
|
||||
* `from_hsv(h, s, v)`: `h` is **normalized** (`[0,1]`); it is clamped, then mapped to the 6 hue sectors; **alpha = 1.0**.
|
||||
* `to_hsv()`: returns `Hsv{h,s,v}` with **`h ∈ [0,1]`** (internally computes degrees and divides by 360), `s,v ∈ [0,1]`.
|
||||
|
||||
---
|
||||
|
||||
## Mutators
|
||||
|
||||
```cpp
|
||||
constexpr void set_hue(float h) noexcept; // h ∈ [0,1] recommended
|
||||
constexpr void set_saturation(float s) noexcept; // s ∈ [0,1]
|
||||
constexpr void set_value(float v) noexcept; // v ∈ [0,1]
|
||||
|
||||
// Linear blend: (1-ratio)*this + ratio*other, ratio clamped to [0,1]
|
||||
constexpr Color blend(const Color& other, float ratio) const noexcept;
|
||||
```
|
||||
|
||||
> ⚠️ **Alpha reset on HSV setters:** each `set_*` converts HSV→RGB using `from_hsv(...)`, which **sets alpha to 1.0** (overwriting previous `w`). If you need to preserve alpha:
|
||||
>
|
||||
> ```cpp
|
||||
> float a = col.w;
|
||||
> col.set_value(0.5f);
|
||||
> col.w = a;
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Constants
|
||||
|
||||
```cpp
|
||||
static constexpr Color red(); // (1,0,0,1)
|
||||
static constexpr Color green(); // (0,1,0,1)
|
||||
static constexpr Color blue(); // (0,0,1,1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## String & formatting
|
||||
|
||||
```cpp
|
||||
// "[r:R, g:G, b:B, a:A]" with each channel shown as 0–255 integer
|
||||
std::string to_string() const noexcept;
|
||||
std::wstring to_wstring() const noexcept;
|
||||
std::u8string to_u8string() const noexcept;
|
||||
|
||||
// Formatter forwards to the above (char/wchar_t/char8_t)
|
||||
template<> struct std::formatter<omath::Color>;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ImGui (optional)
|
||||
|
||||
```cpp
|
||||
#ifdef OMATH_IMGUI_INTEGRATION
|
||||
ImColor to_im_color() const noexcept; // constructs from Vector4's to_im_vec4()
|
||||
#endif
|
||||
```
|
||||
|
||||
Ensure `<imgui.h>` is included somewhere before this header when the macro is enabled.
|
||||
|
||||
---
|
||||
|
||||
## Notes & caveats
|
||||
|
||||
* **Alpha clamping:** `Vector4::clamp(min,max)` (called by `Color` ctors) clamps **x,y,z** only in the provided `Vector4` implementation; `w` is **left unchanged**. If you require strict `[0,1]` alpha, clamp it yourself:
|
||||
|
||||
```cpp
|
||||
col.w = std::clamp(col.w, 0.0f, 1.0f);
|
||||
```
|
||||
* **HSV range:** The API consistently uses **normalized hue** (`[0,1]`). Convert degrees ↔ normalized as `h_norm = h_deg / 360.f`.
|
||||
* **Blend space:** `blend` is a **linear** interpolation in RGBA; it is not perceptually uniform.
|
||||
|
||||
---
|
||||
|
||||
## API summary
|
||||
|
||||
```cpp
|
||||
struct Hsv { float hue{}, saturation{}, value{}; };
|
||||
|
||||
class Color final : public Vector4<float> {
|
||||
public:
|
||||
constexpr Color(float r, float g, float b, float a) noexcept;
|
||||
constexpr Color() noexcept;
|
||||
constexpr explicit Color(const Vector4& vec) noexcept;
|
||||
|
||||
static constexpr Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept;
|
||||
static constexpr Color from_hsv(float hue, float saturation, float value) noexcept;
|
||||
static constexpr Color from_hsv(const Hsv& hsv) noexcept;
|
||||
|
||||
constexpr Hsv to_hsv() const noexcept;
|
||||
|
||||
constexpr void set_hue(float h) noexcept;
|
||||
constexpr void set_saturation(float s) noexcept;
|
||||
constexpr void set_value(float v) noexcept;
|
||||
|
||||
constexpr Color blend(const Color& other, float ratio) const noexcept;
|
||||
|
||||
static constexpr Color red();
|
||||
static constexpr Color green();
|
||||
static constexpr Color blue();
|
||||
|
||||
#ifdef OMATH_IMGUI_INTEGRATION
|
||||
ImColor to_im_color() const noexcept;
|
||||
#endif
|
||||
|
||||
std::string to_string() const noexcept;
|
||||
std::wstring to_wstring() const noexcept;
|
||||
std::u8string to_u8string() const noexcept;
|
||||
};
|
||||
|
||||
// formatter<omath::Color> provided
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 31 Oct 2025*
|
||||
194
docs/utility/pattern_scan.md
Normal file
194
docs/utility/pattern_scan.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# `omath::PatternScanner` — Fast byte-pattern search with wildcards
|
||||
|
||||
> Header: your project’s `pattern_scanner.hpp`
|
||||
> Namespace: `omath`
|
||||
> Core API: `scan_for_pattern(...)` (span or iterators)
|
||||
> Errors: `PatternScanError::INVALID_PATTERN_STRING` (from `parse_pattern`)
|
||||
> C++: uses `std::span`, `std::expected`, `std::byte`
|
||||
|
||||
`PatternScanner` scans a contiguous byte range for a **hex pattern** that may include **wildcards**. It returns an iterator to the **first match** or the end iterator if not found (or if the pattern string is invalid).
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```cpp
|
||||
#include "pattern_scanner.hpp"
|
||||
using omath::PatternScanner;
|
||||
|
||||
std::vector<std::byte> buf = /* ... bytes ... */;
|
||||
std::span<std::byte> s{buf.data(), buf.size()};
|
||||
|
||||
// Example pattern: "48 8B ?? ?? 89" (hex bytes with '?' as any byte)
|
||||
auto it = PatternScanner::scan_for_pattern(s, "48 8B ?? ?? 89");
|
||||
if (it != s.end()) {
|
||||
// Found at offset:
|
||||
auto offset = static_cast<std::size_t>(it - s.begin());
|
||||
}
|
||||
```
|
||||
|
||||
Or with iterators:
|
||||
|
||||
```cpp
|
||||
auto it = PatternScanner::scan_for_pattern(buf.begin(), buf.end(), "DE AD BE EF");
|
||||
if (it != buf.end()) { /* ... */ }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pattern string grammar
|
||||
|
||||
The pattern string is parsed into a sequence of byte **tokens**:
|
||||
|
||||
* **Hex byte**: two hexadecimal digits form one byte.
|
||||
Examples: `90`, `4F`, `00`, `ff`
|
||||
* **Wildcard byte**: `?` or `??` matches **any single byte**.
|
||||
* **Separators**: any ASCII whitespace (space, tab, newline) is **ignored** and may be used to group tokens.
|
||||
|
||||
> ✔️ Valid: `"48 8B ?? 05 00"`, `"90 90 90"`, `"??"`, `"00??FF"` (no spaces)
|
||||
> ❌ Invalid: odd number of hex digits in a token, non-hex characters (besides `?` and whitespace)
|
||||
|
||||
If the string cannot be parsed into a clean sequence of tokens, `parse_pattern()` returns `std::unexpected(PatternScanError::INVALID_PATTERN_STRING)`, and the public scan function returns **end**.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace omath {
|
||||
|
||||
enum class PatternScanError { INVALID_PATTERN_STRING };
|
||||
|
||||
class PatternScanner final {
|
||||
public:
|
||||
// Contiguous range (span) overload
|
||||
[[nodiscard]]
|
||||
static std::span<std::byte>::iterator
|
||||
scan_for_pattern(const std::span<std::byte>& range,
|
||||
const std::string_view& pattern);
|
||||
|
||||
// Deleted rvalue-span overload (prevents dangling)
|
||||
static std::span<std::byte>::iterator
|
||||
scan_for_pattern(std::span<std::byte>&&,
|
||||
const std::string_view&) = delete;
|
||||
|
||||
// Iterator overload
|
||||
template<class IteratorType>
|
||||
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
|
||||
static IteratorType
|
||||
scan_for_pattern(const IteratorType& begin,
|
||||
const IteratorType& end,
|
||||
const std::string_view& pattern);
|
||||
private:
|
||||
[[nodiscard]]
|
||||
static std::expected<std::vector<std::optional<std::byte>>, PatternScanError>
|
||||
parse_pattern(const std::string_view& pattern_string);
|
||||
};
|
||||
|
||||
} // namespace omath
|
||||
```
|
||||
|
||||
### Return value
|
||||
|
||||
* On success: iterator to the **first** matching position.
|
||||
* On failure / not found / invalid pattern: **`end`**.
|
||||
|
||||
---
|
||||
|
||||
## Complexity
|
||||
|
||||
Let `N` be the number of bytes in the range and `M` the number of **pattern tokens**.
|
||||
|
||||
* Time: **O(N × M)** (simple sliding window with early break).
|
||||
* Space: **O(M)** for the parsed pattern vector.
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Find a function prologue
|
||||
|
||||
```cpp
|
||||
// x86-64: push rbp; mov rbp, rsp
|
||||
auto it = PatternScanner::scan_for_pattern(s, "55 48 89 E5");
|
||||
if (it != s.end()) {
|
||||
// ... process
|
||||
}
|
||||
```
|
||||
|
||||
### Skip variable bytes with wildcards
|
||||
|
||||
```cpp
|
||||
// mov rax, <imm32>; call <rel32>
|
||||
auto it = PatternScanner::scan_for_pattern(s, "48 B8 ?? ?? ?? ?? ?? ?? ?? ?? E8 ?? ?? ?? ??");
|
||||
```
|
||||
|
||||
### Iterator-based scan (subrange)
|
||||
|
||||
```cpp
|
||||
auto sub_begin = buf.begin() + 1024;
|
||||
auto sub_end = buf.begin() + 4096;
|
||||
auto it = PatternScanner::scan_for_pattern(sub_begin, sub_end, "DE AD ?? BE EF");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Behavior & edge cases
|
||||
|
||||
* **Empty or too-short**: If the effective pattern token count `M` is `0` or `M > N`, the function returns `end`.
|
||||
* **Wildcards**: Each `?`/`??` token matches **exactly one** byte.
|
||||
* **No exceptions**: Invalid pattern → parsed as `unexpected`, public API returns `end`.
|
||||
* **No ownership**: The span overload takes `const std::span<std::byte>&` and returns a **mutable iterator** into that span. You must ensure the underlying memory stays alive. The rvalue-span overload is **deleted** to prevent dangling.
|
||||
|
||||
---
|
||||
|
||||
## Notes & caveats (implementation-sensitive)
|
||||
|
||||
* Although the iterator overload is constrained with `std::input_or_output_iterator`, the implementation uses `*(begin + i + j)` and arithmetic on iterators. In practice this means you should pass **random-access / contiguous iterators** (e.g., from `std::vector<std::byte>` or `std::span<std::byte>`). Using non-random-access iterators would be ill-formed.
|
||||
* The inner matching loop compares a parsed token (`std::optional<std::byte>`) to the candidate byte; `std::nullopt` is treated as a **wildcard** (always matches).
|
||||
* **Implementation note:** the outer scan currently derives its scan bound from the **pattern string length**; conceptually it should use the **number of parsed tokens** (hex/wildcard bytes). If you plan to accept spaces (or other non-byte characters) in the pattern string, ensure the scan window uses the parsed token count to avoid false negatives near the end of the buffer.
|
||||
|
||||
---
|
||||
|
||||
## Testing hooks
|
||||
|
||||
The class befriends several unit tests:
|
||||
|
||||
```
|
||||
unit_test_pattern_scan_read_test_Test
|
||||
unit_test_pattern_scan_corner_case_1_Test
|
||||
unit_test_pattern_scan_corner_case_2_Test
|
||||
unit_test_pattern_scan_corner_case_3_Test
|
||||
unit_test_pattern_scan_corner_case_4_Test
|
||||
```
|
||||
|
||||
Use these to validate parsing correctness, wildcard handling, and boundary conditions.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* **Always returns end**: verify the pattern string is valid hex/wildcards and that you’re scanning the intended subrange. Try a simpler pattern (e.g., a single known byte) to sanity-check.
|
||||
* **Crashes or compile errors with iterator overload**: use iterators that support random access (e.g., from `std::vector`), or prefer the `std::span` overload.
|
||||
* **Ambiguous wildcards**: this scanner treats `?` and `??` as **byte-wide** wildcards (not per-nibble). If you need nibble-level masks, extend `parse_pattern` to support patterns like `A?`/`?F` with bitmask matching.
|
||||
|
||||
---
|
||||
|
||||
## Minimal unit test sketch
|
||||
|
||||
```cpp
|
||||
TEST(pattern, basic) {
|
||||
std::array<std::byte, 8> data{
|
||||
std::byte{0x48}, std::byte{0x8B}, std::byte{0x01}, std::byte{0x02},
|
||||
std::byte{0x89}, std::byte{0x50}, std::byte{0x90}, std::byte{0xCC}
|
||||
};
|
||||
std::span<std::byte> s{data.data(), data.size()};
|
||||
auto it = omath::PatternScanner::scan_for_pattern(s, "48 8B ?? ?? 89");
|
||||
ASSERT_NE(it, s.end());
|
||||
EXPECT_EQ(static_cast<size_t>(it - s.begin()), 0U);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 31 Oct 2025*
|
||||
Reference in New Issue
Block a user