Skip to content

Commit 15510ed

Browse files
authored
Merge pull request #205 from orange-cpp/feature/unreal_engine
Feature/unreal engine
2 parents 3a72baf + 6f6dba0 commit 15510ed

17 files changed

Lines changed: 2853 additions & 6 deletions

File tree

README.md

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div align = center>
22

3-
![banner](docs/images/logos/omath_logo_macro.png)
3+
![banner](https://blog.libomath.org/wp-content/uploads/2026/08/omath_logo.png)
44

55
![GitHub License](https://img.shields.io/github/license/orange-cpp/omath)
66
![GitHub contributors](https://img.shields.io/github/contributors/orange-cpp/omath)
@@ -11,12 +11,15 @@
1111
[![Vcpkg package](https://repology.org/badge/version-for-repo/vcpkg/orange-math.svg)](https://repology.org/project/orange-math/versions)
1212
![Conan Center](https://img.shields.io/conan/v/omath)
1313
![GitHub forks](https://img.shields.io/github/forks/orange-cpp/omath)
14+
![GitHub Repo stars](https://img.shields.io/github/stars/orange-cpp/omath)
15+
![GitHub watchers](https://img.shields.io/github/watchers/orange-cpp/omath)
16+
![GitHub Release](https://img.shields.io/github/v/release/orange-cpp/omath)
1417
[![discord badge](https://dcbadge.limes.pink/api/server/https://discord.gg/eDgdaWbqwZ?style=flat)](https://discord.gg/eDgdaWbqwZ)
1518
[![telegram badge](https://img.shields.io/badge/Telegram-2CA5E0?style=flat-squeare&logo=telegram&logoColor=white)](https://t.me/orangennotes)
1619

17-
OMath is a 100% independent, constexpr template blazingly fast math/physics/games/mods/cheats development framework that doesn't have legacy C++ code.
20+
omath is a 100% independent, constexpr template blazingly fast math/physics/games/mods/cheats development framework that doesn't have legacy C++ code.
1821

19-
It provides the latest features, is highly customizable, has all for cheat development, DirectX/OpenGL/Vulkan support, premade support for different game engines, much more constexpr stuff than in other libraries and more...
22+
It provides the latest features, is highly customizable, has all for cheat development, DirectX/OpenGL hooking, premade support for different game engines, much more constexpr stuff than in other libraries and more...
2023
<br>
2124
<br>
2225

@@ -62,6 +65,46 @@ if (auto screen = camera.world_to_screen(world_position)) {
6265

6366
**[See more examples and tutorials][TUTORIALS]**
6467

68+
## Reverse Engineering Toolkit
69+
70+
`omath::rev_eng` gives every reversed structure typed, self-documenting field access instead of raw `reinterpret_cast` and magic offsets. The same class works against a process you injected into (`InternalReverseEngineeredObject`, plain memory access) or a target read from outside (`ExternalReverseEngineeredObject`, backed by any trait you write around `ReadProcessMemory`/`process_vm_readv`/a driver):
71+
72+
```cpp
73+
#include <omath/linear_algebra/vector3.hpp>
74+
#include <omath/rev_eng/external_rev_object.hpp>
75+
76+
using omath::Vector3;
77+
using omath::rev_eng::ExternalReverseEngineeredObject;
78+
79+
// Any trait with read_memory<T>/write_memory<T> works - ReadProcessMemory, process_vm_readv, a DMA device, ...
80+
struct RpmTrait {
81+
template<class T>
82+
static T read_memory(std::uintptr_t address) {
83+
T value{};
84+
ReadProcessMemory(g_handle, reinterpret_cast<LPCVOID>(address), &value, sizeof(T), nullptr);
85+
return value;
86+
}
87+
};
88+
89+
class Player final : public ExternalReverseEngineeredObject<RpmTrait> {
90+
public:
91+
using ExternalReverseEngineeredObject::ExternalReverseEngineeredObject;
92+
93+
[[nodiscard]] Vector3<float> origin() const { return get_by_offset<Vector3<float>>(0x134); }
94+
[[nodiscard]] int health() const { return get_by_offset<int>(0x140); }
95+
};
96+
97+
Player local_player{local_player_address};
98+
auto pos = local_player.origin();
99+
```
100+
101+
See [external_rev_object.md](docs/rev_eng/external_rev_object.md) and [internal_rev_object.md](docs/rev_eng/internal_rev_object.md) for the full API. On top of that foundation OMath ships ready-made helpers for the harder, engine-specific parts of reverse engineering:
102+
103+
- **Byte pattern scanning** with wildcards across **PE, ELF and Mach-O** - files, loaded modules, or a memory dump, works even against Wine apps.
104+
- **Function hooking** for **DirectX 9/11/12** and **OpenGL** via `omath::hooks::HooksManager`, for drawing an overlay into someone else's render loop.
105+
- **Unreal Engine name resolution** - `get_actor_name`/`get_object_by_index` walk `GNames`/`GObjects` across **UE 2.5 through UE 5** (pointer-array, chunked-array and `FNamePool` layouts) to turn a raw `UObject*` into its class and instance name. See [actor_name.md](docs/engines/unreal_engine/actor_name.md) and [object_array.md](docs/engines/unreal_engine/object_array.md).
106+
- A full, runnable example that ties all three together - process attach, `GObjects` walk, `FName` resolution, and a live GLFW/OpenGL/ImGui overlay with `world_to_screen`/`world_to_radar` - lives in [`examples/example_kf1_dumper`](examples/example_kf1_dumper) and [`examples/example_kf1_overlay`](examples/example_kf1_overlay).
107+
65108
# Features
66109
- **Efficiency**: Optimized for performance, ensuring quick computations using AVX2.
67110
- **Versatility**: Includes a wide array of mathematical functions and algorithms.
@@ -73,7 +116,8 @@ if (auto screen = camera.world_to_screen(world_position)) {
73116
- **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types!
74117
- **Engine support**: Supports coordinate systems of **Source, Rage, Unity, Unreal, Frostbite, IWEngine, CryEngine and canonical OpenGL**.
75118
- **Cross platform**: Supports Windows, MacOS and Linux.
76-
- **Algorithms**: Has ability to scan for byte pattern with wildcards in ELF/Mach-O/PE files/modules, binary slices, works even with Wine apps.
119+
- **Reverse Engineering**: Typed offset access over internal or external process memory, byte pattern scanning with wildcards in ELF/Mach-O/PE files/modules and loaded processes (works even with Wine apps), and per-engine helpers to resolve names and walk the global object table - see the [Reverse Engineering Toolkit](#reverse-engineering-toolkit) section above.
120+
- **Hooking**: `omath::hooks::HooksManager` hooks DirectX 9/11/12 and OpenGL's present/swap calls for you, so you only write the draw code.
77121
- **Scripting**: Supports to make scripts in Lua out of box.
78122
- **Handy**: Allow to design wall hacks in modern jetpack compose like way.
79123
- **Battle tested**: It's already used by some big players on the market like wraith.su and bluedream.ltd
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# `omath::unreal_engine::get_actor_name` — resolving FName through GNames
2+
3+
> Header: `omath/engines/unreal_engine/actor_name.hpp`
4+
> Namespace: `omath::unreal_engine`
5+
> Purpose: turn an actor (or any other `UObject`) pointer into its name, on UE 2.5, UE 3, UE 4 and UE 5
6+
7+
---
8+
9+
## Summary
10+
11+
```cpp
12+
template<rev_eng::MemoryReadTrait MemoryTrait = rev_eng::InternalMemoryTrait>
13+
std::expected<std::string, ActorNameError>
14+
get_actor_name(std::uintptr_t actor, std::uintptr_t g_names, const NameLayout& layout);
15+
```
16+
17+
The function reads `UObject::Name`, resolves that `FName` index inside the name container `g_names` points at, and
18+
transcodes the entry to UTF-8. Just like `UObject::GetName()` it appends `_<Number - 1>` when the `FName` carries a
19+
non zero instance number.
20+
21+
Every engine generation stores names differently, so the whole traversal is driven by a `NameLayout`:
22+
23+
| Generation | `NamePoolKind` | Lookup |
24+
| --- | --- | --- |
25+
| UE 2.5, UE 3 | `POINTER_ARRAY` | `TArray<FNameEntry*>`, `entry = Data[index]` |
26+
| UE 4.0 - 4.22 | `CHUNKED_ARRAY` | `entry = Chunks[index / elements_per_chunk][index % elements_per_chunk]` |
27+
| UE 4.23+, UE 5 | `BLOCK_POOL` | `entry = Blocks[index >> block_offset_bits] + (index & mask) * block_entry_stride` |
28+
29+
`POINTER_ARRAY` and `CHUNKED_ARRAY` entries are null terminated, `BLOCK_POOL` entries carry their length inside
30+
`FNameEntryHeader`.
31+
32+
---
33+
34+
## Internal usage
35+
36+
```cpp
37+
#include <omath/engines/unreal_engine/actor_name.hpp>
38+
39+
using omath::unreal_engine::get_actor_name;
40+
using omath::unreal_engine::NameLayout;
41+
42+
const auto name = get_actor_name(actor_address, g_names_address, NameLayout::ue5());
43+
44+
if (name.has_value())
45+
std::println("{}", *name);
46+
```
47+
48+
`MemoryTrait` defaults to `omath::rev_eng::InternalMemoryTrait`, which dereferences the address directly, so the call
49+
above works from inside the game process.
50+
51+
---
52+
53+
## External usage
54+
55+
Any type with a `read_memory<Type>(std::uintptr_t)` static template satisfies `omath::rev_eng::MemoryReadTrait`, which
56+
makes the same code work over `ReadProcessMemory`, `process_vm_readv`, a driver or a DMA device:
57+
58+
```cpp
59+
struct RpmTrait
60+
{
61+
template<class Type>
62+
static Type read_memory(const std::uintptr_t address)
63+
{
64+
Type value{};
65+
ReadProcessMemory(g_process, reinterpret_cast<LPCVOID>(address), &value, sizeof(Type), nullptr);
66+
67+
return value;
68+
}
69+
};
70+
71+
auto layout = NameLayout::ue3();
72+
layout.pointer_size = sizeof(std::uint32_t); // 64 bit tool reading a 32 bit game
73+
74+
const auto name = get_actor_name<RpmTrait>(actor_address, g_names_address, layout);
75+
```
76+
77+
---
78+
79+
## Presets
80+
81+
```cpp
82+
NameLayout::ue2_5(); // UE 2.5, x86, UTF-16 entries, FName without Number
83+
NameLayout::ue3(); // UE 3, x86
84+
NameLayout::ue4_legacy(); // UE 4.0 - 4.22, x64
85+
NameLayout::ue4(); // UE 4.23+, x64, FNamePool
86+
NameLayout::ue5(); // same layout as ue4()
87+
```
88+
89+
Presets hold the values that are typical for their generation, **they are not guaranteed for your game**. The UE 2.5
90+
one was derived from Killing Floor 1 build 1065, see `examples/example_kf1_dumper`. Dump the target and override the
91+
fields that differ:
92+
93+
```cpp
94+
auto layout = NameLayout::ue5();
95+
layout.object_name_offset = 0x20; // UObject::NamePrivate moved
96+
layout.pool_data_offset = 0x40; // FNameEntryAllocator::Blocks on Linux, FRWLock is a pthread_rwlock_t there
97+
layout.header_length_shift = 1; // game built WITH_CASE_PRESERVING_NAME
98+
```
99+
100+
`g_names` must be the address of the name container itself. When your signature resolves to a pointer variable holding
101+
it, dereference that pointer before passing it in.
102+
103+
---
104+
105+
## Errors
106+
107+
| `ActorNameError` | Meaning |
108+
| --- | --- |
109+
| `NULL_POINTER` | `actor` or `g_names` is zero |
110+
| `INVALID_NAME_INDEX` | the chunk, block or entry pointer behind the `FName` index is null |
111+
| `EMPTY_NAME` | the entry decoded to an empty string, usually a wrong `entry_text_offset` |
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# `omath::unreal_engine` object array — walking GObjects
2+
3+
> Header: `omath/engines/unreal_engine/object_array.hpp`
4+
> Namespace: `omath::unreal_engine`
5+
> Purpose: enumerate the global `UObject` array on UE 2.5, UE 3, UE 4 and UE 5
6+
7+
---
8+
9+
## Summary
10+
11+
```cpp
12+
template<rev_eng::MemoryReadTrait MemoryTrait = rev_eng::InternalMemoryTrait>
13+
std::expected<std::uint32_t, ObjectArrayError>
14+
get_object_count(std::uintptr_t g_objects, const ObjectArrayLayout& layout);
15+
16+
template<rev_eng::MemoryReadTrait MemoryTrait = rev_eng::InternalMemoryTrait>
17+
std::expected<std::uintptr_t, ObjectArrayError>
18+
get_object_by_index(std::uintptr_t g_objects, std::uint32_t index, const ObjectArrayLayout& layout);
19+
```
20+
21+
`get_object_by_index` bounds checks the index against the stored count, walks the array and returns the `UObject`
22+
address, which is exactly what [`get_actor_name`](actor_name.md) expects.
23+
24+
`g_objects` is the address of `UObject::GObjObjects` on UE 2.5 and UE 3, and the address of `GUObjectArray` on UE 4
25+
and UE 5.
26+
27+
| Generation | `ObjectArrayKind` | Container |
28+
| --- | --- | --- |
29+
| UE 2.5, UE 3 | `FLAT` | `TArray<UObject*>` |
30+
| UE 4 with a fixed array | `FLAT` | `FFixedUObjectArray`, elements are `FUObjectItem` |
31+
| UE 4.11+, UE 5 | `CHUNKED` | `FChunkedFixedUObjectArray`, 64K `FUObjectItem` per chunk |
32+
| UE 4.0 - 4.10 | `CHUNKED` with `indirect_chunk_table = false` | chunk table inlined into the array object |
33+
34+
---
35+
36+
## Iterating
37+
38+
```cpp
39+
#include <omath/engines/unreal_engine/actor_name.hpp>
40+
#include <omath/engines/unreal_engine/object_array.hpp>
41+
42+
using namespace omath::unreal_engine;
43+
44+
void dump_objects(const std::uintptr_t g_objects, const std::uintptr_t g_names)
45+
{
46+
const auto count = get_object_count(g_objects, ObjectArrayLayout::ue5());
47+
48+
if (!count.has_value())
49+
return;
50+
51+
for (std::uint32_t i = 0; i < *count; ++i)
52+
{
53+
const auto object = get_object_by_index(g_objects, i, ObjectArrayLayout::ue5());
54+
55+
// Slots of destroyed objects are empty, skipping them is normal
56+
if (!object.has_value())
57+
continue;
58+
59+
const auto name = get_actor_name(*object, g_names, NameLayout::ue5());
60+
61+
if (name.has_value())
62+
std::println("[{}] {:#x} {}", i, *object, *name);
63+
}
64+
}
65+
```
66+
67+
---
68+
69+
## Presets
70+
71+
```cpp
72+
ObjectArrayLayout::ue2_5(); // UE 2.5, x86, TArray<UObject*>
73+
ObjectArrayLayout::ue3(); // UE 3, x86, same TArray<UObject*>
74+
ObjectArrayLayout::ue4_fixed(); // UE 4 with FFixedUObjectArray, x64
75+
ObjectArrayLayout::ue4(); // UE 4.11+, x64, FChunkedFixedUObjectArray
76+
ObjectArrayLayout::ue5(); // same layout as ue4()
77+
```
78+
79+
Presets hold the values that are typical for their generation, **they are not guaranteed for your game**. Common
80+
overrides:
81+
82+
```cpp
83+
auto layout = ObjectArrayLayout::ue5();
84+
layout.item_stride = 0x20; // FUObjectItem grew in this build
85+
layout.count_offset = 0x2C; // FUObjectArray header differs
86+
87+
auto early = ObjectArrayLayout::ue4(); // UE 4.0 - 4.10
88+
early.indirect_chunk_table = false; // chunk table is inlined, not behind a pointer
89+
early.elements_per_chunk = 16384;
90+
early.item_stride = sizeof(std::uintptr_t); // chunks hold bare UObject*, no FUObjectItem
91+
92+
auto ue3 = ObjectArrayLayout::ue3(); // 64 bit tool reading a 32 bit game
93+
ue3.pointer_size = sizeof(std::uint32_t);
94+
ue3.item_stride = sizeof(std::uint32_t);
95+
```
96+
97+
---
98+
99+
## Errors
100+
101+
| `ObjectArrayError` | Meaning |
102+
| --- | --- |
103+
| `NULL_POINTER` | `g_objects` is zero, or the array/chunk pointer behind it is zero |
104+
| `INDEX_OUT_OF_RANGE` | index is not below the stored element count |
105+
| `NULL_OBJECT` | the slot is empty, its object was destroyed |

docs/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ OMath provides built-in support for multiple game engines with proper coordinate
118118
- [Pred Engine Trait](engines/unreal_engine/pred_engine_trait.md)
119119
- [Constants](engines/unreal_engine/constants.md)
120120
- [Formulas](engines/unreal_engine/formulas.md)
121+
- [Actor Name](engines/unreal_engine/actor_name.md)
122+
- [Object Array](engines/unreal_engine/object_array.md)
121123
122124
**Frostbite Engine** (EA - Battlefield, etc.)
123125
- [Camera Trait](engines/frostbite/camera_trait.md)

examples/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ add_subdirectory(example_proj_mat_builder)
44
add_subdirectory(example_signature_scan)
55
add_subdirectory(example_hud)
66

7+
if(WIN32)
8+
add_subdirectory(example_kf1_dumper)
9+
10+
find_package(imgui CONFIG QUIET)
11+
if(imgui_FOUND)
12+
add_subdirectory(example_kf1_overlay)
13+
else()
14+
message(STATUS "[omath] imgui not found - kf1 overlay example skipped")
15+
endif()
16+
endif()
17+
718
if(OMATH_ENABLE_HOOKING AND WIN32)
819
# Requires imgui with dx9-binding, dx11-binding, dx12-binding, opengl3-binding, win32-binding.
920
# Install via: vcpkg install imgui[dx9-binding,dx11-binding,dx12-binding,opengl3-binding,win32-binding]
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
project(example_kf1_dumper)
2+
3+
add_executable(${PROJECT_NAME} example_kf1_dumper.cpp)
4+
set_target_properties(
5+
${PROJECT_NAME}
6+
PROPERTIES CXX_STANDARD 23
7+
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
8+
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
9+
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
10+
# kf1_common/kf1_target.hpp is shared with example_kf1_overlay
11+
target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
12+
13+
target_link_libraries(${PROJECT_NAME} PRIVATE omath::omath)

0 commit comments

Comments
 (0)