diff --git a/include/omath/rev_eng/internal_rev_object.hpp b/include/omath/rev_eng/internal_rev_object.hpp index d8eac41f..0270870d 100644 --- a/include/omath/rev_eng/internal_rev_object.hpp +++ b/include/omath/rev_eng/internal_rev_object.hpp @@ -116,6 +116,21 @@ namespace omath::rev_eng return call_method(vtable[Id], arg_list...); } + template + ReturnType call_virtual_method(auto... arg_list) + { + const auto vtable = *reinterpret_cast( + reinterpret_cast(this) + TableIndex * sizeof(std::uintptr_t)); + return call_method(vtable[Id], arg_list...); + } + template + ReturnType call_virtual_method(auto... arg_list) const + { + const auto vtable = *reinterpret_cast( + reinterpret_cast(this) + TableIndex * sizeof(std::uintptr_t)); + 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) diff --git a/tests/general/unit_test_reverse_enineering.cpp b/tests/general/unit_test_reverse_enineering.cpp index 55b783af..2736cf2b 100644 --- a/tests/general/unit_test_reverse_enineering.cpp +++ b/tests/general/unit_test_reverse_enineering.cpp @@ -27,6 +27,45 @@ inline const void* get_vtable_entry(const void* obj, const std::size_t index) return vtable[index]; } +class BaseA +{ +public: + [[nodiscard]] virtual int get_a() const { return 10; } + [[nodiscard]] virtual int get_a2() const { return 11; } +}; + +class BaseB +{ +public: + [[nodiscard]] virtual int get_b() const { return 20; } + [[nodiscard]] virtual int get_b2() const { return 21; } +}; + +class MultiPlayer final : public BaseA, public BaseB +{ +public: + [[nodiscard]] int get_a() const override { return 100; } + [[nodiscard]] int get_a2() const override { return 101; } + [[nodiscard]] int get_b() const override { return 200; } + [[nodiscard]] int get_b2() const override { return 201; } +}; + +class RevMultiPlayer final : omath::rev_eng::InternalReverseEngineeredObject +{ +public: + // Table 0 (BaseA vtable): index 0 = get_a, 1 = get_a2 + [[nodiscard]] int rev_get_a() const { return call_virtual_method<0, 0, int>(); } + [[nodiscard]] int rev_get_a2() const { return call_virtual_method<0, 1, int>(); } + + // Table 1 (BaseB vtable): index 0 = get_b, 1 = get_b2 + [[nodiscard]] int rev_get_b() const { return call_virtual_method<1, 0, int>(); } + [[nodiscard]] int rev_get_b2() const { return call_virtual_method<1, 1, int>(); } + + // Non-const versions + int rev_get_a_mut() { return call_virtual_method<0, 0, int>(); } + int rev_get_b_mut() { return call_virtual_method<1, 0, int>(); } +}; + class RevPlayer final : omath::rev_eng::InternalReverseEngineeredObject { public: @@ -117,4 +156,45 @@ TEST(unit_test_reverse_enineering, call_virtual_method_delegates_to_call_method) EXPECT_EQ(1, rev->rev_foo()); EXPECT_EQ(2, rev->rev_bar()); EXPECT_EQ(2, rev->rev_bar_const()); +} + +TEST(unit_test_reverse_enineering, call_virtual_method_table_index_first_table) +{ + MultiPlayer mp; + const auto* rev = reinterpret_cast(&mp); + + EXPECT_EQ(mp.get_a(), rev->rev_get_a()); + EXPECT_EQ(mp.get_a2(), rev->rev_get_a2()); + EXPECT_EQ(100, rev->rev_get_a()); + EXPECT_EQ(101, rev->rev_get_a2()); +} + +TEST(unit_test_reverse_enineering, call_virtual_method_table_index_second_table) +{ + MultiPlayer mp; + const auto* rev = reinterpret_cast(&mp); + + EXPECT_EQ(mp.get_b(), rev->rev_get_b()); + EXPECT_EQ(mp.get_b2(), rev->rev_get_b2()); + EXPECT_EQ(200, rev->rev_get_b()); + EXPECT_EQ(201, rev->rev_get_b2()); +} + +TEST(unit_test_reverse_enineering, call_virtual_method_table_index_non_const) +{ + MultiPlayer mp; + auto* rev = reinterpret_cast(&mp); + + EXPECT_EQ(100, rev->rev_get_a_mut()); + EXPECT_EQ(200, rev->rev_get_b_mut()); +} + +TEST(unit_test_reverse_enineering, call_virtual_method_table_zero_matches_default) +{ + // Table 0 with the TableIndex overload should match the original non-TableIndex overload + MultiPlayer mp; + const auto* rev = reinterpret_cast(&mp); + + // Both access table 0, method index 1 — should return the same value + EXPECT_EQ(rev->rev_get_a(), 100); } \ No newline at end of file