Compare commits

..

2 Commits

Author SHA1 Message Date
orange 92f5378a70 stage 2026-06-03 15:32:53 +03:00
orange dc56400927 added rage engine support 2026-06-03 13:42:13 +03:00
34 changed files with 284 additions and 1037 deletions
+1 -6
View File
@@ -81,7 +81,7 @@ if (auto screen = camera.world_to_screen(world_position)) {
- **Collision Detection**: Production ready code to handle collision detection by using simple interfaces. - **Collision Detection**: Production ready code to handle collision detection by using simple interfaces.
- **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution - **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution
- **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types! - **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types!
- **Engine support**: Supports coordinate systems of **Source, Rage, Unity, Unreal, Frostbite, IWEngine, CryEngine and canonical OpenGL**. - **Engine support**: Supports coordinate systems of **Source, Unity, Unreal, Frostbite, IWEngine, CryEngine and canonical OpenGL**.
- **Cross platform**: Supports Windows, MacOS and Linux. - **Cross platform**: Supports Windows, MacOS and Linux.
- **Algorithms**: Has ability to scan for byte pattern with wildcards in ELF/Mach-O/PE files/modules, binary slices, works even with Wine apps. - **Algorithms**: Has ability to scan for byte pattern with wildcards in ELF/Mach-O/PE files/modules, binary slices, works even with Wine apps.
- **Scripting**: Supports to make scripts in Lua out of box. - **Scripting**: Supports to make scripts in Lua out of box.
@@ -113,10 +113,6 @@ if (auto screen = camera.world_to_screen(world_position)) {
<br> <br>
![GTA5 Preview]
<br>
![OpenGL Preview] ![OpenGL Preview]
<br> <br>
@@ -148,7 +144,6 @@ if (auto screen = camera.world_to_screen(world_position)) {
[BO2 Preview]: docs/images/showcase/cod_bo2.png [BO2 Preview]: docs/images/showcase/cod_bo2.png
[CS2 Preview]: docs/images/showcase/cs2.jpeg [CS2 Preview]: docs/images/showcase/cs2.jpeg
[TF2 Preview]: docs/images/showcase/tf2.jpg [TF2 Preview]: docs/images/showcase/tf2.jpg
[GTA5 Preview]: https://i.imgur.com/W7T8RhZ.png
[OpenGL Preview]: docs/images/showcase/opengl.png [OpenGL Preview]: docs/images/showcase/opengl.png
<!----------------------------------{ Buttons }---------------------------------> <!----------------------------------{ Buttons }--------------------------------->
[QUICKSTART]: docs/getting_started.md [QUICKSTART]: docs/getting_started.md
+19 -38
View File
@@ -71,7 +71,6 @@ namespace imgui_desktop::gui
ImGui::SliderFloat("X##ent", &m_entity_x, 100.f, vp->Size.x - 100.f); ImGui::SliderFloat("X##ent", &m_entity_x, 100.f, vp->Size.x - 100.f);
ImGui::SliderFloat("Top Y", &m_entity_top_y, 20.f, m_entity_bottom_y - 20.f); ImGui::SliderFloat("Top Y", &m_entity_top_y, 20.f, m_entity_bottom_y - 20.f);
ImGui::SliderFloat("Bottom Y", &m_entity_bottom_y, m_entity_top_y + 20.f, vp->Size.y - 20.f); ImGui::SliderFloat("Bottom Y", &m_entity_bottom_y, m_entity_top_y + 20.f, vp->Size.y - 20.f);
ImGui::SliderFloat("Aspect", &m_entity_aspect, 1.f, 10.f);
} }
if (ImGui::CollapsingHeader("Box", ImGuiTreeNodeFlags_DefaultOpen)) if (ImGui::CollapsingHeader("Box", ImGuiTreeNodeFlags_DefaultOpen))
@@ -197,11 +196,7 @@ namespace imgui_desktop::gui
const DashedBar dbar{m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, const DashedBar dbar{m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width,
m_bar_value, m_bar_dash_len, m_bar_dash_gap, m_bar_offset}; m_bar_value, m_bar_dash_len, m_bar_dash_gap, m_bar_offset};
auto outline_helper = [](const bool is_outline) -> Outlined omath::hud::EntityOverlay({m_entity_x, m_entity_top_y}, {m_entity_x, m_entity_bottom_y},
{
return is_outline ? Outlined::On : Outlined::Off;
};
omath::hud::EntityOverlay({m_entity_x, m_entity_top_y}, {m_entity_x, m_entity_bottom_y}, m_entity_aspect,
std::make_shared<omath::hud::ImguiHudRenderer>()) std::make_shared<omath::hud::ImguiHudRenderer>())
.contents( .contents(
// ── Boxes ──────────────────────────────────────────────────── // ── Boxes ────────────────────────────────────────────────────
@@ -212,18 +207,12 @@ namespace imgui_desktop::gui
RightSide{ RightSide{
when(m_show_right_bar, bar), when(m_show_right_bar, bar),
when(m_show_right_dashed_bar, dbar), when(m_show_right_dashed_bar, dbar),
when(m_show_right_labels, Label{{0.f, 1.f, 0.f, 1.f}, when(m_show_right_labels,
m_label_offset, Label{{0.f, 1.f, 0.f, 1.f}, m_label_offset, m_outlined, "Health: 100/100"}),
outline_helper(m_outlined), when(m_show_right_labels,
"Health: 100/100"}), Label{{1.f, 0.f, 0.f, 1.f}, m_label_offset, m_outlined, "Shield: 125/125"}),
when(m_show_right_labels, Label{{1.f, 0.f, 0.f, 1.f}, when(m_show_right_labels,
m_label_offset, Label{{1.f, 0.f, 1.f, 1.f}, m_label_offset, m_outlined, "*LOCKED*"}),
outline_helper(m_outlined),
"Shield: 125/125"}),
when(m_show_right_labels, Label{{1.f, 0.f, 1.f, 1.f},
m_label_offset,
outline_helper(m_outlined),
"*LOCKED*"}),
SpaceVertical{10}, SpaceVertical{10},
when(m_show_ring, ProgressRing{m_ring_color, m_ring_bg, m_ring_radius, m_ring_ratio, when(m_show_ring, ProgressRing{m_ring_color, m_ring_bg, m_ring_radius, m_ring_ratio,
@@ -232,41 +221,33 @@ namespace imgui_desktop::gui
LeftSide{ LeftSide{
when(m_show_left_bar, bar), when(m_show_left_bar, bar),
when(m_show_left_dashed_bar, dbar), when(m_show_left_dashed_bar, dbar),
when(m_show_left_labels, when(m_show_left_labels, Label{omath::Color::from_rgba(255, 128, 0, 255),
Label{omath::Color::from_rgba(255, 128, 0, 255), m_label_offset, m_label_offset, m_outlined, "Armor: 75"}),
outline_helper(m_outlined), "Armor: 75"}), when(m_show_left_labels, Label{omath::Color::from_rgba(0, 200, 255, 255),
when(m_show_left_labels, m_label_offset, m_outlined, "Level: 42"}),
Label{omath::Color::from_rgba(0, 200, 255, 255), m_label_offset,
outline_helper(m_outlined), "Level: 42"}),
}, },
TopSide{ TopSide{
when(m_show_top_bar, bar), when(m_show_top_bar, bar),
when(m_show_top_dashed_bar, dbar), when(m_show_top_dashed_bar, dbar),
when(m_show_centered_top, when(m_show_centered_top, Centered{Label{omath::Color::from_rgba(0, 255, 255, 255),
Centered{Label{omath::Color::from_rgba(0, 255, 255, 255), m_label_offset, m_label_offset, m_outlined, "*VISIBLE*"}}),
outline_helper(m_outlined), "*VISIBLE*"}}),
when(m_show_top_labels, Label{omath::Color::from_rgba(255, 255, 0, 255), m_label_offset, when(m_show_top_labels, Label{omath::Color::from_rgba(255, 255, 0, 255), m_label_offset,
outline_helper(m_outlined), "*SCOPED*"}), m_outlined, "*SCOPED*"}),
when(m_show_top_labels, Label{omath::Color::from_rgba(255, 0, 0, 255), m_label_offset, when(m_show_top_labels, Label{omath::Color::from_rgba(255, 0, 0, 255), m_label_offset,
outline_helper(m_outlined), "*BLEEDING*"}), m_outlined, "*BLEEDING*"}),
}, },
BottomSide{ BottomSide{
when(m_show_bottom_bar, bar), when(m_show_bottom_bar, bar),
when(m_show_bottom_dashed_bar, dbar), when(m_show_bottom_dashed_bar, dbar),
when(m_show_centered_bottom, when(m_show_centered_bottom, Centered{Label{omath::Color::from_rgba(255, 255, 255, 255),
Centered{Label{omath::Color::from_rgba(255, 255, 255, 255), m_label_offset, m_label_offset, m_outlined, "PlayerName"}}),
outline_helper(m_outlined), "PlayerName"}}),
when(m_show_bottom_labels, Label{omath::Color::from_rgba(200, 200, 0, 255), when(m_show_bottom_labels, Label{omath::Color::from_rgba(200, 200, 0, 255),
m_label_offset, outline_helper(m_outlined), "42m"}), m_label_offset, m_outlined, "42m"}),
}, },
when(m_show_aim, AimDot{{m_entity_x, m_entity_top_y+40.f}, m_aim_color, m_aim_radius}), when(m_show_aim, AimDot{{m_entity_x, m_entity_top_y+40.f}, m_aim_color, m_aim_radius}),
when(m_show_scan, ScanMarker{m_scan_color, m_scan_outline, m_scan_outline_thickness}), when(m_show_scan, ScanMarker{m_scan_color, m_scan_outline, m_scan_outline_thickness}),
when(m_show_skeleton, Skeleton{m_skel_color, m_skel_thickness}), when(m_show_skeleton, Skeleton{m_skel_color, m_skel_thickness}),
when(m_show_proj, ProjectileAim{{m_proj_pos_x, m_proj_pos_y}, when(m_show_proj, ProjectileAim{{m_proj_pos_x, m_proj_pos_y}, m_proj_color, m_proj_size, m_proj_line_width, static_cast<ProjectileAim::Figure>(m_proj_figure)}),
m_proj_color,
m_proj_size,
m_proj_line_width,
static_cast<ProjectileAim::Figure>(m_proj_figure)}),
when(m_show_snap, SnapLine{{vp->Size.x / 2.f, vp->Size.y}, m_snap_color, m_snap_width})); when(m_show_snap, SnapLine{{vp->Size.x / 2.f, vp->Size.y}, m_snap_color, m_snap_width}));
} }
+2 -1
View File
@@ -26,7 +26,8 @@ namespace imgui_desktop::gui
bool m_opened = true; bool m_opened = true;
// Entity // Entity
float m_entity_x = 550.f, m_entity_top_y = 150.f, m_entity_bottom_y = 450.f, m_entity_aspect = 4.f; float m_entity_x = 550.f, m_entity_top_y = 150.f, m_entity_bottom_y = 450.f;
// Box // Box
omath::Color m_box_color{1.f, 1.f, 1.f, 1.f}; omath::Color m_box_color{1.f, 1.f, 1.f, 1.f};
omath::Color m_box_fill{0.f, 0.f, 0.f, 0.f}; omath::Color m_box_fill{0.f, 0.f, 0.f, 0.f};
+1 -1
View File
@@ -1,5 +1,5 @@
// //
// Created by Orange on 6/3/2026. // Created by Codex on 6/3/2026.
// //
#pragma once #pragma once
@@ -1,5 +1,5 @@
// //
// Created by Orange on 6/3/2026. // Created by Codex on 6/3/2026.
// //
#pragma once #pragma once
@@ -1,5 +1,5 @@
// //
// Created by Orange on 6/3/2026. // Created by Codex on 6/3/2026.
// //
#pragma once #pragma once
+1 -1
View File
@@ -1,5 +1,5 @@
// //
// Created by Orange on 6/3/2026. // Created by Codex on 6/3/2026.
// //
#pragma once #pragma once
@@ -1,5 +1,5 @@
// //
// Created by Orange on 6/3/2026. // Created by Codex on 6/3/2026.
// //
#pragma once #pragma once
@@ -1,5 +1,5 @@
// //
// Created by Orange on 6/3/2026. // Created by Codex on 6/3/2026.
// //
#pragma once #pragma once
@@ -1,5 +1,5 @@
// //
// Created by Orange on 6/3/2026. // Created by Codex on 6/3/2026.
// //
#pragma once #pragma once
+13 -13
View File
@@ -15,7 +15,7 @@ namespace omath::hud
class EntityOverlay final class EntityOverlay final
{ {
public: public:
EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom, float aspect, EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom,
const std::shared_ptr<HudRendererInterface>& renderer); const std::shared_ptr<HudRendererInterface>& renderer);
// ── Boxes ──────────────────────────────────────────────────────── // ── Boxes ────────────────────────────────────────────────────────
@@ -57,22 +57,22 @@ namespace omath::hud
float offset = 5.f); float offset = 5.f);
// ── Labels ─────────────────────────────────────────────────────── // ── Labels ───────────────────────────────────────────────────────
EntityOverlay& add_right_label(const Color& color, float offset, widget::Outlined outlined, const std::string_view& text); EntityOverlay& add_right_label(const Color& color, float offset, bool outlined, const std::string_view& text);
EntityOverlay& add_left_label(const Color& color, float offset, widget::Outlined outlined, const std::string_view& text); EntityOverlay& add_left_label(const Color& color, float offset, bool outlined, const std::string_view& text);
EntityOverlay& add_top_label(const Color& color, float offset, widget::Outlined outlined, std::string_view text); EntityOverlay& add_top_label(const Color& color, float offset, bool outlined, std::string_view text);
EntityOverlay& add_bottom_label(const Color& color, float offset, widget::Outlined outlined, std::string_view text); EntityOverlay& add_bottom_label(const Color& color, float offset, bool outlined, std::string_view text);
EntityOverlay& add_centered_top_label(const Color& color, float offset, widget::Outlined outlined, EntityOverlay& add_centered_top_label(const Color& color, float offset, bool outlined,
const std::string_view& text); const std::string_view& text);
EntityOverlay& add_centered_bottom_label(const Color& color, float offset, widget::Outlined outlined, EntityOverlay& add_centered_bottom_label(const Color& color, float offset, bool outlined,
const std::string_view& text); const std::string_view& text);
template<typename... Args> template<typename... Args>
EntityOverlay& add_right_label(const Color& color, const float offset, const widget::Outlined outlined, std::format_string<Args...> fmt, EntityOverlay& add_right_label(const Color& color, const float offset, const bool outlined, std::format_string<Args...> fmt,
Args&&... args) Args&&... args)
{ {
return add_right_label(color, offset, outlined, return add_right_label(color, offset, outlined,
@@ -80,7 +80,7 @@ namespace omath::hud
} }
template<typename... Args> template<typename... Args>
EntityOverlay& add_left_label(const Color& color, const float offset, const widget::Outlined outlined, std::format_string<Args...> fmt, EntityOverlay& add_left_label(const Color& color, const float offset, const bool outlined, std::format_string<Args...> fmt,
Args&&... args) Args&&... args)
{ {
return add_left_label(color, offset, outlined, return add_left_label(color, offset, outlined,
@@ -88,7 +88,7 @@ namespace omath::hud
} }
template<typename... Args> template<typename... Args>
EntityOverlay& add_top_label(const Color& color, const float offset, const widget::Outlined outlined, std::format_string<Args...> fmt, EntityOverlay& add_top_label(const Color& color, const float offset, const bool outlined, std::format_string<Args...> fmt,
Args&&... args) Args&&... args)
{ {
return add_top_label(color, offset, outlined, return add_top_label(color, offset, outlined,
@@ -96,7 +96,7 @@ namespace omath::hud
} }
template<typename... Args> template<typename... Args>
EntityOverlay& add_bottom_label(const Color& color, const float offset, const widget::Outlined outlined, EntityOverlay& add_bottom_label(const Color& color, const float offset, const bool outlined,
std::format_string<Args...> fmt, Args&&... args) std::format_string<Args...> fmt, Args&&... args)
{ {
return add_bottom_label(color, offset, outlined, return add_bottom_label(color, offset, outlined,
@@ -104,7 +104,7 @@ namespace omath::hud
} }
template<typename... Args> template<typename... Args>
EntityOverlay& add_centered_top_label(const Color& color, const float offset, const widget::Outlined outlined, EntityOverlay& add_centered_top_label(const Color& color, const float offset, const bool outlined,
std::format_string<Args...> fmt, Args&&... args) std::format_string<Args...> fmt, Args&&... args)
{ {
return add_centered_top_label(color, offset, outlined, return add_centered_top_label(color, offset, outlined,
@@ -112,7 +112,7 @@ namespace omath::hud
} }
template<typename... Args> template<typename... Args>
EntityOverlay& add_centered_bottom_label(const Color& color, const float offset, const widget::Outlined outlined, EntityOverlay& add_centered_bottom_label(const Color& color, const float offset, const bool outlined,
std::format_string<Args...> fmt, Args&&... args) std::format_string<Args...> fmt, Args&&... args)
{ {
return add_centered_bottom_label(color, offset, outlined, return add_centered_bottom_label(color, offset, outlined,
+1 -6
View File
@@ -30,11 +30,6 @@ namespace omath::hud::widget
float thickness = 1.f; float thickness = 1.f;
}; };
enum class Outlined
{
Off,
On,
};
struct CorneredBox struct CorneredBox
{ {
Color color; Color color;
@@ -123,7 +118,7 @@ namespace omath::hud::widget
{ {
Color color; Color color;
float offset; float offset;
Outlined outlined; bool outlined;
std::string_view text; std::string_view text;
}; };
@@ -9,6 +9,7 @@
#include "omath/projectile_prediction/proj_pred_engine.hpp" #include "omath/projectile_prediction/proj_pred_engine.hpp"
#include "omath/projectile_prediction/projectile.hpp" #include "omath/projectile_prediction/projectile.hpp"
#include "omath/projectile_prediction/target.hpp" #include "omath/projectile_prediction/target.hpp"
#include <cmath>
#include <optional> #include <optional>
namespace omath::projectile_prediction namespace omath::projectile_prediction
@@ -96,7 +97,7 @@ namespace omath::projectile_prediction
EngineTrait::predict_target_position(target, time, m_gravity_constant); EngineTrait::predict_target_position(target, time, m_gravity_constant);
const auto projectile_pitch = const auto projectile_pitch =
maybe_calculate_projectile_launch_pitch_angle(projectile, predicted_target_position); maybe_calculate_projectile_launch_pitch_angle(projectile, predicted_target_position, time);
if (!projectile_pitch.has_value()) [[unlikely]] if (!projectile_pitch.has_value()) [[unlikely]]
continue; continue;
@@ -130,6 +131,19 @@ namespace omath::projectile_prediction
[[nodiscard]] [[nodiscard]]
std::optional<ArithmeticType> std::optional<ArithmeticType>
maybe_calculate_projectile_launch_pitch_angle(const Projectile<ArithmeticType>& projectile, maybe_calculate_projectile_launch_pitch_angle(const Projectile<ArithmeticType>& projectile,
const Vector3<ArithmeticType>& target_position,
const ArithmeticType time) const noexcept
{
if (projectile.m_air_friction > ArithmeticType{0})
return maybe_calculate_projectile_launch_pitch_angle_with_air_friction(projectile, target_position,
time);
return maybe_calculate_projectile_launch_pitch_angle_without_air_friction(projectile, target_position);
}
[[nodiscard]]
std::optional<ArithmeticType> maybe_calculate_projectile_launch_pitch_angle_without_air_friction(
const Projectile<ArithmeticType>& projectile,
const Vector3<ArithmeticType>& target_position) const noexcept const Vector3<ArithmeticType>& target_position) const noexcept
{ {
const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale; const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale;
@@ -160,6 +174,31 @@ namespace omath::projectile_prediction
return angles::radians_to_degrees(angle); return angles::radians_to_degrees(angle);
} }
[[nodiscard]]
std::optional<ArithmeticType>
maybe_calculate_projectile_launch_pitch_angle_with_air_friction(const Projectile<ArithmeticType>& projectile,
const Vector3<ArithmeticType>& target_position,
const ArithmeticType time) const noexcept
{
if (time <= ArithmeticType{0})
return std::nullopt;
const auto launch_origin = projectile.m_origin + projectile.m_launch_offset;
const auto velocity_factor = calculate_air_friction_velocity_factor(projectile.m_air_friction, time);
if (velocity_factor == ArithmeticType{0}) [[unlikely]]
return std::nullopt;
const auto gravity_acceleration = calculate_projectile_gravity_acceleration(projectile);
const auto gravity_displacement =
gravity_acceleration * ((time - velocity_factor) / projectile.m_air_friction);
const auto required_velocity = (target_position - launch_origin - gravity_displacement) / velocity_factor;
const auto horizontal_speed = EngineTrait::calc_vector_2d_distance(required_velocity);
const auto vertical_speed = EngineTrait::get_vector_height_coordinate(required_velocity);
return angles::radians_to_degrees(std::atan2(vertical_speed, horizontal_speed));
}
[[nodiscard]] [[nodiscard]]
bool is_projectile_reached_target(const Vector3<ArithmeticType>& target_position, bool is_projectile_reached_target(const Vector3<ArithmeticType>& target_position,
const Projectile<ArithmeticType>& projectile, const Projectile<ArithmeticType>& projectile,
@@ -167,10 +206,65 @@ namespace omath::projectile_prediction
{ {
const auto yaw = EngineTrait::calc_direct_yaw_angle( const auto yaw = EngineTrait::calc_direct_yaw_angle(
projectile.m_origin + projectile.m_launch_offset, target_position); projectile.m_origin + projectile.m_launch_offset, target_position);
const auto projectile_position = const auto projectile_position = predict_projectile_position(projectile, pitch, yaw, time);
EngineTrait::predict_projectile_position(projectile, pitch, yaw, time, m_gravity_constant);
return projectile_position.distance_to(target_position) <= m_distance_tolerance; return projectile_position.distance_to(target_position) <= m_distance_tolerance;
} }
[[nodiscard]]
Vector3<ArithmeticType> predict_projectile_position(const Projectile<ArithmeticType>& projectile,
const ArithmeticType pitch, const ArithmeticType yaw,
const ArithmeticType time) const noexcept
{
if (projectile.m_air_friction <= ArithmeticType{0})
return EngineTrait::predict_projectile_position(projectile, pitch, yaw, time, m_gravity_constant);
const auto launch_origin = projectile.m_origin + projectile.m_launch_offset;
const auto launch_velocity = calculate_projectile_launch_velocity(projectile, pitch, yaw);
const auto gravity_acceleration = calculate_projectile_gravity_acceleration(projectile);
const auto velocity_factor = calculate_air_friction_velocity_factor(projectile.m_air_friction, time);
const auto gravity_factor = (time - velocity_factor) / projectile.m_air_friction;
return launch_origin + launch_velocity * velocity_factor + gravity_acceleration * gravity_factor;
}
[[nodiscard]]
static ArithmeticType calculate_air_friction_velocity_factor(const ArithmeticType air_friction,
const ArithmeticType time) noexcept
{
return -std::expm1(-air_friction * time) / air_friction;
}
[[nodiscard]]
Vector3<ArithmeticType> calculate_projectile_launch_velocity(const Projectile<ArithmeticType>& projectile,
const ArithmeticType pitch,
const ArithmeticType yaw) const noexcept
{
const auto launch_origin = projectile.m_origin + projectile.m_launch_offset;
auto projectile_without_air_friction = projectile;
projectile_without_air_friction.m_air_friction = ArithmeticType{0};
return EngineTrait::predict_projectile_position(projectile_without_air_friction, pitch, yaw,
ArithmeticType{1}, ArithmeticType{0})
- launch_origin;
}
[[nodiscard]]
Vector3<ArithmeticType>
calculate_projectile_gravity_acceleration(const Projectile<ArithmeticType>& projectile) const noexcept
{
constexpr ArithmeticType test_time = ArithmeticType{1};
constexpr ArithmeticType acceleration_multiplier = ArithmeticType{2};
auto projectile_without_air_friction = projectile;
projectile_without_air_friction.m_air_friction = ArithmeticType{0};
const auto no_gravity_position = EngineTrait::predict_projectile_position(
projectile_without_air_friction, ArithmeticType{0}, ArithmeticType{0}, test_time,
ArithmeticType{0});
const auto with_gravity_position = EngineTrait::predict_projectile_position(
projectile_without_air_friction, ArithmeticType{0}, ArithmeticType{0}, test_time,
m_gravity_constant);
return (with_gravity_position - no_gravity_position) * acceleration_multiplier;
}
}; };
} // namespace omath::projectile_prediction } // namespace omath::projectile_prediction
@@ -15,5 +15,6 @@ namespace omath::projectile_prediction
Vector3<ArithmeticType> m_launch_offset{}; Vector3<ArithmeticType> m_launch_offset{};
ArithmeticType m_launch_speed{}; ArithmeticType m_launch_speed{};
ArithmeticType m_gravity_scale{}; ArithmeticType m_gravity_scale{};
ArithmeticType m_air_friction{};
}; };
} // namespace omath::projectile_prediction } // namespace omath::projectile_prediction
+1 -62
View File
@@ -2,13 +2,12 @@
// Created by Vladislav on 30.12.2025. // Created by Vladislav on 30.12.2025.
// //
#pragma once #pragma once
#include "pattern_scan.hpp"
#include "section_scan_result.hpp"
#include <cstdint> #include <cstdint>
#include <filesystem> #include <filesystem>
#include <optional> #include <optional>
#include <span> #include <span>
#include <string_view> #include <string_view>
#include "section_scan_result.hpp"
namespace omath namespace omath
{ {
class ElfPatternScanner final class ElfPatternScanner final
@@ -19,74 +18,14 @@ namespace omath
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern, scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = ".text"); const std::string_view& target_section_name = ".text");
template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_loaded_module(module_base_address, target_section_name,
&ElfPatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]] [[nodiscard]]
static std::optional<SectionScanResult> static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern, 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"); const std::string_view& target_section_name = ".text");
template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_file(path_to_file, target_section_name,
&ElfPatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]] [[nodiscard]]
static std::optional<SectionScanResult> static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern, scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
const std::string_view& target_section_name = ".text"); const std::string_view& target_section_name = ".text");
template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_memory_file(file_data, target_section_name,
&ElfPatternScanner::scan_section_for_pattern<Pattern>);
}
private:
using SectionScanFunction = std::optional<std::ptrdiff_t> (*)(std::span<const std::byte>);
template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<std::ptrdiff_t> scan_section_for_pattern(const std::span<const std::byte> section_data)
{
const auto result = PatternScanner::scan_for_pattern<Pattern>(section_data.begin(), section_data.end());
if (result == section_data.end())
return std::nullopt;
return result - section_data.begin();
}
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult> scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name, SectionScanFunction scan_pattern);
}; };
} // namespace omath } // namespace omath
+1 -62
View File
@@ -2,13 +2,12 @@
// Created by Copilot on 04.02.2026. // Created by Copilot on 04.02.2026.
// //
#pragma once #pragma once
#include "pattern_scan.hpp"
#include "section_scan_result.hpp"
#include <cstdint> #include <cstdint>
#include <filesystem> #include <filesystem>
#include <optional> #include <optional>
#include <span> #include <span>
#include <string_view> #include <string_view>
#include "section_scan_result.hpp"
namespace omath namespace omath
{ {
class MachOPatternScanner final class MachOPatternScanner final
@@ -19,74 +18,14 @@ namespace omath
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern, scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = "__text"); const std::string_view& target_section_name = "__text");
template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name = "__text")
{
return scan_for_pattern_in_loaded_module(module_base_address, target_section_name,
&MachOPatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]] [[nodiscard]]
static std::optional<SectionScanResult> static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern, 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"); const std::string_view& target_section_name = "__text");
template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name = "__text")
{
return scan_for_pattern_in_file(path_to_file, target_section_name,
&MachOPatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]] [[nodiscard]]
static std::optional<SectionScanResult> static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern, scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
const std::string_view& target_section_name = "__text"); const std::string_view& target_section_name = "__text");
template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name = "__text")
{
return scan_for_pattern_in_memory_file(file_data, target_section_name,
&MachOPatternScanner::scan_section_for_pattern<Pattern>);
}
private:
using SectionScanFunction = std::optional<std::ptrdiff_t> (*)(std::span<const std::byte>);
template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<std::ptrdiff_t> scan_section_for_pattern(const std::span<const std::byte> section_data)
{
const auto result = PatternScanner::scan_for_pattern<Pattern>(section_data.begin(), section_data.end());
if (result == section_data.end())
return std::nullopt;
return result - section_data.begin();
}
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult> scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name, SectionScanFunction scan_pattern);
}; };
} // namespace omath } // namespace omath
+5 -127
View File
@@ -3,12 +3,9 @@
// //
#pragma once #pragma once
#include <algorithm>
#include <array>
#include <expected> #include <expected>
#include <optional> #include <optional>
#include <span> #include <span>
#include <stdexcept>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@@ -18,8 +15,6 @@ class unit_test_pattern_scan_corner_case_1_Test;
class unit_test_pattern_scan_corner_case_2_Test; class unit_test_pattern_scan_corner_case_2_Test;
class unit_test_pattern_scan_corner_case_3_Test; class unit_test_pattern_scan_corner_case_3_Test;
class unit_test_pattern_scan_corner_case_4_Test; class unit_test_pattern_scan_corner_case_4_Test;
class unit_test_pattern_scan_consteval_read_test_Test;
class unit_test_pattern_scan_consteval_spacing_and_case_Test;
// ReSharper restore CppInconsistentNaming // ReSharper restore CppInconsistentNaming
namespace omath namespace omath
{ {
@@ -34,21 +29,8 @@ namespace omath
friend unit_test_pattern_scan_corner_case_2_Test; friend unit_test_pattern_scan_corner_case_2_Test;
friend unit_test_pattern_scan_corner_case_3_Test; friend unit_test_pattern_scan_corner_case_3_Test;
friend unit_test_pattern_scan_corner_case_4_Test; friend unit_test_pattern_scan_corner_case_4_Test;
friend unit_test_pattern_scan_consteval_read_test_Test;
friend unit_test_pattern_scan_consteval_spacing_and_case_Test;
public: public:
template<std::size_t N>
struct fixed_string final
{
char value[N]{};
constexpr fixed_string(const char (&text)[N])
{
std::ranges::copy(text, value);
}
};
[[nodiscard]] [[nodiscard]]
static std::span<std::byte>::iterator scan_for_pattern(const std::span<std::byte>& range, static std::span<std::byte>::iterator scan_for_pattern(const std::span<std::byte>& range,
const std::string_view& pattern); const std::string_view& pattern);
@@ -67,26 +49,9 @@ namespace omath
if (!parsed_pattern) [[unlikely]] if (!parsed_pattern) [[unlikely]]
return end; return end;
return scan_for_parsed_pattern(begin, end, parsed_pattern.value());
}
template<fixed_string Pattern, class IteratorType>
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
static IteratorType scan_for_pattern(const IteratorType& begin, const IteratorType& end)
{
constexpr auto parsed_pattern = parse_pattern<Pattern>();
return scan_for_parsed_pattern(begin, end, parsed_pattern);
}
private:
template<class IteratorType, class ParsedPattern>
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
static IteratorType scan_for_parsed_pattern(const IteratorType& begin, const IteratorType& end,
const ParsedPattern& parsed_pattern)
{
const auto whole_range_size = static_cast<std::ptrdiff_t>(std::distance(begin, end)); const auto whole_range_size = static_cast<std::ptrdiff_t>(std::distance(begin, end));
const auto pattern_size = static_cast<std::ptrdiff_t>(parsed_pattern.size()); const auto pattern_size = static_cast<std::ptrdiff_t>(parsed_pattern->size());
const std::ptrdiff_t scan_size = whole_range_size - pattern_size; const std::ptrdiff_t scan_size = whole_range_size - pattern_size;
if (scan_size < 0) if (scan_size < 0)
@@ -96,9 +61,9 @@ namespace omath
{ {
bool found = true; bool found = true;
for (std::ptrdiff_t j = 0; j < static_cast<std::ptrdiff_t>(parsed_pattern.size()); j++) for (std::ptrdiff_t j = 0; j < static_cast<std::ptrdiff_t>(parsed_pattern->size()); j++)
{ {
found = parsed_pattern.at(j) == std::nullopt || parsed_pattern.at(j) == *(begin + i + j); found = parsed_pattern->at(j) == std::nullopt || parsed_pattern->at(j) == *(begin + i + j);
if (!found) if (!found)
break; break;
@@ -108,97 +73,10 @@ namespace omath
} }
return end; return end;
} }
private:
[[nodiscard]] [[nodiscard]]
static std::expected<std::vector<std::optional<std::byte>>, PatternScanError> static std::expected<std::vector<std::optional<std::byte>>, PatternScanError>
parse_pattern(const std::string_view& pattern_string); parse_pattern(const std::string_view& pattern_string);
[[nodiscard]]
constexpr static bool is_space(const char c)
{
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
[[nodiscard]]
constexpr static int hex_value(const char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return -1;
}
template<fixed_string Pattern>
[[nodiscard]]
static consteval std::size_t signature_size()
{
std::size_t count = 0;
bool in_token = false;
for (std::size_t i = 0; i + 1 < sizeof(Pattern.value); ++i)
{
if (is_space(Pattern.value[i]))
{
in_token = false;
}
else if (!in_token)
{
++count;
in_token = true;
}
}
return count;
}
template<fixed_string Pattern>
static consteval std::array<std::optional<std::byte>, signature_size<Pattern>()> parse_pattern()
{
std::array<std::optional<std::byte>, signature_size<Pattern>()> result{};
std::size_t out = 0;
std::size_t i = 0;
while (i + 1 < sizeof(Pattern.value))
{
while (i + 1 < sizeof(Pattern.value) && is_space(Pattern.value[i]))
++i;
const std::size_t token_start = i;
while (i + 1 < sizeof(Pattern.value) && !is_space(Pattern.value[i]))
++i;
const std::size_t token_size = i - token_start;
if (token_size == 0)
continue;
// ReSharper disable once CppTooWideScope
const bool is_wildcard = (token_size == 1 || token_size == 2) && Pattern.value[token_start] == '?';
if (is_wildcard)
{
if (token_size == 2 && Pattern.value[token_start + 1] != '?')
throw std::logic_error("invalid wildcard token");
result[out++] = std::nullopt;
continue;
}
if (token_size != 2)
throw std::logic_error("invalid byte token");
const int high = hex_value(Pattern.value[token_start]);
const int low = hex_value(Pattern.value[token_start + 1]);
if (high < 0 || low < 0)
throw std::logic_error("invalid hex byte");
result[out++] = static_cast<std::byte>((high << 4) | low);
}
return result;
}
}; };
} // namespace omath } // namespace omath
+1 -62
View File
@@ -3,13 +3,12 @@
// //
#pragma once #pragma once
#include "pattern_scan.hpp"
#include "section_scan_result.hpp"
#include <cstdint> #include <cstdint>
#include <filesystem> #include <filesystem>
#include <optional> #include <optional>
#include <span> #include <span>
#include <string_view> #include <string_view>
#include "section_scan_result.hpp"
namespace omath namespace omath
{ {
@@ -21,74 +20,14 @@ namespace omath
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern, scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = ".text"); const std::string_view& target_section_name = ".text");
template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_loaded_module(module_base_address, target_section_name,
&PePatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]] [[nodiscard]]
static std::optional<SectionScanResult> static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern, 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"); const std::string_view& target_section_name = ".text");
template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_file(path_to_file, target_section_name,
&PePatternScanner::scan_section_for_pattern<Pattern>);
}
[[nodiscard]] [[nodiscard]]
static std::optional<SectionScanResult> static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern, scan_for_pattern_in_memory_file(std::span<const std::byte> file_data, const std::string_view& pattern,
const std::string_view& target_section_name = ".text"); const std::string_view& target_section_name = ".text");
template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name = ".text")
{
return scan_for_pattern_in_memory_file(file_data, target_section_name,
&PePatternScanner::scan_section_for_pattern<Pattern>);
}
private:
using SectionScanFunction = std::optional<std::ptrdiff_t> (*)(std::span<const std::byte>);
template<PatternScanner::fixed_string Pattern>
[[nodiscard]]
static std::optional<std::ptrdiff_t> scan_section_for_pattern(const std::span<const std::byte> section_data)
{
const auto result = PatternScanner::scan_for_pattern<Pattern>(section_data.begin(), section_data.end());
if (result == section_data.end())
return std::nullopt;
return result - section_data.begin();
}
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult> scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
SectionScanFunction scan_pattern);
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_memory_file(std::span<const std::byte> file_data,
const std::string_view& target_section_name, SectionScanFunction scan_pattern);
}; };
} // namespace omath } // namespace omath
+1 -1
View File
@@ -1,5 +1,5 @@
// //
// Created by Orange on 6/3/2026. // Created by Codex on 6/3/2026.
// //
#include "omath/engines/rage_engine/formulas.hpp" #include "omath/engines/rage_engine/formulas.hpp"
@@ -1,5 +1,5 @@
// //
// Created by Orange on 6/3/2026. // Created by Codex on 6/3/2026.
// //
#include "omath/engines/rage_engine/traits/camera_trait.hpp" #include "omath/engines/rage_engine/traits/camera_trait.hpp"
+1 -1
View File
@@ -660,7 +660,7 @@ namespace omath::hooks
LRESULT __stdcall HooksManager::wnd_proc_detour(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param) LRESULT __stdcall HooksManager::wnd_proc_detour(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param)
{ {
const auto& mgr = get(); auto& mgr = get();
callback_ptr<wnd_proc_callback> cb; callback_ptr<wnd_proc_callback> cb;
WNDPROC original; WNDPROC original;
{ {
+14 -15
View File
@@ -98,11 +98,10 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay& EntityOverlay::add_right_label(const Color& color, const float offset, EntityOverlay& EntityOverlay::add_right_label(const Color& color, const float offset, const bool outlined,
const widget::Outlined outlined,
const std::string_view& text) const std::string_view& text)
{ {
if (outlined == widget::Outlined::On) if (outlined)
draw_outlined_text(m_text_cursor_right + Vector2<float>{offset, 0.f}, color, text); draw_outlined_text(m_text_cursor_right + Vector2<float>{offset, 0.f}, color, text);
else else
m_renderer->add_text(m_text_cursor_right + Vector2<float>{offset, 0.f}, color, text.data()); m_renderer->add_text(m_text_cursor_right + Vector2<float>{offset, 0.f}, color, text.data());
@@ -111,12 +110,12 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay& EntityOverlay::add_top_label(const Color& color, const float offset, const widget::Outlined outlined, EntityOverlay& EntityOverlay::add_top_label(const Color& color, const float offset, const bool outlined,
const std::string_view text) const std::string_view text)
{ {
m_text_cursor_top.y -= m_renderer->calc_text_size(text.data()).y; m_text_cursor_top.y -= m_renderer->calc_text_size(text.data()).y;
if (outlined == widget::Outlined::On) if (outlined)
draw_outlined_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text); draw_outlined_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text);
else else
m_renderer->add_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text.data()); m_renderer->add_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text.data());
@@ -393,12 +392,12 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay& EntityOverlay::add_bottom_label(const Color& color, const float offset, const widget::Outlined outlined, EntityOverlay& EntityOverlay::add_bottom_label(const Color& color, const float offset, const bool outlined,
const std::string_view text) const std::string_view text)
{ {
const auto text_size = m_renderer->calc_text_size(text); const auto text_size = m_renderer->calc_text_size(text);
if (outlined == widget::Outlined::On) if (outlined)
draw_outlined_text(m_text_cursor_bottom + Vector2<float>{0.f, offset}, color, text); draw_outlined_text(m_text_cursor_bottom + Vector2<float>{0.f, offset}, color, text);
else else
m_renderer->add_text(m_text_cursor_bottom + Vector2<float>{0.f, offset}, color, text); m_renderer->add_text(m_text_cursor_bottom + Vector2<float>{0.f, offset}, color, text);
@@ -408,13 +407,13 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay& EntityOverlay::add_left_label(const Color& color, const float offset, const widget::Outlined outlined, EntityOverlay& EntityOverlay::add_left_label(const Color& color, const float offset, const bool outlined,
const std::string_view& text) const std::string_view& text)
{ {
const auto text_size = m_renderer->calc_text_size(text); const auto text_size = m_renderer->calc_text_size(text);
const auto pos = m_text_cursor_left + Vector2<float>{-(offset + text_size.x), 0.f}; const auto pos = m_text_cursor_left + Vector2<float>{-(offset + text_size.x), 0.f};
if (outlined == widget::Outlined::On) if (outlined)
draw_outlined_text(pos, color, text); draw_outlined_text(pos, color, text);
else else
m_renderer->add_text(pos, color, text); m_renderer->add_text(pos, color, text);
@@ -424,7 +423,7 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay& EntityOverlay::add_centered_bottom_label(const Color& color, const float offset, const widget::Outlined outlined, EntityOverlay& EntityOverlay::add_centered_bottom_label(const Color& color, const float offset, const bool outlined,
const std::string_view& text) const std::string_view& text)
{ {
const auto text_size = m_renderer->calc_text_size(text); const auto text_size = m_renderer->calc_text_size(text);
@@ -432,7 +431,7 @@ namespace omath::hud
m_canvas.bottom_left_corner.x + (m_canvas.bottom_right_corner.x - m_canvas.bottom_left_corner.x) / 2.f; m_canvas.bottom_left_corner.x + (m_canvas.bottom_right_corner.x - m_canvas.bottom_left_corner.x) / 2.f;
const auto pos = Vector2<float>{box_center_x - text_size.x / 2.f, m_text_cursor_bottom.y + offset}; const auto pos = Vector2<float>{box_center_x - text_size.x / 2.f, m_text_cursor_bottom.y + offset};
if (outlined == widget::Outlined::On) if (outlined)
draw_outlined_text(pos, color, text); draw_outlined_text(pos, color, text);
else else
m_renderer->add_text(pos, color, text); m_renderer->add_text(pos, color, text);
@@ -442,7 +441,7 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay& EntityOverlay::add_centered_top_label(const Color& color, const float offset, const widget::Outlined outlined, EntityOverlay& EntityOverlay::add_centered_top_label(const Color& color, const float offset, const bool outlined,
const std::string_view& text) const std::string_view& text)
{ {
const auto text_size = m_renderer->calc_text_size(text); const auto text_size = m_renderer->calc_text_size(text);
@@ -452,7 +451,7 @@ namespace omath::hud
m_text_cursor_top.y -= text_size.y; m_text_cursor_top.y -= text_size.y;
const auto pos = Vector2<float>{box_center_x - text_size.x / 2.f, m_text_cursor_top.y - offset}; const auto pos = Vector2<float>{box_center_x - text_size.x / 2.f, m_text_cursor_top.y - offset};
if (outlined == widget::Outlined::On) if (outlined)
draw_outlined_text(pos, color, text); draw_outlined_text(pos, color, text);
else else
m_renderer->add_text(pos, color, text); m_renderer->add_text(pos, color, text);
@@ -460,9 +459,9 @@ namespace omath::hud
return *this; return *this;
} }
EntityOverlay::EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom, const float aspect, EntityOverlay::EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom,
const std::shared_ptr<HudRendererInterface>& renderer) const std::shared_ptr<HudRendererInterface>& renderer)
: m_canvas(top, bottom, aspect), m_text_cursor_right(m_canvas.top_right_corner), : m_canvas(top, bottom), m_text_cursor_right(m_canvas.top_right_corner),
m_text_cursor_top(m_canvas.top_left_corner), m_text_cursor_bottom(m_canvas.bottom_left_corner), m_text_cursor_top(m_canvas.top_left_corner), m_text_cursor_bottom(m_canvas.bottom_left_corner),
m_text_cursor_left(m_canvas.top_left_corner), m_renderer(renderer) m_text_cursor_left(m_canvas.top_left_corner), m_renderer(renderer)
{ {
+7 -13
View File
@@ -159,19 +159,13 @@ namespace
return {1.f, 1.f, 1.f, 1.f}; return {1.f, 1.f, 1.f, 1.f};
} }
[[nodiscard]]
omath::hud::widget::Outlined outlined_from_bool(const bool outlined)
{
return outlined ? omath::hud::widget::Outlined::On : omath::hud::widget::Outlined::Off;
}
[[nodiscard]] [[nodiscard]]
omath::hud::EntityOverlay make_overlay(const omath::Vector2<float>& top, const omath::Vector2<float>& bottom, omath::hud::EntityOverlay make_overlay(const omath::Vector2<float>& top, const omath::Vector2<float>& bottom,
const std::shared_ptr<LuaHudRenderer>& renderer) const std::shared_ptr<LuaHudRenderer>& renderer)
{ {
if (!renderer) if (!renderer)
throw std::invalid_argument("hud renderer must not be nil"); throw std::invalid_argument("hud renderer must not be nil");
return {top, bottom, 4.f, renderer}; return {top, bottom, renderer};
} }
[[nodiscard]] [[nodiscard]]
@@ -380,37 +374,37 @@ namespace omath::lua
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset, [](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay& const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{ {
return overlay.add_right_label(color, offset, outlined_from_bool(outlined), text); return overlay.add_right_label(color, offset, outlined, text);
}, },
"add_left_label", "add_left_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset, [](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay& const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{ {
return overlay.add_left_label(color, offset, outlined_from_bool(outlined), text); return overlay.add_left_label(color, offset, outlined, text);
}, },
"add_top_label", "add_top_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset, [](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay& const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{ {
return overlay.add_top_label(color, offset, outlined_from_bool(outlined), text); return overlay.add_top_label(color, offset, outlined, text);
}, },
"add_bottom_label", "add_bottom_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset, [](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay& const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{ {
return overlay.add_bottom_label(color, offset, outlined_from_bool(outlined), text); return overlay.add_bottom_label(color, offset, outlined, text);
}, },
"add_centered_top_label", "add_centered_top_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset, [](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay& const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{ {
return overlay.add_centered_top_label(color, offset, outlined_from_bool(outlined), text); return overlay.add_centered_top_label(color, offset, outlined, text);
}, },
"add_centered_bottom_label", "add_centered_bottom_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset, [](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay& const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{ {
return overlay.add_centered_bottom_label(color, offset, outlined_from_bool(outlined), text); return overlay.add_centered_bottom_label(color, offset, outlined, text);
}, },
"add_right_space_vertical", &omath::hud::EntityOverlay::add_right_space_vertical, "add_right_space_vertical", &omath::hud::EntityOverlay::add_right_space_vertical,
+8 -92
View File
@@ -304,8 +304,8 @@ namespace
elf_headers); elf_headers);
} }
template<class FileHeader, class SectionHeader, class ScanPattern> template<class FileHeader, class SectionHeader>
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const ScanPattern scan_pattern, 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 std::string_view target_section_name)
{ {
const auto* file_header = reinterpret_cast<const FileHeader*>(base); const auto* file_header = reinterpret_cast<const FileHeader*>(base);
@@ -336,14 +336,13 @@ namespace
continue; continue;
const auto* section_begin = base + static_cast<std::size_t>(section->sh_addr); const auto* section_begin = base + static_cast<std::size_t>(section->sh_addr);
const auto scan_range = const auto* section_end = section_begin + static_cast<std::size_t>(section->sh_size);
std::span<const std::byte>{section_begin, static_cast<std::size_t>(section->sh_size)};
const auto target_offset = scan_pattern(scan_range); const auto scan_result = omath::PatternScanner::scan_for_pattern(section_begin, section_end, pattern);
if (!target_offset.has_value()) if (scan_result == section_end)
return std::nullopt; return std::nullopt;
return reinterpret_cast<std::uintptr_t>(section_begin + target_offset.value()); return reinterpret_cast<std::uintptr_t>(scan_result);
} }
return std::nullopt; return std::nullopt;
@@ -375,58 +374,15 @@ namespace omath
if (!arch.has_value()) [[unlikely]] if (!arch.has_value()) [[unlikely]]
return std::nullopt; return std::nullopt;
const auto scan_pattern =
[&pattern](const std::span<const std::byte> section_data) -> std::optional<std::ptrdiff_t>
{
const auto scan_result =
PatternScanner::scan_for_pattern(section_data.begin(), section_data.end(), pattern);
if (scan_result == section_data.end())
return std::nullopt;
return scan_result - section_data.begin();
};
if (arch == FileArch::x64) if (arch == FileArch::x64)
return scan_in_module_impl<Elf64Ehdr, Elf64Shdr>(static_cast<const std::byte*>(module_base_address), return scan_in_module_impl<Elf64Ehdr, Elf64Shdr>(static_cast<const std::byte*>(module_base_address),
scan_pattern, target_section_name); pattern, target_section_name);
if (arch == FileArch::x32) if (arch == FileArch::x32)
return scan_in_module_impl<Elf32Ehdr, Elf32Shdr>(static_cast<const std::byte*>(module_base_address), return scan_in_module_impl<Elf32Ehdr, Elf32Shdr>(static_cast<const std::byte*>(module_base_address),
scan_pattern, target_section_name); pattern, target_section_name);
std::unreachable(); std::unreachable();
} }
std::optional<std::uintptr_t>
ElfPatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
if (module_base_address == nullptr) [[unlikely]]
return std::nullopt;
const auto* base = static_cast<const std::byte*>(module_base_address);
constexpr std::string_view valid_elf_signature = "\x7F"
"ELF";
if (std::string_view{reinterpret_cast<const char*>(base), valid_elf_signature.size()} != valid_elf_signature)
return std::nullopt;
const auto ei_class_value = static_cast<uint8_t>(base[ei_class]);
const auto arch = ei_class_value == elfclass64 ? FileArch::x64
: ei_class_value == elfclass32 ? FileArch::x32
: std::optional<FileArch>{};
if (!arch.has_value()) [[unlikely]]
return std::nullopt;
if (arch == FileArch::x64)
return scan_in_module_impl<Elf64Ehdr, Elf64Shdr>(base, scan_pattern, target_section_name);
if (arch == FileArch::x32)
return scan_in_module_impl<Elf32Ehdr, Elf32Shdr>(base, scan_pattern, target_section_name);
std::unreachable();
}
std::optional<SectionScanResult> std::optional<SectionScanResult>
ElfPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file, ElfPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& pattern, const std::string_view& pattern,
@@ -449,26 +405,6 @@ namespace omath
.target_offset = offset}; .target_offset = offset};
} }
std::optional<SectionScanResult>
ElfPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto section = get_elf_section_by_name(path_to_file, target_section_name);
if (!section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset = scan_pattern(std::span<const std::byte>{section->data.data(), section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = section->virtual_base_addr,
.raw_base_addr = section->raw_base_addr,
.target_offset = target_offset.value()};
}
std::optional<SectionScanResult> std::optional<SectionScanResult>
ElfPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data, ElfPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& pattern, const std::string_view& pattern,
@@ -491,24 +427,4 @@ namespace omath
.raw_base_addr = section->raw_base_addr, .raw_base_addr = section->raw_base_addr,
.target_offset = offset}; .target_offset = offset};
} }
std::optional<SectionScanResult>
ElfPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto section = get_elf_section_by_name_from_memory(file_data, target_section_name);
if (!section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset = scan_pattern(std::span<const std::byte>{section->data.data(), section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = section->virtual_base_addr,
.raw_base_addr = section->raw_base_addr,
.target_offset = target_offset.value()};
}
} // namespace omath } // namespace omath
+10 -89
View File
@@ -316,8 +316,8 @@ namespace
data, section_name); data, section_name);
if (magic == mh_magic_32 || magic == mh_cigam_32) if (magic == mh_magic_32 || magic == mh_cigam_32)
return extract_section_from_memory_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>( return extract_section_from_memory_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(data,
data, section_name); section_name);
return std::nullopt; return std::nullopt;
} }
@@ -344,9 +344,8 @@ namespace
return extract_section_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(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, template<typename HeaderType, typename SegmentType, typename SectionType, std::uint32_t segment_cmd>
class ScanPattern> std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const std::string_view pattern,
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const ScanPattern scan_pattern,
const std::string_view target_section_name) const std::string_view target_section_name)
{ {
const auto* header = reinterpret_cast<const HeaderType*>(base); const auto* header = reinterpret_cast<const HeaderType*>(base);
@@ -375,13 +374,12 @@ namespace
continue; continue;
} }
const auto* section_begin = base + static_cast<std::size_t>(section->addr); const auto* section_begin = base + static_cast<std::size_t>(section->addr);
const auto scan_range = const auto* section_end = section_begin + static_cast<std::size_t>(section->size);
std::span<const std::byte>{section_begin, static_cast<std::size_t>(section->size)};
const auto target_offset = scan_pattern(scan_range); const auto scan_result = omath::PatternScanner::scan_for_pattern(section_begin, section_end, pattern);
if (target_offset.has_value()) if (scan_result != section_end)
return reinterpret_cast<std::uintptr_t>(section_begin + target_offset.value()); return reinterpret_cast<std::uintptr_t>(scan_result);
} }
} }
@@ -406,48 +404,12 @@ namespace omath
std::uint32_t magic{}; std::uint32_t magic{};
std::memcpy(&magic, base, sizeof(magic)); std::memcpy(&magic, base, sizeof(magic));
const auto scan_pattern =
[&pattern](const std::span<const std::byte> section_data) -> std::optional<std::ptrdiff_t>
{
const auto scan_result =
PatternScanner::scan_for_pattern(section_data.begin(), section_data.end(), pattern);
if (scan_result == section_data.end())
return std::nullopt;
return scan_result - section_data.begin();
};
if (magic == mh_magic_64 || magic == mh_cigam_64) if (magic == mh_magic_64 || magic == mh_cigam_64)
return scan_in_module_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(base, scan_pattern, return scan_in_module_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(base, pattern,
target_section_name); target_section_name);
if (magic == mh_magic_32 || magic == mh_cigam_32) if (magic == mh_magic_32 || magic == mh_cigam_32)
return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, scan_pattern, return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, pattern,
target_section_name);
return std::nullopt;
}
std::optional<std::uintptr_t>
MachOPatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
if (module_base_address == nullptr) [[unlikely]]
return std::nullopt;
const auto* base = static_cast<const std::byte*>(module_base_address);
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, scan_pattern,
target_section_name);
if (magic == mh_magic_32 || magic == mh_cigam_32)
return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, scan_pattern,
target_section_name); target_section_name);
return std::nullopt; return std::nullopt;
@@ -476,27 +438,6 @@ namespace omath
.target_offset = offset}; .target_offset = offset};
} }
std::optional<SectionScanResult>
MachOPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
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 target_offset =
scan_pattern(std::span<const std::byte>{macho_section->data.data(), macho_section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = macho_section->virtual_base_addr,
.raw_base_addr = macho_section->raw_base_addr,
.target_offset = target_offset.value()};
}
std::optional<SectionScanResult> std::optional<SectionScanResult>
MachOPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data, MachOPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& pattern, const std::string_view& pattern,
@@ -519,24 +460,4 @@ namespace omath
.raw_base_addr = section->raw_base_addr, .raw_base_addr = section->raw_base_addr,
.target_offset = offset}; .target_offset = offset};
} }
std::optional<SectionScanResult>
MachOPatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto section = get_macho_section_by_name_from_memory(file_data, target_section_name);
if (!section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset = scan_pattern(std::span<const std::byte>{section->data.data(), section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = section->virtual_base_addr,
.raw_base_addr = section->raw_base_addr,
.target_offset = target_offset.value()};
}
} // namespace omath } // namespace omath
+5 -109
View File
@@ -234,11 +234,7 @@ namespace
constexpr bool invalid_nt_header_file(const NtHeaderVariant& variant) constexpr bool invalid_nt_header_file(const NtHeaderVariant& variant)
{ {
constexpr std::uint32_t nt_hdr_magic = 0x4550; constexpr std::uint32_t nt_hdr_magic = 0x4550;
return std::visit( return std::visit([&nt_hdr_magic](const auto& header) -> bool { return header.signature != nt_hdr_magic; },
[&nt_hdr_magic](const auto& header) -> bool
{
return header.signature != nt_hdr_magic;
},
variant); variant);
} }
@@ -266,7 +262,8 @@ namespace
if (nt_off + sizeof(ImageNtHeaders<NtArchitecture::x32_bit>) > data.size()) if (nt_off + sizeof(ImageNtHeaders<NtArchitecture::x32_bit>) > data.size())
return std::nullopt; return std::nullopt;
const auto* x86_hdrs = reinterpret_cast<const ImageNtHeaders<NtArchitecture::x32_bit>*>(data.data() + nt_off); const auto* x86_hdrs =
reinterpret_cast<const ImageNtHeaders<NtArchitecture::x32_bit>*>(data.data() + nt_off);
NtHeaderVariant nt_headers; NtHeaderVariant nt_headers;
if (x86_hdrs->optional_header.magic == opt_hdr32_magic) if (x86_hdrs->optional_header.magic == opt_hdr32_magic)
@@ -287,8 +284,8 @@ namespace
[&data, &section_name, nt_off](const auto& concrete_headers) -> std::optional<ExtractedSection> [&data, &section_name, nt_off](const auto& concrete_headers) -> std::optional<ExtractedSection>
{ {
constexpr std::size_t sig_size = sizeof(concrete_headers.signature); constexpr std::size_t sig_size = sizeof(concrete_headers.signature);
const auto section_table_off = const auto section_table_off = nt_off + sig_size + sizeof(FileHeader)
nt_off + sig_size + sizeof(FileHeader) + concrete_headers.file_header.size_optional_header; + concrete_headers.file_header.size_optional_header;
for (std::size_t i = 0; i < concrete_headers.file_header.num_sections; ++i) for (std::size_t i = 0; i < concrete_headers.file_header.num_sections; ++i)
{ {
@@ -438,65 +435,6 @@ namespace omath
}, },
nt_header_variant.value()); nt_header_variant.value());
} }
std::optional<std::uintptr_t>
PePatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto base_address = reinterpret_cast<std::uintptr_t>(module_base_address);
const auto* base_bytes = static_cast<const std::byte*>(module_base_address);
if (!base_address)
return std::nullopt;
const auto* dos_header = static_cast<const DosHeader*>(module_base_address);
if (invalid_dos_header_file(*dos_header)) [[unlikely]]
return std::nullopt;
const auto nt_header_variant = get_nt_header_from_loaded_module(module_base_address);
if (!nt_header_variant) [[unlikely]]
return std::nullopt;
return std::visit(
[base_bytes, base_address, lfanew = dos_header->e_lfanew, &target_section_name,
scan_pattern](const auto& nt_header) -> std::optional<std::uintptr_t>
{
constexpr std::size_t signature_size = sizeof(nt_header.signature);
const auto section_table_off = static_cast<std::size_t>(lfanew) + signature_size
+ sizeof(FileHeader) + nt_header.file_header.size_optional_header;
const auto* section_table = reinterpret_cast<const SectionHeader*>(base_bytes + section_table_off);
for (std::size_t i = 0; i < nt_header.file_header.num_sections; ++i)
{
const auto* section = section_table + i;
if (std::string_view{section->name, sizeof(section->name)} != target_section_name
|| section->size_raw_data == 0)
continue;
const auto section_size = section->virtual_size != 0
? static_cast<std::size_t>(section->virtual_size)
: static_cast<std::size_t>(section->size_raw_data);
const auto* section_begin =
reinterpret_cast<const std::byte*>(base_address + section->virtual_address);
const auto scan_range = std::span<const std::byte>{section_begin, section_size};
const auto target_offset = scan_pattern(scan_range);
if (target_offset.has_value())
return reinterpret_cast<std::uintptr_t>(section_begin + target_offset.value());
}
return std::nullopt;
},
nt_header_variant.value());
}
std::optional<SectionScanResult> std::optional<SectionScanResult>
PePatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file, PePatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& pattern, const std::string_view& pattern,
@@ -519,27 +457,6 @@ namespace omath
.target_offset = offset}; .target_offset = offset};
} }
std::optional<SectionScanResult>
PePatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto pe_section = extract_section_from_pe_file(path_to_file, target_section_name);
if (!pe_section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset =
scan_pattern(std::span<const std::byte>{pe_section->data.data(), pe_section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
.raw_base_addr = pe_section->raw_base_addr,
.target_offset = target_offset.value()};
}
std::optional<SectionScanResult> std::optional<SectionScanResult>
PePatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data, PePatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& pattern, const std::string_view& pattern,
@@ -562,25 +479,4 @@ namespace omath
.raw_base_addr = pe_section->raw_base_addr, .raw_base_addr = pe_section->raw_base_addr,
.target_offset = offset}; .target_offset = offset};
} }
std::optional<SectionScanResult>
PePatternScanner::scan_for_pattern_in_memory_file(const std::span<const std::byte> file_data,
const std::string_view& target_section_name,
const SectionScanFunction scan_pattern)
{
const auto pe_section = extract_section_from_pe_memory(file_data, target_section_name);
if (!pe_section.has_value()) [[unlikely]]
return std::nullopt;
const auto target_offset =
scan_pattern(std::span<const std::byte>{pe_section->data.data(), pe_section->data.size()});
if (!target_offset.has_value())
return std::nullopt;
return SectionScanResult{.virtual_base_addr = pe_section->virtual_base_addr,
.raw_base_addr = pe_section->raw_base_addr,
.target_offset = target_offset.value()};
}
} // namespace omath } // namespace omath
+1 -1
View File
@@ -1,5 +1,5 @@
// //
// Created by Orange on 6/3/2026. // Created by Codex on 6/3/2026.
// //
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <omath/engines/rage_engine/camera.hpp> #include <omath/engines/rage_engine/camera.hpp>
+8 -53
View File
@@ -35,22 +35,13 @@ static std::vector<std::byte> make_elf64_with_text_section(const std::vector<std
std::vector<std::byte> buf(total_size, std::byte{0}); std::vector<std::byte> buf(total_size, std::byte{0});
auto w8 = [&](std::size_t off, std::uint8_t v) auto w8 = [&](std::size_t off, std::uint8_t v) { buf[off] = std::byte{v}; };
{
buf[off] = std::byte{v};
};
auto w16 = [&](std::size_t off, std::uint16_t v) auto w16 = [&](std::size_t off, std::uint16_t v)
{ { std::memcpy(buf.data() + off, &v, 2); };
std::memcpy(buf.data() + off, &v, 2);
};
auto w32 = [&](std::size_t off, std::uint32_t v) auto w32 = [&](std::size_t off, std::uint32_t v)
{ { std::memcpy(buf.data() + off, &v, 4); };
std::memcpy(buf.data() + off, &v, 4);
};
auto w64 = [&](std::size_t off, std::uint64_t v) auto w64 = [&](std::size_t off, std::uint64_t v)
{ { std::memcpy(buf.data() + off, &v, 8); };
std::memcpy(buf.data() + off, &v, 8);
};
// --- ELF64 file header --- // --- ELF64 file header ---
// e_ident // e_ident
@@ -160,18 +151,6 @@ TEST(unit_test_elf_pattern_scan_memory, finds_pattern_with_wildcard)
EXPECT_EQ(result->target_offset, 0); EXPECT_EQ(result->target_offset, 0);
} }
TEST(unit_test_elf_pattern_scan_memory, consteval_finds_pattern_with_wildcard)
{
const std::vector<std::uint8_t> code = {0x00, 0xDE, 0xAD, 0xBE, 0xEF};
const auto buf = make_elf64_with_text_section(code);
const auto result =
ElfPatternScanner::scan_for_pattern_in_memory_file<"DE ?? BE EF">(std::span<const std::byte>{buf}, ".text");
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 1);
}
TEST(unit_test_elf_pattern_scan_memory, pattern_not_found_returns_nullopt) TEST(unit_test_elf_pattern_scan_memory, pattern_not_found_returns_nullopt)
{ {
const std::vector<std::uint8_t> code = {0x01, 0x02, 0x03, 0x04}; const std::vector<std::uint8_t> code = {0x01, 0x02, 0x03, 0x04};
@@ -203,8 +182,8 @@ TEST(unit_test_elf_pattern_scan_memory, missing_section_returns_nullopt)
const std::vector<std::uint8_t> code = {0x90, 0x90}; const std::vector<std::uint8_t> code = {0x90, 0x90};
const auto buf = make_elf64_with_text_section(code); const auto buf = make_elf64_with_text_section(code);
const auto result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf}, "90 90", const auto result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf},
".nonexistent"); "90 90", ".nonexistent");
EXPECT_FALSE(result.has_value()); EXPECT_FALSE(result.has_value());
} }
@@ -222,8 +201,8 @@ TEST(unit_test_elf_pattern_scan_memory, matches_file_scan)
} }
const auto file_result = ElfPatternScanner::scan_for_pattern_in_file(tmp_path, "48 89 E5 DE AD", ".text"); const auto file_result = ElfPatternScanner::scan_for_pattern_in_file(tmp_path, "48 89 E5 DE AD", ".text");
const auto mem_result = ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf}, const auto mem_result =
"48 89 E5 DE AD", ".text"); ElfPatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf}, "48 89 E5 DE AD", ".text");
std::filesystem::remove(tmp_path); std::filesystem::remove(tmp_path);
@@ -233,27 +212,3 @@ TEST(unit_test_elf_pattern_scan_memory, matches_file_scan)
EXPECT_EQ(file_result->raw_base_addr, mem_result->raw_base_addr); EXPECT_EQ(file_result->raw_base_addr, mem_result->raw_base_addr);
EXPECT_EQ(file_result->target_offset, mem_result->target_offset); EXPECT_EQ(file_result->target_offset, mem_result->target_offset);
} }
TEST(unit_test_elf_pattern_scan_memory, consteval_file_scan_finds_pattern)
{
const std::vector<std::uint8_t> code = {0x48, 0x89, 0xE5, 0xDE, 0xAD};
const auto buf = make_elf64_with_text_section(code);
const auto tmp_path = std::filesystem::temp_directory_path() / "omath_elf_consteval_test.elf";
{
std::ofstream out(tmp_path, std::ios::binary);
out.write(reinterpret_cast<const char*>(buf.data()), static_cast<std::streamsize>(buf.size()));
}
const auto result = ElfPatternScanner::scan_for_pattern_in_file<"48 ?? E5">(tmp_path, ".text");
std::filesystem::remove(tmp_path);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_elf_pattern_scan_memory, consteval_loaded_module_null_returns_nullopt)
{
const auto result = ElfPatternScanner::scan_for_pattern_in_loaded_module<"DE AD">(nullptr);
EXPECT_FALSE(result.has_value());
}
@@ -1,7 +1,5 @@
// Tests for MachOPatternScanner::scan_for_pattern_in_memory_file // Tests for MachOPatternScanner::scan_for_pattern_in_memory_file
#include <cstring> #include <cstring>
#include <filesystem>
#include <fstream>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <omath/utility/macho_pattern_scan.hpp> #include <omath/utility/macho_pattern_scan.hpp>
#include <span> #include <span>
@@ -39,18 +37,13 @@ static std::vector<std::byte> make_macho64_with_text_section(const std::vector<s
constexpr std::size_t total_size = text_raw_off + text_raw_size; constexpr std::size_t total_size = text_raw_off + text_raw_size;
constexpr std::uint64_t text_vmaddr = 0x1000ULL; constexpr std::uint64_t text_vmaddr = 0x1000ULL;
constexpr std::uint32_t cmd_size = static_cast<std::uint32_t>(seg_size + sect_hdr_size); // segment + 1 section constexpr std::uint32_t cmd_size =
static_cast<std::uint32_t>(seg_size + sect_hdr_size); // segment + 1 section
std::vector<std::byte> buf(total_size, std::byte{0}); std::vector<std::byte> buf(total_size, std::byte{0});
auto w32 = [&](std::size_t off, std::uint32_t v) auto w32 = [&](std::size_t off, std::uint32_t v) { std::memcpy(buf.data() + off, &v, 4); };
{ auto w64 = [&](std::size_t off, std::uint64_t v) { std::memcpy(buf.data() + off, &v, 8); };
std::memcpy(buf.data() + off, &v, 4);
};
auto w64 = [&](std::size_t off, std::uint64_t v)
{
std::memcpy(buf.data() + off, &v, 8);
};
// MachHeader64 // MachHeader64
w32(0, mh_magic_64); w32(0, mh_magic_64);
@@ -112,18 +105,6 @@ TEST(unit_test_macho_memory_file_scan, finds_pattern_with_wildcard)
EXPECT_EQ(result->target_offset, 0); EXPECT_EQ(result->target_offset, 0);
} }
TEST(unit_test_macho_memory_file_scan, consteval_finds_pattern_with_wildcard)
{
const std::vector<std::uint8_t> code = {0x00, 0xDE, 0xAD, 0xBE, 0xEF};
const auto buf = make_macho64_with_text_section(code);
const auto result =
MachOPatternScanner::scan_for_pattern_in_memory_file<"DE ?? BE EF">(std::span<const std::byte>{buf});
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 1);
}
TEST(unit_test_macho_memory_file_scan, pattern_not_found_returns_nullopt) TEST(unit_test_macho_memory_file_scan, pattern_not_found_returns_nullopt)
{ {
const std::vector<std::uint8_t> code = {0x01, 0x02, 0x03}; const std::vector<std::uint8_t> code = {0x01, 0x02, 0x03};
@@ -162,27 +143,3 @@ TEST(unit_test_macho_memory_file_scan, raw_addr_and_virtual_addr_correct)
EXPECT_EQ(result->raw_base_addr, expected_raw_off); EXPECT_EQ(result->raw_base_addr, expected_raw_off);
EXPECT_EQ(result->virtual_base_addr, 0x1000u); EXPECT_EQ(result->virtual_base_addr, 0x1000u);
} }
TEST(unit_test_macho_memory_file_scan, consteval_file_scan_finds_pattern)
{
const std::vector<std::uint8_t> code = {0x48, 0x89, 0xE5, 0xDE, 0xAD};
const auto buf = make_macho64_with_text_section(code);
const auto tmp_path = std::filesystem::temp_directory_path() / "omath_macho_consteval_test.bin";
{
std::ofstream out(tmp_path, std::ios::binary);
out.write(reinterpret_cast<const char*>(buf.data()), static_cast<std::streamsize>(buf.size()));
}
const auto result = MachOPatternScanner::scan_for_pattern_in_file<"48 ?? E5">(tmp_path);
std::filesystem::remove(tmp_path);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_macho_memory_file_scan, consteval_loaded_module_null_returns_nullopt)
{
const auto result = MachOPatternScanner::scan_for_pattern_in_loaded_module<"DE AD">(nullptr);
EXPECT_FALSE(result.has_value());
}
+1 -35
View File
@@ -4,8 +4,8 @@
#include "omath/utility/pe_pattern_scan.hpp" #include "omath/utility/pe_pattern_scan.hpp"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include <omath/utility/pattern_scan.hpp> #include <omath/utility/pattern_scan.hpp>
#include <print>
#include <source_location> #include <source_location>
#include <print>
TEST(unit_test_pattern_scan, read_test) TEST(unit_test_pattern_scan, read_test)
{ {
const auto result = omath::PatternScanner::parse_pattern("FF ? ?? E9"); const auto result = omath::PatternScanner::parse_pattern("FF ? ?? E9");
@@ -51,37 +51,3 @@ TEST(unit_test_pattern_scan, corner_case_4)
const auto result = omath::PatternScanner::parse_pattern("X ? ?? E9 "); const auto result = omath::PatternScanner::parse_pattern("X ? ?? E9 ");
EXPECT_FALSE(result.has_value()); EXPECT_FALSE(result.has_value());
} }
TEST(unit_test_pattern_scan, consteval_read_test)
{
constexpr auto result = omath::PatternScanner::parse_pattern<"FF ? ?? E9">();
static_assert(result.size() == 4);
static_assert(result[0] == static_cast<std::byte>(0xFF));
static_assert(result[1] == std::nullopt);
static_assert(result[2] == std::nullopt);
static_assert(result[3] == static_cast<std::byte>(0xE9));
EXPECT_EQ(result[0], static_cast<std::byte>(0xFF));
EXPECT_EQ(result[1], std::nullopt);
EXPECT_EQ(result[2], std::nullopt);
EXPECT_EQ(result[3], static_cast<std::byte>(0xE9));
}
TEST(unit_test_pattern_scan, consteval_spacing_and_case)
{
constexpr auto result = omath::PatternScanner::parse_pattern<" \tde\nAD\r?? ? ef ">();
static_assert(result.size() == 5);
static_assert(result[0] == static_cast<std::byte>(0xDE));
static_assert(result[1] == static_cast<std::byte>(0xAD));
static_assert(result[2] == std::nullopt);
static_assert(result[3] == std::nullopt);
static_assert(result[4] == static_cast<std::byte>(0xEF));
EXPECT_EQ(result[0], static_cast<std::byte>(0xDE));
EXPECT_EQ(result[1], static_cast<std::byte>(0xAD));
EXPECT_EQ(result[2], std::nullopt);
EXPECT_EQ(result[3], std::nullopt);
EXPECT_EQ(result[4], static_cast<std::byte>(0xEF));
}
@@ -14,16 +14,6 @@ TEST(unit_test_pattern_scan_extra, IteratorScanFound)
EXPECT_EQ(std::distance(buf.begin(), it), 0); EXPECT_EQ(std::distance(buf.begin(), it), 0);
} }
TEST(unit_test_pattern_scan_extra, ConstevalIteratorScan)
{
std::vector<std::byte> buf = {static_cast<std::byte>(0x00), static_cast<std::byte>(0xDE),
static_cast<std::byte>(0xAD), static_cast<std::byte>(0xBE),
static_cast<std::byte>(0xEF), static_cast<std::byte>(0x11)};
const auto it = PatternScanner::scan_for_pattern<"DE ?? BE EF">(buf.begin(), buf.end());
EXPECT_NE(it, buf.end());
EXPECT_EQ(std::distance(buf.begin(), it), 1);
}
TEST(unit_test_pattern_scan_extra, IteratorScanNotFound) TEST(unit_test_pattern_scan_extra, IteratorScanNotFound)
{ {
std::vector<std::byte> buf = {static_cast<std::byte>(0x00), static_cast<std::byte>(0x11), std::vector<std::byte> buf = {static_cast<std::byte>(0x00), static_cast<std::byte>(0x11),
@@ -1,7 +1,5 @@
// Tests for PePatternScanner::scan_for_pattern_in_memory_file // Tests for PePatternScanner::scan_for_pattern_in_memory_file
#include <cstring> #include <cstring>
#include <filesystem>
#include <fstream>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <omath/utility/pe_pattern_scan.hpp> #include <omath/utility/pe_pattern_scan.hpp>
#include <span> #include <span>
@@ -12,7 +10,8 @@ using namespace omath;
// Reuse the fake-module builder from unit_test_pe_pattern_scan_loaded.cpp but // Reuse the fake-module builder from unit_test_pe_pattern_scan_loaded.cpp but
// lay out the buffer as a raw PE *file* (ptr_raw_data != virtual_address). // lay out the buffer as a raw PE *file* (ptr_raw_data != virtual_address).
static std::vector<std::byte> make_fake_pe_file(std::uint32_t virtual_address, std::uint32_t ptr_raw_data, static std::vector<std::byte> make_fake_pe_file(std::uint32_t virtual_address, std::uint32_t ptr_raw_data,
std::uint32_t section_size, const std::vector<std::uint8_t>& code_bytes) std::uint32_t section_size,
const std::vector<std::uint8_t>& code_bytes)
{ {
constexpr std::uint32_t e_lfanew = 0x80; constexpr std::uint32_t e_lfanew = 0x80;
constexpr std::uint32_t nt_sig = 0x4550; constexpr std::uint32_t nt_sig = 0x4550;
@@ -25,18 +24,9 @@ static std::vector<std::byte> make_fake_pe_file(std::uint32_t virtual_address, s
const std::uint32_t total_size = ptr_raw_data + section_size + 0x100; const std::uint32_t total_size = ptr_raw_data + section_size + 0x100;
std::vector<std::byte> buf(total_size, std::byte{0}); std::vector<std::byte> buf(total_size, std::byte{0});
auto w16 = [&](std::size_t off, std::uint16_t v) auto w16 = [&](std::size_t off, std::uint16_t v) { std::memcpy(buf.data() + off, &v, 2); };
{ auto w32 = [&](std::size_t off, std::uint32_t v) { std::memcpy(buf.data() + off, &v, 4); };
std::memcpy(buf.data() + off, &v, 2); auto w64 = [&](std::size_t off, std::uint64_t v) { std::memcpy(buf.data() + off, &v, 8); };
};
auto w32 = [&](std::size_t off, std::uint32_t v)
{
std::memcpy(buf.data() + off, &v, 4);
};
auto w64 = [&](std::size_t off, std::uint64_t v)
{
std::memcpy(buf.data() + off, &v, 8);
};
// DOS header // DOS header
w16(0x00, 0x5A4D); w16(0x00, 0x5A4D);
@@ -97,24 +87,13 @@ TEST(unit_test_pe_memory_file_scan, finds_pattern_with_wildcard)
EXPECT_EQ(result->target_offset, 0); EXPECT_EQ(result->target_offset, 0);
} }
TEST(unit_test_pe_memory_file_scan, consteval_finds_pattern_with_wildcard)
{
const std::vector<std::uint8_t> code = {0x00, 0xDE, 0xAD, 0xBE, 0xEF};
const auto buf = make_fake_pe_file(0x2000, 0x600, static_cast<std::uint32_t>(code.size()), code);
const auto result =
PePatternScanner::scan_for_pattern_in_memory_file<"DE ?? BE EF">(std::span<const std::byte>{buf});
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 1);
}
TEST(unit_test_pe_memory_file_scan, pattern_not_found_returns_nullopt) TEST(unit_test_pe_memory_file_scan, pattern_not_found_returns_nullopt)
{ {
const std::vector<std::uint8_t> code = {0x01, 0x02, 0x03}; const std::vector<std::uint8_t> code = {0x01, 0x02, 0x03};
const auto buf = make_fake_pe_file(0x1000, 0x400, static_cast<std::uint32_t>(code.size()), code); const auto buf = make_fake_pe_file(0x1000, 0x400, static_cast<std::uint32_t>(code.size()), code);
const auto result = PePatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf}, "AA BB CC"); const auto result =
PePatternScanner::scan_for_pattern_in_memory_file(std::span<const std::byte>{buf}, "AA BB CC");
EXPECT_FALSE(result.has_value()); EXPECT_FALSE(result.has_value());
} }
@@ -147,27 +126,3 @@ TEST(unit_test_pe_memory_file_scan, raw_addr_differs_from_virtual_address)
// virtual_base_addr = virtual_address + image_base (image_base = 0) // virtual_base_addr = virtual_address + image_base (image_base = 0)
EXPECT_EQ(result->virtual_base_addr, 0x3000u); EXPECT_EQ(result->virtual_base_addr, 0x3000u);
} }
TEST(unit_test_pe_memory_file_scan, consteval_file_scan_finds_pattern)
{
const std::vector<std::uint8_t> code = {0x48, 0x89, 0xE5, 0xDE, 0xAD};
const auto buf = make_fake_pe_file(0x2000, 0x600, static_cast<std::uint32_t>(code.size()), code);
const auto tmp_path = std::filesystem::temp_directory_path() / "omath_pe_consteval_test.exe";
{
std::ofstream out(tmp_path, std::ios::binary);
out.write(reinterpret_cast<const char*>(buf.data()), static_cast<std::streamsize>(buf.size()));
}
const auto result = PePatternScanner::scan_for_pattern_in_file<"48 ?? E5">(tmp_path);
std::filesystem::remove(tmp_path);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->target_offset, 0);
}
TEST(unit_test_pe_memory_file_scan, consteval_loaded_module_null_returns_nullopt)
{
const auto result = PePatternScanner::scan_for_pattern_in_loaded_module<"DE AD">(nullptr);
EXPECT_FALSE(result.has_value());
}
+28
View File
@@ -283,6 +283,34 @@ TEST(ProjectileSimulation, HitsNegativeYawTarget_WithOffset)
expect_projectile_hits_target(proj, target, 400, 1.f / 1000.f, 50, 5.f, 10.f); expect_projectile_hits_target(proj, target, 400, 1.f / 1000.f, 50, 5.f, 10.f);
} }
TEST(ProjectileSimulation, HitsStaticTarget_WithAirFriction)
{
constexpr Target target{
.m_origin = {75, 0, 0}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
constexpr Projectile proj = {
.m_origin = {0, 0, 0}, .m_launch_speed = 100.f, .m_gravity_scale = 0.f, .m_air_friction = 0.5f};
const Engine engine(0.f, 1.f / 1000.f, 10.f, 0.1f);
const auto aim_angles = engine.maybe_calculate_aim_angles(proj, target);
ASSERT_TRUE(aim_angles.has_value());
EXPECT_NEAR(aim_angles->pitch, 0.f, 0.1f);
EXPECT_NEAR(aim_angles->yaw, 0.f, 0.1f);
}
TEST(ProjectileSimulation, AirFrictionLimitsMaximumReach)
{
constexpr Target target{
.m_origin = {150, 0, 0}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
constexpr Projectile proj = {
.m_origin = {0, 0, 0}, .m_launch_speed = 100.f, .m_gravity_scale = 0.f, .m_air_friction = 1.f};
const Engine engine(0.f, 1.f / 1000.f, 10.f, 0.1f);
EXPECT_FALSE(engine.maybe_calculate_aim_point(proj, target).has_value());
EXPECT_FALSE(engine.maybe_calculate_aim_angles(proj, target).has_value());
}
TEST(UnitTestPrediction, AimAnglesReturnsNulloptWhenNoSolution) TEST(UnitTestPrediction, AimAnglesReturnsNulloptWhenNoSolution)
{ {
constexpr Target target{ constexpr Target target{
-92
View File
@@ -9,7 +9,6 @@
#include <omath/engines/frostbite_engine/camera.hpp> #include <omath/engines/frostbite_engine/camera.hpp>
#include <omath/engines/iw_engine/camera.hpp> #include <omath/engines/iw_engine/camera.hpp>
#include <omath/engines/opengl_engine/camera.hpp> #include <omath/engines/opengl_engine/camera.hpp>
#include <omath/engines/rage_engine/camera.hpp>
#include <omath/engines/source_engine/camera.hpp> #include <omath/engines/source_engine/camera.hpp>
#include <omath/engines/unreal_engine/camera.hpp> #include <omath/engines/unreal_engine/camera.hpp>
#include <omath/linear_algebra/triangle.hpp> #include <omath/linear_algebra/triangle.hpp>
@@ -17,49 +16,6 @@
#include <print> #include <print>
#include <random> #include <random>
template<class NumericType>
static NumericType random_visible_component(std::mt19937& gen, std::uniform_real_distribution<NumericType>& dist)
{
auto value = dist(gen);
if (std::abs(value) < static_cast<NumericType>(50))
value += value < NumericType{} ? -static_cast<NumericType>(50) : static_cast<NumericType>(50);
return value;
}
template<class CameraType, class ViewAnglesType, class NumericType>
static void verify_random_look_at_targets_project_to_screen_center(const omath::Vector3<NumericType>& origin,
const ViewAnglesType& view_angles,
const omath::projection::ViewPort& view_port,
const NumericType near_plane,
const NumericType far_plane, const unsigned int seed)
{
std::mt19937 gen(seed);
std::uniform_real_distribution<NumericType> dist(static_cast<NumericType>(-500), static_cast<NumericType>(500));
constexpr auto fov = omath::projection::FieldOfView::from_degrees(75.f);
auto cam = CameraType(origin, view_angles, view_port, fov, near_plane, far_plane);
const auto screen_center_x = static_cast<NumericType>(view_port.m_width / 2.f);
const auto screen_center_y = static_cast<NumericType>(view_port.m_height / 2.f);
for (int i = 0; i < 100; i++)
{
const auto target = origin
+ omath::Vector3<NumericType>{
random_visible_component(gen, dist),
random_visible_component(gen, dist),
random_visible_component(gen, dist),
};
cam.look_at(target);
const auto projected = cam.world_to_screen(target);
ASSERT_TRUE(projected.has_value()) << "iteration: " << i;
EXPECT_NEAR(projected->x, screen_center_x, static_cast<NumericType>(0.1));
EXPECT_NEAR(projected->y, screen_center_y, static_cast<NumericType>(0.1));
}
}
TEST(UnitTestProjection, Projection) TEST(UnitTestProjection, Projection)
{ {
constexpr auto fov = omath::Angle<float, 0.f, 180.f, omath::AngleFlags::Clamped>::from_degrees(90.f); constexpr auto fov = omath::Angle<float, 0.f, 180.f, omath::AngleFlags::Clamped>::from_degrees(90.f);
@@ -1159,54 +1115,6 @@ TEST(UnitTestProjection, LookAt_ForwardVectorPointsAtTarget)
EXPECT_NEAR(fwd.z, 0.f, 1e-4f); EXPECT_NEAR(fwd.z, 0.f, 1e-4f);
} }
TEST(UnitTestProjection, SourceEngine_LookAtRandomWorldPointsProjectToScreenCenter)
{
verify_random_look_at_targets_project_to_screen_center<omath::source_engine::Camera>(
{10.f, -20.f, 30.f}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, 0.01f, 5000.f, 101);
}
TEST(UnitTestProjection, UnityEngine_LookAtRandomWorldPointsProjectToScreenCenter)
{
verify_random_look_at_targets_project_to_screen_center<omath::unity_engine::Camera>(
{-15.f, 25.f, -35.f}, omath::unity_engine::ViewAngles{}, {1280.f, 720.f}, 0.03f, 5000.f, 102);
}
TEST(UnitTestProjection, UnrealEngine_LookAtRandomWorldPointsProjectToScreenCenter)
{
verify_random_look_at_targets_project_to_screen_center<omath::unreal_engine::Camera>(
{100.0, -50.0, 25.0}, omath::unreal_engine::ViewAngles{}, {1920.f, 1080.f}, 0.01, 5000.0, 103);
}
TEST(UnitTestProjection, FrostbiteEngine_LookAtRandomWorldPointsProjectToScreenCenter)
{
verify_random_look_at_targets_project_to_screen_center<omath::frostbite_engine::Camera>(
{-4.f, 2.f, 8.f}, omath::frostbite_engine::ViewAngles{}, {1600.f, 900.f}, 0.01f, 5000.f, 104);
}
TEST(UnitTestProjection, IWEngine_LookAtRandomWorldPointsProjectToScreenCenter)
{
verify_random_look_at_targets_project_to_screen_center<omath::iw_engine::Camera>(
{40.f, -60.f, 20.f}, omath::iw_engine::ViewAngles{}, {1920.f, 1080.f}, 0.01f, 5000.f, 105);
}
TEST(UnitTestProjection, OpenGLEngine_LookAtRandomWorldPointsProjectToScreenCenter)
{
verify_random_look_at_targets_project_to_screen_center<omath::opengl_engine::Camera>(
{-30.f, 70.f, 15.f}, omath::opengl_engine::ViewAngles{}, {1920.f, 1080.f}, 0.01f, 5000.f, 106);
}
TEST(UnitTestProjection, CryEngine_LookAtRandomWorldPointsProjectToScreenCenter)
{
verify_random_look_at_targets_project_to_screen_center<omath::cry_engine::Camera>(
{5.f, 15.f, -25.f}, omath::cry_engine::ViewAngles{}, {1280.f, 720.f}, 0.01f, 5000.f, 107);
}
TEST(UnitTestProjection, RageEngine_LookAtRandomWorldPointsProjectToScreenCenter)
{
verify_random_look_at_targets_project_to_screen_center<omath::rage_engine::Camera>(
{-5.f, 45.f, 10.f}, omath::rage_engine::ViewAngles{}, {1280.f, 720.f}, 0.01f, 5000.f, 108);
}
// ---- is_culled_by_frustum (triangle) ---- // ---- is_culled_by_frustum (triangle) ----
TEST(UnitTestProjection, TriangleInsideFrustumNotCulled) TEST(UnitTestProjection, TriangleInsideFrustumNotCulled)