Compare commits

...

28 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
a5fcb884f4 Remove TOC gradient mask and add liquid glass to search results
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2026-02-07 01:35:03 +00:00
copilot-swe-agent[bot]
4ba5355837 Add dynamic mouse-tracking liquid glass effect (JS + CSS)
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2026-02-07 01:26:14 +00:00
copilot-swe-agent[bot]
e9c2ea7e47 Add Apple Liquid Glass glassmorphism design for mkdocs site
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2026-02-07 01:18:20 +00:00
copilot-swe-agent[bot]
0120c193a5 Enable C++ syntax highlighting via pymdownx.highlight and pymdownx.superfences
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2026-02-07 00:55:51 +00:00
copilot-swe-agent[bot]
b9d83f2bba Switch to Material theme with dark mode and orange accent colors
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2026-02-07 00:48:58 +00:00
copilot-swe-agent[bot]
66c47d87c1 Add Roboto Condensed font and documentation for new features
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2026-02-07 00:30:39 +00:00
copilot-swe-agent[bot]
6dd48f7a0c Initial plan 2026-02-07 00:25:23 +00:00
9c4e2a3319 Merge pull request #146 from orange-cpp/feature/line_tracer_template
improvement
2026-02-06 00:14:15 +03:00
7597d95778 fixed warnings 2026-02-06 00:02:00 +03:00
5aa0e2e949 added noexcept 2026-02-05 23:45:41 +03:00
b7b1154f29 simplified shit 2026-02-05 23:43:17 +03:00
b10e26e6ba added constexpr 2026-02-05 23:38:51 +03:00
ba23fee243 removed uselss c++ file 2026-02-05 23:31:14 +03:00
32e0f9e636 improvement 2026-02-05 23:27:31 +03:00
63b4327c91 Merge pull request #145 from orange-cpp/feature/macho_improvement
Feature/macho improvement
2026-02-04 19:27:44 +03:00
dbad87de0f fixed bug 2026-02-04 19:10:06 +03:00
8dd044fa1e removed nesting 2026-02-04 18:35:04 +03:00
c25a3da196 removed nesting 2026-02-04 18:33:05 +03:00
d64d60cfcd fixed codestyle 2026-02-04 18:30:45 +03:00
2ef25b0ce8 added resharper ignore segment 2026-02-04 18:29:55 +03:00
Copilot
775949887a Add Mach-O pattern scanner (#144)
* Initial plan

* Add Mach-O pattern scanner implementation and unit tests

Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>

* Add Mach-O pattern scanner

Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>

* Remove CodeQL build artifacts from PR

Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2026-02-04 17:30:20 +03:00
510e3d7d1c Merge pull request #143 from luadebug/opengl
fix `pixi run examples` for linux
2026-01-30 16:00:50 +03:00
Saikari
72404f2159 do similiar for aarch64 linux 2026-01-30 05:13:27 +03:00
Saikari
0e792c9b9c add pixi.lock to gitignore 2026-01-30 05:06:14 +03:00
Saikari
7e9151084e fix pixi run examples for linux 2026-01-30 05:04:50 +03:00
Saikari
43c8f2e6a5 Add initial project configuration and build scripts for omath library
- Created pixi.toml for project metadata and dependencies management.
- Added formatting script (fmt.cmake) to ensure consistent CMake file formatting.
- Implemented benchmark execution script (run.benchmark.cmake) to run benchmark tests.
- Developed example execution script (run.examples.cmake) to run example applications.
- Created unit test execution script (run.unit.tests.cmake) to run unit tests.

removed lock file
2026-01-30 02:36:34 +03:00
b0fd8d42f4 updated tag 2026-01-29 20:09:36 +03:00
b5229f72d5 removed unity build completely 2026-01-29 20:06:18 +03:00
40 changed files with 2460 additions and 410 deletions

25
.cmake-format Normal file
View File

@@ -0,0 +1,25 @@
format:
line_width: 100
tab_size: 4
use_tabchars: false
max_subgroups_hwrap: 3
max_pargs_hwrap: 5
separate_ctrl_name_with_space: false
separate_fn_name_with_space: false
dangle_parens: false
dangle_align: child
line_ending: unix
command_case: canonical
keyword_case: upper
enable_sort: true
autosort: true
markup:
bullet_char: "*"
enum_char: .
enable_markup: false
additional_commands:
target_link_libraries:
kwargs:
PUBLIC: "*"
SHARED: "*"
PRIVATE: "*"

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# SCM syntax highlighting & preventing 3-way merges
pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff

7
.gitignore vendored
View File

@@ -6,3 +6,10 @@
/build/ /build/
/clang-coverage/ /clang-coverage/
*.gcov *.gcov
*.bin
/site/
# pixi lock
pixi.lock
# pixi environments
.pixi/*
!.pixi/config.toml

View File

@@ -9,49 +9,56 @@ include(CheckCXXCompilerFlag)
include(cmake/Coverage.cmake) include(cmake/Coverage.cmake)
include(cmake/Valgrind.cmake) include(cmake/Valgrind.cmake)
if (MSVC) if(MSVC)
check_cxx_compiler_flag("/arch:AVX2" COMPILER_SUPPORTS_AVX2) check_cxx_compiler_flag("/arch:AVX2" COMPILER_SUPPORTS_AVX2)
else () else()
check_cxx_compiler_flag("-mavx2" COMPILER_SUPPORTS_AVX2) check_cxx_compiler_flag("-mavx2" COMPILER_SUPPORTS_AVX2)
endif () endif()
option(OMATH_BUILD_TESTS "Build unit tests" OFF) option(OMATH_BUILD_TESTS "Build unit tests" OFF)
option(OMATH_BUILD_BENCHMARK "Build benchmarks" OFF) option(OMATH_BUILD_BENCHMARK "Build benchmarks" OFF)
option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON) option(OMATH_THREAT_WARNING_AS_ERROR
"Set highest level of warnings and force compiler to treat them as errors" ON)
option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF) option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF)
option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ${COMPILER_SUPPORTS_AVX2}) option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ${COMPILER_SUPPORTS_AVX2})
option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types to imgui types" OFF) option(OMATH_IMGUI_INTEGRATION "Omath will define method to convert omath types to imgui types" OFF)
option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" OFF) option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" OFF)
option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OFF) option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OFF)
option(OMATH_SUPRESS_SAFETY_CHECKS "Supress some safety checks in release build to improve general performance" ON) option(OMATH_ENABLE_LEGACY
option(OMATH_USE_UNITY_BUILD "Will enable unity build to speed up compilation" OFF) "Will enable legacy classes that MUST be used ONLY for backward compatibility" ON)
option(OMATH_ENABLE_LEGACY "Will enable legacy classes that MUST be used ONLY for backward compatibility" ON) option(OMATH_SUPRESS_SAFETY_CHECKS
"Supress some safety checks in release build to improve general performance" ON)
option(OMATH_ENABLE_COVERAGE "Enable coverage" OFF) option(OMATH_ENABLE_COVERAGE "Enable coverage" OFF)
option(OMATH_ENABLE_FORCE_INLINE "Will for compiler to make some functions to be force inlined no matter what" ON) option(OMATH_ENABLE_FORCE_INLINE
if (VCPKG_MANIFEST_FEATURES) "Will for compiler to make some functions to be force inlined no matter what" ON)
foreach (omath_feature IN LISTS VCPKG_MANIFEST_FEATURES) if(VCPKG_MANIFEST_FEATURES)
if (omath_feature STREQUAL "imgui") foreach(omath_feature IN LISTS VCPKG_MANIFEST_FEATURES)
if(omath_feature STREQUAL "imgui")
set(OMATH_IMGUI_INTEGRATION ON) set(OMATH_IMGUI_INTEGRATION ON)
elseif (omath_feature STREQUAL "avx2") elseif(omath_feature STREQUAL "avx2")
set(OMATH_USE_AVX2 ${COMPILER_SUPPORTS_AVX2}) set(OMATH_USE_AVX2 ${COMPILER_SUPPORTS_AVX2})
elseif (omath_feature STREQUAL "tests") elseif(omath_feature STREQUAL "tests")
set(OMATH_BUILD_TESTS ON) set(OMATH_BUILD_TESTS ON)
elseif (omath_feature STREQUAL "benchmark") elseif(omath_feature STREQUAL "benchmark")
set(OMATH_BUILD_BENCHMARK ON) set(OMATH_BUILD_BENCHMARK ON)
elseif (omath_feature STREQUAL "examples") elseif(omath_feature STREQUAL "examples")
set(OMATH_BUILD_EXAMPLES ON) set(OMATH_BUILD_EXAMPLES ON)
endif () endif()
endforeach () endforeach()
endif () endif()
if (OMATH_USE_AVX2 AND NOT COMPILER_SUPPORTS_AVX2) if(OMATH_USE_AVX2 AND NOT COMPILER_SUPPORTS_AVX2)
message(WARNING "OMATH_USE_AVX2 requested, but compiler/target does not support AVX2. Disabling.") message(
WARNING "OMATH_USE_AVX2 requested, but compiler/target does not support AVX2. Disabling.")
set(OMATH_USE_AVX2 OFF CACHE BOOL "Omath will use AVX2 to boost performance" FORCE) set(OMATH_USE_AVX2 OFF CACHE BOOL "Omath will use AVX2 to boost performance" FORCE)
endif () endif()
if (${PROJECT_IS_TOP_LEVEL}) if(${PROJECT_IS_TOP_LEVEL})
message(STATUS "[${PROJECT_NAME}]: Building on ${CMAKE_HOST_SYSTEM_NAME}, compiler ${CMAKE_CXX_COMPILER_ID}") message(
STATUS
"[${PROJECT_NAME}]: Building on ${CMAKE_HOST_SYSTEM_NAME}, compiler ${CMAKE_CXX_COMPILER_ID}"
)
message(STATUS "[${PROJECT_NAME}]: Warnings as errors ${OMATH_THREAT_WARNING_AS_ERROR}") message(STATUS "[${PROJECT_NAME}]: Warnings as errors ${OMATH_THREAT_WARNING_AS_ERROR}")
message(STATUS "[${PROJECT_NAME}]: Build unit tests ${OMATH_BUILD_TESTS}") message(STATUS "[${PROJECT_NAME}]: Build unit tests ${OMATH_BUILD_TESTS}")
message(STATUS "[${PROJECT_NAME}]: Build benchmark ${OMATH_BUILD_BENCHMARK}") message(STATUS "[${PROJECT_NAME}]: Build benchmark ${OMATH_BUILD_BENCHMARK}")
@@ -65,84 +72,79 @@ if (${PROJECT_IS_TOP_LEVEL})
message(STATUS "[${PROJECT_NAME}]: Building using vcpkg ${OMATH_BUILD_VIA_VCPKG}") message(STATUS "[${PROJECT_NAME}]: Building using vcpkg ${OMATH_BUILD_VIA_VCPKG}")
message(STATUS "[${PROJECT_NAME}]: Coverage feature status ${OMATH_ENABLE_COVERAGE}") message(STATUS "[${PROJECT_NAME}]: Coverage feature status ${OMATH_ENABLE_COVERAGE}")
message(STATUS "[${PROJECT_NAME}]: Valgrind feature status ${OMATH_ENABLE_VALGRIND}") message(STATUS "[${PROJECT_NAME}]: Valgrind feature status ${OMATH_ENABLE_VALGRIND}")
endif () endif()
file(GLOB_RECURSE OMATH_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp") file(GLOB_RECURSE OMATH_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp")
file(GLOB_RECURSE OMATH_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp") file(GLOB_RECURSE OMATH_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp")
if (OMATH_BUILD_AS_SHARED_LIBRARY) if(OMATH_BUILD_AS_SHARED_LIBRARY)
add_library(${PROJECT_NAME} SHARED ${OMATH_SOURCES} ${OMATH_HEADERS}) add_library(${PROJECT_NAME} SHARED ${OMATH_SOURCES} ${OMATH_HEADERS})
else () else()
add_library(${PROJECT_NAME} STATIC ${OMATH_SOURCES} ${OMATH_HEADERS}) add_library(${PROJECT_NAME} STATIC ${OMATH_SOURCES} ${OMATH_HEADERS})
endif () endif()
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_VERSION="${PROJECT_VERSION}") target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_VERSION="${PROJECT_VERSION}")
if (OMATH_IMGUI_INTEGRATION) if(OMATH_IMGUI_INTEGRATION)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_IMGUI_INTEGRATION) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_IMGUI_INTEGRATION)
# IMGUI is being linked as submodule # IMGUI is being linked as submodule
if (TARGET imgui) if(TARGET imgui)
target_link_libraries(${PROJECT_NAME} PUBLIC imgui) target_link_libraries(${PROJECT_NAME} PUBLIC imgui)
install(TARGETS imgui install(
TARGETS imgui
EXPORT omathTargets EXPORT omathTargets
ARCHIVE DESTINATION lib ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib LIBRARY DESTINATION lib
RUNTIME DESTINATION bin) RUNTIME DESTINATION bin)
else () else()
# Assume that IMGUI linked via VCPKG. # Assume that IMGUI linked via VCPKG.
find_package(imgui CONFIG REQUIRED) find_package(imgui CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC imgui::imgui) target_link_libraries(${PROJECT_NAME} PUBLIC imgui::imgui)
endif () endif()
endif () endif()
if (OMATH_USE_AVX2) if(OMATH_USE_AVX2)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_USE_AVX2) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_USE_AVX2)
endif () endif()
if (OMATH_SUPRESS_SAFETY_CHECKS) if(OMATH_SUPRESS_SAFETY_CHECKS)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_SUPRESS_SAFETY_CHECKS) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_SUPRESS_SAFETY_CHECKS)
endif () endif()
if (OMATH_ENABLE_LEGACY) if(OMATH_ENABLE_LEGACY)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_LEGACY) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_LEGACY)
endif () endif()
if (OMATH_ENABLE_FORCE_INLINE) if(OMATH_ENABLE_FORCE_INLINE)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_FORCE_INLINE) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_FORCE_INLINE)
endif () endif()
set_target_properties(${PROJECT_NAME} PROPERTIES set_target_properties(
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" ${PROJECT_NAME}
PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
CXX_STANDARD 23 CXX_STANDARD 23
CXX_STANDARD_REQUIRED ON) CXX_STANDARD_REQUIRED ON)
if (OMATH_USE_UNITY_BUILD)
set_target_properties(${PROJECT_NAME} PROPERTIES
UNITY_BUILD ON
UNITY_BUILD_BATCH_SIZE 20)
endif ()
if (OMATH_STATIC_MSVC_RUNTIME_LIBRARY) if (OMATH_STATIC_MSVC_RUNTIME_LIBRARY)
set_target_properties(${PROJECT_NAME} PROPERTIES set_target_properties(${PROJECT_NAME} PROPERTIES
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>" MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>"
) )
endif () endif ()
if (OMATH_USE_AVX2) if(OMATH_USE_AVX2)
if (MSVC) if(MSVC)
target_compile_options(${PROJECT_NAME} PUBLIC /arch:AVX2) target_compile_options(${PROJECT_NAME} PUBLIC /arch:AVX2)
elseif (EMSCRIPTEN) elseif(EMSCRIPTEN)
target_compile_options(${PROJECT_NAME} PUBLIC -msimd128 -mavx2) target_compile_options(${PROJECT_NAME} PUBLIC -msimd128 -mavx2)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
target_compile_options(${PROJECT_NAME} PUBLIC -mfma -mavx2) target_compile_options(${PROJECT_NAME} PUBLIC -mfma -mavx2)
endif () endif()
endif () endif()
if(EMSCRIPTEN) if(EMSCRIPTEN)
target_compile_options(${PROJECT_NAME} PUBLIC -fexceptions) target_compile_options(${PROJECT_NAME} PUBLIC -fexceptions)
@@ -151,79 +153,79 @@ endif()
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
if(OMATH_BUILD_TESTS)
if (OMATH_BUILD_TESTS)
add_subdirectory(tests) add_subdirectory(tests)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_BUILD_TESTS) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_BUILD_TESTS)
if(OMATH_ENABLE_COVERAGE) if(OMATH_ENABLE_COVERAGE)
omath_setup_coverage(${PROJECT_NAME}) omath_setup_coverage(${PROJECT_NAME})
endif() endif()
endif () endif()
if (OMATH_BUILD_BENCHMARK) if(OMATH_BUILD_BENCHMARK)
add_subdirectory(benchmark) add_subdirectory(benchmark)
endif () endif()
if (OMATH_BUILD_EXAMPLES) if(OMATH_BUILD_EXAMPLES)
add_subdirectory(examples) add_subdirectory(examples)
endif () endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND OMATH_THREAT_WARNING_AS_ERROR)
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND OMATH_THREAT_WARNING_AS_ERROR)
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX) target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX)
elseif (OMATH_THREAT_WARNING_AS_ERROR) elseif(OMATH_THREAT_WARNING_AS_ERROR)
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror) target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror)
endif () endif()
# Windows SDK redefine min/max via preprocessor and break std::min and std::max # Windows SDK redefine min/max via preprocessor and break std::min and std::max
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_definitions(${PROJECT_NAME} INTERFACE NOMINMAX) target_compile_definitions(${PROJECT_NAME} INTERFACE NOMINMAX)
endif () endif()
target_include_directories(${PROJECT_NAME}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # Use this path when building the project
$<INSTALL_INTERFACE:include> # Use this path when the project is installed
)
target_include_directories(
${PROJECT_NAME}
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # Use this path
# when building
# the project
$<INSTALL_INTERFACE:include> # Use this path when the project is
# installed
)
# Installation rules # Installation rules
# Install the library # Install the library
install(TARGETS ${PROJECT_NAME} install(
TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}Targets EXPORT ${PROJECT_NAME}Targets
ARCHIVE DESTINATION lib COMPONENT ${PROJECT_NAME} # For static libraries ARCHIVE DESTINATION lib COMPONENT ${PROJECT_NAME} # For static libraries
LIBRARY DESTINATION lib COMPONENT ${PROJECT_NAME} # For shared libraries LIBRARY DESTINATION lib COMPONENT ${PROJECT_NAME} # For shared libraries
RUNTIME DESTINATION bin COMPONENT ${PROJECT_NAME} # For executables (on Windows) RUNTIME DESTINATION bin COMPONENT ${PROJECT_NAME} # For executables (on
) # Windows)
)
# Install headers as part of omath_component # Install headers as part of omath_component
install(DIRECTORY include/ DESTINATION include COMPONENT ${PROJECT_NAME}) install(DIRECTORY include/ DESTINATION include COMPONENT ${PROJECT_NAME})
# Export omath target for CMake find_package support, also under omath_component # Export omath target for CMake find_package support, also under omath_component
install(EXPORT ${PROJECT_NAME}Targets install(
EXPORT ${PROJECT_NAME}Targets
FILE ${PROJECT_NAME}Targets.cmake FILE ${PROJECT_NAME}Targets.cmake
NAMESPACE ${PROJECT_NAME}:: NAMESPACE ${PROJECT_NAME}::
DESTINATION lib/cmake/${PROJECT_NAME} COMPONENT ${PROJECT_NAME} DESTINATION lib/cmake/${PROJECT_NAME}
) COMPONENT ${PROJECT_NAME})
# Generate the omathConfigVersion.cmake file # Generate the omathConfigVersion.cmake file
write_basic_package_version_file( write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion)
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
# Generate the omathConfig.cmake file from the template (which is in the cmake/ folder) # Generate the omathConfig.cmake file from the template (which is in the cmake/
# folder)
configure_package_config_file( configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/omathConfig.cmake.in" # Path to the .in file "${CMAKE_CURRENT_SOURCE_DIR}/cmake/omathConfig.cmake.in" # Path to the .in
"${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake" # Output path for the generated file # file
INSTALL_DESTINATION lib/cmake/${PROJECT_NAME} "${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake" # Output path for the
) # generated file
INSTALL_DESTINATION lib/cmake/${PROJECT_NAME})
# Install the generated config files # Install the generated config files
install(FILES install(FILES "${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/omathConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake" "${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake"
DESTINATION lib/cmake/${PROJECT_NAME} DESTINATION lib/cmake/${PROJECT_NAME})
)

View File

@@ -1 +1 @@
4.6.0 4.7.1

View File

@@ -1,22 +1,23 @@
project(omath_benchmark) project(omath_benchmark)
file(GLOB_RECURSE OMATH_BENCHMARK_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") file(GLOB_RECURSE OMATH_BENCHMARK_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
add_executable(${PROJECT_NAME} ${OMATH_BENCHMARK_SOURCES}) add_executable(${PROJECT_NAME} ${OMATH_BENCHMARK_SOURCES})
set_target_properties(${PROJECT_NAME} PROPERTIES set_target_properties(
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" ${PROJECT_NAME}
PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
CXX_STANDARD 23 CXX_STANDARD 23
CXX_STANDARD_REQUIRED ON) CXX_STANDARD_REQUIRED ON)
if (TARGET benchmark::benchmark) # Benchmark is being linked as submodule if(TARGET benchmark::benchmark) # Benchmark is being linked as submodule
target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark omath) target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark omath)
else() else()
find_package(benchmark CONFIG REQUIRED) # Benchmark is being linked as vcpkg package find_package(benchmark CONFIG REQUIRED) # Benchmark is being linked as vcpkg
# package
target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark omath) target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark omath)
endif () endif()
if(OMATH_ENABLE_VALGRIND) if(OMATH_ENABLE_VALGRIND)
omath_setup_valgrind(${PROJECT_NAME}) omath_setup_valgrind(${PROJECT_NAME})

View File

@@ -7,38 +7,23 @@ function(omath_setup_coverage TARGET_NAME)
endif() endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND MSVC) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND MSVC)
target_compile_options(${TARGET_NAME} PRIVATE target_compile_options(${TARGET_NAME} PRIVATE -fprofile-instr-generate -fcoverage-mapping
-fprofile-instr-generate /Zi)
-fcoverage-mapping target_link_options(
/Zi ${TARGET_NAME}
) PRIVATE
target_link_options(${TARGET_NAME} PRIVATE
-fprofile-instr-generate -fprofile-instr-generate
-fcoverage-mapping -fcoverage-mapping
/DEBUG:FULL /DEBUG:FULL
/INCREMENTAL:NO /INCREMENTAL:NO
/PROFILE /PROFILE)
)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang")
target_compile_options(${TARGET_NAME} PRIVATE target_compile_options(${TARGET_NAME} PRIVATE -fprofile-instr-generate -fcoverage-mapping
-fprofile-instr-generate -g -O0)
-fcoverage-mapping target_link_options(${TARGET_NAME} PRIVATE -fprofile-instr-generate -fcoverage-mapping)
-g
-O0
)
target_link_options(${TARGET_NAME} PRIVATE
-fprofile-instr-generate
-fcoverage-mapping
)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(${TARGET_NAME} PRIVATE target_compile_options(${TARGET_NAME} PRIVATE --coverage -g -O0)
--coverage target_link_options(${TARGET_NAME} PRIVATE --coverage)
-g
-O0
)
target_link_options(${TARGET_NAME} PRIVATE
--coverage
)
endif() endif()
if(TARGET coverage) if(TARGET coverage)
@@ -47,40 +32,36 @@ function(omath_setup_coverage TARGET_NAME)
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND MSVC) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND MSVC)
message(STATUS "MSVC detected: Use VS Code Coverage from CI workflow") message(STATUS "MSVC detected: Use VS Code Coverage from CI workflow")
add_custom_target(coverage add_custom_target(
coverage
DEPENDS unit_tests DEPENDS unit_tests
COMMAND $<TARGET_FILE:unit_tests> COMMAND $<TARGET_FILE:unit_tests>
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT "Running tests for coverage (use VS Code Coverage from CI)" COMMENT "Running tests for coverage (use VS Code Coverage from CI)")
)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang")
add_custom_target(coverage add_custom_target(
coverage
DEPENDS unit_tests DEPENDS unit_tests
COMMAND bash "${CMAKE_SOURCE_DIR}/scripts/coverage-llvm.sh" COMMAND bash "${CMAKE_SOURCE_DIR}/scripts/coverage-llvm.sh" "${CMAKE_SOURCE_DIR}"
"${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" "$<TARGET_FILE:unit_tests>" "${CMAKE_BINARY_DIR}/coverage"
"${CMAKE_BINARY_DIR}"
"$<TARGET_FILE:unit_tests>"
"${CMAKE_BINARY_DIR}/coverage"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT "Running LLVM coverage" COMMENT "Running LLVM coverage")
)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
add_custom_target(coverage add_custom_target(
coverage
DEPENDS unit_tests DEPENDS unit_tests
COMMAND $<TARGET_FILE:unit_tests> || true COMMAND $<TARGET_FILE:unit_tests> || true
COMMAND lcov --capture --directory "${CMAKE_BINARY_DIR}" COMMAND lcov --capture --directory "${CMAKE_BINARY_DIR}" --output-file
--output-file "${CMAKE_BINARY_DIR}/coverage.info" "${CMAKE_BINARY_DIR}/coverage.info" --ignore-errors mismatch,gcov
--ignore-errors mismatch,gcov COMMAND
COMMAND lcov --remove "${CMAKE_BINARY_DIR}/coverage.info" lcov --remove "${CMAKE_BINARY_DIR}/coverage.info" "*/tests/*" "*/gtest/*"
"*/tests/*" "*/gtest/*" "*/googletest/*" "*/_deps/*" "/usr/*" "*/googletest/*" "*/_deps/*" "/usr/*" --output-file
--output-file "${CMAKE_BINARY_DIR}/coverage.info" "${CMAKE_BINARY_DIR}/coverage.info" --ignore-errors unused
--ignore-errors unused COMMAND genhtml "${CMAKE_BINARY_DIR}/coverage.info" --output-directory
COMMAND genhtml "${CMAKE_BINARY_DIR}/coverage.info" "${CMAKE_BINARY_DIR}/coverage"
--output-directory "${CMAKE_BINARY_DIR}/coverage"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
COMMENT "Running lcov/genhtml" COMMENT "Running lcov/genhtml")
)
endif() endif()
endfunction() endfunction()

View File

@@ -22,23 +22,19 @@ function(omath_setup_valgrind TARGET_NAME)
return() return()
endif() endif()
set(VALGRIND_FLAGS set(VALGRIND_FLAGS --leak-check=full --show-leak-kinds=all --track-origins=yes
--leak-check=full --error-exitcode=99)
--show-leak-kinds=all
--track-origins=yes
--error-exitcode=99
)
set(VALGRIND_TARGET "valgrind_${TARGET_NAME}") set(VALGRIND_TARGET "valgrind_${TARGET_NAME}")
if(NOT TARGET ${VALGRIND_TARGET}) if(NOT TARGET ${VALGRIND_TARGET})
add_custom_target(${VALGRIND_TARGET} add_custom_target(
${VALGRIND_TARGET}
DEPENDS ${TARGET_NAME} DEPENDS ${TARGET_NAME}
COMMAND ${VALGRIND_EXECUTABLE} ${VALGRIND_FLAGS} $<TARGET_FILE:${TARGET_NAME}> COMMAND ${VALGRIND_EXECUTABLE} ${VALGRIND_FLAGS} $<TARGET_FILE:${TARGET_NAME}>
WORKING_DIRECTORY $<TARGET_FILE_DIR:${TARGET_NAME}> WORKING_DIRECTORY $<TARGET_FILE_DIR:${TARGET_NAME}>
COMMENT "Running Valgrind memory check on ${TARGET_NAME}..." COMMENT "Running Valgrind memory check on ${TARGET_NAME}..."
USES_TERMINAL USES_TERMINAL)
)
add_dependencies(valgrind_all ${VALGRIND_TARGET}) add_dependencies(valgrind_all ${VALGRIND_TARGET})
endif() endif()

View File

@@ -0,0 +1,93 @@
# `omath::collision::ColliderInterface` — Abstract collider base class
> Header: `omath/collision/collider_interface.hpp`
> Namespace: `omath::collision`
> Depends on: `omath/linear_algebra/vector3.hpp`
`ColliderInterface` is the abstract base class for all colliders used by the GJK and EPA algorithms. Any shape that can report its furthest vertex along a given direction can implement this interface and be used for collision detection.
---
## API
```cpp
namespace omath::collision {
template<class VecType = Vector3<float>>
class ColliderInterface {
public:
using VectorType = VecType;
virtual ~ColliderInterface() = default;
// Return the world-space position of the vertex furthest along `direction`.
[[nodiscard]]
virtual VectorType find_abs_furthest_vertex_position(
const VectorType& direction) const = 0;
// Get the collider's origin (center / position).
[[nodiscard]]
virtual const VectorType& get_origin() const = 0;
// Reposition the collider.
virtual void set_origin(const VectorType& new_origin) = 0;
};
} // namespace omath::collision
```
---
## Implementing a custom collider
To create a new collider shape, derive from `ColliderInterface` and implement the three pure-virtual methods:
```cpp
#include "omath/collision/collider_interface.hpp"
class SphereCollider final
: public omath::collision::ColliderInterface<omath::Vector3<float>>
{
public:
SphereCollider(omath::Vector3<float> center, float radius)
: m_center(center), m_radius(radius) {}
[[nodiscard]]
omath::Vector3<float> find_abs_furthest_vertex_position(
const omath::Vector3<float>& direction) const override
{
return m_center + direction.normalized() * m_radius;
}
[[nodiscard]]
const omath::Vector3<float>& get_origin() const override
{ return m_center; }
void set_origin(const omath::Vector3<float>& new_origin) override
{ m_center = new_origin; }
private:
omath::Vector3<float> m_center;
float m_radius;
};
```
---
## Notes
* **Template parameter**: The default vector type is `Vector3<float>`, but any vector type with a `dot()` method can be used.
* **GJK/EPA compatibility**: Both `GjkAlgorithm` and `EpaAlgorithm` accept any type satisfying the `ColliderInterface` contract through their template parameters.
---
## See also
* [GJK Algorithm](gjk_algorithm.md) — collision detection using GJK.
* [EPA Algorithm](epa_algorithm.md) — penetration depth via EPA.
* [Mesh Collider](mesh_collider.md) — concrete collider wrapping a `Mesh`.
* [Simplex](simplex.md) — simplex data structure used by GJK/EPA.
---
*Last updated: Feb 2026*

View File

@@ -0,0 +1,115 @@
# `omath::EncryptedVariable` — Compile-time XOR-encrypted variable
> Header: `omath/containers/encrypted_variable.hpp`
> Namespace: `omath`
> Depends on: `<array>`, `<cstddef>`, `<cstdint>`, `<span>`
`EncryptedVariable` keeps a value XOR-encrypted in memory at rest, using a **compile-time generated random key**. It is designed to hinder static analysis and memory scanners from reading sensitive values (e.g., game constants, keys, thresholds) directly from process memory.
---
## Key concepts
* **Compile-time key generation** — a unique random byte array is produced at compile time via SplitMix64 + FNV-1a seeded from `__FILE__`, `__DATE__`, and `__TIME__`. Each `OMATH_DEF_CRYPT_VAR` expansion receives a distinct key.
* **XOR cipher** — `encrypt()` / `decrypt()` toggle the encrypted state by XOR-ing the raw bytes of the stored value with the key.
* **VarAnchor (RAII guard)** — `drop_anchor()` returns a `VarAnchor` that decrypts on construction and re-encrypts on destruction, ensuring the plaintext window is as short as possible.
---
## API
```cpp
namespace omath {
template<class T, std::size_t key_size, std::array<std::uint8_t, key_size> key>
class EncryptedVariable final {
public:
using value_type = std::remove_cvref_t<T>;
constexpr explicit EncryptedVariable(const value_type& data);
[[nodiscard]] constexpr bool is_encrypted() const;
constexpr void decrypt();
constexpr void encrypt();
[[nodiscard]] constexpr value_type& value();
[[nodiscard]] constexpr const value_type& value() const;
[[nodiscard]] constexpr auto drop_anchor(); // returns VarAnchor
constexpr ~EncryptedVariable(); // decrypts on destruction
};
template<class EncryptedVarType>
class VarAnchor final {
public:
constexpr VarAnchor(EncryptedVarType& var); // decrypts
constexpr ~VarAnchor(); // re-encrypts
};
} // namespace omath
```
### Helper macros
```cpp
// Generate a compile-time random byte array of length N
#define OMATH_CT_RAND_ARRAY_BYTE(N) /* ... */
// Declare a type alias for EncryptedVariable<TYPE> with KEY_SIZE random bytes
#define OMATH_DEF_CRYPT_VAR(TYPE, KEY_SIZE) /* ... */
```
---
## Usage examples
### Basic encrypt / decrypt
```cpp
#include "omath/containers/encrypted_variable.hpp"
// Define an encrypted float with a 16-byte key
using EncFloat = OMATH_DEF_CRYPT_VAR(float, 16);
EncFloat secret(3.14f); // encrypted immediately
// secret.value() is XOR-scrambled in memory
secret.decrypt();
float v = secret.value(); // v == 3.14f
secret.encrypt(); // scrambled again
```
### RAII guard with `drop_anchor()`
```cpp
EncFloat secret(42.0f);
{
auto anchor = secret.drop_anchor(); // decrypts
float val = secret.value(); // safe to read
// ... use val ...
} // anchor destroyed → re-encrypts automatically
```
---
## Notes & edge cases
* **Force-inline**: When `OMATH_ENABLE_FORCE_INLINE` is defined, encrypt/decrypt operations use compiler-specific force-inline attributes to reduce the call-site footprint visible in disassembly.
* **Not cryptographically secure**: XOR with a static key is an obfuscation technique, not encryption. It raises the bar for casual memory scanning but does not resist a determined attacker who can read the binary.
* **Destructor decrypts**: The destructor calls `decrypt()` so the value is in plaintext at the end of its lifetime (e.g., for logging or cleanup).
* **Thread safety**: No internal synchronization. Protect concurrent access with external locks.
* **`constexpr` support**: All operations are `constexpr`-friendly (C++20).
---
## See also
* [Pattern Scan](../utility/pattern_scan.md) — scan memory for byte patterns.
* [Getting Started](../getting_started.md) — quick start with OMath.
---
*Last updated: Feb 2026*

View File

@@ -0,0 +1,76 @@
/**
* Dynamic Liquid Glass — mouse-tracking specular highlight
*
* Creates a radial-gradient light spot that follows the cursor across
* glass-styled elements, giving them an interactive "liquid glass"
* refraction/reflection feel inspired by Apple's Liquid Glass design.
*/
(function () {
"use strict";
var SELECTORS = [
".md-header",
".md-content",
".md-sidebar__scrollwrap",
".highlight",
".md-search__form",
".md-footer"
];
/** Apply the radial highlight via CSS custom properties. */
function applyHighlight(el, x, y) {
var rect = el.getBoundingClientRect();
var px = x - rect.left;
var py = y - rect.top;
el.style.setProperty("--glass-x", px + "px");
el.style.setProperty("--glass-y", py + "px");
el.classList.add("glass-active");
}
function clearHighlight(el) {
el.classList.remove("glass-active");
}
/** Bind events once the DOM is ready. */
function init() {
var elements = [];
SELECTORS.forEach(function (sel) {
var nodes = document.querySelectorAll(sel);
for (var i = 0; i < nodes.length; i++) {
elements.push(nodes[i]);
}
});
var ticking = false;
document.addEventListener("mousemove", function (e) {
if (ticking) return;
ticking = true;
requestAnimationFrame(function () {
elements.forEach(function (el) {
var rect = el.getBoundingClientRect();
if (
e.clientX >= rect.left &&
e.clientX <= rect.right &&
e.clientY >= rect.top &&
e.clientY <= rect.bottom
) {
applyHighlight(el, e.clientX, e.clientY);
} else {
clearHighlight(el);
}
});
ticking = false;
});
});
document.addEventListener("mouseleave", function () {
elements.forEach(clearHighlight);
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();

View File

@@ -1,11 +0,0 @@
/* Widen the navbar container */
.navbar .container {
max-width: 100%; /* adjust to your target width */
width: 95%;
}
/* Tighter spacing between navbar items */
.navbar-nav > li > a {
padding-left: 8px;
padding-right: 8px;
}

172
docs/styles/fonts.css Normal file
View File

@@ -0,0 +1,172 @@
/* cyrillic-ext */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 300;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19-7DRs5.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 300;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19a7DRs5.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 300;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-1967DRs5.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 300;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19G7DRs5.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 300;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-1927DRs5.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 300;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19y7DRs5.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 300;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19K7DQ.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19-7DRs5.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19a7DRs5.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-1967DRs5.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19G7DRs5.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-1927DRs5.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19y7DRs5.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19K7DQ.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 700;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19-7DRs5.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 700;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19a7DRs5.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 700;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-1967DRs5.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 700;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19G7DRs5.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 700;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-1927DRs5.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 700;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19y7DRs5.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 700;
src: url(https://fonts.gstatic.com/s/robotocondensed/v31/ieVl2ZhZI2eCN5jzbjEETS9weq8-19K7DQ.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
body {
font-family: 'Roboto Condensed', sans-serif;
}

View File

@@ -1,65 +0,0 @@
/* Normal links */
a {
color: orange;
}
/* On hover/focus */
a:hover,
a:focus {
color: #ff9900; /* a slightly different orange, optional */
}
/* Navbar background */
.navbar,
.navbar-default,
.navbar-inverse {
background-color: #a26228 !important; /* your orange */
border-color: #ff6600 !important;
}
/* Navbar brand + links */
.navbar .navbar-brand,
.navbar .navbar-nav > li > a {
color: #ffffff !important;
}
/* Active and hover states */
.navbar .navbar-nav > .active > a,
.navbar .navbar-nav > .active > a:focus,
.navbar .navbar-nav > .active > a:hover,
.navbar .navbar-nav > li > a:hover,
.navbar .navbar-nav > li > a:focus {
color: #ffffff !important;
}
/* === DROPDOWN MENU BACKGROUND === */
.navbar .dropdown-menu {
border-color: #ff6600 !important;
}
/* Caret icon (the little triangle) */
.navbar .dropdown-toggle .caret {
border-top-color: #ffffff !important;
border-bottom-color: #ffffff !important;
}
/* === BOOTSTRAP 3 STYLE ITEMS (mkdocs + bootswatch darkly often use this) === */
.navbar .dropdown-menu > li > a {
color: #ffffff !important;
}
.navbar .dropdown-menu > li > a:hover,
.navbar .dropdown-menu > li > a:focus {
background-color: #e65c00 !important; /* darker orange on hover */
color: #ffffff !important;
}
/* === BOOTSTRAP 4+ STYLE ITEMS (if your theme uses .dropdown-item) === */
.navbar .dropdown-item {
color: #ffffff !important;
}
.navbar .dropdown-item:hover,
.navbar .dropdown-item:focus {
background-color: #e65c00 !important;
color: #ffffff !important;
}

View File

@@ -0,0 +1,271 @@
/* ============================================================
Apple Liquid Glass Design — glassmorphism overrides
for Material for MkDocs (slate / dark mode)
============================================================ */
/* ---------- shared glass mixin values ---------- */
:root {
--glass-bg: rgba(255, 255, 255, 0.04);
--glass-bg-hover: rgba(255, 255, 255, 0.07);
--glass-border: rgba(255, 255, 255, 0.08);
--glass-border-strong: rgba(255, 255, 255, 0.12);
--glass-blur: 16px;
--glass-radius: 14px;
--glass-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
--glass-shadow-sm: 0 2px 12px rgba(0, 0, 0, 0.18);
--glass-accent: rgba(255, 152, 0, 0.12);
}
/* ---------- header / top-bar ---------- */
.md-header {
background: rgba(30, 30, 30, 0.55) !important;
-webkit-backdrop-filter: saturate(180%) blur(var(--glass-blur));
backdrop-filter: saturate(180%) blur(var(--glass-blur));
border-bottom: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow-sm);
}
/* ---------- navigation sidebar ---------- */
.md-sidebar {
background: transparent !important;
}
.md-sidebar__scrollwrap {
background: var(--glass-bg);
-webkit-backdrop-filter: saturate(160%) blur(var(--glass-blur));
backdrop-filter: saturate(160%) blur(var(--glass-blur));
border-right: 1px solid var(--glass-border);
border-radius: 0 var(--glass-radius) var(--glass-radius) 0;
}
/* Remove the gradient mask at the top of the table of contents */
.md-sidebar__scrollwrap {
mask-image: none !important;
-webkit-mask-image: none !important;
}
/* active nav item — subtle glass highlight */
.md-nav__item--active > .md-nav__link {
background: var(--glass-accent) !important;
border-radius: 8px;
}
/* ---------- content area ---------- */
.md-main__inner {
background: transparent;
}
.md-content {
background: var(--glass-bg);
-webkit-backdrop-filter: saturate(140%) blur(12px);
backdrop-filter: saturate(140%) blur(12px);
border: 1px solid var(--glass-border);
border-radius: var(--glass-radius);
box-shadow: var(--glass-shadow);
margin: 12px 0;
padding: 4px;
}
/* ---------- code blocks ---------- */
.highlight {
background: rgba(0, 0, 0, 0.25) !important;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
border: 1px solid var(--glass-border-strong);
border-radius: 12px !important;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04),
var(--glass-shadow-sm);
overflow: hidden;
}
.highlight code {
background: transparent !important;
}
/* inline code */
:not(pre) > code {
background: rgba(255, 255, 255, 0.06) !important;
border: 1px solid var(--glass-border);
border-radius: 6px;
padding: 2px 6px;
}
/* ---------- tables ---------- */
.md-typeset table:not([class]) {
background: var(--glass-bg);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
border: 1px solid var(--glass-border);
border-radius: 12px;
overflow: hidden;
box-shadow: var(--glass-shadow-sm);
}
.md-typeset table:not([class]) th {
background: rgba(255, 152, 0, 0.08);
}
/* ---------- admonitions / call-outs ---------- */
.md-typeset .admonition,
.md-typeset details {
background: var(--glass-bg) !important;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
border: 1px solid var(--glass-border-strong) !important;
border-radius: 12px !important;
box-shadow: var(--glass-shadow-sm);
}
/* ---------- tabs ---------- */
.md-typeset .tabbed-set {
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 12px;
overflow: hidden;
}
/* ---------- search bar ---------- */
.md-search__form {
background: rgba(255, 255, 255, 0.06) !important;
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
border-radius: 10px !important;
}
/* search results / output — liquid glass */
.md-search__output {
background: rgba(30, 30, 30, 0.70) !important;
-webkit-backdrop-filter: saturate(160%) blur(var(--glass-blur));
backdrop-filter: saturate(160%) blur(var(--glass-blur));
border: 1px solid var(--glass-border);
border-radius: 0 0 var(--glass-radius) var(--glass-radius);
box-shadow: var(--glass-shadow);
}
.md-search-result__link {
border-radius: 8px;
transition: background 0.2s ease;
}
.md-search-result__link:hover {
background: var(--glass-bg-hover) !important;
}
/* ---------- footer ---------- */
.md-footer {
background: rgba(30, 30, 30, 0.50) !important;
-webkit-backdrop-filter: saturate(180%) blur(var(--glass-blur));
backdrop-filter: saturate(180%) blur(var(--glass-blur));
border-top: 1px solid var(--glass-border);
}
/* ---------- horizontal rules — subtle glow ---------- */
.md-typeset hr {
border-image: linear-gradient(
to right,
transparent,
rgba(255, 152, 0, 0.25),
transparent
) 1;
}
/* ---------- scrollbar ---------- */
* {
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.10) transparent;
}
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.10);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.18);
}
/* ---------- links — subtle glass-glow on hover ---------- */
.md-typeset a:hover {
text-shadow: 0 0 8px rgba(255, 152, 0, 0.3);
}
/* ---------- images — soft glass frame ---------- */
.md-typeset img {
border-radius: 10px;
}
/* ============================================================
Dynamic Liquid Glass — mouse-tracking specular highlight
============================================================ */
/* Shared: every glass surface gets a hidden radial-light overlay
that becomes visible when JS adds the .glass-active class and
sets --glass-x / --glass-y custom properties. */
.md-header,
.md-content,
.md-sidebar__scrollwrap,
.highlight,
.md-search__form,
.md-footer {
position: relative;
overflow: hidden;
}
.md-header::after,
.md-content::after,
.md-sidebar__scrollwrap::after,
.highlight::after,
.md-search__form::after,
.md-footer::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s ease;
background: radial-gradient(
circle 220px at var(--glass-x, 50%) var(--glass-y, 50%),
rgba(255, 200, 120, 0.10) 0%,
rgba(255, 152, 0, 0.04) 40%,
transparent 70%
);
z-index: 1;
}
.md-header.glass-active::after,
.md-content.glass-active::after,
.md-sidebar__scrollwrap.glass-active::after,
.highlight.glass-active::after,
.md-search__form.glass-active::after,
.md-footer.glass-active::after {
opacity: 1;
}
/* Keep header text / nav icons above the overlay */
.md-header > *,
.md-content > *,
.md-sidebar__scrollwrap > *,
.md-search__form > *,
.md-footer > * {
position: relative;
z-index: 2;
}
/* Highlight code blocks get a slightly brighter spot */
.highlight.glass-active::after {
background: radial-gradient(
circle 180px at var(--glass-x, 50%) var(--glass-y, 50%),
rgba(255, 200, 120, 0.12) 0%,
rgba(255, 152, 0, 0.05) 35%,
transparent 65%
);
}

View File

@@ -0,0 +1,142 @@
# `omath::ElfPatternScanner` — Scan ELF binaries for byte patterns
> Header: `omath/utility/elf_pattern_scan.hpp`
> Namespace: `omath`
> Platform: **Linux / ELF (Executable and Linkable Format) binaries**
> Depends on: `<cstdint>`, `<filesystem>`, `<optional>`, `<string_view>`, `omath/utility/section_scan_result.hpp`
> Companion: works well with `omath::PatternScanner` (same pattern grammar)
`ElfPatternScanner` searches **ELF** binaries for a hex pattern (with wildcards). You can scan:
* a **loaded module** in the current process, or
* an **ELF file on disk** (by section name; defaults to **`.text`**).
---
## Pattern string grammar (same as `PatternScanner`)
* **Hex byte**: two hex digits → one byte (`90`, `4F`, `00`, `ff`).
* **Wildcard byte**: `?` or `??` matches **any byte**.
* **Whitespace**: ignored (use to group tokens).
✔️ `"48 8B ?? ?? 89"`, `"55 48 89 E5"`, `"??"`
❌ odd digit counts, non-hex characters (besides `?` and whitespace)
---
## API
```cpp
namespace omath {
class ElfPatternScanner final {
public:
// Scan a module already loaded in *this* process.
// module_base_address: base address of the loaded ELF (e.g., from dlopen / /proc/self/maps)
// Returns absolute address (process VA) of the first match, or nullopt.
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(
const void* module_base_address,
const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
// Scan an ELF file on disk, by section name (default ".text").
// Returns section bases (virtual + raw) and match offset within the section, or nullopt.
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");
};
} // namespace omath
```
---
## Return values
* **Loaded module**: `std::optional<std::uintptr_t>`
* `value()` = **process virtual address** of the first match.
* `nullopt` = no match or parse/ELF error.
* **File scan**: `std::optional<SectionScanResult>`
* `virtual_base_addr` = virtual address base of the scanned section.
* `raw_base_addr` = file offset of section start.
* `target_offset` = offset from section base to the first matched byte.
* To get addresses:
* **Virtual address** of hit = `virtual_base_addr + target_offset`
* **Raw file offset** of hit = `raw_base_addr + target_offset`
---
## Usage examples
### Scan a loaded module (current process)
```cpp
#include <dlfcn.h>
#include "omath/utility/elf_pattern_scan.hpp"
using omath::ElfPatternScanner;
void* handle = dlopen("libexample.so", RTLD_LAZY);
if (handle) {
auto addr = ElfPatternScanner::scan_for_pattern_in_loaded_module(
handle, "55 48 89 E5 ?? ?? 48"
);
if (addr) {
std::uintptr_t hit_va = *addr;
// ...
}
dlclose(handle);
}
```
### Scan an ELF file on disk
```cpp
#include "omath/utility/elf_pattern_scan.hpp"
using omath::ElfPatternScanner;
auto res = ElfPatternScanner::scan_for_pattern_in_file(
"/usr/lib/libexample.so", "55 48 89 E5"
);
if (res) {
auto va_hit = res->virtual_base_addr + res->target_offset;
auto raw_hit = res->raw_base_addr + res->target_offset;
}
```
### Scan another section (e.g., ".rodata")
```cpp
auto res = ElfPatternScanner::scan_for_pattern_in_file(
"myapp", "48 8D 0D ?? ?? ?? ??", ".rodata"
);
```
---
## Notes & edge cases
* **ELF only**: these functions assume a valid ELF layout. Non-ELF files or corrupted headers yield `nullopt`.
* **Section name**: defaults to **`.text`**; pass a different name to target other sections.
* **Performance**: Pattern matching is **O(N × M)** (sliding window with wildcards). For large binaries, prefer scanning only necessary sections.
* **Architecture**: works for 32-bit and 64-bit ELF binaries.
---
## See also
* [`omath::PatternScanner`](pattern_scan.md) — raw buffer/iterator scanning with the same pattern grammar.
* [`omath::PePatternScanner`](pe_pattern_scan.md) — PE (Windows) binary scanner.
* [`omath::MachOPatternScanner`](macho_pattern_scan.md) — Mach-O (macOS) binary scanner.
* [`omath::SectionScanResult`](section_scan_result.md) — return type for file-based scans.
---
*Last updated: Feb 2026*

View File

@@ -0,0 +1,142 @@
# `omath::MachOPatternScanner` — Scan Mach-O binaries for byte patterns
> Header: `omath/utility/macho_pattern_scan.hpp`
> Namespace: `omath`
> Platform: **macOS / Mach-O binaries**
> Depends on: `<cstdint>`, `<filesystem>`, `<optional>`, `<string_view>`, `omath/utility/section_scan_result.hpp`
> Companion: works well with `omath::PatternScanner` (same pattern grammar)
`MachOPatternScanner` searches **Mach-O** binaries for a hex pattern (with wildcards). You can scan:
* a **loaded module** in the current process, or
* a **Mach-O file on disk** (by section name; defaults to **`__text`**).
---
## Pattern string grammar (same as `PatternScanner`)
* **Hex byte**: two hex digits → one byte (`90`, `4F`, `00`, `ff`).
* **Wildcard byte**: `?` or `??` matches **any byte**.
* **Whitespace**: ignored (use to group tokens).
✔️ `"48 8B ?? ?? 89"`, `"55 48 89 E5"`, `"??"`
❌ odd digit counts, non-hex characters (besides `?` and whitespace)
---
## API
```cpp
namespace omath {
class MachOPatternScanner final {
public:
// Scan a module already loaded in *this* process.
// module_base_address: base address of the loaded Mach-O image
// Returns absolute address (process VA) of the first match, or nullopt.
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(
const void* module_base_address,
const std::string_view& pattern,
const std::string_view& target_section_name = "__text");
// Scan a Mach-O file on disk, by section name (default "__text").
// Returns section bases (virtual + raw) and match offset within the section, or nullopt.
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");
};
} // namespace omath
```
---
## Return values
* **Loaded module**: `std::optional<std::uintptr_t>`
* `value()` = **process virtual address** of the first match.
* `nullopt` = no match or parse/Mach-O error.
* **File scan**: `std::optional<SectionScanResult>`
* `virtual_base_addr` = virtual address base of the scanned section.
* `raw_base_addr` = file offset of section start.
* `target_offset` = offset from section base to the first matched byte.
* To get addresses:
* **Virtual address** of hit = `virtual_base_addr + target_offset`
* **Raw file offset** of hit = `raw_base_addr + target_offset`
---
## Usage examples
### Scan a loaded module (current process)
```cpp
#include <dlfcn.h>
#include "omath/utility/macho_pattern_scan.hpp"
using omath::MachOPatternScanner;
void* handle = dlopen("libexample.dylib", RTLD_LAZY);
if (handle) {
auto addr = MachOPatternScanner::scan_for_pattern_in_loaded_module(
handle, "55 48 89 E5 ?? ?? 48"
);
if (addr) {
std::uintptr_t hit_va = *addr;
// ...
}
dlclose(handle);
}
```
### Scan a Mach-O file on disk
```cpp
#include "omath/utility/macho_pattern_scan.hpp"
using omath::MachOPatternScanner;
auto res = MachOPatternScanner::scan_for_pattern_in_file(
"/usr/local/lib/libexample.dylib", "55 48 89 E5"
);
if (res) {
auto va_hit = res->virtual_base_addr + res->target_offset;
auto raw_hit = res->raw_base_addr + res->target_offset;
}
```
### Scan another section (e.g., "__cstring")
```cpp
auto res = MachOPatternScanner::scan_for_pattern_in_file(
"myapp", "48 8D 0D ?? ?? ?? ??", "__cstring"
);
```
---
## Notes & edge cases
* **Mach-O only**: these functions assume a valid Mach-O layout. Non-Mach-O files or corrupted headers yield `nullopt`.
* **Section name**: defaults to **`__text`** (note the double underscore, per Mach-O convention); pass a different name to target other sections.
* **Performance**: Pattern matching is **O(N × M)** (sliding window with wildcards). For large binaries, prefer scanning only necessary sections.
* **Architecture**: works for 64-bit Mach-O binaries (x86_64 and arm64).
---
## See also
* [`omath::PatternScanner`](pattern_scan.md) — raw buffer/iterator scanning with the same pattern grammar.
* [`omath::PePatternScanner`](pe_pattern_scan.md) — PE (Windows) binary scanner.
* [`omath::ElfPatternScanner`](elf_pattern_scan.md) — ELF (Linux) binary scanner.
* [`omath::SectionScanResult`](section_scan_result.md) — return type for file-based scans.
---
*Last updated: Feb 2026*

View File

@@ -0,0 +1,58 @@
# `omath::SectionScanResult` — File-based pattern scan result
> Header: `omath/utility/section_scan_result.hpp`
> Namespace: `omath`
> Depends on: `<cstddef>`, `<cstdint>`
`SectionScanResult` is the return type for file-based pattern scans across all binary formats (PE, ELF, Mach-O). It carries the section's virtual and raw base addresses together with the offset to the matched pattern.
---
## API
```cpp
namespace omath {
struct SectionScanResult final {
std::uintptr_t virtual_base_addr; // virtual address base of the scanned section
std::uintptr_t raw_base_addr; // file offset of the section start
std::ptrdiff_t target_offset; // offset from section base to the first matched byte
};
} // namespace omath
```
---
## Computing addresses from a result
```cpp
omath::SectionScanResult res = /* ... */;
// Virtual address of the match (as if the binary were loaded at its preferred base)
auto va_hit = res.virtual_base_addr + res.target_offset;
// Raw file offset of the match
auto raw_hit = res.raw_base_addr + res.target_offset;
```
---
## Notes
* `virtual_base_addr` is computed from the section header (RVA for PE, `sh_addr` for ELF, `addr` for Mach-O).
* `raw_base_addr` is the file offset where the section data begins on disk.
* `target_offset` is always relative to the section base — add it to either address to locate the match.
---
## See also
* [`omath::PePatternScanner`](pe_pattern_scan.md) — PE (Windows) binary scanner.
* [`omath::ElfPatternScanner`](elf_pattern_scan.md) — ELF (Linux) binary scanner.
* [`omath::MachOPatternScanner`](macho_pattern_scan.md) — Mach-O (macOS) binary scanner.
* [`omath::PatternScanner`](pattern_scan.md) — raw buffer/iterator scanning.
---
*Last updated: Feb 2026*

View File

@@ -1,34 +1,35 @@
project(examples) project(examples)
add_executable(example_projection_matrix_builder example_proj_mat_builder.cpp) add_executable(example_projection_matrix_builder example_proj_mat_builder.cpp)
set_target_properties(example_projection_matrix_builder PROPERTIES set_target_properties(
CXX_STANDARD 26 example_projection_matrix_builder
PROPERTIES CXX_STANDARD 23
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
)
target_link_libraries(example_projection_matrix_builder PRIVATE omath::omath) target_link_libraries(example_projection_matrix_builder PRIVATE omath::omath)
add_executable(example_signature_scan example_signature_scan.cpp) add_executable(example_signature_scan example_signature_scan.cpp)
set_target_properties(example_signature_scan PROPERTIES set_target_properties(
CXX_STANDARD 26 example_signature_scan
PROPERTIES CXX_STANDARD 23
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
)
target_link_libraries(example_signature_scan PRIVATE omath::omath) target_link_libraries(example_signature_scan PRIVATE omath::omath)
add_executable(example_glfw3 example_glfw3.cpp) add_executable(example_glfw3 example_glfw3.cpp)
set_target_properties(example_glfw3 PROPERTIES CXX_STANDARD 26 set_target_properties(
example_glfw3
PROPERTIES CXX_STANDARD 23
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
)
find_package(OpenGL)
find_package(GLEW REQUIRED) find_package(GLEW REQUIRED)
find_package(glfw3 CONFIG REQUIRED) find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(example_glfw3 PRIVATE omath::omath GLEW::GLEW glfw) target_link_libraries(example_glfw3 PRIVATE omath::omath GLEW::GLEW glfw OpenGL::OpenGL)
if(OMATH_ENABLE_VALGRIND) if(OMATH_ENABLE_VALGRIND)
omath_setup_valgrind(example_projection_matrix_builder) omath_setup_valgrind(example_projection_matrix_builder)

View File

@@ -120,9 +120,14 @@ int main()
return -1; return -1;
} }
std::cout << "GLFW Version: " << glfwGetVersionString() << "\n";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Force GLX context creation API to ensure compatibility with GLEW
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
#ifdef __APPLE__ #ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif #endif
@@ -141,16 +146,31 @@ int main()
glfwMakeContextCurrent(window); glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// Check if context is valid using standard GL
const GLubyte* renderer = glGetString(GL_RENDERER);
const GLubyte* version = glGetString(GL_VERSION);
if (renderer && version) {
std::cout << "Renderer: " << renderer << "\n";
std::cout << "OpenGL version supported: " << version << "\n";
} else {
std::cerr << "Failed to get GL_RENDERER or GL_VERSION. Context might be invalid.\n";
}
// ---------- GLEW init ---------- // ---------- GLEW init ----------
glewExperimental = GL_TRUE; glewExperimental = GL_TRUE;
GLenum glewErr = glewInit(); GLenum glewErr = glewInit();
if (glewErr != GLEW_OK) if (glewErr != GLEW_OK)
{ {
// Ignore NO_GLX_DISPLAY if we have a valid context
if (glewErr == GLEW_ERROR_NO_GLX_DISPLAY && renderer) {
std::cerr << "GLEW warning: " << glewGetErrorString(glewErr) << " (Ignored because context seems valid)\n";
} else {
std::cerr << "Failed to initialize GLEW: " << reinterpret_cast<const char*>(glewGetErrorString(glewErr)) std::cerr << "Failed to initialize GLEW: " << reinterpret_cast<const char*>(glewGetErrorString(glewErr))
<< "\n"; << "\n";
glfwTerminate(); glfwTerminate();
return -1; return -1;
} }
}
// ---------- GL state ---------- // ---------- GL state ----------
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);

View File

@@ -8,43 +8,96 @@
namespace omath::collision namespace omath::collision
{ {
class Ray template<class T = Vector3<float>>
class Ray final
{ {
public: public:
Vector3<float> start; using VectorType = T;
Vector3<float> end; VectorType start;
VectorType end;
bool infinite_length = false; bool infinite_length = false;
[[nodiscard]] [[nodiscard]]
Vector3<float> direction_vector() const noexcept; constexpr VectorType direction_vector() const noexcept
{
return end - start;
}
[[nodiscard]] [[nodiscard]]
Vector3<float> direction_vector_normalized() const noexcept; constexpr VectorType direction_vector_normalized() const noexcept
};
class LineTracer
{ {
return direction_vector().normalized();
}
};
template<class RayType = Ray<>>
class LineTracer final
{
using TriangleType = Triangle<typename RayType::VectorType>;
public: public:
LineTracer() = delete; LineTracer() = delete;
[[nodiscard]] [[nodiscard]]
static bool can_trace_line(const Ray& ray, const Triangle<Vector3<float>>& triangle) noexcept; constexpr static bool can_trace_line(const RayType& ray, const TriangleType& triangle) noexcept
{
return get_ray_hit_point(ray, triangle) == ray.end;
}
// Realization of MöllerTrumbore intersection algorithm // Realization of MöllerTrumbore intersection algorithm
// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm // https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
[[nodiscard]] [[nodiscard]]
static Vector3<float> get_ray_hit_point(const Ray& ray, const Triangle<Vector3<float>>& triangle) noexcept; constexpr static auto get_ray_hit_point(const RayType& ray, const TriangleType& triangle) noexcept
{
constexpr float k_epsilon = std::numeric_limits<float>::epsilon();
const auto side_a = triangle.side_a_vector();
const auto side_b = triangle.side_b_vector();
const auto ray_dir = ray.direction_vector();
const auto p = ray_dir.cross(side_b);
const auto det = side_a.dot(p);
if (std::abs(det) < k_epsilon)
return ray.end;
const auto inv_det = 1 / det;
const auto t = ray.start - triangle.m_vertex2;
const auto u = t.dot(p) * inv_det;
if ((u < 0 && std::abs(u) > k_epsilon) || (u > 1 && std::abs(u - 1) > k_epsilon))
return ray.end;
const auto q = t.cross(side_a);
// ReSharper disable once CppTooWideScopeInitStatement
const auto v = ray_dir.dot(q) * inv_det;
if ((v < 0 && std::abs(v) > k_epsilon) || (u + v > 1 && std::abs(u + v - 1) > k_epsilon))
return ray.end;
const auto t_hit = side_b.dot(q) * inv_det;
if (ray.infinite_length && t_hit <= k_epsilon)
return ray.end;
if (t_hit <= k_epsilon || t_hit > 1 - k_epsilon)
return ray.end;
return ray.start + ray_dir * t_hit;
}
template<class MeshType> template<class MeshType>
[[nodiscard]] [[nodiscard]]
static Vector3<float> get_ray_hit_point(const Ray& ray, const MeshType& mesh) noexcept constexpr static auto get_ray_hit_point(const RayType& ray, const MeshType& mesh) noexcept
{ {
Vector3<float> mesh_hit = ray.end; auto mesh_hit = ray.end;
auto begin = mesh.m_element_buffer_object.cbegin(); const auto begin = mesh.m_element_buffer_object.cbegin();
auto end = mesh.m_element_buffer_object.cend(); const auto end = mesh.m_element_buffer_object.cend();
for (auto current = begin; current < end; current = std::next(current)) for (auto current = begin; current < end; current = std::next(current))
{ {
auto face = mesh.make_face_in_world_space(current); const auto face = mesh.make_face_in_world_space(current);
auto ray_stop_point = get_ray_hit_point(ray, face); auto ray_stop_point = get_ray_hit_point(ray, face);
if (ray_stop_point.distance_to(ray.start) < mesh_hit.distance_to(ray.start)) if (ray_stop_point.distance_to(ray.start) < mesh_hit.distance_to(ray.start))

View File

@@ -130,7 +130,7 @@ namespace omath::collision
template<class V> template<class V>
[[nodiscard]] [[nodiscard]]
static constexpr bool near_zero(const V& v, const float eps = 1e-7f) static constexpr bool near_zero(const V& v, const float eps = 1e-7f) noexcept
{ {
return v.dot(v) <= eps * eps; return v.dot(v) <= eps * eps;
} }
@@ -146,7 +146,7 @@ namespace omath::collision
} }
[[nodiscard]] [[nodiscard]]
constexpr bool handle_line(VectorType& direction) constexpr bool handle_line(VectorType& direction) noexcept
{ {
const auto& a = m_points[0]; const auto& a = m_points[0];
const auto& b = m_points[1]; const auto& b = m_points[1];
@@ -158,21 +158,11 @@ namespace omath::collision
{ {
// ReSharper disable once CppTooWideScopeInitStatement // ReSharper disable once CppTooWideScopeInitStatement
auto n = ab.cross(ao); // Needed to valid handle collision if colliders placed at same origin pos auto n = ab.cross(ao); // Needed to valid handle collision if colliders placed at same origin pos
if (near_zero(n)) direction = near_zero(n) ? any_perp(ab) : n.cross(ab);
{ return false;
// collinear: origin lies on ray AB (often on segment), pick any perp to escape
direction = any_perp(ab);
} }
else
{
direction = n.cross(ab);
}
}
else
{
*this = {a}; *this = {a};
direction = ao; direction = ao;
}
return false; return false;
} }

View File

@@ -0,0 +1,25 @@
//
// Created by Copilot on 04.02.2026.
//
#pragma once
#include <cstdint>
#include <filesystem>
#include <optional>
#include <string_view>
#include "section_scan_result.hpp"
namespace omath
{
class MachOPatternScanner final
{
public:
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = "__text");
[[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");
};
} // namespace omath

View File

@@ -1,7 +1,19 @@
site_name: OMATH Docs site_name: OMATH Docs
theme: theme:
name: darkly name: material
palette:
scheme: slate
primary: deep orange
accent: orange
font:
text: Roboto Condensed
markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.superfences
extra_css: extra_css:
- styles/fonts.css
- styles/center.css - styles/center.css
- styles/custom-header.css - styles/liquid-glass.css
- styles/links.css extra_javascript:
- javascripts/liquid-glass.js

69
pixi.toml Normal file
View File

@@ -0,0 +1,69 @@
[workspace]
name = "omath"
version = "5.7.0"
description = "Cross-platform modern general purpose math library written in C++23 that suitable for cheat/game development."
authors = [
"orange-cpp <orange_github@proton.me>"
]
license = "Zlib"
license-file = "LICENSE"
readme = "README.md"
documentation = "http://libomath.org"
repository = "https://github.com/orange-cpp/omath"
channels = ["conda-forge"]
platforms = ["win-64", "linux-64", "linux-aarch64", "osx-64", "osx-arm64"]
[tasks]
format = { cwd = "pixi", cmd = "cmake -P fmt.cmake" }
configure = { cmd = "cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DOMATH_USE_AVX2=OFF -DOMATH_IMGUI_INTEGRATION=ON -DOMATH_BUILD_TESTS=ON -DOMATH_BUILD_BENCHMARK=ON -DOMATH_BUILD_EXAMPLES=ON .", depends-on = ["format"] }
build = { cmd = "cmake --build build --config Debug -j", depends-on = ["configure"] }
examples = { cwd = "pixi", cmd = "cmake -DCMAKE_BUILD_TYPE=Debug -P run.examples.cmake", depends-on = ["build"] }
tests = { cwd = "pixi", cmd = "cmake -DCMAKE_BUILD_TYPE=Debug -P run.unit.tests.cmake", depends-on = ["build"] }
benchmark = { cwd = "pixi", cmd = "cmake -DCMAKE_BUILD_TYPE=Debug -P run.benchmark.cmake", depends-on = ["build"] }
[dependencies]
benchmark = ">=1.9.5,<2"
ccache = ">=4.12.2,<5"
cmake = ">=4.2.3,<5"
cmake-format = ">=0.6.13,<0.7"
cxx-compiler = ">=1.11.0,<2"
imgui = ">=1.92.3,<2"
gtest = ">=1.17.0,<2"
glew = ">=2.3.0,<3"
glfw = ">=3.4,<4"
ninja = ">=1.13.2,<2"
[target.linux-64.dependencies]
mesa-libgl-devel-cos7-x86_64 = ">=18.3.4,<19"
xorg-x11-server-xvfb-cos7-x86_64 = ">=1.20.4,<2"
[target.linux-64.activation.env]
__GLX_VENDOR_LIBRARY_NAME = "mesa"
EGL_PLATFORM = "x11"
GLFW_PLATFORM = "x11"
[target.linux-64.tasks]
examples = { cwd = "pixi", cmd = "xvfb-run -a -s '-screen 0 1024x768x24 +extension GLX +render' cmake -DCMAKE_BUILD_TYPE=Debug -P run.examples.cmake", depends-on = ["build"] }
[target.win-64.dependencies]
mesa-libgl-devel-cos7-x86_64 = ">=18.3.4,<19"
[target.osx-64.dependencies]
mesa-libgl-devel-cos7-x86_64 = ">=18.3.4,<19"
[target.osx-arm64.dependencies]
mesa-libgl-devel-cos7-aarch64 = ">=18.3.4,<19"
[target.linux-aarch64.dependencies]
mesa-libgl-devel-cos7-aarch64 = ">=18.3.4,<19"
xorg-x11-server-xvfb-cos7-aarch64 = ">=1.20.4,<2"
[target.linux-aarch64.activation.env]
__GLX_VENDOR_LIBRARY_NAME = "mesa"
EGL_PLATFORM = "x11"
GLFW_PLATFORM = "x11"
[target.linux-aarch64.tasks]
examples = { cwd = "pixi", cmd = "xvfb-run -a -s '-screen 0 1024x768x24 +extension GLX +render' cmake -DCMAKE_BUILD_TYPE=Debug -P run.examples.cmake", depends-on = ["build"] }

36
pixi/fmt.cmake Normal file
View File

@@ -0,0 +1,36 @@
# cmake/Format.cmake
# Find cmake-format executable
find_program(CMAKE_FORMAT_EXECUTABLE NAMES cmake-format)
if(NOT CMAKE_FORMAT_EXECUTABLE)
message(FATAL_ERROR "cmake-format not found. Please install it first.")
endif()
# Get the project root directory (assuming this script is in cmake/
# subdirectory)
get_filename_component(PROJECT_ROOT "../${CMAKE_CURRENT_LIST_DIR}" ABSOLUTE)
# Iterate over all files in the project root
file(GLOB_RECURSE ALL_FILES "${PROJECT_ROOT}/*")
foreach(FILE ${ALL_FILES})
# Basic ignores for common directories to avoid formatting external/build
# files Note: We check for substrings in the full path
if("${FILE}" MATCHES "/\\.git/"
OR "${FILE}" MATCHES "/build/"
OR "${FILE}" MATCHES "/cmake-build/"
OR "${FILE}" MATCHES "/\\.pixi/"
OR "${FILE}" MATCHES "/vcpkg_installed/")
continue()
endif()
get_filename_component(FILENAME "${FILE}" NAME)
# Check if file ends with .cmake or matches exactly to CMakeLists.txt
if("${FILENAME}" STREQUAL "CMakeLists.txt" OR "${FILENAME}" MATCHES "\\.cmake$")
message(STATUS "Formatting ${FILE}")
execute_process(COMMAND ${CMAKE_FORMAT_EXECUTABLE} --config-files
"${PROJECT_ROOT}/.cmake-format" -i "${FILE}")
endif()
endforeach()

63
pixi/run.benchmark.cmake Normal file
View File

@@ -0,0 +1,63 @@
# cmake/run.examples.cmake
# Get the project root directory (assuming this script is in cmake/ subdirectory)
get_filename_component(PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
# Default to Debug if CMAKE_BUILD_TYPE is not specified
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
else()
message(STATUS "CMAKE_BUILD_TYPE is set to: ${CMAKE_BUILD_TYPE}")
endif()
# Define the directory where executables are located
# Based on CMakeLists.txt: "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
set(EXAMPLES_BIN_DIR "${PROJECT_ROOT}/out/${CMAKE_BUILD_TYPE}")
if(NOT EXISTS "${EXAMPLES_BIN_DIR}")
message(FATAL_ERROR "Examples binary directory not found: ${EXAMPLES_BIN_DIR}. Please build the project first.")
endif()
message(STATUS "Looking for benchmark executables in: ${EXAMPLES_BIN_DIR}")
# Find all files starting with "omath_benchmark"
file(GLOB EXAMPLE_FILES "${EXAMPLES_BIN_DIR}/omath_benchmark*")
foreach(EXAMPLE_PATH ${EXAMPLE_FILES})
# Skip directories
if(IS_DIRECTORY "${EXAMPLE_PATH}")
continue()
endif()
get_filename_component(FILENAME "${EXAMPLE_PATH}" NAME)
get_filename_component(EXTENSION "${EXAMPLE_PATH}" EXT)
# Filter out potential debug symbols or non-executable artifacts
if(EXTENSION STREQUAL ".pdb" OR EXTENSION STREQUAL ".ilk" OR EXTENSION STREQUAL ".obj")
continue()
endif()
# On Windows, we expect .exe
if(MSVC AND NOT EXTENSION STREQUAL ".exe")
continue()
endif()
# On Linux/macOS, check permissions or just try to run it.
message(STATUS "-------------------------------------------------")
message(STATUS "Running benchmark: ${FILENAME}")
message(STATUS "-------------------------------------------------")
execute_process(
COMMAND "${EXAMPLE_PATH}"
WORKING_DIRECTORY "${PROJECT_ROOT}"
RESULT_VARIABLE EXIT_CODE
)
if(NOT EXIT_CODE EQUAL 0)
message(WARNING "Benchmark ${FILENAME} exited with error code: ${EXIT_CODE}")
else()
message(STATUS "Benchmark ${FILENAME} completed successfully.")
endif()
endforeach()

63
pixi/run.examples.cmake Normal file
View File

@@ -0,0 +1,63 @@
# cmake/run.examples.cmake
# Get the project root directory (assuming this script is in cmake/ subdirectory)
get_filename_component(PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
# Default to Debug if CMAKE_BUILD_TYPE is not specified
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
else()
message(STATUS "CMAKE_BUILD_TYPE is set to: ${CMAKE_BUILD_TYPE}")
endif()
# Define the directory where executables are located
# Based on CMakeLists.txt: "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
set(EXAMPLES_BIN_DIR "${PROJECT_ROOT}/out/${CMAKE_BUILD_TYPE}")
if(NOT EXISTS "${EXAMPLES_BIN_DIR}")
message(FATAL_ERROR "Examples binary directory not found: ${EXAMPLES_BIN_DIR}. Please build the project first.")
endif()
message(STATUS "Looking for example executables in: ${EXAMPLES_BIN_DIR}")
# Find all files starting with "example_"
file(GLOB EXAMPLE_FILES "${EXAMPLES_BIN_DIR}/example_*")
foreach(EXAMPLE_PATH ${EXAMPLE_FILES})
# Skip directories
if(IS_DIRECTORY "${EXAMPLE_PATH}")
continue()
endif()
get_filename_component(FILENAME "${EXAMPLE_PATH}" NAME)
get_filename_component(EXTENSION "${EXAMPLE_PATH}" EXT)
# Filter out potential debug symbols or non-executable artifacts
if(EXTENSION STREQUAL ".pdb" OR EXTENSION STREQUAL ".ilk" OR EXTENSION STREQUAL ".obj")
continue()
endif()
# On Windows, we expect .exe
if(MSVC AND NOT EXTENSION STREQUAL ".exe")
continue()
endif()
# On Linux/macOS, check permissions or just try to run it.
message(STATUS "-------------------------------------------------")
message(STATUS "Running example: ${FILENAME}")
message(STATUS "-------------------------------------------------")
execute_process(
COMMAND "${EXAMPLE_PATH}"
WORKING_DIRECTORY "${PROJECT_ROOT}"
RESULT_VARIABLE EXIT_CODE
)
if(NOT EXIT_CODE EQUAL 0)
message(WARNING "Example ${FILENAME} exited with error code: ${EXIT_CODE}")
else()
message(STATUS "Example ${FILENAME} completed successfully.")
endif()
endforeach()

63
pixi/run.unit.tests.cmake Normal file
View File

@@ -0,0 +1,63 @@
# cmake/run.examples.cmake
# Get the project root directory (assuming this script is in cmake/ subdirectory)
get_filename_component(PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
# Default to Debug if CMAKE_BUILD_TYPE is not specified
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
else()
message(STATUS "CMAKE_BUILD_TYPE is set to: ${CMAKE_BUILD_TYPE}")
endif()
# Define the directory where executables are located
# Based on CMakeLists.txt: "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
set(EXAMPLES_BIN_DIR "${PROJECT_ROOT}/out/${CMAKE_BUILD_TYPE}")
if(NOT EXISTS "${EXAMPLES_BIN_DIR}")
message(FATAL_ERROR "Examples binary directory not found: ${EXAMPLES_BIN_DIR}. Please build the project first.")
endif()
message(STATUS "Looking for unit test executables in: ${EXAMPLES_BIN_DIR}")
# Find all files starting with "unit_tests"
file(GLOB EXAMPLE_FILES "${EXAMPLES_BIN_DIR}/unit_tests*")
foreach(EXAMPLE_PATH ${EXAMPLE_FILES})
# Skip directories
if(IS_DIRECTORY "${EXAMPLE_PATH}")
continue()
endif()
get_filename_component(FILENAME "${EXAMPLE_PATH}" NAME)
get_filename_component(EXTENSION "${EXAMPLE_PATH}" EXT)
# Filter out potential debug symbols or non-executable artifacts
if(EXTENSION STREQUAL ".pdb" OR EXTENSION STREQUAL ".ilk" OR EXTENSION STREQUAL ".obj")
continue()
endif()
# On Windows, we expect .exe
if(MSVC AND NOT EXTENSION STREQUAL ".exe")
continue()
endif()
# On Linux/macOS, check permissions or just try to run it.
message(STATUS "-------------------------------------------------")
message(STATUS "Running unit_tests: ${FILENAME}")
message(STATUS "-------------------------------------------------")
execute_process(
COMMAND "${EXAMPLE_PATH}"
WORKING_DIRECTORY "${PROJECT_ROOT}"
RESULT_VARIABLE EXIT_CODE
)
if(NOT EXIT_CODE EQUAL 0)
message(WARNING "Example ${FILENAME} exited with error code: ${EXIT_CODE}")
else()
message(STATUS "Example ${FILENAME} completed successfully.")
endif()
endforeach()

View File

@@ -1,61 +0,0 @@
//
// Created by Orange on 11/13/2024.
//
#include "omath/collision/line_tracer.hpp"
namespace omath::collision
{
bool LineTracer::can_trace_line(const Ray& ray, const Triangle<Vector3<float>>& triangle) noexcept
{
return get_ray_hit_point(ray, triangle) == ray.end;
}
Vector3<float> Ray::direction_vector() const noexcept
{
return end - start;
}
Vector3<float> Ray::direction_vector_normalized() const noexcept
{
return direction_vector().normalized();
}
Vector3<float> LineTracer::get_ray_hit_point(const Ray& ray, const Triangle<Vector3<float>>& triangle) noexcept
{
constexpr float k_epsilon = std::numeric_limits<float>::epsilon();
const auto side_a = triangle.side_a_vector();
const auto side_b = triangle.side_b_vector();
const auto ray_dir = ray.direction_vector();
const auto p = ray_dir.cross(side_b);
const auto det = side_a.dot(p);
if (std::abs(det) < k_epsilon)
return ray.end;
const auto inv_det = 1.0f / det;
const auto t = ray.start - triangle.m_vertex2;
const auto u = t.dot(p) * inv_det;
if ((u < 0 && std::abs(u) > k_epsilon) || (u > 1 && std::abs(u - 1) > k_epsilon))
return ray.end;
const auto q = t.cross(side_a);
// ReSharper disable once CppTooWideScopeInitStatement
const auto v = ray_dir.dot(q) * inv_det;
if ((v < 0 && std::abs(v) > k_epsilon) || (u + v > 1 && std::abs(u + v - 1) > k_epsilon))
return ray.end;
const auto t_hit = side_b.dot(q) * inv_det;
if (ray.infinite_length && t_hit <= k_epsilon)
return ray.end;
if (t_hit <= k_epsilon || t_hit > 1.0f - k_epsilon)
return ray.end;
return ray.start + ray_dir * t_hit;
}
} // namespace omath::collision

View File

@@ -0,0 +1,349 @@
//
// Created by Copilot on 04.02.2026.
//
#include "omath/utility/macho_pattern_scan.hpp"
#include "omath/utility/pattern_scan.hpp"
#include <cstring>
#include <fstream>
#include <variant>
#include <vector>
#pragma pack(push, 1)
namespace
{
// Mach-O magic numbers
constexpr std::uint32_t mh_magic_32 = 0xFEEDFACE;
constexpr std::uint32_t mh_magic_64 = 0xFEEDFACF;
constexpr std::uint32_t mh_cigam_32 = 0xCEFAEDFE; // Byte-swapped 32-bit
constexpr std::uint32_t mh_cigam_64 = 0xCFFAEDFE; // Byte-swapped 64-bit
// Load command types
constexpr std::uint32_t lc_segment = 0x1;
constexpr std::uint32_t lc_segment_64 = 0x19;
// ReSharper disable CppDeclaratorNeverUsed
// Mach-O header for 32-bit
struct MachHeader32 final
{
std::uint32_t magic;
std::uint32_t cputype;
std::uint32_t cpusubtype;
std::uint32_t filetype;
std::uint32_t ncmds;
std::uint32_t sizeofcmds;
std::uint32_t flags;
};
// Mach-O header for 64-bit
struct MachHeader64 final
{
std::uint32_t magic;
std::uint32_t cputype;
std::uint32_t cpusubtype;
std::uint32_t filetype;
std::uint32_t ncmds;
std::uint32_t sizeofcmds;
std::uint32_t flags;
std::uint32_t reserved;
};
// Load command header
struct LoadCommand final
{
std::uint32_t cmd;
std::uint32_t cmdsize;
};
// Segment command for 32-bit
struct SegmentCommand32 final
{
std::uint32_t cmd;
std::uint32_t cmdsize;
char segname[16];
std::uint32_t vmaddr;
std::uint32_t vmsize;
std::uint32_t fileoff;
std::uint32_t filesize;
std::uint32_t maxprot;
std::uint32_t initprot;
std::uint32_t nsects;
std::uint32_t flags;
};
// Segment command for 64-bit
struct SegmentCommand64 final
{
std::uint32_t cmd;
std::uint32_t cmdsize;
char segname[16];
std::uint64_t vmaddr;
std::uint64_t vmsize;
std::uint64_t fileoff;
std::uint64_t filesize;
std::uint32_t maxprot;
std::uint32_t initprot;
std::uint32_t nsects;
std::uint32_t flags;
};
// Section for 32-bit
struct Section32 final
{
char sectname[16];
char segname[16];
std::uint32_t addr;
std::uint32_t size;
std::uint32_t offset;
std::uint32_t align;
std::uint32_t reloff;
std::uint32_t nreloc;
std::uint32_t flags;
std::uint32_t reserved1;
std::uint32_t reserved2;
};
// Section for 64-bit
struct Section64 final
{
char sectname[16];
char segname[16];
std::uint64_t addr;
std::uint64_t size;
std::uint32_t offset;
std::uint32_t align;
std::uint32_t reloff;
std::uint32_t nreloc;
std::uint32_t flags;
std::uint32_t reserved1;
std::uint32_t reserved2;
std::uint32_t reserved3;
};
// ReSharper enable CppDeclaratorNeverUsed
#pragma pack(pop)
enum class MachOArch : std::int8_t
{
x32,
x64,
};
struct ExtractedSection final
{
std::uintptr_t virtual_base_addr{};
std::uintptr_t raw_base_addr{};
std::vector<std::byte> data;
};
[[nodiscard]]
std::optional<MachOArch> get_macho_arch(std::fstream& file)
{
std::uint32_t magic{};
const std::streampos backup_pos = file.tellg();
file.seekg(0, std::ios_base::beg);
file.read(reinterpret_cast<char*>(&magic), sizeof(magic));
file.seekg(backup_pos, std::ios_base::beg);
if (magic == mh_magic_64 || magic == mh_cigam_64)
return MachOArch::x64;
if (magic == mh_magic_32 || magic == mh_cigam_32)
return MachOArch::x32;
return std::nullopt;
}
[[nodiscard]]
bool is_macho_file(std::fstream& file)
{
return get_macho_arch(file).has_value();
}
[[nodiscard]]
std::string_view get_section_name(const char* sectname)
{
// Mach-O section names are fixed 16-byte arrays, not necessarily null-terminated
return std::string_view(sectname, std::min(std::strlen(sectname), std::size_t{16}));
}
template<typename HeaderType, typename SegmentType, typename SectionType, std::uint32_t segment_cmd>
std::optional<ExtractedSection> extract_section_impl(std::fstream& file, const std::string_view& section_name)
{
HeaderType header{};
file.seekg(0, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&header), sizeof(header))) [[unlikely]]
return std::nullopt;
std::streamoff cmd_offset = sizeof(header);
for (std::uint32_t i = 0; i < header.ncmds; ++i)
{
LoadCommand lc{};
file.seekg(cmd_offset, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&lc), sizeof(lc))) [[unlikely]]
return std::nullopt;
if (lc.cmd != segment_cmd)
{
cmd_offset += static_cast<std::streamoff>(lc.cmdsize);
continue;
}
SegmentType segment{};
file.seekg(cmd_offset, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&segment), sizeof(segment))) [[unlikely]]
return std::nullopt;
if (!segment.nsects)
{
cmd_offset += static_cast<std::streamoff>(lc.cmdsize);
continue;
}
std::streamoff sect_offset = cmd_offset + static_cast<std::streamoff>(sizeof(segment));
for (std::uint32_t j = 0; j < segment.nsects; ++j)
{
SectionType section{};
file.seekg(sect_offset, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&section), sizeof(section))) [[unlikely]]
return std::nullopt;
if (get_section_name(section.sectname) != section_name)
{
sect_offset += static_cast<std::streamoff>(sizeof(section));
continue;
}
ExtractedSection out;
out.virtual_base_addr = static_cast<std::uintptr_t>(section.addr);
out.raw_base_addr = static_cast<std::uintptr_t>(section.offset);
out.data.resize(static_cast<std::size_t>(section.size));
file.seekg(static_cast<std::streamoff>(section.offset), std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(out.data.data()), static_cast<std::streamsize>(out.data.size())))
[[unlikely]]
return std::nullopt;
return out;
}
}
return std::nullopt;
}
[[nodiscard]]
std::optional<ExtractedSection> get_macho_section_by_name(const std::filesystem::path& path,
const std::string_view& section_name)
{
std::fstream file(path, std::ios::binary | std::ios::in);
if (!file.is_open()) [[unlikely]]
return std::nullopt;
if (!is_macho_file(file)) [[unlikely]]
return std::nullopt;
const auto arch = get_macho_arch(file);
if (!arch.has_value()) [[unlikely]]
return std::nullopt;
if (arch.value() == MachOArch::x64)
return extract_section_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(file, section_name);
return extract_section_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(file, section_name);
}
template<typename HeaderType, typename SegmentType, typename SectionType, std::uint32_t segment_cmd>
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const std::string_view pattern,
const std::string_view target_section_name)
{
const auto* header = reinterpret_cast<const HeaderType*>(base);
std::size_t cmd_offset = sizeof(HeaderType);
for (std::uint32_t i = 0; i < header->ncmds; ++i)
{
const auto* lc = reinterpret_cast<const LoadCommand*>(base + cmd_offset);
if (lc->cmd != segment_cmd)
{
cmd_offset += lc->cmdsize;
continue;
}
const auto* segment = reinterpret_cast<const SegmentType*>(base + cmd_offset);
std::size_t sect_offset = cmd_offset + sizeof(SegmentType);
for (std::uint32_t j = 0; j < segment->nsects; ++j)
{
const auto* section = reinterpret_cast<const SectionType*>(base + sect_offset);
if (get_section_name(section->sectname) != target_section_name && section->size > 0)
{
sect_offset += sizeof(SectionType);
continue;
}
const auto* section_begin = base + static_cast<std::size_t>(section->addr);
const auto* section_end = section_begin + static_cast<std::size_t>(section->size);
const auto scan_result = omath::PatternScanner::scan_for_pattern(section_begin, section_end, pattern);
if (scan_result != section_end)
return reinterpret_cast<std::uintptr_t>(scan_result);
}
}
return std::nullopt;
}
} // namespace
namespace omath
{
std::optional<std::uintptr_t>
MachOPatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& pattern,
const std::string_view& target_section_name)
{
if (module_base_address == nullptr) [[unlikely]]
return std::nullopt;
const auto* base = static_cast<const std::byte*>(module_base_address);
// Read magic to determine architecture
std::uint32_t magic{};
std::memcpy(&magic, base, sizeof(magic));
if (magic == mh_magic_64 || magic == mh_cigam_64)
return scan_in_module_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(base, pattern,
target_section_name);
if (magic == mh_magic_32 || magic == mh_cigam_32)
return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, pattern,
target_section_name);
return std::nullopt;
}
std::optional<SectionScanResult>
MachOPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& pattern,
const std::string_view& target_section_name)
{
const auto macho_section = get_macho_section_by_name(path_to_file, target_section_name);
if (!macho_section.has_value()) [[unlikely]]
return std::nullopt;
const auto scan_result =
PatternScanner::scan_for_pattern(macho_section->data.cbegin(), macho_section->data.cend(), pattern);
if (scan_result == macho_section->data.cend())
return std::nullopt;
const auto offset = std::distance(macho_section->data.begin(), scan_result);
return SectionScanResult{.virtual_base_addr = macho_section->virtual_base_addr,
.raw_base_addr = macho_section->raw_base_addr,
.target_offset = offset};
}
} // namespace omath

View File

@@ -237,7 +237,7 @@ namespace
variant); variant);
} }
struct ExtractedSection struct ExtractedSection final
{ {
std::uintptr_t virtual_base_addr; std::uintptr_t virtual_base_addr;
std::uintptr_t raw_base_addr; std::uintptr_t raw_base_addr;

View File

@@ -7,14 +7,15 @@ include(GoogleTest)
file(GLOB_RECURSE UNIT_TESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") file(GLOB_RECURSE UNIT_TESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
add_executable(${PROJECT_NAME} ${UNIT_TESTS_SOURCES}) add_executable(${PROJECT_NAME} ${UNIT_TESTS_SOURCES})
set_target_properties(${PROJECT_NAME} PROPERTIES set_target_properties(
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" ${PROJECT_NAME}
PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
CXX_STANDARD 23 CXX_STANDARD 23
CXX_STANDARD_REQUIRED ON) CXX_STANDARD_REQUIRED ON)
if (TARGET gtest) # GTest is being linked as submodule if(TARGET gtest) # GTest is being linked as submodule
target_link_libraries(${PROJECT_NAME} PRIVATE gtest gtest_main omath::omath) target_link_libraries(${PROJECT_NAME} PRIVATE gtest gtest_main omath::omath)
else() # GTest is being linked as vcpkg package else() # GTest is being linked as vcpkg package
find_package(GTest CONFIG REQUIRED) find_package(GTest CONFIG REQUIRED)
@@ -30,7 +31,8 @@ if(OMATH_ENABLE_VALGRIND)
omath_setup_valgrind(${PROJECT_NAME}) omath_setup_valgrind(${PROJECT_NAME})
endif() endif()
# Skip test discovery for Android/iOS builds or when cross-compiling - binaries cannot run on host # Skip test discovery for Android/iOS builds or when cross-compiling - binaries
if (NOT (ANDROID OR IOS OR EMSCRIPTEN)) # cannot run on host
if(NOT (ANDROID OR IOS OR EMSCRIPTEN))
gtest_discover_tests(${PROJECT_NAME}) gtest_discover_tests(${PROJECT_NAME})
endif() endif()

View File

@@ -47,7 +47,7 @@ namespace
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
struct TraceCase struct TraceCase
{ {
Ray ray; Ray<> ray;
bool expected_clear; // true => segment does NOT hit the triangle bool expected_clear; // true => segment does NOT hit the triangle
friend std::ostream& operator<<(std::ostream& os, const TraceCase& tc) friend std::ostream& operator<<(std::ostream& os, const TraceCase& tc)
{ {
@@ -66,7 +66,7 @@ namespace
TEST_P(CanTraceLineParam, VariousRays) TEST_P(CanTraceLineParam, VariousRays)
{ {
const auto& [ray, expected_clear] = GetParam(); const auto& [ray, expected_clear] = GetParam();
EXPECT_EQ(LineTracer::can_trace_line(ray, triangle), expected_clear); EXPECT_EQ(LineTracer<>::can_trace_line(ray, triangle), expected_clear);
} }
INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P(
@@ -91,7 +91,7 @@ namespace
constexpr Ray ray{{0.3f, 0.3f, -1.f}, {0.3f, 0.3f, 1.f}}; constexpr Ray ray{{0.3f, 0.3f, -1.f}, {0.3f, 0.3f, 1.f}};
constexpr Vec3 expected{0.3f, 0.3f, 0.f}; constexpr Vec3 expected{0.3f, 0.3f, 0.f};
const Vec3 hit = LineTracer::get_ray_hit_point(ray, triangle); const Vec3 hit = LineTracer<>::get_ray_hit_point(ray, triangle);
ASSERT_FALSE(vec_equal(hit, ray.end)); ASSERT_FALSE(vec_equal(hit, ray.end));
EXPECT_TRUE(vec_equal(hit, expected)); EXPECT_TRUE(vec_equal(hit, expected));
} }
@@ -106,7 +106,7 @@ namespace
{1001.f, 1000.f, 1000.f}, {1001.f, 1000.f, 1000.f},
{1000.f, 1001.f, 1000.f}}; {1000.f, 1001.f, 1000.f}};
EXPECT_TRUE(LineTracer::can_trace_line(short_ray, distant)); EXPECT_TRUE(LineTracer<>::can_trace_line(short_ray, distant));
} }
TEST(unit_test_unity_engine, CantHit) TEST(unit_test_unity_engine, CantHit)
@@ -115,13 +115,13 @@ namespace
constexpr Ray ray{{}, {1.0, 0, 0}, false}; constexpr Ray ray{{}, {1.0, 0, 0}, false};
EXPECT_TRUE(omath::collision::LineTracer::can_trace_line(ray, triangle)); EXPECT_TRUE(omath::collision::LineTracer<>::can_trace_line(ray, triangle));
} }
TEST(unit_test_unity_engine, CanHit) TEST(unit_test_unity_engine, CanHit)
{ {
constexpr omath::Triangle<Vector3<float>> triangle{{2, 0, 0}, {2, 2, 0}, {2, 2, 2}}; constexpr omath::Triangle<Vector3<float>> triangle{{2, 0, 0}, {2, 2, 0}, {2, 2, 2}};
constexpr Ray ray{{}, {2.1, 0, 0}, false}; constexpr Ray ray{{}, {2.1, 0, 0}, false};
EXPECT_FALSE(omath::collision::LineTracer::can_trace_line(ray, triangle)); EXPECT_FALSE(omath::collision::LineTracer<>::can_trace_line(ray, triangle));
} }
} // namespace } // namespace

View File

@@ -15,9 +15,9 @@ TEST(LineTracerTests, ParallelRayReturnsEnd)
ray.end = Vector3<float>{1.f,1.f,1.f}; ray.end = Vector3<float>{1.f,1.f,1.f};
// For a ray parallel to the triangle plane the algorithm should return ray.end // For a ray parallel to the triangle plane the algorithm should return ray.end
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri); const auto hit = omath::collision::LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_TRUE(hit == ray.end); EXPECT_TRUE(hit == ray.end);
EXPECT_TRUE(omath::collision::LineTracer::can_trace_line(ray, tri)); EXPECT_TRUE(omath::collision::LineTracer<>::can_trace_line(ray, tri));
} }
TEST(LineTracerTests, MissesTriangleReturnsEnd) TEST(LineTracerTests, MissesTriangleReturnsEnd)
@@ -27,7 +27,7 @@ TEST(LineTracerTests, MissesTriangleReturnsEnd)
ray.start = Vector3<float>{2.f,2.f,-1.f}; ray.start = Vector3<float>{2.f,2.f,-1.f};
ray.end = Vector3<float>{2.f,2.f,1.f}; // passes above the triangle area ray.end = Vector3<float>{2.f,2.f,1.f}; // passes above the triangle area
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri); const auto hit = omath::collision::LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_TRUE(hit == ray.end); EXPECT_TRUE(hit == ray.end);
} }
@@ -38,7 +38,7 @@ TEST(LineTracerTests, HitTriangleReturnsPointInsideSegment)
ray.start = Vector3<float>{0.25f,0.25f,-1.f}; ray.start = Vector3<float>{0.25f,0.25f,-1.f};
ray.end = Vector3<float>{0.25f,0.25f,1.f}; ray.end = Vector3<float>{0.25f,0.25f,1.f};
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri); const auto hit = omath::collision::LineTracer<>::get_ray_hit_point(ray, tri);
// Should return a point between start and end (z approximately 0) // Should return a point between start and end (z approximately 0)
EXPECT_NE(hit, ray.end); EXPECT_NE(hit, ray.end);
EXPECT_NEAR(hit.z, 0.f, 1e-4f); EXPECT_NEAR(hit.z, 0.f, 1e-4f);
@@ -60,6 +60,6 @@ TEST(LineTracerTests, InfiniteLengthEarlyOut)
// If t_hit <= epsilon the algorithm should return ray.end when infinite_length is true. // If t_hit <= epsilon the algorithm should return ray.end when infinite_length is true.
// Using start on the triangle plane should produce t_hit <= epsilon. // Using start on the triangle plane should produce t_hit <= epsilon.
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri); const auto hit = omath::collision::LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_TRUE(hit == ray.end); EXPECT_TRUE(hit == ray.end);
} }

View File

@@ -10,7 +10,7 @@ TEST(LineTracerExtra, MissParallel)
{ {
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0}); constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
constexpr Ray ray{ {0.3f,0.3f,1.f}, {0.3f,0.3f,2.f}, false }; // parallel above triangle constexpr Ray ray{ {0.3f,0.3f,1.f}, {0.3f,0.3f,2.f}, false }; // parallel above triangle
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -18,7 +18,7 @@ TEST(LineTracerExtra, HitCenter)
{ {
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0}); constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
constexpr Ray ray{ {0.3f,0.3f,-1.f}, {0.3f,0.3f,1.f}, false }; constexpr Ray ray{ {0.3f,0.3f,-1.f}, {0.3f,0.3f,1.f}, false };
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
ASSERT_FALSE(hit == ray.end); ASSERT_FALSE(hit == ray.end);
EXPECT_NEAR(hit.x, 0.3f, 1e-6f); EXPECT_NEAR(hit.x, 0.3f, 1e-6f);
EXPECT_NEAR(hit.y, 0.3f, 1e-6f); EXPECT_NEAR(hit.y, 0.3f, 1e-6f);
@@ -30,7 +30,7 @@ TEST(LineTracerExtra, HitOnEdge)
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0}); constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
constexpr Ray ray{ {0.0f,0.0f,1.f}, {0.0f,0.0f,0.f}, false }; constexpr Ray ray{ {0.0f,0.0f,1.f}, {0.0f,0.0f,0.f}, false };
// hitting exact vertex/edge may be considered miss; ensure function handles without crash // hitting exact vertex/edge may be considered miss; ensure function handles without crash
if (const auto hit = LineTracer::get_ray_hit_point(ray, tri); hit != ray.end) if (const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); hit != ray.end)
{ {
EXPECT_NEAR(hit.x, 0.0f, 1e-6f); EXPECT_NEAR(hit.x, 0.0f, 1e-6f);
EXPECT_NEAR(hit.y, 0.0f, 1e-6f); EXPECT_NEAR(hit.y, 0.0f, 1e-6f);
@@ -42,6 +42,6 @@ TEST(LineTracerExtra, InfiniteRayIgnoredIfBehind)
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0}); constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
// Ray pointing away but infinite_length true should be ignored // Ray pointing away but infinite_length true should be ignored
constexpr Ray ray{ {0.5f,0.5f,-1.f}, {0.5f,0.5f,-2.f}, true }; constexpr Ray ray{ {0.5f,0.5f,-1.f}, {0.5f,0.5f,-2.f}, true };
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }

View File

@@ -14,7 +14,7 @@ TEST(LineTracerMore, ParallelRayReturnsEnd)
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f}); constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {0.f,0.f,1.f}; ray.end = {1.f,0.f,1.f}; Ray ray; ray.start = {0.f,0.f,1.f}; ray.end = {1.f,0.f,1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -24,7 +24,7 @@ TEST(LineTracerMore, UOutOfRangeReturnsEnd)
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f}); constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {-1.f,-1.f,-1.f}; ray.end = {-0.5f,-1.f,1.f}; Ray ray; ray.start = {-1.f,-1.f,-1.f}; ray.end = {-0.5f,-1.f,1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -34,7 +34,7 @@ TEST(LineTracerMore, VOutOfRangeReturnsEnd)
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f}); constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {2.f,2.f,-1.f}; ray.end = {2.f,2.f,1.f}; Ray ray; ray.start = {2.f,2.f,-1.f}; ray.end = {2.f,2.f,1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -43,7 +43,7 @@ TEST(LineTracerMore, THitTooSmallReturnsEnd)
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f}); constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {0.f,0.f,0.0000000001f}; ray.end = {0.f,0.f,1.f}; Ray ray; ray.start = {0.f,0.f,0.0000000001f}; ray.end = {0.f,0.f,1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -53,7 +53,7 @@ TEST(LineTracerMore, THitGreaterThanOneReturnsEnd)
// Choose a ray and compute t_hit locally to assert consistency // Choose a ray and compute t_hit locally to assert consistency
Ray ray; ray.start = {0.f,0.f,-1.f}; ray.end = {0.f,0.f,-0.5f}; Ray ray; ray.start = {0.f,0.f,-1.f}; ray.end = {0.f,0.f,-0.5f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
constexpr float k_epsilon = std::numeric_limits<float>::epsilon(); constexpr float k_epsilon = std::numeric_limits<float>::epsilon();
constexpr auto side_a = tri.side_a_vector(); constexpr auto side_a = tri.side_a_vector();
@@ -87,7 +87,7 @@ TEST(LineTracerMore, InfiniteLengthWithSmallTHitReturnsEnd)
// Create triangle slightly behind so t_hit <= eps // Create triangle slightly behind so t_hit <= eps
tri = tri2; tri = tri2;
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -96,7 +96,7 @@ TEST(LineTracerMore, SuccessfulHitReturnsPoint)
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f}); constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {0.1f,0.1f,-1.f}; ray.end = {0.1f,0.1f,1.f}; Ray ray; ray.start = {0.1f,0.1f,-1.f}; ray.end = {0.1f,0.1f,1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_NE(hit, ray.end); EXPECT_NE(hit, ray.end);
// Hit should be on plane z=0 and near x=0.1,y=0.1 // Hit should be on plane z=0 and near x=0.1,y=0.1
EXPECT_NEAR(hit.z, 0.f, 1e-6f); EXPECT_NEAR(hit.z, 0.f, 1e-6f);

View File

@@ -14,7 +14,7 @@ TEST(LineTracerMore2, UGreaterThanOneReturnsEnd)
// choose ray so barycentric u > 1 // choose ray so barycentric u > 1
Ray ray; ray.start = {2.f, -1.f, -1.f}; ray.end = {2.f, -1.f, 1.f}; Ray ray; ray.start = {2.f, -1.f, -1.f}; ray.end = {2.f, -1.f, 1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -24,7 +24,7 @@ TEST(LineTracerMore2, VGreaterThanOneReturnsEnd)
// choose ray so barycentric v > 1 // choose ray so barycentric v > 1
Ray ray; ray.start = {-1.f, 2.f, -1.f}; ray.end = {-1.f, 2.f, 1.f}; Ray ray; ray.start = {-1.f, 2.f, -1.f}; ray.end = {-1.f, 2.f, 1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -34,7 +34,7 @@ TEST(LineTracerMore2, UPlusVGreaterThanOneReturnsEnd)
// Ray aimed so u+v > 1 (outside triangle region) // Ray aimed so u+v > 1 (outside triangle region)
Ray ray; ray.start = {1.f, 1.f, -1.f}; ray.end = {1.f, 1.f, 1.f}; Ray ray; ray.start = {1.f, 1.f, -1.f}; ray.end = {1.f, 1.f, 1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }
@@ -52,6 +52,6 @@ TEST(LineTracerMore2, ZeroLengthRayHandled)
Ray ray; ray.start = {0.f,0.f,0.f}; ray.end = {0.f,0.f,0.f}; Ray ray; ray.start = {0.f,0.f,0.f}; ray.end = {0.f,0.f,0.f};
// Zero-length ray: direction length == 0; algorithm should handle without crash // Zero-length ray: direction length == 0; algorithm should handle without crash
const auto hit = LineTracer::get_ray_hit_point(ray, tri); const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end); EXPECT_EQ(hit, ray.end);
} }

View File

@@ -0,0 +1,358 @@
//
// Created by Copilot on 04.02.2026.
//
// Unit tests for MachOPatternScanner
#include <gtest/gtest.h>
#include <omath/utility/macho_pattern_scan.hpp>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <vector>
using namespace omath;
namespace
{
// Mach-O magic numbers
constexpr std::uint32_t mh_magic_64 = 0xFEEDFACF;
constexpr std::uint32_t mh_magic_32 = 0xFEEDFACE;
constexpr std::uint32_t lc_segment = 0x1;
constexpr std::uint32_t lc_segment_64 = 0x19;
constexpr std::string_view segment_name = "__TEXT";
constexpr std::string_view section_name = "__text";
#pragma pack(push, 1)
struct MachHeader64
{
std::uint32_t magic;
std::uint32_t cputype;
std::uint32_t cpusubtype;
std::uint32_t filetype;
std::uint32_t ncmds;
std::uint32_t sizeofcmds;
std::uint32_t flags;
std::uint32_t reserved;
};
struct MachHeader32
{
std::uint32_t magic;
std::uint32_t cputype;
std::uint32_t cpusubtype;
std::uint32_t filetype;
std::uint32_t ncmds;
std::uint32_t sizeofcmds;
std::uint32_t flags;
};
struct SegmentCommand64
{
std::uint32_t cmd;
std::uint32_t cmdsize;
char segname[16];
std::uint64_t vmaddr;
std::uint64_t vmsize;
std::uint64_t fileoff;
std::uint64_t filesize;
std::uint32_t maxprot;
std::uint32_t initprot;
std::uint32_t nsects;
std::uint32_t flags;
};
struct SegmentCommand32
{
std::uint32_t cmd;
std::uint32_t cmdsize;
char segname[16];
std::uint32_t vmaddr;
std::uint32_t vmsize;
std::uint32_t fileoff;
std::uint32_t filesize;
std::uint32_t maxprot;
std::uint32_t initprot;
std::uint32_t nsects;
std::uint32_t flags;
};
struct Section64
{
char sectname[16];
char segname[16];
std::uint64_t addr;
std::uint64_t size;
std::uint32_t offset;
std::uint32_t align;
std::uint32_t reloff;
std::uint32_t nreloc;
std::uint32_t flags;
std::uint32_t reserved1;
std::uint32_t reserved2;
std::uint32_t reserved3;
};
struct Section32
{
char sectname[16];
char segname[16];
std::uint32_t addr;
std::uint32_t size;
std::uint32_t offset;
std::uint32_t align;
std::uint32_t reloff;
std::uint32_t nreloc;
std::uint32_t flags;
std::uint32_t reserved1;
std::uint32_t reserved2;
};
#pragma pack(pop)
// Helper function to create a minimal 64-bit Mach-O file with a __text section
bool write_minimal_macho64_file(const std::string& path, const std::vector<std::uint8_t>& section_bytes)
{
std::ofstream f(path, std::ios::binary);
if (!f.is_open())
return false;
// Calculate sizes
constexpr std::size_t header_size = sizeof(MachHeader64);
constexpr std::size_t segment_size = sizeof(SegmentCommand64);
constexpr std::size_t section_size = sizeof(Section64);
constexpr std::size_t load_cmd_size = segment_size + section_size;
// Section data will start after headers
const std::size_t section_offset = header_size + load_cmd_size;
// Create Mach-O header
MachHeader64 header{};
header.magic = mh_magic_64;
header.cputype = 0x01000007; // CPU_TYPE_X86_64
header.cpusubtype = 0x3; // CPU_SUBTYPE_X86_64_ALL
header.filetype = 0x2; // MH_EXECUTE
header.ncmds = 1;
header.sizeofcmds = static_cast<std::uint32_t>(load_cmd_size);
header.flags = 0;
header.reserved = 0;
f.write(reinterpret_cast<const char*>(&header), sizeof(header));
// Create segment command
SegmentCommand64 segment{};
segment.cmd = lc_segment_64;
segment.cmdsize = static_cast<std::uint32_t>(load_cmd_size);
std::ranges::copy(segment_name, segment.segname);
segment.vmaddr = 0x100000000;
segment.vmsize = section_bytes.size();
segment.fileoff = section_offset;
segment.filesize = section_bytes.size();
segment.maxprot = 7; // VM_PROT_ALL
segment.initprot = 5; // VM_PROT_READ | VM_PROT_EXECUTE
segment.nsects = 1;
segment.flags = 0;
f.write(reinterpret_cast<const char*>(&segment), sizeof(segment));
// Create section
Section64 section{};
std::ranges::copy(section_name, section.sectname);
std::ranges::copy(segment_name, segment.segname);
section.addr = 0x100000000;
section.size = section_bytes.size();
section.offset = static_cast<std::uint32_t>(section_offset);
section.align = 0;
section.reloff = 0;
section.nreloc = 0;
section.flags = 0;
section.reserved1 = 0;
section.reserved2 = 0;
section.reserved3 = 0;
f.write(reinterpret_cast<const char*>(&section), sizeof(section));
// Write section data
f.write(reinterpret_cast<const char*>(section_bytes.data()), static_cast<std::streamsize>(section_bytes.size()));
f.close();
return true;
}
// Helper function to create a minimal 32-bit Mach-O file with a __text section
bool write_minimal_macho32_file(const std::string& path, const std::vector<std::uint8_t>& section_bytes)
{
std::ofstream f(path, std::ios::binary);
if (!f.is_open())
return false;
// Calculate sizes
constexpr std::size_t header_size = sizeof(MachHeader32);
constexpr std::size_t segment_size = sizeof(SegmentCommand32);
constexpr std::size_t section_size = sizeof(Section32);
constexpr std::size_t load_cmd_size = segment_size + section_size;
// Section data will start after headers
constexpr std::size_t section_offset = header_size + load_cmd_size;
// Create Mach-O header
MachHeader32 header{};
header.magic = mh_magic_32;
header.cputype = 0x7; // CPU_TYPE_X86
header.cpusubtype = 0x3; // CPU_SUBTYPE_X86_ALL
header.filetype = 0x2; // MH_EXECUTE
header.ncmds = 1;
header.sizeofcmds = static_cast<std::uint32_t>(load_cmd_size);
header.flags = 0;
f.write(reinterpret_cast<const char*>(&header), sizeof(header));
// Create segment command
SegmentCommand32 segment{};
segment.cmd = lc_segment;
segment.cmdsize = static_cast<std::uint32_t>(load_cmd_size);
std::ranges::copy(segment_name, segment.segname);
segment.vmaddr = 0x1000;
segment.vmsize = static_cast<std::uint32_t>(section_bytes.size());
segment.fileoff = static_cast<std::uint32_t>(section_offset);
segment.filesize = static_cast<std::uint32_t>(section_bytes.size());
segment.maxprot = 7; // VM_PROT_ALL
segment.initprot = 5; // VM_PROT_READ | VM_PROT_EXECUTE
segment.nsects = 1;
segment.flags = 0;
f.write(reinterpret_cast<const char*>(&segment), sizeof(segment));
// Create section
Section32 section{};
std::ranges::copy(section_name, section.sectname);
std::ranges::copy(segment_name, segment.segname);
section.addr = 0x1000;
section.size = static_cast<std::uint32_t>(section_bytes.size());
section.offset = static_cast<std::uint32_t>(section_offset);
section.align = 0;
section.reloff = 0;
section.nreloc = 0;
section.flags = 0;
section.reserved1 = 0;
section.reserved2 = 0;
f.write(reinterpret_cast<const char*>(&section), sizeof(section));
// Write section data
f.write(reinterpret_cast<const char*>(section_bytes.data()), static_cast<std::streamsize>(section_bytes.size()));
f.close();
return true;
}
} // namespace
// Test scanning for a pattern that exists in a 64-bit Mach-O file
TEST(unit_test_macho_pattern_scan_file, ScanFindsPattern64)
{
constexpr std::string_view path = "./test_minimal_macho64.bin";
const std::vector<std::uint8_t> bytes = {0x55, 0x48, 0x89, 0xE5, 0x90, 0x90}; // push rbp; mov rbp, rsp; nop; nop
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48 89 E5", "__text");
EXPECT_TRUE(res.has_value());
if (res.has_value())
{
EXPECT_EQ(res->target_offset, 0);
}
}
// Test scanning for a pattern that exists in a 32-bit Mach-O file
TEST(unit_test_macho_pattern_scan_file, ScanFindsPattern32)
{
constexpr std::string_view path = "./test_minimal_macho32.bin";
const std::vector<std::uint8_t> bytes = {0x55, 0x89, 0xE5, 0x90, 0x90}; // push ebp; mov ebp, esp; nop; nop
ASSERT_TRUE(write_minimal_macho32_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 89 E5", "__text");
EXPECT_TRUE(res.has_value());
if (res.has_value())
{
EXPECT_EQ(res->target_offset, 0);
}
}
// Test scanning for a pattern that does not exist
TEST(unit_test_macho_pattern_scan_file, ScanMissingPattern)
{
constexpr std::string_view path = "./test_minimal_macho_missing.bin";
const std::vector<std::uint8_t> bytes = {0x00, 0x01, 0x02, 0x03};
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "FF EE DD", "__text");
EXPECT_FALSE(res.has_value());
}
// Test scanning for a pattern at a non-zero offset
TEST(unit_test_macho_pattern_scan_file, ScanPatternAtOffset)
{
constexpr std::string_view path = "./test_minimal_macho_offset.bin";
const std::vector<std::uint8_t> bytes = {0x90, 0x90, 0x90, 0x55, 0x48, 0x89, 0xE5}; // nops then pattern
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48 89 E5", "__text");
EXPECT_TRUE(res.has_value());
if (res.has_value())
{
EXPECT_EQ(res->target_offset, 3);
}
}
// Test scanning with wildcards
TEST(unit_test_macho_pattern_scan_file, ScanWithWildcard)
{
constexpr std::string_view path = "./test_minimal_macho_wildcard.bin";
const std::vector<std::uint8_t> bytes = {0x55, 0x48, 0x89, 0xE5, 0x90};
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 ? 89 E5", "__text");
EXPECT_TRUE(res.has_value());
}
// Test scanning a non-existent file
TEST(unit_test_macho_pattern_scan_file, ScanNonExistentFile)
{
const auto res = MachOPatternScanner::scan_for_pattern_in_file("/non/existent/file.bin", "55 48", "__text");
EXPECT_FALSE(res.has_value());
}
// Test scanning an invalid (non-Mach-O) file
TEST(unit_test_macho_pattern_scan_file, ScanInvalidFile)
{
constexpr std::string_view path = "./test_invalid_macho.bin";
std::ofstream f(path.data(), std::ios::binary);
const std::vector<std::uint8_t> garbage = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
f.write(reinterpret_cast<const char*>(garbage.data()), static_cast<std::streamsize>(garbage.size()));
f.close();
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48", "__text");
EXPECT_FALSE(res.has_value());
}
// Test scanning for a non-existent section
TEST(unit_test_macho_pattern_scan_file, ScanNonExistentSection)
{
constexpr std::string_view path = "./test_minimal_macho_nosect.bin";
const std::vector<std::uint8_t> bytes = {0x55, 0x48, 0x89, 0xE5};
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48", "__nonexistent");
EXPECT_FALSE(res.has_value());
}
// Test scanning with null module base address
TEST(unit_test_macho_pattern_scan_loaded, ScanNullModule)
{
const auto res = MachOPatternScanner::scan_for_pattern_in_loaded_module(nullptr, "55 48", "__text");
EXPECT_FALSE(res.has_value());
}
// Test scanning in loaded module with invalid magic
TEST(unit_test_macho_pattern_scan_loaded, ScanInvalidMagic)
{
std::vector<std::uint8_t> invalid_data(256, 0x00);
const auto res = MachOPatternScanner::scan_for_pattern_in_loaded_module(invalid_data.data(), "55 48", "__text");
EXPECT_FALSE(res.has_value());
}

View File

@@ -12,5 +12,5 @@ TEST(test, test)
{0.f, 30.f, 0.f}, {}, omath::opengl_engine::k_abs_forward, omath::opengl_engine::k_abs_right); {0.f, 30.f, 0.f}, {}, omath::opengl_engine::k_abs_forward, omath::opengl_engine::k_abs_right);
omath::collision::Ray ray{.start = {0, 0, 0}, .end = {-100, 0, 0}}; omath::collision::Ray ray{.start = {0, 0, 0}, .end = {-100, 0, 0}};
std::ignore = omath::collision::LineTracer::get_ray_hit_point(ray, result); std::ignore = omath::collision::LineTracer<>::get_ray_hit_point(ray, result);
} }