From ab653555f6aa0ec57e1b23a590fb3061929fdc30 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 5 May 2024 18:00:04 +0200 Subject: [PATCH 01/41] Fix plugin with NULL strings causing UB (#695) Instance string members only after checking for null pointers to prevent UB --- primedev/plugins/plugins.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/primedev/plugins/plugins.cpp b/primedev/plugins/plugins.cpp index eddaa8acf..ae6fd0cbf 100644 --- a/primedev/plugins/plugins.cpp +++ b/primedev/plugins/plugins.cpp @@ -69,10 +69,6 @@ Plugin::Plugin(std::string path) : m_location(path) m_runOnServer = context & PluginContext::DEDICATED; m_runOnClient = context & PluginContext::CLIENT; - m_name = std::string(name); - m_logName = std::string(logName); - m_dependencyName = std::string(dependencyName); - if (!name) { NS::log::PLUGINSYS->error("Could not load name of plugin at '{}'", path); @@ -91,6 +87,10 @@ Plugin::Plugin(std::string path) : m_location(path) return; } + m_name = std::string(name); + m_logName = std::string(logName); + m_dependencyName = std::string(dependencyName); + if (!isValidSquirrelIdentifier(m_dependencyName)) { NS::log::PLUGINSYS->error("Dependency name \"{}\" of plugin {} is not valid", dependencyName, name); From 220b7a1bf7915a336b9c2a08806e60c1c32bde8e Mon Sep 17 00:00:00 2001 From: wolf109909 <84360921+wolf109909@users.noreply.github.com> Date: Tue, 21 May 2024 21:50:22 +0800 Subject: [PATCH 02/41] Fix engine error crash caused by language detection hooks (#699) Previous logic would always return `true` --- primedev/client/languagehooks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primedev/client/languagehooks.cpp b/primedev/client/languagehooks.cpp index 35ca5659f..0146d1d4c 100644 --- a/primedev/client/languagehooks.cpp +++ b/primedev/client/languagehooks.cpp @@ -41,7 +41,7 @@ std::vector file_list(fs::path dir, std::regex ext_pattern) std::string GetAnyInstalledAudioLanguage() { for (const auto& lang : file_list("r2\\sound\\", std::regex(".*?general_([a-z]+)_patch_1\\.mstr"))) - if (lang != "general" || lang != "") + if (lang != "general" && lang != "" && lang != "stream") return lang; return "NO LANGUAGE DETECTED"; } From a06319c974342c41294fef601a8a873a3e488bfc Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 14 Jun 2024 10:40:05 +0200 Subject: [PATCH 03/41] Use old Mutex constructor to deal with redist incompatibility (#704) See GitHub PR for detailed explanation --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index ab461ae29..23a8b7f51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,8 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_C_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_VS_PLATFORM_TOOLSET v143) +# Deal with MSVC incompatiblity +add_compile_definitions(_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR) # This determines the real binary root directory set(NS_BINARY_DIR ${CMAKE_BINARY_DIR}/game) From d8d861c3db5e33870c41864c3f16ead17732935a Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 15 Jun 2024 12:39:35 +0200 Subject: [PATCH 04/41] Assign `CreateInterface` member instead of creating a new variable (#705) Assign CreateInterface member instead of creating a new variable. This fixes valid Plugins failing to load. --- primedev/plugins/plugins.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/primedev/plugins/plugins.cpp b/primedev/plugins/plugins.cpp index ae6fd0cbf..03dd2c9e2 100644 --- a/primedev/plugins/plugins.cpp +++ b/primedev/plugins/plugins.cpp @@ -45,15 +45,15 @@ Plugin::Plugin(std::string path) : m_location(path) m_initData = {.pluginHandle = m_handle}; - CreateInterfaceFn CreatePluginInterface = (CreateInterfaceFn)GetProcAddress(m_handle, "CreateInterface"); + m_pCreateInterface = (CreateInterfaceFn)GetProcAddress(m_handle, "CreateInterface"); - if (!CreatePluginInterface) + if (!m_pCreateInterface) { NS::log::PLUGINSYS->error("Plugin at '{}' does not expose CreateInterface()", path); return; } - m_pluginId = (IPluginId*)CreatePluginInterface(PLUGIN_ID_VERSION, 0); + m_pluginId = (IPluginId*)m_pCreateInterface(PLUGIN_ID_VERSION, 0); if (!m_pluginId) { @@ -97,7 +97,7 @@ Plugin::Plugin(std::string path) : m_location(path) return; } - m_callbacks = (IPluginCallbacks*)CreatePluginInterface("PluginCallbacks001", 0); + m_callbacks = (IPluginCallbacks*)m_pCreateInterface("PluginCallbacks001", 0); if (!m_callbacks) { From 742c4a133a7313940da82bda4f3a51e3b84be594 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 16 Jun 2024 13:06:37 +0200 Subject: [PATCH 05/41] Remove duplicate wsock32 export (#714) --- primedev/wsockproxy/wsock32.def | 1 - 1 file changed, 1 deletion(-) diff --git a/primedev/wsockproxy/wsock32.def b/primedev/wsockproxy/wsock32.def index 448440b48..187b29594 100644 --- a/primedev/wsockproxy/wsock32.def +++ b/primedev/wsockproxy/wsock32.def @@ -69,7 +69,6 @@ EXPORTS rresvport=ws2_32.rresvport s_perror=PROXY_s_perror select=ws2_32.select @18 - select=ws2_32.select @18 send=ws2_32.send @19 sendto=ws2_32.sendto @20 sethostname=ws2_32.sethostname From 4a716e54f7a33ed5c2cf8bc2eb508fa1b2f4991a Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 16 Jun 2024 13:11:51 +0200 Subject: [PATCH 06/41] Check if hook target is non `null` before calling MinHook (#711) --- primedev/core/hooks.cpp | 4 +++- primedev/core/hooks.h | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/primedev/core/hooks.cpp b/primedev/core/hooks.cpp index 20f0cbef1..6e8492914 100644 --- a/primedev/core/hooks.cpp +++ b/primedev/core/hooks.cpp @@ -104,7 +104,9 @@ bool ManualHook::Dispatch(LPVOID addr, LPVOID* orig) if (orig) ppOrigFunc = orig; - if (MH_CreateHook(addr, pHookFunc, ppOrigFunc) == MH_OK) + if (!addr) + spdlog::error("Address for hook {} is invalid", pFuncName); + else if (MH_CreateHook(addr, pHookFunc, ppOrigFunc) == MH_OK) { if (MH_EnableHook(addr) == MH_OK) { diff --git a/primedev/core/hooks.h b/primedev/core/hooks.h index f842afbb9..e5a653549 100644 --- a/primedev/core/hooks.h +++ b/primedev/core/hooks.h @@ -201,7 +201,9 @@ class __autohook } } - if (MH_CreateHook(targetAddr, pHookFunc, ppOrigFunc) == MH_OK) + if (!targetAddr) + spdlog::error("Address for hook {} is invalid", pFuncName); + else if (MH_CreateHook(targetAddr, pHookFunc, ppOrigFunc) == MH_OK) { if (MH_EnableHook(targetAddr) == MH_OK) spdlog::info("Enabling hook {}", pFuncName); From f75aff142508796ba66cc43c3135cbb317c920db Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 16 Jun 2024 13:14:24 +0200 Subject: [PATCH 07/41] Use double brackets to let compiler know assignment is intentional (#707) Use double brackets to let compiler know that assignment inside `if` statement is intentional and not the cause of a missing second `=`. --- primedev/client/latencyflex.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/primedev/client/latencyflex.cpp b/primedev/client/latencyflex.cpp index 25e38c7a4..395578709 100644 --- a/primedev/client/latencyflex.cpp +++ b/primedev/client/latencyflex.cpp @@ -24,10 +24,10 @@ ON_DLL_LOAD_CLIENT_RELIESON("client.dll", LatencyFlex, ConVar, (CModule module)) // https://ishitatsuyuki.github.io/post/latencyflex/ HMODULE pLfxModule; - if (pLfxModule = LoadLibraryA("latencyflex_layer.dll")) + if ((pLfxModule = LoadLibraryA("latencyflex_layer.dll"))) m_winelfx_WaitAndBeginFrame = reinterpret_cast(reinterpret_cast(GetProcAddress(pLfxModule, "lfx_WaitAndBeginFrame"))); - else if (pLfxModule = LoadLibraryA("latencyflex_wine.dll")) + else if ((pLfxModule = LoadLibraryA("latencyflex_wine.dll"))) m_winelfx_WaitAndBeginFrame = reinterpret_cast(reinterpret_cast(GetProcAddress(pLfxModule, "winelfx_WaitAndBeginFrame"))); else From 75bb4143ced983be7d76f154fc0016d17fc92b65 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 16 Jun 2024 17:57:52 +0200 Subject: [PATCH 08/41] Add default case for SquirrelContext switch (#716) Add default case for SquirrelContext switch and logs errors so we can catch them if they do ever happen. --- primedev/mods/modmanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/primedev/mods/modmanager.cpp b/primedev/mods/modmanager.cpp index 268a65a56..98d4cdc42 100644 --- a/primedev/mods/modmanager.cpp +++ b/primedev/mods/modmanager.cpp @@ -601,6 +601,8 @@ auto ModConCommandCallback(const CCommand& command) case ScriptContext::UI: ModConCommandCallback_Internal(found->Function, command); break; + default: + spdlog::error("ModConCommandCallback on invalid Context {}", found->Context); }; } From ec974f60cbfa3814c5d654a0f77f0b27a6cc5d94 Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 17 Jun 2024 14:35:59 +0200 Subject: [PATCH 09/41] Don't instantiate templates before declaration, add missing instantiation (#706) In C++ explicit template instantiations need to be done AFTER template declarations are made. --- primedev/squirrel/squirrel.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/primedev/squirrel/squirrel.cpp b/primedev/squirrel/squirrel.cpp index 41a6a782f..f6df0c5f3 100644 --- a/primedev/squirrel/squirrel.cpp +++ b/primedev/squirrel/squirrel.cpp @@ -157,11 +157,6 @@ const char* SQTypeNameFromID(int type) return ""; } -// needed to define implementations for squirrelmanager outside of squirrel.h without compiler errors -template class SquirrelManager; -template class SquirrelManager; -template class SquirrelManager; - template void SquirrelManager::VMCreated(CSquirrelVM* newSqvm) { m_pSQVM = newSqvm; @@ -845,3 +840,12 @@ void InitialiseSquirrelManagers() g_pSquirrel = new SquirrelManager; g_pSquirrel = new SquirrelManager; } + +// needed to define implementations for squirrelmanager outside of squirrel.h without compiler errors +template class SquirrelManager; +template class SquirrelManager; +template class SquirrelManager; + +template std::shared_ptr NS::log::squirrel_logger(); +template std::shared_ptr NS::log::squirrel_logger(); +template std::shared_ptr NS::log::squirrel_logger(); From b02e6097c471b8bf196a2fce4eb22e4b0bae4e11 Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:32:40 +0200 Subject: [PATCH 10/41] Update `actions/checkout` to v4 (#724) v3 is outdated and will soon be deprecated Co-authored-by: Jan --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6048a57d..20870278b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: runs-on: windows-2022 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'true' - name: Setup msvc @@ -40,7 +40,7 @@ jobs: format-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: DoozyX/clang-format-lint-action@v0.16.2 with: source: 'primedev' @@ -52,7 +52,7 @@ jobs: format-check-cmake-files: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: puneetmatharu/cmake-format-lint-action@v1.0.4 with: args: "--in-place" From 9a60c23811b9e78ae2ef86cdf009f4fdffd896e4 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 19 Jun 2024 13:44:34 +0200 Subject: [PATCH 11/41] Add clang CI (#721) Builds Northstar with the Microsoft provided clang-cl build next to MSVC Release builds are unchanged and still only built with MSVC --- .github/workflows/ci.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20870278b..413ecb9ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,14 @@ env: jobs: build: runs-on: windows-2022 + strategy: + matrix: + config: + - { name: "MSVC", cc: cl } + - { name: "LLVM", cc: clang-cl } + env: + CC: ${{ matrix.config.cc }} + CXX: ${{ matrix.config.cc }} steps: - name: Checkout uses: actions/checkout@v4 @@ -33,7 +41,7 @@ jobs: - name: Upload Build Artifact uses: actions/upload-artifact@v3 with: - name: NorthstarLauncher-${{ steps.extract.outputs.commit }} + name: NorthstarLauncher-${{ matrix.config.name }}-${{ steps.extract.outputs.commit }} path: | game/ From 7f3fe6c9b4170eee20e3c8002769a6528da10b62 Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:55:53 +0200 Subject: [PATCH 12/41] Update `actions/upload-artifact` to v4 (#725) v3 is outdated and will soon be deprecated Co-authored-by: Jan --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 413ecb9ff..e562dc3bf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: shell: bash run: echo commit=$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT - name: Upload Build Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: NorthstarLauncher-${{ matrix.config.name }}-${{ steps.extract.outputs.commit }} path: | From 8e56367314315c53c8c7bede25e5900da9c9e31c Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 19 Jun 2024 14:40:23 +0200 Subject: [PATCH 13/41] Add missing case for switch cases in JSON deserialization function (#712) RapidJSON switch gets a new case for null, where we do nothing. Future enum values would result in a new warning. --- primedev/scripts/scriptjson.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/primedev/scripts/scriptjson.cpp b/primedev/scripts/scriptjson.cpp index 06bda6f42..8959bf471 100644 --- a/primedev/scripts/scriptjson.cpp +++ b/primedev/scripts/scriptjson.cpp @@ -42,6 +42,8 @@ DecodeJsonArray(HSquirrelVM* sqvm, rapidjson::GenericValue g_pSquirrel->pushinteger(sqvm, itr.GetInt()); g_pSquirrel->arrayappend(sqvm, -2); break; + case rapidjson::kNullType: + break; } } } @@ -92,6 +94,8 @@ DecodeJsonTable(HSquirrelVM* sqvm, rapidjson::GenericValue } g_pSquirrel->newslot(sqvm, -3, false); break; + case rapidjson::kNullType: + break; } } } From 26fffcfdbaa23def1d2ed0a15e53ed7907883083 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 20 Jun 2024 16:42:17 +0200 Subject: [PATCH 14/41] Remove invalid memset in ServerPresence (#709) The use of sizeof is incorrect here since `this` is a pointer and sizeof is used on the pointer directly, instead of what the pointer points to. It seems to work without issue due to padding but could cause issues if an instanced class like `std::string` gets nulled. --- primedev/server/serverpresence.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/primedev/server/serverpresence.h b/primedev/server/serverpresence.h index 94ecfe6a0..07c6fb551 100644 --- a/primedev/server/serverpresence.h +++ b/primedev/server/serverpresence.h @@ -19,10 +19,7 @@ struct ServerPresence int m_iPlayerCount; int m_iMaxPlayers; - ServerPresence() - { - memset(this, 0, sizeof(this)); - } + ServerPresence() {} ServerPresence(const ServerPresence* obj) { From f1a990575eb87787c77eacb99be29171e4b3098d Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 20 Jun 2024 16:44:01 +0200 Subject: [PATCH 15/41] Lowercase all linked libraries for building on Linux (#726) Microsoft, in their infinite wisdom, decided to suffix some libraries with `.Lib` instead of `.lib` This causes issues with cmake on Linux because it only looks for `.lib` which it won't be able to resolve because the file system is case sensitive. Microsoft does this for backwards compatibility, in cmake this is a limitation so the best solution is to bite the bullet and lowercase all libraries which setups such as wine-msvc and xwin already do. --- primedev/Launcher.cmake | 2 +- primedev/Northstar.cmake | 12 ++++++------ primedev/wsockproxy/CMakeLists.txt | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/primedev/Launcher.cmake b/primedev/Launcher.cmake index c04fc70b1..d23c7c4b5 100644 --- a/primedev/Launcher.cmake +++ b/primedev/Launcher.cmake @@ -19,7 +19,7 @@ target_link_libraries( uuid.lib odbc32.lib odbccp32.lib - WS2_32.lib + ws2_32.lib ) set_target_properties( diff --git a/primedev/Northstar.cmake b/primedev/Northstar.cmake index aef630c80..40583d283 100644 --- a/primedev/Northstar.cmake +++ b/primedev/Northstar.cmake @@ -172,13 +172,13 @@ target_link_libraries( libcurl minizip silver-bun - WS2_32.lib - Crypt32.lib - Cryptui.lib + ws2_32.lib + crypt32.lib + cryptui.lib dbghelp.lib - Wldap32.lib - Normaliz.lib - Bcrypt.lib + wldap32.lib + normaliz.lib + bcrypt.lib version.lib ) diff --git a/primedev/wsockproxy/CMakeLists.txt b/primedev/wsockproxy/CMakeLists.txt index 0dbac745d..b1d03ce7d 100644 --- a/primedev/wsockproxy/CMakeLists.txt +++ b/primedev/wsockproxy/CMakeLists.txt @@ -15,7 +15,7 @@ target_link_libraries( PRIVATE minhook mswsock.lib ws2_32.lib - ShLwApi.lib + shlwapi.lib imagehlp.lib dbghelp.lib kernel32.lib From 874afd95ff1bf88b602241486e39b3ecfaadd758 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 20 Jun 2024 16:47:05 +0200 Subject: [PATCH 16/41] Remove useless `this` pointer check (#710) The standard states that this must always be a valid pointer so these checks are optimized out anyway. Sane compilers, such as clang, also complain about this and state that this is pointless. --- primedev/shared/keyvalues.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/primedev/shared/keyvalues.cpp b/primedev/shared/keyvalues.cpp index 36f891eb8..46ce4e04d 100644 --- a/primedev/shared/keyvalues.cpp +++ b/primedev/shared/keyvalues.cpp @@ -511,7 +511,7 @@ bool KeyValues::IsEmpty(const char* pszKeyName) KeyValues* KeyValues::GetFirstTrueSubKey(void) const { assert_msg(this, "Member function called on NULL KeyValues"); - KeyValues* pRet = this ? m_pSub : nullptr; + KeyValues* pRet = m_pSub; while (pRet && pRet->m_iDataType != TYPE_NONE) pRet = pRet->m_pPeer; @@ -525,7 +525,7 @@ KeyValues* KeyValues::GetFirstTrueSubKey(void) const KeyValues* KeyValues::GetNextTrueSubKey(void) const { assert_msg(this, "Member function called on NULL KeyValues"); - KeyValues* pRet = this ? m_pPeer : nullptr; + KeyValues* pRet = m_pPeer; while (pRet && pRet->m_iDataType != TYPE_NONE) pRet = pRet->m_pPeer; @@ -539,7 +539,7 @@ KeyValues* KeyValues::GetNextTrueSubKey(void) const KeyValues* KeyValues::GetFirstValue(void) const { assert_msg(this, "Member function called on NULL KeyValues"); - KeyValues* pRet = this ? m_pSub : nullptr; + KeyValues* pRet = m_pSub; while (pRet && pRet->m_iDataType == TYPE_NONE) pRet = pRet->m_pPeer; @@ -553,7 +553,7 @@ KeyValues* KeyValues::GetFirstValue(void) const KeyValues* KeyValues::GetNextValue(void) const { assert_msg(this, "Member function called on NULL KeyValues"); - KeyValues* pRet = this ? m_pPeer : nullptr; + KeyValues* pRet = m_pPeer; while (pRet && pRet->m_iDataType == TYPE_NONE) pRet = pRet->m_pPeer; @@ -566,7 +566,7 @@ KeyValues* KeyValues::GetNextValue(void) const KeyValues* KeyValues::GetFirstSubKey() const { assert_msg(this, "Member function called on NULL KeyValues"); - return this ? m_pSub : nullptr; + return m_pSub; } //----------------------------------------------------------------------------- @@ -575,7 +575,7 @@ KeyValues* KeyValues::GetFirstSubKey() const KeyValues* KeyValues::GetNextKey() const { assert_msg(this, "Member function called on NULL KeyValues"); - return this ? m_pPeer : nullptr; + return m_pPeer; } //----------------------------------------------------------------------------- From 3c876448164e8d611d8b3c14cff481712b5391a8 Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 22 Jun 2024 11:59:46 +0200 Subject: [PATCH 17/41] Add overrides to `custom_sink_it_` methods (#708) the compiler knows we want to override here, since the original `custom_sink_it_` is virtual but we should be explicit to prevent any mistakes. --- primedev/dedicated/dedicatedlogtoclient.h | 2 +- primedev/logging/sourceconsole.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/primedev/dedicated/dedicatedlogtoclient.h b/primedev/dedicated/dedicatedlogtoclient.h index 82f4c56b4..5678982e6 100644 --- a/primedev/dedicated/dedicatedlogtoclient.h +++ b/primedev/dedicated/dedicatedlogtoclient.h @@ -5,7 +5,7 @@ class DedicatedServerLogToClientSink : public CustomSink { protected: - void custom_sink_it_(const custom_log_msg& msg); + void custom_sink_it_(const custom_log_msg& msg) override; void sink_it_(const spdlog::details::log_msg& msg) override; void flush_() override; }; diff --git a/primedev/logging/sourceconsole.h b/primedev/logging/sourceconsole.h index 44d738434..215dae1a3 100644 --- a/primedev/logging/sourceconsole.h +++ b/primedev/logging/sourceconsole.h @@ -77,7 +77,7 @@ class SourceConsoleSink : public CustomSink {spdlog::level::off, NS::Colors::OFF.ToSourceColor()}}; protected: - void custom_sink_it_(const custom_log_msg& msg); + void custom_sink_it_(const custom_log_msg& msg) override; void sink_it_(const spdlog::details::log_msg& msg) override; void flush_() override; }; From 225eb1849a7b4ef019c04e4aa12b85295e301c28 Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 22 Jun 2024 12:03:04 +0200 Subject: [PATCH 18/41] Use static MSVC Runtime Library (#718) I think its best if we use the static runtime to remove more dynamic dependencies that we can't anticipate. --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 23a8b7f51..a9646ae10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,9 @@ cmake_minimum_required(VERSION 3.15) +cmake_policy( + SET + CMP0091 + NEW + ) project( Northstar @@ -20,6 +25,7 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_C_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_VS_PLATFORM_TOOLSET v143) +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded") # Deal with MSVC incompatiblity add_compile_definitions(_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR) From 1c9f39ddde4076b7a0f0b0527d89fc69b47d0602 Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 22 Jun 2024 20:46:43 +0200 Subject: [PATCH 19/41] Fix memalloc problems (#728) alloc declarations to work with CRT implement missing _recalloc_base and _msize --- primedev/core/memalloc.cpp | 36 +++++++++++++++++++++++++++++++----- primedev/core/memalloc.h | 16 ++++++++++------ 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/primedev/core/memalloc.cpp b/primedev/core/memalloc.cpp index 0a75bc2bd..511677179 100644 --- a/primedev/core/memalloc.cpp +++ b/primedev/core/memalloc.cpp @@ -3,7 +3,7 @@ // TODO: rename to malloc and free after removing statically compiled .libs -extern "C" void* _malloc_base(size_t n) +void* _malloc_base(size_t n) { // allocate into static buffer if g_pMemAllocSingleton isn't initialised if (!g_pMemAllocSingleton) @@ -17,7 +17,7 @@ extern "C" void* _malloc_base(size_t n) return _malloc_base(n); }*/ -extern "C" void _free_base(void* p) +void _free_base(void* p) { if (!g_pMemAllocSingleton) TryCreateGlobalMemAlloc(); @@ -25,7 +25,7 @@ extern "C" void _free_base(void* p) g_pMemAllocSingleton->m_vtable->Free(g_pMemAllocSingleton, p); } -extern "C" void* _realloc_base(void* oldPtr, size_t size) +void* _realloc_base(void* oldPtr, size_t size) { if (!g_pMemAllocSingleton) TryCreateGlobalMemAlloc(); @@ -33,7 +33,7 @@ extern "C" void* _realloc_base(void* oldPtr, size_t size) return g_pMemAllocSingleton->m_vtable->Realloc(g_pMemAllocSingleton, oldPtr, size); } -extern "C" void* _calloc_base(size_t n, size_t size) +void* _calloc_base(size_t n, size_t size) { size_t bytes = n * size; void* memory = _malloc_base(bytes); @@ -44,7 +44,33 @@ extern "C" void* _calloc_base(size_t n, size_t size) return memory; } -extern "C" char* _strdup_base(const char* src) +void* _recalloc_base(void* const block, size_t const count, size_t const size) +{ + if (!block) + return _calloc_base(count, size); + + const size_t new_size = count * size; + const size_t old_size = _msize(block); + + void* const memory = _realloc_base(block, new_size); + + if (memory && old_size < new_size) + { + memset(static_cast(memory) + old_size, 0, new_size - old_size); + } + + return memory; +} + +size_t _msize(void* const block) +{ + if (!g_pMemAllocSingleton) + TryCreateGlobalMemAlloc(); + + return g_pMemAllocSingleton->m_vtable->GetSize(g_pMemAllocSingleton, block); +} + +char* _strdup_base(const char* src) { char* str; char* p; diff --git a/primedev/core/memalloc.h b/primedev/core/memalloc.h index 2f3833350..73e078f5f 100644 --- a/primedev/core/memalloc.h +++ b/primedev/core/memalloc.h @@ -1,14 +1,18 @@ #pragma once +#include + #include "rapidjson/document.h" // #include "include/rapidjson/allocators.h" -extern "C" void* _malloc_base(size_t size); -extern "C" void* _calloc_base(size_t const count, size_t const size); -extern "C" void* _realloc_base(void* block, size_t size); -extern "C" void* _recalloc_base(void* const block, size_t const count, size_t const size); -extern "C" void _free_base(void* const block); -extern "C" char* _strdup_base(const char* src); +// The prelude is needed for these to be usable by the CRT +extern "C" __declspec(noinline) void* __cdecl _malloc_base(size_t const size); +extern "C" __declspec(noinline) void* __cdecl _calloc_base(size_t const count, size_t const size); +extern "C" __declspec(noinline) void* __cdecl _realloc_base(void* const block, size_t const size); +extern "C" __declspec(noinline) void* __cdecl _recalloc_base(void* const block, size_t const count, size_t const size); +extern "C" __declspec(noinline) void __cdecl _free_base(void* const block); +extern "C" __declspec(noinline) size_t __cdecl _msize(void* const block); +extern "C" __declspec(noinline) char* __cdecl _strdup_base(const char* src); void* operator new(size_t n); void operator delete(void* p) noexcept; From c07ebd8728f89b0db621da6a2c512b33345df444 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 23 Jun 2024 01:34:30 +0200 Subject: [PATCH 20/41] Restructure primelauncher cmake logic (#727) Move primelauncher cmake logic into its subdirectory allowing everything related to it to be self contained --- primedev/CMakeLists.txt | 2 +- primedev/{Launcher.cmake => primelauncher/CMakeLists.txt} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename primedev/{Launcher.cmake => primelauncher/CMakeLists.txt} (88%) diff --git a/primedev/CMakeLists.txt b/primedev/CMakeLists.txt index 31dda4b21..a33fd351c 100644 --- a/primedev/CMakeLists.txt +++ b/primedev/CMakeLists.txt @@ -1,3 +1,3 @@ include(Northstar.cmake) -include(Launcher.cmake) +add_subdirectory(primelauncher) add_subdirectory(wsockproxy) diff --git a/primedev/Launcher.cmake b/primedev/primelauncher/CMakeLists.txt similarity index 88% rename from primedev/Launcher.cmake rename to primedev/primelauncher/CMakeLists.txt index d23c7c4b5..f36781a93 100644 --- a/primedev/Launcher.cmake +++ b/primedev/primelauncher/CMakeLists.txt @@ -1,6 +1,6 @@ # NorthstarLauncher -add_executable(NorthstarLauncher "primelauncher/main.cpp" "primelauncher/resources.rc") +add_executable(NorthstarLauncher "main.cpp" "resources.rc") target_compile_definitions(NorthstarLauncher PRIVATE UNICODE _UNICODE) From 4ada6a354e9480af1cfbf8a751e27dac11aa7f9c Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 23 Jun 2024 01:38:58 +0200 Subject: [PATCH 21/41] Fix const-qualified assignment in RapidJSON (#717) pulls in https://github.com/Tencent/rapidjson/pull/719 resolves https://github.com/Tencent/rapidjson/issues/2277 --- primedev/thirdparty/rapidjson/document.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/primedev/thirdparty/rapidjson/document.h b/primedev/thirdparty/rapidjson/document.h index 22fb2f562..a5465a3cb 100644 --- a/primedev/thirdparty/rapidjson/document.h +++ b/primedev/thirdparty/rapidjson/document.h @@ -318,8 +318,6 @@ struct GenericStringRef { GenericStringRef(const GenericStringRef& rhs) : s(rhs.s), length(rhs.length) {} - GenericStringRef& operator=(const GenericStringRef& rhs) { s = rhs.s; length = rhs.length; } - //! implicit conversion to plain CharType pointer operator const Ch *() const { return s; } @@ -330,6 +328,8 @@ struct GenericStringRef { //! Disallow construction from non-const array template GenericStringRef(CharType (&str)[N]) /* = delete */; + //! Copy assignment operator not permitted - immutable type + GenericStringRef& operator=(const GenericStringRef& rhs) /* = delete */; }; //! Mark a character pointer as constant string From d52aaadc9ab0bd80ccd2a64818e64934adeefbf4 Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 24 Jun 2024 14:38:56 +0200 Subject: [PATCH 22/41] Add missing object check in mod manager (#731) `HasMember` asserts `IsObject()` internally. In release builds this is not an issue but this does not work on Debug builds. --- 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 98d4cdc42..edf69c9fb 100644 --- a/primedev/mods/modmanager.cpp +++ b/primedev/mods/modmanager.cpp @@ -748,7 +748,7 @@ void ModManager::LoadMods() continue; // Add mod entry to enabledmods.json if it doesn't exist - if (!mod.m_bIsRemote && !m_EnabledModsCfg.HasMember(mod.Name.c_str())) + if (!mod.m_bIsRemote && m_bHasEnabledModsCfg && !m_EnabledModsCfg.HasMember(mod.Name.c_str())) { m_EnabledModsCfg.AddMember(rapidjson_document::StringRefType(mod.Name.c_str()), true, m_EnabledModsCfg.GetAllocator()); newModsDetected = true; From f29823a0d3d934c90569e668785cd8965df7ffa4 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 28 Jun 2024 00:37:26 +0200 Subject: [PATCH 23/41] Check for Console Window before allocating one (#741) Check for Console Window before allocating one and remove "console already exists" from error message, we know it doesn't --- primedev/logging/logging.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/primedev/logging/logging.cpp b/primedev/logging/logging.cpp index 72171e254..4367b384b 100644 --- a/primedev/logging/logging.cpp +++ b/primedev/logging/logging.cpp @@ -124,9 +124,9 @@ void CustomSink::custom_log(const custom_log_msg& msg) void InitialiseConsole() { - if (AllocConsole() == FALSE) + if (GetConsoleWindow() == NULL && AllocConsole() == FALSE) { - std::cout << "[*] Failed to create a console window, maybe a console already exists?" << std::endl; + std::cout << "[*] Failed to create a console window" << std::endl; } else { From 4d8798e5ccc01d10bb7109a3ca19f4f5b54471c6 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 28 Jun 2024 00:39:03 +0200 Subject: [PATCH 24/41] Remove EXPORT macro from precompiled header (#738) as its used once and does not warrant being in the precompiled header --- primedev/pch.h | 2 -- primedev/plugins/interfaces/interface.h | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/primedev/pch.h b/primedev/pch.h index 577f803c4..0b2d44060 100644 --- a/primedev/pch.h +++ b/primedev/pch.h @@ -20,8 +20,6 @@ namespace fs = std::filesystem; -#define EXPORT extern "C" __declspec(dllexport) - typedef void (*callable)(); typedef void (*callable_v)(void* v); diff --git a/primedev/plugins/interfaces/interface.h b/primedev/plugins/interfaces/interface.h index 440db5b22..c4f8b6aef 100644 --- a/primedev/plugins/interfaces/interface.h +++ b/primedev/plugins/interfaces/interface.h @@ -34,6 +34,6 @@ class InterfaceReg static className __g_##className##_singleton; \ EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, __g_##className##_singleton) -EXPORT void* CreateInterface(const char* pName, InterfaceStatus* pReturnCode); +extern "C" __declspec(dllexport) void* CreateInterface(const char* pName, InterfaceStatus* pReturnCode); #endif From 778e338335f4323892b469971c057efc2845d822 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 28 Jun 2024 00:44:59 +0200 Subject: [PATCH 25/41] Remove precompiled header from wsock proxy (#739) The wsock proxy is so small and self contained that it really does not benefit from a precompiled header. --- primedev/wsockproxy/CMakeLists.txt | 9 ++++----- primedev/wsockproxy/dllmain.cpp | 1 + primedev/wsockproxy/loader.cpp | 2 ++ primedev/wsockproxy/loader.h | 2 ++ primedev/wsockproxy/pch.h | 16 ---------------- 5 files changed, 9 insertions(+), 21 deletions(-) delete mode 100644 primedev/wsockproxy/pch.h diff --git a/primedev/wsockproxy/CMakeLists.txt b/primedev/wsockproxy/CMakeLists.txt index b1d03ce7d..3f6bce454 100644 --- a/primedev/wsockproxy/CMakeLists.txt +++ b/primedev/wsockproxy/CMakeLists.txt @@ -32,14 +32,13 @@ target_link_libraries( odbccp32.lib ) -target_precompile_headers( +target_compile_definitions( loader_wsock32_proxy - PRIVATE - pch.h + PRIVATE UNICODE + _UNICODE + WIN32_LEAN_AND_MEAN ) -target_compile_definitions(loader_wsock32_proxy PRIVATE UNICODE _UNICODE) - set_target_properties( loader_wsock32_proxy PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${NS_BINARY_DIR}/bin/x64_retail diff --git a/primedev/wsockproxy/dllmain.cpp b/primedev/wsockproxy/dllmain.cpp index 5a606e45d..9716c1b84 100644 --- a/primedev/wsockproxy/dllmain.cpp +++ b/primedev/wsockproxy/dllmain.cpp @@ -1,5 +1,6 @@ #include "loader.h" +#include #include FARPROC p[73]; diff --git a/primedev/wsockproxy/loader.cpp b/primedev/wsockproxy/loader.cpp index a3abf11c9..4664c20c3 100644 --- a/primedev/wsockproxy/loader.cpp +++ b/primedev/wsockproxy/loader.cpp @@ -7,6 +7,8 @@ #include #include +#include "MinHook.h" + namespace fs = std::filesystem; static wchar_t northstarPath[8192]; diff --git a/primedev/wsockproxy/loader.h b/primedev/wsockproxy/loader.h index 0c6fb0538..6287e5153 100644 --- a/primedev/wsockproxy/loader.h +++ b/primedev/wsockproxy/loader.h @@ -1,5 +1,7 @@ #pragma once +#include + extern wchar_t exePath[4096]; extern wchar_t buffer1[8192]; extern wchar_t buffer2[12288]; diff --git a/primedev/wsockproxy/pch.h b/primedev/wsockproxy/pch.h deleted file mode 100644 index ebc295470..000000000 --- a/primedev/wsockproxy/pch.h +++ /dev/null @@ -1,16 +0,0 @@ -// pch.h: This is a precompiled header file. -// Files listed below are compiled only once, improving build performance for future builds. -// This also affects IntelliSense performance, including code completion and many code browsing features. -// However, files listed here are ALL re-compiled if any one of them is updated between builds. -// Do not add files here that you will be updating frequently as this negates the performance advantage. - -#ifndef PCH_H -#define PCH_H - -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers -// Windows Header Files -#include - -#include "MinHook.h" - -#endif // PCH_H From fc087d804464cc6cb12498e171248186eb7b7c26 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 28 Jun 2024 01:01:28 +0200 Subject: [PATCH 26/41] Remove unused typedefs from precompiled header (#737) --- primedev/pch.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/primedev/pch.h b/primedev/pch.h index 0b2d44060..bfd255976 100644 --- a/primedev/pch.h +++ b/primedev/pch.h @@ -20,9 +20,6 @@ namespace fs = std::filesystem; -typedef void (*callable)(); -typedef void (*callable_v)(void* v); - // clang-format off #define assert_msg(exp, msg) assert((exp, msg)) //clang-format on From 193ab4905664259cbb5035b0ec9b2cb3e0e6a994 Mon Sep 17 00:00:00 2001 From: BobTheBob <32057864+BobTheBob9@users.noreply.github.com> Date: Thu, 4 Jul 2024 21:53:38 +0100 Subject: [PATCH 27/41] Properly handle invalid cvar replications without blocking netmessage (#408) Properly handle invalid cvar replications without blocking netmessage entirely and restore `ns_server_name` replication --- primedev/server/serverpresence.cpp | 4 +-- .../shared/exploit_fixes/exploitfixes.cpp | 36 ++++++++++++------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/primedev/server/serverpresence.cpp b/primedev/server/serverpresence.cpp index 509243f0d..099f6e648 100644 --- a/primedev/server/serverpresence.cpp +++ b/primedev/server/serverpresence.cpp @@ -78,7 +78,7 @@ void ServerPresenceManager::CreateConVars() Cvar_ns_server_presence_update_rate = new ConVar( "ns_server_presence_update_rate", "5000", FCVAR_GAMEDLL, "How often we update our server's presence on server lists in ms"); - Cvar_ns_server_name = new ConVar("ns_server_name", "Unnamed Northstar Server", FCVAR_GAMEDLL, "This server's name", false, 0, false, 0, [](ConVar* cvar, const char* pOldValue, float flOldValue) { + Cvar_ns_server_name = new ConVar("ns_server_name", "Unnamed Northstar Server", FCVAR_GAMEDLL | FCVAR_REPLICATED, "This server's name", false, 0, false, 0, [](ConVar* cvar, const char* pOldValue, float flOldValue) { NOTE_UNUSED(cvar); NOTE_UNUSED(pOldValue); NOTE_UNUSED(flOldValue); @@ -88,7 +88,7 @@ void ServerPresenceManager::CreateConVars() Cvar_hostname->SetValue(g_pServerPresence->Cvar_ns_server_name->GetString()); }); - Cvar_ns_server_desc = new ConVar("ns_server_desc", "Default server description", FCVAR_GAMEDLL, "This server's description", false, 0, false, 0, [](ConVar* cvar, const char* pOldValue, float flOldValue) { + Cvar_ns_server_desc = new ConVar("ns_server_desc", "Default server description", FCVAR_GAMEDLL | FCVAR_REPLICATED, "This server's description", false, 0, false, 0, [](ConVar* cvar, const char* pOldValue, float flOldValue) { NOTE_UNUSED(cvar); NOTE_UNUSED(pOldValue); NOTE_UNUSED(flOldValue); diff --git a/primedev/shared/exploit_fixes/exploitfixes.cpp b/primedev/shared/exploit_fixes/exploitfixes.cpp index d96bc41ec..1b3069f5a 100644 --- a/primedev/shared/exploit_fixes/exploitfixes.cpp +++ b/primedev/shared/exploit_fixes/exploitfixes.cpp @@ -120,19 +120,31 @@ bool, __fastcall, (void* pMsg)) // 48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10 if (!nameValid || !valValid) return BLOCKED_INFO("Missing null terminators"); - ConVar* pVar = g_pCVar->FindVar(entry->name); - - if (pVar) + // we only need to check if these cvars are valid on client as it will set actual cvars there + // on server this won't set any actual convars, only keyvalues in the player, which doesn't have really any potential for dumb + // stuff + if (!bIsServerFrame) { - memcpy( - entry->name, - pVar->m_ConCommandBase.m_pszName, - strlen(pVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case - - int iFlags = bIsServerFrame ? FCVAR_USERINFO : FCVAR_REPLICATED; - if (!pVar->IsFlagSet(iFlags)) - return BLOCKED_INFO( - "Invalid flags (" << std::hex << "0x" << pVar->m_ConCommandBase.m_nFlags << "), var is " << entry->name); + ConVar* pVar = g_pCVar->FindVar(entry->name); + if (pVar) + { + memcpy( + entry->name, + pVar->m_ConCommandBase.m_pszName, + strlen(pVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case + + if (!pVar->IsFlagSet(FCVAR_REPLICATED)) + { + spdlog::warn( + "Blocking replication of remote cvar {} from server (server's var has flag REPLICATED, while ours does not)", + entry->name); + + // don't block, as non-malicious servers might send bad cvars, and we still want those clients to be able to + // connect + memset(entry->name, 0, ENTRY_STR_LEN); + memset(entry->val, 0, ENTRY_STR_LEN); + } + } } } else From afa246978001db279d3e0eabbbe6b32f5c42e1f4 Mon Sep 17 00:00:00 2001 From: EladNLG Date: Fri, 5 Jul 2024 00:25:07 +0300 Subject: [PATCH 28/41] Make Script Errors from Northstar Callbacks Fatal (#698) Because: - Errors are incredibly confusing when non-fatal - there is no indicator that an error is happening - The error is unknown without a try/catch block - Errors cannot be tracked to a line, or file, and its location has to be figured out manually - They can throw a game into an unexpected state with no indicator, confusing both players AND developers --- primedev/squirrel/squirrel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primedev/squirrel/squirrel.h b/primedev/squirrel/squirrel.h index 0c1f24d36..1054de7f9 100644 --- a/primedev/squirrel/squirrel.h +++ b/primedev/squirrel/squirrel.h @@ -136,7 +136,7 @@ class SquirrelManagerBase inline SQRESULT _call(HSquirrelVM* sqvm, const SQInteger args) { - return __sq_call(sqvm, args + 1, false, false); + return __sq_call(sqvm, args + 1, false, true); } inline SQInteger raiseerror(HSquirrelVM* sqvm, const SQChar* sError) From 3edcc91c72c96c33f8eae76a7078f78126bbea28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Raes?= Date: Thu, 4 Jul 2024 23:34:16 +0200 Subject: [PATCH 29/41] Expose mods remote status to Squirrel VM (#684) Add a Squirrel VM method to know if a given mod is remote or not. --- primedev/scripts/client/scriptmodmenu.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/primedev/scripts/client/scriptmodmenu.cpp b/primedev/scripts/client/scriptmodmenu.cpp index 2e877db48..5ffe0fdf7 100644 --- a/primedev/scripts/client/scriptmodmenu.cpp +++ b/primedev/scripts/client/scriptmodmenu.cpp @@ -49,6 +49,23 @@ ADD_SQFUNC("void", NSSetModEnabled, "string modName, bool enabled", "", ScriptCo return SQRESULT_NULL; } +ADD_SQFUNC("bool", NSIsModRemote, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +{ + const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); + + // 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); From 497945bbbd18b4ff9cd264dc6a9d6cf8ba6bf08e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Raes?= Date: Sat, 6 Jul 2024 12:40:36 +0200 Subject: [PATCH 30/41] Fix multiple audio file overrides (#677) This basically prevents audio files from being loaded into memory if matching audio event has already been overriden by a previous mod, preventing a crash from occurring. This means that audio mods now respect the load priority, i.e. mods with higher priority (= lower int value) will have priority over other mods on audio overrides. --- primedev/client/audio.cpp | 31 ++++++++++++++++++++++++++++--- primedev/client/audio.h | 4 ++-- primedev/mods/modmanager.cpp | 2 +- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/primedev/client/audio.cpp b/primedev/client/audio.cpp index 099fdcee5..e5989f5c8 100644 --- a/primedev/client/audio.cpp +++ b/primedev/client/audio.cpp @@ -7,6 +7,7 @@ #include #include #include +#include AUTOHOOK_INIT() @@ -28,7 +29,7 @@ unsigned char EMPTY_WAVE[45] = {0x52, 0x49, 0x46, 0x46, 0x25, 0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x44, 0xAC, 0x00, 0x00, 0x88, 0x58, 0x01, 0x00, 0x02, 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x74, 0x00, 0x00, 0x00, 0x00}; -EventOverrideData::EventOverrideData(const std::string& data, const fs::path& path) +EventOverrideData::EventOverrideData(const std::string& data, const fs::path& path, const std::vector& registeredEvents) { if (data.length() <= 0) { @@ -191,6 +192,14 @@ EventOverrideData::EventOverrideData(const std::string& data, const fs::path& pa { std::string pathString = file.path().string(); + // Retrieve event id from path (standard?) + std::string eventId = file.path().parent_path().filename().string(); + if (std::find(registeredEvents.begin(), registeredEvents.end(), eventId) != registeredEvents.end()) + { + spdlog::warn("{} couldn't be loaded because {} event has already been overrided, skipping.", pathString, eventId); + continue; + } + // Open the file. std::ifstream wavStream(pathString, std::ios::binary); @@ -259,7 +268,7 @@ EventOverrideData::EventOverrideData(const std::string& data, const fs::path& pa LoadedSuccessfully = true; } -bool CustomAudioManager::TryLoadAudioOverride(const fs::path& defPath) +bool CustomAudioManager::TryLoadAudioOverride(const fs::path& defPath, std::string modName) { if (IsDedicatedServer()) return true; // silently fail @@ -279,19 +288,35 @@ bool CustomAudioManager::TryLoadAudioOverride(const fs::path& defPath) jsonStream.close(); - std::shared_ptr data = std::make_shared(jsonStringStream.str(), defPath); + // Pass the list of overriden events to avoid multiple event registrations crash + auto kv = std::views::keys(m_loadedAudioOverrides); + std::vector keys {kv.begin(), kv.end()}; + std::shared_ptr data = std::make_shared(jsonStringStream.str(), defPath, keys); if (!data->LoadedSuccessfully) return false; // no logging, the constructor has probably already logged for (const std::string& eventId : data->EventIds) { + if (m_loadedAudioOverrides.contains(eventId)) + { + spdlog::warn("\"{}\" mod tried to override sound event \"{}\" but it is already overriden, skipping.", modName, eventId); + continue; + } spdlog::info("Registering sound event {}", eventId); m_loadedAudioOverrides.insert({eventId, data}); } for (const auto& eventIdRegexData : data->EventIdsRegex) { + if (m_loadedAudioOverridesRegex.contains(eventIdRegexData.first)) + { + spdlog::warn( + "\"{}\" mod tried to override sound event regex \"{}\" but it is already overriden, skipping.", + modName, + eventIdRegexData.first); + continue; + } spdlog::info("Registering sound event regex {}", eventIdRegexData.first); m_loadedAudioOverridesRegex.insert({eventIdRegexData.first, data}); } diff --git a/primedev/client/audio.h b/primedev/client/audio.h index 15fd1a351..7cd0ddd1c 100644 --- a/primedev/client/audio.h +++ b/primedev/client/audio.h @@ -15,7 +15,7 @@ enum class AudioSelectionStrategy class EventOverrideData { public: - EventOverrideData(const std::string&, const fs::path&); + EventOverrideData(const std::string&, const fs::path&, const std::vector& registeredEvents); EventOverrideData(); public: @@ -35,7 +35,7 @@ class EventOverrideData class CustomAudioManager { public: - bool TryLoadAudioOverride(const fs::path&); + bool TryLoadAudioOverride(const fs::path&, std::string modName); void ClearAudioOverrides(); std::shared_mutex m_loadingMutex; diff --git a/primedev/mods/modmanager.cpp b/primedev/mods/modmanager.cpp index edf69c9fb..68f9bd0f9 100644 --- a/primedev/mods/modmanager.cpp +++ b/primedev/mods/modmanager.cpp @@ -972,7 +972,7 @@ void ModManager::LoadMods() { if (fs::is_regular_file(file) && file.path().extension().string() == ".json") { - if (!g_CustomAudioManager.TryLoadAudioOverride(file.path())) + if (!g_CustomAudioManager.TryLoadAudioOverride(file.path(), mod.Name)) { spdlog::warn("Mod {} has an invalid audio def {}", mod.Name, file.path().filename().string()); continue; From 52b11022997c834efd2b50234e1e664603806503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Raes?= Date: Wed, 10 Jul 2024 01:21:40 +0200 Subject: [PATCH 31/41] fix: Do not crash on unknown MAD manifesto format (#749) Verify JSON has attributes before trying to access them --- primedev/mods/autodownload/moddownloader.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/primedev/mods/autodownload/moddownloader.cpp b/primedev/mods/autodownload/moddownloader.cpp index 3a9462630..feaa08ea9 100644 --- a/primedev/mods/autodownload/moddownloader.cpp +++ b/primedev/mods/autodownload/moddownloader.cpp @@ -92,6 +92,13 @@ void ModDownloader::FetchModsListFromAPI() verifiedModsJson.Parse(readBuffer); for (auto i = verifiedModsJson.MemberBegin(); i != verifiedModsJson.MemberEnd(); ++i) { + // Format testing + if (!i->value.HasMember("DependencyPrefix") || !i->value.HasMember("Versions")) + { + spdlog::warn("Verified mods manifesto format is unrecognized, skipping loading."); + return; + } + std::string name = i->name.GetString(); std::string dependency = i->value["DependencyPrefix"].GetString(); @@ -101,6 +108,13 @@ void ModDownloader::FetchModsListFromAPI() for (auto& attribute : versions.GetArray()) { assert(attribute.IsObject()); + // Format testing + if (!attribute.HasMember("Version") || !attribute.HasMember("Checksum")) + { + spdlog::warn("Verified mods manifesto format is unrecognized, skipping loading."); + return; + } + std::string version = attribute["Version"].GetString(); std::string checksum = attribute["Checksum"].GetString(); modVersions.insert({version, {.checksum = checksum}}); From acdfefdc335d5c7817c52986103d0ef2ce522ec9 Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:13:31 +0200 Subject: [PATCH 32/41] Fix typo in class member function (#750) `pushSQObject` does not exist Co-authored-by: EladNLG --- primedev/squirrel/squirrel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primedev/squirrel/squirrel.h b/primedev/squirrel/squirrel.h index 1054de7f9..1225f9098 100644 --- a/primedev/squirrel/squirrel.h +++ b/primedev/squirrel/squirrel.h @@ -447,7 +447,7 @@ inline VoidFunction SQMessageBufferPushArg(Vector3& arg) { // Vectors template inline VoidFunction SQMessageBufferPushArg(SQObject* arg) { - return [arg]{ g_pSquirrel->pushSQObject(g_pSquirrel->m_pSQVM->sqvm, arg); }; + return [arg]{ g_pSquirrel->pushobject(g_pSquirrel->m_pSQVM->sqvm, arg); }; } // Ints template From 2cd78297274e1173d64f8f6e78383bfd6da88c7e Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:13:52 +0200 Subject: [PATCH 33/41] Define `fs` alias in source file where used (#747) for filesystem namespace instead of relying on implicit include Co-authored-by: Jan --- primedev/client/audio.cpp | 2 ++ primedev/client/audio.h | 2 ++ primedev/client/languagehooks.cpp | 2 ++ primedev/core/hooks.cpp | 2 ++ primedev/mods/autodownload/moddownloader.h | 2 ++ primedev/mods/modmanager.h | 2 ++ primedev/plugins/pluginmanager.cpp | 2 ++ primedev/plugins/pluginmanager.h | 2 ++ primedev/server/buildainfile.cpp | 2 ++ primedev/squirrel/squirrel.h | 2 ++ 10 files changed, 20 insertions(+) diff --git a/primedev/client/audio.cpp b/primedev/client/audio.cpp index e5989f5c8..635014141 100644 --- a/primedev/client/audio.cpp +++ b/primedev/client/audio.cpp @@ -9,6 +9,8 @@ #include #include +namespace fs = std::filesystem; + AUTOHOOK_INIT() static const char* pszAudioEventName; diff --git a/primedev/client/audio.h b/primedev/client/audio.h index 7cd0ddd1c..22bcf3f08 100644 --- a/primedev/client/audio.h +++ b/primedev/client/audio.h @@ -5,6 +5,8 @@ #include #include +namespace fs = std::filesystem; + enum class AudioSelectionStrategy { INVALID = -1, diff --git a/primedev/client/languagehooks.cpp b/primedev/client/languagehooks.cpp index 0146d1d4c..36b5d5aed 100644 --- a/primedev/client/languagehooks.cpp +++ b/primedev/client/languagehooks.cpp @@ -3,6 +3,8 @@ #include #include +namespace fs = std::filesystem; + AUTOHOOK_INIT() typedef LANGID (*Tier0_DetectDefaultLanguageType)(); diff --git a/primedev/core/hooks.cpp b/primedev/core/hooks.cpp index 6e8492914..fef8bbcf1 100644 --- a/primedev/core/hooks.cpp +++ b/primedev/core/hooks.cpp @@ -12,6 +12,8 @@ #define XINPUT1_3_DLL "XInput1_3.dll" +namespace fs = std::filesystem; + AUTOHOOK_INIT() // called from the ON_DLL_LOAD macros diff --git a/primedev/mods/autodownload/moddownloader.h b/primedev/mods/autodownload/moddownloader.h index 10df39ceb..0d851e2fe 100644 --- a/primedev/mods/autodownload/moddownloader.h +++ b/primedev/mods/autodownload/moddownloader.h @@ -1,3 +1,5 @@ +namespace fs = std::filesystem; + class ModDownloader { private: diff --git a/primedev/mods/modmanager.h b/primedev/mods/modmanager.h index 233f004d4..95a8fe121 100644 --- a/primedev/mods/modmanager.h +++ b/primedev/mods/modmanager.h @@ -9,6 +9,8 @@ #include #include +namespace fs = std::filesystem; + const std::string MOD_FOLDER_SUFFIX = "\\mods"; const std::string THUNDERSTORE_MOD_FOLDER_SUFFIX = "\\packages"; const std::string REMOTE_MOD_FOLDER_SUFFIX = "\\runtime\\remote\\mods"; diff --git a/primedev/plugins/pluginmanager.cpp b/primedev/plugins/pluginmanager.cpp index 718e69568..14d5692b3 100644 --- a/primedev/plugins/pluginmanager.cpp +++ b/primedev/plugins/pluginmanager.cpp @@ -5,6 +5,8 @@ #include "config/profile.h" #include "core/convar/concommand.h" +namespace fs = std::filesystem; + PluginManager* g_pPluginManager; const std::vector& PluginManager::GetLoadedPlugins() const diff --git a/primedev/plugins/pluginmanager.h b/primedev/plugins/pluginmanager.h index 8c0218510..7993cbb86 100644 --- a/primedev/plugins/pluginmanager.h +++ b/primedev/plugins/pluginmanager.h @@ -3,6 +3,8 @@ #include +namespace fs = std::filesystem; + class Plugin; class PluginManager diff --git a/primedev/server/buildainfile.cpp b/primedev/server/buildainfile.cpp index a7f599618..19a6d0e36 100644 --- a/primedev/server/buildainfile.cpp +++ b/primedev/server/buildainfile.cpp @@ -5,6 +5,8 @@ #include #include +namespace fs = std::filesystem; + AUTOHOOK_INIT() const int AINET_VERSION_NUMBER = 57; diff --git a/primedev/squirrel/squirrel.h b/primedev/squirrel/squirrel.h index 1225f9098..d747bce39 100644 --- a/primedev/squirrel/squirrel.h +++ b/primedev/squirrel/squirrel.h @@ -5,6 +5,8 @@ #include "core/math/vector.h" #include "mods/modmanager.h" +namespace fs = std::filesystem; + /* definitions from hell required to function From c4055830dd03ab3df11071bdf1f1f6410c79e055 Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Sun, 28 Jul 2024 13:14:09 +0100 Subject: [PATCH 34/41] Fix logging sometimes not working (#754) Revert #741 and remove log message since it's not a big deal --- primedev/logging/logging.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/primedev/logging/logging.cpp b/primedev/logging/logging.cpp index 4367b384b..6d71eea0a 100644 --- a/primedev/logging/logging.cpp +++ b/primedev/logging/logging.cpp @@ -124,11 +124,7 @@ void CustomSink::custom_log(const custom_log_msg& msg) void InitialiseConsole() { - if (GetConsoleWindow() == NULL && AllocConsole() == FALSE) - { - std::cout << "[*] Failed to create a console window" << std::endl; - } - else + if (AllocConsole() != FALSE) { freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); From 65516328610c136db00b644d06bdcf98912ddf70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Raes?= Date: Tue, 30 Jul 2024 00:40:59 +0200 Subject: [PATCH 35/41] Only fetch MAD manifesto on server join (#751) Previously, the verified mods manifesto was fetched on game start without checking if the verified mod feature is enabled Squirrel-side; with this, the manifesto is only fetched when the user wants to download a mod (meaning they enabled the feature beforehand). --- primedev/mods/autodownload/moddownloader.cpp | 17 ++++++++++++++++- primedev/mods/autodownload/moddownloader.h | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/primedev/mods/autodownload/moddownloader.cpp b/primedev/mods/autodownload/moddownloader.cpp index feaa08ea9..8e533decb 100644 --- a/primedev/mods/autodownload/moddownloader.cpp +++ b/primedev/mods/autodownload/moddownloader.cpp @@ -55,6 +55,8 @@ size_t WriteToString(void* ptr, size_t size, size_t count, void* stream) void ModDownloader::FetchModsListFromAPI() { + modState.state = MANIFESTO_FETCHING; + std::thread requestThread( [this]() { @@ -63,6 +65,9 @@ void ModDownloader::FetchModsListFromAPI() rapidjson::Document verifiedModsJson; std::string url = modsListUrl; + // Empty verified mods manifesto + verifiedMods = {}; + curl_global_init(CURL_GLOBAL_ALL); easyhandle = curl_easy_init(); std::string readBuffer; @@ -75,7 +80,12 @@ void ModDownloader::FetchModsListFromAPI() curl_easy_setopt(easyhandle, CURLOPT_WRITEDATA, &readBuffer); curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION, WriteToString); result = curl_easy_perform(easyhandle); - ScopeGuard cleanup([&] { curl_easy_cleanup(easyhandle); }); + ScopeGuard cleanup( + [&] + { + curl_easy_cleanup(easyhandle); + modState.state = DOWNLOADING; + }); if (result == CURLcode::CURLE_OK) { @@ -614,7 +624,12 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion) ON_DLL_LOAD_RELIESON("engine.dll", ModDownloader, (ConCommand), (CModule module)) { g_pModDownloader = new ModDownloader(); +} + +ADD_SQFUNC("void", NSFetchVerifiedModsManifesto, "", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +{ g_pModDownloader->FetchModsListFromAPI(); + return SQRESULT_NULL; } ADD_SQFUNC( diff --git a/primedev/mods/autodownload/moddownloader.h b/primedev/mods/autodownload/moddownloader.h index 0d851e2fe..98fc27ae3 100644 --- a/primedev/mods/autodownload/moddownloader.h +++ b/primedev/mods/autodownload/moddownloader.h @@ -116,6 +116,8 @@ class ModDownloader enum ModInstallState { + MANIFESTO_FETCHING, + // Normal installation process DOWNLOADING, CHECKSUMING, From 51991cc45f8557dbeedbf6de176285e51a77676f Mon Sep 17 00:00:00 2001 From: EladNLG Date: Tue, 30 Jul 2024 01:52:15 +0300 Subject: [PATCH 36/41] Add CreateScriptInstance (#692) Adds a function that converts entities to SQObjects to the SquirrelManagers. --- primedev/squirrel/squirrel.cpp | 2 ++ primedev/squirrel/squirrel.h | 1 + primedev/squirrel/squirrelclasstypes.h | 1 + 3 files changed, 4 insertions(+) diff --git a/primedev/squirrel/squirrel.cpp b/primedev/squirrel/squirrel.cpp index f6df0c5f3..29540ccea 100644 --- a/primedev/squirrel/squirrel.cpp +++ b/primedev/squirrel/squirrel.cpp @@ -707,6 +707,7 @@ ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrel, ConCommand, (CModule module)) g_pSquirrel->__sq_GetEntityConstant_CBaseEntity = module.Offset(0x3E49B0).RCast(); g_pSquirrel->__sq_getentityfrominstance = module.Offset(0x114F0).RCast(); + g_pSquirrel->__sq_createscriptinstance = module.Offset(0xC20E0).RCast(); g_pSquirrel->__sq_GetEntityConstant_CBaseEntity = g_pSquirrel->__sq_GetEntityConstant_CBaseEntity; g_pSquirrel->__sq_getentityfrominstance = g_pSquirrel->__sq_getentityfrominstance; @@ -800,6 +801,7 @@ ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module)) g_pSquirrel->__sq_GetEntityConstant_CBaseEntity = module.Offset(0x418AF0).RCast(); g_pSquirrel->__sq_getentityfrominstance = module.Offset(0x1E920).RCast(); + g_pSquirrel->__sq_createscriptinstance = module.Offset(0x43F2F0).RCast(); g_pSquirrel->logger = NS::log::SCRIPT_SV; // Message buffer stuff diff --git a/primedev/squirrel/squirrel.h b/primedev/squirrel/squirrel.h index d747bce39..547b1efcd 100644 --- a/primedev/squirrel/squirrel.h +++ b/primedev/squirrel/squirrel.h @@ -117,6 +117,7 @@ class SquirrelManagerBase sq_getfunctionType __sq_getfunction; sq_getentityfrominstanceType __sq_getentityfrominstance; + sq_createscriptinstanceType __sq_createscriptinstance; sq_GetEntityConstantType __sq_GetEntityConstant_CBaseEntity; sq_pushnewstructinstanceType __sq_pushnewstructinstance; diff --git a/primedev/squirrel/squirrelclasstypes.h b/primedev/squirrel/squirrelclasstypes.h index 91c3c4683..3a39c957e 100644 --- a/primedev/squirrel/squirrelclasstypes.h +++ b/primedev/squirrel/squirrelclasstypes.h @@ -227,6 +227,7 @@ typedef SQRESULT (*sq_setuserdatatypeidType)(HSquirrelVM* sqvm, SQInteger iStack // sq misc entity funcs typedef void* (*sq_getentityfrominstanceType)(CSquirrelVM* sqvm, SQObject* pInstance, char** ppEntityConstant); +typedef SQObject* (*sq_createscriptinstanceType)(void* ent); typedef char** (*sq_GetEntityConstantType)(); typedef int (*sq_getfunctionType)(HSquirrelVM* sqvm, const char* name, SQObject* returnObj, const char* signature); From 9715c89b1ccccb8ca58e6bf3cc37fdfcc0e31f5f Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:04:52 +0200 Subject: [PATCH 37/41] Add comment explaining time choice for cron job (#759) --- .github/workflows/merge-conflict-auto-label.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge-conflict-auto-label.yml b/.github/workflows/merge-conflict-auto-label.yml index cf5598a6e..40d481198 100644 --- a/.github/workflows/merge-conflict-auto-label.yml +++ b/.github/workflows/merge-conflict-auto-label.yml @@ -4,7 +4,7 @@ on: branches: - main schedule: - - cron: "10 21 * * *" + - cron: "10 21 * * *" # Runs at 21:10; time was chosen based on contributor activity and low GitHub Actions cron load. jobs: triage: From a176b707faa24547e1f00a1a7dcdeb92811d7fe7 Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:13:46 +0200 Subject: [PATCH 38/41] Add option to manually run merge conflict label action (#760) so that labels can easily be updated if need be --- .github/workflows/merge-conflict-auto-label.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/merge-conflict-auto-label.yml b/.github/workflows/merge-conflict-auto-label.yml index 40d481198..abb7cabde 100644 --- a/.github/workflows/merge-conflict-auto-label.yml +++ b/.github/workflows/merge-conflict-auto-label.yml @@ -1,5 +1,6 @@ name: Merge Conflict Auto Label on: + workflow_dispatch: # Manual run push: branches: - main From a28c1cb10cc805907d22b656ccc300b39b720a88 Mon Sep 17 00:00:00 2001 From: FourthVolt <87427011+EM4Volts@users.noreply.github.com> Date: Fri, 9 Aug 2024 20:28:26 +0200 Subject: [PATCH 39/41] Fix postload crash from missing `rpak.json` (#762) Missing `rpak.json` would previously crash the game due to faulty boolean logic in the `if` statement. This change ensures that a missing `rpak.json` is handled gracefully. --- primedev/mods/modmanager.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/primedev/mods/modmanager.cpp b/primedev/mods/modmanager.cpp index 68f9bd0f9..45eddd3ee 100644 --- a/primedev/mods/modmanager.cpp +++ b/primedev/mods/modmanager.cpp @@ -866,16 +866,24 @@ void ModManager::LoadMods() if (fs::is_regular_file(file) && file.path().extension() == ".rpak") { std::string pakName(file.path().filename().string()); - ModRpakEntry& modPak = mod.Rpaks.emplace_back(); - modPak.m_bAutoLoad = - !bUseRpakJson || (dRpakJson.HasMember("Preload") && dRpakJson["Preload"].IsObject() && - dRpakJson["Preload"].HasMember(pakName) && dRpakJson["Preload"][pakName].IsTrue()); - - // postload things - if (!bUseRpakJson || - (dRpakJson.HasMember("Postload") && dRpakJson["Postload"].IsObject() && dRpakJson["Postload"].HasMember(pakName))) - modPak.m_sLoadAfterPak = dRpakJson["Postload"][pakName].GetString(); + + if (!bUseRpakJson) + { + spdlog::warn("Mod {} contains rpaks without valid rpak.json, rpaks might not be loaded", mod.Name); + } + else + { + modPak.m_bAutoLoad = + (dRpakJson.HasMember("Preload") && dRpakJson["Preload"].IsObject() && dRpakJson["Preload"].HasMember(pakName) && + dRpakJson["Preload"][pakName].IsTrue()); + + // postload things + if (dRpakJson.HasMember("Postload") && dRpakJson["Postload"].IsObject() && dRpakJson["Postload"].HasMember(pakName)) + { + modPak.m_sLoadAfterPak = dRpakJson["Postload"][pakName].GetString(); + } + } modPak.m_sPakName = pakName; From 5c730b0bb3ffcb5f6da9b15e05f54f778d86c23f Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Sun, 18 Aug 2024 11:21:06 +0100 Subject: [PATCH 40/41] Refactor DLL callbacks (#665) Cherry-picked from primedev and slightly modified Co-authored-by: F1F7Y --- primedev/Northstar.cmake | 2 + primedev/core/hooks.cpp | 85 ++----------------------- primedev/core/hooks.h | 28 +++++++- primedev/dllmain.cpp | 8 ++- primedev/windows/libsys.cpp | 123 ++++++++++++++++++++++++++++++++++++ primedev/windows/libsys.h | 3 + 6 files changed, 166 insertions(+), 83 deletions(-) create mode 100644 primedev/windows/libsys.cpp create mode 100644 primedev/windows/libsys.h diff --git a/primedev/Northstar.cmake b/primedev/Northstar.cmake index 40583d283..9e9d1ed60 100644 --- a/primedev/Northstar.cmake +++ b/primedev/Northstar.cmake @@ -161,6 +161,8 @@ add_library( "util/version.h" "util/wininfo.cpp" "util/wininfo.h" + "windows/libsys.cpp" + "windows/libsys.h" "dllmain.cpp" "ns_version.h" "Northstar.def" diff --git a/primedev/core/hooks.cpp b/primedev/core/hooks.cpp index fef8bbcf1..5026f837b 100644 --- a/primedev/core/hooks.cpp +++ b/primedev/core/hooks.cpp @@ -10,8 +10,6 @@ #include #include -#define XINPUT1_3_DLL "XInput1_3.dll" - namespace fs = std::filesystem; AUTOHOOK_INIT() @@ -392,87 +390,12 @@ void CallAllPendingDLLLoadCallbacks() } } -// clang-format off -AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExA, (LPVOID)LoadLibraryExA, -HMODULE, WINAPI, (LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)) -// clang-format on -{ - HMODULE moduleAddress; - - LPCSTR lpLibFileNameEnd = lpLibFileName + strlen(lpLibFileName); - LPCSTR lpLibName = lpLibFileNameEnd - strlen(XINPUT1_3_DLL); - - // replace xinput dll with one that has ASLR - if (lpLibFileName <= lpLibName && !strncmp(lpLibName, XINPUT1_3_DLL, strlen(XINPUT1_3_DLL) + 1)) - { - moduleAddress = _LoadLibraryExA("XInput9_1_0.dll", hFile, dwFlags); - - if (!moduleAddress) - { - MessageBoxA(0, "Could not find XInput9_1_0.dll", "Northstar", MB_ICONERROR); - exit(EXIT_FAILURE); - - return nullptr; - } - } - else - moduleAddress = _LoadLibraryExA(lpLibFileName, hFile, dwFlags); - - if (moduleAddress) - { - CallLoadLibraryACallbacks(lpLibFileName, moduleAddress); - g_pPluginManager->InformDllLoad(moduleAddress, fs::path(lpLibFileName)); - } - - return moduleAddress; -} - -// clang-format off -AUTOHOOK_ABSOLUTEADDR(_LoadLibraryA, (LPVOID)LoadLibraryA, -HMODULE, WINAPI, (LPCSTR lpLibFileName)) -// clang-format on -{ - HMODULE moduleAddress = _LoadLibraryA(lpLibFileName); - - if (moduleAddress) - CallLoadLibraryACallbacks(lpLibFileName, moduleAddress); - - return moduleAddress; -} - -// clang-format off -AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExW, (LPVOID)LoadLibraryExW, -HMODULE, WINAPI, (LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)) -// clang-format on -{ - HMODULE moduleAddress = _LoadLibraryExW(lpLibFileName, hFile, dwFlags); - - if (moduleAddress) - CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress); - - return moduleAddress; -} - -// clang-format off -AUTOHOOK_ABSOLUTEADDR(_LoadLibraryW, (LPVOID)LoadLibraryW, -HMODULE, WINAPI, (LPCWSTR lpLibFileName)) -// clang-format on -{ - HMODULE moduleAddress = _LoadLibraryW(lpLibFileName); - - if (moduleAddress) - { - CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress); - g_pPluginManager->InformDllLoad(moduleAddress, fs::path(lpLibFileName)); - } - - return moduleAddress; -} - -void InstallInitialHooks() +void HookSys_Init() { if (MH_Initialize() != MH_OK) + { spdlog::error("MH_Initialize (minhook initialization) failed"); - + } + // todo: remove remaining instances of autohook in this file AUTOHOOK_DISPATCH() } diff --git a/primedev/core/hooks.h b/primedev/core/hooks.h index e5a653549..facf51bf6 100644 --- a/primedev/core/hooks.h +++ b/primedev/core/hooks.h @@ -3,7 +3,33 @@ #include #include -void InstallInitialHooks(); +//----------------------------------------------------------------------------- +// Purpose: Init minhook +//----------------------------------------------------------------------------- +void HookSys_Init(); + +//----------------------------------------------------------------------------- +// Purpose: MH_MakeHook wrapper +// Input : *ppOriginal - Original function being detoured +// pDetour - Detour function +//----------------------------------------------------------------------------- +inline void HookAttach(PVOID* ppOriginal, PVOID pDetour) +{ + PVOID pAddr = *ppOriginal; + if (MH_CreateHook(pAddr, pDetour, ppOriginal) == MH_OK) + { + if (MH_EnableHook(pAddr) != MH_OK) + { + spdlog::error("Failed enabling a function hook!"); + } + } + else + { + spdlog::error("Failed creating a function hook!"); + } +} + +void CallLoadLibraryACallbacks(LPCSTR lpLibFileName, HMODULE moduleAddress); typedef void (*DllLoadCallbackFuncType)(CModule moduleAddress); void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector reliesOn = {}); diff --git a/primedev/dllmain.cpp b/primedev/dllmain.cpp index 1191307fb..95ea103f5 100644 --- a/primedev/dllmain.cpp +++ b/primedev/dllmain.cpp @@ -10,6 +10,8 @@ #include "squirrel/squirrel.h" #include "server/serverpresence.h" +#include "windows/libsys.h" + #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" @@ -64,7 +66,11 @@ bool InitialiseNorthstar() // Write launcher version to log StartupLog(); - InstallInitialHooks(); + // Init minhook + HookSys_Init(); + + // Init loadlibrary callbacks + LibSys_Init(); g_pServerPresence = new ServerPresenceManager(); diff --git a/primedev/windows/libsys.cpp b/primedev/windows/libsys.cpp new file mode 100644 index 000000000..cda104351 --- /dev/null +++ b/primedev/windows/libsys.cpp @@ -0,0 +1,123 @@ +#include "libsys.h" +#include "plugins/pluginmanager.h" + +#define XINPUT1_3_DLL "XInput1_3.dll" + +typedef HMODULE (*WINAPI ILoadLibraryA)(LPCSTR lpLibFileName); +typedef HMODULE (*WINAPI ILoadLibraryExA)(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); +typedef HMODULE (*WINAPI ILoadLibraryW)(LPCWSTR lpLibFileName); +typedef HMODULE (*WINAPI ILoadLibraryExW)(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); + +ILoadLibraryA o_LoadLibraryA = nullptr; +ILoadLibraryExA o_LoadLibraryExA = nullptr; +ILoadLibraryW o_LoadLibraryW = nullptr; +ILoadLibraryExW o_LoadLibraryExW = nullptr; + +//----------------------------------------------------------------------------- +// Purpose: Run detour callbacks for given HMODULE +//----------------------------------------------------------------------------- +void LibSys_RunModuleCallbacks(HMODULE hModule) +{ + if (!hModule) + { + return; + } + + // Get module base name in ASCII as noone wants to deal with unicode + CHAR szModuleName[MAX_PATH]; + GetModuleBaseNameA(GetCurrentProcess(), hModule, szModuleName, MAX_PATH); + + // DevMsg(eLog::NONE, "%s\n", szModuleName); + + // Call callbacks + CallLoadLibraryACallbacks(szModuleName, hModule); + g_pPluginManager->InformDllLoad(hModule, fs::path(szModuleName)); +} + +//----------------------------------------------------------------------------- +// Load library callbacks + +HMODULE WINAPI WLoadLibraryA(LPCSTR lpLibFileName) +{ + HMODULE hModule = o_LoadLibraryA(lpLibFileName); + + LibSys_RunModuleCallbacks(hModule); + + return hModule; +} + +HMODULE WINAPI WLoadLibraryExA(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) +{ + HMODULE hModule; + + LPCSTR lpLibFileNameEnd = lpLibFileName + strlen(lpLibFileName); + LPCSTR lpLibName = lpLibFileNameEnd - strlen(XINPUT1_3_DLL); + + // replace xinput dll with one that has ASLR + if (lpLibFileName <= lpLibName && !strncmp(lpLibName, XINPUT1_3_DLL, strlen(XINPUT1_3_DLL) + 1)) + { + hModule = o_LoadLibraryExA("XInput9_1_0.dll", hFile, dwFlags); + + if (!hModule) + { + MessageBoxA(0, "Could not find XInput9_1_0.dll", "Northstar", MB_ICONERROR); + exit(EXIT_FAILURE); + + return nullptr; + } + } + else + { + hModule = o_LoadLibraryExA(lpLibFileName, hFile, dwFlags); + } + + bool bShouldRunCallbacks = + !(dwFlags & (LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE | LOAD_LIBRARY_AS_IMAGE_RESOURCE)); + if (bShouldRunCallbacks) + { + LibSys_RunModuleCallbacks(hModule); + } + + return hModule; +} + +HMODULE WINAPI WLoadLibraryW(LPCWSTR lpLibFileName) +{ + HMODULE hModule = o_LoadLibraryW(lpLibFileName); + + LibSys_RunModuleCallbacks(hModule); + + return hModule; +} + +HMODULE WINAPI WLoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) +{ + HMODULE hModule = o_LoadLibraryExW(lpLibFileName, hFile, dwFlags); + + bool bShouldRunCallbacks = + !(dwFlags & (LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE | LOAD_LIBRARY_AS_IMAGE_RESOURCE)); + if (bShouldRunCallbacks) + { + LibSys_RunModuleCallbacks(hModule); + } + + return hModule; +} + +//----------------------------------------------------------------------------- +// Purpose: Initilase dll load callbacks +//----------------------------------------------------------------------------- +void LibSys_Init() +{ + HMODULE hKernel = GetModuleHandleA("Kernel32.dll"); + + o_LoadLibraryA = reinterpret_cast(GetProcAddress(hKernel, "LoadLibraryA")); + o_LoadLibraryExA = reinterpret_cast(GetProcAddress(hKernel, "LoadLibraryExA")); + o_LoadLibraryW = reinterpret_cast(GetProcAddress(hKernel, "LoadLibraryW")); + o_LoadLibraryExW = reinterpret_cast(GetProcAddress(hKernel, "LoadLibraryExW")); + + HookAttach(&(PVOID&)o_LoadLibraryA, (PVOID)WLoadLibraryA); + HookAttach(&(PVOID&)o_LoadLibraryExA, (PVOID)WLoadLibraryExA); + HookAttach(&(PVOID&)o_LoadLibraryW, (PVOID)WLoadLibraryW); + HookAttach(&(PVOID&)o_LoadLibraryExW, (PVOID)WLoadLibraryExW); +} diff --git a/primedev/windows/libsys.h b/primedev/windows/libsys.h new file mode 100644 index 000000000..91345d8ff --- /dev/null +++ b/primedev/windows/libsys.h @@ -0,0 +1,3 @@ +#pragma once + +void LibSys_Init(); From bebda79491b11a74195f79a80e0b929ef6edf2b1 Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Mon, 19 Aug 2024 00:06:48 +0100 Subject: [PATCH 41/41] Fix casing of `KERNEL32.DLL` (#768) This is a certified windows moment. Case insensitive but also not sometimes I guess --- primedev/windows/libsys.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primedev/windows/libsys.cpp b/primedev/windows/libsys.cpp index cda104351..dc699b842 100644 --- a/primedev/windows/libsys.cpp +++ b/primedev/windows/libsys.cpp @@ -109,7 +109,7 @@ HMODULE WINAPI WLoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlag //----------------------------------------------------------------------------- void LibSys_Init() { - HMODULE hKernel = GetModuleHandleA("Kernel32.dll"); + HMODULE hKernel = GetModuleHandleA("KERNEL32.DLL"); o_LoadLibraryA = reinterpret_cast(GetProcAddress(hKernel, "LoadLibraryA")); o_LoadLibraryExA = reinterpret_cast(GetProcAddress(hKernel, "LoadLibraryExA"));