#include "pch.h" #include "squirrel.h" #include "concommand.h" #include "modmanager.h" #include "dedicated.h" #include "r2engine.h" AUTOHOOK_INIT() const char* GetContextName(ScriptContext context) { switch (context) { case ScriptContext::CLIENT: return "CLIENT"; case ScriptContext::SERVER: return "SERVER"; case ScriptContext::UI: return "UI"; } } // hooks template void* (*sq_compiler_create)(void* sqvm, void* a2, void* a3, SQBool bShouldThrowError); template void* sq_compiler_createHook(void* sqvm, void* a2, void* a3, SQBool bShouldThrowError) { // store whether errors generated from this compile should be fatal if (context == ScriptContext::CLIENT && sqvm == g_pSquirrel->sqvm2) g_pSquirrel->m_bCompilationErrorsFatal = bShouldThrowError; else g_pSquirrel->m_bCompilationErrorsFatal = bShouldThrowError; return sq_compiler_create(sqvm, a2, a3, bShouldThrowError); } template SQInteger (*SQPrint)(void* sqvm, const char* fmt); template SQInteger SQPrintHook(void* sqvm, const 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); } va_end(va); return 0; } template void* (*CreateNewVM)(void* a1, ScriptContext contextArg); template void* CreateNewVMHook(void* a1, ScriptContext realContext) { void* sqvm = CreateNewVM(a1, realContext); if (realContext == ScriptContext::UI) g_pSquirrel->VMCreated(sqvm); else g_pSquirrel->VMCreated(sqvm); spdlog::info("CreateNewVM {} {}", GetContextName(realContext), sqvm); return sqvm; } template void (*DestroyVM)(void* a1, void* sqvm); template void DestroyVMHook(void* a1, void* sqvm) { ScriptContext realContext = context; // ui and client use the same function so we use this for prints if (context == ScriptContext::CLIENT && sqvm == g_pSquirrel->sqvm) { realContext = ScriptContext::UI; g_pSquirrel->VMDestroyed(); } else DestroyVM(a1, sqvm); spdlog::info("DestroyVM {} {}", GetContextName(realContext), sqvm); } template void (*SQCompileError)(void* sqvm, const char* error, const char* file, int line, int column); template void ScriptCompileErrorHook(void* sqvm, const char* error, const char* file, int line, int column) { bool bIsFatalError = g_pSquirrel->m_bCompilationErrorsFatal; ScriptContext realContext = context; // ui and client use the same function so we use this for prints if (context == ScriptContext::CLIENT && sqvm == g_pSquirrel->sqvm2) { realContext = ScriptContext::UI; bIsFatalError = g_pSquirrel->m_bCompilationErrorsFatal; } spdlog::error("{} SCRIPT COMPILE ERROR {}", GetContextName(realContext), error); spdlog::error("{} line [{}] column [{}]", file, line, column); // use disconnect to display an error message for the compile error, but only if the compilation error was fatal // todo, we could get this from sqvm itself probably, rather than hooking sq_compiler_create if (bIsFatalError) { // kill dedicated server if we hit this if (IsDedicatedServer()) abort(); else { R2::Cbuf_AddText( R2::Cbuf_GetCurrentPlayer(), fmt::format("disconnect \"Encountered {} script compilation error, see console for details.\"", GetContextName(realContext)) .c_str(), R2::cmd_source_t::kCommandSrcCode); // likely temp: show console so user can see any errors, as error message wont display if ui is dead // maybe we could disable all mods other than the coremods and try a reload before doing this? // could also maybe do some vgui bullshit to show something visually rather than console if (realContext == ScriptContext::UI) R2::Cbuf_AddText(R2::Cbuf_GetCurrentPlayer(), "showconsole", R2::cmd_source_t::kCommandSrcCode); } } // dont call the original function since it kills game lol } template bool (*CallScriptInitCallback)(void* sqvm, const char* callback); template bool CallScriptInitCallbackHook(void* sqvm, const char* callback) { ScriptContext realContext = context; bool bShouldCallCustomCallbacks = true; if (context == ScriptContext::CLIENT) { if (!strcmp(callback, "UICodeCallback_UIInit")) realContext = ScriptContext::UI; else if (strcmp(callback, "ClientCodeCallback_MapSpawn")) bShouldCallCustomCallbacks = false; } else if (context == ScriptContext::SERVER) bShouldCallCustomCallbacks = !strcmp(callback, "CodeCallback_MapSpawn"); if (bShouldCallCustomCallbacks) { for (Mod mod : g_pModManager->m_LoadedMods) { if (!mod.m_bEnabled) 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); CallScriptInitCallback(sqvm, modCallback.BeforeCallback.c_str()); } } } } } spdlog::info("{} CodeCallback {} called", GetContextName(realContext), callback); if (!bShouldCallCustomCallbacks) spdlog::info("Not executing custom callbacks for CodeCallback {}", callback); bool ret = CallScriptInitCallback(sqvm, callback); // run after callbacks if (bShouldCallCustomCallbacks) { for (Mod mod : g_pModManager->m_LoadedMods) { if (!mod.m_bEnabled) 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); CallScriptInitCallback(sqvm, modCallback.AfterCallback.c_str()); } } } } } return ret; } template void ConCommand_script(const CCommand& args) { g_pSquirrel->ExecuteCode(args.ArgS()); } ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrel, ConCommand, (CModule module)) { AUTOHOOK_DISPATCH_MODULE(client.dll) g_pSquirrel = new SquirrelManager; g_pSquirrel = new SquirrelManager; g_pSquirrel->RegisterSquirrelFunc = module.Offset(0x108E0).As(); g_pSquirrel->RegisterSquirrelFunc = g_pSquirrel->RegisterSquirrelFunc; g_pSquirrel->__sq_compilebuffer = module.Offset(0x3110).As(); g_pSquirrel->__sq_pushroottable = module.Offset(0x5860).As(); g_pSquirrel->__sq_compilebuffer = g_pSquirrel->__sq_compilebuffer; g_pSquirrel->__sq_pushroottable = g_pSquirrel->__sq_pushroottable; g_pSquirrel->__sq_call = module.Offset(0x8650).As(); g_pSquirrel->__sq_call = g_pSquirrel->__sq_call; g_pSquirrel->__sq_newarray = module.Offset(0x39F0).As(); g_pSquirrel->__sq_arrayappend = module.Offset(0x3C70).As(); g_pSquirrel->__sq_newarray = g_pSquirrel->__sq_newarray; g_pSquirrel->__sq_arrayappend = g_pSquirrel->__sq_arrayappend; g_pSquirrel->__sq_pushstring = module.Offset(0x3440).As(); g_pSquirrel->__sq_pushinteger = module.Offset(0x36A0).As(); g_pSquirrel->__sq_pushfloat = module.Offset(0x3800).As(); g_pSquirrel->__sq_pushbool = module.Offset(0x3710).As(); g_pSquirrel->__sq_raiseerror = module.Offset(0x8470).As(); g_pSquirrel->__sq_pushstring = g_pSquirrel->__sq_pushstring; g_pSquirrel->__sq_pushinteger = g_pSquirrel->__sq_pushinteger; g_pSquirrel->__sq_pushfloat = g_pSquirrel->__sq_pushfloat; g_pSquirrel->__sq_pushbool = g_pSquirrel->__sq_pushbool; g_pSquirrel->__sq_raiseerror = g_pSquirrel->__sq_raiseerror; g_pSquirrel->__sq_getstring = module.Offset(0x60C0).As(); g_pSquirrel->__sq_getinteger = module.Offset(0x60E0).As(); g_pSquirrel->__sq_getfloat = module.Offset(0x6100).As(); g_pSquirrel->__sq_getbool = module.Offset(0x6130).As(); g_pSquirrel->__sq_get = module.Offset(0x7C30).As(); g_pSquirrel->__sq_getstring = g_pSquirrel->__sq_getstring; g_pSquirrel->__sq_getinteger = g_pSquirrel->__sq_getinteger; g_pSquirrel->__sq_getfloat = g_pSquirrel->__sq_getfloat; g_pSquirrel->__sq_getbool = g_pSquirrel->__sq_getbool; g_pSquirrel->__sq_get = g_pSquirrel->__sq_get; // uiscript_reset concommand: don't loop forever if compilation fails module.Offset(0x3C6E4C).NOP(6); MAKEHOOK(module.Offset(0x8AD0), &sq_compiler_createHook, &sq_compiler_create); MAKEHOOK(module.Offset(0x12B00), &SQPrintHook, &SQPrint); MAKEHOOK(module.Offset(0x12BA0), &SQPrintHook, &SQPrint); MAKEHOOK(module.Offset(0x26130), &CreateNewVMHook, &CreateNewVM); MAKEHOOK(module.Offset(0x26E70), &DestroyVMHook, &DestroyVM); MAKEHOOK( module.Offset(0x79A50), &ScriptCompileErrorHook, &SQCompileError); MAKEHOOK( module.Offset(0x10190), &CallScriptInitCallbackHook, &CallScriptInitCallback); RegisterConCommand("script_client", ConCommand_script, "Executes script code on the client vm", FCVAR_CLIENTDLL); RegisterConCommand("script_ui", ConCommand_script, "Executes script code on the ui vm", FCVAR_CLIENTDLL); } ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module)) { AUTOHOOK_DISPATCH_MODULE(server.dll) g_pSquirrel = new SquirrelManager; g_pSquirrel->RegisterSquirrelFunc = module.Offset(0x1DD10).As(); g_pSquirrel->__sq_compilebuffer = module.Offset(0x3110).As(); g_pSquirrel->__sq_pushroottable = module.Offset(0x5840).As(); g_pSquirrel->__sq_call = module.Offset(0x8620).As(); g_pSquirrel->__sq_newarray = module.Offset(0x39F0).As(); g_pSquirrel->__sq_arrayappend = module.Offset(0x3C70).As(); g_pSquirrel->__sq_pushstring = module.Offset(0x3440).As(); g_pSquirrel->__sq_pushinteger = module.Offset(0x36A0).As(); g_pSquirrel->__sq_pushfloat = module.Offset(0x3800).As(); g_pSquirrel->__sq_pushbool = module.Offset(0x3710).As(); g_pSquirrel->__sq_raiseerror = module.Offset(0x8440).As(); g_pSquirrel->__sq_getstring = module.Offset(0x60A0).As(); g_pSquirrel->__sq_getinteger = module.Offset(0x60C0).As(); g_pSquirrel->__sq_getfloat = module.Offset(0x60E0).As(); g_pSquirrel->__sq_getbool = module.Offset(0x6110).As(); g_pSquirrel->__sq_get = module.Offset(0x7C00).As(); MAKEHOOK(module.Offset(0x8AA0), &sq_compiler_createHook, &sq_compiler_create); MAKEHOOK(module.Offset(0x1FE90), &SQPrintHook, &SQPrint); MAKEHOOK(module.Offset(0x260E0), &CreateNewVMHook, &CreateNewVM); MAKEHOOK(module.Offset(0x26E20), &DestroyVMHook, &DestroyVM); MAKEHOOK( module.Offset(0x799E0), &ScriptCompileErrorHook, &SQCompileError); MAKEHOOK( module.Offset(0x1D5C0), &CallScriptInitCallbackHook, &CallScriptInitCallback); // FCVAR_CHEAT and FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS 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", ConCommand_script, "Executes script code on the server vm", FCVAR_GAMEDLL | FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS | FCVAR_CHEAT); }