diff options
-rw-r--r-- | NorthstarDedicatedTest/modmanager.cpp | 54 | ||||
-rw-r--r-- | NorthstarDedicatedTest/modmanager.h | 5 | ||||
-rw-r--r-- | NorthstarDedicatedTest/sourceconsole.cpp | 1 | ||||
-rw-r--r-- | NorthstarDedicatedTest/squirrel.cpp | 130 | ||||
-rw-r--r-- | NorthstarDedicatedTest/squirrel.h | 83 |
5 files changed, 237 insertions, 36 deletions
diff --git a/NorthstarDedicatedTest/modmanager.cpp b/NorthstarDedicatedTest/modmanager.cpp index 72998347..a45d2b2a 100644 --- a/NorthstarDedicatedTest/modmanager.cpp +++ b/NorthstarDedicatedTest/modmanager.cpp @@ -106,20 +106,44 @@ Mod::Mod(fs::path modDir, char* jsonBuf) script->Path = scriptObj["Path"].GetString(); script->RsonRunOn = scriptObj["RunOn"].GetString(); - // callbacks - for (auto iterator = scriptObj.MemberBegin(); iterator != scriptObj.MemberEnd(); iterator++) + if (scriptObj.HasMember("ServerCallback") && scriptObj["ServerCallback"].IsObject()) { - if (!iterator->name.IsString() || !iterator->value.IsObject()) - continue; + ModScriptCallback* callback = new ModScriptCallback; + callback->Context = SERVER; + + if (scriptObj["ServerCallback"].HasMember("Before") && scriptObj["ServerCallback"]["Before"].IsString()) + callback->BeforeCallback = scriptObj["ServerCallback"]["Before"].GetString(); + if (scriptObj["ServerCallback"].HasMember("After") && scriptObj["ServerCallback"]["After"].IsString()) + callback->AfterCallback = scriptObj["ServerCallback"]["After"].GetString(); + + script->Callbacks.push_back(callback); + } + + if (scriptObj.HasMember("ClientCallback") && scriptObj["ClientCallback"].IsObject()) + { ModScriptCallback* callback = new ModScriptCallback; - callback->HookedCodeCallback = iterator->name.GetString(); + callback->Context = CLIENT; - if (iterator->value.HasMember("Before") && iterator->value["Before"].IsString()) - callback->BeforeCallback = iterator->value["Before"].GetString(); + if (scriptObj["ClientCallback"].HasMember("Before") && scriptObj["ClientCallback"]["Before"].IsString()) + callback->BeforeCallback = scriptObj["ClientCallback"]["Before"].GetString(); - if (iterator->value.HasMember("After") && iterator->value["After"].IsString()) - callback->AfterCallback = iterator->value["After"].GetString(); + if (scriptObj["ClientCallback"].HasMember("After") && scriptObj["ClientCallback"]["After"].IsString()) + callback->AfterCallback = scriptObj["ClientCallback"]["After"].GetString(); + + script->Callbacks.push_back(callback); + } + + if (scriptObj.HasMember("UICallback") && scriptObj["UICallback"].IsObject()) + { + ModScriptCallback* callback = new ModScriptCallback; + callback->Context = UI; + + if (scriptObj["UICallback"].HasMember("Before") && scriptObj["UICallback"]["Before"].IsString()) + callback->BeforeCallback = scriptObj["UICallback"]["Before"].GetString(); + + if (scriptObj["UICallback"].HasMember("After") && scriptObj["UICallback"]["After"].IsString()) + callback->AfterCallback = scriptObj["UICallback"]["After"].GetString(); script->Callbacks.push_back(callback); } @@ -128,6 +152,12 @@ Mod::Mod(fs::path modDir, char* jsonBuf) } } + // vpk stuff + if (fs::exists(modDir / "vpk")) + for (fs::directory_entry file : fs::directory_iterator(modDir / "vpk")) + if (fs::is_regular_file(file) && file.path().extension() == "vpk") + Vpks.push_back(file.path().string()); + wasReadSuccessfully = true; } @@ -138,6 +168,8 @@ ModManager::ModManager() void ModManager::LoadMods() { + // this needs better support for reloads + std::vector<fs::path> modDirs; // get mod directories @@ -179,8 +211,10 @@ void ModManager::LoadMods() for (Mod* mod : loadedMods) { + // for reloads, this is sorta barebones, when we have a good findconvar method, we could probably reset flags and stuff on preexisting convars + // potentially it might also be good to unregister convars when they get removed on a reload, but unsure if necessary for (ModConVar* convar : mod->ConVars) - if (g_CustomConvars.find(convar->Name) == g_CustomConvars.end()) // make sure convar isn't registered yet + if (g_CustomConvars.find(convar->Name) == g_CustomConvars.end()) // make sure convar isn't registered yet, unsure if necessary but idk what behaviour is for defining same convar multiple times RegisterConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.c_str()); } diff --git a/NorthstarDedicatedTest/modmanager.h b/NorthstarDedicatedTest/modmanager.h index f2501bed..c5ddb09f 100644 --- a/NorthstarDedicatedTest/modmanager.h +++ b/NorthstarDedicatedTest/modmanager.h @@ -19,7 +19,10 @@ public: class ModScriptCallback { public: - std::string HookedCodeCallback; + // would've liked to make it possible to hook arbitrary codecallbacks, but couldn't find a function that calls some ui ones + //std::string HookedCodeCallback; + + Context Context; // called before the codecallback is executed std::string BeforeCallback; diff --git a/NorthstarDedicatedTest/sourceconsole.cpp b/NorthstarDedicatedTest/sourceconsole.cpp index 88d038df..b6e69568 100644 --- a/NorthstarDedicatedTest/sourceconsole.cpp +++ b/NorthstarDedicatedTest/sourceconsole.cpp @@ -51,6 +51,7 @@ void InitialiseSourceConsole(HMODULE baseAddress) { g_SourceGameConsole = new SourceInterface<CGameConsole>("client.dll", "GameConsole004"); RegisterConCommand("toggleconsole", ConCommand_toggleconsole, "toggles the console", FCVAR_NONE); + RegisterConVar("ns_test", "yeah", 0, "fuck"); } diff --git a/NorthstarDedicatedTest/squirrel.cpp b/NorthstarDedicatedTest/squirrel.cpp index 90d16e89..27b997ab 100644 --- a/NorthstarDedicatedTest/squirrel.cpp +++ b/NorthstarDedicatedTest/squirrel.cpp @@ -3,6 +3,7 @@ #include "hooks.h" #include "hookutils.h" #include "sigscanning.h" +#include "concommand.h" #include <iostream> // hook forward declarations @@ -12,10 +13,31 @@ SQPrintType UISQPrint; SQPrintType ServerSQPrint; template<Context context> SQInteger SQPrintHook(void* sqvm, char* fmt, ...); -typedef void* (*CreateNewVMType)(void* a1, Context contextArg); +typedef void*(*CreateNewVMType)(void* a1, Context contextArg); CreateNewVMType ClientCreateNewVM; // only need a client one since ui doesn't have its own func for this CreateNewVMType ServerCreateNewVM; -void* CreateNewVMHook(void* a1, Context contextArg); +template<Context context> void* CreateNewVMHook(void* a1, Context contextArg); + +typedef void(*DestroyVMType)(void* a1, void* sqvm); +DestroyVMType ClientDestroyVM; // only need a client one since ui doesn't have its own func for this +DestroyVMType ServerDestroyVM; +template<Context context> void DestroyVMHook(void* a1, void* sqvm); + +typedef void (*SQCompileErrorType)(void* sqvm, const char* error, const char* file, int line, int column); +SQCompileErrorType ClientSQCompileError; // only need a client one since ui doesn't have its own func for this +SQCompileErrorType ServerSQCompileError; +template<Context context> void SQCompileErrorHook(void* sqvm, const char* error, const char* file, int line, int column); + +sq_compilebufferType ClientSq_compilebuffer; +sq_compilebufferType ServerSq_compilebuffer; + +sq_pushroottableType ClientSq_pushroottable; +sq_pushroottableType ServerSq_pushroottable; + +sq_callType ClientSq_call; +sq_callType ServerSq_call; + +template<Context context> void ExecuteCodeCommand(const CCommand& args); // inits SquirrelManager<CLIENT>* g_ClientSquirrelManager; @@ -29,13 +51,21 @@ void InitialiseClientSquirrel(HMODULE baseAddress) // client inits g_ClientSquirrelManager = new SquirrelManager<CLIENT>(); ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x12B00, &SQPrintHook<CLIENT>, reinterpret_cast<LPVOID*>(&ClientSQPrint)); // client print function + RegisterConCommand("script_client", ExecuteCodeCommand<CLIENT>, "Executes script code in the client vm", FCVAR_CLIENTDLL); // ui inits g_UISquirrelManager = new SquirrelManager<UI>(); ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x12BA0, &SQPrintHook<UI>, reinterpret_cast<LPVOID*>(&UISQPrint)); // ui print function + RegisterConCommand("script_ui", ExecuteCodeCommand<UI>, "Executes script code in the client vm", FCVAR_CLIENTDLL); + + // inits for both client and ui, since they share some functions + ClientSq_compilebuffer = (sq_compilebufferType)((char*)baseAddress + 0x3110); + ClientSq_pushroottable = (sq_pushroottableType)((char*)baseAddress + 0x5860); + ClientSq_call = (sq_callType)((char*)baseAddress + 0x8650); - // hooks for both client and ui, since they share some functions - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x26130, &CreateNewVMHook, reinterpret_cast<LPVOID*>(&ClientCreateNewVM)); // client createnewvm function + ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x26130, &CreateNewVMHook<CLIENT>, reinterpret_cast<LPVOID*>(&ClientCreateNewVM)); // client createnewvm function + ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x26E70, &DestroyVMHook<CLIENT>, reinterpret_cast<LPVOID*>(&ClientDestroyVM)); // client destroyvm function + ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x79A50, &SQCompileErrorHook<CLIENT>, reinterpret_cast<LPVOID*>(&ClientSQCompileError)); // client compileerror function } void InitialiseServerSquirrel(HMODULE baseAddress) @@ -43,8 +73,17 @@ void InitialiseServerSquirrel(HMODULE baseAddress) g_ServerSquirrelManager = new SquirrelManager<SERVER>(); HookEnabler hook; + + ServerSq_compilebuffer = (sq_compilebufferType)((char*)baseAddress + 0x3110); + ServerSq_pushroottable = (sq_pushroottableType)((char*)baseAddress + 0x5840); + ServerSq_call = (sq_callType)((char*)baseAddress + 0x8620); + ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1FE90, &SQPrintHook<SERVER>, reinterpret_cast<LPVOID*>(&ServerSQPrint)); // server print function - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x260E0, &CreateNewVMHook, reinterpret_cast<LPVOID*>(&ServerCreateNewVM)); // server createnewvm function + ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x260E0, &CreateNewVMHook<SERVER>, reinterpret_cast<LPVOID*>(&ServerCreateNewVM)); // server createnewvm function + ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x26E20, &DestroyVMHook<SERVER>, reinterpret_cast<LPVOID*>(&ServerDestroyVM)); // server destroyvm function + ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x799E0, &SQCompileErrorHook<SERVER>, reinterpret_cast<LPVOID*>(&ServerSQCompileError)); // server compileerror function + + RegisterConCommand("script", ExecuteCodeCommand<SERVER>, "Executes script code in the server vm", FCVAR_GAMEDLL); } // hooks @@ -59,7 +98,7 @@ template<Context context> SQInteger SQPrintHook(void* sqvm, char* fmt, ...) if (charsWritten > 0) { if (buf[charsWritten - 1] == '\n') - buf[charsWritten - 1] == '\0'; + buf[charsWritten - 1] = '\0'; spdlog::info("[{} SCRIPT] {}", GetContextName(context), buf); } @@ -68,35 +107,78 @@ template<Context context> SQInteger SQPrintHook(void* sqvm, char* fmt, ...) return 0; } -void* CreateNewVMHook(void* a1, Context context) -{ - spdlog::info("CreateNewVM {}", GetContextName(context)); - +template<Context context> void* CreateNewVMHook(void* a1, Context realContext) +{ + void* sqvm; + if (context == CLIENT) { - void* sqvm = ClientCreateNewVM(a1, context); - g_ClientSquirrelManager->sqvm = sqvm; + sqvm = ClientCreateNewVM(a1, realContext); - return sqvm; + if (realContext == UI) + g_UISquirrelManager->sqvm = sqvm; + else + g_ClientSquirrelManager->sqvm = sqvm; } - else if (context == UI) + else if (context == SERVER) { - void* sqvm = ClientCreateNewVM(a1, context); - g_UISquirrelManager->sqvm = sqvm; + sqvm = ServerCreateNewVM(a1, context); + g_ServerSquirrelManager->sqvm = sqvm; + } + + spdlog::info("CreateNewVM {} {}", GetContextName(context), sqvm); + return sqvm; +} + +template<Context context> void DestroyVMHook(void* a1, void* sqvm) +{ + Context realContext = context; // ui and client use the same function so we use this for prints - return sqvm; + if (context == CLIENT) + { + if (g_ClientSquirrelManager->sqvm == sqvm) + g_ClientSquirrelManager->sqvm = nullptr; + else if (g_UISquirrelManager->sqvm == sqvm) + { + g_UISquirrelManager->sqvm = nullptr; + realContext == UI; + } + + ClientDestroyVM(a1, sqvm); } else if (context == SERVER) { - void* sqvm = ServerCreateNewVM(a1, context); - g_ServerSquirrelManager->sqvm = sqvm; - - return sqvm; + g_ServerSquirrelManager->sqvm = nullptr; + ServerDestroyVM(a1, sqvm); } + + spdlog::info("DestroyVM {} {}", GetContextName(realContext), sqvm); } -// manager -template<Context context> SquirrelManager<context>::SquirrelManager() : sqvm(nullptr) +template<Context context> void SQCompileErrorHook(void* sqvm, const char* error, const char* file, int line, int column) { - + // note: i think vanilla might actually show the script line that errored, might be nice to implement that if it's a thing + // look into client.dll+79540 for way better errors too + + Context realContext = context; + if (context == CLIENT && sqvm == g_UISquirrelManager->sqvm) + realContext = UI; + + spdlog::error("{} SCRIPT COMPILE ERROR {}", GetContextName(realContext), error); + spdlog::error("{} line [{}] column [{}]", file, line, column); + + // dont call the original since it kills game + // in the future it'd be nice to do an actual error with UICodeCallback_ErrorDialog here, but only if we're compiling level scripts + // compilestring and stuff shouldn't tho + // though, that also has potential to be REALLY bad if we're compiling ui scripts lol +} + +template<Context context> void ExecuteCodeCommand(const CCommand& args) +{ + if (context == CLIENT) + g_ClientSquirrelManager->ExecuteCode(args.ArgS()); + else if (context == UI) + g_UISquirrelManager->ExecuteCode(args.ArgS()); + else if (context == SERVER) + g_ServerSquirrelManager->ExecuteCode(args.ArgS()); }
\ No newline at end of file diff --git a/NorthstarDedicatedTest/squirrel.h b/NorthstarDedicatedTest/squirrel.h index c682fb60..3c8f9fed 100644 --- a/NorthstarDedicatedTest/squirrel.h +++ b/NorthstarDedicatedTest/squirrel.h @@ -12,13 +12,94 @@ typedef char SQChar; typedef SQUnsignedInteger SQBool; typedef SQInteger SQRESULT; +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(); + } +}; + +typedef SQRESULT(*sq_compilebufferType)(void* sqvm, CompileBufferState* compileBuffer, const char* file, int a1, int a2); +extern sq_compilebufferType ClientSq_compilebuffer; +extern sq_compilebufferType ServerSq_compilebuffer; + +typedef void(*sq_pushroottableType)(void* sqvm); +extern sq_pushroottableType ClientSq_pushroottable; +extern sq_pushroottableType ServerSq_pushroottable; + +typedef SQRESULT(*sq_callType)(void* sqvm, SQInteger s1, SQBool a2, SQBool a3); +extern sq_callType ClientSq_call; +extern sq_callType ServerSq_call; + +//template<Context context> void ExecuteSQCode(SquirrelManager<context> sqManager, const char* code); // need this because we can't do template class functions in the .cpp file + +typedef SQInteger(*SQFunction)(void* sqvm); + template<Context context> class SquirrelManager { +private: + //std::vector< + public: void* sqvm; public: - SquirrelManager(); + SquirrelManager() : sqvm(nullptr) + { + } + + void ExecuteCode(const char* code) + { + // ttf2sdk checks ThreadIsInMainThread here, might be good to do that? doesn't seem like an issue rn tho + + if (!sqvm) + { + spdlog::error("Cannot execute code, {} squirrel vm is not initialised", GetContextName(context)); + return; + } + + void* sqvm2 = *((void**)((char*)sqvm + 8)); // honestly not 100% sure on what this is, but it seems to be what this function is supposed to take + // potentially move to a property later if it's used alot + + spdlog::info("Executing {} script code {} ", GetContextName(context), code); + + std::string strCode(code); + CompileBufferState bufferState = CompileBufferState(strCode); + + SQRESULT compileResult; + if (context == CLIENT || context == UI) + compileResult = ClientSq_compilebuffer(sqvm2, &bufferState, "console", -1, context); + else if (context == SERVER) + compileResult = ServerSq_compilebuffer(sqvm2, &bufferState, "console", -1, context); + + spdlog::info("sq_compilebuffer returned {}", compileResult); + if (compileResult >= 0) + { + if (context == CLIENT || context == UI) + { + ClientSq_pushroottable(sqvm2); + SQRESULT callResult = ClientSq_call(sqvm2, 1, false, false); + spdlog::info("sq_call returned {}", callResult); + } + else if (context == SERVER) + { + ServerSq_pushroottable(sqvm2); + SQRESULT callResult = ServerSq_call(sqvm2, 1, false, false); + spdlog::info("sq_call returned {}", callResult); + } + } + } + void AddFuncRegistration(std::string returnType, std::string name, std::string argTypes, std::string helpText, SQFunction func) + { + + } }; extern SquirrelManager<CLIENT>* g_ClientSquirrelManager; |