From 90a06cd98b1f5f219bc726ed5b81bd71404815ef Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Fri, 15 Nov 2024 14:54:18 +0000 Subject: [PATCH 1/6] engine: Remove uses of Autohook from `runframe.cpp` (#810) * Manually hook CEngine__Frame * Remove AUTOHOOK_INIT and AUTOHOOK_DISPATCH --- primedev/engine/runframe.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/primedev/engine/runframe.cpp b/primedev/engine/runframe.cpp index ddfd92534..bfec9b8f6 100644 --- a/primedev/engine/runframe.cpp +++ b/primedev/engine/runframe.cpp @@ -3,17 +3,14 @@ #include "hoststate.h" #include "server/serverpresence.h" -AUTOHOOK_INIT() - -// clang-format off -AUTOHOOK(CEngine__Frame, engine.dll + 0x1C8650, -void, __fastcall, (CEngine* self)) -// clang-format on +static void(__fastcall* o_pCEngine__Frame)(CEngine* self) = nullptr; +static void __fastcall h_CEngine__Frame(CEngine* self) { - CEngine__Frame(self); + o_pCEngine__Frame(self); } ON_DLL_LOAD("engine.dll", RunFrame, (CModule module)) { - AUTOHOOK_DISPATCH() + o_pCEngine__Frame = module.Offset(0x1C8650).RCast(); + HookAttach(&(PVOID&)o_pCEngine__Frame, (PVOID)h_CEngine__Frame); } From 3e40fa3c9a589b7fc5088c43ead2b32bf68c6bbe Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Wed, 20 Nov 2024 20:31:40 +0000 Subject: [PATCH 2/6] Remove uses of Autohook from `loghooks.cpp` (#812) * Manually hook TextMsg * Manually hook fprintf * Manually hook ConCommand_echo * Manually hook EngineSpewFunc * Manually hook Status_ConMsg * Manually hook CClientState_ProcessPrint * Remove AUTOHOOK_INIT and AUTOHOOK_DISPATCH_MODULE --- primedev/logging/loghooks.cpp | 56 +++++++++++++++++------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/primedev/logging/loghooks.cpp b/primedev/logging/loghooks.cpp index dcd9b85a8..51b4c241e 100644 --- a/primedev/logging/loghooks.cpp +++ b/primedev/logging/loghooks.cpp @@ -9,8 +9,6 @@ #include #include -AUTOHOOK_INIT() - ConVar* Cvar_spewlog_enable; ConVar* Cvar_cl_showtextmsg; @@ -70,10 +68,8 @@ const std::unordered_map PrintSpewTypes_Short = { ICenterPrint* pInternalCenterPrint = NULL; -// clang-format off -AUTOHOOK(TextMsg, client.dll + 0x198710, -void,, (BFRead* msg)) -// clang-format on +static void (*o_pTextMsg)(BFRead* msg) = nullptr; +static void h_TextMsg(BFRead* msg) { TextMsgPrintType_t msg_dest = (TextMsgPrintType_t)msg->ReadByte(); @@ -103,10 +99,8 @@ void,, (BFRead* msg)) } } -// clang-format off -AUTOHOOK(Hook_fprintf, engine.dll + 0x51B1F0, -int,, (void* const stream, const char* const format, ...)) -// clang-format on +static int (*o_pfprintf)(void* const stream, const char* const format, ...) = nullptr; +static int h_fprintf(void* const stream, const char* const format, ...) { NOTE_UNUSED(stream); @@ -127,19 +121,15 @@ int,, (void* const stream, const char* const format, ...)) return 0; } -// clang-format off -AUTOHOOK(ConCommand_echo, engine.dll + 0x123680, -void,, (const CCommand& arg)) -// clang-format on +static void (*o_pConCommand_echo)(const CCommand& arg) = nullptr; +static void h_ConCommand_echo(const CCommand& arg) { if (arg.ArgC() >= 2) NS::log::echo->info("{}", arg.ArgS()); } -// clang-format off -AUTOHOOK(EngineSpewFunc, engine.dll + 0x11CA80, -void, __fastcall, (void* pEngineServer, SpewType_t type, const char* format, va_list args)) -// clang-format on +static void(__fastcall* o_pEngineSpewFunc)(void* pEngineServer, SpewType_t type, const char* format, va_list args) = nullptr; +static void __fastcall h_EngineSpewFunc(void* pEngineServer, SpewType_t type, const char* format, va_list args) { NOTE_UNUSED(pEngineServer); if (!Cvar_spewlog_enable->GetBool()) @@ -214,10 +204,8 @@ void, __fastcall, (void* pEngineServer, SpewType_t type, const char* format, va_ } // used for printing the output of status -// clang-format off -AUTOHOOK(Status_ConMsg, engine.dll + 0x15ABD0, -void,, (const char* text, ...)) -// clang-format on +static void (*o_pStatus_ConMsg)(const char* text, ...) = nullptr; +static void h_Status_ConMsg(const char* text, ...) { char formatted[2048]; va_list list; @@ -233,10 +221,8 @@ void,, (const char* text, ...)) spdlog::info(formatted); } -// clang-format off -AUTOHOOK(CClientState_ProcessPrint, engine.dll + 0x1A1530, -bool,, (void* thisptr, uintptr_t msg)) -// clang-format on +static bool (*o_pCClientState_ProcessPrint)(void* thisptr, uintptr_t msg) = nullptr; +static bool h_CClientState_ProcessPrint(void* thisptr, uintptr_t msg) { NOTE_UNUSED(thisptr); @@ -252,14 +238,28 @@ bool,, (void* thisptr, uintptr_t msg)) ON_DLL_LOAD_RELIESON("engine.dll", EngineSpewFuncHooks, ConVar, (CModule module)) { - AUTOHOOK_DISPATCH_MODULE(engine.dll) + o_pfprintf = module.Offset(0x51B1F0).RCast(); + HookAttach(&(PVOID&)o_pfprintf, (PVOID)h_fprintf); + + o_pConCommand_echo = module.Offset(0x123680).RCast(); + HookAttach(&(PVOID&)o_pConCommand_echo, (PVOID)h_ConCommand_echo); + + o_pEngineSpewFunc = module.Offset(0x11CA80).RCast(); + HookAttach(&(PVOID&)o_pEngineSpewFunc, (PVOID)h_EngineSpewFunc); + + o_pStatus_ConMsg = module.Offset(0x15ABD0).RCast(); + HookAttach(&(PVOID&)o_pStatus_ConMsg, (PVOID)h_Status_ConMsg); + + o_pCClientState_ProcessPrint = module.Offset(0x1A1530).RCast(); + HookAttach(&(PVOID&)o_pCClientState_ProcessPrint, (PVOID)h_CClientState_ProcessPrint); Cvar_spewlog_enable = new ConVar("spewlog_enable", "0", FCVAR_NONE, "Enables/disables whether the engine spewfunc should be logged"); } ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientPrintHooks, ConVar, (CModule module)) { - AUTOHOOK_DISPATCH_MODULE(client.dll) + o_pTextMsg = module.Offset(0x198710).RCast(); + HookAttach(&(PVOID&)o_pTextMsg, (PVOID)h_TextMsg); Cvar_cl_showtextmsg = new ConVar("cl_showtextmsg", "1", FCVAR_NONE, "Enable/disable text messages printing on the screen."); pInternalCenterPrint = module.Offset(0x216E940).RCast(); From 2d97883eecb66c5cebd97d9e51fb07c4af07e752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Raes?= Date: Fri, 22 Nov 2024 00:38:58 +0100 Subject: [PATCH 3/6] script: Refactor mod information (#826) Adds struct containing mod information instead of getting every info with its dedicated function --- primedev/scripts/client/scriptmodmenu.cpp | 171 +++++++--------------- 1 file changed, 53 insertions(+), 118 deletions(-) diff --git a/primedev/scripts/client/scriptmodmenu.cpp b/primedev/scripts/client/scriptmodmenu.cpp index 5ffe0fdf7..7a2627c84 100644 --- a/primedev/scripts/client/scriptmodmenu.cpp +++ b/primedev/scripts/client/scriptmodmenu.cpp @@ -1,180 +1,115 @@ #include "mods/modmanager.h" #include "squirrel/squirrel.h" -ADD_SQFUNC("array", NSGetModNames, "", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +template void ModToSquirrel(HSQUIRRELVM sqvm, Mod& mod) { - g_pSquirrel->newarray(sqvm, 0); + g_pSquirrel->pushnewstructinstance(sqvm, 9); - for (Mod& mod : g_pModManager->m_LoadedMods) - { - g_pSquirrel->pushstring(sqvm, mod.Name.c_str()); - g_pSquirrel->arrayappend(sqvm, -2); - } + // name + g_pSquirrel->pushstring(sqvm, mod.Name.c_str(), -1); + g_pSquirrel->sealstructslot(sqvm, 0); - return SQRESULT_NOTNULL; -} + // description + g_pSquirrel->pushstring(sqvm, mod.Description.c_str(), -1); + g_pSquirrel->sealstructslot(sqvm, 1); -ADD_SQFUNC("bool", NSIsModEnabled, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); + // version + g_pSquirrel->pushstring(sqvm, mod.Version.c_str(), -1); + g_pSquirrel->sealstructslot(sqvm, 2); - // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.Name.compare(modName)) - { - g_pSquirrel->pushbool(sqvm, mod.m_bEnabled); - return SQRESULT_NOTNULL; - } - } + // download link + g_pSquirrel->pushstring(sqvm, mod.DownloadLink.c_str(), -1); + g_pSquirrel->sealstructslot(sqvm, 3); - return SQRESULT_NULL; -} + // load priority + g_pSquirrel->pushinteger(sqvm, mod.LoadPriority); + g_pSquirrel->sealstructslot(sqvm, 4); -ADD_SQFUNC("void", NSSetModEnabled, "string modName, bool enabled", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); - const SQBool enabled = g_pSquirrel->getbool(sqvm, 2); + // enabled + g_pSquirrel->pushbool(sqvm, mod.m_bEnabled); + g_pSquirrel->sealstructslot(sqvm, 5); - // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.Name.compare(modName)) - { - mod.m_bEnabled = enabled; - return SQRESULT_NULL; - } - } - - return SQRESULT_NULL; -} - -ADD_SQFUNC("bool", NSIsModRemote, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); + // required on client + g_pSquirrel->pushbool(sqvm, mod.RequiredOnClient); + g_pSquirrel->sealstructslot(sqvm, 6); - // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.Name.compare(modName)) - { - g_pSquirrel->pushbool(sqvm, mod.m_bIsRemote); - return SQRESULT_NOTNULL; - } - } - - return SQRESULT_NULL; -} - -ADD_SQFUNC("string", NSGetModDescriptionByModName, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); + // is remote + g_pSquirrel->pushbool(sqvm, mod.m_bIsRemote); + g_pSquirrel->sealstructslot(sqvm, 7); - // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) + // convars + g_pSquirrel->newarray(sqvm); + for (ModConVar* cvar : mod.ConVars) { - if (!mod.Name.compare(modName)) - { - g_pSquirrel->pushstring(sqvm, mod.Description.c_str()); - return SQRESULT_NOTNULL; - } + g_pSquirrel->pushstring(sqvm, cvar->Name.c_str()); + g_pSquirrel->arrayappend(sqvm, -2); } + g_pSquirrel->sealstructslot(sqvm, 8); - return SQRESULT_NULL; + // add current object to squirrel array + g_pSquirrel->arrayappend(sqvm, -2); } -ADD_SQFUNC("string", NSGetModVersionByModName, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +ADD_SQFUNC("array", NSGetModsInformation, "", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) { - const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); + g_pSquirrel->newarray(sqvm, 0); - // manual lookup, not super performant but eh not a big deal for (Mod& mod : g_pModManager->m_LoadedMods) { - if (!mod.Name.compare(modName)) - { - g_pSquirrel->pushstring(sqvm, mod.Version.c_str()); - return SQRESULT_NOTNULL; - } + ModToSquirrel(sqvm, mod); } - return SQRESULT_NULL; + return SQRESULT_NOTNULL; } -ADD_SQFUNC("string", NSGetModDownloadLinkByModName, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +ADD_SQFUNC("array", NSGetModInformation, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) { const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); + g_pSquirrel->newarray(sqvm, 0); - // manual lookup, not super performant but eh not a big deal for (Mod& mod : g_pModManager->m_LoadedMods) { - if (!mod.Name.compare(modName)) + if (mod.Name.compare(modName) != 0) { - g_pSquirrel->pushstring(sqvm, mod.DownloadLink.c_str()); - return SQRESULT_NOTNULL; + continue; } + ModToSquirrel(sqvm, mod); } - return SQRESULT_NULL; + return SQRESULT_NOTNULL; } -ADD_SQFUNC("int", NSGetModLoadPriority, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +ADD_SQFUNC("array", NSGetModNames, "", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) { - const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); + g_pSquirrel->newarray(sqvm, 0); - // manual lookup, not super performant but eh not a big deal for (Mod& mod : g_pModManager->m_LoadedMods) { - if (!mod.Name.compare(modName)) - { - g_pSquirrel->pushinteger(sqvm, mod.LoadPriority); - return SQRESULT_NOTNULL; - } + g_pSquirrel->pushstring(sqvm, mod.Name.c_str()); + g_pSquirrel->arrayappend(sqvm, -2); } - return SQRESULT_NULL; + return SQRESULT_NOTNULL; } -ADD_SQFUNC("bool", NSIsModRequiredOnClient, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +ADD_SQFUNC("void", NSSetModEnabled, "string modName, bool enabled", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) { const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); + const SQBool enabled = g_pSquirrel->getbool(sqvm, 2); // manual lookup, not super performant but eh not a big deal for (Mod& mod : g_pModManager->m_LoadedMods) { if (!mod.Name.compare(modName)) { - g_pSquirrel->pushbool(sqvm, mod.RequiredOnClient); - return SQRESULT_NOTNULL; + mod.m_bEnabled = enabled; + return SQRESULT_NULL; } } return SQRESULT_NULL; } -ADD_SQFUNC( - "array", NSGetModConvarsByModName, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) -{ - const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); - g_pSquirrel->newarray(sqvm, 0); - - // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) - { - if (!mod.Name.compare(modName)) - { - for (ModConVar* cvar : mod.ConVars) - { - g_pSquirrel->pushstring(sqvm, cvar->Name.c_str()); - g_pSquirrel->arrayappend(sqvm, -2); - } - - return SQRESULT_NOTNULL; - } - } - - return SQRESULT_NOTNULL; // return empty array -} - ADD_SQFUNC("void", NSReloadMods, "", "", ScriptContext::UI) { NOTE_UNUSED(sqvm); From 6585d629ca60e2ce457750f12d40dd9cf742ff8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Raes?= Date: Fri, 22 Nov 2024 15:18:38 +0100 Subject: [PATCH 4/6] mods(MAD): Make MAD process cancellable (#815) Adds logic to make an active mod download via MAD cancellable. --- primedev/mods/autodownload/moddownloader.cpp | 35 ++++++++++++++++++-- primedev/mods/autodownload/moddownloader.h | 1 + 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/primedev/mods/autodownload/moddownloader.cpp b/primedev/mods/autodownload/moddownloader.cpp index c20a3adbc..21b98942a 100644 --- a/primedev/mods/autodownload/moddownloader.cpp +++ b/primedev/mods/autodownload/moddownloader.cpp @@ -156,6 +156,14 @@ int ModDownloader::ModFetchingProgressCallback( { NOTE_UNUSED(totalToUpload); NOTE_UNUSED(nowUploaded); + + // Abort download + ModDownloader* instance = static_cast(ptr); + if (instance->modState.state == ABORTED) + { + return 1; + } + if (totalDownloadSize != 0 && finishedDownloadSize != 0) { ModDownloader* instance = static_cast(ptr); @@ -563,6 +571,13 @@ void ModDownloader::ExtractMod(fs::path modPath, VerifiedModPlatform platform) } } + // Abort mod extraction if needed + if (modState.state == ABORTED) + { + spdlog::info("User cancelled mod installation, aborting mod extraction."); + return; + } + // Go to next file if ((i + 1) < gi.number_entry) { @@ -602,8 +617,7 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion) spdlog::error("Error while removing downloaded archive: {}", a.what()); } - modState.state = DONE; - spdlog::info("Done downloading {}.", modName); + spdlog::info("Done cleaning after downloading {}.", modName); }); // Download mod archive @@ -613,7 +627,10 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion) if (!fetchingResult.has_value()) { spdlog::error("Something went wrong while fetching archive, aborting."); - modState.state = MOD_FETCHING_FAILED; + if (modState.state != ABORTED) + { + modState.state = MOD_FETCHING_FAILED; + } return; } archiveLocation = fetchingResult.value(); @@ -626,11 +643,17 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion) // Extract downloaded mod archive ExtractMod(archiveLocation, fullVersion.platform); + modState.state = DONE; }); requestThread.detach(); } +void ModDownloader::CancelDownload() +{ + modState.state = ABORTED; +} + ON_DLL_LOAD_RELIESON("engine.dll", ModDownloader, (ConCommand), (CModule module)) { g_pModDownloader = new ModDownloader(); @@ -687,3 +710,9 @@ ADD_SQFUNC("ModInstallState", NSGetModInstallState, "", "", ScriptContext::SERVE return SQRESULT_NOTNULL; } + +ADD_SQFUNC("void", NSCancelModDownload, "", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +{ + g_pModDownloader->CancelDownload(); + return SQRESULT_NULL; +} diff --git a/primedev/mods/autodownload/moddownloader.h b/primedev/mods/autodownload/moddownloader.h index c7a88c19a..2ac72a48c 100644 --- a/primedev/mods/autodownload/moddownloader.h +++ b/primedev/mods/autodownload/moddownloader.h @@ -129,6 +129,7 @@ class ModDownloader CHECKSUMING, EXTRACTING, DONE, // Everything went great, mod can be used in-game + ABORTED, // User cancelled mod install process // Errors FAILED, // Generic error message, should be avoided as much as possible From db40260d57a9480b82d850c093aeaf14eefda9fd Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:20:03 +0100 Subject: [PATCH 5/6] mods(Safe I/O): Only allow creating files with whitelisted filetypes (#682) Restricts file types that can be created via Safe I/O to a list of whitelisted file types --- primedev/mods/modsavefiles.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/primedev/mods/modsavefiles.cpp b/primedev/mods/modsavefiles.cpp index 13239c99f..d73b867cd 100644 --- a/primedev/mods/modsavefiles.cpp +++ b/primedev/mods/modsavefiles.cpp @@ -74,6 +74,26 @@ template void SaveFileManager::SaveFileAsync(fs::path fi std::thread writeThread( [mutex, file, contents]() { + // Check if has extension and return early if not + if (!file.has_extension()) + { + spdlog::error("A mod failed to save a file via Safe I/O due to the following error:"); + spdlog::error("No file extension specified"); + return; + } + + // If there's a file extension missing here that you need, feel free to make a PR adding it + static const std::set whitelist = {".txt", ".json"}; + + // Check if file extension is whitelisted + std::string extension = file.extension().string(); + if (whitelist.find(extension) == whitelist.end()) + { + spdlog::error("A mod failed to save a file via Safe I/O due to the following error:"); + spdlog::error("Disallowed file extension: {}", extension); + return; + } + try { mutex.get().lock(); From fad89b3536e0db610a1c93334e4c7f8a5b00ded2 Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:27:48 +0000 Subject: [PATCH 6/6] Send all mods to Atlas that are enabled (#536) Instead of just RequiredOnClient mods and mods that have pdiff The idea here is that it gives clients a better overview of what mods are enabled on the server they are joining. --- primedev/mods/modmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primedev/mods/modmanager.cpp b/primedev/mods/modmanager.cpp index 52fc6e8b1..0c162de0e 100644 --- a/primedev/mods/modmanager.cpp +++ b/primedev/mods/modmanager.cpp @@ -1060,7 +1060,7 @@ void ModManager::LoadMods() int currentModIndex = 0; for (Mod& mod : m_LoadedMods) { - if (!mod.m_bEnabled || (!mod.RequiredOnClient && !mod.Pdiff.size())) + if (!mod.m_bEnabled) continue; modinfoDoc["Mods"].PushBack(rapidjson::kObjectType, modinfoDoc.GetAllocator());