From 222c174f0da57a3531272d7429dffb41f92abfc1 Mon Sep 17 00:00:00 2001 From: Kenzzer Date: Sat, 20 Apr 2024 14:10:59 +0200 Subject: [PATCH] add x64 stub generator support --- AMBuildScript | 3 +- core/AMBuilder | 3 + core/metamod.cpp | 14 +- core/sourcehook/sourcehook.cpp | 21 + core/sourcehook/sourcehook.h | 5 +- core/sourcehook/sourcehook_hookmangen.h | 29 +- .../sourcehook/sourcehook_hookmangen_test.cpp | 546 ++++++ core/sourcehook/sourcehook_hookmangen_test.h | 57 + .../sourcehook_hookmangen_x86_64.cpp | 1488 +++++++++++++++++ .../sourcehook/sourcehook_hookmangen_x86_64.h | 666 ++++++++ core/sourcehook/sourcehook_impl.h | 21 + .../sourcehook_impl_chookmaninfo.cpp | 13 + 12 files changed, 2846 insertions(+), 20 deletions(-) create mode 100644 core/sourcehook/sourcehook_hookmangen_test.cpp create mode 100644 core/sourcehook/sourcehook_hookmangen_test.h create mode 100644 core/sourcehook/sourcehook_hookmangen_x86_64.cpp create mode 100644 core/sourcehook/sourcehook_hookmangen_x86_64.h diff --git a/AMBuildScript b/AMBuildScript index 0c4f54a..0999471 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -178,7 +178,8 @@ class MMSConfig(object): ] cxx.cflags += [ '/W3', - '/Zi', + '/Z7', + '/EHsc', '/std:c++17', ] cxx.cxxflags += ['/TP'] diff --git a/core/AMBuilder b/core/AMBuilder index 3adf7ad..65b67b0 100644 --- a/core/AMBuilder +++ b/core/AMBuilder @@ -16,6 +16,7 @@ for sdk_target in MMS.sdk_targets: 'metamod_util.cpp', 'provider/provider_base.cpp', 'sourcehook/sourcehook.cpp', + 'sourcehook/sourcehook_hookmangen_test.cpp', 'sourcehook/sourcehook_impl_chookidman.cpp', 'sourcehook/sourcehook_impl_chookmaninfo.cpp', 'sourcehook/sourcehook_impl_cproto.cpp', @@ -41,5 +42,7 @@ for sdk_target in MMS.sdk_targets: if cxx.target.arch == 'x86': binary.sources += ['sourcehook/sourcehook_hookmangen.cpp'] + elif binary.compiler.target.arch == 'x86_64': + binary.sources += ['sourcehook/sourcehook_hookmangen_x86_64.cpp'] nodes = builder.Add(binary) MMS.binaries += [nodes] diff --git a/core/metamod.cpp b/core/metamod.cpp index bfe44d0..f0f6b7c 100644 --- a/core/metamod.cpp +++ b/core/metamod.cpp @@ -34,6 +34,7 @@ #include "metamod_util.h" #include "metamod_console.h" #include "provider/provider_base.h" +#include "sourcehook/sourcehook_hookmangen_test.h" #include #define X64_SUFFIX ".x64" @@ -84,9 +85,7 @@ static MetamodSourceConVar *mm_basedir = NULL; static CreateInterfaceFn engine_factory = NULL; static CreateInterfaceFn physics_factory = NULL; static CreateInterfaceFn filesystem_factory = NULL; -#if !defined( _WIN64 ) && !defined( __amd64__ ) static CHookManagerAutoGen g_SH_HookManagerAutoGen(&g_SourceHook); -#endif static META_RES last_meta_res; static IServerPluginCallbacks *vsp_callbacks = NULL; static bool were_plugins_loaded = false; @@ -222,6 +221,7 @@ mm_InitializeForLoad() in_first_level = true; provider->SetCallbacks(&s_ProviderCallbacks); + SourceHook::Impl::run_tests(); } bool @@ -420,6 +420,7 @@ mm_LogMessage(const char *msg, ...) va_start(ap, msg); size_t len = vsnprintf(buffer, sizeof(buffer) - 2, msg, ap); + len = min(len, 2046); va_end(ap); buffer[len++] = '\n'; @@ -429,6 +430,7 @@ mm_LogMessage(const char *msg, ...) { fprintf(stdout, "%s", buffer); } + provider->ConsolePrint(buffer); } static void @@ -814,19 +816,11 @@ void *MetamodSource::MetaFactory(const char *iface, int *ret, PluginId *id) } else if (strcmp(iface, MMIFACE_SH_HOOKMANAUTOGEN) == 0) { -#if defined( _WIN64 ) || defined( __amd64__ ) - if (ret) - { - *ret = META_IFACE_FAILED; - } - return nullptr; -#else if (ret) { *ret = META_IFACE_OK; } return static_cast(static_cast(&g_SH_HookManagerAutoGen)); -#endif } CPluginManager::CPlugin *pl; diff --git a/core/sourcehook/sourcehook.cpp b/core/sourcehook/sourcehook.cpp index 8b66b2c..430d1c8 100644 --- a/core/sourcehook/sourcehook.cpp +++ b/core/sourcehook/sourcehook.cpp @@ -42,6 +42,9 @@ namespace SourceHook namespace Impl { + // Log level + SH_LOG sh_log_level = SH_LOG::TEST; + ////////////////////////////////////////////////////////////////////////// // CVfnPtrList ////////////////////////////////////////////////////////////////////////// @@ -488,6 +491,18 @@ namespace SourceHook IHookContext *CSourceHookImpl::SetupHookLoop(IHookManagerInfo *hi, void *vfnptr, void *thisptr, void **origCallAddr, META_RES *statusPtr, META_RES *prevResPtr, META_RES *curResPtr, const void *origRetPtr, void *overrideRetPtr) { + SH_DEBUG_LOG(VERBOSE, "CSourceHookImpl(%p)::SetupHookLoop\n" + "- hi %p\n" + "- vfnptr %p\n" + "- thisptr %p\n" + "- origCallAddr %p\n" + "- statusPtr %p\n" + "- prevResPtr %p\n" + "- curResPtr %p\n" + "- origRetPtr %p\n" + "- overrideRetPtr %p", + (void*)this, (void*)hi, (void*)vfnptr, (void*)thisptr, (void*)origCallAddr, (void*)statusPtr, (void*)prevResPtr, (void*)curResPtr, (void*)origRetPtr, (void*)overrideRetPtr); + CHookContext *pCtx = NULL; CHookContext *oldctx = m_ContextStack.empty() ? NULL : &m_ContextStack.front(); if (oldctx) @@ -568,6 +583,7 @@ namespace SourceHook if (vfnptr_iter == vfnptr_list.end()) { pCtx->m_State = CHookContext::State_Dead; + SH_DEBUG_LOG(VERBOSE, "CSourceHookImpl(%p)::SetupHookLoop\nThe hook is dead", this); } else { @@ -696,6 +712,8 @@ namespace SourceHook ////////////////////////////////////////////////////////////////////////// ISHDelegate *CHookContext::GetNext() { + SH_DEBUG_LOG(VERBOSE, "CHookContext(%p)::GetNext\n- m_State %d\n",(void*)this, m_State); + CIface *pVPIface; switch (m_State) { @@ -753,6 +771,7 @@ namespace SourceHook // end VP hooks -> orig call m_State = State_OrigCall; + SH_DEBUG_LOG(VERBOSE, "CHookContext(%p)::GetNext\n- No more PRE handler!\n",(void*)this); return NULL; case State_OrigCall: @@ -801,6 +820,7 @@ namespace SourceHook // end VP hooks -> done m_State = State_Dead; + SH_DEBUG_LOG(VERBOSE, "CHookContext(%p)::GetNext\n- No more POST handler!\n",(void*)this); return NULL; case State_Recall_Post: @@ -813,6 +833,7 @@ namespace SourceHook m_State = State_PostVP; return NULL; } + SH_DEBUG_LOG(VERBOSE, "CHookContext(%p)::GetNext\n- Unknown state we fell through!\n",(void*)this); return NULL; } diff --git a/core/sourcehook/sourcehook.h b/core/sourcehook/sourcehook.h index faf5b24..2ab0c0a 100644 --- a/core/sourcehook/sourcehook.h +++ b/core/sourcehook/sourcehook.h @@ -52,6 +52,7 @@ #endif #ifdef SH_DEBUG + # include # include @@ -194,7 +195,7 @@ namespace SourceHook // SH tries to auto-detect these // If you want to override SH's auto-detection, pass them in yourself PassFlag_RetMem = (1<<6), /**< Object is returned in memory (through hidden first param */ - PassFlag_RetReg = (1<<7) /**< Object is returned in EAX(:EDX) */ + PassFlag_RetReg = (1<<7) /**< Object is returned in EAX(:EDX)/RAX(x86_64) */ }; size_t size; //!< Size of the data being passed @@ -4928,4 +4929,4 @@ namespace SourceHook } #endif - // The pope is dead. -> :( + // The pope is dead. -> :( \ No newline at end of file diff --git a/core/sourcehook/sourcehook_hookmangen.h b/core/sourcehook/sourcehook_hookmangen.h index 9e565f7..cc22866 100644 --- a/core/sourcehook/sourcehook_hookmangen.h +++ b/core/sourcehook/sourcehook_hookmangen.h @@ -12,6 +12,8 @@ #define __SOURCEHOOK_HOOKMANGEN_H__ #include "sh_pagealloc.h" +#include +#include namespace SourceHook { @@ -38,6 +40,16 @@ namespace SourceHook typedef unsigned int jitoffs_t; typedef signed int jitrel_t; + class IGenContext + { + public: + virtual ~IGenContext() {}; + + virtual bool Equal(const CProto& proto, int vtbl_offs, int vtbl_idx) = 0; + virtual bool Equal(HookManagerPubFunc other) = 0; + + virtual HookManagerPubFunc GetPubFunc() = 0; + }; class GenBuffer { @@ -140,6 +152,9 @@ namespace SourceHook void write_uint32(jit_uint32_t x) { push(x); } void write_int32(jit_uint32_t x) { push(x); } + void write_uint64(jit_uint64_t x) { push(x); } + void write_int64(jit_int64_t x) { push(x); } + jitoffs_t get_outputpos() { return m_Size; @@ -155,7 +170,7 @@ namespace SourceHook } }; - class GenContext + class GenContext : public IGenContext { const static int SIZE_MWORD = 4; const static int SIZE_PTR = sizeof(void*); @@ -263,12 +278,12 @@ namespace SourceHook public: // Level 1 -> Public interface GenContext(const ProtoInfo *proto, int vtbl_offs, int vtbl_idx, ISourceHook *pSHPtr); - ~GenContext(); + virtual ~GenContext(); - bool Equal(const CProto &proto, int vtbl_offs, int vtbl_idx); - bool Equal(HookManagerPubFunc other); + virtual bool Equal(const CProto &proto, int vtbl_offs, int vtbl_idx) override; + virtual bool Equal(HookManagerPubFunc other) override; - HookManagerPubFunc GetPubFunc(); + virtual HookManagerPubFunc GetPubFunc() override; }; class CHookManagerAutoGen : public IHookManagerAutoGen @@ -276,9 +291,9 @@ namespace SourceHook struct StoredContext { int m_RefCnt; - GenContext *m_GenContext; + std::unique_ptr m_GenContext; }; - List m_Contexts; + std::list m_Contexts; ISourceHook *m_pSHPtr; public: diff --git a/core/sourcehook/sourcehook_hookmangen_test.cpp b/core/sourcehook/sourcehook_hookmangen_test.cpp new file mode 100644 index 0000000..1712b72 --- /dev/null +++ b/core/sourcehook/sourcehook_hookmangen_test.cpp @@ -0,0 +1,546 @@ +#include "sourcehook_pibuilder.h" +#include "sourcehook_hookmangen_test.h" + +#include "metamod.h" + +extern MetamodSource g_Metamod; + +namespace SourceHook { +namespace Impl { + SourceHook::IHookManagerAutoGen* g_pHookManager = nullptr; + + std::vector SHT::m_tests; + + void run_tests() { + SH_DEBUG_LOG(TEST, "[SourceHook] Test Suite"); + + g_pHookManager = static_cast(g_Metamod.MetaFactory(MMIFACE_SH_HOOKMANAUTOGEN, NULL, NULL)); + + if (!g_SHPtr || !g_pHookManager) { + SH_DEBUG_LOG(TEST, "SourceHook is not initialised!"); + return; + } + + static std::uint32_t testIndex = 1; + for (auto lambdas : SHT::m_tests) { + try { + lambdas->Call(); + SH_DEBUG_LOG(TEST, "TEST #%d \"%s\" - Passed", testIndex, lambdas->GetName().c_str()); + } + catch (const SHTException& e) { + SH_DEBUG_LOG(TEST, "TEST #%d \"%s\" - Failed\nError: \"%s\"", testIndex, lambdas->GetName().c_str(), e.what()); + } + catch (const std::exception& e) { + SH_DEBUG_LOG(TEST, "TEST #%d \"%s\" - Failed\nUnknown error: \"%s\"", testIndex, lambdas->GetName().c_str(), e.what()); + } + catch (...) { + SH_DEBUG_LOG(TEST, "TEST #%d \"%s\" - Unknown exception during test", testIndex, lambdas->GetName().c_str()); + } + testIndex++; + } + + SH_DEBUG_LOG(TEST, "[SourceHook] Test Suite - Ended"); + } + + template + class Function : public ISHDelegate { + public: + Function(CProtoInfoBuilder info, ReturnType (*virtualFunc)(void* thisPtr, Args... args), ReturnType (*hookFuncPre)(void* thisPtr, Args... args), ReturnType (*hookFuncPost)(void* thisPtr, Args... args)) : + m_virtualFunc(virtualFunc), + m_hookFuncPre(hookFuncPre), + m_hookFuncPost(hookFuncPost), + m_preCalled(false) + { + MemFuncInfo mfi = {false, -1, -1, -1}; + GetFuncInfo(&Function::Test, mfi); + + if (!mfi.isVirtual || mfi.vtblindex <= 0) { + throw SHTException("Failed to initialise test class"); + } + + m_hookMan = g_pHookManager->MakeHookMan(info, mfi.vtbloffs, mfi.vtblindex); + m_hookIdPre = g_SHPtr->AddHook(g_PLID, ISourceHook::Hook_Normal, this, 0, m_hookMan, this, false); + m_hookIdPost = g_SHPtr->AddHook(g_PLID, ISourceHook::Hook_Normal, this, 0, m_hookMan, this, true); + + SH_DEBUG_LOG(VERBOSE, "Setup Test Class %p - Vfnptr %p", this, (*(void***)this)[mfi.vtblindex]); + } + + protected: + virtual bool IsEqual(ISHDelegate *pOtherDeleg) override { return false; }; + virtual void DeleteThis() override { }; + virtual ReturnType Call(Args... args) { + if (m_preCalled) { + return (ReturnType)m_hookFuncPost(this, args...); + } + else { + m_preCalled = true; + return (ReturnType)m_hookFuncPre(this, args...); + } + }; + + ReturnType (*m_virtualFunc)(void* thisPtr, Args... args); + ReturnType (*m_hookFuncPre)(void* thisPtr, Args... args); + ReturnType (*m_hookFuncPost)(void* thisPtr, Args... args); + + HookManagerPubFunc m_hookMan; + int m_hookIdPre; + int m_hookIdPost; + bool m_preCalled; + + public: + virtual ReturnType Test(Args... args) { + return (ReturnType)m_virtualFunc(this, args...); + }; + }; + + void sht_assert(bool assert, const char* message) { + if (!assert) { + throw SHTException(message); + } + } + +SHT test1("void_no_param", []() { + static bool g_called = false; + static bool g_pre_hook_called = false; + static bool g_post_hook_called = false; + static bool g_consistent_thisptr = true; + static void* testThisPtr = nullptr; + + CProtoInfoBuilder info(ProtoInfo::CallConv_ThisCall); + info.SetReturnType(0, SourceHook::PassInfo::PassType_Unknown, 0, nullptr, nullptr, nullptr, nullptr); + + auto test = new Function(info, + [](void* thisPtr) { + // Virtual Function + g_called = true; + g_consistent_thisptr &= (thisPtr == testThisPtr); + }, + [](void* thisPtr) { + // Pre Hook + g_pre_hook_called = true; + g_consistent_thisptr &= (thisPtr == testThisPtr); + RETURN_META(MRES_IGNORED); + }, + [](void* thisPtr) { + // Post hook + g_post_hook_called = true; + g_consistent_thisptr &= (thisPtr == testThisPtr); + RETURN_META(MRES_IGNORED); + }); + testThisPtr = test; + + test->Test(); + + sht_assert(g_called, "Original function not called"); + sht_assert(g_pre_hook_called, "Pre hook not called"); + sht_assert(g_post_hook_called, "Post hook not called"); + sht_assert(g_consistent_thisptr, "Inconsistent this ptr across hooks"); +}); + +SHT test2("void_one_int_param", []() { + static bool g_called = false; + static bool g_pre_hook_called = false; + static bool g_post_hook_called = false; + static bool g_number_consistent = true; + static int g_test_number = 7680212; + + CProtoInfoBuilder info(ProtoInfo::CallConv_ThisCall); + info.AddParam(sizeof(int), PassInfo::PassType_Basic, PassInfo::PassFlag_ByVal, nullptr, nullptr, nullptr, nullptr); + info.SetReturnType(0, PassInfo::PassType_Unknown, 0, nullptr, nullptr, nullptr, nullptr); + + auto test = new Function(info, + [](void* thisPtr, int param) { + // Virtual Function + g_called = true; + g_number_consistent &= (param == g_test_number); + }, + [](void* thisPtr, int param) { + // Pre Hook + g_pre_hook_called = true; + g_number_consistent &= (param == g_test_number); + RETURN_META(MRES_IGNORED); + }, + [](void* thisPtr, int param) { + // Post hook + g_post_hook_called = true; + g_number_consistent &= (param == g_test_number); + RETURN_META(MRES_IGNORED); + }); + + test->Test(g_test_number); + + sht_assert(g_called, "Original function not called"); + sht_assert(g_pre_hook_called, "Pre hook not called"); + sht_assert(g_post_hook_called, "Post hook not called"); + sht_assert(g_number_consistent, "Int parameter changed"); +}); + +SHT test3("void_mres_supercede", []() { + static bool g_called = false; + + CProtoInfoBuilder info(ProtoInfo::CallConv_ThisCall); + info.SetReturnType(0, PassInfo::PassType_Unknown, 0, nullptr, nullptr, nullptr, nullptr); + + auto test = new Function(info, + [](void* thisPtr) { + // Virtual Function + g_called = true; + }, + [](void* thisPtr) { + // Pre Hook + RETURN_META(MRES_SUPERCEDE); + }, + [](void* thisPtr) { + // Post hook + RETURN_META(MRES_IGNORED); + }); + + test->Test(); + + sht_assert(!g_called, "Original function was called"); +}); + +SHT test4("return_mres_supercede", []() { + static bool g_called = false; + static bool g_valid_override = true; + + CProtoInfoBuilder info(ProtoInfo::CallConv_ThisCall); + info.SetReturnType(sizeof(int), PassInfo::PassType_Basic, PassInfo::PassFlag_ByVal, nullptr, nullptr, nullptr, nullptr); + + auto test = new Function(info, + [](void* thisPtr) { + // Virtual Function + g_called = true; + return -1; + }, + [](void* thisPtr) { + // Pre Hook + RETURN_META_VALUE(MRES_SUPERCEDE, 0); + }, + [](void* thisPtr) { + // Post hook + int value = *static_cast(g_SHPtr->GetOverrideRet()); + g_valid_override &= (value == 0); + RETURN_META_VALUE(MRES_IGNORED, 0); + }); + + g_valid_override &= (test->Test() == 0); + + sht_assert(!g_called, "Original function was called"); + sht_assert(g_valid_override, "Return value wasn't not overridden"); +}); + +SHT test5("void_mres_override", []() { + static bool g_called = false; + + CProtoInfoBuilder info(ProtoInfo::CallConv_ThisCall); + info.SetReturnType(0, PassInfo::PassType_Unknown, 0, nullptr, nullptr, nullptr, nullptr); + + auto test = new Function(info, + [](void* thisPtr) { + // Virtual Function + g_called = true; + }, + [](void* thisPtr) { + // Pre Hook + RETURN_META(MRES_OVERRIDE); + }, + [](void* thisPtr) { + // Post hook + RETURN_META(MRES_IGNORED); + }); + + test->Test(); + + sht_assert(g_called, "Original function was not called"); +}); + +SHT test6("void_mres_override", []() { + static bool g_called = false; + + CProtoInfoBuilder info(ProtoInfo::CallConv_ThisCall); + info.SetReturnType(0, PassInfo::PassType_Unknown, 0, nullptr, nullptr, nullptr, nullptr); + + auto test = new Function(info, + [](void* thisPtr) { + // Virtual Function + g_called = true; + }, + [](void* thisPtr) { + // Pre Hook + RETURN_META(MRES_IGNORED); + }, + [](void* thisPtr) { + // Post hook + RETURN_META(MRES_OVERRIDE); + }); + + test->Test(); + + sht_assert(g_called, "Original function was not called"); +}); + +SHT test7("void_mres_supercede_2", []() { + static bool g_called = false; + + CProtoInfoBuilder info(ProtoInfo::CallConv_ThisCall); + info.SetReturnType(0, PassInfo::PassType_Unknown, 0, nullptr, nullptr, nullptr, nullptr); + + auto test = new Function(info, + [](void* thisPtr) { + // Virtual Function + g_called = true; + }, + [](void* thisPtr) { + // Pre Hook + RETURN_META(MRES_IGNORED); + }, + [](void* thisPtr) { + // Post hook + RETURN_META(MRES_SUPERCEDE); + }); + + test->Test(); + + sht_assert(g_called, "Original function was not called"); +}); + +SHT test8("return_mres_supercede", []() { + static bool g_called = false; + static bool g_valid_override = true; + + CProtoInfoBuilder info(ProtoInfo::CallConv_ThisCall); + info.SetReturnType(sizeof(int), PassInfo::PassType_Basic, PassInfo::PassFlag_ByVal, nullptr, nullptr, nullptr, nullptr); + + auto test = new Function(info, + [](void* thisPtr) { + // Virtual Function + g_called = true; + return -1; + }, + [](void* thisPtr) { + // Pre Hook + RETURN_META_VALUE(MRES_OVERRIDE, 7); + }, + [](void* thisPtr) { + // Post hook + int value = *static_cast(g_SHPtr->GetOverrideRet()); + g_valid_override &= (value == 7); + RETURN_META_VALUE(MRES_IGNORED, 5); + }); + + g_valid_override &= (test->Test() == 7); + + sht_assert(g_called, "Original function was not called"); + sht_assert(g_valid_override, "Return value wasn't not overridden"); +}); + +SHT test9("return_vector_ref", []() { + static bool g_called = false; + static bool g_consistent_thisptr = true; + static void* testThisPtr = nullptr; + + CProtoInfoBuilder info(ProtoInfo::CallConv_ThisCall); + info.SetReturnType(sizeof(SDKVector), PassInfo::PassType_Object, PassInfo::PassFlag_ByRef, nullptr, nullptr, nullptr, nullptr); + + static SDKVector originalVector(1, 2, 3); + static SDKVector hookVector(4, 5, 6); + + auto test = new Function(info, + [](void* thisPtr) -> SDKVector& { + // Virtual Function + g_called = true; + g_consistent_thisptr &= (thisPtr == testThisPtr); + return originalVector; + }, + [](void* thisPtr) -> SDKVector& { + // Pre Hook + g_consistent_thisptr &= (thisPtr == testThisPtr); + RETURN_META_VALUE(MRES_IGNORED, hookVector); + }, + [](void* thisPtr) -> SDKVector& { + // Post hook + g_consistent_thisptr &= (thisPtr == testThisPtr); + RETURN_META_VALUE(MRES_IGNORED, hookVector); + }); + testThisPtr = test; + + auto& vector = test->Test(); + + sht_assert(g_called, "Original function was not called"); + sht_assert(g_consistent_thisptr, "The this ptr was not consistent"); + sht_assert(&vector == &originalVector, "Original vector was not returned"); +}); + +SHT test10("return_vector_ref_mres_supercede", []() { + static bool g_called = false; + + CProtoInfoBuilder info(ProtoInfo::CallConv_ThisCall); + info.SetReturnType(sizeof(SDKVector), PassInfo::PassType_Object, PassInfo::PassFlag_ByRef, nullptr, nullptr, nullptr, nullptr); + + static SDKVector originalVector(1, 2, 3); + static SDKVector hookVector(4, 5, 6); + + auto test = new Function(info, + [](void* thisPtr) -> SDKVector& { + // Virtual Function + g_called = true; + return originalVector; + }, + [](void* thisPtr) -> SDKVector& { + // Pre Hook + RETURN_META_VALUE(MRES_SUPERCEDE, hookVector); + }, + [](void* thisPtr) -> SDKVector& { + // Post hook + RETURN_META_VALUE(MRES_IGNORED, originalVector); + }); + + auto& vector = test->Test(); + + sht_assert(!g_called, "Original function was called"); + sht_assert(&vector == &hookVector, "Hook vector was not returned"); +}); + +SHT test11("return_vector_ref_mres_supercede_2", []() { + static bool g_called = false; + + CProtoInfoBuilder info(ProtoInfo::CallConv_ThisCall); + info.SetReturnType(sizeof(SDKVector), PassInfo::PassType_Object, PassInfo::PassFlag_ByRef, nullptr, nullptr, nullptr, nullptr); + + static SDKVector originalVector(1, 2, 3); + static SDKVector hookVector(4, 5, 6); + + auto test = new Function(info, + [](void* thisPtr) -> SDKVector& { + // Virtual Function + g_called = true; + return originalVector; + }, + [](void* thisPtr) -> SDKVector& { + // Pre Hook + RETURN_META_VALUE(MRES_IGNORED, originalVector); + }, + [](void* thisPtr) -> SDKVector& { + // Post hook + RETURN_META_VALUE(MRES_SUPERCEDE, hookVector); + }); + + auto& vector = test->Test(); + + sht_assert(g_called, "Original function was not called"); + sht_assert(&vector == &hookVector, "Hook vector was not returned"); +}); + +SHT test12("return_vector_ref_mres_override", []() { + static bool g_called = false; + + CProtoInfoBuilder info(ProtoInfo::CallConv_ThisCall); + info.SetReturnType(sizeof(SDKVector), PassInfo::PassType_Object, PassInfo::PassFlag_ByRef, nullptr, nullptr, nullptr, nullptr); + + static SDKVector originalVector(1, 2, 3); + static SDKVector hookVector(4, 5, 6); + + auto test = new Function(info, + [](void* thisPtr) -> SDKVector& { + // Virtual Function + g_called = true; + return originalVector; + }, + [](void* thisPtr) -> SDKVector& { + // Pre Hook + RETURN_META_VALUE(MRES_OVERRIDE, hookVector); + }, + [](void* thisPtr) -> SDKVector& { + // Post hook + RETURN_META_VALUE(MRES_IGNORED, originalVector); + }); + + auto& vector = test->Test(); + + sht_assert(g_called, "Original function was not called"); + sht_assert(&vector == &hookVector, "Hook vector was not returned"); +}); + +SHT test13("return_vector_mres_override", []() { + static bool g_called = false; + + CProtoInfoBuilder info(ProtoInfo::CallConv_ThisCall); + info.SetReturnType(sizeof(SDKVector), PassInfo::PassType_Object, PassInfo::PassFlag_ByVal, nullptr, nullptr, nullptr, nullptr); + + static SDKVector originalVector(1, 2, 3); + static SDKVector hookVector(4, 5, 6); + + auto test = new Function(info, + [](void* thisPtr) -> SDKVector { + // Virtual Function + g_called = true; + return originalVector; + }, + [](void* thisPtr) -> SDKVector { + // Pre Hook + RETURN_META_VALUE(MRES_OVERRIDE, hookVector); + }, + [](void* thisPtr) -> SDKVector { + // Post hook + RETURN_META_VALUE(MRES_IGNORED, originalVector); + }); + + auto vector = test->Test(); + + sht_assert(g_called, "Original function was not called"); + sht_assert(vector.x == 4.0 && vector.y == 5.0 && vector.z == 6.0, "Hook vector was not returned"); +}); + +SHT test14("void_three_params_vector_int_vector", []() { + static bool g_called = false; + static bool g_hook_pre_called = false; + static bool g_hook_post_called = false; + static bool g_valid_arg[4] = { true, true, true, true }; + static void* testThisPtr = nullptr; + + CProtoInfoBuilder info(ProtoInfo::CallConv_ThisCall); + info.SetReturnType(0, PassInfo::PassType_Basic, PassInfo::PassFlag_ByVal, nullptr, nullptr, nullptr, nullptr); + info.AddParam(sizeof(SDKVector), PassInfo::PassType_Object, PassInfo::PassFlag_ByVal, nullptr, nullptr, nullptr, nullptr); + info.AddParam(sizeof(int), PassInfo::PassType_Basic, PassInfo::PassFlag_ByVal, nullptr, nullptr, nullptr, nullptr); + info.AddParam(sizeof(SDKVector), PassInfo::PassType_Object, PassInfo::PassFlag_ByVal, nullptr, nullptr, nullptr, nullptr); + + auto test = new Function(info, + [](void* thisPtr, SDKVector vec1, int arg2, SDKVector vec2) { + // Virtual Function + g_called = true; + g_valid_arg[0] &= (vec1.x == 1.0 && vec1.y == 2.0 && vec1.z == 3.0); + g_valid_arg[1] &= (arg2 == 777); + g_valid_arg[2] &= (vec2.x == 4.0 && vec2.y == 5.0 && vec2.z == 6.0); + g_valid_arg[3] &= (testThisPtr == thisPtr); + }, + [](void* thisPtr, SDKVector vec1, int arg2, SDKVector vec2) { + // Pre Hook + g_valid_arg[0] &= (vec1.x == 1.0 && vec1.y == 2.0 && vec1.z == 3.0); + g_valid_arg[1] &= (arg2 == 777); + g_valid_arg[2] &= (vec2.x == 4.0 && vec2.y == 5.0 && vec2.z == 6.0); + g_valid_arg[3] &= (testThisPtr == thisPtr); + g_hook_pre_called = true; + RETURN_META(MRES_IGNORED); + }, + [](void* thisPtr, SDKVector vec1, int arg2, SDKVector vec2) { + // Post hook + g_valid_arg[0] &= (vec1.x == 1.0 && vec1.y == 2.0 && vec1.z == 3.0); + g_valid_arg[1] &= (arg2 == 777); + g_valid_arg[2] &= (vec2.x == 4.0 && vec2.y == 5.0 && vec2.z == 6.0); + g_valid_arg[3] &= (testThisPtr == thisPtr); + g_hook_post_called = true; + RETURN_META(MRES_IGNORED); + }); + testThisPtr = test; + + test->Test(SDKVector(1, 2, 3), 777, SDKVector(4, 5, 6)); + + sht_assert(g_called, "Original function was not called"); + sht_assert(g_hook_pre_called, "Pre hook was not called"); + sht_assert(g_hook_pre_called, "Post hook was not called"); + sht_assert(g_valid_arg[3], "This ptr was not consistent"); + sht_assert(g_valid_arg[0] && g_valid_arg[1] && g_valid_arg[2], "Arguments were not valid"); +}); +} +} \ No newline at end of file diff --git a/core/sourcehook/sourcehook_hookmangen_test.h b/core/sourcehook/sourcehook_hookmangen_test.h new file mode 100644 index 0000000..4c779ff --- /dev/null +++ b/core/sourcehook/sourcehook_hookmangen_test.h @@ -0,0 +1,57 @@ +#pragma once + +#include "sourcehook_impl.h" +#include "sourcehook_hookmangen.h" + +#include +#include +#include + +extern SourceHook::ISourceHook* g_SHPtr; +extern SourceMM::PluginId g_PLID; + +namespace SourceHook { +namespace Impl { +void run_tests(); + +class SDKVector +{ +public: + SDKVector(float x1, float y1, float z1) + { + this->x = x1; + this->y = y1; + this->z = z1; + } + SDKVector(void) + { + this->x = 0.0; + this->y = 0.0; + this->z = 0.0; + } + float x; + float y; + float z; +}; + +class SHTException : public std::exception { +public: + SHTException(const char* message) : std::exception(message) {}; +}; + +class SHT { +public: + SHT(const char* name, void (*func)(void)) noexcept : m_function(func), m_name(name) { + m_tests.push_back(this); + }; + + void Call() { m_function(); } + const std::string& GetName() { return m_name; } + + static std::vector m_tests; +private: + void (*m_function)(void); + std::string m_name; +}; +} +} \ No newline at end of file diff --git a/core/sourcehook/sourcehook_hookmangen_x86_64.cpp b/core/sourcehook/sourcehook_hookmangen_x86_64.cpp new file mode 100644 index 0000000..68ec51c --- /dev/null +++ b/core/sourcehook/sourcehook_hookmangen_x86_64.cpp @@ -0,0 +1,1488 @@ +/* ======== SourceHook ======== +* Copyright (C) 2024 Metamod:Source Development Team +* No warranties of any kind +* +* License: zlib/libpng +* +* Author(s): Andre "Kenzzer" Benoist +* ============================ +*/ + +// recommended literature: +// https://www.felixcloutier.com/x86/ +// http://ref.x86asm.net/coder64.html +// https://defuse.ca/online-x86-assembler.htm +// https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention +// https://refspecs.linuxbase.orgz/elf/x86_64-abi-0.99.pdf + +#include +#include +#include "sourcehook_impl.h" +#include "sourcehook_hookmangen.h" +#include "sourcehook_hookmangen_x86_64.h" +#include "sourcehook_pibuilder.h" + +#include "metamod_oslink.h" +#include "metamod.h" +#include +#include +#include +#include + +extern SourceHook::ISourceHook *g_SHPtr; +extern SourceMM::IMetamodSourceProvider *provider; + +#if SH_COMP == SH_COMP_MSVC +# define GCC_ONLY(x) +# define MSVC_ONLY(x) x +#elif SH_COMP == SH_COMP_GCC +# define GCC_ONLY(x) x +# define MSVC_ONLY(x) +#endif + +namespace SourceHook +{ + namespace Impl + { + void PrintDebug(x64JitWriter& jit, const char* message) { + static MemFuncInfo mfi = {false, -1, -1, -1}; + if (mfi.vtblindex == -1) + { + GetFuncInfo(&SourceMM::IMetamodSourceProvider::ConsolePrint, mfi); + if (!mfi.isVirtual || mfi.thisptroffs != 0 || mfi.vtbloffs != 0 || mfi.vtblindex < 0) + { + mfi.vtblindex = -1; + SH_ASSERT(0, ("Couldn't retrieve details of SourceMM::IMetamodSourceProvider::ConsolePrint!")); + return; + } + } + + static MemFuncInfo mfi2 = {false, -1, -1, -1}; + if (mfi2.vtblindex == -1) + { + GetFuncInfo(&SourceMM::IMetamodSourceProvider::LogMessage, mfi2); + if (!mfi2.isVirtual || mfi2.thisptroffs != 0 || mfi2.vtbloffs != 0 || mfi2.vtblindex < 0) + { + mfi2.vtblindex = -1; + SH_ASSERT(0, ("Couldn't retrieve details of SourceMM::IMetamodSourceProvider::ConsolePrint!")); + return; + } + } + + // Shadow space + MSVC_ONLY(jit.sub(rsp, 40)); + + MSVC_ONLY(jit.mov(rcx, reinterpret_cast(provider))); + GCC_ONLY(jit.mov(rdi, reinterpret_cast(provider))); + + MSVC_ONLY(jit.mov(rdx, reinterpret_cast(message))); + GCC_ONLY(jit.mov(rsi, reinterpret_cast(message))); + + jit.mov(rax, reinterpret_cast(provider)); + jit.mov(rax, rax(mfi.vtbloffs)); + jit.mov(rax, rax(sizeof(void*) * mfi.vtblindex)); + jit.call(rax); + + MSVC_ONLY(jit.mov(rcx, reinterpret_cast(provider))); + GCC_ONLY(jit.mov(rdi, reinterpret_cast(provider))); + + MSVC_ONLY(jit.mov(rdx, reinterpret_cast(message))); + GCC_ONLY(jit.mov(rsi, reinterpret_cast(message))); + + jit.mov(rax, reinterpret_cast(provider)); + jit.mov(rax, rax(mfi2.vtbloffs)); + jit.mov(rax, rax(sizeof(void*) * mfi2.vtblindex)); + jit.call(rax); + + // Free shadow space + MSVC_ONLY(jit.add(rsp, 40)); + } + + CPageAlloc GenBuffer::ms_Allocator(16); + + x64GenContext::x64GenContext() + : m_GeneratedPubFunc(nullptr), m_VtblOffs(0), + m_VtblIdx(666), m_SHPtr((ISourceHook*)0x1122334455667788), m_pHI(nullptr), m_HookfuncVfnptr(nullptr), m_HookFunc_FrameOffset(0), m_HookFunc_FrameVarsSize(0) { + m_pHI = new void*; + *m_pHI = (void*)0x77777777; + m_HookfuncVfnptr = new void*; + m_BuiltPI = new ProtoInfo; + m_BuiltPI_Params = nullptr; + m_BuiltPI_Params2 = nullptr; + } + + x64GenContext::x64GenContext(const ProtoInfo *proto, int vtbl_offs, int vtbl_idx, ISourceHook *pSHPtr) + : m_GeneratedPubFunc(nullptr), m_OrigProto(proto), m_Proto(proto), m_VtblOffs(vtbl_offs), + m_VtblIdx(vtbl_idx), m_SHPtr(pSHPtr), m_pHI(nullptr), m_HookfuncVfnptr(nullptr), m_HookFunc_FrameOffset(0), m_HookFunc_FrameVarsSize(0) + { + m_pHI = new void*; + *m_pHI = (void*)0x77777777; // Magic number for debugging + m_HookfuncVfnptr = new void*; + m_BuiltPI = new ProtoInfo; + m_BuiltPI_Params = nullptr; + m_BuiltPI_Params2 = nullptr; + } + + x64GenContext::~x64GenContext() + { + //Clear(); + delete m_pHI; + delete m_HookfuncVfnptr; + delete m_BuiltPI; + } + + void x64GenContext::Clear() + { + m_HookFunc.clear(); + m_PubFunc.clear(); + if (m_BuiltPI_Params) + { + delete [] m_BuiltPI_Params; + m_BuiltPI_Params = NULL; + } + if (m_BuiltPI_Params2) + { + delete [] m_BuiltPI_Params2; + m_BuiltPI_Params2 = NULL; + } + } + + void x64GenContext::BuildProtoInfo() + { + m_BuiltPI->convention = m_Proto.GetConvention(); + m_BuiltPI->numOfParams = m_Proto.GetNumOfParams(); + + m_BuiltPI->retPassInfo.size = m_Proto.GetRet().size; + m_BuiltPI->retPassInfo.type = m_Proto.GetRet().type; + m_BuiltPI->retPassInfo.flags = m_Proto.GetRet().flags; + m_BuiltPI->retPassInfo2.pNormalCtor = m_Proto.GetRet().pNormalCtor; + m_BuiltPI->retPassInfo2.pCopyCtor = m_Proto.GetRet().pCopyCtor; + m_BuiltPI->retPassInfo2.pDtor = m_Proto.GetRet().pDtor; + m_BuiltPI->retPassInfo2.pAssignOperator = m_Proto.GetRet().pAssignOperator; + + if (m_BuiltPI_Params) + delete m_BuiltPI_Params; + m_BuiltPI_Params = new PassInfo[m_BuiltPI->numOfParams + 1]; + if (m_BuiltPI_Params2) + delete m_BuiltPI_Params2; + m_BuiltPI_Params2 = new PassInfo::V2Info[m_BuiltPI->numOfParams + 1]; + + m_BuiltPI_Params[0].size = 1; // Version 1 + m_BuiltPI_Params[0].type = 0; + m_BuiltPI_Params[0].flags = 0; + + for (int i = 0; i < m_Proto.GetNumOfParams(); ++i) + { + m_BuiltPI_Params[i+1].size = m_Proto.GetParam(i).size; + m_BuiltPI_Params[i+1].type = m_Proto.GetParam(i).type; + m_BuiltPI_Params[i+1].flags = m_Proto.GetParam(i).flags; + + m_BuiltPI_Params2[i+1].pNormalCtor = m_Proto.GetParam(i).pNormalCtor; + m_BuiltPI_Params2[i+1].pCopyCtor = m_Proto.GetParam(i).pCopyCtor; + m_BuiltPI_Params2[i+1].pDtor = m_Proto.GetParam(i).pDtor; + m_BuiltPI_Params2[i+1].pAssignOperator = m_Proto.GetParam(i).pAssignOperator; + } + + m_BuiltPI->paramsPassInfo = m_BuiltPI_Params; + m_BuiltPI->paramsPassInfo2 = m_BuiltPI_Params2; + } + + std::int32_t x64GenContext::AddVarToFrame(std::int32_t size) + { + m_HookFunc_FrameOffset -= size; + m_HookFunc_FrameVarsSize += size; + return m_HookFunc_FrameOffset; + } + + std::int32_t x64GenContext::ComputeVarsSize() + { + return m_HookFunc_FrameVarsSize; + } + + std::int32_t x64GenContext::GetRealSize(const IntPassInfo& info) + { + if ((info.flags & PassInfo::PassFlag_ByRef) == PassInfo::PassFlag_ByRef) { + return SIZE_PTR; + } + return static_cast(info.size); + } + + std::int32_t x64GenContext::AlignSize(std::int32_t x, std::int32_t boundary) + { + if (x % boundary != 0) + x = (x & ~(boundary-1)) + boundary; + return x; + } + + // Computes size on the stack + std::int32_t x64GenContext::GetParamStackSize(const IntPassInfo &info) + { + // Align up to 4 byte boundaries + return AlignSize(GetRealSize(info), 8); + } + + HookManagerPubFunc x64GenContext::Generate() + { + Clear(); + + // Check conditions: + // -1) good proto version + // 0) we don't support unknown passtypes, convention, ... + // 1) we don't support functions which return objects by value or take parameters by value + // that have a constructor, a destructor or an overloaded assignment op + // (we wouldn't know how to call it!) + + if (m_Proto.GetVersion() < 1) + { + return nullptr; + } + + // Detect the pass flags (if they're missing) for return and parameters type + AutoDetectRetType(); + AutoDetectParamFlags(); + + // Calling conventions are gone on x86_64, there's only one to call all functions + // however act as if they still exist to avoid code duplication on the user's side + // TO-DO: Handle microsoft's vectorcall + if ((m_Proto.GetConvention() & (~ProtoInfo::CallConv_HasVafmt)) != ProtoInfo::CallConv_ThisCall) + { + return nullptr; + } + + // Non void return, ensure we support it + if (m_Proto.GetRet().size != 0 && !PassInfoSupported(m_Proto.GetRet(), true)) + { + return nullptr; + } + + // Ensure we support each param + for (int i = 0; i < m_Proto.GetNumOfParams(); ++i) + { + if (!PassInfoSupported(m_Proto.GetParam(i), false)) + { + return nullptr; + } + } + + BuildProtoInfo(); + GenerateHookFunc(); + return fastdelegate::detail::horrible_cast(GeneratePubFunc()); + } + + bool x64GenContext::PassInfoSupported(const IntPassInfo& pi, bool is_ret) + { + if (pi.type != PassInfo::PassType_Basic && + pi.type != PassInfo::PassType_Float && + pi.type != PassInfo::PassType_Object) { + return false; + } + + if (pi.type == PassInfo::PassType_Object && + (pi.flags & PassInfo::PassFlag_ByVal)) { + if ((pi.flags & PassInfo::PassFlag_CCtor) && !pi.pCopyCtor) { + return false; + } + + if ((pi.flags & PassInfo::PassFlag_ODtor) && !pi.pDtor) { + return false; + } + + if ((pi.flags & PassInfo::PassFlag_AssignOp) && !pi.pAssignOperator) { + return false; + } + + if ((pi.flags & PassInfo::PassFlag_OCtor) && !pi.pNormalCtor) { + return false; + } + } + + if ((pi.flags & (PassInfo::PassFlag_ByVal | PassInfo::PassFlag_ByRef)) == 0) { + return false; // Neither byval nor byref! + } + return true; + } + + void* x64GenContext::GenerateHookFunc() + { + const auto& retInfo = m_Proto.GetRet(); + m_HookFunc.breakpoint(); + + // For the time being, we only consider xmm0-xmm15 registers + // are only used to store 64bits worth of data, despite being + // able to store up to 128bits + + // RBP is a general purpose register on x86_64 + // we will therefore use it on both linux and windows + // to refer to our space in the stack where we grew + + // *********** stack frame ************* + + // MSVC ONLY START + // rbp + 40 end of shadow space + // rbp + 8 start of shadow space + // MSVC ONLY END + // + // rbp - 0 begining of (old) rsp + // rbp - 8 saved old rbp value + // rbp - 16 vfnptr_origentry + // rbp - 24 status + // rbp - 32 prev_res + // rbp - 40 cur_res + // rbp - 48 iter + // rbp - 56 context + // rbp - 64 this ptr + // [Non void functions:] + // rbp - 64 - sizeof(returntype) original return + // rbp - 64 - sizeof(returntype) * 2 override return + // rbp - 64 - sizeof(returntype) * 3 plugin return + + const std::int8_t v_original_rbp = AddVarToFrame(SIZE_PTR); // -8 + const std::int8_t v_vfnptr_origentry = AddVarToFrame(SIZE_PTR); // -16 + const std::int8_t v_status = AddVarToFrame(SIZE_PTR /*sizeof(META_RES)*/); // -24 + const std::int8_t v_prev_res = AddVarToFrame(SIZE_PTR /*sizeof(META_RES)*/); // -32 + const std::int8_t v_cur_res = AddVarToFrame(SIZE_PTR /*sizeof(META_RES)*/); // -40 + const std::int8_t v_iter = AddVarToFrame(SIZE_PTR); // -48 + const std::int8_t v_pContext = AddVarToFrame(SIZE_PTR); // -56 + const std::int8_t v_this = AddVarToFrame(SIZE_PTR); // -64 + + // Non void return, track the values + std::int32_t v_ret_ptr = 0; + std::int32_t v_memret_ptr = 0; + std::int32_t v_orig_ret = 0; + std::int32_t v_override_ret = 0; + std::int32_t v_plugin_ret = 0; + std::int32_t v_mem_ret = 0; + if (m_Proto.GetRet().size != 0) + { + v_ret_ptr = AddVarToFrame(SIZE_PTR); + v_memret_ptr = AddVarToFrame(SIZE_PTR); + v_orig_ret = AddVarToFrame(AlignSize(GetParamStackSize(retInfo), 16)); // 16 bytes aligned + v_override_ret = AddVarToFrame(AlignSize(GetParamStackSize(retInfo), 16)); + v_plugin_ret = AddVarToFrame(AlignSize(GetParamStackSize(retInfo), 16)); + v_mem_ret = AddVarToFrame(AlignSize(GetParamStackSize(retInfo), 16)); + } + + std::int32_t stack_frame_size = ComputeVarsSize(); + m_HookFunc.sub(rsp, stack_frame_size); + + // Store rbp where it should be + m_HookFunc.mov(rsp(stack_frame_size - SIZE_PTR), rbp); + m_HookFunc.lea(rbp, rsp(stack_frame_size)); + + // MSVC ONLY - Save the registers into shadow space +#if SH_COMP == SH_COMP_MSVC + const x86_64_Reg params_reg[] = { rcx, rdx, r8, r9 }; + const x86_64_FloatReg params_floatreg[] = { xmm0, xmm1, xmm2, xmm3 }; + + int reg_index = 0; + + // retrieve this ptr + m_HookFunc.mov(rbp(v_this), params_reg[reg_index]); + m_HookFunc.mov(rbp(reg_index * 8 + 8), params_reg[reg_index]); + reg_index++; + + // Non standard return size, a ptr has been passed into rcx. Shifting all the parameters + if ((retInfo.flags & PassInfo::PassFlag_RetMem) == PassInfo::PassFlag_RetMem) { + m_HookFunc.mov(rbp(reg_index * 8 + 8), params_reg[reg_index]); + m_HookFunc.mov(rbp(v_memret_ptr), params_reg[reg_index]); + reg_index++; + } + + m_HookFunc.mov(rax, m_Proto.GetNumOfParams()); + m_HookFunc.mov(rax, reg_index); + m_HookFunc.mov(rax, retInfo.size); + + for (int i = 0; i < m_Proto.GetNumOfParams() && reg_index < 4; reg_index++, i++) { + auto& info = m_Proto.GetParam(i); + if (info.type == PassInfo::PassType_Float && (info.flags & PassInfo::PassFlag_ByRef) != PassInfo::PassFlag_ByRef) { + m_HookFunc.movsd(rbp(reg_index * 8 + 8), params_floatreg[reg_index]); + } else { + m_HookFunc.mov(rbp(reg_index * 8 + 8), params_reg[reg_index]); + } + } +#else +static_assert(false, "Missing registers saving for linux"); +#endif + + // From this point on, no matter what. RSP should be aligned on 16 bytes boundary + + // If return value has a constructor, call it + if ((retInfo.flags & PassInfo::PassFlag_ByVal) && retInfo.pNormalCtor != nullptr) + { + std::int32_t v_ret_vals[] = {v_orig_ret, v_override_ret, v_plugin_ret}; + + for (int i = 0; i < 3; i++) { + // Shadow space + MSVC_ONLY(m_HookFunc.sub(rsp, 40)); + + // First param is this + MSVC_ONLY(m_HookFunc.lea(rcx, rbp(v_ret_vals[i]))); + GCC_ONLY(m_HookFunc.lea(rdi, rbp(v_ret_vals[i]))); + + // We've saved (or not) r8 value, use the freed register to store function ptr + m_HookFunc.mov(r8, reinterpret_cast(retInfo.pNormalCtor)); + m_HookFunc.call(r8); + + // Free shadow space + MSVC_ONLY(m_HookFunc.add(rsp, 40)); + } + } + + m_HookFunc.mov(rbp(v_status), MRES_IGNORED); + m_HookFunc.mov(rbp(v_cur_res), MRES_IGNORED); + m_HookFunc.mov(rbp(v_prev_res), MRES_IGNORED); + + // ********************** SetupHookLoop ********************** + //PrintDebug(m_HookFunc, "Call - SetupHookLoop\n"); + + CallSetupHookLoop(v_orig_ret, v_override_ret, v_cur_res, v_prev_res, v_status, v_vfnptr_origentry, + v_this, v_pContext); + + //PrintDebug(m_HookFunc, "Call - SetupHookLoop - END\n"); + + // ********************** call pre hooks ********************** + //PrintDebug(m_HookFunc, "Call - CallHooks [PRE]\n"); + + GenerateCallHooks(v_status, v_prev_res, v_cur_res, v_iter, v_pContext, v_plugin_ret, v_mem_ret); + + //PrintDebug(m_HookFunc, "Call - CallHooks [PRE] - END\n"); + + // ********************** call orig func ********************** + //PrintDebug(m_HookFunc, "Call - CallOrig\n"); + + GenerateCallOrig(v_status, v_pContext, v_this, v_vfnptr_origentry, v_orig_ret, v_override_ret, v_mem_ret); + + //PrintDebug(m_HookFunc, "Call - CallOrig - END\n"); + + // ********************** call post hooks ********************** + //PrintDebug(m_HookFunc, "Call - Hooks [POST]\n"); + + GenerateCallHooks(v_status, v_prev_res, v_cur_res, v_iter, v_pContext, v_plugin_ret, v_mem_ret); + + //PrintDebug(m_HookFunc, "Call - Hooks [POST] - END\n"); + + // ********************** end context and return ********************** + + PrepareReturn(v_status, v_pContext, v_ret_ptr); + + CallEndContext(v_pContext); + + // Call destructors of byval object params which have a destructor +#if SH_COMP == SH_COMP_MSVC + int stack_index = 1; // account this pointer + if ((retInfo.flags & PassInfo::PassFlag_RetMem) == PassInfo::PassFlag_RetMem) { + // Non trivial return value + stack_index++; + } + + for (int i = 0; i < m_Proto.GetNumOfParams(); ++i, ++stack_index) { + // Shadow space + MSVC_ONLY(m_HookFunc.sub(rsp, 40)); + + const IntPassInfo &pi = m_Proto.GetParam(i); + if (pi.type == PassInfo::PassType_Object && (pi.flags & PassInfo::PassFlag_ODtor) && + (pi.flags & PassInfo::PassFlag_ByVal)) { + // Every non trivial types are passed as a pointer to a special dedicated space + MSVC_ONLY(m_HookFunc.mov(rcx, rbp(8 + stack_index * 8))); + GCC_ONLY(m_HookFunc.mov(rdi, rbp(8 + stack_index * 8))); + + m_HookFunc.mov(rax, reinterpret_cast(pi.pDtor)); + m_HookFunc.call(rax); + } + + // Free shadow space + MSVC_ONLY(m_HookFunc.add(rsp, 40)); + } +#else +static_assert(false, "Missing parameters destruction for linux"); +#endif + + DoReturn(v_ret_ptr, v_memret_ptr); + // From then on, rax cannot be used as a general register + // Use r8 or r9 instead + + // If return value type has a destructor, call it + if ((retInfo.flags & PassInfo::PassFlag_ByVal) && retInfo.pDtor != nullptr) + { + std::int32_t v_ret_vals[] = {v_orig_ret, v_override_ret, v_plugin_ret}; + + // Shadow space + MSVC_ONLY(m_HookFunc.sub(rsp, 40)); + for (int i = 0; i < 3; i++) { + // First param is this + MSVC_ONLY(m_HookFunc.lea(rcx, rbp(v_ret_vals[i]))); + GCC_ONLY(m_HookFunc.lea(rdi, rbp(v_ret_vals[i]))); + + m_HookFunc.mov(r8, reinterpret_cast(retInfo.pDtor)); + m_HookFunc.call(r8); + } + // Free shadow space + MSVC_ONLY(m_HookFunc.add(rsp, 40)); + } + + // Restore rbp + m_HookFunc.mov(rbp, rbp(v_original_rbp)); + // Free the stack frame + m_HookFunc.add(rsp, stack_frame_size); + + m_HookFunc.retn(); + + // Store pointer for later use + // m_HookfuncVfnPtr is a pointer to a void* because SH expects a pointer + // into the hookman's vtable + *m_HookfuncVfnptr = reinterpret_cast(m_HookFunc.GetData()); + + m_HookFunc.SetRE(); + + return m_HookFunc.GetData(); + } + + void x64GenContext::CallSetupHookLoop(int v_orig_ret, int v_override_ret, + int v_cur_res, int v_prev_res, int v_status, int v_vfnptr_origentry, + int v_this, int v_pContext) + { + // IHookContext *shptr->SetupHookLoop(IHookManagerInfo *hi, void *vfnptr, void *thisptr, void **origCallAddr, META_RES *statusPtr, + // META_RES *prevResPtr, META_RES *curResPtr, const void *origRetPtr, void *overrideRetPtr); + + static MemFuncInfo mfi = {false, -1, -1, -1}; + if (mfi.vtblindex == -1) + { + GetFuncInfo(&ISourceHook::SetupHookLoop, mfi); + // The function is somehow not virtual, or has a non trivial this ptr + if (!mfi.isVirtual || mfi.thisptroffs != 0 || mfi.vtbloffs != 0 || mfi.vtblindex < 0) + { + mfi.vtblindex = -1; // Ensure we go through there again on subsequent calls + SH_ASSERT(0, ("Couldn't retrieve details of ISourceHook::SetupHookLoop!")); + return; + } + } + + // Allocate the necessary stack space + MSVC_ONLY(m_HookFunc.sub(rsp, 88)); // shadow space (32 bytes) + 6 stack arguments (48 bytes) + 8 bytes + + // 1st parameter (this) + GCC_ONLY(m_HookFunc.mov(rdi, reinterpret_cast(m_SHPtr))); + MSVC_ONLY(m_HookFunc.mov(rcx, reinterpret_cast(m_SHPtr))); + // 2nd parameter - IHookManagerInfo* hi + GCC_ONLY(m_HookFunc.mov(rsi, reinterpret_cast(m_pHI))); + GCC_ONLY(m_HookFunc.mov(rsi, rsi())); + MSVC_ONLY(m_HookFunc.mov(rdx, reinterpret_cast(m_pHI))); + MSVC_ONLY(m_HookFunc.mov(rdx, rdx())); + // 3rd parameter - void* vfnptr + GCC_ONLY(m_HookFunc.mov(rdx, rbp(v_this))); + GCC_ONLY(m_HookFunc.mov(rdx, rdx(m_VtblOffs))); // *(this + m_VtblOffs) + GCC_ONLY(m_HookFunc.add(rdx, SIZE_PTR * m_VtblIdx)); // vtable + m_VtblIdx + + MSVC_ONLY(m_HookFunc.mov(r8, rbp(v_this))); + MSVC_ONLY(m_HookFunc.mov(r8, r8(m_VtblOffs))); // *(this + m_VtblOffs) + MSVC_ONLY(m_HookFunc.add(r8, SIZE_PTR * m_VtblIdx)); // vtable + m_VtblIdx + // 4th parameter - void* thisptr + GCC_ONLY(m_HookFunc.mov(rcx, rbp(v_this))); + MSVC_ONLY(m_HookFunc.mov(r9, rbp(v_this))); + // 5th argument - void** original call address + GCC_ONLY(m_HookFunc.lea(r8, rbp(v_vfnptr_origentry))); + MSVC_ONLY(m_HookFunc.lea(rax, rbp(v_vfnptr_origentry))); + MSVC_ONLY(m_HookFunc.mov(rsp(0x20), rax)); + // 6th argument - META_RES* statusPtr + GCC_ONLY(m_HookFunc.lea(r9, rbp(v_status))); + MSVC_ONLY(m_HookFunc.lea(rax, rbp(v_status))); + MSVC_ONLY(m_HookFunc.mov(rsp(0x28), rax)); + // 7th argument - META_RES* prevResPtr + MSVC_ONLY(m_HookFunc.lea(rax, rbp(v_prev_res))); + MSVC_ONLY(m_HookFunc.mov(rsp(0x30), rax)); + // 8th argument - META_RES* curResPtr + MSVC_ONLY(m_HookFunc.lea(rax, rbp(v_cur_res))); + MSVC_ONLY(m_HookFunc.mov(rsp(0x38), rax)); + if (m_Proto.GetRet().size == 0) // void return function + { + // nullptr + m_HookFunc.xor(rax, rax); + // 9th argument - const void* origRetPtr + MSVC_ONLY(m_HookFunc.mov(rsp(0x40), rax)); + // 10th argument - void* overrideRetPtr + MSVC_ONLY(m_HookFunc.mov(rsp(0x48), rax)); + } + else + { + // 9th argument - const void* origRetPtr + MSVC_ONLY(m_HookFunc.lea(rax, rbp(v_orig_ret))); + MSVC_ONLY(m_HookFunc.mov(rsp(0x40), rax)); + // 10th argument - void* overrideRetPtr + MSVC_ONLY(m_HookFunc.lea(rax, rbp(v_override_ret))); + MSVC_ONLY(m_HookFunc.mov(rsp(0x48), rax)); + } + + // Retrieve the function address + m_HookFunc.mov(rax, (*reinterpret_cast(m_SHPtr))[mfi.vtblindex]); + m_HookFunc.call(rax); + + // Store the return value + m_HookFunc.mov(rbp(v_pContext), rax); + + // Restore the rsp value + MSVC_ONLY(m_HookFunc.add(rsp, 88)); + } + + // Extension of MAKE_DELEG macro + struct IMyDelegate : ::SourceHook::ISHDelegate { virtual void Call() = 0; }; + + void x64GenContext::GenerateCallHooks(int v_status, int v_prev_res, int v_cur_res, int v_iter, + int v_pContext, int v_plugin_ret, int v_mem_ret) + { + static MemFuncInfo getNext = {false, -1, -1, -1}; + if (getNext.vtblindex == -1) + { + GetFuncInfo(&IHookContext::GetNext, getNext); + // The function is somehow not virtual, or has a non trivial this ptr + if (!getNext.isVirtual || getNext.thisptroffs != 0 || getNext.vtbloffs != 0 || getNext.vtblindex < 0) + { + getNext.vtblindex = -1; // Ensure we go through there again on subsequent calls + SH_ASSERT(0, ("Unexpected compilation of IHookContext::GetNext!")); + return; + } + } + + static MemFuncInfo callMfi = {false, -1, -1, -1}; + if (callMfi.vtblindex == -1) + { + GetFuncInfo(&IMyDelegate::Call, callMfi); + // The function is somehow not virtual, or has a non trivial this ptr + if (!callMfi.isVirtual || callMfi.thisptroffs != 0 || callMfi.vtbloffs != 0 || callMfi.vtblindex < 0) + { + callMfi.vtblindex = -1; // Ensure we go through there again on subsequent calls + SH_ASSERT(0, ("Unexpected compilation of IMyDelegate::Call!")); + return; + } + } + + static MemFuncInfo getOverrideRetPtrMfi = {false, -1, -1, -1}; + if (getOverrideRetPtrMfi.vtblindex == -1) + { + GetFuncInfo(&IHookContext::GetOverrideRetPtr, getOverrideRetPtrMfi); + // The function is somehow not virtual, or has a non trivial this ptr + if (!getOverrideRetPtrMfi.isVirtual || getOverrideRetPtrMfi.thisptroffs != 0 || getOverrideRetPtrMfi.vtbloffs != 0 || getOverrideRetPtrMfi.vtblindex < 0) + { + getOverrideRetPtrMfi.vtblindex = -1; // Ensure we go through there again on subsequent calls + SH_ASSERT(0, ("Unexpected compilation of IHookContext::GetOverrideRetPtr!")); + return; + } + } + + //prev_res = MRES_IGNORED; + //while ( (iter = static_cast(pContext->GetNext())) ) + //{ + // cur_res = MRES_IGNORED; + // plugin_ret = iter->Call params; + // prev_res = cur_res; + // if (cur_res > status) + // status = cur_res; + // if (cur_res >= MRES_OVERRIDE) + // *reinterpret_cast(pContext->GetOverrideRetPtr()) = plugin_ret; + //} + + // prev_res = MRES_IGNORED; + //m_HookFunc.breakpoint(); + + m_HookFunc.mov(rbp(v_prev_res), MRES_IGNORED); + + auto startLoop = m_HookFunc.get_outputpos(); + // while ( (iter = static_cast(pContext->GetNext())) ) + m_HookFunc.mov(rax, rbp(v_pContext)); + m_HookFunc.mov(rax, rax()); // *this (vtable) + m_HookFunc.mov(rax, rax(getNext.vtblindex * SIZE_PTR)); // vtable[vtblindex] + + // Shadow space 32 bytes + 8 bytes + MSVC_ONLY(m_HookFunc.sub(rsp, 40)); + + GCC_ONLY(m_HookFunc.mov(rdi, rbp(v_pContext))); + MSVC_ONLY(m_HookFunc.mov(rcx, rbp(v_pContext))); + m_HookFunc.call(rax); // pContext->GetNext() + + // store into iter + m_HookFunc.mov(rbp(v_iter), rax); + + MSVC_ONLY(m_HookFunc.add(rsp, 40)); + + // null check iter + m_HookFunc.test(rax, rax); + m_HookFunc.jz(0x0); // Leave loop if nullptr + std::int32_t jumpOff = m_HookFunc.get_outputpos(); + + // cur_res = MRES_IGNORED; + m_HookFunc.mov(rbp(v_cur_res), MRES_IGNORED); + + // prev_res = cur_res; + m_HookFunc.mov(rax, rbp(v_cur_res)); + m_HookFunc.mov(rbp(v_prev_res), rax); + + // call + std::int32_t stackSpace = PushParameters(v_iter, MemRetWithTempObj() ? v_mem_ret : v_plugin_ret); + m_HookFunc.mov(rax, rbp(v_iter)); + m_HookFunc.mov(rax, rax()); // *this (vtable) + m_HookFunc.mov(rax, rax(callMfi.vtblindex * SIZE_PTR)); // vtable[vtblindex] iter -> Call + m_HookFunc.call(rax); + // epilog free the stack + m_HookFunc.add(rsp, stackSpace); + + SaveReturnValue(v_mem_ret, v_plugin_ret); + + // if (cur_res > status) + m_HookFunc.mov(rax, rbp(v_cur_res)); + m_HookFunc.cmp(rax, rbp(v_status)); + // status = cur_res; + m_HookFunc.mov(rax, rbp(v_status)); + m_HookFunc.cmovg(rax, rbp(v_cur_res)); + m_HookFunc.mov(rbp(v_status), rax); + + // Are we dealing with a non void function ? + auto retInfo = m_Proto.GetRet(); + if (retInfo.size != 0) + { + // if (cur_res >= MRES_OVERRIDE) + m_HookFunc.mov(rax, MRES_OVERRIDE); + m_HookFunc.cmp(rbp(v_cur_res), rax); + + m_HookFunc.jl(0x0); + std::int32_t earlyLoopBack = m_HookFunc.get_outputpos() - startLoop; + m_HookFunc.rewrite(m_HookFunc.get_outputpos() - sizeof(std::int32_t), -earlyLoopBack); + + // Shadow space 32 bytes + 8 bytes + MSVC_ONLY(m_HookFunc.sub(rsp, 40)); + + m_HookFunc.mov(rax, rbp(v_pContext)); + m_HookFunc.mov(rax, rax()); // *this (vtable) + m_HookFunc.mov(rax, rax(getOverrideRetPtrMfi.vtblindex * SIZE_PTR)); // vtable[vtblindex] + + GCC_ONLY(m_HookFunc.mov(rdi, rbp(v_pContext))); + MSVC_ONLY(m_HookFunc.mov(rcx, rbp(v_pContext))); + m_HookFunc.call(rax); // pContext->GetOverrideRetPtr() + + MSVC_ONLY(m_HookFunc.add(rsp, 40)); + + // *reinterpret_cast(pContext->GetOverrideRetPtr()) = plugin_ret; + + // byref is always a pointer underneath + if (retInfo.flags & PassInfo::PassFlag_ByRef) + { + m_HookFunc.mov(r8, rbp(v_plugin_ret)); + m_HookFunc.mov(rax(), r8); + } + else + { + // custom assignment operator, so call it + if (retInfo.pAssignOperator) + { + // Shadow space 32 bytes + 8 bytes + MSVC_ONLY(m_HookFunc.sub(rsp, 40)); + + // 1st parameter (this) + GCC_ONLY(m_HookFunc.mov(rdi, rax)); + MSVC_ONLY(m_HookFunc.mov(rcx, rax)); + + // 2nd parameter (copy) + GCC_ONLY(m_HookFunc.lea(rsi, rbp(v_plugin_ret))); + MSVC_ONLY(m_HookFunc.lea(rdx, rbp(v_plugin_ret))); + + // Move address and call + m_HookFunc.mov(rax, reinterpret_cast(retInfo.pAssignOperator)); + m_HookFunc.call(rax); + + MSVC_ONLY(m_HookFunc.add(rsp, 40)); + } + else + { + m_HookFunc.push(rdi); + m_HookFunc.push(rsi); + m_HookFunc.push(rcx); + + m_HookFunc.mov(rcx, retInfo.size); + m_HookFunc.mov(rdi, rax); + m_HookFunc.lea(rsi, rbp(v_plugin_ret)); + + m_HookFunc.rep_movs_bytes(); + + m_HookFunc.pop(rcx); + m_HookFunc.pop(rsi); + m_HookFunc.pop(rdi); + } + } + } + + m_HookFunc.jump(0x0); + std::int32_t loopBack = m_HookFunc.get_outputpos() - startLoop; + m_HookFunc.rewrite(m_HookFunc.get_outputpos() - sizeof(std::int32_t), -loopBack); + + m_HookFunc.rewrite(jumpOff - sizeof(std::int32_t), m_HookFunc.get_outputpos() - jumpOff); + } + + void x64GenContext::GenerateCallOrig(int v_status, int v_pContext, int v_this, int v_vfnptr_origentry, int v_orig_ret, int v_override_ret, int v_place_for_memret) + { + static MemFuncInfo shouldCallOrigMfi = {false, -1, -1, -1}; + if (shouldCallOrigMfi.vtblindex == -1) + { + GetFuncInfo(&IHookContext::ShouldCallOrig, shouldCallOrigMfi); + // The function is somehow not virtual, or has a non trivial this ptr + if (!shouldCallOrigMfi.isVirtual || shouldCallOrigMfi.thisptroffs != 0 || shouldCallOrigMfi.vtbloffs != 0 || shouldCallOrigMfi.vtblindex < 0) + { + shouldCallOrigMfi.vtblindex = -1; // Ensure we go through there again on subsequent calls + SH_ASSERT(0, ("Unexpected compilation of IHookContext::ShouldCallOrig!")); + return; + } + } + + //if (status != MRES_SUPERCEDE && pContext->ShouldCallOrig()) + //{ + // rettype (EmptyClass::*mfp)paramtypes; + // SH_SETUP_MFP(mfp); + // orig_ret = (reinterpret_cast(this)->*mfp)params; + //} + //else + // orig_ret = override_ret; + //m_HookFunc.breakpoint(); + + m_HookFunc.mov(rax, rbp(v_status)); + m_HookFunc.cmp(rax, MRES_SUPERCEDE); + m_HookFunc.je(0x0); + auto statusCmpOff = m_HookFunc.get_outputpos(); + + // Shadow space 32 bytes + 8 bytes + MSVC_ONLY(m_HookFunc.sub(rsp, 40)); + + m_HookFunc.mov(rax, rbp(v_pContext)); + + // 1st parameter (this) + GCC_ONLY(m_HookFunc.mov(rdi, rax)); + MSVC_ONLY(m_HookFunc.mov(rcx, rax)); + + m_HookFunc.mov(rax, rax()); + m_HookFunc.mov(rax, rax(SIZE_PTR * shouldCallOrigMfi.vtblindex)); + + m_HookFunc.call(rax); // pContext->ShouldCallOrig() + + MSVC_ONLY(m_HookFunc.add(rsp, 40)); + + // Don't have the lower register yet, so this will do for now + m_HookFunc.test(rax, 0x1); + m_HookFunc.jz(0x0); + auto shouldCallOff = m_HookFunc.get_outputpos(); + + // original call + std::int32_t stackSpace = PushParameters(v_this, MemRetWithTempObj() ? v_place_for_memret : v_orig_ret); + m_HookFunc.mov(rax, rbp(v_vfnptr_origentry)); + m_HookFunc.call(rax); + // epilog free the stack + m_HookFunc.add(rsp, stackSpace); + + SaveReturnValue(v_place_for_memret, v_orig_ret); + + m_HookFunc.jump(0x0); + auto callOriginalOff = m_HookFunc.get_outputpos(); + + // else + auto elseStartOff = m_HookFunc.get_outputpos(); + m_HookFunc.rewrite(statusCmpOff - sizeof(std::int32_t), static_cast(elseStartOff - statusCmpOff)); + m_HookFunc.rewrite(shouldCallOff - sizeof(std::int32_t), static_cast(elseStartOff - shouldCallOff)); + + auto retInfo = m_Proto.GetRet(); + if (retInfo.size != 0) + { + if (retInfo.flags & PassInfo::PassFlag_ByRef) + { + m_HookFunc.mov(rax, rbp(v_override_ret)); + m_HookFunc.mov(rbp(v_orig_ret), rax); + } + else + { + // custom assignment operator, so call it + if (retInfo.pAssignOperator) + { + // Shadow space 32 bytes + 8 bytes + MSVC_ONLY(m_HookFunc.sub(rsp, 40)); + + // 1st parameter (this) + GCC_ONLY(m_HookFunc.lea(rdi, rbp(v_orig_ret))); + MSVC_ONLY(m_HookFunc.lea(rcx, rbp(v_orig_ret))); + + // 2nd parameter (copy) + GCC_ONLY(m_HookFunc.lea(rsi, rbp(v_override_ret))); + MSVC_ONLY(m_HookFunc.lea(rdx, rbp(v_override_ret))); + + // Move address and call + m_HookFunc.mov(rax, reinterpret_cast(retInfo.pAssignOperator)); + m_HookFunc.call(rax); + + MSVC_ONLY(m_HookFunc.add(rsp, 40)); + } + else + { + m_HookFunc.push(rdi); + m_HookFunc.push(rsi); + m_HookFunc.push(rcx); + + m_HookFunc.mov(rcx, retInfo.size); + m_HookFunc.lea(rdi, rbp(v_orig_ret)); + m_HookFunc.lea(rsi, rbp(v_override_ret)); + + m_HookFunc.rep_movs_bytes(); + + m_HookFunc.pop(rcx); + m_HookFunc.pop(rsi); + m_HookFunc.pop(rdi); + } + } + } + + m_HookFunc.rewrite(callOriginalOff - sizeof(std::int32_t), static_cast(m_HookFunc.get_outputpos() - callOriginalOff)); + } + + std::int32_t x64GenContext::PushParameters(int v_this, int v_ret) + { + auto retInfo = m_Proto.GetRet(); + std::int32_t stackSpace = 0; + +#if SH_COMP == SH_COMP_MSVC + const x86_64_Reg params_reg[] = { rcx, rdx, r8, r9 }; + const x86_64_FloatReg params_floatreg[] = { xmm0, xmm1, xmm2, xmm3 }; + + int reg_index = 0; + + // setup this parameter + m_HookFunc.mov(params_reg[reg_index], rbp(v_this)); + reg_index++; + + // Non standard return + if (retInfo.size != 0 && (retInfo.flags & PassInfo::PassFlag_RetMem) == PassInfo::PassFlag_RetMem) { + m_HookFunc.lea(params_reg[reg_index], rbp(v_ret)); + reg_index++; + } + + // We've backed up the parameters into the shadow space + int parameter_index = 0; + for (; parameter_index < m_Proto.GetNumOfParams() && reg_index < 4; reg_index++, parameter_index++) { + auto& info = m_Proto.GetParam(parameter_index); + + if (info.type == PassInfo::PassType_Float && (info.flags & PassInfo::PassFlag_ByRef) != PassInfo::PassFlag_ByRef) { + m_HookFunc.movsd(params_floatreg[reg_index], rbp(reg_index * 8 + 8)); + } else { + m_HookFunc.mov(params_reg[reg_index], rbp(reg_index * 8 + 8)); + } + } + + // Allocate the shadow space + m_HookFunc.sub(rsp, 32); + stackSpace += 32; + + int parameters_on_stack = m_Proto.GetNumOfParams() - parameter_index; + m_HookFunc.sub(rsp, parameters_on_stack * 8); + stackSpace += parameters_on_stack * 8; + + // If this number is even we need to allocate an extra 8 bytes + if (parameters_on_stack % 2 == 0) { + m_HookFunc.sub(rsp, 8); + stackSpace += 8; + } + + for (int i = 0; parameter_index < m_Proto.GetNumOfParams(); parameter_index++, i++) { + m_HookFunc.mov(rax, rbp(40 + (8 * i))); // We need to skip the shadow space + return address + m_HookFunc.mov(rsp(32 + (8 * i)), rax); + } + + return stackSpace; +#else +static_assert(false, "Missing registers saving for linux"); +#endif + } + + void x64GenContext::SaveReturnValue(int v_mem_ret, int v_ret) { + const auto& retInfo = m_Proto.GetRet(); + // void, early return + if (retInfo.size == 0) { + return; + } +#if SH_COMP == SH_COMP_MSVC + if ((retInfo.flags & PassInfo::PassFlag_ByRef) == PassInfo::PassFlag_ByRef) { + m_HookFunc.mov(rbp(v_ret), rax); + return; + } + + // ByVal + + if (retInfo.type == PassInfo::PassType_Float) { + m_HookFunc.movsd(rbp(v_ret), xmm0); + } else if (retInfo.type == PassInfo::PassType_Basic) { + m_HookFunc.mov(rbp(v_ret), rax); + } else if ((retInfo.flags & PassInfo::PassFlag_RetMem) == PassInfo::PassFlag_RetMem) { + if (MemRetWithTempObj()) { + if (retInfo.pAssignOperator) { + // Shadow space 32 bytes + 8 bytes + MSVC_ONLY(m_HookFunc.sub(rsp, 40)); + + // 1st parameter (this) + GCC_ONLY(m_HookFunc.lea(rdi, rbp(v_ret))); + MSVC_ONLY(m_HookFunc.lea(rcx, rbp(v_ret))); + + // 2nd parameter (copy) + GCC_ONLY(m_HookFunc.lea(rsi, rbp(v_mem_ret))); + MSVC_ONLY(m_HookFunc.lea(rdx, rbp(v_mem_ret))); + + // Move address and call + m_HookFunc.mov(rax, reinterpret_cast(retInfo.pAssignOperator)); + m_HookFunc.call(rax); + + MSVC_ONLY(m_HookFunc.add(rsp, 40)); + } + else { + m_HookFunc.push(rdi); + m_HookFunc.push(rsi); + m_HookFunc.push(rcx); + + m_HookFunc.mov(rcx, retInfo.size); + m_HookFunc.lea(rdi, rbp(v_ret)); + m_HookFunc.lea(rsi, rbp(v_mem_ret)); + + m_HookFunc.rep_movs_bytes(); + + m_HookFunc.pop(rcx); + m_HookFunc.pop(rsi); + m_HookFunc.pop(rdi); + } + + if (retInfo.pDtor) { + // Shadow space 32 bytes + 8 bytes + MSVC_ONLY(m_HookFunc.sub(rsp, 40)); + + // 1st parameter (this) + GCC_ONLY(m_HookFunc.lea(rdi, rbp(v_mem_ret))); + MSVC_ONLY(m_HookFunc.lea(rcx, rbp(v_mem_ret))); + + // Move address and call + m_HookFunc.mov(rax, reinterpret_cast(retInfo.pDtor)); + m_HookFunc.call(rax); + + MSVC_ONLY(m_HookFunc.add(rsp, 40)); + } + + } else { + // Already copied in the proper location + return; + } + } else { + SH_ASSERT(0, ("Unknown handling of return type!")); + return; + } +#else + static_assert(false, "Missing SaveReturnValue for linux"); +#endif + } + + void x64GenContext::PrepareReturn(int v_status, int v_pContext, int v_retptr) + { + const auto& retInfo = m_Proto.GetRet(); + if (retInfo.size == 0) { + return; + } + + static MemFuncInfo getOverrideRetPtrMfi = {false, -1, -1, -1}; + if (getOverrideRetPtrMfi.vtblindex == -1) { + GetFuncInfo(&IHookContext::GetOverrideRetPtr, getOverrideRetPtrMfi); + if (!getOverrideRetPtrMfi.isVirtual || getOverrideRetPtrMfi.thisptroffs != 0 || getOverrideRetPtrMfi.vtbloffs != 0 || getOverrideRetPtrMfi.vtblindex < 0) { + getOverrideRetPtrMfi.vtblindex = -1; + SH_ASSERT(0, ("Unexpected compilation of IHookContext::GetOverrideRetPtr!")); + return; + } + } + + static MemFuncInfo getOrigRetPtrMfi = {false, -1, -1, -1}; + if (getOrigRetPtrMfi.vtblindex == -1) { + GetFuncInfo(&IHookContext::GetOrigRetPtr, getOrigRetPtrMfi); + if (!getOrigRetPtrMfi.isVirtual || getOrigRetPtrMfi.thisptroffs != 0 || getOrigRetPtrMfi.vtbloffs != 0 || getOrigRetPtrMfi.vtblindex < 0) { + getOrigRetPtrMfi.vtblindex = -1; + SH_ASSERT(0, ("Unexpected compilation of IHookContext::GetOverrideRetPtr!")); + return; + } + } + + //const my_rettype *retptr = reinterpret_cast( + //(status >= MRES_OVERRIDE) ? pContext->GetOverrideRetPtr() : pContext->GetOrigRetPtr()); + + m_HookFunc.mov(rax, rbp(v_pContext)); + m_HookFunc.mov(rax, rax()); + m_HookFunc.mov(r8, rax); + + m_HookFunc.mov(rax, rax(getOrigRetPtrMfi.vtblindex * SIZE_PTR)); + m_HookFunc.mov(r8, r8(getOverrideRetPtrMfi.vtblindex * SIZE_PTR)); + + m_HookFunc.xor(r9, r9); + m_HookFunc.mov(r9, rbp(v_status)); + m_HookFunc.cmp(r9, MRES_OVERRIDE); + + m_HookFunc.cmovge(rax, r8); + + // Shadow space 32 bytes + 8 bytes + MSVC_ONLY(m_HookFunc.sub(rsp, 40)); + + // 1st parameter (this) + GCC_ONLY(m_HookFunc.mov(rdi, rbp(v_pContext))); + MSVC_ONLY(m_HookFunc.mov(rcx, rbp(v_pContext))); + + m_HookFunc.call(rax); + + MSVC_ONLY(m_HookFunc.add(rsp, 40)); + + m_HookFunc.mov(rbp(v_retptr), rax); + } + + void x64GenContext::DoReturn(int v_retptr, int v_memret_outaddr) + { + const auto& retInfo = m_Proto.GetRet(); + if (retInfo.size == 0) { + return; + } + + m_HookFunc.mov(r8, rbp(v_retptr)); + + if (retInfo.flags & PassInfo::PassFlag_ByRef) { + m_HookFunc.mov(rax, r8()); + return; + } + // else: byval + + if (retInfo.type == PassInfo::PassType_Float) { + m_HookFunc.movsd(xmm0, r8()); + } + else if (retInfo.type == PassInfo::PassType_Basic || + ((retInfo.type == PassInfo::PassType_Object) && (retInfo.flags & PassInfo::PassFlag_RetReg)) ) { + m_HookFunc.mov(rax, r8()); + } + + if (retInfo.flags & PassInfo::PassFlag_RetMem) + { + // *memret_outaddr = plugin_ret + if (retInfo.pCopyCtor) + { + // Shadow space 32 bytes + 8 bytes + MSVC_ONLY(m_HookFunc.sub(rsp, 40)); + + // 1st parameter (this) + GCC_ONLY(m_HookFunc.mov(rdi, rbp(v_memret_outaddr))); + MSVC_ONLY(m_HookFunc.mov(rcx, rbp(v_memret_outaddr))); + + // 2nd parameter (copy) + GCC_ONLY(m_HookFunc.mov(rsi, r8)); + MSVC_ONLY(m_HookFunc.mov(rdx, r8)); + + // Move address and call + m_HookFunc.mov(rax, reinterpret_cast(retInfo.pCopyCtor)); + m_HookFunc.call(rax); + + MSVC_ONLY(m_HookFunc.add(rsp, 40)); + } + else + { + m_HookFunc.push(rdi); + m_HookFunc.push(rsi); + m_HookFunc.push(rcx); + + m_HookFunc.mov(rcx, retInfo.size); + m_HookFunc.mov(rdi, rbp(v_memret_outaddr)); + m_HookFunc.mov(rsi, r8); + + m_HookFunc.rep_movs_bytes(); + + m_HookFunc.pop(rcx); + m_HookFunc.pop(rsi); + m_HookFunc.pop(rdi); + } + m_HookFunc.mov(rax, rbp(v_memret_outaddr)); + } + } + + void x64GenContext::CallEndContext(int v_pContext) { + static MemFuncInfo mfi = {false, -1, -1, -1}; + if (mfi.vtblindex == -1) + { + GetFuncInfo(&ISourceHook::EndContext, mfi); + // The function is somehow not virtual, or has a non trivial this ptr + if (!mfi.isVirtual || mfi.thisptroffs != 0 || mfi.vtbloffs != 0 || mfi.vtblindex < 0) + { + mfi.vtblindex = -1; // Ensure we go through there again on subsequent calls + SH_ASSERT(0, ("Couldn't retrieve details of ISourceHook::EndContext!")); + return; + } + } + + // Shadow space 32 bytes + 8 bytes + MSVC_ONLY(m_HookFunc.sub(rsp, 40)); + + // 1st parameter (this) + GCC_ONLY(m_HookFunc.mov(rdi, reinterpret_cast(m_SHPtr))); + MSVC_ONLY(m_HookFunc.mov(rcx, reinterpret_cast(m_SHPtr))); + + // 2nd param + GCC_ONLY(m_HookFunc.mov(rsi, rbp(v_pContext))); + MSVC_ONLY(m_HookFunc.mov(rdx, rbp(v_pContext))); + + // Move address and call + m_HookFunc.mov(rax, (*reinterpret_cast(m_SHPtr))[mfi.vtblindex]); + m_HookFunc.call(rax); + + MSVC_ONLY(m_HookFunc.add(rsp, 40)); + } + + bool x64GenContext::MemRetWithTempObj() { + const auto& retInfo = m_Proto.GetRet(); + // Memory return AND (has destructor OR has assign operator) + return ((retInfo.flags & PassInfo::PassFlag_RetMem) + && (retInfo.flags & (PassInfo::PassFlag_ODtor | PassInfo::PassFlag_AssignOp))); + } + + void x64GenContext::AutoDetectRetType() { + auto& pi = m_Proto.GetRet(); + // Void return, ignore + if (pi.size == 0) { + return; + } + + // Only relevant for byval types + if (pi.flags & PassInfo::PassFlag_ByVal) + { + // Basic + float: + if (pi.type == PassInfo::PassType_Basic || + pi.type == PassInfo::PassType_Float) + { + // <= 8 bytes: + // _always_ in registers, no matter what the user says + if (pi.size <= 8) + { + pi.flags &= ~PassInfo::PassFlag_RetMem; + pi.flags |= PassInfo::PassFlag_RetReg; + } + else + { + // Does this even exist? No idea, if it does: in memory! + pi.flags &= ~PassInfo::PassFlag_RetReg; + pi.flags |= PassInfo::PassFlag_RetMem; + } + } + // Object: + else if (pi.type == PassInfo::PassType_Object) + { + // If the user says nothing, auto-detect + if ((pi.flags & (PassInfo::PassFlag_RetMem | PassInfo::PassFlag_RetReg)) == 0) + { +#if SH_COMP == SH_COMP_MSVC + // MSVC has various criteria for passing in memory + // if object doesn't fit on 8, 16, 32, or 64 bits. It's in memory + // if object has a constructor or destructor. It's in memory + bool unconventionalsize = (pi.size == 3 || (pi.size != 8 && pi.size > 4)); + bool hasSpecialFunctions = (pi.flags & PassInfo::PassFlag_OCtor|PassInfo::PassFlag_ODtor|PassInfo::PassFlag_CCtor) != 0; + + if (unconventionalsize || hasSpecialFunctions) { + pi.flags |= PassInfo::PassFlag_RetMem; + } else { + pi.flags |= PassInfo::PassFlag_RetReg; + } +#elif SH_COMP == SH_COMP_GCC +static_assert(false, "Missing auto-detect type for linux!"); +#endif + } + } + } + else + { + // byref: make sure that the flag is _not_ set + pi.flags &= ~PassInfo::PassFlag_RetMem; + pi.flags |= PassInfo::PassFlag_RetReg; + } + } + + void x64GenContext::AutoDetectParamFlags() + { + } + + void* x64GenContext::GeneratePubFunc() + { + // The pubfunc is a static cdecl function. + // C Code: + // int HookManPubFunc( + // bool store, // rdi (AMD) rcx (microsoft) + // IHookManagerInfo *hi // rsi (AMD) rdx (microsoft) + // ) + // { + // if (store) + // *m_pHI = hi; + // if (hi) + // hi->SetInfo(HOOKMAN_VERSION, m_VtblOffs, m_VtblIdx, m_Proto.GetProto(), m_HookfuncVfnptr) + // } + + // prologue + MSVC_ONLY(m_PubFunc.sub(rsp, 0x38)); // Shadow space 32 bytes + 2 * 8 bytes (for our parameters) + 8 bytes + + // Unnecessary according to AMD manual (Section 3.2.2 The Stack Frame) + // but GCC still does it anyways, so let's do it as well + GCC_ONLY(m_PubFunc.push(rbp)); + GCC_ONLY(m_PubFunc.mov(rbp, rsp)); + + // Both Microsoft and AMD uses r8 and r9 as argument parameters + // Therefore they need not to be preserved across function calls + // Let's use them as local variables, this will make writing the + // rest of the function much easier + + // Store 'store' into r8 + GCC_ONLY(m_PubFunc.mov(r8, rdi)); + MSVC_ONLY(m_PubFunc.mov(r8, rcx)); + + // Store 'hi' into r9 + GCC_ONLY(m_PubFunc.mov(r9, rsi)); + MSVC_ONLY(m_PubFunc.mov(r9, rdx)); + + // If 'store' is true, store hi into rax + m_PubFunc.test(r8, 0x1); + m_PubFunc.jz(0x0); + + auto storeOff = m_PubFunc.get_outputpos(); + m_PubFunc.mov(rax, reinterpret_cast(m_pHI)); + m_PubFunc.mov(rax(), r9); + + m_PubFunc.rewrite(storeOff - sizeof(std::int32_t), m_PubFunc.get_outputpos() - storeOff); + + // If 'hi' is not null, call SetInfo + m_PubFunc.test(r9, r9); + m_PubFunc.jz(0x0); // We will write the real offset later + auto jumpOff = m_PubFunc.get_outputpos(); + + static MemFuncInfo mfi = {false, -1, -1, -1}; + if (mfi.vtblindex == -1) + { + GetFuncInfo(&IHookManagerInfo::SetInfo, mfi); + // The function is somehow not virtual, or has a non trivial this ptr + if (!mfi.isVirtual || mfi.thisptroffs != 0 || mfi.vtbloffs != 0 || mfi.vtblindex < 0) + { + mfi.vtblindex = -1; // Ensure we go through there again on subsequent calls + return nullptr; + } + } + + // Obtain the vtable + m_PubFunc.mov(rax, r9()); + m_PubFunc.mov(rax, rax(SIZE_PTR * mfi.vtblindex)); + + // 1st parameter (this) + GCC_ONLY(m_PubFunc.mov(rdi, r9)); + MSVC_ONLY(m_PubFunc.mov(rcx, r9)); + // 2nd parameter + GCC_ONLY(m_PubFunc.mov(rsi, SH_HOOKMAN_VERSION)); + MSVC_ONLY(m_PubFunc.mov(rdx, SH_HOOKMAN_VERSION)); + // 3rd parameter + GCC_ONLY(m_PubFunc.mov(rdx, m_VtblOffs)); + MSVC_ONLY(m_PubFunc.mov(r8, m_VtblOffs)); + // 4th parameter + GCC_ONLY(m_PubFunc.mov(rcx, m_VtblIdx)); + MSVC_ONLY(m_PubFunc.mov(r9, m_VtblIdx)); + // 5th argument + GCC_ONLY(m_PubFunc.mov(r8, reinterpret_cast(m_BuiltPI))); + MSVC_ONLY(m_PubFunc.mov(r10, reinterpret_cast(m_BuiltPI))); + MSVC_ONLY(m_PubFunc.mov(rsp(0x20), r10)); + // 6th argument + GCC_ONLY(m_PubFunc.mov(r9, reinterpret_cast(m_HookfuncVfnptr))); + MSVC_ONLY(m_PubFunc.mov(r10, reinterpret_cast(m_HookfuncVfnptr))); + MSVC_ONLY(m_PubFunc.mov(rsp(0x28), r10)); + + m_PubFunc.call(rax); + + // Now that we've written the conditional branch + // we can move set the offset at our earlier jump + std::int32_t endOff = static_cast(m_PubFunc.get_outputpos()) - jumpOff; + m_PubFunc.rewrite(jumpOff - sizeof(std::int32_t), endOff); + + // epilogue + + MSVC_ONLY(m_PubFunc.add(rsp, 0x38)); + + GCC_ONLY(m_PubFunc.pop(rbp)); + + // Return 0 + m_PubFunc.xor(rax, rax); + + m_PubFunc.retn(); + + m_PubFunc.SetRE(); + return m_PubFunc; + } + + HookManagerPubFunc x64GenContext::GetPubFunc() + { + if (m_GeneratedPubFunc == nullptr) + { + // Try generating the function + m_GeneratedPubFunc = Generate(); + } + return m_GeneratedPubFunc; + } + + bool x64GenContext::Equal(const CProto &proto, int vtbl_offs, int vtbl_idx) + { + return (m_OrigProto.ExactlyEqual(proto) && m_VtblOffs == vtbl_offs && m_VtblIdx == vtbl_idx); + } + + bool x64GenContext::Equal(HookManagerPubFunc other) + { + return m_GeneratedPubFunc == other; + } + + CHookManagerAutoGen::CHookManagerAutoGen(ISourceHook *pSHPtr) : m_pSHPtr(pSHPtr) { } + + CHookManagerAutoGen::~CHookManagerAutoGen() { } + + int CHookManagerAutoGen::GetIfaceVersion() + { + return SH_HOOKMANAUTOGEN_IFACE_VERSION; + } + + int CHookManagerAutoGen::GetImplVersion() + { + return SH_HOOKMANAUTOGEN_IMPL_VERSION; + } + + HookManagerPubFunc CHookManagerAutoGen::MakeHookMan(const ProtoInfo *proto, int vtbl_offs, int vtbl_idx) + { + CProto mproto(proto); + for (auto iter = m_Contexts.begin(); iter != m_Contexts.end(); ++iter) + { + if (iter->m_GenContext->Equal(mproto, vtbl_offs, vtbl_idx)) + { + iter->m_RefCnt++; + return iter->m_GenContext->GetPubFunc(); + } + } + + // Not found yet -> new one + StoredContext sctx; + sctx.m_RefCnt = 1; + sctx.m_GenContext = std::make_unique(proto, vtbl_offs, vtbl_idx, m_pSHPtr); + + auto pubFunc = sctx.m_GenContext->GetPubFunc(); + if (pubFunc != nullptr) + { + m_Contexts.emplace_back(std::move(sctx)); + } + return pubFunc; + } + + void CHookManagerAutoGen::ReleaseHookMan(HookManagerPubFunc pubFunc) + { + for (auto iter = m_Contexts.begin(); iter != m_Contexts.end(); ++iter) + { + if (iter->m_GenContext->Equal(pubFunc)) + { + iter->m_RefCnt--; + if (iter->m_RefCnt == 0) + { + iter = m_Contexts.erase(iter); + } + break; + } + } + } + } +} diff --git a/core/sourcehook/sourcehook_hookmangen_x86_64.h b/core/sourcehook/sourcehook_hookmangen_x86_64.h new file mode 100644 index 0000000..27bef3b --- /dev/null +++ b/core/sourcehook/sourcehook_hookmangen_x86_64.h @@ -0,0 +1,666 @@ +/* ======== SourceHook ======== +* vim: set ts=4 : +* Copyright (C) 2024 AlliedModders LLC. All rights reserved. +* No warranties of any kind +* +* License: zlib/libpng +* +* ============================ +*/ + +#ifndef __SOURCEHOOK_HOOKMANGEN_X86_64_H__ +#define __SOURCEHOOK_HOOKMANGEN_X86_64_H__ + +#include +#include + +namespace SourceHook +{ + namespace Impl + { + enum x8664Reg : std::uint8_t { + RAX = 0, + RCX = 1, + RDX = 2, + RBX = 3, + RSP = 4, + RBP = 5, + RSI = 6, + RDI = 7, + R8 = 8, + R9 = 9, + R10 = 10, + R11 = 11, + R12 = 12, + R13 = 13, + R14 = 14, + R15 = 15 + }; + + enum x8664FloatReg : std::uint8_t { + XMM0 = 0, + XMM1 = 1, + XMM2 = 2, + XMM3 = 3, + XMM4 = 4, + XMM5 = 5, + XMM6 = 6, + XMM7 = 7, + XMM8 = 8, + XMM9 = 9, + XMM10 = 10, + XMM11 = 11, + XMM12 = 12, + XMM13 = 13, + XMM14 = 14, + XMM15 = 15 + }; + + class x86_64_FloatReg { + public: + constexpr x86_64_FloatReg(x8664FloatReg op) : code(op) { } + + constexpr bool operator==(x86_64_FloatReg a) const { return code == a.code; } + constexpr bool operator!=(x86_64_FloatReg a) const { return code != a.code; } + + constexpr bool extended() const { return ((code & 0x8) == 0x8); } + constexpr std::uint8_t low() const { return code & 0x7; } + constexpr operator x8664FloatReg() const { return code; } + protected: + x8664FloatReg code; + }; + + static const x86_64_FloatReg xmm0 = { x8664FloatReg::XMM0 }; + static const x86_64_FloatReg xmm1 = { x8664FloatReg::XMM1 }; + static const x86_64_FloatReg xmm2 = { x8664FloatReg::XMM2 }; + static const x86_64_FloatReg xmm3 = { x8664FloatReg::XMM3 }; + static const x86_64_FloatReg xmm4 = { x8664FloatReg::XMM4 }; + static const x86_64_FloatReg xmm5 = { x8664FloatReg::XMM5 }; + static const x86_64_FloatReg xmm6 = { x8664FloatReg::XMM6 }; + static const x86_64_FloatReg xmm7 = { x8664FloatReg::XMM7 }; + + static const x86_64_FloatReg xmm8 = { x8664FloatReg::XMM8 }; + static const x86_64_FloatReg xmm9 = { x8664FloatReg::XMM9 }; + static const x86_64_FloatReg xmm10 = { x8664FloatReg::XMM10 }; + static const x86_64_FloatReg xmm11 = { x8664FloatReg::XMM11 }; + static const x86_64_FloatReg xmm12 = { x8664FloatReg::XMM12 }; + static const x86_64_FloatReg xmm13 = { x8664FloatReg::XMM13 }; + static const x86_64_FloatReg xmm14 = { x8664FloatReg::XMM14 }; + static const x86_64_FloatReg xmm15 = { x8664FloatReg::XMM15 }; + + enum MOD_MODRM : std::uint8_t { + DISP0 = 0b00, + DISP8 = 0b01, + DISP32 = 0b10, + REG = 0b11 + }; + + class x86_64_RegRm { + public: + friend class x86_64_Reg; + + inline std::uint8_t sib() { + // For the time being, we don't support multiple register + return (0 << 6) | (this->low() << 3) | this->low(); + } + + inline std::uint8_t modrm(x8664Reg reg) { + return (mod << 6) | ((reg & 0x7) << 3) | this->low(); + } + + inline std::uint8_t modrm(x8664FloatReg reg) { + return (mod << 6) | ((reg & 0x7) << 3) | this->low(); + } + + inline std::uint8_t modrm() { + return (mod << 6) | (0x0 << 3) | this->low(); + } + + void write_modrm(GenBuffer* buffer); + void write_modrm(GenBuffer* buffer, x8664Reg op); + void write_modrm(GenBuffer* buffer, x8664FloatReg op); + + bool extended() const { return ((rm & 0x8) == 0x8); } + std::uint8_t low() const { return rm & 0x7; } + constexpr operator x8664Reg() const { return rm; } + + protected: + x86_64_RegRm(x8664Reg reg, std::int32_t disp); + x86_64_RegRm(x8664Reg reg); + void Setup(); + + x8664Reg rm; + std::int32_t disp; + MOD_MODRM mod; + }; + + class x86_64_Reg { + public: + constexpr x86_64_Reg(x8664Reg op) : code(op) { } + + constexpr bool operator==(x86_64_Reg a) const { return code == a.code; } + constexpr bool operator!=(x86_64_Reg a) const { return code != a.code; } + x86_64_RegRm operator()() const { return x86_64_RegRm(*this, 0); } + x86_64_RegRm operator()(std::int32_t disp) const { return x86_64_RegRm(*this, disp); } + + constexpr bool extended() const { return ((code & 0x8) == 0x8); } + constexpr std::uint8_t low() const { return code & 0x7; } + constexpr operator x8664Reg() const { return code; } + protected: + x8664Reg code; + }; + + static const x86_64_Reg rax = { x8664Reg::RAX }; + static const x86_64_Reg rcx = { x8664Reg::RCX }; + static const x86_64_Reg rdx = { x8664Reg::RDX }; + static const x86_64_Reg rbx = { x8664Reg::RBX }; + static const x86_64_Reg rsp = { x8664Reg::RSP }; + static const x86_64_Reg rbp = { x8664Reg::RBP }; + static const x86_64_Reg rsi = { x8664Reg::RSI }; + static const x86_64_Reg rdi = { x8664Reg::RDI }; + + static const x86_64_Reg r8 = { x8664Reg::R8 }; + static const x86_64_Reg r9 = { x8664Reg::R9 }; + static const x86_64_Reg r10 = { x8664Reg::R10 }; + static const x86_64_Reg r11 = { x8664Reg::R11 }; + static const x86_64_Reg r12 = { x8664Reg::R12 }; + static const x86_64_Reg r13 = { x8664Reg::R13 }; + static const x86_64_Reg r14 = { x8664Reg::R14 }; + static const x86_64_Reg r15 = { x8664Reg::R15 }; + + enum REX : std::uint8_t { + BASE = 0x40, + B = 0x41, + X = 0x42, + XB = 0x43, + R = 0x44, + RB = 0x45, + RX = 0x46, + RXB = 0x47, + W = 0x48, + WB = 0x49, + WX = 0x4A, + WXB = 0x4B, + WR = 0x4C, + WRB = 0x4D, + WRX = 0x4E, + WRXB = 0x4F + }; + + constexpr inline std::uint8_t w_rex(x8664Reg reg, x8664Reg rm) { + return REX::W | ((static_cast((reg & 0x8) == 0x8) << 2) | static_cast((rm & 0x8) == 0x8)); + } + + constexpr inline std::uint8_t w_rex(x8664FloatReg reg, x8664Reg rm) { + return REX::W | ((static_cast((reg & 0x8) == 0x8) << 2) | static_cast((rm & 0x8) == 0x8)); + } + + constexpr inline std::uint8_t w_rex(x8664Reg reg, x8664FloatReg rm) { + return REX::W | ((static_cast((reg & 0x8) == 0x8) << 2) | static_cast((rm & 0x8) == 0x8)); + } + + constexpr inline std::uint8_t modrm(x8664Reg reg, x8664Reg rm) { + return (MOD_MODRM::REG << 6) | ((reg & 0x7) << 3) | (rm & 0x7); + } + + constexpr inline std::uint8_t modrm_rm(x8664Reg rm, std::uint8_t base) { + return (MOD_MODRM::REG << 6) | (base << 3) | (rm & 0x7); + } + + x86_64_RegRm::x86_64_RegRm(x8664Reg reg, std::int32_t disp) : rm(reg), disp(disp) { + Setup(); + } + + x86_64_RegRm::x86_64_RegRm(x8664Reg reg) : rm(reg), disp(0) { + Setup(); + } + + void x86_64_RegRm::Setup() { + if (disp == 0 && rm != x8664Reg::RBP && rm != x8664Reg::R13) { + mod = DISP0; + } + else if (disp >= SCHAR_MIN && disp <= SCHAR_MAX) { + mod = DISP8; + } else { + mod = DISP32; + } + } + + void x86_64_RegRm::write_modrm(GenBuffer* buffer) { + // modrm + buffer->write_ubyte(modrm()); + + // Special register we need a sib byte + if (rm == x8664Reg::RSP || rm == x8664Reg::R12) { // rsp/r12 + buffer->write_ubyte(sib()); + } + + // Special disp mod + if (mod != DISP0) { + if (mod == DISP8) { + buffer->write_byte(disp); + } else if (mod == DISP32) { + buffer->write_int32(disp); + } + } + } + + void x86_64_RegRm::write_modrm(GenBuffer* buffer, x8664Reg reg) { + // modrm + buffer->write_ubyte(modrm(reg)); + + // Special register we need a sib byte + if (rm == x8664Reg::RSP || rm == x8664Reg::R12) { // rsp/r12 + buffer->write_ubyte(sib()); + } + + // Special disp mod + if (mod != DISP0) { + if (mod == DISP8) { + buffer->write_byte(disp); + } else if (mod == DISP32) { + buffer->write_int32(disp); + } + } + } + + void x86_64_RegRm::write_modrm(GenBuffer* buffer, x8664FloatReg reg) { + // modrm + buffer->write_ubyte(modrm(reg)); + + // Special register we need a sib byte + if (rm == x8664Reg::RSP || rm == x8664Reg::R12) { // rsp/r12 + buffer->write_ubyte(sib()); + } + + // Special disp mod + if (mod != DISP0) { + if (mod == DISP8) { + buffer->write_byte(disp); + } else if (mod == DISP32) { + buffer->write_int32(disp); + } + } + } + + class x64JitWriter : public GenBuffer { + public: + void breakpoint() { + this->write_ubyte(0xCC); + } + + void rep_movs_bytes() { + this->write_ubyte(0xF3); + this->write_ubyte(0x48); + this->write_ubyte(0xA4); + } + + void call(x86_64_Reg reg) { + if (reg.extended()) { + this->write_ubyte(REX::B); + } + this->write_ubyte(0xFF); + this->write_ubyte(0xD0 + reg.low()); + } + + void jump(x86_64_Reg reg) { + if (reg.extended()) { + this->write_ubyte(REX::B); + } + this->write_ubyte(0xFF); + this->write_ubyte(0xE0 + reg.low()); + } + + void jump(std::int32_t off) { + this->write_ubyte(0xE9); + this->write_int32(off); + } + + void jz(std::int32_t off) { + this->write_ubyte(0x0F); + this->write_ubyte(0x84); + this->write_int32(off); + } + + void jl(std::int32_t off) { + this->write_ubyte(0x0F); + this->write_ubyte(0x8C); + this->write_int32(off); + } + + void jle(std::int32_t off) { + this->write_ubyte(0x0F); + this->write_ubyte(0x8E); + this->write_int32(off); + } + + void je(std::int32_t off) { + this->write_ubyte(0x0F); + this->write_ubyte(0x84); + this->write_int32(off); + } + + void jne(std::int32_t off) { + this->write_ubyte(0x0F); + this->write_ubyte(0x85); + this->write_int32(off); + } + + void push(x86_64_Reg reg) { + if (reg.extended()) { + this->write_ubyte(REX::B); + } + this->write_ubyte(0x50 + reg.low()); + } + + void push(std::int32_t val) { + if (val >= SCHAR_MIN && val <= SCHAR_MAX) { + this->write_ubyte(0x6A); + this->write_byte(std::int8_t(val)); + } else { + this->write_ubyte(0x68); + this->write_int32(val); + } + } + + void pop(x86_64_Reg reg) { + if (reg.extended()) { + this->write_ubyte(REX::B); + } + this->write_ubyte(0x58 + reg.low()); + } + + // mov_rm + void mov(x86_64_Reg dst, x86_64_Reg src) { + this->write_ubyte(w_rex(src, dst)); + this->write_ubyte(0x89); + this->write_ubyte(modrm(src, dst)); + } + + void mov(x86_64_RegRm rm, x86_64_Reg reg) { + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x89); + rm.write_modrm(this, reg); + } + + void mov(x86_64_Reg reg, x86_64_RegRm rm) { + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x8B); + rm.write_modrm(this, reg); + } + + void mov(x86_64_Reg dst, std::int32_t imm) { + if (dst.extended()) { + this->write_ubyte(REX::B); + } + this->write_ubyte(0xB8 + dst.low()); + this->write_int32(imm); + } + + void mov(x86_64_RegRm dst, std::int32_t imm) { + if (dst.extended()) { + this->write_ubyte(REX::WB); + } else { + this->write_ubyte(REX::W); + } + this->write_ubyte(0xC7); + dst.write_modrm(this); + this->write_int32(imm); + } + + void mov(x86_64_Reg dst, std::uint64_t imm) { + if (imm <= UINT32_MAX) { + this->mov(dst, std::int32_t(imm)); + return; + } + + if (dst.extended()) { + this->write_ubyte(REX::WB); + } else { + this->write_ubyte(REX::W); + } + this->write_ubyte(0xB8 + dst.low()); + this->write_uint64(imm); + } + + void movsd(x86_64_FloatReg reg, x86_64_RegRm rm) { + this->write_ubyte(0xF2); + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x0F); + this->write_ubyte(0x10); + rm.write_modrm(this, reg); + } + + void movsd(x86_64_RegRm rm, x86_64_FloatReg reg) { + this->write_ubyte(0xF2); + if (reg.extended() || rm.extended()) { + this->write_ubyte(w_rex(reg, rm)); + } + this->write_ubyte(0x0F); + this->write_ubyte(0x11); + rm.write_modrm(this, reg); + } + + + void add(x86_64_Reg dst, x86_64_Reg src) { + this->write_ubyte(w_rex(src, dst)); + this->write_ubyte(0x01); + this->write_ubyte(modrm(src, dst)); + } + + void add(x86_64_Reg dst, int32_t imm) { + if (dst.extended()) { + this->write_ubyte(REX::WB); + } else { + this->write_ubyte(REX::W); + } + this->write_ubyte(0x81); + this->write_ubyte(modrm_rm(dst, 0)); + this->write_int32(imm); + } + + void sub(x86_64_Reg dst, x86_64_Reg src) { + this->write_ubyte(w_rex(src, dst)); + this->write_ubyte(0x29); + this->write_ubyte(modrm(src, dst)); + } + + void sub(x86_64_Reg dst, int32_t imm) { + if (dst.extended()) { + this->write_ubyte(REX::WB); + } else { + this->write_ubyte(REX::W); + } + this->write_ubyte(0x81); + this->write_ubyte(modrm_rm(dst, 5)); + this->write_int32(imm); + } + + void xor(x86_64_Reg dst, x86_64_Reg src) { + this->write_ubyte(w_rex(src, dst)); + this->write_ubyte(0x31); + this->write_ubyte(modrm(src, dst)); + } + + void test(x86_64_Reg dst, x86_64_Reg src) { + this->write_ubyte(w_rex(src, dst)); + this->write_ubyte(0x85); + this->write_ubyte(modrm(src, dst)); + } + + void test(x86_64_Reg reg, int32_t imm) { + if (reg.extended()) { + this->write_ubyte(REX::WB); + } else { + this->write_ubyte(REX::W); + } + this->write_ubyte(0xF7); + this->write_ubyte(modrm_rm(reg, 0)); + this->write_int32(imm); + } + + void cmovne(x86_64_Reg reg, x86_64_RegRm rm) { + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x0F); + this->write_ubyte(0x45); + rm.write_modrm(this, reg); + } + + void cmovne(x86_64_Reg reg, x86_64_Reg rm) { + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x0F); + this->write_ubyte(0x45); + this->write_ubyte(modrm(reg, rm)); + } + + void cmovnz(x86_64_Reg reg, x86_64_RegRm rm) { + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x0F); + this->write_ubyte(0x45); + rm.write_modrm(this, reg); + } + + void cmovnz(x86_64_Reg reg, x86_64_Reg rm) { + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x0F); + this->write_ubyte(0x45); + this->write_ubyte(modrm(reg, rm)); + } + + void cmovge(x86_64_Reg reg, x86_64_RegRm rm) { + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x0F); + this->write_ubyte(0x4D); + rm.write_modrm(this, reg); + } + + void cmovge(x86_64_Reg reg, x86_64_Reg rm) { + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x0F); + this->write_ubyte(0x4D); + this->write_ubyte(modrm(reg, rm)); + } + + void cmovg(x86_64_Reg reg, x86_64_RegRm rm) { + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x0F); + this->write_ubyte(0x4F); + rm.write_modrm(this, reg); + } + + void cmovg(x86_64_Reg reg, x86_64_Reg rm) { + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x0F); + this->write_ubyte(0x4F); + this->write_ubyte(modrm(reg, rm)); + } + + void lea(x86_64_Reg reg, x86_64_RegRm rm) { + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x8D); + rm.write_modrm(this, reg); + } + + void cmp(x86_64_Reg reg, x86_64_RegRm rm) { + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x3B); + rm.write_modrm(this, reg); + } + + void cmp(x86_64_RegRm rm, x86_64_Reg reg) { + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x39); + rm.write_modrm(this, reg); + } + + void cmp(x86_64_Reg reg, x86_64_Reg rm) { + this->write_ubyte(w_rex(reg, rm)); + this->write_ubyte(0x3B); + this->write_ubyte(modrm(reg, rm)); + } + + void cmp(x86_64_Reg dst, int32_t imm) { + if (dst.extended()) { + this->write_ubyte(REX::WB); + } else { + this->write_ubyte(REX::W); + } + this->write_ubyte(0x81); + this->write_ubyte(modrm_rm(dst, 7)); + this->write_int32(imm); + } + + void retn() { + this->write_ubyte(0xC3); + } + }; + + class x64GenContext : public IGenContext + { + public: + x64GenContext(); + x64GenContext(const ProtoInfo *proto, int vtbl_offs, int vtbl_idx, ISourceHook *pSHPtr); + virtual ~x64GenContext(); + + virtual bool Equal(const CProto &proto, int vtbl_offs, int vtbl_idx) override; + virtual bool Equal(HookManagerPubFunc other) override; + + virtual HookManagerPubFunc GetPubFunc() override; + HookManagerPubFunc Generate(); + protected: + friend void foo_test(); + + static const std::int32_t SIZE_PTR = sizeof(void*); + + std::int32_t AddVarToFrame(std::int32_t size); + std::int32_t ComputeVarsSize(); + std::int32_t x64GenContext::GetRealSize(const IntPassInfo& info); + std::int32_t AlignSize(std::int32_t x, std::int32_t boundary); + std::int32_t GetParamStackSize(const IntPassInfo &info); + + void Clear(); + void AutoDetectRetType(); + void AutoDetectParamFlags(); + bool PassInfoSupported(const IntPassInfo& pi, bool is_ret); + void BuildProtoInfo(); + bool MemRetWithTempObj(); + + void* GeneratePubFunc(); + void* GenerateHookFunc(); + + void CallSetupHookLoop(int v_orig_ret, int v_override_ret, int v_cur_res, int v_prev_res, int v_status, int v_vfnptr_origentry, int v_this, int v_pContext); + void GenerateCallHooks(int v_status, int v_prev_res, int v_cur_res, int v_iter, + int v_pContext, int v_plugin_ret, int v_mem_ret); + void GenerateCallOrig(int v_status, int v_pContext, int v_this, int v_vfnptr_origentry, int v_orig_ret, int v_override_ret, int v_place_for_memret); + void PrepareReturn(int v_status, int v_pContext, int v_retptr); + void CallEndContext(int v_pContext); + void DoReturn(int v_retptr, int v_memret_outaddr); + + std::int32_t PushParameters(int v_this, int v_ret); + void SaveReturnValue(int v_mem_ret, int v_ret); + + HookManagerPubFunc m_GeneratedPubFunc; + + CProto m_OrigProto; + CProto m_Proto; + int m_VtblOffs; + int m_VtblIdx; + ISourceHook *m_SHPtr; + + x64JitWriter m_HookFunc; + x64JitWriter m_PubFunc; + + ProtoInfo *m_BuiltPI; + PassInfo *m_BuiltPI_Params; + PassInfo::V2Info *m_BuiltPI_Params2; + + void **m_pHI; + void **m_HookfuncVfnptr; + + std::int32_t m_HookFunc_FrameOffset; + std::int32_t m_HookFunc_FrameVarsSize; + }; + } +} + +#endif //__SOURCEHOOK_HOOKMANGEN_X86_64_H__ \ No newline at end of file diff --git a/core/sourcehook/sourcehook_impl.h b/core/sourcehook/sourcehook_impl.h index f9ef068..9755ab9 100644 --- a/core/sourcehook/sourcehook_impl.h +++ b/core/sourcehook/sourcehook_impl.h @@ -179,6 +179,9 @@ New SH_CALL #include "sourcehook_impl_ciface.h" #include "sourcehook_impl_cvfnptr.h" #include "sourcehook_impl_chookidman.h" +#include + +void mm_LogMessage(const char* msg, ...); namespace SourceHook { @@ -188,6 +191,24 @@ namespace SourceHook namespace Impl { + enum SH_LOG { + VERBOSE, + NORMAL, + TEST, + NONE, + }; + + extern SH_LOG sh_log_level; + + template + inline void SH_DEBUG_LOG(SH_LOG log_level, const char* message, Args... args) + { + if (log_level < sh_log_level) { + return; + } + mm_LogMessage(message, args...); + } + struct CHookContext : IHookContext { CHookContext() : m_CleanupTask(NULL) diff --git a/core/sourcehook/sourcehook_impl_chookmaninfo.cpp b/core/sourcehook/sourcehook_impl_chookmaninfo.cpp index 639b7a2..03dea6e 100644 --- a/core/sourcehook/sourcehook_impl_chookmaninfo.cpp +++ b/core/sourcehook/sourcehook_impl_chookmaninfo.cpp @@ -39,6 +39,13 @@ namespace SourceHook void CHookManager::Register() { + SH_DEBUG_LOG(VERBOSE, "CHookManager(%p)::SetInfo\n" + "- hookman_version %d\n" + "- vtbloffs %d\n" + "- vtblidx %d\n" + "- hookfunc_vfnptr %p\n\n", + (void*)this, m_Version, m_VtblOffs, m_VtblIdx, m_HookfuncVfnptr); + m_PubFunc(true, this); } @@ -49,6 +56,9 @@ namespace SourceHook void CHookManager::IncrRef(CVfnPtr *pVfnPtr) { + SH_DEBUG_LOG(VERBOSE, "CHookManager(%p)::IncrRef\n", + (void*)this); + m_VfnPtrs.push_back(pVfnPtr); if (m_VfnPtrs.size() == 1) Register(); @@ -56,6 +66,9 @@ namespace SourceHook void CHookManager::DecrRef(CVfnPtr *pVfnPtr) { + SH_DEBUG_LOG(VERBOSE, "CHookManager(%p)::DecrRef\n", + (void*)this); + m_VfnPtrs.remove(pVfnPtr); if (m_VfnPtrs.empty()) Unregister();