Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 63 additions & 2 deletions include/omath/utility/elf_pattern_scan.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
// Created by Vladislav on 30.12.2025.
//
#pragma once
#include "pattern_scan.hpp"
#include "section_scan_result.hpp"
#include <cstdint>
#include <filesystem>
#include <optional>
#include <span>
#include <string_view>
#include "section_scan_result.hpp"
namespace omath
{
class ElfPatternScanner final
Expand All @@ -18,14 +19,74 @@ namespace omath
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");

template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_loaded_module(module_base_address, target_section_name,
&ElfPatternScanner::scan_section_for_pattern<Pattern>);
}

[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");

template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_file(path_to_file, target_section_name,
&ElfPatternScanner::scan_section_for_pattern<Pattern>);
}

[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
const std::string_view& target_section_name = ".text");

template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_memory_file(file_data, target_section_name,
&ElfPatternScanner::scan_section_for_pattern<Pattern>);
}

private:
using SectionScanFunction = std::optional<std::ptrdiff_t> (*)(std::span<const std::byte>);

template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<std::ptrdiff_t> scan_section_for_pattern(const std::span<const std::byte> section_data)
{
const auto result = PatternScanner::scan_for_pattern<Pattern>(section_data.begin(), section_data.end());

if (result == section_data.end())
return std::nullopt;

return result - section_data.begin();
}

[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& target_section_name,
SectionScanFunction scan_pattern);

[[nodiscard]]
static std::optional<SectionScanResult> scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
SectionScanFunction scan_pattern);

[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name, SectionScanFunction scan_pattern);
};
} // namespace omath
} // namespace omath
63 changes: 62 additions & 1 deletion include/omath/utility/macho_pattern_scan.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
// Created by Copilot on 04.02.2026.
//
#pragma once
#include "pattern_scan.hpp"
#include "section_scan_result.hpp"
#include <cstdint>
#include <filesystem>
#include <optional>
#include <span>
#include <string_view>
#include "section_scan_result.hpp"
namespace omath
{
class MachOPatternScanner final
Expand All @@ -18,14 +19,74 @@ namespace omath
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = "__text");

template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name = "__text")
{
return scan_for_pattern_in_loaded_module(module_base_address, target_section_name,
&MachOPatternScanner::scan_section_for_pattern<Pattern>);
}

[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = "__text");

template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name = "__text")
{
return scan_for_pattern_in_file(path_to_file, target_section_name,
&MachOPatternScanner::scan_section_for_pattern<Pattern>);
}

[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
const std::string_view& target_section_name = "__text");

template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name = "__text")
{
return scan_for_pattern_in_memory_file(file_data, target_section_name,
&MachOPatternScanner::scan_section_for_pattern<Pattern>);
}

private:
using SectionScanFunction = std::optional<std::ptrdiff_t> (*)(std::span<const std::byte>);

template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<std::ptrdiff_t> scan_section_for_pattern(const std::span<const std::byte> section_data)
{
const auto result = PatternScanner::scan_for_pattern<Pattern>(section_data.begin(), section_data.end());

if (result == section_data.end())
return std::nullopt;

return result - section_data.begin();
}

[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& target_section_name,
SectionScanFunction scan_pattern);

[[nodiscard]]
static std::optional<SectionScanResult> scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
SectionScanFunction scan_pattern);

[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name, SectionScanFunction scan_pattern);
};
} // namespace omath
134 changes: 128 additions & 6 deletions include/omath/utility/pattern_scan.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
//

#pragma once
#include <algorithm>
#include <array>
#include <expected>
#include <optional>
#include <span>
#include <stdexcept>
#include <string_view>
#include <vector>
Comment on lines +6 to 13

Expand All @@ -15,6 +18,8 @@ class unit_test_pattern_scan_corner_case_1_Test;
class unit_test_pattern_scan_corner_case_2_Test;
class unit_test_pattern_scan_corner_case_3_Test;
class unit_test_pattern_scan_corner_case_4_Test;
class unit_test_pattern_scan_consteval_read_test_Test;
class unit_test_pattern_scan_consteval_spacing_and_case_Test;
// ReSharper restore CppInconsistentNaming
namespace omath
{
Expand All @@ -29,8 +34,21 @@ namespace omath
friend unit_test_pattern_scan_corner_case_2_Test;
friend unit_test_pattern_scan_corner_case_3_Test;
friend unit_test_pattern_scan_corner_case_4_Test;
friend unit_test_pattern_scan_consteval_read_test_Test;
friend unit_test_pattern_scan_consteval_spacing_and_case_Test;

public:
template<std::size_t N>
struct fixed_string final
{
char value[N]{};

constexpr fixed_string(const char (&text)[N])
{
std::ranges::copy(text, value);
}
};

[[nodiscard]]
static std::span<std::byte>::iterator scan_for_pattern(const std::span<std::byte>& range,
const std::string_view& pattern);
Expand All @@ -49,9 +67,26 @@ namespace omath
if (!parsed_pattern) [[unlikely]]
return end;

return scan_for_parsed_pattern(begin, end, parsed_pattern.value());
}
template<fixed_string Pattern, class IteratorType>
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
static IteratorType scan_for_pattern(const IteratorType& begin, const IteratorType& end)
Comment on lines +72 to +74
{
constexpr auto parsed_pattern = parse_pattern<Pattern>();

return scan_for_parsed_pattern(begin, end, parsed_pattern);
}

private:
template<class IteratorType, class ParsedPattern>
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
static IteratorType scan_for_parsed_pattern(const IteratorType& begin, const IteratorType& end,
const ParsedPattern& parsed_pattern)
{
Comment on lines +82 to +86
const auto whole_range_size = static_cast<std::ptrdiff_t>(std::distance(begin, end));

const auto pattern_size = static_cast<std::ptrdiff_t>(parsed_pattern->size());
const auto pattern_size = static_cast<std::ptrdiff_t>(parsed_pattern.size());
const std::ptrdiff_t scan_size = whole_range_size - pattern_size;

if (scan_size < 0)
Expand All @@ -61,9 +96,9 @@ namespace omath
{
bool found = true;

for (std::ptrdiff_t j = 0; j < static_cast<std::ptrdiff_t>(parsed_pattern->size()); j++)
for (std::ptrdiff_t j = 0; j < static_cast<std::ptrdiff_t>(parsed_pattern.size()); j++)
{
found = parsed_pattern->at(j) == std::nullopt || parsed_pattern->at(j) == *(begin + i + j);
found = parsed_pattern.at(j) == std::nullopt || parsed_pattern.at(j) == *(begin + i + j);

if (!found)
break;
Expand All @@ -73,10 +108,97 @@ namespace omath
}
return end;
}

private:
[[nodiscard]]
static std::expected<std::vector<std::optional<std::byte>>, PatternScanError>
parse_pattern(const std::string_view& pattern_string);

[[nodiscard]]
constexpr static bool is_space(const char c)
{
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}

[[nodiscard]]
constexpr static int hex_value(const char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return -1;
}
template<fixed_string Pattern>
[[nodiscard]]
static consteval std::size_t signature_size()
{
std::size_t count = 0;
bool in_token = false;

for (std::size_t i = 0; i + 1 < sizeof(Pattern.value); ++i)
{
if (is_space(Pattern.value[i]))
{
in_token = false;
}
else if (!in_token)
{
++count;
in_token = true;
}
}

return count;
}

template<fixed_string Pattern>
static consteval std::array<std::optional<std::byte>, signature_size<Pattern>()> parse_pattern()
{
std::array<std::optional<std::byte>, signature_size<Pattern>()> result{};
std::size_t out = 0;
std::size_t i = 0;

while (i + 1 < sizeof(Pattern.value))
{
while (i + 1 < sizeof(Pattern.value) && is_space(Pattern.value[i]))
++i;

const std::size_t token_start = i;

while (i + 1 < sizeof(Pattern.value) && !is_space(Pattern.value[i]))
++i;

const std::size_t token_size = i - token_start;

if (token_size == 0)
continue;

// ReSharper disable once CppTooWideScope
const bool is_wildcard = (token_size == 1 || token_size == 2) && Pattern.value[token_start] == '?';

if (is_wildcard)
{
if (token_size == 2 && Pattern.value[token_start + 1] != '?')
throw std::logic_error("invalid wildcard token");

result[out++] = std::nullopt;
continue;
}

if (token_size != 2)
throw std::logic_error("invalid byte token");

const int high = hex_value(Pattern.value[token_start]);
const int low = hex_value(Pattern.value[token_start + 1]);

if (high < 0 || low < 0)
throw std::logic_error("invalid hex byte");

result[out++] = static_cast<std::byte>((high << 4) | low);
}

return result;
}
};
} // namespace omath
} // namespace omath
Loading
Loading