diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6048a57d..e562dc3bf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,9 +8,17 @@ 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@v3 + uses: actions/checkout@v4 with: submodules: 'true' - name: Setup msvc @@ -31,16 +39,16 @@ 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-${{ steps.extract.outputs.commit }} + name: NorthstarLauncher-${{ matrix.config.name }}-${{ steps.extract.outputs.commit }} path: | game/ 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 +60,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" diff --git a/.github/workflows/merge-conflict-auto-label.yml b/.github/workflows/merge-conflict-auto-label.yml index cf5598a6e..abb7cabde 100644 --- a/.github/workflows/merge-conflict-auto-label.yml +++ b/.github/workflows/merge-conflict-auto-label.yml @@ -1,10 +1,11 @@ name: Merge Conflict Auto Label on: + workflow_dispatch: # Manual run push: 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: diff --git a/CMakeLists.txt b/CMakeLists.txt index ab461ae29..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,9 @@ 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) # This determines the real binary root directory set(NS_BINARY_DIR ${CMAKE_BINARY_DIR}/game) 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/Northstar.cmake b/primedev/Northstar.cmake index aef630c80..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" @@ -172,13 +174,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/client/audio.cpp b/primedev/client/audio.cpp index 099fdcee5..635014141 100644 --- a/primedev/client/audio.cpp +++ b/primedev/client/audio.cpp @@ -7,6 +7,9 @@ #include #include #include +#include + +namespace fs = std::filesystem; AUTOHOOK_INIT() @@ -28,7 +31,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 +194,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 +270,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 +290,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..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, @@ -15,7 +17,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 +37,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/client/languagehooks.cpp b/primedev/client/languagehooks.cpp index 35ca5659f..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)(); @@ -41,7 +43,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"; } 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 diff --git a/primedev/core/hooks.cpp b/primedev/core/hooks.cpp index 20f0cbef1..5026f837b 100644 --- a/primedev/core/hooks.cpp +++ b/primedev/core/hooks.cpp @@ -10,7 +10,7 @@ #include #include -#define XINPUT1_3_DLL "XInput1_3.dll" +namespace fs = std::filesystem; AUTOHOOK_INIT() @@ -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) { @@ -388,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 f842afbb9..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 = {}); @@ -201,7 +227,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); 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; 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/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/logging/logging.cpp b/primedev/logging/logging.cpp index 72171e254..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 (AllocConsole() == FALSE) - { - std::cout << "[*] Failed to create a console window, maybe a console already exists?" << std::endl; - } - else + if (AllocConsole() != FALSE) { freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); 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; }; diff --git a/primedev/mods/autodownload/moddownloader.cpp b/primedev/mods/autodownload/moddownloader.cpp index 3a9462630..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) { @@ -92,6 +102,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 +118,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}}); @@ -600,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 10df39ceb..98fc27ae3 100644 --- a/primedev/mods/autodownload/moddownloader.h +++ b/primedev/mods/autodownload/moddownloader.h @@ -1,3 +1,5 @@ +namespace fs = std::filesystem; + class ModDownloader { private: @@ -114,6 +116,8 @@ class ModDownloader enum ModInstallState { + MANIFESTO_FETCHING, + // Normal installation process DOWNLOADING, CHECKSUMING, diff --git a/primedev/mods/modmanager.cpp b/primedev/mods/modmanager.cpp index 268a65a56..45eddd3ee 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); }; } @@ -746,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; @@ -864,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; @@ -970,7 +980,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; 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/pch.h b/primedev/pch.h index 577f803c4..bfd255976 100644 --- a/primedev/pch.h +++ b/primedev/pch.h @@ -20,11 +20,6 @@ namespace fs = std::filesystem; -#define EXPORT extern "C" __declspec(dllexport) - -typedef void (*callable)(); -typedef void (*callable_v)(void* v); - // clang-format off #define assert_msg(exp, msg) assert((exp, msg)) //clang-format on 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 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/plugins/plugins.cpp b/primedev/plugins/plugins.cpp index eddaa8acf..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) { @@ -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,13 +87,17 @@ 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); return; } - m_callbacks = (IPluginCallbacks*)CreatePluginInterface("PluginCallbacks001", 0); + m_callbacks = (IPluginCallbacks*)m_pCreateInterface("PluginCallbacks001", 0); if (!m_callbacks) { diff --git a/primedev/Launcher.cmake b/primedev/primelauncher/CMakeLists.txt similarity index 86% rename from primedev/Launcher.cmake rename to primedev/primelauncher/CMakeLists.txt index c04fc70b1..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) @@ -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/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); 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; } } } 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/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/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) { 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 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; } //----------------------------------------------------------------------------- diff --git a/primedev/squirrel/squirrel.cpp b/primedev/squirrel/squirrel.cpp index 41a6a782f..29540ccea 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; @@ -712,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; @@ -805,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 @@ -845,3 +842,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(); diff --git a/primedev/squirrel/squirrel.h b/primedev/squirrel/squirrel.h index 0c1f24d36..547b1efcd 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 @@ -115,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; @@ -136,7 +139,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) @@ -447,7 +450,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 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); 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 diff --git a/primedev/windows/libsys.cpp b/primedev/windows/libsys.cpp new file mode 100644 index 000000000..dc699b842 --- /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(); diff --git a/primedev/wsockproxy/CMakeLists.txt b/primedev/wsockproxy/CMakeLists.txt index 0dbac745d..3f6bce454 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 @@ -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 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