Fix script VM value cleanup and null vector handling
Some checks failed
Build / Build binaries (push) Has been cancelled
Build / Build binaries-1 (push) Has been cancelled

This commit is contained in:
William Roy 2026-08-14 12:38:55 -04:00
parent 091655f2f5
commit c75a6288c2
3 changed files with 92 additions and 14 deletions

View file

@ -602,25 +602,33 @@ namespace Components
int Chat::GetCallbackReturn()
{
if (Game::scrVmPub->inparamcount == 0)
const auto returnCount = Game::scrVmPub->inparamcount;
if (returnCount == 0)
{
// Nothing. Let's not mute the player
return 1;
}
Game::Scr_ClearOutParams();
Game::scrVmPub->outparamcount = Game::scrVmPub->inparamcount;
Game::scrVmPub->inparamcount = 0;
const auto* result = &Game::scrVmPub->top[1 - Game::scrVmPub->outparamcount];
const auto* result = &Game::scrVmPub->top[1 - returnCount];
auto mute = 1;
if (result->type != Game::VAR_INTEGER)
if (result->type == Game::VAR_INTEGER)
{
// Garbage was returned
return 1;
mute = result->u.intValue;
}
return result->u.intValue;
for (auto i = 0u; i < returnCount; ++i)
{
Game::RemoveRefToValue(Game::scrVmPub->top->type, Game::scrVmPub->top->u);
Game::scrVmPub->top->type = Game::VAR_UNDEFINED;
--Game::scrVmPub->top;
}
Game::scrVmPub->inparamcount = 0;
return mute;
}
int Chat::ChatCallback(Game::gentity_s* self, const char* codePos, const char* message, int mode)
@ -637,12 +645,6 @@ namespace Components
const auto result = GetCallbackReturn();
Game::RemoveRefToValue(Game::scrVmPub->top->type, Game::scrVmPub->top->u);
Game::scrVmPub->top->type = Game::VAR_UNDEFINED;
--Game::scrVmPub->top;
--Game::scrVmPub->inparamcount;
Game::Scr_FreeThread(static_cast<std::uint16_t>(id));
return result;

View file

@ -11,6 +11,22 @@ namespace Components::GSC
std::unordered_map<std::string, int> Script::ScriptMainHandles;
std::unordered_map<std::string, int> Script::ScriptInitHandles;
namespace
{
void WarnNullVectorValue(const char* function)
{
static bool warningPrinted = false;
if (warningPrinted)
{
return;
}
warningPrinted = true;
Logger::Warning(Game::CON_CHANNEL_SCRIPT,
"Ignoring null script vector in {}.\n", function);
}
}
void Script::Scr_LoadGameType_Stub()
{
for (const auto& handle : ScriptMainHandles)
@ -300,6 +316,62 @@ namespace Components::GSC
return Game::Scr_GetNumParam();
}
void Script::AddRefToValueStub(int type, Game::VariableUnion u)
{
switch (type)
{
case Game::VAR_POINTER:
Game::AddRefToObject(u.pointerValue);
break;
case Game::VAR_STRING:
case Game::VAR_ISTRING:
Game::SL_AddRefToString(u.stringValue);
break;
case Game::VAR_VECTOR:
{
if (!u.vectorValue)
{
WarnNullVectorValue("AddRefToValue");
return;
}
const auto* value = reinterpret_cast<const std::uint8_t*>(u.vectorValue);
if (value[-1] == 0)
{
++(*reinterpret_cast<unsigned short*>(const_cast<std::uint8_t*>(value - 4)));
}
break;
}
default:
break;
}
}
void Script::RemoveRefToValueStub(int type, Game::VariableUnion u)
{
switch (type)
{
case Game::VAR_POINTER:
Game::RemoveRefToObject(u.pointerValue);
break;
case Game::VAR_STRING:
case Game::VAR_ISTRING:
Game::SL_RemoveRefToString(u.stringValue);
break;
case Game::VAR_VECTOR:
if (!u.vectorValue)
{
WarnNullVectorValue("RemoveRefToValue");
return;
}
Utils::Hook::Call<void(const float*)>(0x4C10F0)(u.vectorValue);
break;
default:
break;
}
}
Game::client_s* Script::GetClient(const Game::gentity_s* ent)
{
assert(ent);
@ -358,6 +430,8 @@ namespace Components::GSC
Utils::Hook(0x5F41A3, SetExpFogStub, HOOK_CALL).install()->quick();
Utils::Hook(0x482740, AddRefToValueStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x48E170, RemoveRefToValueStub, HOOK_JUMP).install()->quick();
// Restore IW3's compiler behaviour when dealing with 'overriding builtin function'
Utils::Hook::Nop(0x613EDA, 2); // Scr_GetFunction

View file

@ -57,5 +57,7 @@ namespace Components::GSC
static Game::BuiltinMethod BuiltIn_GetMethodStub(const char** pName, int* type);
static unsigned int SetExpFogStub();
static void AddRefToValueStub(int type, Game::VariableUnion u);
static void RemoveRefToValueStub(int type, Game::VariableUnion u);
};
}