diff options
Diffstat (limited to 'NorthstarDLL/squirrel')
-rw-r--r-- | NorthstarDLL/squirrel/squirrel.cpp | 795 | ||||
-rw-r--r-- | NorthstarDLL/squirrel/squirrel.h | 493 | ||||
-rw-r--r-- | NorthstarDLL/squirrel/squirrelautobind.cpp | 21 | ||||
-rw-r--r-- | NorthstarDLL/squirrel/squirrelautobind.h | 76 | ||||
-rw-r--r-- | NorthstarDLL/squirrel/squirrelclasstypes.h | 242 | ||||
-rw-r--r-- | NorthstarDLL/squirrel/squirreldatatypes.h | 495 |
6 files changed, 2122 insertions, 0 deletions
diff --git a/NorthstarDLL/squirrel/squirrel.cpp b/NorthstarDLL/squirrel/squirrel.cpp new file mode 100644 index 00000000..8dd93ed0 --- /dev/null +++ b/NorthstarDLL/squirrel/squirrel.cpp @@ -0,0 +1,795 @@ +#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 <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 ""; +} + +// 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<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); + } + g_pSquirrel<context>->messageBuffer = new SquirrelMessageBuffer(); +} + +template <ScriptContext context> void SquirrelManager<context>::VMDestroyed() +{ + m_pSQVM = 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 context != ScriptContext::SERVER && g_pSquirrel<ScriptContext::UI>->m_pSQVM && + g_pSquirrel<ScriptContext::UI>->m_pSQVM->sqvm == pSqvm; +} + +template <ScriptContext context> void* (*__fastcall 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* (*__fastcall 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> void (*__fastcall 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 (*__fastcall 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()) + { + // 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 <ScriptContext context> +int64_t (*__fastcall 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 (*__fastcall 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 (!Tier0::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); + + 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()); + } +} + +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).As<sq_defconstType>(); + g_pSquirrel<ScriptContext::UI>->__sq_defconst = g_pSquirrel<ScriptContext::CLIENT>->__sq_defconst; + + g_pSquirrel<ScriptContext::CLIENT>->__sq_compilebuffer = module.Offset(0x3110).As<sq_compilebufferType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushroottable = module.Offset(0x5860).As<sq_pushroottableType>(); + 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::CLIENT>->__sq_call = module.Offset(0x8650).As<sq_callType>(); + g_pSquirrel<ScriptContext::UI>->__sq_call = g_pSquirrel<ScriptContext::CLIENT>->__sq_call; + + g_pSquirrel<ScriptContext::CLIENT>->__sq_newarray = module.Offset(0x39F0).As<sq_newarrayType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_arrayappend = module.Offset(0x3C70).As<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).As<sq_newtableType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_newslot = module.Offset(0x70B0).As<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).As<sq_pushstringType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushinteger = module.Offset(0x36A0).As<sq_pushintegerType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushfloat = module.Offset(0x3800).As<sq_pushfloatType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushbool = module.Offset(0x3710).As<sq_pushboolType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushasset = module.Offset(0x3560).As<sq_pushassetType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushvector = module.Offset(0x3780).As<sq_pushvectorType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_pushobject = module.Offset(0x83D0).As<sq_pushobjectType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_raiseerror = module.Offset(0x8470).As<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).As<sq_getstringType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getinteger = module.Offset(0x60E0).As<sq_getintegerType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getfloat = module.Offset(0x6100).As<sq_getfloatType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getbool = module.Offset(0x6130).As<sq_getboolType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_get = module.Offset(0x7C30).As<sq_getType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getasset = module.Offset(0x6010).As<sq_getassetType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getuserdata = module.Offset(0x63D0).As<sq_getuserdataType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getvector = module.Offset(0x6140).As<sq_getvectorType>(); + 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).As<sq_createuserdataType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_setuserdatatypeid = module.Offset(0x6490).As<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).As<sq_GetEntityConstantType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_getentityfrominstance = module.Offset(0x114F0).As<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).As<sq_getfunctionType>(); + g_pSquirrel<ScriptContext::UI>->__sq_getfunction = g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction; + g_pSquirrel<ScriptContext::CLIENT>->__sq_stackinfos = module.Offset(0x35970).As<sq_stackinfosType>(); + g_pSquirrel<ScriptContext::UI>->__sq_stackinfos = g_pSquirrel<ScriptContext::CLIENT>->__sq_stackinfos; + + 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>); + + 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).As<sq_getfunctionType>(); + g_pSquirrel<ScriptContext::UI>->__sq_getfunction = g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction; +} + +ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module)) +{ + AUTOHOOK_DISPATCH_MODULE(server.dll) + + g_pSquirrel<ScriptContext::SERVER>->__sq_defconst = module.Offset(0x1F550).As<sq_defconstType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_compilebuffer = module.Offset(0x3110).As<sq_compilebufferType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_pushroottable = module.Offset(0x5840).As<sq_pushroottableType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_call = module.Offset(0x8620).As<sq_callType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_newarray = module.Offset(0x39F0).As<sq_newarrayType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_arrayappend = module.Offset(0x3C70).As<sq_arrayappendType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_newtable = module.Offset(0x3960).As<sq_newtableType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_newslot = module.Offset(0x7080).As<sq_newslotType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_pushstring = module.Offset(0x3440).As<sq_pushstringType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_pushinteger = module.Offset(0x36A0).As<sq_pushintegerType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_pushfloat = module.Offset(0x3800).As<sq_pushfloatType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_pushbool = module.Offset(0x3710).As<sq_pushboolType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_pushasset = module.Offset(0x3560).As<sq_pushassetType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_pushvector = module.Offset(0x3780).As<sq_pushvectorType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_pushobject = module.Offset(0x83A0).As<sq_pushobjectType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_raiseerror = module.Offset(0x8440).As<sq_raiseerrorType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_getstring = module.Offset(0x60A0).As<sq_getstringType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getinteger = module.Offset(0x60C0).As<sq_getintegerType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getfloat = module.Offset(0x60E0).As<sq_getfloatType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getbool = module.Offset(0x6110).As<sq_getboolType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getasset = module.Offset(0x5FF0).As<sq_getassetType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getuserdata = module.Offset(0x63B0).As<sq_getuserdataType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getvector = module.Offset(0x6120).As<sq_getvectorType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_get = module.Offset(0x7C00).As<sq_getType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_getthisentity = module.Offset(0x203B0).As<sq_getthisentityType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getobject = module.Offset(0x6140).As<sq_getobjectType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_createuserdata = module.Offset(0x38D0).As<sq_createuserdataType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_setuserdatatypeid = module.Offset(0x6470).As<sq_setuserdatatypeidType>(); + + g_pSquirrel<ScriptContext::SERVER>->__sq_GetEntityConstant_CBaseEntity = module.Offset(0x418AF0).As<sq_GetEntityConstantType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_getentityfrominstance = module.Offset(0x1E920).As<sq_getentityfrominstanceType>(); + + g_pSquirrel<ScriptContext::SERVER>->logger = NS::log::SCRIPT_SV; + // Message buffer stuff + g_pSquirrel<ScriptContext::SERVER>->__sq_getfunction = module.Offset(0x6C85).As<sq_getfunctionType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_stackinfos = module.Offset(0x35920).As<sq_stackinfosType>(); + + 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>); + + // 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>(); +} + +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>; +} diff --git a/NorthstarDLL/squirrel/squirrel.h b/NorthstarDLL/squirrel/squirrel.h new file mode 100644 index 00000000..82825b3c --- /dev/null +++ b/NorthstarDLL/squirrel/squirrel.h @@ -0,0 +1,493 @@ +#pragma once + +#include "squirrelclasstypes.h" +#include "squirrelautobind.h" +#include "core/math/vector.h" +#include "mods/modmanager.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<int>(second)); +} + +static constexpr int operator|(ScriptContext first, ScriptContext second) +{ + return (1 << static_cast<int>(first)) + (1 << static_cast<int>(second)); +} + +static constexpr int operator|(int first, ScriptContext second) +{ + return first + (1 << static_cast<int>(second)); +} + +const char* GetContextName(ScriptContext context); +const char* GetContextName_Short(ScriptContext context); +eSQReturnType SQReturnTypeFromString(const char* pReturnType); +const char* SQTypeNameFromID(const int iTypeId); + +ScriptContext ScriptContextFromString(std::string string); + +namespace NS::log +{ + template <ScriptContext context> std::shared_ptr<spdlog::logger> 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<SQFuncRegistration*> m_funcRegistrations; + + public: + CSquirrelVM* m_pSQVM; + std::map<std::string, SQFunction> m_funcOverrides = {}; + std::map<std::string, SQFunction> m_funcOriginals = {}; + + bool m_bFatalCompilationErrors = false; + + std::shared_ptr<spdlog::logger> 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_stackinfosType __sq_stackinfos; + + 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); + } + + inline long long sq_stackinfos(HSquirrelVM* sqvm, int level, SQStackInfos& out) + { + return __sq_stackinfos(sqvm, level, &out, sqvm->_callstacksize); + } + + inline Mod* getcallingmod(HSquirrelVM* sqvm, int depth = 0) + { + SQStackInfos stackInfo {}; + if (1 + depth >= sqvm->_callstacksize) + { + return nullptr; + } + sq_stackinfos(sqvm, 1 + depth, stackInfo); + std::string sourceName = stackInfo._sourceName; + std::replace(sourceName.begin(), sourceName.end(), '/', '\\'); + std::string filename = "scripts\\vscripts\\" + sourceName; + if (auto res = g_pModManager->m_ModFiles.find(filename); res != g_pModManager->m_ModFiles.end()) + { + return res->second.m_pOwningMod; + } + return nullptr; + } + template <typename T> 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 <typename T> 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 <typename T> inline SQBool getthisentity(HSquirrelVM* sqvm, T* ppEntity) + { + return __sq_getentity(sqvm, (void**)ppEntity); + } + + template <typename T> 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 <ScriptContext context> class SquirrelManager : public virtual SquirrelManagerBase +{ + public: +#pragma region MessageBuffer + SquirrelMessageBuffer* messageBuffer; + + template <typename... Args> 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<context>(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<context>()->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 <typename... Args> 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<context>()->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<context>(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 <ScriptContext context> SquirrelManager<context>* 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 <ScriptContext context, typename T> +requires std::convertible_to<T, bool> && (!std::is_floating_point_v<T>) && (!std::convertible_to<T, std::string>) && (!std::convertible_to<T, int>) +inline VoidFunction SQMessageBufferPushArg(T& arg) { + return [arg]{ g_pSquirrel<context>->pushbool(g_pSquirrel<context>->m_pSQVM->sqvm, static_cast<bool>(arg)); }; +} +// Vectors +template <ScriptContext context> +inline VoidFunction SQMessageBufferPushArg(Vector3& arg) { + return [arg]{ g_pSquirrel<context>->pushvector(g_pSquirrel<context>->m_pSQVM->sqvm, arg); }; +} +// Vectors +template <ScriptContext context> +inline VoidFunction SQMessageBufferPushArg(SQObject* arg) { + return [arg]{ g_pSquirrel<context>->pushSQObject(g_pSquirrel<context>->m_pSQVM->sqvm, arg); }; +} +// Ints +template <ScriptContext context, typename T> +requires std::convertible_to<T, int> && (!std::is_floating_point_v<T>) +inline VoidFunction SQMessageBufferPushArg(T& arg) { + return [arg]{ g_pSquirrel<context>->pushinteger(g_pSquirrel<context>->m_pSQVM->sqvm, static_cast<int>(arg)); }; +} +// Floats +template <ScriptContext context, typename T> +requires std::convertible_to<T, float> && (std::is_floating_point_v<T>) +inline VoidFunction SQMessageBufferPushArg(T& arg) { + return [arg]{ g_pSquirrel<context>->pushfloat(g_pSquirrel<context>->m_pSQVM->sqvm, static_cast<float>(arg)); }; +} +// Strings +template <ScriptContext context, typename T> +requires (std::convertible_to<T, std::string> || std::is_constructible_v<std::string, T>) +inline VoidFunction SQMessageBufferPushArg(T& arg) { + auto converted = std::string(arg); + return [converted]{ g_pSquirrel<context>->pushstring(g_pSquirrel<context>->m_pSQVM->sqvm, converted.c_str(), converted.length()); }; +} +// Assets +template <ScriptContext context> +inline VoidFunction SQMessageBufferPushArg(SquirrelAsset& arg) { + return [arg]{ g_pSquirrel<context>->pushasset(g_pSquirrel<context>->m_pSQVM->sqvm, arg.path.c_str(), arg.path.length()); }; +} +// Maps +template <ScriptContext context, typename T> +requires is_iterable<T> +inline VoidFunction SQMessageBufferPushArg(T& arg) { + FunctionVector localv = {}; + localv.push_back([]{g_pSquirrel<context>->newarray(g_pSquirrel<context>->m_pSQVM->sqvm, 0);}); + + for (const auto& item : arg) { + localv.push_back(SQMessageBufferPushArg<context>(item)); + localv.push_back([]{g_pSquirrel<context>->arrayappend(g_pSquirrel<context>->m_pSQVM->sqvm, -2);}); + } + + return [localv] { for (auto& func : localv) { func(); } }; +} +// Vectors +template <ScriptContext context, typename T> +requires is_map<T> +inline VoidFunction SQMessageBufferPushArg(T& map) { + FunctionVector localv = {}; + localv.push_back([]{g_pSquirrel<context>->newtable(g_pSquirrel<context>->m_pSQVM->sqvm);}); + + for (const auto& item : map) { + localv.push_back(SQMessageBufferPushArg<context>(item.first)); + localv.push_back(SQMessageBufferPushArg<context>(item.second)); + localv.push_back([]{g_pSquirrel<context>->newslot(g_pSquirrel<context>->m_pSQVM->sqvm, -3, false);}); + } + + return [localv]{ for (auto& func : localv) { func(); } }; +} + +template <ScriptContext context, typename T> +inline void SqRecurseArgs(FunctionVector& v, T& arg) { + v.push_back(SQMessageBufferPushArg<context>(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 <ScriptContext context, typename T, typename... Args> +inline void SqRecurseArgs(FunctionVector& v, T& arg, Args... args) { + v.push_back(SQMessageBufferPushArg<context>(arg)); + SqRecurseArgs<context>(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 <vector> + +typedef void (*SqAutoBindFunc)(); + +class SquirrelAutoBindContainer +{ + public: + std::vector<std::function<void()>> clientSqAutoBindFuncs; + std::vector<std::function<void()>> serverSqAutoBindFuncs; +}; + +extern SquirrelAutoBindContainer* g_pSqAutoBindContainer; + +class __squirrelautobind; + +#define ADD_SQFUNC(returnType, funcName, argTypes, helpText, runOnContext) \ + template <ScriptContext context> SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm); \ + namespace \ + { \ + __squirrelautobind CONCAT2(__squirrelautobind, __LINE__)( \ + []() \ + { \ + if constexpr (runOnContext & ScriptContext::UI) \ + g_pSquirrel<ScriptContext::UI>->AddFuncRegistration( \ + returnType, __STR(funcName), argTypes, helpText, CONCAT2(Script_, funcName) < ScriptContext::UI >); \ + if constexpr (runOnContext & ScriptContext::CLIENT) \ + g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration( \ + returnType, __STR(funcName), argTypes, helpText, CONCAT2(Script_, funcName) < ScriptContext::CLIENT >); \ + }, \ + []() \ + { \ + if constexpr (runOnContext & ScriptContext::SERVER) \ + g_pSquirrel<ScriptContext::SERVER>->AddFuncRegistration( \ + returnType, __STR(funcName), argTypes, helpText, CONCAT2(Script_, funcName) < ScriptContext::SERVER >); \ + }); \ + } \ + template <ScriptContext context> SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm) + +#define REPLACE_SQFUNC(funcName, runOnContext) \ + template <ScriptContext context> SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm); \ + namespace \ + { \ + __squirrelautobind CONCAT2(__squirrelautobind, __LINE__)( \ + []() \ + { \ + if constexpr (runOnContext & ScriptContext::UI) \ + g_pSquirrel<ScriptContext::UI>->AddFuncOverride(__STR(funcName), CONCAT2(Script_, funcName) < ScriptContext::UI >); \ + if constexpr (runOnContext & ScriptContext::CLIENT) \ + g_pSquirrel<ScriptContext::CLIENT>->AddFuncOverride( \ + __STR(funcName), CONCAT2(Script_, funcName) < ScriptContext::CLIENT >); \ + }, \ + []() \ + { \ + if constexpr (runOnContext & ScriptContext::SERVER) \ + g_pSquirrel<ScriptContext::SERVER>->AddFuncOverride( \ + __STR(funcName), CONCAT2(Script_, funcName) < ScriptContext::SERVER >); \ + }); \ + } \ + template <ScriptContext context> SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm) + +class __squirrelautobind +{ + public: + __squirrelautobind() = delete; + + __squirrelautobind(std::function<void()> clientAutoBindFunc, std::function<void()> 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..efc80f15 --- /dev/null +++ b/NorthstarDLL/squirrel/squirrelclasstypes.h @@ -0,0 +1,242 @@ +#pragma once +#include "pch.h" +#include "squirreldatatypes.h" + +#include <queue> + +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<SQRESULT, const char*> 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 +{ + INVALID = -1, + SERVER, + CLIENT, + UI, +}; + +typedef std::vector<std::function<void()>> FunctionVector; +typedef std::function<void()> VoidFunction; + +// clang-format off +template <typename T> +concept is_map = + // Simple maps + std::same_as<T, std::map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type>> || + std::same_as<T, std::unordered_map<typename T::key_type, typename T::mapped_type, typename T::hasher, typename T::key_equal, typename T::allocator_type>> || + + // Nested maps + std::same_as < + std::map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type>, + std::map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type> + > || + std::same_as < + std::unordered_map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type>, + std::unordered_map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type> + > || + std::same_as < + std::unordered_map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type>, + std::map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type> + > || + std::same_as < + std::map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type>, + std::unordered_map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type> + > +; + +template<typename T> +concept is_iterable = requires(std::ranges::range_value_t<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<SquirrelMessage> messages = {}; + + public: + std::mutex mutex; + std::optional<SquirrelMessage> pop() + { + std::lock_guard<std::mutex> 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<std::mutex> 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); + +typedef long long (*sq_stackinfosType)(HSquirrelVM* sqvm, int iLevel, SQStackInfos* pOutObj, int iCallStackSize); + +// 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..b5025f6b --- /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 _callstacksize; + 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 _unk; + 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]; +}; |