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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 140 additions & 11 deletions include/omath/rev_eng/internal_rev_object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,43 @@
//

#pragma once
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <string_view>

#ifdef _WIN32
#include "omath/utility/pe_pattern_scan.hpp"
#include <windows.h>
#elif defined(__APPLE__)
#include "omath/utility/macho_pattern_scan.hpp"
#include <mach-o/dyld.h>
#else
#include "omath/utility/elf_pattern_scan.hpp"
#include <link.h>
#endif

namespace omath::rev_eng
{
template<std::size_t N>
struct FixedString final
{
char data[N]{};
// ReSharper disable once CppNonExplicitConvertingConstructor
constexpr FixedString(const char (&str)[N]) noexcept // NOLINT(*-explicit-constructor)
{
for (std::size_t i = 0; i < N; ++i)
data[i] = str[i];
}
// ReSharper disable once CppNonExplicitConversionOperator
constexpr operator std::string_view() const noexcept // NOLINT(*-explicit-constructor)
{
return {data, N - 1};
}
};
template<std::size_t N>
FixedString(const char (&)[N]) -> FixedString<N>;

class InternalReverseEngineeredObject
{
protected:
Expand All @@ -23,26 +55,123 @@ namespace omath::rev_eng
return *reinterpret_cast<Type*>(reinterpret_cast<std::uintptr_t>(this) + offset);
}

template<std::size_t id, class ReturnType>
ReturnType call_virtual_method(auto... arg_list)
template<class ReturnType>
ReturnType call_method(const void* ptr, auto... arg_list)
{
#ifdef _MSC_VER
using VirtualMethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
using MethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
#else
using VirtualMethodType = ReturnType (*)(void*, decltype(arg_list)...);
using MethodType = ReturnType (*)(void*, decltype(arg_list)...);
#endif
return (*reinterpret_cast<VirtualMethodType**>(this))[id](this, arg_list...);
return reinterpret_cast<MethodType>(const_cast<void*>(ptr))(this, arg_list...);
}
template<std::size_t id, class ReturnType>
ReturnType call_virtual_method(auto... arg_list) const
template<class ReturnType>
ReturnType call_method(const void* ptr, auto... arg_list) const
{
#ifdef _MSC_VER
using VirtualMethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
using MethodType = ReturnType(__thiscall*)(const void*, decltype(arg_list)...);
#else
using MethodType = ReturnType (*)(const void*, decltype(arg_list)...);
#endif
return reinterpret_cast<MethodType>(const_cast<void*>(ptr))(this, arg_list...);
}

template<FixedString ModuleName, FixedString Pattern, class ReturnType>
ReturnType call_method(auto... arg_list)
{
static const auto* address = resolve_pattern(ModuleName, Pattern);
return call_method<ReturnType>(address, arg_list...);
}

template<FixedString ModuleName, FixedString Pattern, class ReturnType>
ReturnType call_method(auto... arg_list) const
{
static const auto* address = resolve_pattern(ModuleName, Pattern);
return call_method<ReturnType>(address, arg_list...);
}

template<class ReturnType>
ReturnType call_method(const std::string_view& module_name,const std::string_view& pattern, auto... arg_list)
{
static const auto* address = resolve_pattern(module_name, pattern);
return call_method<ReturnType>(address, arg_list...);
}

template<class ReturnType>
ReturnType call_method(const std::string_view& module_name,const std::string_view& pattern, auto... arg_list) const
{
static const auto* address = resolve_pattern(module_name, pattern);
return call_method<ReturnType>(address, arg_list...);
}
template<std::size_t Id, class ReturnType>
ReturnType call_virtual_method(auto... arg_list)
{
const auto vtable = *reinterpret_cast<void***>(this);
return call_method<ReturnType>(vtable[Id], arg_list...);
}
template<std::size_t Id, class ReturnType>
ReturnType call_virtual_method(auto... arg_list) const
{
const auto vtable = *reinterpret_cast<void* const* const*>(this);
return call_method<ReturnType>(vtable[Id], arg_list...);
}

private:
[[nodiscard]]
static const void* resolve_pattern(const std::string_view module_name, const std::string_view pattern)
{
const auto* base = get_module_base(module_name);
assert(base && "Failed to find module");

#ifdef _WIN32
const auto result = PePatternScanner::scan_for_pattern_in_loaded_module(base, pattern);
#elif defined(__APPLE__)
const auto result = MachOPatternScanner::scan_for_pattern_in_loaded_module(base, pattern);
#else
const auto result = ElfPatternScanner::scan_for_pattern_in_loaded_module(base, pattern);
#endif
assert(result.has_value() && "Pattern scan failed");
return reinterpret_cast<const void*>(*result);
}

[[nodiscard]]
static const void* get_module_base(const std::string_view module_name)
{
#ifdef _WIN32
return GetModuleHandleA(module_name.data());
#elif defined(__APPLE__)
// On macOS, iterate loaded images to find the module by name
const auto count = _dyld_image_count();
for (std::uint32_t i = 0; i < count; ++i)
{
const auto* name = _dyld_get_image_name(i);
if (name && std::string_view{name}.find(module_name) != std::string_view::npos)
return static_cast<const void*>(_dyld_get_image_header(i));
}
return nullptr;
#else
using VirtualMethodType = ReturnType (*)(void*, decltype(arg_list)...);
// On Linux, use dl_iterate_phdr to find loaded module by name
struct CallbackData
{
std::string_view name;
const void* base;
} cb_data{module_name, nullptr};

dl_iterate_phdr(
[](dl_phdr_info* info, std::size_t, void* data) -> int
{
auto* cb = static_cast<CallbackData*>(data);
if (info->dlpi_name
&& std::string_view{info->dlpi_name}.find(cb->name) != std::string_view::npos)
{
cb->base = reinterpret_cast<const void*>(info->dlpi_addr);
return 1;
}
return 0;
},
&cb_data);
return cb_data.base;
#endif
return (*static_cast<VirtualMethodType**>((void*)(this)))[id](
const_cast<void*>(static_cast<const void*>(this)), arg_list...);
}
};
} // namespace omath::rev_eng
53 changes: 53 additions & 0 deletions tests/general/unit_test_reverse_enineering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ class Player final
int m_health{123};
};

// Extract a raw function pointer from an object's vtable
inline const void* get_vtable_entry(const void* obj, const std::size_t index)
{
const auto vtable = *static_cast<void* const* const*>(obj);
return vtable[index];
}

class RevPlayer final : omath::rev_eng::InternalReverseEngineeredObject
{
public:
Expand Down Expand Up @@ -51,6 +58,17 @@ class RevPlayer final : omath::rev_eng::InternalReverseEngineeredObject
{
return call_virtual_method<1, int>();
}

// Wrappers exposing call_method for testing — use vtable entries as known-good function pointers
int call_foo_via_ptr(const void* fn_ptr) const
{
return call_method<int>(fn_ptr);
}

int call_bar_via_ptr(const void* fn_ptr) const
{
return call_method<int>(fn_ptr);
}
};

TEST(unit_test_reverse_enineering, read_test)
Expand All @@ -64,4 +82,39 @@ TEST(unit_test_reverse_enineering, read_test)
EXPECT_EQ(player_original.bar(), player_reversed->rev_bar());
EXPECT_EQ(player_original.foo(), player_reversed->rev_foo());
EXPECT_EQ(player_original.bar(), player_reversed->rev_bar_const());
}

TEST(unit_test_reverse_enineering, call_method_with_vtable_ptr)
{
// Extract raw function pointers from Player's vtable, then call them via call_method
Player player;
const auto* rev = reinterpret_cast<const RevPlayer*>(&player);

const auto* foo_ptr = get_vtable_entry(&player, 0);
const auto* bar_ptr = get_vtable_entry(&player, 1);

EXPECT_EQ(player.foo(), rev->call_foo_via_ptr(foo_ptr));
EXPECT_EQ(player.bar(), rev->call_bar_via_ptr(bar_ptr));
EXPECT_EQ(1, rev->call_foo_via_ptr(foo_ptr));
EXPECT_EQ(2, rev->call_bar_via_ptr(bar_ptr));
}

TEST(unit_test_reverse_enineering, call_method_same_result_as_virtual)
{
// call_virtual_method delegates to call_method — both paths must agree
Player player;
const auto* rev = reinterpret_cast<const RevPlayer*>(&player);

EXPECT_EQ(rev->rev_foo(), rev->call_foo_via_ptr(get_vtable_entry(&player, 0)));
EXPECT_EQ(rev->rev_bar(), rev->call_bar_via_ptr(get_vtable_entry(&player, 1)));
}

TEST(unit_test_reverse_enineering, call_virtual_method_delegates_to_call_method)
{
Player player;
auto* rev = reinterpret_cast<RevPlayer*>(&player);

EXPECT_EQ(1, rev->rev_foo());
EXPECT_EQ(2, rev->rev_bar());
EXPECT_EQ(2, rev->rev_bar_const());
}
Loading