From e04f3b36accccb590a2d51b4829256b9964ac3fd Mon Sep 17 00:00:00 2001 From: Emma Miler Date: Mon, 19 Dec 2022 19:32:16 +0100 Subject: Restructuring (#365) * Remove launcher proxy * Restructuring * More restructuring * Fix include dirs * Fix merge * Remove clang thing * Filters * Oops --- NorthstarDLL/squirrel/squirrel.cpp | 739 +++++++++++++++++++++++++++++ NorthstarDLL/squirrel/squirrel.h | 469 ++++++++++++++++++ NorthstarDLL/squirrel/squirrelautobind.cpp | 21 + NorthstarDLL/squirrel/squirrelautobind.h | 76 +++ NorthstarDLL/squirrel/squirrelclasstypes.h | 239 ++++++++++ NorthstarDLL/squirrel/squirreldatatypes.h | 495 +++++++++++++++++++ 6 files changed, 2039 insertions(+) create mode 100644 NorthstarDLL/squirrel/squirrel.cpp create mode 100644 NorthstarDLL/squirrel/squirrel.h create mode 100644 NorthstarDLL/squirrel/squirrelautobind.cpp create mode 100644 NorthstarDLL/squirrel/squirrelautobind.h create mode 100644 NorthstarDLL/squirrel/squirrelclasstypes.h create mode 100644 NorthstarDLL/squirrel/squirreldatatypes.h (limited to 'NorthstarDLL/squirrel') diff --git a/NorthstarDLL/squirrel/squirrel.cpp b/NorthstarDLL/squirrel/squirrel.cpp new file mode 100644 index 00000000..eb4b6bed --- /dev/null +++ b/NorthstarDLL/squirrel/squirrel.cpp @@ -0,0 +1,739 @@ +#include "pch.h" +#include "squirrel.h" +#include "core/convar/concommand.h" +#include "mods/modmanager.h" +#include "dedicated/dedicated.h" +#include "engine/r2engine.h" +#include "core/tier0.h" + +#include + +AUTOHOOK_INIT() + +std::shared_ptr 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 std::shared_ptr 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 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 +} + +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 ""; +} + +// Allows for generating squirrelmessages from plugins. +// Not used in this version, but will be used later +void AsyncCall_External(ScriptContext context, const char* func_name, SquirrelMessage_External_Pop function) +{ + SquirrelMessage message {}; + message.functionName = func_name; + message.isExternal = true; + message.externalFunc = function; + switch (context) + { + case ScriptContext::CLIENT: + g_pSquirrel->messageBuffer->push(message); + case ScriptContext::SERVER: + g_pSquirrel->messageBuffer->push(message); + case ScriptContext::UI: + g_pSquirrel->messageBuffer->push(message); + } +} + +// needed to define implementations for squirrelmanager outside of squirrel.h without compiler errors +template class SquirrelManager; +template class SquirrelManager; +template class SquirrelManager; + +template void SquirrelManager::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); + } + g_pSquirrel->messageBuffer = new SquirrelMessageBuffer(); +} + +template void SquirrelManager::VMDestroyed() +{ + m_pSQVM = nullptr; +} + +template void SquirrelManager::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 void SquirrelManager::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 SQRESULT SquirrelManager::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 void SquirrelManager::AddFuncOverride(std::string name, SQFunction func) +{ + m_funcOverrides[name] = func; +} + +// hooks +bool IsUIVM(ScriptContext context, HSquirrelVM* pSqvm) +{ + return context != ScriptContext::SERVER && g_pSquirrel->m_pSQVM && + g_pSquirrel->m_pSQVM->sqvm == pSqvm; +} + +template void* (*__fastcall sq_compiler_create)(HSquirrelVM* sqvm, void* a2, void* a3, SQBool bShouldThrowError); +template 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->m_bFatalCompilationErrors = bShouldThrowError; + else + g_pSquirrel->m_bFatalCompilationErrors = bShouldThrowError; + + return sq_compiler_create(sqvm, a2, a3, bShouldThrowError); +} + +template SQInteger (*SQPrint)(HSquirrelVM* sqvm, const char* fmt); +template 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->logger->info("{}", buf); + } + + va_end(va); + return 0; +} + +template CSquirrelVM* (*__fastcall CreateNewVM)(void* a1, ScriptContext realContext); +template CSquirrelVM* __fastcall CreateNewVMHook(void* a1, ScriptContext realContext) +{ + CSquirrelVM* sqvm = CreateNewVM(a1, realContext); + if (realContext == ScriptContext::UI) + g_pSquirrel->VMCreated(sqvm); + else + g_pSquirrel->VMCreated(sqvm); + + spdlog::info("CreateNewVM {} {}", GetContextName(realContext), (void*)sqvm); + return sqvm; +} + +template void (*__fastcall DestroyVM)(void* a1, HSquirrelVM* sqvm); +template void __fastcall DestroyVMHook(void* a1, HSquirrelVM* sqvm) +{ + ScriptContext realContext = context; // ui and client use the same function so we use this for prints + if (IsUIVM(context, sqvm)) + { + realContext = ScriptContext::UI; + g_pSquirrel->VMDestroyed(); + DestroyVM(a1, sqvm); // If we pass UI here it crashes + } + else + { + g_pSquirrel->m_pSQVM = nullptr; // Fixes a race-like bug + DestroyVM(a1, sqvm); + } + + spdlog::info("DestroyVM {} {}", GetContextName(realContext), (void*)sqvm); +} + +template +void (*__fastcall SQCompileError)(HSquirrelVM* sqvm, const char* error, const char* file, int line, int column); +template +void __fastcall ScriptCompileErrorHook(HSquirrelVM* sqvm, const char* error, const char* file, int line, int column) +{ + bool bIsFatalError = g_pSquirrel->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->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()) + { + // flush the logger before we exit so debug things get saved to log file + logger->flush(); + exit(EXIT_FAILURE); + } + 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 +int64_t (*__fastcall RegisterSquirrelFunction)(CSquirrelVM* sqvm, SQFuncRegistration* funcReg, char unknown); +template +int64_t __fastcall RegisterSquirrelFunctionHook(CSquirrelVM* sqvm, SQFuncRegistration* funcReg, char unknown) +{ + if (IsUIVM(context, sqvm->sqvm)) + { + if (g_pSquirrel->m_funcOverrides.count(funcReg->squirrelFuncName)) + { + g_pSquirrel->m_funcOriginals[funcReg->squirrelFuncName] = funcReg->funcPtr; + funcReg->funcPtr = g_pSquirrel->m_funcOverrides[funcReg->squirrelFuncName]; + spdlog::info("Replacing {} in UI", std::string(funcReg->squirrelFuncName)); + } + + return g_pSquirrel->RegisterSquirrelFunc(sqvm, funcReg, unknown); + } + + if (g_pSquirrel->m_funcOverrides.find(funcReg->squirrelFuncName) != g_pSquirrel->m_funcOverrides.end()) + { + g_pSquirrel->m_funcOriginals[funcReg->squirrelFuncName] = funcReg->funcPtr; + funcReg->funcPtr = g_pSquirrel->m_funcOverrides[funcReg->squirrelFuncName]; + spdlog::info("Replacing {} in Client", std::string(funcReg->squirrelFuncName)); + } + + return g_pSquirrel->RegisterSquirrelFunc(sqvm, funcReg, unknown); +} + +template bool (*__fastcall CallScriptInitCallback)(void* sqvm, const char* callback); +template 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(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()); +} + +// literal class type that wraps a constant expression string +template struct TemplateStringLiteral +{ + constexpr TemplateStringLiteral(const char (&str)[N]) + { + std::copy_n(str, N, value); + } + + char value[N]; +}; + +template SQRESULT SQ_StubbedFunc(HSquirrelVM* sqvm) +{ + spdlog::info("Blocking call to stubbed function {} in {}", funcName.value, GetContextName(context)); + return SQRESULT_NULL; +} + +template void StubUnsafeSQFuncs() +{ + if (!Tier0::CommandLine()->CheckParm("-allowunsafesqfuncs")) + { + g_pSquirrel->AddFuncOverride("DevTextBufferWrite", SQ_StubbedFunc); + g_pSquirrel->AddFuncOverride("DevTextBufferClear", SQ_StubbedFunc); + g_pSquirrel->AddFuncOverride("DevTextBufferDumpToFile", SQ_StubbedFunc); + g_pSquirrel->AddFuncOverride("Dev_CommandLineAddParam", SQ_StubbedFunc); + g_pSquirrel->AddFuncOverride("DevP4Checkout", SQ_StubbedFunc); + g_pSquirrel->AddFuncOverride("DevP4Add", SQ_StubbedFunc); + } +} + +template void SquirrelManager::ProcessMessageBuffer() +{ + auto maybeMessage = messageBuffer->pop(); + if (!maybeMessage) + { + return; + } + + 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()->error( + "ProcessMessageBuffer was unable to find function with name '{}'. Is it global?", message.functionName); + return; + } + pushobject(m_pSQVM->sqvm, &functionobj); // Push the function object + pushroottable(m_pSQVM->sqvm); + if (message.isExternal) + { + message.externalFunc(m_pSQVM->sqvm); + } + else + { + for (auto& v : message.args) + { + // Execute lambda to push arg to stack + v(); + } + } + + _call(m_pSQVM->sqvm, message.args.size()); +} + +ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrel, ConCommand, (CModule module)) +{ + AUTOHOOK_DISPATCH_MODULE(client.dll) + + g_pSquirrel->__sq_defconst = module.Offset(0x12120).As(); + g_pSquirrel->__sq_defconst = g_pSquirrel->__sq_defconst; + + 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_newtable = module.Offset(0x3960).As(); + g_pSquirrel->__sq_newslot = module.Offset(0x70B0).As(); + g_pSquirrel->__sq_newtable = g_pSquirrel->__sq_newtable; + g_pSquirrel->__sq_newslot = g_pSquirrel->__sq_newslot; + + 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_pushasset = module.Offset(0x3560).As(); + g_pSquirrel->__sq_pushvector = module.Offset(0x3780).As(); + g_pSquirrel->__sq_pushobject = module.Offset(0x83D0).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_pushvector = g_pSquirrel->__sq_pushvector; + g_pSquirrel->__sq_pushasset = g_pSquirrel->__sq_pushasset; + g_pSquirrel->__sq_pushobject = g_pSquirrel->__sq_pushobject; + 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_getasset = module.Offset(0x6010).As(); + g_pSquirrel->__sq_getuserdata = module.Offset(0x63D0).As(); + g_pSquirrel->__sq_getvector = module.Offset(0x6140).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; + g_pSquirrel->__sq_getasset = g_pSquirrel->__sq_getasset; + g_pSquirrel->__sq_getuserdata = g_pSquirrel->__sq_getuserdata; + g_pSquirrel->__sq_getvector = g_pSquirrel->__sq_getvector; + g_pSquirrel->__sq_getthisentity = g_pSquirrel->__sq_getthisentity; + g_pSquirrel->__sq_getobject = g_pSquirrel->__sq_getobject; + + g_pSquirrel->__sq_createuserdata = module.Offset(0x38D0).As(); + g_pSquirrel->__sq_setuserdatatypeid = module.Offset(0x6490).As(); + g_pSquirrel->__sq_createuserdata = g_pSquirrel->__sq_createuserdata; + g_pSquirrel->__sq_setuserdatatypeid = g_pSquirrel->__sq_setuserdatatypeid; + + g_pSquirrel->__sq_GetEntityConstant_CBaseEntity = module.Offset(0x3E49B0).As(); + g_pSquirrel->__sq_getentityfrominstance = module.Offset(0x114F0).As(); + g_pSquirrel->__sq_GetEntityConstant_CBaseEntity = + g_pSquirrel->__sq_GetEntityConstant_CBaseEntity; + g_pSquirrel->__sq_getentityfrominstance = g_pSquirrel->__sq_getentityfrominstance; + + // Message buffer stuff + g_pSquirrel->messageBuffer = g_pSquirrel->messageBuffer; + g_pSquirrel->__sq_getfunction = module.Offset(0x572FB0).As(); + g_pSquirrel->__sq_getfunction = g_pSquirrel->__sq_getfunction; + + MAKEHOOK( + module.Offset(0x108E0), + &RegisterSquirrelFunctionHook, + &g_pSquirrel->RegisterSquirrelFunc); + g_pSquirrel->RegisterSquirrelFunc = g_pSquirrel->RegisterSquirrelFunc; + + g_pSquirrel->logger = NS::log::SCRIPT_CL; + g_pSquirrel->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, &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); + + StubUnsafeSQFuncs(); + StubUnsafeSQFuncs(); + + g_pSquirrel->__sq_getfunction = module.Offset(0x6CB0).As(); + g_pSquirrel->__sq_getfunction = g_pSquirrel->__sq_getfunction; +} + +ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module)) +{ + AUTOHOOK_DISPATCH_MODULE(server.dll) + + g_pSquirrel->__sq_defconst = module.Offset(0x1F550).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_newtable = module.Offset(0x3960).As(); + g_pSquirrel->__sq_newslot = module.Offset(0x7080).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_pushasset = module.Offset(0x3560).As(); + g_pSquirrel->__sq_pushvector = module.Offset(0x3780).As(); + g_pSquirrel->__sq_pushobject = module.Offset(0x83A0).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_getasset = module.Offset(0x5FF0).As(); + g_pSquirrel->__sq_getuserdata = module.Offset(0x63B0).As(); + g_pSquirrel->__sq_getvector = module.Offset(0x6120).As(); + g_pSquirrel->__sq_get = module.Offset(0x7C00).As(); + + g_pSquirrel->__sq_getthisentity = module.Offset(0x203B0).As(); + g_pSquirrel->__sq_getobject = module.Offset(0x6140).As(); + + g_pSquirrel->__sq_createuserdata = module.Offset(0x38D0).As(); + g_pSquirrel->__sq_setuserdatatypeid = module.Offset(0x6470).As(); + + g_pSquirrel->__sq_GetEntityConstant_CBaseEntity = module.Offset(0x418AF0).As(); + g_pSquirrel->__sq_getentityfrominstance = module.Offset(0x1E920).As(); + + g_pSquirrel->logger = NS::log::SCRIPT_SV; + // Message buffer stuff + g_pSquirrel->__sq_getfunction = module.Offset(0x6C85).As(); + + MAKEHOOK( + module.Offset(0x1DD10), + &RegisterSquirrelFunctionHook, + &g_pSquirrel->RegisterSquirrelFunc); + + 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); + + StubUnsafeSQFuncs(); +} + +void InitialiseSquirrelManagers() +{ + g_pSquirrel = new SquirrelManager; + g_pSquirrel = new SquirrelManager; + g_pSquirrel = new SquirrelManager; +} diff --git a/NorthstarDLL/squirrel/squirrel.h b/NorthstarDLL/squirrel/squirrel.h new file mode 100644 index 00000000..3e3d08c9 --- /dev/null +++ b/NorthstarDLL/squirrel/squirrel.h @@ -0,0 +1,469 @@ +#pragma once + +#include "logging/logging.h" +#include "squirrelclasstypes.h" +#include "squirrelautobind.h" +#include "core/math/vector.h" + +// stolen from ttf2sdk: sqvm types +typedef float SQFloat; +typedef long SQInteger; +typedef unsigned long SQUnsignedInteger; +typedef char SQChar; +typedef SQUnsignedInteger SQBool; + +static constexpr int operator&(ScriptContext first, ScriptContext second) +{ + return first == second; +} + +static constexpr int operator&(int first, ScriptContext second) +{ + return first & (1 << static_cast(second)); +} + +static constexpr int operator|(ScriptContext first, ScriptContext second) +{ + return (1 << static_cast(first)) + (1 << static_cast(second)); +} + +static constexpr int operator|(int first, ScriptContext second) +{ + return first + (1 << static_cast(second)); +} + +const char* GetContextName(ScriptContext context); +const char* GetContextName_Short(ScriptContext context); +eSQReturnType SQReturnTypeFromString(const char* pReturnType); +const char* SQTypeNameFromID(const int iTypeId); + +std::shared_ptr getSquirrelLoggerByContext(ScriptContext context); + +namespace NS::log +{ + template std::shared_ptr squirrel_logger(); +}; // namespace NS::log + +void schedule_call_external(ScriptContext context, const char* func_name, SquirrelMessage_External_Pop function); + +// This base class means that only the templated functions have to be rebuilt for each template instance +// Cuts down on compile time by ~5 seconds +class SquirrelManagerBase +{ + protected: + std::vector m_funcRegistrations; + + public: + CSquirrelVM* m_pSQVM; + std::map m_funcOverrides = {}; + std::map m_funcOriginals = {}; + + bool m_bFatalCompilationErrors = false; + + std::shared_ptr logger; + +#pragma region SQVM funcs + RegisterSquirrelFuncType RegisterSquirrelFunc; + sq_defconstType __sq_defconst; + + sq_compilebufferType __sq_compilebuffer; + sq_callType __sq_call; + sq_raiseerrorType __sq_raiseerror; + + sq_newarrayType __sq_newarray; + sq_arrayappendType __sq_arrayappend; + + sq_newtableType __sq_newtable; + sq_newslotType __sq_newslot; + + sq_pushroottableType __sq_pushroottable; + sq_pushstringType __sq_pushstring; + sq_pushintegerType __sq_pushinteger; + sq_pushfloatType __sq_pushfloat; + sq_pushboolType __sq_pushbool; + sq_pushassetType __sq_pushasset; + sq_pushvectorType __sq_pushvector; + sq_pushobjectType __sq_pushobject; + + sq_getstringType __sq_getstring; + sq_getintegerType __sq_getinteger; + sq_getfloatType __sq_getfloat; + sq_getboolType __sq_getbool; + sq_getType __sq_get; + sq_getassetType __sq_getasset; + sq_getuserdataType __sq_getuserdata; + sq_getvectorType __sq_getvector; + sq_getthisentityType __sq_getthisentity; + sq_getobjectType __sq_getobject; + + sq_createuserdataType __sq_createuserdata; + sq_setuserdatatypeidType __sq_setuserdatatypeid; + sq_getfunctionType __sq_getfunction; + + sq_getentityfrominstanceType __sq_getentityfrominstance; + sq_GetEntityConstantType __sq_GetEntityConstant_CBaseEntity; + +#pragma endregion + +#pragma region SQVM func wrappers + inline void defconst(CSquirrelVM* sqvm, const SQChar* pName, int nValue) + { + __sq_defconst(sqvm, pName, nValue); + } + + inline SQRESULT + compilebuffer(CompileBufferState* bufferState, const SQChar* bufferName = "unnamedbuffer", const SQBool bShouldThrowError = false) + { + return __sq_compilebuffer(m_pSQVM->sqvm, bufferState, bufferName, -1, bShouldThrowError); + } + + inline SQRESULT _call(HSquirrelVM* sqvm, const SQInteger args) + { + return __sq_call(sqvm, args + 1, false, false); + } + + inline SQInteger raiseerror(HSquirrelVM* sqvm, const SQChar* sError) + { + return __sq_raiseerror(sqvm, sError); + } + + inline void newarray(HSquirrelVM* sqvm, const SQInteger stackpos = 0) + { + __sq_newarray(sqvm, stackpos); + } + + inline SQRESULT arrayappend(HSquirrelVM* sqvm, const SQInteger stackpos) + { + return __sq_arrayappend(sqvm, stackpos); + } + + inline SQRESULT newtable(HSquirrelVM* sqvm) + { + return __sq_newtable(sqvm); + } + + inline SQRESULT newslot(HSquirrelVM* sqvm, SQInteger idx, SQBool bStatic) + { + return __sq_newslot(sqvm, idx, bStatic); + } + + inline void pushroottable(HSquirrelVM* sqvm) + { + __sq_pushroottable(sqvm); + } + + inline void pushstring(HSquirrelVM* sqvm, const SQChar* sVal, int length = -1) + { + __sq_pushstring(sqvm, sVal, length); + } + + inline void pushinteger(HSquirrelVM* sqvm, const SQInteger iVal) + { + __sq_pushinteger(sqvm, iVal); + } + + inline void pushfloat(HSquirrelVM* sqvm, const SQFloat flVal) + { + __sq_pushfloat(sqvm, flVal); + } + + inline void pushbool(HSquirrelVM* sqvm, const SQBool bVal) + { + __sq_pushbool(sqvm, bVal); + } + + inline void pushasset(HSquirrelVM* sqvm, const SQChar* sVal, int length = -1) + { + __sq_pushasset(sqvm, sVal, length); + } + + inline void pushvector(HSquirrelVM* sqvm, const Vector3 pVal) + { + __sq_pushvector(sqvm, *(float**)&pVal); + } + + inline void pushobject(HSquirrelVM* sqvm, SQObject* obj) + { + __sq_pushobject(sqvm, obj); + } + + inline const SQChar* getstring(HSquirrelVM* sqvm, const SQInteger stackpos) + { + return __sq_getstring(sqvm, stackpos); + } + + inline SQInteger getinteger(HSquirrelVM* sqvm, const SQInteger stackpos) + { + return __sq_getinteger(sqvm, stackpos); + } + + inline SQFloat getfloat(HSquirrelVM* sqvm, const SQInteger stackpos) + { + return __sq_getfloat(sqvm, stackpos); + } + + inline SQBool getbool(HSquirrelVM* sqvm, const SQInteger stackpos) + { + return __sq_getbool(sqvm, stackpos); + } + + inline SQRESULT get(HSquirrelVM* sqvm, const SQInteger stackpos) + { + return __sq_get(sqvm, stackpos); + } + + inline Vector3 getvector(HSquirrelVM* sqvm, const SQInteger stackpos) + { + float* pRet = __sq_getvector(sqvm, stackpos); + return *(Vector3*)&pRet; + } + + inline int sq_getfunction(HSquirrelVM* sqvm, const char* name, SQObject* returnObj, const char* signature) + { + return __sq_getfunction(sqvm, name, returnObj, signature); + } + + inline SQRESULT getasset(HSquirrelVM* sqvm, const SQInteger stackpos, const char** result) + { + return __sq_getasset(sqvm, stackpos, result); + } + + template inline SQRESULT getuserdata(HSquirrelVM* sqvm, const SQInteger stackpos, T* data, uint64_t* typeId) + { + return __sq_getuserdata(sqvm, stackpos, (void**)data, typeId); // this sometimes crashes idk + } + + template inline T* createuserdata(HSquirrelVM* sqvm, SQInteger size) + { + void* ret = __sq_createuserdata(sqvm, size); + memset(ret, 0, size); + return (T*)ret; + } + + inline SQRESULT setuserdatatypeid(HSquirrelVM* sqvm, const SQInteger stackpos, uint64_t typeId) + { + return __sq_setuserdatatypeid(sqvm, stackpos, typeId); + } + + template inline SQBool getthisentity(HSquirrelVM* sqvm, T* ppEntity) + { + return __sq_getentity(sqvm, (void**)ppEntity); + } + + template inline T* getentity(HSquirrelVM* sqvm, SQInteger iStackPos) + { + SQObject obj; + __sq_getobject(sqvm, iStackPos, &obj); + + // there are entity constants for other types, but seemingly CBaseEntity's is the only one needed + return (T*)__sq_getentityfrominstance(m_pSQVM, &obj, __sq_GetEntityConstant_CBaseEntity()); + } +#pragma endregion +}; + +template class SquirrelManager : public virtual SquirrelManagerBase +{ + public: +#pragma region MessageBuffer + SquirrelMessageBuffer* messageBuffer; + + template SquirrelMessage AsyncCall(std::string funcname, Args... args) + { + // This function schedules a call to be executed on the next frame + // This is useful for things like threads and plugins, which do not run on the main thread + FunctionVector functionVector; + SqRecurseArgs(functionVector, args...); + SquirrelMessage message = {funcname, functionVector}; + messageBuffer->push(message); + return message; + } + + SquirrelMessage AsyncCall(std::string funcname) + { + // This function schedules a call to be executed on the next frame + // This is useful for things like threads and plugins, which do not run on the main thread + FunctionVector functionVector = {}; + SquirrelMessage message = {funcname, functionVector}; + messageBuffer->push(message); + return message; + } + + SQRESULT Call(const char* funcname) + { + // Warning! + // This function assumes the squirrel VM is stopped/blocked at the moment of call + // Calling this function while the VM is running is likely to result in a crash due to stack destruction + // If you want to call into squirrel asynchronously, use `AsyncCall` instead + + if (!m_pSQVM || !m_pSQVM->sqvm) + { + spdlog::error( + "{} was called on context {} while VM was not initialized. This will crash", __FUNCTION__, GetContextName(context)); + } + + SQObject functionobj {}; + int result = sq_getfunction(m_pSQVM->sqvm, funcname, &functionobj, 0); + if (result != 0) // This func returns 0 on success for some reason + { + NS::log::squirrel_logger()->error("Call was unable to find function with name '{}'. Is it global?", funcname); + return SQRESULT_ERROR; + } + pushobject(m_pSQVM->sqvm, &functionobj); // Push the function object + pushroottable(m_pSQVM->sqvm); // Push root table + return _call(m_pSQVM->sqvm, 0); + } + + template SQRESULT Call(const char* funcname, Args... args) + { + // Warning! + // This function assumes the squirrel VM is stopped/blocked at the moment of call + // Calling this function while the VM is running is likely to result in a crash due to stack destruction + // If you want to call into squirrel asynchronously, use `schedule_call` instead + if (!m_pSQVM || !m_pSQVM->sqvm) + { + spdlog::error( + "{} was called on context {} while VM was not initialized. This will crash", __FUNCTION__, GetContextName(context)); + } + SQObject functionobj {}; + int result = sq_getfunction(m_pSQVM->sqvm, funcname, &functionobj, 0); + if (result != 0) // This func returns 0 on success for some reason + { + NS::log::squirrel_logger()->error("Call was unable to find function with name '{}'. Is it global?", funcname); + return SQRESULT_ERROR; + } + pushobject(m_pSQVM->sqvm, &functionobj); // Push the function object + pushroottable(m_pSQVM->sqvm); // Push root table + + FunctionVector functionVector; + SqRecurseArgs(functionVector, args...); + + for (auto& v : functionVector) + { + // Execute lambda to push arg to stack + v(); + } + + return _call(m_pSQVM->sqvm, functionVector.size()); + } + +#pragma endregion + + public: + SquirrelManager() + { + m_pSQVM = nullptr; + } + + void VMCreated(CSquirrelVM* newSqvm); + void VMDestroyed(); + void ExecuteCode(const char* code); + void AddFuncRegistration(std::string returnType, std::string name, std::string argTypes, std::string helpText, SQFunction func); + SQRESULT setupfunc(const SQChar* funcname); + void AddFuncOverride(std::string name, SQFunction func); + void ProcessMessageBuffer(); +}; + +template SquirrelManager* g_pSquirrel; + +void InitialiseSquirrelManagers(); + +/* + Beware all ye who enter below. + This place is not a place of honor... no highly esteemed deed is commemorated here... nothing valued is here. + What is here was dangerous and repulsive to us. This message is a warning about danger. +*/ + +#pragma region MessageBuffer templates + +// Clang-formatting makes this whole thing unreadable +// clang-format off + +#ifndef MessageBufferFuncs +#define MessageBufferFuncs +// Bools +template +requires std::convertible_to && (!std::is_floating_point_v) && (!std::convertible_to) && (!std::convertible_to) +inline VoidFunction SQMessageBufferPushArg(T& arg) { + return [arg]{ g_pSquirrel->pushbool(g_pSquirrel->m_pSQVM->sqvm, static_cast(arg)); }; +} +// Vectors +template +inline VoidFunction SQMessageBufferPushArg(Vector3& arg) { + return [arg]{ g_pSquirrel->pushvector(g_pSquirrel->m_pSQVM->sqvm, arg); }; +} +// Vectors +template +inline VoidFunction SQMessageBufferPushArg(SQObject* arg) { + return [arg]{ g_pSquirrel->pushSQObject(g_pSquirrel->m_pSQVM->sqvm, arg); }; +} +// Ints +template +requires std::convertible_to && (!std::is_floating_point_v) +inline VoidFunction SQMessageBufferPushArg(T& arg) { + return [arg]{ g_pSquirrel->pushinteger(g_pSquirrel->m_pSQVM->sqvm, static_cast(arg)); }; +} +// Floats +template +requires std::convertible_to && (std::is_floating_point_v) +inline VoidFunction SQMessageBufferPushArg(T& arg) { + return [arg]{ g_pSquirrel->pushfloat(g_pSquirrel->m_pSQVM->sqvm, static_cast(arg)); }; +} +// Strings +template +requires (std::convertible_to || std::is_constructible_v) +inline VoidFunction SQMessageBufferPushArg(T& arg) { + auto converted = std::string(arg); + return [converted]{ g_pSquirrel->pushstring(g_pSquirrel->m_pSQVM->sqvm, converted.c_str(), converted.length()); }; +} +// Assets +template +inline VoidFunction SQMessageBufferPushArg(SquirrelAsset& arg) { + return [arg]{ g_pSquirrel->pushasset(g_pSquirrel->m_pSQVM->sqvm, arg.path.c_str(), arg.path.length()); }; +} +// Maps +template +requires is_iterable +inline VoidFunction SQMessageBufferPushArg(T& arg) { + FunctionVector localv = {}; + localv.push_back([]{g_pSquirrel->newarray(g_pSquirrel->m_pSQVM->sqvm, 0);}); + + for (const auto& item : arg) { + localv.push_back(SQMessageBufferPushArg(item)); + localv.push_back([]{g_pSquirrel->arrayappend(g_pSquirrel->m_pSQVM->sqvm, -2);}); + } + + return [localv] { for (auto& func : localv) { func(); } }; +} +// Vectors +template +requires is_map +inline VoidFunction SQMessageBufferPushArg(T& map) { + FunctionVector localv = {}; + localv.push_back([]{g_pSquirrel->newtable(g_pSquirrel->m_pSQVM->sqvm);}); + + for (const auto& item : map) { + localv.push_back(SQMessageBufferPushArg(item.first)); + localv.push_back(SQMessageBufferPushArg(item.second)); + localv.push_back([]{g_pSquirrel->newslot(g_pSquirrel->m_pSQVM->sqvm, -3, false);}); + } + + return [localv]{ for (auto& func : localv) { func(); } }; +} + +template +inline void SqRecurseArgs(FunctionVector& v, T& arg) { + v.push_back(SQMessageBufferPushArg(arg)); +} + +// This function is separated from the PushArg function so as to not generate too many template instances +// This is the main function responsible for unrolling the argument pack +template +inline void SqRecurseArgs(FunctionVector& v, T& arg, Args... args) { + v.push_back(SQMessageBufferPushArg(arg)); + SqRecurseArgs(v, args...); +} + +// clang-format on +#endif + +#pragma endregion diff --git a/NorthstarDLL/squirrel/squirrelautobind.cpp b/NorthstarDLL/squirrel/squirrelautobind.cpp new file mode 100644 index 00000000..d5f42477 --- /dev/null +++ b/NorthstarDLL/squirrel/squirrelautobind.cpp @@ -0,0 +1,21 @@ +#include "pch.h" +#include "squirrelautobind.h" + +SquirrelAutoBindContainer* g_pSqAutoBindContainer; + +ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrelAutoBind, ClientSquirrel, (CModule module)) +{ + spdlog::info("ClientSquirrelAutoBInd AutoBindFuncsVectorsize {}", g_pSqAutoBindContainer->clientSqAutoBindFuncs.size()); + for (auto& autoBindFunc : g_pSqAutoBindContainer->clientSqAutoBindFuncs) + { + autoBindFunc(); + } +} + +ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrelAutoBind, ServerSquirrel, (CModule module)) +{ + for (auto& autoBindFunc : g_pSqAutoBindContainer->serverSqAutoBindFuncs) + { + autoBindFunc(); + } +} diff --git a/NorthstarDLL/squirrel/squirrelautobind.h b/NorthstarDLL/squirrel/squirrelautobind.h new file mode 100644 index 00000000..865479a1 --- /dev/null +++ b/NorthstarDLL/squirrel/squirrelautobind.h @@ -0,0 +1,76 @@ +#pragma once +#include + +typedef void (*SqAutoBindFunc)(); + +class SquirrelAutoBindContainer +{ + public: + std::vector> clientSqAutoBindFuncs; + std::vector> serverSqAutoBindFuncs; +}; + +extern SquirrelAutoBindContainer* g_pSqAutoBindContainer; + +class __squirrelautobind; + +#define ADD_SQFUNC(returnType, funcName, argTypes, helpText, runOnContext) \ + template SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm); \ + namespace \ + { \ + __squirrelautobind CONCAT2(__squirrelautobind, __LINE__)( \ + []() \ + { \ + if constexpr (runOnContext & ScriptContext::UI) \ + g_pSquirrel->AddFuncRegistration( \ + returnType, __STR(funcName), argTypes, helpText, CONCAT2(Script_, funcName) < ScriptContext::UI >); \ + if constexpr (runOnContext & ScriptContext::CLIENT) \ + g_pSquirrel->AddFuncRegistration( \ + returnType, __STR(funcName), argTypes, helpText, CONCAT2(Script_, funcName) < ScriptContext::CLIENT >); \ + }, \ + []() \ + { \ + if constexpr (runOnContext & ScriptContext::SERVER) \ + g_pSquirrel->AddFuncRegistration( \ + returnType, __STR(funcName), argTypes, helpText, CONCAT2(Script_, funcName) < ScriptContext::SERVER >); \ + }); \ + } \ + template SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm) + +#define REPLACE_SQFUNC(funcName, runOnContext) \ + template SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm); \ + namespace \ + { \ + __squirrelautobind CONCAT2(__squirrelautobind, __LINE__)( \ + []() \ + { \ + if constexpr (runOnContext & ScriptContext::UI) \ + g_pSquirrel->AddFuncOverride(__STR(funcName), CONCAT2(Script_, funcName) < ScriptContext::UI >); \ + if constexpr (runOnContext & ScriptContext::CLIENT) \ + g_pSquirrel->AddFuncOverride( \ + __STR(funcName), CONCAT2(Script_, funcName) < ScriptContext::CLIENT >); \ + }, \ + []() \ + { \ + if constexpr (runOnContext & ScriptContext::SERVER) \ + g_pSquirrel->AddFuncOverride( \ + __STR(funcName), CONCAT2(Script_, funcName) < ScriptContext::SERVER >); \ + }); \ + } \ + template SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm) + +class __squirrelautobind +{ + public: + __squirrelautobind() = delete; + + __squirrelautobind(std::function clientAutoBindFunc, std::function serverAutoBindFunc) + { + // Bit hacky but we can't initialise this normally since this gets run automatically on load + if (g_pSqAutoBindContainer == nullptr) + g_pSqAutoBindContainer = new SquirrelAutoBindContainer(); + + g_pSqAutoBindContainer->clientSqAutoBindFuncs.push_back(clientAutoBindFunc); + g_pSqAutoBindContainer->serverSqAutoBindFuncs.push_back(serverAutoBindFunc); + } +}; diff --git a/NorthstarDLL/squirrel/squirrelclasstypes.h b/NorthstarDLL/squirrel/squirrelclasstypes.h new file mode 100644 index 00000000..e26bc8d0 --- /dev/null +++ b/NorthstarDLL/squirrel/squirrelclasstypes.h @@ -0,0 +1,239 @@ +#pragma once +#include "pch.h" +#include "squirreldatatypes.h" + +#include + +enum SQRESULT : SQInteger +{ + SQRESULT_ERROR = -1, + SQRESULT_NULL = 0, + SQRESULT_NOTNULL = 1, +}; + +typedef SQRESULT (*SQFunction)(HSquirrelVM* sqvm); + +enum class eSQReturnType +{ + Float = 0x1, + Vector = 0x3, + Integer = 0x5, + Boolean = 0x6, + Entity = 0xD, + String = 0x21, + Default = 0x20, + Arrays = 0x25, + Asset = 0x28, + Table = 0x26, +}; + +const std::map PrintSQRESULT = { + {SQRESULT::SQRESULT_ERROR, "SQRESULT_ERROR"}, + {SQRESULT::SQRESULT_NULL, "SQRESULT_NULL"}, + {SQRESULT::SQRESULT_NOTNULL, "SQRESULT_NOTNULL"}}; + +struct CompileBufferState +{ + const SQChar* buffer; + const SQChar* bufferPlusLength; + const SQChar* bufferAgain; + + CompileBufferState(const std::string& code) + { + buffer = code.c_str(); + bufferPlusLength = code.c_str() + code.size(); + bufferAgain = code.c_str(); + } +}; + +struct SQFuncRegistration +{ + const char* squirrelFuncName; + const char* cppFuncName; + const char* helpText; + const char* returnTypeString; + const char* argTypes; + uint32_t unknown1; + uint32_t devLevel; + const char* shortNameMaybe; + uint32_t unknown2; + eSQReturnType returnType; + uint32_t* externalBufferPointer; + uint64_t externalBufferSize; + uint64_t unknown3; + uint64_t unknown4; + SQFunction funcPtr; + + SQFuncRegistration() + { + memset(this, 0, sizeof(SQFuncRegistration)); + this->returnType = eSQReturnType::Default; + } +}; + +enum class ScriptContext : int +{ + SERVER, + CLIENT, + UI, +}; + +typedef std::vector> FunctionVector; +typedef std::function VoidFunction; + +// clang-format off +template +concept is_map = + // Simple maps + std::same_as> || + std::same_as> || + + // Nested maps + std::same_as < + std::map, + std::map + > || + std::same_as < + std::unordered_map, + std::unordered_map + > || + std::same_as < + std::unordered_map, + std::map + > || + std::same_as < + std::map, + std::unordered_map + > +; + +template +concept is_iterable = requires(std::ranges::range_value_t x) +{ + x.begin(); // must have `x.begin()` + x.end(); // and `x.end()` +}; + +// clang-format on + +typedef void (*SquirrelMessage_External_Pop)(HSquirrelVM* sqvm); +typedef void (*sq_schedule_call_externalType)(ScriptContext context, const char* funcname, SquirrelMessage_External_Pop function); + +class SquirrelMessage +{ + public: + std::string functionName; + FunctionVector args; + bool isExternal = false; + SquirrelMessage_External_Pop externalFunc = NULL; +}; + +class SquirrelMessageBuffer +{ + + private: + std::queue messages = {}; + + public: + std::mutex mutex; + std::optional pop() + { + std::lock_guard guard(mutex); + if (!messages.empty()) + { + auto message = messages.front(); + messages.pop(); + return message; + } + else + { + return std::nullopt; + } + } + + void unwind() + { + auto maybeMessage = this->pop(); + if (!maybeMessage) + { + spdlog::error("Plugin tried consuming SquirrelMessage while buffer was empty"); + return; + } + auto message = maybeMessage.value(); + for (auto& v : message.args) + { + // Execute lambda to push arg to stack + v(); + } + } + + void push(SquirrelMessage message) + { + std::lock_guard guard(mutex); + messages.push(message); + } +}; + +// Super simple wrapper class to allow pushing Assets via call +class SquirrelAsset +{ + public: + std::string path; + SquirrelAsset(std::string path) : path(path) {}; +}; + +#pragma region TypeDefs + +// core sqvm funcs +typedef int64_t (*RegisterSquirrelFuncType)(CSquirrelVM* sqvm, SQFuncRegistration* funcReg, char unknown); +typedef void (*sq_defconstType)(CSquirrelVM* sqvm, const SQChar* name, int value); + +typedef SQRESULT (*sq_compilebufferType)( + HSquirrelVM* sqvm, CompileBufferState* compileBuffer, const char* file, int a1, SQBool bShouldThrowError); +typedef SQRESULT (*sq_callType)(HSquirrelVM* sqvm, SQInteger iArgs, SQBool bShouldReturn, SQBool bThrowError); +typedef SQInteger (*sq_raiseerrorType)(HSquirrelVM* sqvm, const SQChar* pError); + +// sq stack array funcs +typedef void (*sq_newarrayType)(HSquirrelVM* sqvm, SQInteger iStackpos); +typedef SQRESULT (*sq_arrayappendType)(HSquirrelVM* sqvm, SQInteger iStackpos); + +// sq table funcs +typedef SQRESULT (*sq_newtableType)(HSquirrelVM* sqvm); +typedef SQRESULT (*sq_newslotType)(HSquirrelVM* sqvm, SQInteger idx, SQBool bStatic); + +// sq stack push funcs +typedef void (*sq_pushroottableType)(HSquirrelVM* sqvm); +typedef void (*sq_pushstringType)(HSquirrelVM* sqvm, const SQChar* pStr, SQInteger iLength); +typedef void (*sq_pushintegerType)(HSquirrelVM* sqvm, SQInteger i); +typedef void (*sq_pushfloatType)(HSquirrelVM* sqvm, SQFloat f); +typedef void (*sq_pushboolType)(HSquirrelVM* sqvm, SQBool b); +typedef void (*sq_pushassetType)(HSquirrelVM* sqvm, const SQChar* str, SQInteger iLength); +typedef void (*sq_pushvectorType)(HSquirrelVM* sqvm, const SQFloat* pVec); +typedef void (*sq_pushobjectType)(HSquirrelVM* sqvm, SQObject* pVec); + +// sq stack get funcs +typedef const SQChar* (*sq_getstringType)(HSquirrelVM* sqvm, SQInteger iStackpos); +typedef SQInteger (*sq_getintegerType)(HSquirrelVM* sqvm, SQInteger iStackpos); +typedef SQFloat (*sq_getfloatType)(HSquirrelVM*, SQInteger iStackpos); +typedef SQBool (*sq_getboolType)(HSquirrelVM*, SQInteger iStackpos); +typedef SQRESULT (*sq_getType)(HSquirrelVM* sqvm, SQInteger iStackpos); +typedef SQRESULT (*sq_getassetType)(HSquirrelVM* sqvm, SQInteger iStackpos, const char** pResult); +typedef SQRESULT (*sq_getuserdataType)(HSquirrelVM* sqvm, SQInteger iStackpos, void** pData, uint64_t* pTypeId); +typedef SQFloat* (*sq_getvectorType)(HSquirrelVM* sqvm, SQInteger iStackpos); +typedef SQBool (*sq_getthisentityType)(HSquirrelVM*, void** ppEntity); +typedef void (*sq_getobjectType)(HSquirrelVM*, SQInteger iStackPos, SQObject* pOutObj); + +// sq stack userpointer funcs +typedef void* (*sq_createuserdataType)(HSquirrelVM* sqvm, SQInteger iSize); +typedef SQRESULT (*sq_setuserdatatypeidType)(HSquirrelVM* sqvm, SQInteger iStackpos, uint64_t iTypeId); + +// sq misc entity funcs +typedef void* (*sq_getentityfrominstanceType)(CSquirrelVM* sqvm, SQObject* pInstance, char** ppEntityConstant); +typedef char** (*sq_GetEntityConstantType)(); + +typedef int (*sq_getfunctionType)(HSquirrelVM* sqvm, const char* name, SQObject* returnObj, const char* signature); + +#pragma endregion + +// These "external" versions of the types are for plugins +typedef int64_t (*RegisterSquirrelFuncType_External)(ScriptContext context, SQFuncRegistration* funcReg, char unknown); diff --git a/NorthstarDLL/squirrel/squirreldatatypes.h b/NorthstarDLL/squirrel/squirreldatatypes.h new file mode 100644 index 00000000..e9f88d08 --- /dev/null +++ b/NorthstarDLL/squirrel/squirreldatatypes.h @@ -0,0 +1,495 @@ +#pragma once +/* + This file has been generated by IDA. + It contains local type definitions from + the type library 'server.dll' +*/ + +struct HSquirrelVM; +struct CallInfo; +struct SQTable; +struct SQString; +struct SQFunctionProto; +struct SQClosure; +struct SQSharedState; +struct StringTable; +struct SQStructInstance; +struct SQStructDef; +struct SQNativeClosure; +struct SQArray; +struct tableNode; +struct SQUserData; + +typedef void (*releasehookType)(void* val, int size); + +// stolen from ttf2sdk: sqvm types +typedef float SQFloat; +typedef long SQInteger; +typedef unsigned long SQUnsignedInteger; +typedef char SQChar; +typedef SQUnsignedInteger SQBool; + +/* 127 */ +enum SQObjectType : int +{ + _RT_NULL = 0x1, + _RT_INTEGER = 0x2, + _RT_FLOAT = 0x4, + _RT_BOOL = 0x8, + _RT_STRING = 0x10, + _RT_TABLE = 0x20, + _RT_ARRAY = 0x40, + _RT_USERDATA = 0x80, + _RT_CLOSURE = 0x100, + _RT_NATIVECLOSURE = 0x200, + _RT_GENERATOR = 0x400, + OT_USERPOINTER = 0x800, + _RT_USERPOINTER = 0x800, + _RT_THREAD = 0x1000, + _RT_FUNCPROTO = 0x2000, + _RT_CLASS = 0x4000, + _RT_INSTANCE = 0x8000, + _RT_WEAKREF = 0x10000, + OT_VECTOR = 0x40000, + SQOBJECT_CANBEFALSE = 0x1000000, + OT_NULL = 0x1000001, + OT_BOOL = 0x1000008, + SQOBJECT_DELEGABLE = 0x2000000, + SQOBJECT_NUMERIC = 0x4000000, + OT_INTEGER = 0x5000002, + OT_FLOAT = 0x5000004, + SQOBJECT_REF_COUNTED = 0x8000000, + OT_STRING = 0x8000010, + OT_ARRAY = 0x8000040, + OT_CLOSURE = 0x8000100, + OT_NATIVECLOSURE = 0x8000200, + OT_ASSET = 0x8000400, + OT_THREAD = 0x8001000, + OT_FUNCPROTO = 0x8002000, + OT_CLAAS = 0x8004000, + OT_STRUCT = 0x8200000, + OT_WEAKREF = 0x8010000, + OT_TABLE = 0xA000020, + OT_USERDATA = 0xA000080, + OT_INSTANCE = 0xA008000, + OT_ENTITY = 0xA400000, +}; + +/* 156 */ +union SQObjectValue +{ + SQString* asString; + SQTable* asTable; + SQClosure* asClosure; + SQFunctionProto* asFuncProto; + SQStructDef* asStructDef; + long long as64Integer; + SQNativeClosure* asNativeClosure; + SQArray* asArray; + HSquirrelVM* asThread; + float asFloat; + int asInteger; + SQUserData* asUserdata; + SQStructInstance* asStructInstance; +}; + +/* 160 */ +struct SQVector +{ + SQObjectType _Type; + float x; + float y; + float z; +}; + +/* 128 */ +struct SQObject +{ + SQObjectType _Type; + int structNumber; + SQObjectValue _VAL; +}; + +/* 138 */ +struct alignas(8) SQString +{ + void* vftable; + int uiRef; + int padding; + SQString* _next_maybe; + SQSharedState* sharedState; + int length; + unsigned char gap_24[4]; + char _hash[8]; + char _val[1]; +}; + +/* 137 */ +struct alignas(8) SQTable +{ + void* vftable; + unsigned char gap_08[4]; + int uiRef; + unsigned char gap_10[8]; + void* pointer_18; + void* pointer_20; + void* _sharedState; + long long field_30; + tableNode* _nodes; + int _numOfNodes; + int size; + int field_48; + int _usedNodes; + unsigned char _gap_50[20]; + int field_64; + unsigned char _gap_68[80]; +}; + +/* 140 */ +struct alignas(8) SQClosure +{ + void* vftable; + unsigned char gap_08[4]; + int uiRef; + void* pointer_10; + void* pointer_18; + void* pointer_20; + void* sharedState; + SQObject obj_30; + SQObject _function; + SQObject* _outervalues; + unsigned char gap_58[8]; + unsigned char gap_60[96]; + SQObject* objectPointer_C0; + unsigned char gap_C8[16]; +}; + +/* 139 */ +struct alignas(8) SQFunctionProto +{ + void* vftable; + unsigned char gap_08[4]; + int uiRef; + unsigned char gap_10[8]; + void* pointer_18; + void* pointer_20; + void* sharedState; + void* pointer_30; + SQObjectType _fileNameType; + SQString* _fileName; + SQObjectType _funcNameType; + SQString* _funcName; + SQObject obj_58; + unsigned char gap_68[12]; + int _stacksize; + unsigned char gap_78[48]; + int nParameters; + unsigned char gap_AC[60]; + int nDefaultParams; + unsigned char gap_EC[200]; +}; + +/* 152 */ +struct SQStructDef +{ + void* vtable; + int uiRef; + unsigned char padding_C[4]; + unsigned char unknown[24]; + SQSharedState* sharedState; + SQObjectType _nameType; + SQString* _name; + unsigned char gap_38[16]; + SQObjectType _variableNamesType; + SQTable* _variableNames; + unsigned char gap_[32]; +}; + +/* 157 */ +struct alignas(8) SQNativeClosure +{ + void* vftable; + int uiRef; + unsigned char gap_C[4]; + long long value_10; + long long value_18; + long long value_20; + SQSharedState* sharedState; + char unknown_30; + unsigned char padding_34[7]; + long long value_38; + long long value_40; + long long value_48; + long long value_50; + long long value_58; + SQObjectType _nameType; + SQString* _name; + long long value_70; + long long value_78; + unsigned char justInCaseGap_80[300]; +}; + +/* 162 */ +struct SQArray +{ + void* vftable; + int uiRef; + unsigned char gap_24[36]; + SQObject* _values; + int _usedSlots; + int _allocated; +}; + +/* 129 */ +struct alignas(8) HSquirrelVM +{ + void* vftable; + int uiRef; + unsigned char gap_8[12]; + void* _toString; + void* _roottable_pointer; + void* pointer_28; + CallInfo* ci; + CallInfo* _callstack; + int _callsstacksize; + int _stackbase; + SQObject* _stackOfCurrentFunction; + SQSharedState* sharedState; + void* pointer_58; + void* pointer_60; + int _top; + SQObject* _stack; + unsigned char gap_78[8]; + SQObject* _vargvstack; + unsigned char gap_88[8]; + SQObject temp_reg; + unsigned char gapA0[8]; + void* pointer_A8; + unsigned char gap_B0[8]; + SQObject _roottable_object; + SQObject _lasterror; + SQObject _errorHandler; + long long field_E8; + int traps; + unsigned char gap_F4[12]; + int _nnativecalls; + int _suspended; + int _suspended_root; + int _callstacksize; + int _suspended_target; + int trapAmount; + int _suspend_varargs; + int unknown_field_11C; + SQObject object_120; +}; + +/* 150 */ +struct SQStructInstance +{ + void* vftable; + __int32 uiRef; + BYTE gap_C[4]; + __int64 unknown_10; + void* pointer_18; + __int64 unknown_20; + SQSharedState* _sharedState; + unsigned int size; + BYTE gap_34[4]; + SQObject data[1]; // This struct is dynamically sized, so this size is unknown +}; + +/* 148 */ +struct SQSharedState +{ + unsigned char gap_0[72]; + void* unknown; + unsigned char gap_50[16344]; + SQObjectType _unknownTableType00; + long long _unknownTableValue00; + unsigned char gap_4038[16]; + StringTable* _stringTable; + unsigned char gap_4050[32]; + SQObjectType _unknownTableType0; + long long _unknownTableValue0; + SQObjectType _unknownObjectType1; + long long _unknownObjectValue1; + unsigned char gap_4090[8]; + SQObjectType _unknownArrayType2; + long long _unknownArrayValue2; + SQObjectType _gobalsArrayType; + SQStructInstance* _globalsArray; + unsigned char gap_40B8[16]; + SQObjectType _nativeClosuresType; + SQTable* _nativeClosures; + SQObjectType _typedConstantsType; + SQTable* _typedConstants; + SQObjectType _untypedConstantsType; + SQTable* _untypedConstants; + SQObjectType _globalsMaybeType; + SQTable* _globals; + SQObjectType _functionsType; + SQTable* _functions; + SQObjectType _structsType; + SQTable* _structs; + SQObjectType _typeDefsType; + SQTable* _typeDefs; + SQObjectType unknownTableType; + SQTable* unknownTable; + SQObjectType _squirrelFilesType; + SQTable* _squirrelFiles; + unsigned char gap_4158[80]; + SQObjectType _nativeClosures2Type; + SQTable* _nativeClosures2; + SQObjectType _entityTypesMaybeType; + SQTable* _entityTypesMaybe; + SQObjectType unknownTable2Type; + SQTable* unknownTable2; + unsigned char gap_41D8[72]; + SQObjectType _compilerKeywordsType; + SQTable* _compilerKeywords; + HSquirrelVM* _currentThreadMaybe; + unsigned char gap_4238[8]; + SQObjectType unknownTable3Type; + SQTable* unknownTable3; + unsigned char gap_4250[16]; + SQObjectType unknownThreadType; + SQTable* unknownThread; + SQObjectType _tableNativeFunctionsType; + SQTable* _tableNativeFunctions; + SQObjectType _unknownTableType4; + long long _unknownObjectValue4; + SQObjectType _unknownObjectType5; + long long _unknownObjectValue5; + SQObjectType _unknownObjectType6; + long long _unknownObjectValue6; + SQObjectType _unknownObjectType7; + long long _unknownObjectValue7; + SQObjectType _unknownObjectType8; + long long _unknownObjectValue8; + SQObjectType _unknownObjectType9; + long long _unknownObjectValue9; + SQObjectType _unknownObjectType10; + long long _unknownObjectValue10; + SQObjectType _unknownObjectType11; + long long _unknownObjectValue11; + SQObjectType _unknownObjectType12; + long long _unknownObjectValue12; + SQObjectType _unknownObjectType13; + long long _unknownObjectValue13; + SQObjectType _unknownObjectType14; + long long _unknownObjectValue14; + SQObjectType _unknownObjectType15; + long long _unknownObjectValue15; + unsigned char gap_4340[16]; + void* printFunction; + unsigned char gap_4358[16]; + void* logEntityFunction; + unsigned char gap_4370[40]; + SQObjectType _waitStringType; + SQString* _waitStringValue; + SQObjectType _SpinOffAndWaitForStringType; + SQString* _SpinOffAndWaitForStringValue; + SQObjectType _SpinOffAndWaitForSoloStringType; + SQString* _SpinOffAndWaitForSoloStringValue; + SQObjectType _SpinOffStringType; + SQString* _SpinOffStringValue; + SQObjectType _SpinOffDelayedStringType; + SQString* _SpinOffDelayedStringValue; + unsigned char gap_43E8[8]; + bool enableDebugInfo; // functionality stripped + unsigned char gap_43F1[23]; +}; + +/* 165 */ +struct tableNode +{ + SQObject val; + SQObject key; + tableNode* next; +}; + +/* 136 */ +struct alignas(8) CallInfo +{ + long long ip; + SQObject* _literals; + SQObject obj10; + SQObject closure; + int _etraps[4]; + int _root; + short _vargs_size; + short _vargs_base; + unsigned char gap[16]; +}; + +/* 149 */ +struct StringTable +{ + unsigned char gap_0[12]; + int _numofslots; + unsigned char gap_10[200]; +}; + +/* 141 */ +struct alignas(8) SQStackInfos +{ + char* _name; + char* _sourceName; + int _line; +}; + +/* 151 */ +struct alignas(4) SQInstruction +{ + int op; + int arg1; + int output; + short arg2; + short arg3; +}; + +/* 154 */ +struct SQLexer +{ + unsigned char gap_0[112]; +}; + +/* 153 */ +struct SQCompiler +{ + unsigned char gap_0[4]; + int _token; + unsigned char gap_8[8]; + SQObject object_10; + SQLexer lexer; + unsigned char gap_90[752]; + HSquirrelVM* sqvm; + unsigned char gap_288[8]; +}; + +/* 155 */ +struct CSquirrelVM +{ + unsigned char gap_0[8]; + HSquirrelVM* sqvm; + unsigned char gap_10[44]; + int loadEnumFromFileMaybe; + unsigned char gap_40[200]; +}; + +struct SQUserData +{ + void* vftable; + int uiRef; + char gap_12[4]; + long long unknown_10; + long long unknown_18; + long long unknown_20; + long long sharedState; + long long unknown_30; + int size; + char padding1[4]; + releasehookType releaseHook; + long long typeId; + char data[1]; +}; -- cgit v1.2.3