diff --git a/src/game/t4/mp/components/events.cpp b/src/game/t4/mp/components/events.cpp index 34a49e4a..5ae4a602 100644 --- a/src/game/t4/mp/components/events.cpp +++ b/src/game/t4/mp/components/events.cpp @@ -59,9 +59,13 @@ Detour Events::Cmd_Init_Detour; void *Events::Scr_ShutdownSystem_Hook(scriptInstance_t inst, int sys, int bComplete) { - for (size_t i = 0; i < ARRAYSIZE(vmShutdownHandlers); ++i) + // These handlers own server script state. + if (inst == SCRIPTINSTANCE_SERVER) { - vmShutdownHandlers[i](); + for (size_t i = 0; i < ARRAYSIZE(vmShutdownHandlers); ++i) + { + vmShutdownHandlers[i](); + } } return Scr_ShutdownSystem_Detour.GetOriginal()(inst, sys, bComplete); diff --git a/src/game/t4/mp/components/gsc_functions.cpp b/src/game/t4/mp/components/gsc_functions.cpp index dbb30ffc..0dee2768 100644 --- a/src/game/t4/mp/components/gsc_functions.cpp +++ b/src/game/t4/mp/components/gsc_functions.cpp @@ -8,6 +8,7 @@ namespace t4 { namespace mp { +std::unordered_map GSCFunctions::ReplacedFunctions; static void CloseAllScriptFiles() { script_files::CloseAll(); @@ -15,6 +16,7 @@ static void CloseAllScriptFiles() void GSCFunctions::OnVMShutdown() { + ReplacedFunctions.clear(); CloseAllScriptFiles(); } @@ -172,8 +174,88 @@ void GSCrGetPlayerclipBrushesContainingPoint() } } +Detour GSCFunctions::VM_Execute_Detour; + +const char *GSCFunctions::GetCodePosForParam(int index) +{ + const int numParams = static_cast(Scr_GetNumParam(SCRIPTINSTANCE_SERVER)); + + if (index < 0 || index >= numParams) + return nullptr; + + const VariableValue *value = &scrVmPub[SCRIPTINSTANCE_SERVER].top[-index]; + + if (value->type != VAR_FUNCTION) + return nullptr; + + return value->u.codePosValue; +} + +const char *GSCFunctions::GetReplacedPos(const char *pos, scriptInstance_t inst) +{ + if (inst != SCRIPTINSTANCE_SERVER || !pos || ReplacedFunctions.empty()) + return pos; + + const auto it = ReplacedFunctions.find(pos); + return it != ReplacedFunctions.end() ? it->second : pos; +} + +extern "C" const char *T4_GetReplacedPos(const char *pos, scriptInstance_t inst) +{ + return GSCFunctions::GetReplacedPos(pos, inst); +} + +__declspec(naked) void GSCFunctions::VM_Execute_Hook() +{ + __asm + { + mr r3, r10 + mr r4, r31 + bl T4_GetReplacedPos + + mr r10, r3 + + addi r11, r10, 1 + lbz r9, 0(r10) + addi r10, r27, -0x4068 + slwi r21, r31, 2 + + lis r12, 0x8234 + ori r12, r12, 0x6480 + mtctr r12 + bctr + } +} + +void GSCFunctions::ReplaceFunc() +{ + if (Scr_GetNumParam(SCRIPTINSTANCE_SERVER) != 2) + { + Scr_Error("replacefunc: expected two function parameters", SCRIPTINSTANCE_SERVER); + return; + } + + const char *what = GetCodePosForParam(0); + const char *with = GetCodePosForParam(1); + + if (!what) + { + Scr_Error("replacefunc: first parameter must be a function", SCRIPTINSTANCE_SERVER); + return; + } + + if (!with) + { + Scr_Error("replacefunc: second parameter must be a function", SCRIPTINSTANCE_SERVER); + return; + } + + ReplacedFunctions[what] = with; +} + static const gsc::Entry functions[] = { {"addtestclient", GScr_AddTestClient, BUILTIN_ANY}, + {"replacefunc", GSCFunctions::ReplaceFunc, BUILTIN_ANY}, {"getplayerclipbrushescontainingpoint", GSCrGetPlayerclipBrushesContainingPoint, BUILTIN_ANY}, {"fs_testfile", GScr_FS_TestFile, BUILTIN_ANY}, {"fs_fopen", GScr_FS_FOpen, BUILTIN_ANY}, @@ -191,7 +273,10 @@ BuiltinFunction Scr_GetFunction_Hook(const char **pName, int *type) { const gsc::Entry *function = gsc::Find(*pName, functions); if (function) + { + *type = function->type; return function->actionFunc; + } } return Scr_GetFunction_Detour.GetOriginal()(pName, type); } @@ -200,11 +285,17 @@ GSCFunctions::GSCFunctions() { Scr_GetFunction_Detour = Detour(Scr_GetFunction, Scr_GetFunction_Hook); Scr_GetFunction_Detour.Install(); + + // Intercept TU7 VM_Execute's opcode fetch, then resume at 0x82346480. + VM_Execute_Detour = Detour(reinterpret_cast(0x82346470), reinterpret_cast(VM_Execute_Hook)); + VM_Execute_Detour.Install(); } GSCFunctions::~GSCFunctions() { Scr_GetFunction_Detour.Remove(); + VM_Execute_Detour.Remove(); + ReplacedFunctions.clear(); CloseAllScriptFiles(); } } // namespace mp diff --git a/src/game/t4/mp/components/gsc_functions.h b/src/game/t4/mp/components/gsc_functions.h index 97f396f7..c259e11c 100644 --- a/src/game/t4/mp/components/gsc_functions.h +++ b/src/game/t4/mp/components/gsc_functions.h @@ -1,11 +1,15 @@ #pragma once #include "pch.h" +#include namespace t4 { namespace mp { +// Keep the assembly bridge distinct from IW3's C-linkage GetReplacedPos symbol. +extern "C" const char *T4_GetReplacedPos(const char *pos, scriptInstance_t inst); + class GSCFunctions : public Module { public: @@ -13,6 +17,18 @@ class GSCFunctions : public Module ~GSCFunctions(); static void OnVMShutdown(); + static void ReplaceFunc(); + + private: + friend const char *T4_GetReplacedPos(const char *pos, scriptInstance_t inst); + + static std::unordered_map ReplacedFunctions; + + static const char *GetCodePosForParam(int index); + static const char *GetReplacedPos(const char *pos, scriptInstance_t inst); + + static Detour VM_Execute_Detour; + static void VM_Execute_Hook(); }; } // namespace mp } // namespace t4 diff --git a/src/game/t4/mp/structs.h b/src/game/t4/mp/structs.h index 6da9e6b9..c1f60e25 100644 --- a/src/game/t4/mp/structs.h +++ b/src/game/t4/mp/structs.h @@ -1393,5 +1393,80 @@ enum connstate_t CA_ACTIVE = 0x9, }; +enum Vartype_t : __int32 +{ + VAR_UNDEFINED = 0x0, + VAR_POINTER = 0x1, + VAR_STRING = 0x2, + VAR_ISTRING = 0x3, + VAR_VECTOR = 0x4, + VAR_FLOAT = 0x5, + VAR_INTEGER = 0x6, + VAR_CODEPOS = 0x7, + VAR_PRECODEPOS = 0x8, + VAR_FUNCTION = 0x9, +}; + +struct VariableStackBuffer; + +union VariableUnion +{ + int intValue; + unsigned int uintValue; + float floatValue; + unsigned int stringValue; + const float *vectorValue; + const char *codePosValue; + unsigned int pointerValue; + VariableStackBuffer *stackValue; + unsigned int entityOffset; +}; + +struct VariableValue +{ + VariableUnion u; + Vartype_t 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 t4 diff --git a/src/game/t4/mp/symbols.h b/src/game/t4/mp/symbols.h index 8f33db97..91c16f38 100644 --- a/src/game/t4/mp/symbols.h +++ b/src/game/t4/mp/symbols.h @@ -174,6 +174,7 @@ static auto Scr_GetGenericField = reinterpret_cast(0x8234AFD0); static auto Scr_GetNumParam = reinterpret_cast(0x82345650); +static scrVmPub_t *scrVmPub = reinterpret_cast(0x85AF2F98); static auto Scr_GetObjectField = reinterpret_cast(0x822556B0); static auto Scr_GetString = reinterpret_cast(0x8234B550);