diff --git a/include/omath/rev_eng/internal_rev_object.hpp b/include/omath/rev_eng/internal_rev_object.hpp index 56f205a9..d8eac41f 100644 --- a/include/omath/rev_eng/internal_rev_object.hpp +++ b/include/omath/rev_eng/internal_rev_object.hpp @@ -3,11 +3,43 @@ // #pragma once +#include #include #include +#include + +#ifdef _WIN32 +#include "omath/utility/pe_pattern_scan.hpp" +#include +#elif defined(__APPLE__) +#include "omath/utility/macho_pattern_scan.hpp" +#include +#else +#include "omath/utility/elf_pattern_scan.hpp" +#include +#endif namespace omath::rev_eng { + template + 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 + FixedString(const char (&)[N]) -> FixedString; + class InternalReverseEngineeredObject { protected: @@ -23,26 +55,123 @@ namespace omath::rev_eng return *reinterpret_cast(reinterpret_cast(this) + offset); } - template - ReturnType call_virtual_method(auto... arg_list) + template + 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(this))[id](this, arg_list...); + return reinterpret_cast(const_cast(ptr))(this, arg_list...); } - template - ReturnType call_virtual_method(auto... arg_list) const + template + 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(const_cast(ptr))(this, arg_list...); + } + + template + ReturnType call_method(auto... arg_list) + { + static const auto* address = resolve_pattern(ModuleName, Pattern); + return call_method(address, arg_list...); + } + + template + ReturnType call_method(auto... arg_list) const + { + static const auto* address = resolve_pattern(ModuleName, Pattern); + return call_method(address, arg_list...); + } + + template + 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(address, arg_list...); + } + + template + 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(address, arg_list...); + } + template + ReturnType call_virtual_method(auto... arg_list) + { + const auto vtable = *reinterpret_cast(this); + return call_method(vtable[Id], arg_list...); + } + template + ReturnType call_virtual_method(auto... arg_list) const + { + const auto vtable = *reinterpret_cast(this); + return call_method(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(*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(_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(data); + if (info->dlpi_name + && std::string_view{info->dlpi_name}.find(cb->name) != std::string_view::npos) + { + cb->base = reinterpret_cast(info->dlpi_addr); + return 1; + } + return 0; + }, + &cb_data); + return cb_data.base; #endif - return (*static_cast((void*)(this)))[id]( - const_cast(static_cast(this)), arg_list...); } }; } // namespace omath::rev_eng diff --git a/tests/general/unit_test_reverse_enineering.cpp b/tests/general/unit_test_reverse_enineering.cpp index b1b0eb4a..55b783af 100644 --- a/tests/general/unit_test_reverse_enineering.cpp +++ b/tests/general/unit_test_reverse_enineering.cpp @@ -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(obj); + return vtable[index]; +} + class RevPlayer final : omath::rev_eng::InternalReverseEngineeredObject { public: @@ -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(fn_ptr); + } + + int call_bar_via_ptr(const void* fn_ptr) const + { + return call_method(fn_ptr); + } }; TEST(unit_test_reverse_enineering, read_test) @@ -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(&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(&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(&player); + + EXPECT_EQ(1, rev->rev_foo()); + EXPECT_EQ(2, rev->rev_bar()); + EXPECT_EQ(2, rev->rev_bar_const()); } \ No newline at end of file