#pragma once /** * Adapted from https://gist.github.com/altalk23/29b97969e9f0624f783b673f6c1cd279 */ #include "../utils/casts.hpp" #include "casts.hpp" #include #include #include #include #include #include namespace geode::addresser { template intptr_t getVirtual(Function func); template intptr_t getNonVirtual(Function func); template Class thunkAdjust(Function func, Class self); template Class rthunkAdjust(Function func, Class self); template concept HasZeroConstructor = requires { new Class(ZeroConstructor); }; class GEODE_DLL Addresser final { template struct SingleInheritance { virtual ~SingleInheritance() {} }; struct MultipleInheritance : SingleInheritance<'L'>, SingleInheritance<'F'> { virtual ~MultipleInheritance() {} }; static MultipleInheritance* instance(); template static ptrdiff_t indexOf(R (T::*func)(Ps...)) { using method_t = ptrdiff_t (T::*)(); return (reinterpret_cast(instance())->*reinterpret_cast(func))(); } template static ptrdiff_t indexOf(R (T::*func)(Ps...) const) { return indexOf(reinterpret_cast(func)); } template static ptrdiff_t thunkOf(T ptr) { // msvc if (sizeof(T) == sizeof(ptrdiff_t)) return 0; // all auto thunk = *(reinterpret_cast(&ptr) + 1); // arm if (thunk & 1) thunk >>= 1; return thunk; } template static Class* cachedInstance() requires HasZeroConstructor { static auto ret = new Class(ZeroConstructor); return ret; } template static Class* cachedInstance() requires (!HasZeroConstructor) { return nullptr; } /** * Specialized functionss */ template static intptr_t addressOfVirtual(Return (Class::*func)(Parameters...)) { using geode::cast::reference_cast; auto ins = cachedInstance(); // generateInstance will return nullptr on most abstract classes, // so dont bother getting the address if (ins == nullptr) { return 0; } auto index = indexOf(func); auto thunk = thunkOf(func); // [[this + thunk] + offset] is the function we want auto address = *reinterpret_cast( *reinterpret_cast(reinterpret_cast(ins) + thunk) + index ); #ifdef GEODE_IS_WINDOWS // if the first instruction is a long jmp then this might still be a thunk if (*reinterpret_cast(address) == 0xE9) { auto relative = *reinterpret_cast(address + 1); address = address + relative + 5; } #endif address = followThunkFunction(address); return address; } template static intptr_t addressOfVirtual(Return (Class::*func)(Parameters...) const) { return addressOfVirtual(reinterpret_cast(func)); } static intptr_t followThunkFunction(intptr_t address); template static intptr_t addressOfNonVirtual(FnPtr func) { return followThunkFunction(geode::cast::reference_cast(func)); } template friend intptr_t getVirtual(Function func); template friend intptr_t getNonVirtual(Function func); template friend Class thunkAdjust(Function func, Class self); template friend Class rthunkAdjust(Function func, Class self); }; /** * Gets the real address of a virtual function */ template inline intptr_t getVirtual(Function func) { return Addresser::addressOfVirtual(func); } /** * Gets the real address of a non-virtual function */ template inline intptr_t getNonVirtual(Function func) { return Addresser::addressOfNonVirtual(func); } /** * Adjusts a class instance to its appropriate base for the given virtual function */ template inline Class thunkAdjust(Function func, Class self) { // do NOT delete the line below. // doing so breaks thunk adjusting on windows. // why? bruh idk auto _ = *geode::cast::template union_cast(&func); return (Class)((intptr_t)self + Addresser::thunkOf(func)); } /** * Adjusts a class instance back from its appropriate base for the given virtual function to the original base */ template inline Class rthunkAdjust(Function func, Class self) { // do NOT delete the line below. // doing so breaks thunk adjusting on windows. // why? bruh idk auto _ = *geode::cast::template union_cast(&func); return (Class)((intptr_t)self - Addresser::thunkOf(func)); } }