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
15 changes: 15 additions & 0 deletions include/omath/rev_eng/internal_rev_object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,21 @@ namespace omath::rev_eng
return call_method<ReturnType>(vtable[Id], arg_list...);
}

template<std::size_t TableIndex, std::size_t Id, class ReturnType>
ReturnType call_virtual_method(auto... arg_list)
{
const auto vtable = *reinterpret_cast<void***>(
reinterpret_cast<std::uintptr_t>(this) + TableIndex * sizeof(std::uintptr_t));
return call_method<ReturnType>(vtable[Id], arg_list...);
}
template<std::size_t TableIndex, std::size_t Id, class ReturnType>
ReturnType call_virtual_method(auto... arg_list) const
{
const auto vtable = *reinterpret_cast<void* const* const*>(
reinterpret_cast<std::uintptr_t>(this) + TableIndex * sizeof(std::uintptr_t));
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)
Expand Down
80 changes: 80 additions & 0 deletions tests/general/unit_test_reverse_enineering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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<const RevMultiPlayer*>(&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<const RevMultiPlayer*>(&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<RevMultiPlayer*>(&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<const RevMultiPlayer*>(&mp);

// Both access table 0, method index 1 — should return the same value
EXPECT_EQ(rev->rev_get_a(), 100);
}
Loading