#include "pch.h" #include "squirrel.h" #include "hooks.h" #include "hookutils.h" #include "sigscanning.h" #include "concommand.h" #include "modmanager.h" #include "dedicated.h" #include "rcon_shared.h" #include "sv_rcon.h" #include "gameutils.h" // hook forward declarations typedef SQInteger (*SQPrintType)(void* sqvm, char* fmt, ...); SQPrintType ClientSQPrint; SQPrintType UISQPrint; SQPrintType ServerSQPrint; template SQInteger SQPrintHook(void* sqvm, char* fmt, ...); typedef void* (*CreateNewVMType)(void* a1, ScriptContext contextArg); CreateNewVMType ClientCreateNewVM; // only need a client one since ui doesn't have its own func for this CreateNewVMType ServerCreateNewVM; template void* CreateNewVMHook(void* a1, ScriptContext contextArg); typedef void (*DestroyVMType)(void* a1, void* sqvm); DestroyVMType ClientDestroyVM; // only need a client one since ui doesn't have its own func for this DestroyVMType ServerDestroyVM; template void DestroyVMHook(void* a1, void* sqvm); typedef void (*ScriptCompileError)(void* sqvm, const char* error, const char* file, int line, int column); ScriptCompileError ClientSQCompileError; // only need a client one since ui doesn't have its own func for this ScriptCompileError ServerSQCompileError; template void ScriptCompileErrorHook(void* sqvm, const char* error, const char* file, int line, int column); typedef char (*CallScriptInitCallbackType)(void* sqvm, const char* callback); CallScriptInitCallbackType ClientCallScriptInitCallback; CallScriptInitCallbackType ServerCallScriptInitCallback; template char CallScriptInitCallbackHook(void* sqvm, const char* callback); RegisterSquirrelFuncType ClientRegisterSquirrelFunc; RegisterSquirrelFuncType ServerRegisterSquirrelFunc; template int64_t RegisterSquirrelFuncHook(void* sqvm, SQFuncRegistration* funcReg, char unknown); // core sqvm funcs sq_compilebufferType ClientSq_compilebuffer; sq_compilebufferType ServerSq_compilebuffer; sq_pushroottableType ClientSq_pushroottable; sq_pushroottableType ServerSq_pushroottable; sq_callType ClientSq_call; sq_callType ServerSq_call; // sq stack array funcs sq_newarrayType ClientSq_newarray; sq_newarrayType ServerSq_newarray; sq_arrayappendType ClientSq_arrayappend; sq_arrayappendType ServerSq_arrayappend; // sq stack push funcs sq_pushstringType ClientSq_pushstring; sq_pushstringType ServerSq_pushstring; sq_pushintegerType ClientSq_pushinteger; sq_pushintegerType ServerSq_pushinteger; sq_pushfloatType ClientSq_pushfloat; sq_pushfloatType ServerSq_pushfloat; sq_pushboolType ClientSq_pushbool; sq_pushboolType ServerSq_pushbool; sq_pusherrorType ClientSq_pusherror; sq_pusherrorType ServerSq_pusherror; sq_defconst ClientSq_defconst; sq_defconst ServerSq_defconst; sq_pushAssetType ClientSq_pushAsset; sq_pushAssetType ServerSq_pushAsset; // sq stack get funcs sq_getstringType ClientSq_getstring; sq_getstringType ServerSq_getstring; sq_getintegerType ClientSq_getinteger; sq_getintegerType ServerSq_getinteger; sq_getfloatType ClientSq_getfloat; sq_getfloatType ServerSq_getfloat; sq_getboolType ClientSq_getbool; sq_getboolType ServerSq_getbool; sq_getType ClientSq_sq_get; sq_getType ServerSq_sq_get; template void ExecuteCodeCommand(const CCommand& args); // inits SquirrelManager* g_ClientSquirrelManager; SquirrelManager* g_ServerSquirrelManager; SquirrelManager* g_UISquirrelManager; SQInteger NSTestFunc(void* sqvm) { return 1; } void InitialiseClientSquirrel(HMODULE baseAddress) { HookEnabler hook; // client inits g_ClientSquirrelManager = new SquirrelManager(); ENABLER_CREATEHOOK( hook, (char*)baseAddress + 0x12B00, &SQPrintHook, reinterpret_cast(&ClientSQPrint)); // client print function RegisterConCommand( "script_client", ExecuteCodeCommand, "Executes script code on the client vm", FCVAR_CLIENTDLL); // ui inits g_UISquirrelManager = new SquirrelManager(); ENABLER_CREATEHOOK( hook, (char*)baseAddress + 0x12BA0, &SQPrintHook, reinterpret_cast(&UISQPrint)); // ui print function RegisterConCommand("script_ui", ExecuteCodeCommand, "Executes script code on the ui vm", FCVAR_CLIENTDLL); // inits for both client and ui, since they share some functions ClientSq_compilebuffer = (sq_compilebufferType)((char*)baseAddress + 0x3110); ClientSq_pushroottable = (sq_pushroottableType)((char*)baseAddress + 0x5860); ClientSq_call = (sq_callType)((char*)baseAddress + 0x8650); ClientRegisterSquirrelFunc = (RegisterSquirrelFuncType)((char*)baseAddress + 0x108E0); ClientSq_newarray = (sq_newarrayType)((char*)baseAddress + 0x39F0); ClientSq_arrayappend = (sq_arrayappendType)((char*)baseAddress + 0x3C70); ClientSq_pushstring = (sq_pushstringType)((char*)baseAddress + 0x3440); ClientSq_pushinteger = (sq_pushintegerType)((char*)baseAddress + 0x36A0); ClientSq_pushfloat = (sq_pushfloatType)((char*)baseAddress + 0x3800); ClientSq_pushbool = (sq_pushboolType)((char*)baseAddress + 0x3710); ClientSq_pusherror = (sq_pusherrorType)((char*)baseAddress + 0x8470); ClientSq_pushAsset = (sq_pushAssetType)((char*)baseAddress + 0x3560); ClientSq_getstring = (sq_getstringType)((char*)baseAddress + 0x60C0); ClientSq_getinteger = (sq_getintegerType)((char*)baseAddress + 0x60E0); ClientSq_getfloat = (sq_getfloatType)((char*)baseAddress + 0x6100); ClientSq_getbool = (sq_getboolType)((char*)baseAddress + 0x6130); ClientSq_sq_get = (sq_getType)((char*)baseAddress + 0x7C30); ClientSq_defconst = (sq_defconst)((char*)baseAddress + 0x12120); ENABLER_CREATEHOOK( hook, (char*)baseAddress + 0x26130, &CreateNewVMHook, reinterpret_cast(&ClientCreateNewVM)); // client createnewvm function ENABLER_CREATEHOOK( hook, (char*)baseAddress + 0x26E70, &DestroyVMHook, reinterpret_cast(&ClientDestroyVM)); // client destroyvm function ENABLER_CREATEHOOK( hook, (char*)baseAddress + 0x79A50, &ScriptCompileErrorHook, reinterpret_cast(&ClientSQCompileError)); // client compileerror function ENABLER_CREATEHOOK( hook, (char*)baseAddress + 0x10190, &CallScriptInitCallbackHook, reinterpret_cast(&ClientCallScriptInitCallback)); // client callscriptinitcallback function ENABLER_CREATEHOOK( hook, (char*)baseAddress + 0x108E0, &RegisterSquirrelFuncHook, reinterpret_cast(&ClientRegisterSquirrelFunc)); // client registersquirrelfunc function } void InitialiseServerSquirrel(HMODULE baseAddress) { g_ServerSquirrelManager = new SquirrelManager(); HookEnabler hook; ServerSq_compilebuffer = (sq_compilebufferType)((char*)baseAddress + 0x3110); ServerSq_pushroottable = (sq_pushroottableType)((char*)baseAddress + 0x5840); ServerSq_call = (sq_callType)((char*)baseAddress + 0x8620); ServerRegisterSquirrelFunc = (RegisterSquirrelFuncType)((char*)baseAddress + 0x1DD10); ServerSq_newarray = (sq_newarrayType)((char*)baseAddress + 0x39F0); ServerSq_arrayappend = (sq_arrayappendType)((char*)baseAddress + 0x3C70); ServerSq_pushstring = (sq_pushstringType)((char*)baseAddress + 0x3440); ServerSq_pushinteger = (sq_pushintegerType)((char*)baseAddress + 0x36A0); ServerSq_pushfloat = (sq_pushfloatType)((char*)baseAddress + 0x3800); ServerSq_pushbool = (sq_pushboolType)((char*)baseAddress + 0x3710); ServerSq_pusherror = (sq_pusherrorType)((char*)baseAddress + 0x8440); ServerSq_pushAsset = (sq_pushAssetType)((char*)baseAddress + 0x3560); ServerSq_getstring = (sq_getstringType)((char*)baseAddress + 0x60A0); ServerSq_getinteger = (sq_getintegerType)((char*)baseAddress + 0x60C0); ServerSq_getfloat = (sq_getfloatType)((char*)baseAddress + 0x60E0); ServerSq_getbool = (sq_getboolType)((char*)baseAddress + 0x6110); ServerSq_sq_get = (sq_getType)((char*)baseAddress + 0x7C00); ServerSq_defconst = (sq_defconst)((char*)baseAddress + 0x1F550); ENABLER_CREATEHOOK( hook, (char*)baseAddress + 0x1FE90, &SQPrintHook, reinterpret_cast(&ServerSQPrint)); // server print function ENABLER_CREATEHOOK( hook, (char*)baseAddress + 0x260E0, &CreateNewVMHook, reinterpret_cast(&ServerCreateNewVM)); // server createnewvm function ENABLER_CREATEHOOK( hook, (char*)baseAddress + 0x26E20, &DestroyVMHook, reinterpret_cast(&ServerDestroyVM)); // server destroyvm function ENABLER_CREATEHOOK( hook, (char*)baseAddress + 0x799E0, &ScriptCompileErrorHook, reinterpret_cast(&ServerSQCompileError)); // server compileerror function ENABLER_CREATEHOOK( hook, (char*)baseAddress + 0x1D5C0, &CallScriptInitCallbackHook, reinterpret_cast(&ServerCallScriptInitCallback)); // server callscriptinitcallback function ENABLER_CREATEHOOK( hook, (char*)baseAddress + 0x1DD10, &RegisterSquirrelFuncHook, reinterpret_cast(&ServerRegisterSquirrelFunc)); // server registersquirrelfunc function // cheat and clientcmd_can_execute allows clients to execute this, but since it's unsafe we only allow it when cheats are enabled // for script_client and script_ui, we don't use cheats, so clients can execute them on themselves all they want RegisterConCommand( "script", ExecuteCodeCommand, "Executes script code on the server vm", FCVAR_GAMEDLL | FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_CHEAT); } // hooks template SQInteger SQPrintHook(void* sqvm, char* fmt, ...) { va_list va; va_start(va, fmt); SQChar buf[1024]; int charsWritten = vsnprintf_s(buf, _TRUNCATE, fmt, va); if (charsWritten > 0) { if (buf[charsWritten - 1] == '\n') buf[charsWritten - 1] = '\0'; spdlog::info("[{} SCRIPT] {}", GetContextName(context), buf); if (IsDedicated()) { if (CVar_sv_rcon_sendlogs->GetBool()) { char sendbuf[1024] {}; snprintf(sendbuf, sizeof(sendbuf), "[%s SCRIPT] %s", GetContextName(context), buf); RCONServer()->Send(RCONServer()->Serialize(sendbuf, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG)); } } } va_end(va); return 0; } template void* CreateNewVMHook(void* a1, ScriptContext realContext) { void* sqvm; if (context == ScriptContext::CLIENT) { sqvm = ClientCreateNewVM(a1, realContext); if (realContext == ScriptContext::UI) g_UISquirrelManager->VMCreated(sqvm); else g_ClientSquirrelManager->VMCreated(sqvm); } else if (context == ScriptContext::SERVER) { sqvm = ServerCreateNewVM(a1, context); g_ServerSquirrelManager->VMCreated(sqvm); } spdlog::info("CreateNewVM {} {}", GetContextName(realContext), sqvm); return sqvm; } template void DestroyVMHook(void* a1, void* sqvm) { ScriptContext realContext = context; // ui and client use the same function so we use this for prints char buffer[2048] {}; if (context == ScriptContext::CLIENT) { if (g_ClientSquirrelManager->sqvm == sqvm) g_ClientSquirrelManager->VMDestroyed(); else if (g_UISquirrelManager->sqvm == sqvm) { g_UISquirrelManager->VMDestroyed(); realContext = ScriptContext::UI; } ClientDestroyVM(a1, sqvm); } else if (context == ScriptContext::SERVER) { g_ServerSquirrelManager->VMDestroyed(); ServerDestroyVM(a1, sqvm); } spdlog::info("DestroyVM {} {}", GetContextName(realContext), sqvm); } template void ScriptCompileErrorHook(void* sqvm, const char* error, const char* file, int line, int column) { char buffer[2048] {}; ScriptContext realContext = context; // ui and client use the same function so we use this for prints if (context == ScriptContext::CLIENT && sqvm == g_UISquirrelManager->sqvm) realContext = ScriptContext::UI; snprintf( buffer, sizeof(buffer), "%s SCRIPT COMPILE ERROR: %s\n%s line [%d] column [%d]", GetContextName(realContext), error, file, line, column); spdlog::error("{}", buffer); if (IsDedicated()) { if (CVar_sv_rcon_sendlogs->GetBool()) { RCONServer()->Send(RCONServer()->Serialize(buffer, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG)); } } // dont call the original since it kills game // in the future it'd be nice to do an actual error with UICodeCallback_ErrorDialog here, but only if we're compiling level scripts // compilestring and stuff shouldn't tho // though, that also has potential to be REALLY bad if we're compiling ui scripts lol } template char CallScriptInitCallbackHook(void* sqvm, const char* callback) { char ret; if (context == ScriptContext::CLIENT) { ScriptContext realContext = context; // ui and client use the same function so we use this for prints bool shouldCallCustomCallbacks = false; // since we don't hook arbitrary callbacks yet, make sure we're only doing callbacks on inits if (!strcmp(callback, "UICodeCallback_UIInit")) { realContext = ScriptContext::UI; shouldCallCustomCallbacks = true; } else if (!strcmp(callback, "ClientCodeCallback_MapSpawn")) shouldCallCustomCallbacks = true; // run before callbacks // todo: we need to verify if RunOn is valid for current state before calling callbacks if (shouldCallCustomCallbacks) { for (Mod mod : g_ModManager->m_loadedMods) { if (!mod.Enabled) continue; for (ModScript script : mod.Scripts) { for (ModScriptCallback modCallback : script.Callbacks) { if (modCallback.Context == realContext && modCallback.BeforeCallback.length()) { spdlog::info( "Running custom {} script callback \"{}\"", GetContextName(realContext), modCallback.BeforeCallback); ClientCallScriptInitCallback(sqvm, modCallback.BeforeCallback.c_str()); } } } } } spdlog::info("{} CodeCallback {} called", GetContextName(realContext), callback); if (!shouldCallCustomCallbacks) spdlog::info("Not executing custom callbacks for CodeCallback {}", callback); ret = ClientCallScriptInitCallback(sqvm, callback); // run after callbacks if (shouldCallCustomCallbacks) { for (Mod mod : g_ModManager->m_loadedMods) { if (!mod.Enabled) continue; for (ModScript script : mod.Scripts) { for (ModScriptCallback modCallback : script.Callbacks) { if (modCallback.Context == realContext && modCallback.AfterCallback.length()) { spdlog::info( "Running custom {} script callback \"{}\"", GetContextName(realContext), modCallback.AfterCallback); ClientCallScriptInitCallback(sqvm, modCallback.AfterCallback.c_str()); } } } } } } else if (context == ScriptContext::SERVER) { // since we don't hook arbitrary callbacks yet, make sure we're only doing callbacks on inits bool shouldCallCustomCallbacks = !strcmp(callback, "CodeCallback_MapSpawn"); // run before callbacks // todo: we need to verify if RunOn is valid for current state before calling callbacks if (shouldCallCustomCallbacks) { for (Mod mod : g_ModManager->m_loadedMods) { if (!mod.Enabled) continue; for (ModScript script : mod.Scripts) { for (ModScriptCallback modCallback : script.Callbacks) { if (modCallback.Context == ScriptContext::SERVER && modCallback.BeforeCallback.length()) { spdlog::info("Running custom {} script callback \"{}\"", GetContextName(context), modCallback.BeforeCallback); ServerCallScriptInitCallback(sqvm, modCallback.BeforeCallback.c_str()); } } } } } spdlog::info("{} CodeCallback {} called", GetContextName(context), callback); if (!shouldCallCustomCallbacks) spdlog::info("Not executing custom callbacks for CodeCallback {}", callback); ret = ServerCallScriptInitCallback(sqvm, callback); // run after callbacks if (shouldCallCustomCallbacks) { for (Mod mod : g_ModManager->m_loadedMods) { if (!mod.Enabled) continue; for (ModScript script : mod.Scripts) { for (ModScriptCallback modCallback : script.Callbacks) { if (modCallback.Context == ScriptContext::SERVER && modCallback.AfterCallback.length()) { spdlog::info("Running custom {} script callback \"{}\"", GetContextName(context), modCallback.AfterCallback); ServerCallScriptInitCallback(sqvm, modCallback.AfterCallback.c_str()); } } } } } } return ret; } template void ExecuteCodeCommand(const CCommand& args) { if (context == ScriptContext::CLIENT) g_ClientSquirrelManager->ExecuteCode(args.ArgS()); else if (context == ScriptContext::UI) g_UISquirrelManager->ExecuteCode(args.ArgS()); else if (context == ScriptContext::SERVER) g_ServerSquirrelManager->ExecuteCode(args.ArgS()); } SQRESULT SQ_DevFuncStub(void* sqvm) { spdlog::warn("Blocked execution of squirrel developer function for security reasons. To re-enable them use start parameter " "-allowSquirrelDevFunctions."); return SQRESULT_NULL; } template int64_t RegisterSquirrelFuncHook(void* sqvm, SQFuncRegistration* funcReg, char unknown) { static std::set allowedDevFunctions = { "Dev_CommandLineHasParm", "Dev_CommandLineParmValue", "Dev_CommandLineRemoveParm", }; if ((funcReg->devLevel == 1) && (!CommandLine()->CheckParm("-allowSquirrelDevFunctions")) && (!allowedDevFunctions.count(funcReg->squirrelFuncName))) funcReg->funcPtr = SQ_DevFuncStub; if (context == ScriptContext::SERVER) return ServerRegisterSquirrelFunc(sqvm, funcReg, unknown); else return ClientRegisterSquirrelFunc(sqvm, funcReg, unknown); } SQReturnTypeEnum GetReturnTypeEnumFromString(const char* returnTypeString) { static std::map sqEnumStrMap = { {"bool", SqReturnBoolean}, {"float", SqReturnFloat}, {"vector", SqReturnVector}, {"int", SqReturnInteger}, {"entity", SqReturnEntity}, {"string", SqReturnString}, {"array", SqReturnArrays}, {"asset", SqReturnAsset}, {"table", SqReturnTable}}; if (sqEnumStrMap.count(returnTypeString)) { return sqEnumStrMap[returnTypeString]; } else { return SqReturnDefault; // previous default value } }