diff --git a/src/game/iw3/mp/components/gsc.cpp b/src/game/iw3/mp/components/gsc.cpp index f6878bb4..65053d1d 100644 --- a/src/game/iw3/mp/components/gsc.cpp +++ b/src/game/iw3/mp/components/gsc.cpp @@ -9,6 +9,7 @@ namespace iw3 { namespace mp { +std::unordered_map GSC::ReplacedFunctions; namespace { static const gsc::Entry functions[] = { @@ -23,6 +24,7 @@ static const gsc::Entry functions[] = { {"float", GScr_Float, BUILTIN_ANY}, {"precachestring", Scr_PrecacheString_Stub, BUILTIN_ANY}, {"addtestclient", GScr_AddTestClient, BUILTIN_ANY}, + {"replacefunc", GSC::ReplaceFunc, BUILTIN_ANY}, }; static const gsc::Entry methods[] = { @@ -83,6 +85,85 @@ BuiltinMethod Scr_GetMethod_Hook(const char **pName, int *type) return Scr_GetMethod_Detour.GetOriginal()(pName, type); } +Detour GSC::VM_Execute_Detour; + +const char *GSC::GetCodePosForParam(int index) +{ + const int numParams = static_cast(Scr_GetNumParam()); + + if (index < 0 || index >= numParams) + return nullptr; + + const VariableValue *value = &scrVmPub.top[-index]; + + if (value->type != VAR_FUNCTION) + return nullptr; + + return value->u.codePosValue; +} + +const char *GSC::GetReplacedPos(const char *pos) +{ + if (!pos || ReplacedFunctions.empty()) + return pos; + + const auto it = ReplacedFunctions.find(pos); + return it != ReplacedFunctions.end() ? it->second : pos; +} + +extern "C" const char *GetReplacedPos(const char *pos) +{ + return GSC::GetReplacedPos(pos); +} + +__declspec(naked) void GSC::VM_Execute_Hook() +{ + __asm + { + mr r3, r11 + bl GetReplacedPos + + mr r11, r3 + mr r10, r3 + + lbz r11, 0(r11) + addi r10, r10, 1 + cmplwi cr6, r11, 0x86 + stw r11, 0x400(r17) + + lis r12, 0x8221 + ori r12, r12, 0x2E40 + mtctr r12 + bctr + } +} + +void GSC::ReplaceFunc() +{ + if (Scr_GetNumParam() != 2) + { + Scr_Error("replacefunc: expected two function parameters"); + return; + } + + const char *what = GetCodePosForParam(0); + const char *with = GetCodePosForParam(1); + + if (!what) + { + Scr_Error("replacefunc: first parameter must be a function"); + return; + } + + if (!with) + { + Scr_Error("replacefunc: second parameter must be a function"); + return; + } + + ReplacedFunctions[what] = with; +} + GSC::GSC() { Scr_GetFunction_Detour = Detour(Scr_GetFunction, Scr_GetFunction_Hook); @@ -91,11 +172,17 @@ GSC::GSC() Scr_GetMethod_Detour = Detour(Scr_GetMethod, Scr_GetMethod_Hook); Scr_GetMethod_Detour.Install(); + // Intercept TU4 VM_Execute's opcode fetch to redirect registered function code positions, then resume at + // 0x82212E40. + VM_Execute_Detour = Detour(reinterpret_cast(0x82212E30), reinterpret_cast(VM_Execute_Hook)); + VM_Execute_Detour.Install(); + InitializeHudElemMethods(); } void GSC::OnVMShutdown() { + ReplacedFunctions.clear(); ClearHudElemLocalizedStringState(); } @@ -106,6 +193,10 @@ GSC::~GSC() Scr_GetFunction_Detour.Remove(); Scr_GetMethod_Detour.Remove(); + + VM_Execute_Detour.Remove(); + + ReplacedFunctions.clear(); } } // namespace mp } // namespace iw3 diff --git a/src/game/iw3/mp/components/gsc.h b/src/game/iw3/mp/components/gsc.h index b71bc572..213ae907 100644 --- a/src/game/iw3/mp/components/gsc.h +++ b/src/game/iw3/mp/components/gsc.h @@ -1,11 +1,15 @@ #pragma once #include "pch.h" +#include namespace iw3 { namespace mp { +// The Xbox PowerPC inline assembler requires an unmangled name for its direct branch. +extern "C" const char *GetReplacedPos(const char *pos); + class GSC : public Module { public: @@ -13,6 +17,18 @@ class GSC : public Module ~GSC(); static void OnVMShutdown(); + static void ReplaceFunc(); + + private: + friend const char *GetReplacedPos(const char *pos); + + static std::unordered_map ReplacedFunctions; + + static const char *GetCodePosForParam(int index); + static const char *GetReplacedPos(const char *pos); + + static Detour VM_Execute_Detour; + static void VM_Execute_Hook(); }; } // namespace mp } // namespace iw3 diff --git a/src/game/iw3/mp/structs.h b/src/game/iw3/mp/structs.h index faaa14c2..bff2e6c4 100644 --- a/src/game/iw3/mp/structs.h +++ b/src/game/iw3/mp/structs.h @@ -3779,5 +3779,64 @@ struct __declspec(align(2)) playerStatNetworkData }; static_assert(sizeof(playerStatNetworkData) == 0x421C, ""); +struct VariableStackBuffer; + +union VariableUnion +{ + int intValue; + float floatValue; + unsigned int stringValue; + const float *vectorValue; + const char *codePosValue; + unsigned int pointerValue; + VariableStackBuffer *stackValue; + unsigned int entityOffset; +}; + +struct VariableValue +{ + VariableUnion u; + int type; +}; +static_assert(sizeof(VariableValue) == 0x8, ""); + +struct function_stack_t +{ + const char *pos; + unsigned int localId; + unsigned int localVarCount; + VariableValue *top; + VariableValue *startTop; +}; + +struct function_frame_t +{ + function_stack_t fs; + int topType; +}; + +struct scrVmPub_t +{ + unsigned int *localVars; + VariableValue *maxstack; + int function_count; + function_frame_t *function_frame; + VariableValue *top; + bool debugCode; + bool abort_on_error; + bool terminal_error; + unsigned int inparamcount; + unsigned int outparamcount; + function_frame_t function_frame_start[32]; + VariableValue stack[2048]; +}; + +static_assert(sizeof(function_frame_t) == 0x18, ""); +static_assert(offsetof(scrVmPub_t, top) == 0x10, ""); +static_assert(offsetof(scrVmPub_t, outparamcount) == 0x1C, ""); +static_assert(offsetof(scrVmPub_t, function_frame_start) == 0x20, ""); +static_assert(offsetof(scrVmPub_t, stack) == 0x320, ""); +static_assert(sizeof(scrVmPub_t) == 0x4320, ""); + } // namespace mp } // namespace iw3 diff --git a/src/game/iw3/mp/symbols.h b/src/game/iw3/mp/symbols.h index d00809a7..28760762 100644 --- a/src/game/iw3/mp/symbols.h +++ b/src/game/iw3/mp/symbols.h @@ -550,6 +550,7 @@ static const char **var_typename = reinterpret_cast(0x823A2820); static auto cgMedia = reinterpret_cast(0x823F28A8); static auto sharedUiInfo = reinterpret_cast(0x849F8080); static auto scr_const = reinterpret_cast(0x82A22F18); +static scrVmPub_t &scrVmPub = *reinterpret_cast(0x82E3B0B0); static int *g_trackLoadProgress = reinterpret_cast(0x824754FC); static int *g_totalSize = reinterpret_cast(0x824754F8);