@@ -27,6 +27,45 @@ inline const void* get_vtable_entry(const void* obj, const std::size_t index)
2727 return vtable[index];
2828}
2929
30+ class BaseA
31+ {
32+ public:
33+ [[nodiscard]] virtual int get_a () const { return 10 ; }
34+ [[nodiscard]] virtual int get_a2 () const { return 11 ; }
35+ };
36+
37+ class BaseB
38+ {
39+ public:
40+ [[nodiscard]] virtual int get_b () const { return 20 ; }
41+ [[nodiscard]] virtual int get_b2 () const { return 21 ; }
42+ };
43+
44+ class MultiPlayer final : public BaseA, public BaseB
45+ {
46+ public:
47+ [[nodiscard]] int get_a () const override { return 100 ; }
48+ [[nodiscard]] int get_a2 () const override { return 101 ; }
49+ [[nodiscard]] int get_b () const override { return 200 ; }
50+ [[nodiscard]] int get_b2 () const override { return 201 ; }
51+ };
52+
53+ class RevMultiPlayer final : omath::rev_eng::InternalReverseEngineeredObject
54+ {
55+ public:
56+ // Table 0 (BaseA vtable): index 0 = get_a, 1 = get_a2
57+ [[nodiscard]] int rev_get_a () const { return call_virtual_method<0 , 0 , int >(); }
58+ [[nodiscard]] int rev_get_a2 () const { return call_virtual_method<0 , 1 , int >(); }
59+
60+ // Table 1 (BaseB vtable): index 0 = get_b, 1 = get_b2
61+ [[nodiscard]] int rev_get_b () const { return call_virtual_method<1 , 0 , int >(); }
62+ [[nodiscard]] int rev_get_b2 () const { return call_virtual_method<1 , 1 , int >(); }
63+
64+ // Non-const versions
65+ int rev_get_a_mut () { return call_virtual_method<0 , 0 , int >(); }
66+ int rev_get_b_mut () { return call_virtual_method<1 , 0 , int >(); }
67+ };
68+
3069class RevPlayer final : omath::rev_eng::InternalReverseEngineeredObject
3170{
3271public:
@@ -117,4 +156,45 @@ TEST(unit_test_reverse_enineering, call_virtual_method_delegates_to_call_method)
117156 EXPECT_EQ (1 , rev->rev_foo ());
118157 EXPECT_EQ (2 , rev->rev_bar ());
119158 EXPECT_EQ (2 , rev->rev_bar_const ());
159+ }
160+
161+ TEST (unit_test_reverse_enineering, call_virtual_method_table_index_first_table)
162+ {
163+ MultiPlayer mp;
164+ const auto * rev = reinterpret_cast <const RevMultiPlayer*>(&mp);
165+
166+ EXPECT_EQ (mp.get_a (), rev->rev_get_a ());
167+ EXPECT_EQ (mp.get_a2 (), rev->rev_get_a2 ());
168+ EXPECT_EQ (100 , rev->rev_get_a ());
169+ EXPECT_EQ (101 , rev->rev_get_a2 ());
170+ }
171+
172+ TEST (unit_test_reverse_enineering, call_virtual_method_table_index_second_table)
173+ {
174+ MultiPlayer mp;
175+ const auto * rev = reinterpret_cast <const RevMultiPlayer*>(&mp);
176+
177+ EXPECT_EQ (mp.get_b (), rev->rev_get_b ());
178+ EXPECT_EQ (mp.get_b2 (), rev->rev_get_b2 ());
179+ EXPECT_EQ (200 , rev->rev_get_b ());
180+ EXPECT_EQ (201 , rev->rev_get_b2 ());
181+ }
182+
183+ TEST (unit_test_reverse_enineering, call_virtual_method_table_index_non_const)
184+ {
185+ MultiPlayer mp;
186+ auto * rev = reinterpret_cast <RevMultiPlayer*>(&mp);
187+
188+ EXPECT_EQ (100 , rev->rev_get_a_mut ());
189+ EXPECT_EQ (200 , rev->rev_get_b_mut ());
190+ }
191+
192+ TEST (unit_test_reverse_enineering, call_virtual_method_table_zero_matches_default)
193+ {
194+ // Table 0 with the TableIndex overload should match the original non-TableIndex overload
195+ MultiPlayer mp;
196+ const auto * rev = reinterpret_cast <const RevMultiPlayer*>(&mp);
197+
198+ // Both access table 0, method index 1 — should return the same value
199+ EXPECT_EQ (rev->rev_get_a (), 100 );
120200}
0 commit comments