From 563444696281d655c3d9795ca9c6e4b2f4ce4c76 Mon Sep 17 00:00:00 2001 From: regenerance <161264320+regenerance@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:52:58 -0700 Subject: [PATCH 1/4] iw3 replaceFunc --- replacefunc_pr_description.txt | 59 +++++++++ src/game/iw3/mp/components/events.cpp | 2 + src/game/iw3/mp/components/gsc.cpp | 144 +++++++++++++++++++++ src/game/iw3/mp/components/gsc.h | 1 + src/game/iw3/mp/components/gsc_functions.h | 1 + src/game/iw3/mp/structs.h | 13 ++ 6 files changed, 220 insertions(+) create mode 100644 replacefunc_pr_description.txt diff --git a/replacefunc_pr_description.txt b/replacefunc_pr_description.txt new file mode 100644 index 00000000..f1e08d76 --- /dev/null +++ b/replacefunc_pr_description.txt @@ -0,0 +1,59 @@ +PR: Add GSC replaceFunc support for IW3 multiplayer + +Summary +------- +This change adds a new GSC builtin, replaceFunc, that allows one GSC function +to be replaced by another function at runtime. It is intended for mods that +need to override or extend existing script behavior without editing the +original function directly. + +What replaceFunc does +--------------------- +replaceFunc takes two function references: + + replaceFunc(originalFunction, replacementFunction); + +The first function is the function to intercept, and the second function is +the function that should run in its place. The replacement is registered by +the compiled code positions of both functions. When the script VM is about to +execute a registered original function, the VM hook resolves that code +position to the replacement code position and continues execution from the +replacement function. + +This makes it possible for a mod to redirect existing script callbacks or +functions while leaving the original game script files unchanged. + +Implementation details +---------------------- +- Registers replaceFunc as a GSC builtin available to all builtin contexts. +- Validates that exactly two parameters are supplied. +- Validates that both parameters are actual function values. +- Tracks replacements by compiled GSC code position. +- Hooks VM execution so replacements are applied whenever the original + function is called. +- Includes registration, replacement-hit, and cleanup logging to make the + behavior easy to verify during development. +- Installs and removes the VM detour with the GSC component lifecycle. + +Game and VM lifecycle +--------------------- +Replacement entries are only valid for the current script VM/game state. All +registered replacements are cleared during the script shutdown hook, before +the original shutdown routine continues. This is important because compiled +GSC code positions can become invalid after a game or map is shut down. + +The cleanup runs every time the current game is shut down, so old replacement +functions do not carry over into a new game. Each new game starts with a clean +replacement table and can register its own mappings safely. Clearing the map +removes the old replacement registrations; it does not modify or delete the +original GSC functions. + +Status +------ +The implementation works perfectly in its intended use: functions register +correctly, calls are redirected through the VM to their replacements, invalid +arguments are rejected with a diagnostic, and all old replacement entries are +cleared when the game shuts down before the next game starts. + +This keeps replaceFunc reliable across map/game transitions and prevents stale +function pointers from being reused by a later script VM instance. diff --git a/src/game/iw3/mp/components/events.cpp b/src/game/iw3/mp/components/events.cpp index 4b1259a6..3919cfd2 100644 --- a/src/game/iw3/mp/components/events.cpp +++ b/src/game/iw3/mp/components/events.cpp @@ -113,6 +113,8 @@ void Events::Scr_ShutdownSystem_Hook(unsigned __int8 sys) vmShutdownHandlers[i](); } + ClearReplacedFunctions(); // must be done on shutdown, using on closevm doesn't work + // Call original function after callbacks Scr_ShutdownSystem_Detour.GetOriginal()(sys); } diff --git a/src/game/iw3/mp/components/gsc.cpp b/src/game/iw3/mp/components/gsc.cpp index f6878bb4..938e9040 100644 --- a/src/game/iw3/mp/components/gsc.cpp +++ b/src/game/iw3/mp/components/gsc.cpp @@ -4,11 +4,13 @@ #include "gsc_functions.h" #include "gsc_methods.h" #include "sv_bots.h" +#include namespace iw3 { namespace mp { +static std::unordered_map ReplacedFunctions; namespace { static const gsc::Entry functions[] = { @@ -23,6 +25,7 @@ static const gsc::Entry functions[] = { {"float", GScr_Float, BUILTIN_ANY}, {"precachestring", Scr_PrecacheString_Stub, BUILTIN_ANY}, {"addtestclient", GScr_AddTestClient, BUILTIN_ANY}, + {"replaceFunc", GScr_ReplaceFunc, BUILTIN_ANY}, }; static const gsc::Entry methods[] = { @@ -83,6 +86,142 @@ BuiltinMethod Scr_GetMethod_Hook(const char **pName, int *type) return Scr_GetMethod_Detour.GetOriginal()(pName, type); } +//replaceFunc +Detour VM_Execute_Detour; + +const char* GetCodePosForParam(int index) +{ + const int numParams = static_cast(Scr_GetNumParam()); + + if (index < 0 || index >= numParams) + return nullptr; + + auto* top = *reinterpret_cast(0x82BA6DF8); + auto* value = top - index; + + if (value->type != VAR_FUNCTION) + return nullptr; + + return value->codePosValue; +} + +const char* IW3_GSC_ResolveReplaceFunc(const char* pos) +{ + if (!pos) + return nullptr; + + const auto it = ReplacedFunctions.find(pos); + + if (it == ReplacedFunctions.end()) + return nullptr; + + printf( + "[ReplaceFunc HIT] %p -> %p | count=%u\n", + pos, + it->second, + static_cast(ReplacedFunctions.size()) + ); + + return it->second; +} + +extern "C" const char* IW3_GSC_GetReplacedPos(const char* pos) +{ + const char* replacement = IW3_GSC_ResolveReplaceFunc(pos); + + if (replacement) + { + printf("[ReplaceFunc VM HIT] %p -> %p\n", pos, replacement); + return replacement; + } + + return pos; +} + +extern "C" __declspec(naked) void VM_Execute_Hook() +{ + __asm + { + mr r3, r11 + bl IW3_GSC_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 GScr_ReplaceFunc() +{ + if (Scr_GetNumParam() != 2) + { + printf("replaceFunc: needs two parameters\n"); + return; + } + + const char* what = GetCodePosForParam(0); + const char* with = GetCodePosForParam(1); + + if (!what) + { + printf("replaceFunc: first parameter is not a function\n"); + return; + } + + if (!with) + { + printf("replaceFunc: second parameter is not a function\n"); + return; + } + + printf( + "[ReplaceFunc REGISTER] %p -> %p | before=%u\n", + what, + with, + static_cast(ReplacedFunctions.size()) + ); + + ReplacedFunctions[what] = with; + + printf( + "[ReplaceFunc REGISTER] after=%u\n", + static_cast(ReplacedFunctions.size()) + ); +} + +void ClearReplacedFunctions() +{ + printf( + "[ReplaceFunc CLEAR] before=%u\n", + static_cast(ReplacedFunctions.size()) + ); + + for (auto it = ReplacedFunctions.begin(); it != ReplacedFunctions.end(); ++it) + { + printf( + "[ReplaceFunc CLEAR] removing %p -> %p\n", + it->first, + it->second + ); + } + + ReplacedFunctions.clear(); + + printf( + "[ReplaceFunc CLEAR] after=%u\n", + static_cast(ReplacedFunctions.size()) + ); +} + GSC::GSC() { Scr_GetFunction_Detour = Detour(Scr_GetFunction, Scr_GetFunction_Hook); @@ -91,6 +230,9 @@ GSC::GSC() Scr_GetMethod_Detour = Detour(Scr_GetMethod, Scr_GetMethod_Hook); Scr_GetMethod_Detour.Install(); + VM_Execute_Detour = Detour(reinterpret_cast(0x82212E30), reinterpret_cast(VM_Execute_Hook)); + VM_Execute_Detour.Install(); + InitializeHudElemMethods(); } @@ -106,6 +248,8 @@ GSC::~GSC() Scr_GetFunction_Detour.Remove(); Scr_GetMethod_Detour.Remove(); + + VM_Execute_Detour.Remove(); } } // namespace mp } // namespace iw3 diff --git a/src/game/iw3/mp/components/gsc.h b/src/game/iw3/mp/components/gsc.h index b71bc572..f8f3f9d2 100644 --- a/src/game/iw3/mp/components/gsc.h +++ b/src/game/iw3/mp/components/gsc.h @@ -6,6 +6,7 @@ namespace iw3 { namespace mp { +void ClearReplacedFunctions(); class GSC : public Module { public: diff --git a/src/game/iw3/mp/components/gsc_functions.h b/src/game/iw3/mp/components/gsc_functions.h index f3f726ed..d1750033 100644 --- a/src/game/iw3/mp/components/gsc_functions.h +++ b/src/game/iw3/mp/components/gsc_functions.h @@ -15,6 +15,7 @@ void GScr_FS_ReadLine(); void GScr_FS_WriteLine(); void Scr_IsArray_f(); void GScr_Float(); +void GScr_ReplaceFunc(); class GSCFunctions : public Module { diff --git a/src/game/iw3/mp/structs.h b/src/game/iw3/mp/structs.h index faaa14c2..cc196302 100644 --- a/src/game/iw3/mp/structs.h +++ b/src/game/iw3/mp/structs.h @@ -3779,5 +3779,18 @@ struct __declspec(align(2)) playerStatNetworkData }; static_assert(sizeof(playerStatNetworkData) == 0x421C, ""); +struct VariableValue +{ + union + { + int intValue; + float floatValue; + const char* stringValue; + const char* codePosValue; + }; + + int type; +}; + } // namespace mp } // namespace iw3 From 1b182c01532c2f4ad8c9c8db7691729fd7611f45 Mon Sep 17 00:00:00 2001 From: Antiga <161264320+regenerance@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:57:18 -0700 Subject: [PATCH 2/4] Delete replacefunc_pr_description.txt Nuked --- replacefunc_pr_description.txt | 59 ---------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 replacefunc_pr_description.txt diff --git a/replacefunc_pr_description.txt b/replacefunc_pr_description.txt deleted file mode 100644 index f1e08d76..00000000 --- a/replacefunc_pr_description.txt +++ /dev/null @@ -1,59 +0,0 @@ -PR: Add GSC replaceFunc support for IW3 multiplayer - -Summary -------- -This change adds a new GSC builtin, replaceFunc, that allows one GSC function -to be replaced by another function at runtime. It is intended for mods that -need to override or extend existing script behavior without editing the -original function directly. - -What replaceFunc does ---------------------- -replaceFunc takes two function references: - - replaceFunc(originalFunction, replacementFunction); - -The first function is the function to intercept, and the second function is -the function that should run in its place. The replacement is registered by -the compiled code positions of both functions. When the script VM is about to -execute a registered original function, the VM hook resolves that code -position to the replacement code position and continues execution from the -replacement function. - -This makes it possible for a mod to redirect existing script callbacks or -functions while leaving the original game script files unchanged. - -Implementation details ----------------------- -- Registers replaceFunc as a GSC builtin available to all builtin contexts. -- Validates that exactly two parameters are supplied. -- Validates that both parameters are actual function values. -- Tracks replacements by compiled GSC code position. -- Hooks VM execution so replacements are applied whenever the original - function is called. -- Includes registration, replacement-hit, and cleanup logging to make the - behavior easy to verify during development. -- Installs and removes the VM detour with the GSC component lifecycle. - -Game and VM lifecycle ---------------------- -Replacement entries are only valid for the current script VM/game state. All -registered replacements are cleared during the script shutdown hook, before -the original shutdown routine continues. This is important because compiled -GSC code positions can become invalid after a game or map is shut down. - -The cleanup runs every time the current game is shut down, so old replacement -functions do not carry over into a new game. Each new game starts with a clean -replacement table and can register its own mappings safely. Clearing the map -removes the old replacement registrations; it does not modify or delete the -original GSC functions. - -Status ------- -The implementation works perfectly in its intended use: functions register -correctly, calls are redirected through the VM to their replacements, invalid -arguments are rejected with a diagnostic, and all old replacement entries are -cleared when the game shuts down before the next game starts. - -This keeps replaceFunc reliable across map/game transitions and prevents stale -function pointers from being reused by a later script VM instance. From 4df38fcfa546fd8e9059231c3ab6b4882e442767 Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Sat, 26 Sep 2026 07:48:30 +0100 Subject: [PATCH 3/4] chore: tidy up various things --- src/game/iw3/mp/components/gsc.cpp | 74 ++++-------------------------- src/game/iw3/mp/structs.h | 62 +++++++++++++++++++++---- src/game/iw3/mp/symbols.h | 1 + 3 files changed, 64 insertions(+), 73 deletions(-) diff --git a/src/game/iw3/mp/components/gsc.cpp b/src/game/iw3/mp/components/gsc.cpp index 938e9040..785ad867 100644 --- a/src/game/iw3/mp/components/gsc.cpp +++ b/src/game/iw3/mp/components/gsc.cpp @@ -25,7 +25,7 @@ static const gsc::Entry functions[] = { {"float", GScr_Float, BUILTIN_ANY}, {"precachestring", Scr_PrecacheString_Stub, BUILTIN_ANY}, {"addtestclient", GScr_AddTestClient, BUILTIN_ANY}, - {"replaceFunc", GScr_ReplaceFunc, BUILTIN_ANY}, + {"replacefunc", GScr_ReplaceFunc, BUILTIN_ANY}, }; static const gsc::Entry methods[] = { @@ -86,7 +86,6 @@ BuiltinMethod Scr_GetMethod_Hook(const char **pName, int *type) return Scr_GetMethod_Detour.GetOriginal()(pName, type); } -//replaceFunc Detour VM_Execute_Detour; const char* GetCodePosForParam(int index) @@ -96,46 +95,21 @@ const char* GetCodePosForParam(int index) if (index < 0 || index >= numParams) return nullptr; - auto* top = *reinterpret_cast(0x82BA6DF8); - auto* value = top - index; + const VariableValue *value = &scrVmPub.top[-index]; if (value->type != VAR_FUNCTION) return nullptr; - return value->codePosValue; + return value->u.codePosValue; } -const char* IW3_GSC_ResolveReplaceFunc(const char* pos) +extern "C" const char* IW3_GSC_GetReplacedPos(const char* pos) { if (!pos) return nullptr; const auto it = ReplacedFunctions.find(pos); - - if (it == ReplacedFunctions.end()) - return nullptr; - - printf( - "[ReplaceFunc HIT] %p -> %p | count=%u\n", - pos, - it->second, - static_cast(ReplacedFunctions.size()) - ); - - return it->second; -} - -extern "C" const char* IW3_GSC_GetReplacedPos(const char* pos) -{ - const char* replacement = IW3_GSC_ResolveReplaceFunc(pos); - - if (replacement) - { - printf("[ReplaceFunc VM HIT] %p -> %p\n", pos, replacement); - return replacement; - } - - return pos; + return it != ReplacedFunctions.end() ? it->second : pos; } extern "C" __declspec(naked) void VM_Execute_Hook() @@ -164,7 +138,7 @@ void GScr_ReplaceFunc() { if (Scr_GetNumParam() != 2) { - printf("replaceFunc: needs two parameters\n"); + Scr_Error("replacefunc: expected two function parameters"); return; } @@ -173,53 +147,22 @@ void GScr_ReplaceFunc() if (!what) { - printf("replaceFunc: first parameter is not a function\n"); + Scr_Error("replacefunc: first parameter must be a function"); return; } if (!with) { - printf("replaceFunc: second parameter is not a function\n"); + Scr_Error("replacefunc: second parameter must be a function"); return; } - printf( - "[ReplaceFunc REGISTER] %p -> %p | before=%u\n", - what, - with, - static_cast(ReplacedFunctions.size()) - ); - ReplacedFunctions[what] = with; - - printf( - "[ReplaceFunc REGISTER] after=%u\n", - static_cast(ReplacedFunctions.size()) - ); } void ClearReplacedFunctions() { - printf( - "[ReplaceFunc CLEAR] before=%u\n", - static_cast(ReplacedFunctions.size()) - ); - - for (auto it = ReplacedFunctions.begin(); it != ReplacedFunctions.end(); ++it) - { - printf( - "[ReplaceFunc CLEAR] removing %p -> %p\n", - it->first, - it->second - ); - } - ReplacedFunctions.clear(); - - printf( - "[ReplaceFunc CLEAR] after=%u\n", - static_cast(ReplacedFunctions.size()) - ); } GSC::GSC() @@ -230,6 +173,7 @@ 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(); diff --git a/src/game/iw3/mp/structs.h b/src/game/iw3/mp/structs.h index cc196302..bff2e6c4 100644 --- a/src/game/iw3/mp/structs.h +++ b/src/game/iw3/mp/structs.h @@ -3779,18 +3779,64 @@ struct __declspec(align(2)) playerStatNetworkData }; static_assert(sizeof(playerStatNetworkData) == 0x421C, ""); -struct VariableValue +struct VariableStackBuffer; + +union VariableUnion { - union - { - int intValue; - float floatValue; - const char* stringValue; - const char* codePosValue; - }; + 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); From 61c1d841b8df7652de3da0b2065fd7f93919b808 Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Tue, 6 Oct 2026 13:18:14 +0100 Subject: [PATCH 4/4] refactor: centralise replacefunc state and helpers in GSC --- src/game/iw3/mp/components/events.cpp | 2 - src/game/iw3/mp/components/gsc.cpp | 43 ++++++++++++---------- src/game/iw3/mp/components/gsc.h | 17 ++++++++- src/game/iw3/mp/components/gsc_functions.h | 1 - 4 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/game/iw3/mp/components/events.cpp b/src/game/iw3/mp/components/events.cpp index 3919cfd2..4b1259a6 100644 --- a/src/game/iw3/mp/components/events.cpp +++ b/src/game/iw3/mp/components/events.cpp @@ -113,8 +113,6 @@ void Events::Scr_ShutdownSystem_Hook(unsigned __int8 sys) vmShutdownHandlers[i](); } - ClearReplacedFunctions(); // must be done on shutdown, using on closevm doesn't work - // Call original function after callbacks Scr_ShutdownSystem_Detour.GetOriginal()(sys); } diff --git a/src/game/iw3/mp/components/gsc.cpp b/src/game/iw3/mp/components/gsc.cpp index 785ad867..65053d1d 100644 --- a/src/game/iw3/mp/components/gsc.cpp +++ b/src/game/iw3/mp/components/gsc.cpp @@ -4,13 +4,12 @@ #include "gsc_functions.h" #include "gsc_methods.h" #include "sv_bots.h" -#include namespace iw3 { namespace mp { -static std::unordered_map ReplacedFunctions; +std::unordered_map GSC::ReplacedFunctions; namespace { static const gsc::Entry functions[] = { @@ -25,7 +24,7 @@ static const gsc::Entry functions[] = { {"float", GScr_Float, BUILTIN_ANY}, {"precachestring", Scr_PrecacheString_Stub, BUILTIN_ANY}, {"addtestclient", GScr_AddTestClient, BUILTIN_ANY}, - {"replacefunc", GScr_ReplaceFunc, BUILTIN_ANY}, + {"replacefunc", GSC::ReplaceFunc, BUILTIN_ANY}, }; static const gsc::Entry methods[] = { @@ -86,9 +85,9 @@ BuiltinMethod Scr_GetMethod_Hook(const char **pName, int *type) return Scr_GetMethod_Detour.GetOriginal()(pName, type); } -Detour VM_Execute_Detour; +Detour GSC::VM_Execute_Detour; -const char* GetCodePosForParam(int index) +const char *GSC::GetCodePosForParam(int index) { const int numParams = static_cast(Scr_GetNumParam()); @@ -103,21 +102,26 @@ const char* GetCodePosForParam(int index) return value->u.codePosValue; } -extern "C" const char* IW3_GSC_GetReplacedPos(const char* pos) +const char *GSC::GetReplacedPos(const char *pos) { - if (!pos) - return nullptr; + if (!pos || ReplacedFunctions.empty()) + return pos; const auto it = ReplacedFunctions.find(pos); return it != ReplacedFunctions.end() ? it->second : pos; } -extern "C" __declspec(naked) void VM_Execute_Hook() +extern "C" const char *GetReplacedPos(const char *pos) +{ + return GSC::GetReplacedPos(pos); +} + +__declspec(naked) void GSC::VM_Execute_Hook() { __asm { mr r3, r11 - bl IW3_GSC_GetReplacedPos + bl GetReplacedPos mr r11, r3 mr r10, r3 @@ -134,7 +138,7 @@ extern "C" __declspec(naked) void VM_Execute_Hook() } } -void GScr_ReplaceFunc() +void GSC::ReplaceFunc() { if (Scr_GetNumParam() != 2) { @@ -142,8 +146,8 @@ void GScr_ReplaceFunc() return; } - const char* what = GetCodePosForParam(0); - const char* with = GetCodePosForParam(1); + const char *what = GetCodePosForParam(0); + const char *with = GetCodePosForParam(1); if (!what) { @@ -160,11 +164,6 @@ void GScr_ReplaceFunc() ReplacedFunctions[what] = with; } -void ClearReplacedFunctions() -{ - ReplacedFunctions.clear(); -} - GSC::GSC() { Scr_GetFunction_Detour = Detour(Scr_GetFunction, Scr_GetFunction_Hook); @@ -173,8 +172,9 @@ 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)); + // 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(); @@ -182,6 +182,7 @@ GSC::GSC() void GSC::OnVMShutdown() { + ReplacedFunctions.clear(); ClearHudElemLocalizedStringState(); } @@ -194,6 +195,8 @@ GSC::~GSC() 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 f8f3f9d2..213ae907 100644 --- a/src/game/iw3/mp/components/gsc.h +++ b/src/game/iw3/mp/components/gsc.h @@ -1,12 +1,15 @@ #pragma once #include "pch.h" +#include namespace iw3 { namespace mp { -void ClearReplacedFunctions(); +// 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: @@ -14,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/components/gsc_functions.h b/src/game/iw3/mp/components/gsc_functions.h index d1750033..f3f726ed 100644 --- a/src/game/iw3/mp/components/gsc_functions.h +++ b/src/game/iw3/mp/components/gsc_functions.h @@ -15,7 +15,6 @@ void GScr_FS_ReadLine(); void GScr_FS_WriteLine(); void Scr_IsArray_f(); void GScr_Float(); -void GScr_ReplaceFunc(); class GSCFunctions : public Module {