diff options
Diffstat (limited to 'primedev/squirrel/squirrel.cpp')
-rw-r--r-- | primedev/squirrel/squirrel.cpp | 943 |
1 files changed, 943 insertions, 0 deletions
diff --git a/primedev/squirrel/squirrel.cpp b/primedev/squirrel/squirrel.cpp new file mode 100644 index 00000000..ac9a2ce9 --- /dev/null +++ b/primedev/squirrel/squirrel.cpp @@ -0,0 +1,943 @@ +#include "squirrel.h" +#include "mods/modsavefiles.h" +#include "logging/logging.h" +#include "core/convar/concommand.h" +#include "mods/modmanager.h" +#include "dedicated/dedicated.h" +#include "engine/r2engine.h" +#include "core/tier0.h" +#include "plugins/plugin_abi.h" +#include "plugins/plugins.h" +#include "ns_version.h" +#include "core/vanilla.h" + +#include <any> + +AUTOHOOK_INIT() + +std::shared_ptr<ColoredLogger> getSquirrelLoggerByContext(ScriptContext context) +{ + switch (context) + { + case ScriptContext::UI: + return NS::log::SCRIPT_UI; + case ScriptContext::CLIENT: + return NS::log::SCRIPT_CL; + case ScriptContext::SERVER: + return NS::log::SCRIPT_SV; + default: + throw std::runtime_error("getSquirrelLoggerByContext called with invalid context"); + return nullptr; + } +} + +namespace NS::log +{ + template <ScriptContext context> std::shared_ptr<spdlog::logger> squirrel_logger() + { + // Switch statements can't be constexpr afaik + // clang-format off + if constexpr (context == ScriptContext::UI) { return SCRIPT_UI; } + if constexpr (context == ScriptContext::CLIENT) { return SCRIPT_CL; } + if constexpr (context == ScriptContext::SERVER) { return SCRIPT_SV; } + // clang-format on + } +}; // namespace NS::log + +const char* GetContextName(ScriptContext context) +{ + switch (context) + { + case ScriptContext::CLIENT: + return "CLIENT"; + case ScriptContext::SERVER: + return "SERVER"; + case ScriptContext::UI: + return "UI"; + default: + return "UNKNOWN"; + } +} + +const char* GetContextName_Short(ScriptContext context) +{ + switch (context) + { + case ScriptContext::CLIENT: + return "CL"; + case ScriptContext::SERVER: + return "SV"; + case ScriptContext::UI: + return "UI"; + default: + return "??"; + } +} + +eSQReturnType SQReturnTypeFromString(const char* pReturnType) +{ + static const std::map<std::string, eSQReturnType> sqReturnTypeNameToString = { + {"bool", eSQReturnType::Boolean}, + {"float", eSQReturnType::Float}, + {"vector", eSQReturnType::Vector}, + {"int", eSQReturnType::Integer}, + {"entity", eSQReturnType::Entity}, + {"string", eSQReturnType::String}, + {"array", eSQReturnType::Arrays}, + {"asset", eSQReturnType::Asset}, + {"table", eSQReturnType::Table}}; + + if (sqReturnTypeNameToString.find(pReturnType) != sqReturnTypeNameToString.end()) + return sqReturnTypeNameToString.at(pReturnType); + else + return eSQReturnType::Default; // previous default value +} + +ScriptContext ScriptContextFromString(std::string string) +{ + if (string == "UI") + return ScriptContext::UI; + if (string == "CLIENT") + return ScriptContext::CLIENT; + if (string == "SERVER") + return ScriptContext::SERVER; + else + return ScriptContext::INVALID; +} + +const char* SQTypeNameFromID(int type) +{ + switch (type) + { + case OT_ASSET: + return "asset"; + case OT_INTEGER: + return "int"; + case OT_BOOL: + return "bool"; + case SQOBJECT_NUMERIC: + return "float or int"; + case OT_NULL: + return "null"; + case OT_VECTOR: + return "vector"; + case 0: + return "var"; + case OT_USERDATA: + return "userdata"; + case OT_FLOAT: + return "float"; + case OT_STRING: + return "string"; + case OT_ARRAY: + return "array"; + case 0x8000200: + return "function"; + case 0x8100000: + return "structdef"; + case OT_THREAD: + return "thread"; + case OT_FUNCPROTO: + return "function"; + case OT_CLAAS: + return "class"; + case OT_WEAKREF: + return "weakref"; + case 0x8080000: + return "unimplemented function"; + case 0x8200000: + return "struct instance"; + case OT_TABLE: + return "table"; + case 0xA008000: + return "instance"; + case OT_ENTITY: + return "entity"; + } + return ""; +} + +template <ScriptContext context> void SquirrelManager<context>::GenerateSquirrelFunctionsStruct(SquirrelFunctions* s) +{ + s->RegisterSquirrelFunc = RegisterSquirrelFunc; + s->__sq_defconst = __sq_defconst; + + s->__sq_compilebuffer = __sq_compilebuffer; + s->__sq_call = __sq_call; + s->__sq_raiseerror = __sq_raiseerror; + s->__sq_compilefile = __sq_compilefile; + + s->__sq_newarray = __sq_newarray; + s->__sq_arrayappend = __sq_arrayappend; + + s->__sq_newtable = __sq_newtable; + s->__sq_newslot = __sq_newslot; + + s->__sq_pushroottable = __sq_pushroottable; + s->__sq_pushstring = __sq_pushstring; + s->__sq_pushinteger = __sq_pushinteger; + s->__sq_pushfloat = __sq_pushfloat; + s->__sq_pushbool = __sq_pushbool; + s->__sq_pushasset = __sq_pushasset; + s->__sq_pushvector = __sq_pushvector; + s->__sq_pushobject = __sq_pushobject; + + s->__sq_getstring = __sq_getstring; + s->__sq_getinteger = __sq_getinteger; + s->__sq_getfloat = __sq_getfloat; + s->__sq_getbool = __sq_getbool; + s->__sq_get = __sq_get; + s->__sq_getasset = __sq_getasset; + s->__sq_getuserdata = __sq_getuserdata; + s->__sq_getvector = __sq_getvector; + s->__sq_getthisentity = __sq_getthisentity; + s->__sq_getobject = __sq_getobject; + + s->__sq_stackinfos = __sq_stackinfos; + + s->__sq_createuserdata = __sq_createuserdata; + s->__sq_setuserdatatypeid = __sq_setuserdatatypeid; + s->__sq_getfunction = __sq_getfunction; + + s->__sq_schedule_call_external = AsyncCall_External; + + s->__sq_getentityfrominstance = __sq_getentityfrominstance; + s->__sq_GetEntityConstant_CBaseEntity = __sq_GetEntityConstant_CBaseEntity; + + s->__sq_pushnewstructinstance = __sq_pushnewstructinstance; + s->__sq_sealstructslot = __sq_sealstructslot; +} + +// Allows for generating squirrelmessages from plugins. +void AsyncCall_External(ScriptContext context, const char* func_name, SquirrelMessage_External_Pop function, void* userdata) +{ + SquirrelMessage message {}; + message.functionName = func_name; + message.isExternal = true; + message.externalFunc = function; + message.userdata = userdata; + switch (context) + { + case ScriptContext::CLIENT: + g_pSquirrel<ScriptContext::CLIENT>->messageBuffer->push(message); + break; + case ScriptContext::SERVER: + g_pSquirrel<ScriptContext::SERVER>->messageBuffer->push(message); + break; + case ScriptContext::UI: + g_pSquirrel<ScriptContext::UI>->messageBuffer->push(message); + break; + } +} + +// needed to define implementations for squirrelmanager outside of squirrel.h without compiler errors +template class SquirrelManager<ScriptContext::SERVER>; +template class SquirrelManager<ScriptContext::CLIENT>; +template class SquirrelManager<ScriptContext::UI>; + +template <ScriptContext context> void SquirrelManager<context>::VMCreated(CSquirrelVM* newSqvm) +{ + m_pSQVM = newSqvm; + + for (SQFuncRegistration* funcReg : m_funcRegistrations) + { + spdlog::info("Registering {} function {}", GetContextName(context), funcReg->squirrelFuncName); + RegisterSquirrelFunc(m_pSQVM, funcReg, 1); + } + + for (auto& pair : g_pModManager->m_DependencyConstants) + { + bool bWasFound = false; + + for (Mod& dependency : g_pModManager->m_LoadedMods) + { + if (!dependency.m_bEnabled) + continue; + + if (dependency.Name == pair.second) + { + bWasFound = true; + break; + } + } + + defconst(m_pSQVM, pair.first.c_str(), bWasFound); + } + + auto loadedPlugins = &g_pPluginManager->m_vLoadedPlugins; + for (const auto& pluginName : g_pModManager->m_PluginDependencyConstants) + { + auto f = [&](Plugin plugin) -> bool { return plugin.dependencyName == pluginName; }; + defconst(m_pSQVM, pluginName.c_str(), std::find_if(loadedPlugins->begin(), loadedPlugins->end(), f) != loadedPlugins->end()); + } + + defconst(m_pSQVM, "MAX_FOLDER_SIZE", GetMaxSaveFolderSize() / 1024); + + // define squirrel constants for northstar(.dll) version + constexpr int version[4] {NORTHSTAR_VERSION}; + defconst(m_pSQVM, "NS_VERSION_MAJOR", version[0]); + defconst(m_pSQVM, "NS_VERSION_MINOR", version[1]); + defconst(m_pSQVM, "NS_VERSION_PATCH", version[2]); + defconst(m_pSQVM, "NS_VERSION_DEV", version[3]); + + // define squirrel constant for if we are in vanilla-compatibility mode + defconst(m_pSQVM, "VANILLA", g_pVanillaCompatibility->GetVanillaCompatibility()); + + g_pSquirrel<context>->messageBuffer = new SquirrelMessageBuffer(); + g_pPluginManager->InformSQVMCreated(context, newSqvm); +} + +template <ScriptContext context> void SquirrelManager<context>::VMDestroyed() +{ + // Call all registered mod Destroy callbacks. + if (g_pModManager) + { + NS::log::squirrel_logger<context>()->info("Calling Destroy callbacks for all loaded mods."); + + for (const Mod& loadedMod : g_pModManager->m_LoadedMods) + { + for (const ModScript& script : loadedMod.Scripts) + { + for (const ModScriptCallback& callback : script.Callbacks) + { + if (callback.Context != context || callback.DestroyCallback.length() == 0) + { + continue; + } + + Call(callback.DestroyCallback.c_str()); + NS::log::squirrel_logger<context>()->info("Executed Destroy callback {}.", callback.DestroyCallback); + } + } + } + } + + g_pPluginManager->InformSQVMDestroyed(context); + + // Discard the previous vm and delete the message buffer. + m_pSQVM = nullptr; + + delete g_pSquirrel<context>->messageBuffer; + g_pSquirrel<context>->messageBuffer = nullptr; +} + +template <ScriptContext context> void SquirrelManager<context>::ExecuteCode(const char* pCode) +{ + if (!m_pSQVM || !m_pSQVM->sqvm) + { + spdlog::error("Cannot execute code, {} squirrel vm is not initialised", GetContextName(context)); + return; + } + + spdlog::info("Executing {} script code {} ", GetContextName(context), pCode); + + std::string strCode(pCode); + CompileBufferState bufferState = CompileBufferState(strCode); + + SQRESULT compileResult = compilebuffer(&bufferState, "console"); + spdlog::info("sq_compilebuffer returned {}", PrintSQRESULT.at(compileResult)); + + if (compileResult != SQRESULT_ERROR) + { + pushroottable(m_pSQVM->sqvm); + SQRESULT callResult = _call(m_pSQVM->sqvm, 0); + spdlog::info("sq_call returned {}", PrintSQRESULT.at(callResult)); + } +} + +template <ScriptContext context> void SquirrelManager<context>::AddFuncRegistration( + std::string returnType, std::string name, std::string argTypes, std::string helpText, SQFunction func) +{ + SQFuncRegistration* reg = new SQFuncRegistration; + + reg->squirrelFuncName = new char[name.size() + 1]; + strcpy((char*)reg->squirrelFuncName, name.c_str()); + reg->cppFuncName = reg->squirrelFuncName; + + reg->helpText = new char[helpText.size() + 1]; + strcpy((char*)reg->helpText, helpText.c_str()); + + reg->returnTypeString = new char[returnType.size() + 1]; + strcpy((char*)reg->returnTypeString, returnType.c_str()); + reg->returnType = SQReturnTypeFromString(returnType.c_str()); + + reg->argTypes = new char[argTypes.size() + 1]; + strcpy((char*)reg->argTypes, argTypes.c_str()); + + reg->funcPtr = func; + + m_funcRegistrations.push_back(reg); +} + +template <ScriptContext context> SQRESULT SquirrelManager<context>::setupfunc(const SQChar* funcname) +{ + pushroottable(m_pSQVM->sqvm); + pushstring(m_pSQVM->sqvm, funcname, -1); + + SQRESULT result = get(m_pSQVM->sqvm, -2); + if (result != SQRESULT_ERROR) + pushroottable(m_pSQVM->sqvm); + + return result; +} + +template <ScriptContext context> void SquirrelManager<context>::AddFuncOverride(std::string name, SQFunction func) +{ + m_funcOverrides[name] = func; +} + +// hooks +bool IsUIVM(ScriptContext context, HSquirrelVM* pSqvm) +{ + return ScriptContext(pSqvm->sharedState->cSquirrelVM->vmContext) == ScriptContext::UI; +} + +template <ScriptContext context> void* (*sq_compiler_create)(HSquirrelVM* sqvm, void* a2, void* a3, SQBool bShouldThrowError); +template <ScriptContext context> void* __fastcall sq_compiler_createHook(HSquirrelVM* sqvm, void* a2, void* a3, SQBool bShouldThrowError) +{ + // store whether errors generated from this compile should be fatal + if (IsUIVM(context, sqvm)) + g_pSquirrel<ScriptContext::UI>->m_bFatalCompilationErrors = bShouldThrowError; + else + g_pSquirrel<context>->m_bFatalCompilationErrors = bShouldThrowError; + + return sq_compiler_create<context>(sqvm, a2, a3, bShouldThrowError); +} + +template <ScriptContext context> SQInteger (*SQPrint)(HSquirrelVM* sqvm, const char* fmt); +template <ScriptContext context> SQInteger SQPrintHook(HSquirrelVM* 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'; + g_pSquirrel<context>->logger->info("{}", buf); + } + + va_end(va); + return 0; +} + +template <ScriptContext context> CSquirrelVM* (*CreateNewVM)(void* a1, ScriptContext realContext); +template <ScriptContext context> CSquirrelVM* __fastcall CreateNewVMHook(void* a1, ScriptContext realContext) +{ + CSquirrelVM* sqvm = CreateNewVM<context>(a1, realContext); + if (realContext == ScriptContext::UI) + g_pSquirrel<ScriptContext::UI>->VMCreated(sqvm); + else + g_pSquirrel<context>->VMCreated(sqvm); + + spdlog::info("CreateNewVM {} {}", GetContextName(realContext), (void*)sqvm); + return sqvm; +} + +template <ScriptContext context> bool (*CSquirrelVM_init)(CSquirrelVM* vm, ScriptContext realContext, float time); +template <ScriptContext context> bool __fastcall CSquirrelVM_initHook(CSquirrelVM* vm, ScriptContext realContext, float time) +{ + bool ret = CSquirrelVM_init<context>(vm, realContext, time); + for (Mod mod : g_pModManager->m_LoadedMods) + { + if (mod.m_bEnabled && mod.initScript.size() != 0) + { + std::string name = mod.initScript.substr(mod.initScript.find_last_of('/') + 1); + std::string path = std::string("scripts/vscripts/") + mod.initScript; + if (g_pSquirrel<context>->compilefile(vm, path.c_str(), name.c_str(), 0)) + g_pSquirrel<context>->compilefile(vm, path.c_str(), name.c_str(), 1); + } + } + return ret; +} + +template <ScriptContext context> void (*DestroyVM)(void* a1, CSquirrelVM* sqvm); +template <ScriptContext context> void __fastcall DestroyVMHook(void* a1, CSquirrelVM* sqvm) +{ + ScriptContext realContext = context; // ui and client use the same function so we use this for prints + if (IsUIVM(context, sqvm->sqvm)) + { + realContext = ScriptContext::UI; + g_pSquirrel<ScriptContext::UI>->VMDestroyed(); + DestroyVM<ScriptContext::CLIENT>(a1, sqvm); // If we pass UI here it crashes + } + else + { + g_pSquirrel<context>->VMDestroyed(); + DestroyVM<context>(a1, sqvm); + } + + spdlog::info("DestroyVM {} {}", GetContextName(realContext), (void*)sqvm); +} + +template <ScriptContext context> void (*SQCompileError)(HSquirrelVM* sqvm, const char* error, const char* file, int line, int column); +template <ScriptContext context> +void __fastcall ScriptCompileErrorHook(HSquirrelVM* sqvm, const char* error, const char* file, int line, int column) +{ + bool bIsFatalError = g_pSquirrel<context>->m_bFatalCompilationErrors; + ScriptContext realContext = context; // ui and client use the same function so we use this for prints + if (IsUIVM(context, sqvm)) + { + realContext = ScriptContext::UI; + bIsFatalError = g_pSquirrel<ScriptContext::UI>->m_bFatalCompilationErrors; + } + + auto logger = getSquirrelLoggerByContext(realContext); + + logger->error("COMPILE ERROR {}", error); + logger->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()) + { + logger->error("Exiting dedicated server, compile error is fatal"); + // flush the logger before we exit so debug things get saved to log file + logger->flush(); + exit(EXIT_FAILURE); + } + else + { + Cbuf_AddText( + Cbuf_GetCurrentPlayer(), + fmt::format("disconnect \"Encountered {} script compilation error, see console for details.\"", GetContextName(realContext)) + .c_str(), + 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) + Cbuf_AddText(Cbuf_GetCurrentPlayer(), "showconsole", cmd_source_t::kCommandSrcCode); + } + } + + // dont call the original function since it kills game lol +} + +template <ScriptContext context> int64_t (*RegisterSquirrelFunction)(CSquirrelVM* sqvm, SQFuncRegistration* funcReg, char unknown); +template <ScriptContext context> +int64_t __fastcall RegisterSquirrelFunctionHook(CSquirrelVM* sqvm, SQFuncRegistration* funcReg, char unknown) +{ + if (IsUIVM(context, sqvm->sqvm)) + { + if (g_pSquirrel<ScriptContext::UI>->m_funcOverrides.count(funcReg->squirrelFuncName)) + { + g_pSquirrel<ScriptContext::UI>->m_funcOriginals[funcReg->squirrelFuncName] = funcReg->funcPtr; + funcReg->funcPtr = g_pSquirrel<ScriptContext::UI>->m_funcOverrides[funcReg->squirrelFuncName]; + spdlog::info("Replacing {} in UI", std::string(funcReg->squirrelFuncName)); + } + + return g_pSquirrel<ScriptContext::UI>->RegisterSquirrelFunc(sqvm, funcReg, unknown); + } + + if (g_pSquirrel<context>->m_funcOverrides.find(funcReg->squirrelFuncName) != g_pSquirrel<context>->m_funcOverrides.end()) + { + g_pSquirrel<context>->m_funcOriginals[funcReg->squirrelFuncName] = funcReg->funcPtr; + funcReg->funcPtr = g_pSquirrel<context>->m_funcOverrides[funcReg->squirrelFuncName]; + spdlog::info("Replacing {} in Client", std::string(funcReg->squirrelFuncName)); + } + + return g_pSquirrel<context>->RegisterSquirrelFunc(sqvm, funcReg, unknown); +} + +template <ScriptContext context> bool (*CallScriptInitCallback)(void* sqvm, const char* callback); +template <ScriptContext context> bool __fastcall 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<context>(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<context>(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<context>(sqvm, modCallback.AfterCallback.c_str()); + } + } + } + } + } + + return ret; +} + +template <ScriptContext context> void ConCommand_script(const CCommand& args) +{ + g_pSquirrel<context>->ExecuteCode(args.ArgS()); +} + +// literal class type that wraps a constant expression string +template <size_t N> struct TemplateStringLiteral +{ + constexpr TemplateStringLiteral(const char (&str)[N]) + { + std::copy_n(str, N, value); + } + + char value[N]; +}; + +template <ScriptContext context, TemplateStringLiteral funcName> SQRESULT SQ_StubbedFunc(HSquirrelVM* sqvm) +{ + spdlog::info("Blocking call to stubbed function {} in {}", funcName.value, GetContextName(context)); + return SQRESULT_NULL; +} + +template <ScriptContext context> void StubUnsafeSQFuncs() +{ + if (!CommandLine()->CheckParm("-allowunsafesqfuncs")) + { + g_pSquirrel<context>->AddFuncOverride("DevTextBufferWrite", SQ_StubbedFunc<context, "DevTextBufferWrite">); + g_pSquirrel<context>->AddFuncOverride("DevTextBufferClear", SQ_StubbedFunc<context, "DevTextBufferClear">); + g_pSquirrel<context>->AddFuncOverride("DevTextBufferDumpToFile", SQ_StubbedFunc<context, "DevTextBufferDumpToFile">); + g_pSquirrel<context>->AddFuncOverride("Dev_CommandLineAddParam", SQ_StubbedFunc<context, "Dev_CommandLineAddParam">); + g_pSquirrel<context>->AddFuncOverride("DevP4Checkout", SQ_StubbedFunc<context, "DevP4Checkout">); + g_pSquirrel<context>->AddFuncOverride("DevP4Add", SQ_StubbedFunc<context, "DevP4Add">); + } +} + +template <ScriptContext context> void SquirrelManager<context>::ProcessMessageBuffer() +{ + while (std::optional<SquirrelMessage> maybeMessage = messageBuffer->pop()) + { + SquirrelMessage message = maybeMessage.value(); + + SQObject functionobj {}; + int result = sq_getfunction(m_pSQVM->sqvm, message.functionName.c_str(), &functionobj, 0); + if (result != 0) // This func returns 0 on success for some reason + { + NS::log::squirrel_logger<context>()->error( + "ProcessMessageBuffer was unable to find function with name '{}'. Is it global?", message.functionName); + continue; + } + + pushobject(m_pSQVM->sqvm, &functionobj); // Push the function object + pushroottable(m_pSQVM->sqvm); + + int argsAmount = message.args.size(); + + if (message.isExternal && message.externalFunc != NULL) + { + argsAmount = message.externalFunc(m_pSQVM->sqvm, message.userdata); + } + else + { + for (auto& v : message.args) + { + // Execute lambda to push arg to stack + v(); + } + } + + _call(m_pSQVM->sqvm, argsAmount); + } +} + +ADD_SQFUNC( + "string", + NSGetCurrentModName, + "", + "Returns the mod name of the script running this function", + ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER) +{ + int depth = g_pSquirrel<context>->getinteger(sqvm, 1); + if (auto mod = g_pSquirrel<context>->getcallingmod(sqvm, depth); mod == nullptr) + { + g_pSquirrel<context>->raiseerror(sqvm, "NSGetModName was called from a non-mod script. This shouldn't be possible"); + return SQRESULT_ERROR; + } + else + { + g_pSquirrel<context>->pushstring(sqvm, mod->Name.c_str()); + } + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC( + "string", + NSGetCallingModName, + "int depth = 0", + "Returns the mod name of the script running this function", + ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER) +{ + int depth = g_pSquirrel<context>->getinteger(sqvm, 1); + if (auto mod = g_pSquirrel<context>->getcallingmod(sqvm, depth); mod == nullptr) + { + g_pSquirrel<context>->pushstring(sqvm, "Unknown"); + } + else + { + g_pSquirrel<context>->pushstring(sqvm, mod->Name.c_str()); + } + return SQRESULT_NOTNULL; +} + +ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrel, ConCommand, (CModule module)) +{ + AUTOHOOK_DISPATCH_MODULE(client.dll) + + g_pSquirrel<ScriptContext::CLIENT>->__sq_defconst = module.Offset(0x12120).RCast<sq_defconstType>(); + g_pSquirrel<ScriptContext::UI>->__sq_defconst = g_pSquirrel<ScriptContext::CLIENT>->__sq_defconst; + + g_pSquirrel<ScriptContext::CLIENT>->__sq_compilebuffer = module.Offset(0x3110).RCast<sq_compilebufferType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushroottable = module.Offset(0x5860).RCast<sq_pushroottableType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_compilefile = module.Offset(0xF950).RCast<sq_compilefileType>(); + g_pSquirrel<ScriptContext::UI>->__sq_compilebuffer = g_pSquirrel<ScriptContext::CLIENT>->__sq_compilebuffer; + g_pSquirrel<ScriptContext::UI>->__sq_pushroottable = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushroottable; + g_pSquirrel<ScriptContext::UI>->__sq_compilefile = g_pSquirrel<ScriptContext::CLIENT>->__sq_compilefile; + + g_pSquirrel<ScriptContext::CLIENT>->__sq_call = module.Offset(0x8650).RCast<sq_callType>(); + g_pSquirrel<ScriptContext::UI>->__sq_call = g_pSquirrel<ScriptContext::CLIENT>->__sq_call; + + g_pSquirrel<ScriptContext::CLIENT>->__sq_newarray = module.Offset(0x39F0).RCast<sq_newarrayType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_arrayappend = module.Offset(0x3C70).RCast<sq_arrayappendType>(); + g_pSquirrel<ScriptContext::UI>->__sq_newarray = g_pSquirrel<ScriptContext::CLIENT>->__sq_newarray; + g_pSquirrel<ScriptContext::UI>->__sq_arrayappend = g_pSquirrel<ScriptContext::CLIENT>->__sq_arrayappend; + + g_pSquirrel<ScriptContext::CLIENT>->__sq_newtable = module.Offset(0x3960).RCast<sq_newtableType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_newslot = module.Offset(0x70B0).RCast<sq_newslotType>(); + g_pSquirrel<ScriptContext::UI>->__sq_newtable = g_pSquirrel<ScriptContext::CLIENT>->__sq_newtable; + g_pSquirrel<ScriptContext::UI>->__sq_newslot = g_pSquirrel<ScriptContext::CLIENT>->__sq_newslot; + + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushstring = module.Offset(0x3440).RCast<sq_pushstringType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushinteger = module.Offset(0x36A0).RCast<sq_pushintegerType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushfloat = module.Offset(0x3800).RCast<sq_pushfloatType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushbool = module.Offset(0x3710).RCast<sq_pushboolType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushasset = module.Offset(0x3560).RCast<sq_pushassetType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushvector = module.Offset(0x3780).RCast<sq_pushvectorType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushobject = module.Offset(0x83D0).RCast<sq_pushobjectType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_raiseerror = module.Offset(0x8470).RCast<sq_raiseerrorType>(); + g_pSquirrel<ScriptContext::UI>->__sq_pushstring = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushstring; + g_pSquirrel<ScriptContext::UI>->__sq_pushinteger = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushinteger; + g_pSquirrel<ScriptContext::UI>->__sq_pushfloat = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushfloat; + g_pSquirrel<ScriptContext::UI>->__sq_pushbool = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushbool; + g_pSquirrel<ScriptContext::UI>->__sq_pushvector = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushvector; + g_pSquirrel<ScriptContext::UI>->__sq_pushasset = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushasset; + g_pSquirrel<ScriptContext::UI>->__sq_pushobject = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushobject; + g_pSquirrel<ScriptContext::UI>->__sq_raiseerror = g_pSquirrel<ScriptContext::CLIENT>->__sq_raiseerror; + + g_pSquirrel<ScriptContext::CLIENT>->__sq_getstring = module.Offset(0x60C0).RCast<sq_getstringType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getinteger = module.Offset(0x60E0).RCast<sq_getintegerType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getfloat = module.Offset(0x6100).RCast<sq_getfloatType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getbool = module.Offset(0x6130).RCast<sq_getboolType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_get = module.Offset(0x7C30).RCast<sq_getType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getasset = module.Offset(0x6010).RCast<sq_getassetType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getuserdata = module.Offset(0x63D0).RCast<sq_getuserdataType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getvector = module.Offset(0x6140).RCast<sq_getvectorType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getthisentity = module.Offset(0x12F80).RCast<sq_getthisentityType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getobject = module.Offset(0x6160).RCast<sq_getobjectType>(); + g_pSquirrel<ScriptContext::UI>->__sq_getstring = g_pSquirrel<ScriptContext::CLIENT>->__sq_getstring; + g_pSquirrel<ScriptContext::UI>->__sq_getinteger = g_pSquirrel<ScriptContext::CLIENT>->__sq_getinteger; + g_pSquirrel<ScriptContext::UI>->__sq_getfloat = g_pSquirrel<ScriptContext::CLIENT>->__sq_getfloat; + g_pSquirrel<ScriptContext::UI>->__sq_getbool = g_pSquirrel<ScriptContext::CLIENT>->__sq_getbool; + g_pSquirrel<ScriptContext::UI>->__sq_get = g_pSquirrel<ScriptContext::CLIENT>->__sq_get; + g_pSquirrel<ScriptContext::UI>->__sq_getasset = g_pSquirrel<ScriptContext::CLIENT>->__sq_getasset; + g_pSquirrel<ScriptContext::UI>->__sq_getuserdata = g_pSquirrel<ScriptContext::CLIENT>->__sq_getuserdata; + g_pSquirrel<ScriptContext::UI>->__sq_getvector = g_pSquirrel<ScriptContext::CLIENT>->__sq_getvector; + g_pSquirrel<ScriptContext::UI>->__sq_getthisentity = g_pSquirrel<ScriptContext::CLIENT>->__sq_getthisentity; + g_pSquirrel<ScriptContext::UI>->__sq_getobject = g_pSquirrel<ScriptContext::CLIENT>->__sq_getobject; + + g_pSquirrel<ScriptContext::CLIENT>->__sq_createuserdata = module.Offset(0x38D0).RCast<sq_createuserdataType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_setuserdatatypeid = module.Offset(0x6490).RCast<sq_setuserdatatypeidType>(); + g_pSquirrel<ScriptContext::UI>->__sq_createuserdata = g_pSquirrel<ScriptContext::CLIENT>->__sq_createuserdata; + g_pSquirrel<ScriptContext::UI>->__sq_setuserdatatypeid = g_pSquirrel<ScriptContext::CLIENT>->__sq_setuserdatatypeid; + + g_pSquirrel<ScriptContext::CLIENT>->__sq_GetEntityConstant_CBaseEntity = module.Offset(0x3E49B0).RCast<sq_GetEntityConstantType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getentityfrominstance = module.Offset(0x114F0).RCast<sq_getentityfrominstanceType>(); + g_pSquirrel<ScriptContext::UI>->__sq_GetEntityConstant_CBaseEntity = + g_pSquirrel<ScriptContext::CLIENT>->__sq_GetEntityConstant_CBaseEntity; + g_pSquirrel<ScriptContext::UI>->__sq_getentityfrominstance = g_pSquirrel<ScriptContext::CLIENT>->__sq_getentityfrominstance; + + // Message buffer stuff + g_pSquirrel<ScriptContext::UI>->messageBuffer = g_pSquirrel<ScriptContext::CLIENT>->messageBuffer; + g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction = module.Offset(0x572FB0).RCast<sq_getfunctionType>(); + g_pSquirrel<ScriptContext::UI>->__sq_getfunction = g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction; + g_pSquirrel<ScriptContext::CLIENT>->__sq_stackinfos = module.Offset(0x35970).RCast<sq_stackinfosType>(); + g_pSquirrel<ScriptContext::UI>->__sq_stackinfos = g_pSquirrel<ScriptContext::CLIENT>->__sq_stackinfos; + + // Structs + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushnewstructinstance = module.Offset(0x5400).RCast<sq_pushnewstructinstanceType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_sealstructslot = module.Offset(0x5530).RCast<sq_sealstructslotType>(); + g_pSquirrel<ScriptContext::UI>->__sq_pushnewstructinstance = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushnewstructinstance; + g_pSquirrel<ScriptContext::UI>->__sq_sealstructslot = g_pSquirrel<ScriptContext::CLIENT>->__sq_sealstructslot; + + MAKEHOOK( + module.Offset(0x108E0), + &RegisterSquirrelFunctionHook<ScriptContext::CLIENT>, + &g_pSquirrel<ScriptContext::CLIENT>->RegisterSquirrelFunc); + g_pSquirrel<ScriptContext::UI>->RegisterSquirrelFunc = g_pSquirrel<ScriptContext::CLIENT>->RegisterSquirrelFunc; + + g_pSquirrel<ScriptContext::CLIENT>->logger = NS::log::SCRIPT_CL; + g_pSquirrel<ScriptContext::UI>->logger = NS::log::SCRIPT_UI; + + // uiscript_reset concommand: don't loop forever if compilation fails + module.Offset(0x3C6E4C).NOP(6); + + MAKEHOOK(module.Offset(0x8AD0), &sq_compiler_createHook<ScriptContext::CLIENT>, &sq_compiler_create<ScriptContext::CLIENT>); + + MAKEHOOK(module.Offset(0x12B00), &SQPrintHook<ScriptContext::CLIENT>, &SQPrint<ScriptContext::CLIENT>); + MAKEHOOK(module.Offset(0x12BA0), &SQPrintHook<ScriptContext::UI>, &SQPrint<ScriptContext::UI>); + + MAKEHOOK(module.Offset(0x26130), &CreateNewVMHook<ScriptContext::CLIENT>, &CreateNewVM<ScriptContext::CLIENT>); + MAKEHOOK(module.Offset(0x26E70), &DestroyVMHook<ScriptContext::CLIENT>, &DestroyVM<ScriptContext::CLIENT>); + MAKEHOOK(module.Offset(0x79A50), &ScriptCompileErrorHook<ScriptContext::CLIENT>, &SQCompileError<ScriptContext::CLIENT>); + + MAKEHOOK(module.Offset(0x10190), &CallScriptInitCallbackHook<ScriptContext::CLIENT>, &CallScriptInitCallback<ScriptContext::CLIENT>); + + MAKEHOOK(module.Offset(0xE3B0), &CSquirrelVM_initHook<ScriptContext::CLIENT>, &CSquirrelVM_init<ScriptContext::CLIENT>); + + RegisterConCommand("script_client", ConCommand_script<ScriptContext::CLIENT>, "Executes script code on the client vm", FCVAR_CLIENTDLL); + RegisterConCommand("script_ui", ConCommand_script<ScriptContext::UI>, "Executes script code on the ui vm", FCVAR_CLIENTDLL); + + StubUnsafeSQFuncs<ScriptContext::CLIENT>(); + StubUnsafeSQFuncs<ScriptContext::UI>(); + + g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction = module.Offset(0x6CB0).RCast<sq_getfunctionType>(); + g_pSquirrel<ScriptContext::UI>->__sq_getfunction = g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction; + + SquirrelFunctions s = {}; + g_pSquirrel<ScriptContext::CLIENT>->GenerateSquirrelFunctionsStruct(&s); + g_pPluginManager->InformSQVMLoad(ScriptContext::CLIENT, &s); +} + +ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module)) +{ + AUTOHOOK_DISPATCH_MODULE(server.dll) + + g_pSquirrel<ScriptContext::SERVER>->__sq_defconst = module.Offset(0x1F550).RCast<sq_defconstType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_compilebuffer = module.Offset(0x3110).RCast<sq_compilebufferType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_pushroottable = module.Offset(0x5840).RCast<sq_pushroottableType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_call = module.Offset(0x8620).RCast<sq_callType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_compilefile = module.Offset(0x1CD80).RCast<sq_compilefileType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_newarray = module.Offset(0x39F0).RCast<sq_newarrayType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_arrayappend = module.Offset(0x3C70).RCast<sq_arrayappendType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_newtable = module.Offset(0x3960).RCast<sq_newtableType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_newslot = module.Offset(0x7080).RCast<sq_newslotType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_pushstring = module.Offset(0x3440).RCast<sq_pushstringType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_pushinteger = module.Offset(0x36A0).RCast<sq_pushintegerType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_pushfloat = module.Offset(0x3800).RCast<sq_pushfloatType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_pushbool = module.Offset(0x3710).RCast<sq_pushboolType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_pushasset = module.Offset(0x3560).RCast<sq_pushassetType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_pushvector = module.Offset(0x3780).RCast<sq_pushvectorType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_pushobject = module.Offset(0x83A0).RCast<sq_pushobjectType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_raiseerror = module.Offset(0x8440).RCast<sq_raiseerrorType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_getstring = module.Offset(0x60A0).RCast<sq_getstringType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getinteger = module.Offset(0x60C0).RCast<sq_getintegerType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getfloat = module.Offset(0x60E0).RCast<sq_getfloatType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getbool = module.Offset(0x6110).RCast<sq_getboolType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getasset = module.Offset(0x5FF0).RCast<sq_getassetType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getuserdata = module.Offset(0x63B0).RCast<sq_getuserdataType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getvector = module.Offset(0x6120).RCast<sq_getvectorType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_get = module.Offset(0x7C00).RCast<sq_getType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_getthisentity = module.Offset(0x203B0).RCast<sq_getthisentityType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getobject = module.Offset(0x6140).RCast<sq_getobjectType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_createuserdata = module.Offset(0x38D0).RCast<sq_createuserdataType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_setuserdatatypeid = module.Offset(0x6470).RCast<sq_setuserdatatypeidType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_GetEntityConstant_CBaseEntity = module.Offset(0x418AF0).RCast<sq_GetEntityConstantType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getentityfrominstance = module.Offset(0x1E920).RCast<sq_getentityfrominstanceType>(); + + g_pSquirrel<ScriptContext::SERVER>->logger = NS::log::SCRIPT_SV; + // Message buffer stuff + g_pSquirrel<ScriptContext::SERVER>->__sq_getfunction = module.Offset(0x6C85).RCast<sq_getfunctionType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_stackinfos = module.Offset(0x35920).RCast<sq_stackinfosType>(); + + // Structs + g_pSquirrel<ScriptContext::SERVER>->__sq_pushnewstructinstance = module.Offset(0x53e0).RCast<sq_pushnewstructinstanceType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_sealstructslot = module.Offset(0x5510).RCast<sq_sealstructslotType>(); + + MAKEHOOK( + module.Offset(0x1DD10), + &RegisterSquirrelFunctionHook<ScriptContext::SERVER>, + &g_pSquirrel<ScriptContext::SERVER>->RegisterSquirrelFunc); + + MAKEHOOK(module.Offset(0x8AA0), &sq_compiler_createHook<ScriptContext::SERVER>, &sq_compiler_create<ScriptContext::SERVER>); + + MAKEHOOK(module.Offset(0x1FE90), &SQPrintHook<ScriptContext::SERVER>, &SQPrint<ScriptContext::SERVER>); + MAKEHOOK(module.Offset(0x260E0), &CreateNewVMHook<ScriptContext::SERVER>, &CreateNewVM<ScriptContext::SERVER>); + MAKEHOOK(module.Offset(0x26E20), &DestroyVMHook<ScriptContext::SERVER>, &DestroyVM<ScriptContext::SERVER>); + MAKEHOOK(module.Offset(0x799E0), &ScriptCompileErrorHook<ScriptContext::SERVER>, &SQCompileError<ScriptContext::SERVER>); + MAKEHOOK(module.Offset(0x1D5C0), &CallScriptInitCallbackHook<ScriptContext::SERVER>, &CallScriptInitCallback<ScriptContext::SERVER>); + MAKEHOOK(module.Offset(0x1B7E0), &CSquirrelVM_initHook<ScriptContext::SERVER>, &CSquirrelVM_init<ScriptContext::SERVER>); + // 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<ScriptContext::SERVER>, + "Executes script code on the server vm", + FCVAR_GAMEDLL | FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS | FCVAR_CHEAT); + + StubUnsafeSQFuncs<ScriptContext::SERVER>(); + + SquirrelFunctions s = {}; + g_pSquirrel<ScriptContext::SERVER>->GenerateSquirrelFunctionsStruct(&s); + g_pPluginManager->InformSQVMLoad(ScriptContext::SERVER, &s); +} + +void InitialiseSquirrelManagers() +{ + g_pSquirrel<ScriptContext::CLIENT> = new SquirrelManager<ScriptContext::CLIENT>; + g_pSquirrel<ScriptContext::UI> = new SquirrelManager<ScriptContext::UI>; + g_pSquirrel<ScriptContext::SERVER> = new SquirrelManager<ScriptContext::SERVER>; +} |