diff options
Diffstat (limited to 'NorthstarDLL/squirrel.cpp')
-rw-r--r-- | NorthstarDLL/squirrel.cpp | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/NorthstarDLL/squirrel.cpp b/NorthstarDLL/squirrel.cpp new file mode 100644 index 00000000..2fa957fb --- /dev/null +++ b/NorthstarDLL/squirrel.cpp @@ -0,0 +1,589 @@ +#include "pch.h" +#include "squirrel.h" +#include "hooks.h" +#include "hookutils.h" +#include "sigscanning.h" +#include "concommand.h" +#include "modmanager.h" +#include <iostream> +#include "gameutils.h" + +// hook forward declarations +typedef SQInteger (*SQPrintType)(void* sqvm, char* fmt, ...); +SQPrintType ClientSQPrint; +SQPrintType UISQPrint; +SQPrintType ServerSQPrint; +template <ScriptContext context> 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 <ScriptContext context> 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 <ScriptContext context> 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 <ScriptContext context> 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 <ScriptContext context> char CallScriptInitCallbackHook(void* sqvm, const char* callback); + +RegisterSquirrelFuncType ClientRegisterSquirrelFunc; +RegisterSquirrelFuncType ServerRegisterSquirrelFunc; +template <ScriptContext context> 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; + +sq_newSlotType ServerSq_newSlot; +sq_newSlotType ClientSq_newSlot; + +sq_newTableType ServerSq_newTable; +sq_newTableType ClientSq_newTable; + +template <ScriptContext context> void ExecuteCodeCommand(const CCommand& args); + +// inits +SquirrelManager<ScriptContext::CLIENT>* g_ClientSquirrelManager; +SquirrelManager<ScriptContext::SERVER>* g_ServerSquirrelManager; +SquirrelManager<ScriptContext::UI>* g_UISquirrelManager; + +SQInteger NSTestFunc(void* sqvm) +{ + return 1; +} + +void InitialiseClientSquirrel(HMODULE baseAddress) +{ + HookEnabler hook; + + // client inits + g_ClientSquirrelManager = new SquirrelManager<ScriptContext::CLIENT>(); + + ENABLER_CREATEHOOK( + hook, + (char*)baseAddress + 0x12B00, + &SQPrintHook<ScriptContext::CLIENT>, + reinterpret_cast<LPVOID*>(&ClientSQPrint)); // client print function + RegisterConCommand( + "script_client", ExecuteCodeCommand<ScriptContext::CLIENT>, "Executes script code on the client vm", FCVAR_CLIENTDLL); + + // ui inits + g_UISquirrelManager = new SquirrelManager<ScriptContext::UI>(); + + ENABLER_CREATEHOOK( + hook, (char*)baseAddress + 0x12BA0, &SQPrintHook<ScriptContext::UI>, reinterpret_cast<LPVOID*>(&UISQPrint)); // ui print function + RegisterConCommand("script_ui", ExecuteCodeCommand<ScriptContext::UI>, "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); + + // Table functions + ClientSq_newTable = (sq_newTableType)((char*)baseAddress + 0x3960); + ClientSq_newSlot = (sq_newSlotType)((char*)baseAddress + 0x70B0); + + ENABLER_CREATEHOOK( + hook, + (char*)baseAddress + 0x26130, + &CreateNewVMHook<ScriptContext::CLIENT>, + reinterpret_cast<LPVOID*>(&ClientCreateNewVM)); // client createnewvm function + ENABLER_CREATEHOOK( + hook, + (char*)baseAddress + 0x26E70, + &DestroyVMHook<ScriptContext::CLIENT>, + reinterpret_cast<LPVOID*>(&ClientDestroyVM)); // client destroyvm function + ENABLER_CREATEHOOK( + hook, + (char*)baseAddress + 0x79A50, + &ScriptCompileErrorHook<ScriptContext::CLIENT>, + reinterpret_cast<LPVOID*>(&ClientSQCompileError)); // client compileerror function + ENABLER_CREATEHOOK( + hook, + (char*)baseAddress + 0x10190, + &CallScriptInitCallbackHook<ScriptContext::CLIENT>, + reinterpret_cast<LPVOID*>(&ClientCallScriptInitCallback)); // client callscriptinitcallback function + ENABLER_CREATEHOOK( + hook, + (char*)baseAddress + 0x108E0, + &RegisterSquirrelFuncHook<ScriptContext::CLIENT>, + reinterpret_cast<LPVOID*>(&ClientRegisterSquirrelFunc)); // client registersquirrelfunc function +} + +void InitialiseServerSquirrel(HMODULE baseAddress) +{ + g_ServerSquirrelManager = new SquirrelManager<ScriptContext::SERVER>(); + + 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); + + ServerSq_newSlot = (sq_newSlotType)((char*)baseAddress + 0x7080); + ServerSq_newTable = (sq_newTableType)((char*)baseAddress + 0x3960); + + ENABLER_CREATEHOOK( + hook, + (char*)baseAddress + 0x1FE90, + &SQPrintHook<ScriptContext::SERVER>, + reinterpret_cast<LPVOID*>(&ServerSQPrint)); // server print function + ENABLER_CREATEHOOK( + hook, + (char*)baseAddress + 0x260E0, + &CreateNewVMHook<ScriptContext::SERVER>, + reinterpret_cast<LPVOID*>(&ServerCreateNewVM)); // server createnewvm function + ENABLER_CREATEHOOK( + hook, + (char*)baseAddress + 0x26E20, + &DestroyVMHook<ScriptContext::SERVER>, + reinterpret_cast<LPVOID*>(&ServerDestroyVM)); // server destroyvm function + ENABLER_CREATEHOOK( + hook, + (char*)baseAddress + 0x799E0, + &ScriptCompileErrorHook<ScriptContext::SERVER>, + reinterpret_cast<LPVOID*>(&ServerSQCompileError)); // server compileerror function + ENABLER_CREATEHOOK( + hook, + (char*)baseAddress + 0x1D5C0, + &CallScriptInitCallbackHook<ScriptContext::SERVER>, + reinterpret_cast<LPVOID*>(&ServerCallScriptInitCallback)); // server callscriptinitcallback function + + ENABLER_CREATEHOOK( + hook, + (char*)baseAddress + 0x1DD10, + &RegisterSquirrelFuncHook<ScriptContext::SERVER>, + reinterpret_cast<LPVOID*>(&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<ScriptContext::SERVER>, + "Executes script code on the server vm", + FCVAR_GAMEDLL | FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_CHEAT); +} + +// hooks +template <ScriptContext context> 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); + } + + va_end(va); + return 0; +} + +template <ScriptContext context> 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 <ScriptContext context> 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) + { + 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 <ScriptContext context> void ScriptCompileErrorHook(void* sqvm, const char* error, const char* file, int line, int column) +{ + 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; + + spdlog::error("{} SCRIPT COMPILE ERROR {}", GetContextName(realContext), error); + spdlog::error("{} line [{}] column [{}]", file, line, column); + + // 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 <ScriptContext context> 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 <ScriptContext context> 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 <ScriptContext context> int64_t RegisterSquirrelFuncHook(void* sqvm, SQFuncRegistration* funcReg, char unknown) +{ + static std::set<std::string> allowedDevFunctions = { + "Dev_CommandLineHasParm", + "Dev_CommandLineParmValue", + "Dev_CommandLineRemoveParm", + }; + + if ((funcReg->devLevel == 1) && (!CommandLine()->CheckParm("-allowSquirrelDevFunctions")) && + (!allowedDevFunctions.count(funcReg->squirrelFuncName))) + funcReg->funcPtr = reinterpret_cast<void*>(SQ_DevFuncStub); + + if (context == ScriptContext::SERVER) + return ServerRegisterSquirrelFunc(sqvm, funcReg, unknown); + else + return ClientRegisterSquirrelFunc(sqvm, funcReg, unknown); +} + +const char* sq_getTypeName(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 0x8000040: + 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 0xA000020: + return "table"; + case 0xA008000: + return "instance"; + case 0xA400000: + return "entity"; + } + return ""; +} + +SQReturnTypeEnum GetReturnTypeEnumFromString(const char* returnTypeString) +{ + + static std::map<std::string, SQReturnTypeEnum> 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 + } +} |