From 2a3be90915b547f21c1dc574a83b24c988cf6b72 Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:32:14 +0200 Subject: [PATCH 01/23] Revert "Add a safeguard to map command" (#561) Revert "Add a safeguard to map command (#529)" This reverts commit cde626b041e72d749c54d0be8119fe3604bbf4f0. --- NorthstarDLL/util/printmaps.cpp | 36 --------------------------------- 1 file changed, 36 deletions(-) diff --git a/NorthstarDLL/util/printmaps.cpp b/NorthstarDLL/util/printmaps.cpp index abd4e1ea6..dd825bff3 100644 --- a/NorthstarDLL/util/printmaps.cpp +++ b/NorthstarDLL/util/printmaps.cpp @@ -31,12 +31,6 @@ struct MapVPKInfo // our current list of maps in the game std::vector vMapList; -typedef void (*Host_Map_helperType)(const CCommand&, void*); -typedef void (*Host_Changelevel_fType)(const CCommand&); - -Host_Map_helperType Host_Map_helper; -Host_Changelevel_fType Host_Changelevel_f; - void RefreshMapList() { // Only update the maps list every 10 seconds max to we avoid constantly reading fs @@ -188,30 +182,6 @@ void ConCommand_maps(const CCommand& args) spdlog::info("({}) {}", PrintMapSource.at(map.source), map.name); } -// clang-format off -AUTOHOOK(Host_Map_f, engine.dll + 0x15B340, void, __fastcall, (const CCommand& args)) -// clang-format on -{ - RefreshMapList(); - - if (args.ArgC() > 1 && - std::find_if(vMapList.begin(), vMapList.end(), [&](MapVPKInfo map) -> bool { return map.name == args.Arg(1); }) == vMapList.end()) - { - spdlog::warn("Map load failed: {} not found or invalid", args.Arg(1)); - return; - } - else if (args.ArgC() == 1) - { - spdlog::warn("Map load failed: no map name provided"); - return; - } - - if (*R2::g_pServerState >= R2::server_state_t::ss_active) - return Host_Changelevel_f(args); - else - return Host_Map_helper(args, nullptr); -} - void InitialiseMapsPrint() { AUTOHOOK_DISPATCH() @@ -219,9 +189,3 @@ void InitialiseMapsPrint() ConCommand* mapsCommand = R2::g_pCVar->FindCommand("maps"); mapsCommand->m_pCommandCallback = ConCommand_maps; } - -ON_DLL_LOAD("engine.dll", Host_Map_f, (CModule module)) -{ - Host_Map_helper = module.Offset(0x15AEF0).RCast(); - Host_Changelevel_f = module.Offset(0x15AAD0).RCast(); -} From 1d30b2d5eeecb1b0bddf75c665939f9532ee235e Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Wed, 4 Oct 2023 00:04:32 +0100 Subject: [PATCH 02/23] Always allow the local player through the banlist (#540) Adds a check to always allow local player through banlist to aid with players accidentally banning themselves. --- NorthstarDLL/server/auth/bansystem.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NorthstarDLL/server/auth/bansystem.cpp b/NorthstarDLL/server/auth/bansystem.cpp index 845a1bff1..9b9d24c43 100644 --- a/NorthstarDLL/server/auth/bansystem.cpp +++ b/NorthstarDLL/server/auth/bansystem.cpp @@ -3,6 +3,7 @@ #include "core/convar/concommand.h" #include "server/r2server.h" #include "engine/r2engine.h" +#include "client/r2client.h" #include "config/profile.h" #include @@ -172,6 +173,10 @@ void ServerBanSystem::UnbanUID(uint64_t uid) bool ServerBanSystem::IsUIDAllowed(uint64_t uid) { + uint64_t localPlayerUserID = strtoull(R2::g_pLocalPlayerUserID, nullptr, 10); + if (localPlayerUserID == uid) + return true; + ReloadBanlist(); // Reload to have up to date list on join return std::find(m_vBannedUids.begin(), m_vBannedUids.end(), uid) == m_vBannedUids.end(); } From 5798c6369c9c093ddd170397177ea75f674a7b4c Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 4 Oct 2023 13:56:48 +0200 Subject: [PATCH 03/23] Give `g_pMemAllocSingleton` a default value (#563) Give default value as standard does not define value if uninitialised. --- NorthstarDLL/core/tier0.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NorthstarDLL/core/tier0.cpp b/NorthstarDLL/core/tier0.cpp index 3b9996a13..167093842 100644 --- a/NorthstarDLL/core/tier0.cpp +++ b/NorthstarDLL/core/tier0.cpp @@ -3,7 +3,7 @@ // use the Tier0 namespace for tier0 funcs namespace Tier0 { - IMemAlloc* g_pMemAllocSingleton; + IMemAlloc* g_pMemAllocSingleton = nullptr; ErrorType Error; CommandLineType CommandLine; From b269c4df616be0ff3f669021726072edf1214c88 Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Wed, 4 Oct 2023 13:57:34 +0200 Subject: [PATCH 04/23] Define indent size in editorconfig (#550) Setting indent size in `.editorconfig` means that things like GitHub web preview will default to showing tab indent size as 4 spaces. --- .editorconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.editorconfig b/.editorconfig index bc423183f..59f4af9f5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,3 +9,4 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = tab +indent_size = 4 From 9d8bedf580184313f419cdb60351e712712832e3 Mon Sep 17 00:00:00 2001 From: H0L0 Date: Fri, 6 Oct 2023 08:50:53 +1000 Subject: [PATCH 05/23] Write date to logs (#565) Next to time also write date to log files. --- NorthstarDLL/logging/logging.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NorthstarDLL/logging/logging.cpp b/NorthstarDLL/logging/logging.cpp index 2e8605d67..a68fe7046 100644 --- a/NorthstarDLL/logging/logging.cpp +++ b/NorthstarDLL/logging/logging.cpp @@ -52,7 +52,7 @@ void CreateLogFiles() stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nslog%Y-%m-%d %H-%M-%S.txt").c_str()); auto sink = std::make_shared(stream.str(), false); - sink->set_pattern("[%H:%M:%S] [%n] [%l] %v"); + sink->set_pattern("[%Y-%m-%d] [%H:%M:%S] [%n] [%l] %v"); for (auto& logger : loggers) { logger->sinks().push_back(sink); From c093ee10f004f7e2b8be2b326a4b087392ded544 Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Sat, 7 Oct 2023 11:25:09 +0100 Subject: [PATCH 06/23] Expose origin auth state and errors to squirrel (#468) Also moves `NSIsMasterServerAuthenticated` out of `scriptserverbrowser.cpp` because it didn't really fit there This will be used for showing failed origin auth errors in the game's UI --- NorthstarDLL/CMakeLists.txt | 1 + NorthstarDLL/masterserver/masterserver.cpp | 21 +++++++++++ NorthstarDLL/masterserver/masterserver.h | 4 +++ .../scripts/client/scriptoriginauth.cpp | 35 +++++++++++++++++++ .../scripts/client/scriptserverbrowser.cpp | 6 ---- 5 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 NorthstarDLL/scripts/client/scriptoriginauth.cpp diff --git a/NorthstarDLL/CMakeLists.txt b/NorthstarDLL/CMakeLists.txt index a7ca10283..288d39b10 100644 --- a/NorthstarDLL/CMakeLists.txt +++ b/NorthstarDLL/CMakeLists.txt @@ -91,6 +91,7 @@ add_library(NorthstarDLL SHARED "scripts/client/scriptbrowserhooks.cpp" "scripts/client/scriptmainmenupromos.cpp" "scripts/client/scriptmodmenu.cpp" + "scripts/client/scriptoriginauth.cpp" "scripts/client/scriptserverbrowser.cpp" "scripts/client/scriptservertoclientstringcommand.cpp" "scripts/server/miscserverfixes.cpp" diff --git a/NorthstarDLL/masterserver/masterserver.cpp b/NorthstarDLL/masterserver/masterserver.cpp index c2bbdfd80..64f172c98 100644 --- a/NorthstarDLL/masterserver/masterserver.cpp +++ b/NorthstarDLL/masterserver/masterserver.cpp @@ -95,6 +95,10 @@ void MasterServerManager::AuthenticateOriginWithMasterServer(const char* uid, co std::string uidStr(uid); std::string tokenStr(originToken); + m_bOriginAuthWithMasterServerSuccessful = false; + m_sOriginAuthWithMasterServerErrorCode = ""; + m_sOriginAuthWithMasterServerErrorMessage = ""; + std::thread requestThread( [this, uidStr, tokenStr]() { @@ -142,9 +146,26 @@ void MasterServerManager::AuthenticateOriginWithMasterServer(const char* uid, co originAuthInfo["token"].GetString(), sizeof(m_sOwnClientAuthToken) - 1); spdlog::info("Northstar origin authentication completed successfully!"); + m_bOriginAuthWithMasterServerSuccessful = true; } else + { spdlog::error("Northstar origin authentication failed"); + + if (originAuthInfo.HasMember("error") && originAuthInfo["error"].IsObject()) + { + + if (originAuthInfo["error"].HasMember("enum") && originAuthInfo["error"]["enum"].IsString()) + { + m_sOriginAuthWithMasterServerErrorCode = originAuthInfo["error"]["enum"].GetString(); + } + + if (originAuthInfo["error"].HasMember("msg") && originAuthInfo["error"]["msg"].IsString()) + { + m_sOriginAuthWithMasterServerErrorMessage = originAuthInfo["error"]["msg"].GetString(); + } + } + } } else { diff --git a/NorthstarDLL/masterserver/masterserver.h b/NorthstarDLL/masterserver/masterserver.h index e87b31a25..c4fa56c21 100644 --- a/NorthstarDLL/masterserver/masterserver.h +++ b/NorthstarDLL/masterserver/masterserver.h @@ -96,6 +96,10 @@ class MasterServerManager bool m_bOriginAuthWithMasterServerDone = false; bool m_bOriginAuthWithMasterServerInProgress = false; + bool m_bOriginAuthWithMasterServerSuccessful = false; + std::string m_sOriginAuthWithMasterServerErrorCode = ""; + std::string m_sOriginAuthWithMasterServerErrorMessage = ""; + bool m_bSavingPersistentData = false; bool m_bScriptRequestingServerList = false; diff --git a/NorthstarDLL/scripts/client/scriptoriginauth.cpp b/NorthstarDLL/scripts/client/scriptoriginauth.cpp new file mode 100644 index 000000000..420c48720 --- /dev/null +++ b/NorthstarDLL/scripts/client/scriptoriginauth.cpp @@ -0,0 +1,35 @@ +#include "squirrel/squirrel.h" +#include "masterserver/masterserver.h" +#include "engine/r2engine.h" +#include "client/r2client.h" + +ADD_SQFUNC("bool", NSIsMasterServerAuthenticated, "", "", ScriptContext::UI) +{ + g_pSquirrel->pushbool(sqvm, g_pMasterServerManager->m_bOriginAuthWithMasterServerDone); + return SQRESULT_NOTNULL; +} + +/* +global struct MasterServerAuthResult +{ + bool success + string errorCode + string errorMessage +} +*/ + +ADD_SQFUNC("MasterServerAuthResult", NSGetMasterServerAuthResult, "", "", ScriptContext::UI) +{ + g_pSquirrel->pushnewstructinstance(sqvm, 3); + + g_pSquirrel->pushbool(sqvm, g_pMasterServerManager->m_bOriginAuthWithMasterServerSuccessful); + g_pSquirrel->sealstructslot(sqvm, 0); + + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_sOriginAuthWithMasterServerErrorCode.c_str(), -1); + g_pSquirrel->sealstructslot(sqvm, 1); + + g_pSquirrel->pushstring(sqvm, g_pMasterServerManager->m_sOriginAuthWithMasterServerErrorMessage.c_str(), -1); + g_pSquirrel->sealstructslot(sqvm, 2); + + return SQRESULT_NOTNULL; +} diff --git a/NorthstarDLL/scripts/client/scriptserverbrowser.cpp b/NorthstarDLL/scripts/client/scriptserverbrowser.cpp index ff6da6e29..1945b3dc9 100644 --- a/NorthstarDLL/scripts/client/scriptserverbrowser.cpp +++ b/NorthstarDLL/scripts/client/scriptserverbrowser.cpp @@ -6,12 +6,6 @@ // functions for viewing server browser -ADD_SQFUNC("bool", NSIsMasterServerAuthenticated, "", "", ScriptContext::UI) -{ - g_pSquirrel->pushbool(sqvm, g_pMasterServerManager->m_bOriginAuthWithMasterServerDone); - return SQRESULT_NOTNULL; -} - ADD_SQFUNC("void", NSRequestServerList, "", "", ScriptContext::UI) { g_pMasterServerManager->RequestServerList(); From 024605399a4878f97fd789416d9ea4234241f039 Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Sat, 7 Oct 2023 13:28:43 +0100 Subject: [PATCH 07/23] Reduce warnings due to calling conventions (#555) Fix instances of "anachronism used: modifiers on data are ignored" --- NorthstarDLL/core/convar/concommand.h | 3 +-- NorthstarDLL/core/hooks.h | 10 +++++----- NorthstarDLL/shared/maxplayers.cpp | 4 ++-- NorthstarDLL/squirrel/squirrel.cpp | 16 +++++++--------- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/NorthstarDLL/core/convar/concommand.h b/NorthstarDLL/core/convar/concommand.h index 89363bc73..c11c7ea4f 100644 --- a/NorthstarDLL/core/convar/concommand.h +++ b/NorthstarDLL/core/convar/concommand.h @@ -83,8 +83,7 @@ typedef void (*FnCommandCallback_t)(const CCommand& command); //----------------------------------------------------------------------------- // Returns 0 to COMMAND_COMPLETION_MAXITEMS worth of completion strings //----------------------------------------------------------------------------- -typedef int (*__fastcall FnCommandCompletionCallback)( - const char* partial, char commands[COMMAND_COMPLETION_MAXITEMS][COMMAND_COMPLETION_ITEM_LENGTH]); +typedef int (*FnCommandCompletionCallback)(const char* partial, char commands[COMMAND_COMPLETION_MAXITEMS][COMMAND_COMPLETION_ITEM_LENGTH]); // From r5reloaded class ConCommandBase diff --git a/NorthstarDLL/core/hooks.h b/NorthstarDLL/core/hooks.h index 8721628a9..01244b3d2 100644 --- a/NorthstarDLL/core/hooks.h +++ b/NorthstarDLL/core/hooks.h @@ -219,7 +219,7 @@ class __autohook type callingConvention CONCAT2(__autohookfunc, name) args; \ namespace \ { \ - type(*callingConvention name) args; \ + type(*name) args; \ __autohook CONCAT2(__autohook, __LINE__)( \ &__FILEAUTOHOOK, __STR(name), __STR(addrString), (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \ } \ @@ -230,7 +230,7 @@ class __autohook type callingConvention CONCAT2(__autohookfunc, name) args; \ namespace \ { \ - type(*callingConvention name) args; \ + type(*name) args; \ __autohook \ CONCAT2(__autohook, __LINE__)(&__FILEAUTOHOOK, __STR(name), addr, (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \ } \ @@ -241,7 +241,7 @@ class __autohook type callingConvention CONCAT2(__autohookfunc, name) args; \ namespace \ { \ - type(*callingConvention name) args; \ + type(*name) args; \ __autohook CONCAT2(__autohook, __LINE__)( \ &__FILEAUTOHOOK, __STR(name), __STR(moduleName), __STR(procName), (LPVOID*)&name, (LPVOID)CONCAT2(__autohookfunc, name)); \ } \ @@ -267,7 +267,7 @@ class ManualHook #define HOOK(varName, originalFunc, type, callingConvention, args) \ namespace \ { \ - type(*callingConvention originalFunc) args; \ + type(*originalFunc) args; \ } \ type callingConvention CONCAT2(__manualhookfunc, varName) args; \ ManualHook varName = ManualHook(__STR(varName), (LPVOID*)&originalFunc, (LPVOID)CONCAT2(__manualhookfunc, varName)); \ @@ -316,7 +316,7 @@ class __autovar // FUNCTION_AT(engine.dll + 0xDEADBEEF, void, __fastcall, SomeFunc, (void* a1)) #define FUNCTION_AT(addrString, type, callingConvention, name, args) \ - type(*callingConvention name) args; \ + type(*name) args; \ namespace \ { \ __autovar CONCAT2(__autovar, __LINE__)(&__FILEAUTOHOOK, __STR(addrString), (void**)&name); \ diff --git a/NorthstarDLL/shared/maxplayers.cpp b/NorthstarDLL/shared/maxplayers.cpp index b2a095788..4af8ea1c1 100644 --- a/NorthstarDLL/shared/maxplayers.cpp +++ b/NorthstarDLL/shared/maxplayers.cpp @@ -282,7 +282,7 @@ void,, (bool a1, float a2)) // clang-format off AUTOHOOK(SendPropArray2, server.dll + 0x12B130, -__int64, __fastcall, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1)) +__int64,, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn, unsigned char unk1)) // clang-format on { // Change the amount of elements to account for a bigger player amount @@ -453,7 +453,7 @@ ON_DLL_LOAD("server.dll", MaxPlayersOverride_Server, (CModule module)) // clang-format off AUTOHOOK(RecvPropArray2, client.dll + 0x1CEDA0, -__int64, __fastcall, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn)) +__int64,, (__int64 recvProp, int elements, int flags, const char* name, __int64 proxyFn)) // clang-format on { // Change the amount of elements to account for a bigger player amount diff --git a/NorthstarDLL/squirrel/squirrel.cpp b/NorthstarDLL/squirrel/squirrel.cpp index 0ee04508c..592cd1537 100644 --- a/NorthstarDLL/squirrel/squirrel.cpp +++ b/NorthstarDLL/squirrel/squirrel.cpp @@ -366,7 +366,7 @@ bool IsUIVM(ScriptContext context, HSquirrelVM* pSqvm) return ScriptContext(pSqvm->sharedState->cSquirrelVM->vmContext) == ScriptContext::UI; } -template void* (*__fastcall sq_compiler_create)(HSquirrelVM* sqvm, void* a2, void* a3, SQBool bShouldThrowError); +template void* (*sq_compiler_create)(HSquirrelVM* sqvm, void* a2, void* a3, SQBool bShouldThrowError); template void* __fastcall sq_compiler_createHook(HSquirrelVM* sqvm, void* a2, void* a3, SQBool bShouldThrowError) { // store whether errors generated from this compile should be fatal @@ -398,7 +398,7 @@ template SQInteger SQPrintHook(HSquirrelVM* sqvm, const return 0; } -template CSquirrelVM* (*__fastcall CreateNewVM)(void* a1, ScriptContext realContext); +template CSquirrelVM* (*CreateNewVM)(void* a1, ScriptContext realContext); template CSquirrelVM* __fastcall CreateNewVMHook(void* a1, ScriptContext realContext) { CSquirrelVM* sqvm = CreateNewVM(a1, realContext); @@ -411,7 +411,7 @@ template CSquirrelVM* __fastcall CreateNewVMHook(void* a return sqvm; } -template bool (*__fastcall CSquirrelVM_init)(CSquirrelVM* vm, ScriptContext realContext, float time); +template bool (*CSquirrelVM_init)(CSquirrelVM* vm, ScriptContext realContext, float time); template bool __fastcall CSquirrelVM_initHook(CSquirrelVM* vm, ScriptContext realContext, float time) { bool ret = CSquirrelVM_init(vm, realContext, time); @@ -428,7 +428,7 @@ template bool __fastcall CSquirrelVM_initHook(CSquirrelV return ret; } -template void (*__fastcall DestroyVM)(void* a1, CSquirrelVM* sqvm); +template void (*DestroyVM)(void* a1, CSquirrelVM* sqvm); template void __fastcall DestroyVMHook(void* a1, CSquirrelVM* sqvm) { ScriptContext realContext = context; // ui and client use the same function so we use this for prints @@ -447,8 +447,7 @@ template void __fastcall DestroyVMHook(void* a1, CSquirr spdlog::info("DestroyVM {} {}", GetContextName(realContext), (void*)sqvm); } -template -void (*__fastcall SQCompileError)(HSquirrelVM* sqvm, const char* error, const char* file, int line, int column); +template void (*SQCompileError)(HSquirrelVM* sqvm, const char* error, const char* file, int line, int column); template void __fastcall ScriptCompileErrorHook(HSquirrelVM* sqvm, const char* error, const char* file, int line, int column) { @@ -496,8 +495,7 @@ void __fastcall ScriptCompileErrorHook(HSquirrelVM* sqvm, const char* error, con // dont call the original function since it kills game lol } -template -int64_t (*__fastcall RegisterSquirrelFunction)(CSquirrelVM* sqvm, SQFuncRegistration* funcReg, char unknown); +template int64_t (*RegisterSquirrelFunction)(CSquirrelVM* sqvm, SQFuncRegistration* funcReg, char unknown); template int64_t __fastcall RegisterSquirrelFunctionHook(CSquirrelVM* sqvm, SQFuncRegistration* funcReg, char unknown) { @@ -523,7 +521,7 @@ int64_t __fastcall RegisterSquirrelFunctionHook(CSquirrelVM* sqvm, SQFuncRegistr return g_pSquirrel->RegisterSquirrelFunc(sqvm, funcReg, unknown); } -template bool (*__fastcall CallScriptInitCallback)(void* sqvm, const char* callback); +template bool (*CallScriptInitCallback)(void* sqvm, const char* callback); template bool __fastcall CallScriptInitCallbackHook(void* sqvm, const char* callback) { ScriptContext realContext = context; From 70a0114caa21ab7417c3cc74c1cf8f46cfd197a1 Mon Sep 17 00:00:00 2001 From: F1F7Y <64418963+F1F7Y@users.noreply.github.com> Date: Sun, 8 Oct 2023 22:11:55 +0200 Subject: [PATCH 08/23] Rewrite crash handler (#477) The goal of this commit is to make the crash handler code more readable and fix some issues. - Format registers - Format Modules - Format stacktrace - Format mods - Create minidump --- NorthstarDLL/dllmain.cpp | 4 +- NorthstarDLL/logging/crashhandler.cpp | 791 ++++++++++++++++---------- NorthstarDLL/logging/crashhandler.h | 107 +++- 3 files changed, 592 insertions(+), 310 deletions(-) diff --git a/NorthstarDLL/dllmain.cpp b/NorthstarDLL/dllmain.cpp index 87c72dcfd..cfcc1a8c3 100644 --- a/NorthstarDLL/dllmain.cpp +++ b/NorthstarDLL/dllmain.cpp @@ -49,7 +49,9 @@ bool InitialiseNorthstar() InitialiseVersion(); CreateLogFiles(); - InitialiseCrashHandler(); + g_pCrashHandler = new CCrashHandler(); + bool bAllFatal = strstr(GetCommandLineA(), "-crash_handle_all") != NULL; + g_pCrashHandler->SetAllFatal(bAllFatal); // Write launcher version to log StartupLog(); diff --git a/NorthstarDLL/logging/crashhandler.cpp b/NorthstarDLL/logging/crashhandler.cpp index 5d120d1f7..5dec315ab 100644 --- a/NorthstarDLL/logging/crashhandler.cpp +++ b/NorthstarDLL/logging/crashhandler.cpp @@ -1,377 +1,586 @@ #include "crashhandler.h" -#include "dedicated/dedicated.h" #include "config/profile.h" +#include "dedicated/dedicated.h" #include "util/version.h" #include "mods/modmanager.h" +#include "plugins/plugins.h" #include -HANDLE hExceptionFilter; - -std::shared_ptr storedException {}; - -#define RUNTIME_EXCEPTION 3765269347 -// clang format did this :/ -std::map ExceptionNames = { - {EXCEPTION_ACCESS_VIOLATION, "Access Violation"}, {EXCEPTION_IN_PAGE_ERROR, "Access Violation"}, - {EXCEPTION_ARRAY_BOUNDS_EXCEEDED, "Array bounds exceeded"}, {EXCEPTION_DATATYPE_MISALIGNMENT, "Datatype misalignment"}, - {EXCEPTION_FLT_DENORMAL_OPERAND, "Denormal operand"}, {EXCEPTION_FLT_DIVIDE_BY_ZERO, "Divide by zero (float)"}, - {EXCEPTION_FLT_INEXACT_RESULT, "Inexact float result"}, {EXCEPTION_FLT_INVALID_OPERATION, "Invalid operation"}, - {EXCEPTION_FLT_OVERFLOW, "Numeric overflow (float)"}, {EXCEPTION_FLT_STACK_CHECK, "Stack check"}, - {EXCEPTION_FLT_UNDERFLOW, "Numeric underflow (float)"}, {EXCEPTION_ILLEGAL_INSTRUCTION, "Illegal instruction"}, - {EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero (int)"}, {EXCEPTION_INT_OVERFLOW, "Numeric overfloat (int)"}, - {EXCEPTION_INVALID_DISPOSITION, "Invalid disposition"}, {EXCEPTION_NONCONTINUABLE_EXCEPTION, "Non-continuable exception"}, - {EXCEPTION_PRIV_INSTRUCTION, "Priviledged instruction"}, {EXCEPTION_STACK_OVERFLOW, "Stack overflow"}, - {RUNTIME_EXCEPTION, "Uncaught runtime exception:"}, -}; - -void PrintExceptionLog(ExceptionLog& exc) +#define CRASHHANDLER_MAX_FRAMES 32 +#define CRASHHANDLER_GETMODULEHANDLE_FAIL "GetModuleHandleExA failed!" + +//----------------------------------------------------------------------------- +// Purpose: Vectored exception callback +//----------------------------------------------------------------------------- +LONG WINAPI ExceptionFilter(EXCEPTION_POINTERS* pExceptionInfo) { - // General crash message - spdlog::error("Northstar version: {}", version); - spdlog::error("Northstar has crashed! a minidump has been written and exception info is available below:"); - if (g_pModManager) + g_pCrashHandler->Lock(); + + g_pCrashHandler->SetExceptionInfos(pExceptionInfo); + + // Check if we should handle this + // NOTE [Fifty]: This gets called before even a try{} catch() {} can handle an exception + // we don't handle these unless "-crash_handle_all" is passed as a launch arg + if (!g_pCrashHandler->IsExceptionFatal() && !g_pCrashHandler->GetAllFatal()) { - spdlog::error("Loaded mods: "); - for (const auto& mod : g_pModManager->m_LoadedMods) - { - if (mod.m_bEnabled) - { - spdlog::error("{} {}", mod.Name, mod.Version); - } - } + g_pCrashHandler->Unlock(); + return EXCEPTION_CONTINUE_SEARCH; } - spdlog::error(exc.cause); - // If this was a runtime error, print the message - if (exc.runtimeInfo.length() != 0) - spdlog::error("\"{}\"", exc.runtimeInfo); - spdlog::error("At: {} + {}", exc.trace[0].name, exc.trace[0].relativeAddress); - spdlog::error(""); - spdlog::error("Stack trace:"); - - // Generate format string for stack trace - std::stringstream formatString; - formatString << " {:<" << exc.longestModuleNameLength + 2 << "} {:<" << exc.longestRelativeAddressLength << "} {}"; - std::string guide = fmt::format(formatString.str(), "Module Name", "Offset", "Full Address"); - std::string line(guide.length() + 2, '-'); - spdlog::error(guide); - spdlog::error(line); - - for (const auto& module : exc.trace) - spdlog::error(formatString.str(), module.name, module.relativeAddress, module.address); - - // Print dump of most CPU registers - spdlog::error(""); - for (const auto& reg : exc.registerDump) - spdlog::error(reg); - if (!IsDedicatedServer()) - MessageBoxA( - 0, - "Northstar has crashed! Crash info can be found in R2Northstar/logs", - "Northstar has crashed!", - MB_ICONERROR | MB_OK | MB_SYSTEMMODAL); + // Don't run if a debbuger is attached + if (IsDebuggerPresent()) + { + g_pCrashHandler->Unlock(); + return EXCEPTION_CONTINUE_SEARCH; + } + // Prevent recursive calls + if (g_pCrashHandler->GetState()) + { + g_pCrashHandler->Unlock(); + ExitProcess(1); + } + + g_pCrashHandler->SetState(true); + + // Needs to be called first as we use the members this sets later on + g_pCrashHandler->SetCrashedModule(); + + // Format + g_pCrashHandler->FormatException(); + g_pCrashHandler->FormatCallstack(); + g_pCrashHandler->FormatRegisters(); + g_pCrashHandler->FormatLoadedMods(); + g_pCrashHandler->FormatLoadedPlugins(); + g_pCrashHandler->FormatModules(); + + // Flush NS::log::FlushLoggers(); + + // Write minidump + g_pCrashHandler->WriteMinidump(); + + // Show message box + g_pCrashHandler->ShowPopUpMessage(); + + g_pCrashHandler->Unlock(); + + // We showed the "Northstar has crashed" message box + // make sure we terminate + if (!g_pCrashHandler->IsExceptionFatal()) + ExitProcess(1); + + return EXCEPTION_EXECUTE_HANDLER; } -std::string GetExceptionName(ExceptionLog& exc) +//----------------------------------------------------------------------------- +// Purpose: console control signal handler +//----------------------------------------------------------------------------- +BOOL WINAPI ConsoleCtrlRoutine(DWORD dwCtrlType) { - const DWORD exceptionCode = exc.exceptionRecord.ExceptionCode; - auto name = ExceptionNames[exceptionCode]; - if (exceptionCode == EXCEPTION_ACCESS_VIOLATION || exceptionCode == EXCEPTION_IN_PAGE_ERROR) + // NOTE [Fifty]: When closing the process by closing the console we don't want + // to trigger the crash handler so we remove it + switch (dwCtrlType) { - std::stringstream returnString; - returnString << name << ": "; - - auto exceptionInfo0 = exc.exceptionRecord.ExceptionInformation[0]; - auto exceptionInfo1 = exc.exceptionRecord.ExceptionInformation[1]; - - if (!exceptionInfo0) - returnString << "Attempted to read from: 0x" << (void*)exceptionInfo1; - else if (exceptionInfo0 == 1) - returnString << "Attempted to write to: 0x" << (void*)exceptionInfo1; - else if (exceptionInfo0 == 8) - returnString << "Data Execution Prevention (DEP) at: 0x" << (void*)std::hex << exceptionInfo1; - else - returnString << "Unknown access violation at: 0x" << (void*)exceptionInfo1; - return returnString.str(); + case CTRL_CLOSE_EVENT: + spdlog::info("Exiting due to console close..."); + delete g_pCrashHandler; + g_pCrashHandler = nullptr; + std::exit(EXIT_SUCCESS); + return TRUE; } - return name; + + return FALSE; } -// Custom formatter for the Xmm registers -template <> struct fmt::formatter : fmt::formatter +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CCrashHandler::CCrashHandler() + : m_hExceptionFilter(nullptr), m_pExceptionInfos(nullptr), m_bHasSetConsolehandler(false), m_bAllExceptionsFatal(false), + m_bHasShownCrashMsg(false), m_bState(false) { - template auto format(const M128A& obj, FormatContext& ctx) - { - // Masking the top and bottom half of the long long - int v1 = obj.Low & INT_MAX; - int v2 = obj.Low >> 32; - int v3 = obj.High & INT_MAX; - int v4 = obj.High >> 32; - return fmt::format_to( - ctx.out(), - "[ {:G}, {:G}, {:G}, {:G}], [ 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x} ]", - *reinterpret_cast(&v1), - *reinterpret_cast(&v2), - *reinterpret_cast(&v3), - *reinterpret_cast(&v4), - v1, - v2, - v3, - v4); - } -}; + Init(); +} -void GenerateTrace(ExceptionLog& exc, bool skipErrorHandlingFrames = true, int numSkipFrames = 0) +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CCrashHandler::~CCrashHandler() { + Shutdown(); +} - MODULEINFO crashedModuleInfo; - GetModuleInformation(GetCurrentProcess(), exc.crashedModule, &crashedModuleInfo, sizeof(crashedModuleInfo)); - - char crashedModuleFullName[MAX_PATH]; - GetModuleFileNameExA(GetCurrentProcess(), exc.crashedModule, crashedModuleFullName, MAX_PATH); - char* crashedModuleName = strrchr(crashedModuleFullName, '\\') + 1; - - DWORD64 crashedModuleOffset = ((DWORD64)exc.exceptionRecord.ExceptionAddress) - ((DWORD64)crashedModuleInfo.lpBaseOfDll); - - PVOID framesToCapture[62]; - int frames = RtlCaptureStackBackTrace(0, 62, framesToCapture, NULL); - bool haveSkippedErrorHandlingFrames = false; +//----------------------------------------------------------------------------- +// Purpose: Initilazes crash handler +//----------------------------------------------------------------------------- +void CCrashHandler::Init() +{ + m_hExceptionFilter = AddVectoredExceptionHandler(TRUE, ExceptionFilter); + m_bHasSetConsolehandler = SetConsoleCtrlHandler(ConsoleCtrlRoutine, TRUE); +} - for (int i = 0; i < frames; i++) +//----------------------------------------------------------------------------- +// Purpose: Shutdowns crash handler +//----------------------------------------------------------------------------- +void CCrashHandler::Shutdown() +{ + if (m_hExceptionFilter) { + RemoveVectoredExceptionHandler(m_hExceptionFilter); + m_hExceptionFilter = nullptr; + } - HMODULE backtraceModuleHandle; - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast(framesToCapture[i]), &backtraceModuleHandle); + if (m_bHasSetConsolehandler) + { + SetConsoleCtrlHandler(ConsoleCtrlRoutine, FALSE); + } +} - char backtraceModuleFullName[MAX_PATH]; - GetModuleFileNameExA(GetCurrentProcess(), backtraceModuleHandle, backtraceModuleFullName, MAX_PATH); - char* backtraceModuleName = strrchr(backtraceModuleFullName, '\\') + 1; +//----------------------------------------------------------------------------- +// Purpose: Sets the exception info +//----------------------------------------------------------------------------- +void CCrashHandler::SetExceptionInfos(EXCEPTION_POINTERS* pExceptionPointers) +{ + m_pExceptionInfos = pExceptionPointers; +} +//----------------------------------------------------------------------------- +// Purpose: Sets the exception stirngs for message box +//----------------------------------------------------------------------------- +void CCrashHandler::SetCrashedModule() +{ + LPCSTR pCrashAddress = static_cast(m_pExceptionInfos->ExceptionRecord->ExceptionAddress); + HMODULE hCrashedModule; + if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, pCrashAddress, &hCrashedModule)) + { + m_svCrashedModule = CRASHHANDLER_GETMODULEHANDLE_FAIL; + m_svCrashedOffset = ""; - if (!haveSkippedErrorHandlingFrames) + DWORD dwErrorID = GetLastError(); + if (dwErrorID != 0) { - if (!strncmp(backtraceModuleFullName, crashedModuleFullName, MAX_PATH) && - !strncmp(backtraceModuleName, crashedModuleName, MAX_PATH)) - { - haveSkippedErrorHandlingFrames = true; - } - else + LPSTR pszBuffer; + DWORD dwSize = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dwErrorID, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&pszBuffer, + 0, + NULL); + + if (dwSize > 0) { - continue; + m_svError = pszBuffer; + LocalFree(pszBuffer); } } - if (numSkipFrames > 0) - { - numSkipFrames--; - continue; - } + return; + } - void* actualAddress = (void*)framesToCapture[i]; - void* relativeAddress = (void*)(uintptr_t(actualAddress) - uintptr_t(backtraceModuleHandle)); - std::string s_moduleName {backtraceModuleName}; - std::string s_relativeAddress {fmt::format("{}", relativeAddress)}; - // These are used for formatting later on - if (s_moduleName.length() > exc.longestModuleNameLength) - { - exc.longestModuleNameLength = s_moduleName.length(); - } - if (s_relativeAddress.length() > exc.longestRelativeAddressLength) - { - exc.longestRelativeAddressLength = s_relativeAddress.length(); - } + // Get module filename + CHAR szCrashedModulePath[MAX_PATH]; + GetModuleFileNameExA(GetCurrentProcess(), hCrashedModule, szCrashedModulePath, sizeof(szCrashedModulePath)); - exc.trace.push_back(BacktraceModule {s_moduleName, s_relativeAddress, fmt::format("{}", actualAddress)}); - } + const CHAR* pszCrashedModuleFileName = strrchr(szCrashedModulePath, '\\') + 1; + + // Get relative address + LPCSTR pModuleBase = reinterpret_cast(pCrashAddress - reinterpret_cast(hCrashedModule)); - CONTEXT* exceptionContext = &exc.contextRecord; - - exc.registerDump.push_back(fmt::format("Flags: 0b{0:b}", exceptionContext->ContextFlags)); - exc.registerDump.push_back(fmt::format("RIP: 0x{0:x}", exceptionContext->Rip)); - exc.registerDump.push_back(fmt::format("CS : 0x{0:x}", exceptionContext->SegCs)); - exc.registerDump.push_back(fmt::format("DS : 0x{0:x}", exceptionContext->SegDs)); - exc.registerDump.push_back(fmt::format("ES : 0x{0:x}", exceptionContext->SegEs)); - exc.registerDump.push_back(fmt::format("SS : 0x{0:x}", exceptionContext->SegSs)); - exc.registerDump.push_back(fmt::format("FS : 0x{0:x}", exceptionContext->SegFs)); - exc.registerDump.push_back(fmt::format("GS : 0x{0:x}", exceptionContext->SegGs)); - - exc.registerDump.push_back(fmt::format("RAX: 0x{0:x}", exceptionContext->Rax)); - exc.registerDump.push_back(fmt::format("RBX: 0x{0:x}", exceptionContext->Rbx)); - exc.registerDump.push_back(fmt::format("RCX: 0x{0:x}", exceptionContext->Rcx)); - exc.registerDump.push_back(fmt::format("RDX: 0x{0:x}", exceptionContext->Rdx)); - exc.registerDump.push_back(fmt::format("RSI: 0x{0:x}", exceptionContext->Rsi)); - exc.registerDump.push_back(fmt::format("RDI: 0x{0:x}", exceptionContext->Rdi)); - exc.registerDump.push_back(fmt::format("RBP: 0x{0:x}", exceptionContext->Rbp)); - exc.registerDump.push_back(fmt::format("RSP: 0x{0:x}", exceptionContext->Rsp)); - exc.registerDump.push_back(fmt::format("R8 : 0x{0:x}", exceptionContext->R8)); - exc.registerDump.push_back(fmt::format("R9 : 0x{0:x}", exceptionContext->R9)); - exc.registerDump.push_back(fmt::format("R10: 0x{0:x}", exceptionContext->R10)); - exc.registerDump.push_back(fmt::format("R11: 0x{0:x}", exceptionContext->R11)); - exc.registerDump.push_back(fmt::format("R12: 0x{0:x}", exceptionContext->R12)); - exc.registerDump.push_back(fmt::format("R13: 0x{0:x}", exceptionContext->R13)); - exc.registerDump.push_back(fmt::format("R14: 0x{0:x}", exceptionContext->R14)); - exc.registerDump.push_back(fmt::format("R15: 0x{0:x}", exceptionContext->R15)); - - exc.registerDump.push_back(fmt::format("Xmm0 : {}", exceptionContext->Xmm0)); - exc.registerDump.push_back(fmt::format("Xmm1 : {}", exceptionContext->Xmm1)); - exc.registerDump.push_back(fmt::format("Xmm2 : {}", exceptionContext->Xmm2)); - exc.registerDump.push_back(fmt::format("Xmm3 : {}", exceptionContext->Xmm3)); - exc.registerDump.push_back(fmt::format("Xmm4 : {}", exceptionContext->Xmm4)); - exc.registerDump.push_back(fmt::format("Xmm5 : {}", exceptionContext->Xmm5)); - exc.registerDump.push_back(fmt::format("Xmm6 : {}", exceptionContext->Xmm6)); - exc.registerDump.push_back(fmt::format("Xmm7 : {}", exceptionContext->Xmm7)); - exc.registerDump.push_back(fmt::format("Xmm8 : {}", exceptionContext->Xmm8)); - exc.registerDump.push_back(fmt::format("Xmm9 : {}", exceptionContext->Xmm9)); - exc.registerDump.push_back(fmt::format("Xmm10: {}", exceptionContext->Xmm10)); - exc.registerDump.push_back(fmt::format("Xmm11: {}", exceptionContext->Xmm11)); - exc.registerDump.push_back(fmt::format("Xmm12: {}", exceptionContext->Xmm12)); - exc.registerDump.push_back(fmt::format("Xmm13: {}", exceptionContext->Xmm13)); - exc.registerDump.push_back(fmt::format("Xmm14: {}", exceptionContext->Xmm14)); - exc.registerDump.push_back(fmt::format("Xmm15: {}", exceptionContext->Xmm15)); + m_svCrashedModule = pszCrashedModuleFileName; + m_svCrashedOffset = fmt::format("{:#x}", reinterpret_cast(pModuleBase)); } -void CreateMiniDump(EXCEPTION_POINTERS* exceptionInfo) +//----------------------------------------------------------------------------- +// Purpose: Gets the exception null terminated stirng +//----------------------------------------------------------------------------- + +const CHAR* CCrashHandler::GetExceptionString() const { - time_t time = std::time(nullptr); - tm currentTime = *std::localtime(&time); - std::stringstream stream; - stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nsdump%Y-%m-%d %H-%M-%S.dmp").c_str()); + return GetExceptionString(m_pExceptionInfos->ExceptionRecord->ExceptionCode); +} - auto hMinidumpFile = CreateFileA(stream.str().c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); - if (hMinidumpFile) +//----------------------------------------------------------------------------- +// Purpose: Gets the exception null terminated stirng +//----------------------------------------------------------------------------- +const CHAR* CCrashHandler::GetExceptionString(DWORD dwExceptionCode) const +{ + // clang-format off + switch (dwExceptionCode) { - MINIDUMP_EXCEPTION_INFORMATION dumpExceptionInfo; - dumpExceptionInfo.ThreadId = GetCurrentThreadId(); - dumpExceptionInfo.ExceptionPointers = exceptionInfo; - dumpExceptionInfo.ClientPointers = false; - - MiniDumpWriteDump( - GetCurrentProcess(), - GetCurrentProcessId(), - hMinidumpFile, - MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory), - &dumpExceptionInfo, - nullptr, - nullptr); - CloseHandle(hMinidumpFile); + case EXCEPTION_ACCESS_VIOLATION: return "EXCEPTION_ACCESS_VIOLATION"; + case EXCEPTION_DATATYPE_MISALIGNMENT: return "EXCEPTION_DATATYPE_MISALIGNMENT"; + case EXCEPTION_BREAKPOINT: return "EXCEPTION_BREAKPOINT"; + case EXCEPTION_SINGLE_STEP: return "EXCEPTION_SINGLE_STEP"; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; + case EXCEPTION_FLT_DENORMAL_OPERAND: return "EXCEPTION_FLT_DENORMAL_OPERAND"; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; + case EXCEPTION_FLT_INEXACT_RESULT: return "EXCEPTION_FLT_INEXACT_RESULT"; + case EXCEPTION_FLT_INVALID_OPERATION: return "EXCEPTION_FLT_INVALID_OPERATION"; + case EXCEPTION_FLT_OVERFLOW: return "EXCEPTION_FLT_OVERFLOW"; + case EXCEPTION_FLT_STACK_CHECK: return "EXCEPTION_FLT_STACK_CHECK"; + case EXCEPTION_FLT_UNDERFLOW: return "EXCEPTION_FLT_UNDERFLOW"; + case EXCEPTION_INT_DIVIDE_BY_ZERO: return "EXCEPTION_INT_DIVIDE_BY_ZERO"; + case EXCEPTION_INT_OVERFLOW: return "EXCEPTION_INT_OVERFLOW"; + case EXCEPTION_PRIV_INSTRUCTION: return "EXCEPTION_PRIV_INSTRUCTION"; + case EXCEPTION_IN_PAGE_ERROR: return "EXCEPTION_IN_PAGE_ERROR"; + case EXCEPTION_ILLEGAL_INSTRUCTION: return "EXCEPTION_ILLEGAL_INSTRUCTION"; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; + case EXCEPTION_STACK_OVERFLOW: return "EXCEPTION_STACK_OVERFLOW"; + case EXCEPTION_INVALID_DISPOSITION: return "EXCEPTION_INVALID_DISPOSITION"; + case EXCEPTION_GUARD_PAGE: return "EXCEPTION_GUARD_PAGE"; + case EXCEPTION_INVALID_HANDLE: return "EXCEPTION_INVALID_HANDLE"; + case 3765269347: return "RUNTIME_EXCEPTION"; } - else - spdlog::error("Failed to write minidump file {}!", stream.str()); + // clang-format on + return "UNKNOWN_EXCEPTION"; } -long GenerateExceptionLog(EXCEPTION_POINTERS* exceptionInfo) +//----------------------------------------------------------------------------- +// Purpose: Returns true if exception is known +//----------------------------------------------------------------------------- +bool CCrashHandler::IsExceptionFatal() const { - storedException->exceptionRecord = *exceptionInfo->ExceptionRecord; - storedException->contextRecord = *exceptionInfo->ContextRecord; - const DWORD exceptionCode = exceptionInfo->ExceptionRecord->ExceptionCode; + return IsExceptionFatal(m_pExceptionInfos->ExceptionRecord->ExceptionCode); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if exception is known +//----------------------------------------------------------------------------- +bool CCrashHandler::IsExceptionFatal(DWORD dwExceptionCode) const +{ + // clang-format off + switch (dwExceptionCode) + { + case EXCEPTION_ACCESS_VIOLATION: + case EXCEPTION_DATATYPE_MISALIGNMENT: + case EXCEPTION_BREAKPOINT: + case EXCEPTION_SINGLE_STEP: + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + case EXCEPTION_FLT_DENORMAL_OPERAND: + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + case EXCEPTION_FLT_INEXACT_RESULT: + case EXCEPTION_FLT_INVALID_OPERATION: + case EXCEPTION_FLT_OVERFLOW: + case EXCEPTION_FLT_STACK_CHECK: + case EXCEPTION_FLT_UNDERFLOW: + case EXCEPTION_INT_DIVIDE_BY_ZERO: + case EXCEPTION_INT_OVERFLOW: + case EXCEPTION_PRIV_INSTRUCTION: + case EXCEPTION_IN_PAGE_ERROR: + case EXCEPTION_ILLEGAL_INSTRUCTION: + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + case EXCEPTION_STACK_OVERFLOW: + case EXCEPTION_INVALID_DISPOSITION: + case EXCEPTION_GUARD_PAGE: + case EXCEPTION_INVALID_HANDLE: + return true; + } + // clang-format on + return false; +} - void* exceptionAddress = exceptionInfo->ExceptionRecord->ExceptionAddress; +//----------------------------------------------------------------------------- +// Purpose: Shows a message box +//----------------------------------------------------------------------------- +void CCrashHandler::ShowPopUpMessage() +{ + if (m_bHasShownCrashMsg) + return; - storedException->cause = GetExceptionName(*storedException); + m_bHasShownCrashMsg = true; - HMODULE crashedModuleHandle; - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast(exceptionAddress), &crashedModuleHandle); + if (!IsDedicatedServer()) + { + std::string svMessage = fmt::format( + "Northstar has crashed! Crash info can be found at {}/logs!\n\n{}\n{} + {}", + GetNorthstarPrefix(), + GetExceptionString(), + m_svCrashedModule, + m_svCrashedOffset); + + MessageBoxA(GetForegroundWindow(), svMessage.c_str(), "Northstar has crashed!", MB_ICONERROR | MB_OK); + } +} - storedException->crashedModule = crashedModuleHandle; +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCrashHandler::FormatException() +{ + spdlog::error("-------------------------------------------"); + spdlog::error("Northstar has crashed!"); + spdlog::error("\tVersion: {}", version); + if (!m_svError.empty()) + { + spdlog::info("\tEncountered an error when gathering crash information!"); + spdlog::info("\tWinApi Error: {}", m_svError.c_str()); + } + spdlog::error("\t{}", GetExceptionString()); - // When encountering a runtime exception, we store the exception to be displayed later - // We then have to return EXCEPTION_CONTINUE_SEARCH so that our runtime handler may be called - // This might possibly cause some issues if client and server are crashing at the same time, but honestly i don't care - if (exceptionCode == RUNTIME_EXCEPTION) + DWORD dwExceptionCode = m_pExceptionInfos->ExceptionRecord->ExceptionCode; + if (dwExceptionCode == EXCEPTION_ACCESS_VIOLATION || dwExceptionCode == EXCEPTION_IN_PAGE_ERROR) { - GenerateTrace(*storedException, false, 2); - storedException = storedException; - return EXCEPTION_CONTINUE_SEARCH; + ULONG_PTR uExceptionInfo0 = m_pExceptionInfos->ExceptionRecord->ExceptionInformation[0]; + ULONG_PTR uExceptionInfo1 = m_pExceptionInfos->ExceptionRecord->ExceptionInformation[1]; + + if (!uExceptionInfo0) + spdlog::error("\tAttempted to read from: {:#x}", uExceptionInfo1); + else if (uExceptionInfo0 == 1) + spdlog::error("\tAttempted to write to: {:#x}", uExceptionInfo1); + else if (uExceptionInfo0 == 8) + spdlog::error("\tData Execution Prevention (DEP) at: {:#x}", uExceptionInfo1); + else + spdlog::error("\tUnknown access violation at: {:#x}", uExceptionInfo1); } - GenerateTrace(*storedException, true, 0); - CreateMiniDump(exceptionInfo); - PrintExceptionLog(*storedException); - return EXCEPTION_EXECUTE_HANDLER; + spdlog::error("\tAt: {} + {}", m_svCrashedModule, m_svCrashedOffset); } -long __stdcall ExceptionFilter(EXCEPTION_POINTERS* exceptionInfo) +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCrashHandler::FormatCallstack() { - if (!IsDebuggerPresent()) + spdlog::error("Callstack:"); + + PVOID pFrames[CRASHHANDLER_MAX_FRAMES]; + + int iFrames = RtlCaptureStackBackTrace(0, CRASHHANDLER_MAX_FRAMES, pFrames, NULL); + + // Above call gives us frames after the crash occured, we only want to print the ones starting from where + // the exception was called + bool bSkipExceptionHandlingFrames = true; + + // We ran into an error when getting the offset, just print all frames + if (m_svCrashedOffset.empty()) + bSkipExceptionHandlingFrames = false; + + for (int i = 0; i < iFrames; i++) { - // Check if we are capable of handling this type of exception - if (ExceptionNames.find(exceptionInfo->ExceptionRecord->ExceptionCode) == ExceptionNames.end()) - return EXCEPTION_CONTINUE_SEARCH; - if (exceptionInfo->ExceptionRecord->ExceptionCode == RUNTIME_EXCEPTION) + const CHAR* pszModuleFileName; + + LPCSTR pAddress = static_cast(pFrames[i]); + HMODULE hModule; + if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, pAddress, &hModule)) { - storedException = std::make_shared(); - storedException->exceptionRecord = *exceptionInfo->ExceptionRecord; - storedException->contextRecord = *exceptionInfo->ContextRecord; + pszModuleFileName = CRASHHANDLER_GETMODULEHANDLE_FAIL; + // If we fail here it's too late to do any damage control } else { - CreateMiniDump(exceptionInfo); - return GenerateExceptionLog(exceptionInfo); + CHAR szModulePath[MAX_PATH]; + GetModuleFileNameExA(GetCurrentProcess(), hModule, szModulePath, sizeof(szModulePath)); + pszModuleFileName = strrchr(szModulePath, '\\') + 1; } + + // Get relative address + LPCSTR pCrashOffset = reinterpret_cast(pAddress - reinterpret_cast(hModule)); + std::string svCrashOffset = fmt::format("{:#x}", reinterpret_cast(pCrashOffset)); + + // Should we log this frame + if (bSkipExceptionHandlingFrames) + { + if (m_svCrashedModule == pszModuleFileName && m_svCrashedOffset == svCrashOffset) + { + bSkipExceptionHandlingFrames = false; + } + else + { + continue; + } + } + + // Log module + offset + spdlog::error("\t{} + {:#x}", pszModuleFileName, reinterpret_cast(pCrashOffset)); } +} - return EXCEPTION_EXECUTE_HANDLER; +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCrashHandler::FormatFlags(const CHAR* pszRegister, DWORD nValue) +{ + spdlog::error("\t{}: {:#b}", pszRegister, nValue); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCrashHandler::FormatIntReg(const CHAR* pszRegister, DWORD64 nValue) +{ + spdlog::error("\t{}: {:#x}", pszRegister, nValue); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCrashHandler::FormatFloatReg(const CHAR* pszRegister, M128A nValue) +{ + DWORD nVec[4] = { + static_cast(nValue.Low & UINT_MAX), + static_cast(nValue.Low >> 32), + static_cast(nValue.High & UINT_MAX), + static_cast(nValue.High >> 32)}; + + spdlog::error( + "\t{}: [ {:G}, {:G}, {:G}, {:G} ]; [ {:#x}, {:#x}, {:#x}, {:#x} ]", + pszRegister, + static_cast(nVec[0]), + static_cast(nVec[1]), + static_cast(nVec[2]), + static_cast(nVec[3]), + nVec[0], + nVec[1], + nVec[2], + nVec[3]); } -void RuntimeExceptionHandler() +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCrashHandler::FormatRegisters() { - auto ptr = std::current_exception(); - if (ptr) + spdlog::error("Registers:"); + + PCONTEXT pContext = m_pExceptionInfos->ContextRecord; + + FormatFlags("Flags:", pContext->ContextFlags); + + FormatIntReg("Rax", pContext->Rax); + FormatIntReg("Rcx", pContext->Rcx); + FormatIntReg("Rdx", pContext->Rdx); + FormatIntReg("Rbx", pContext->Rbx); + FormatIntReg("Rsp", pContext->Rsp); + FormatIntReg("Rbp", pContext->Rbp); + FormatIntReg("Rsi", pContext->Rsi); + FormatIntReg("Rdi", pContext->Rdi); + FormatIntReg("R8 ", pContext->R8); + FormatIntReg("R9 ", pContext->R9); + FormatIntReg("R10", pContext->R10); + FormatIntReg("R11", pContext->R11); + FormatIntReg("R12", pContext->R12); + FormatIntReg("R13", pContext->R13); + FormatIntReg("R14", pContext->R14); + FormatIntReg("R15", pContext->R15); + FormatIntReg("Rip", pContext->Rip); + + FormatFloatReg("Xmm0 ", pContext->Xmm0); + FormatFloatReg("Xmm1 ", pContext->Xmm1); + FormatFloatReg("Xmm2 ", pContext->Xmm2); + FormatFloatReg("Xmm3 ", pContext->Xmm3); + FormatFloatReg("Xmm4 ", pContext->Xmm4); + FormatFloatReg("Xmm5 ", pContext->Xmm5); + FormatFloatReg("Xmm6 ", pContext->Xmm6); + FormatFloatReg("Xmm7 ", pContext->Xmm7); + FormatFloatReg("Xmm8 ", pContext->Xmm8); + FormatFloatReg("Xmm9 ", pContext->Xmm9); + FormatFloatReg("Xmm10", pContext->Xmm10); + FormatFloatReg("Xmm11", pContext->Xmm11); + FormatFloatReg("Xmm12", pContext->Xmm12); + FormatFloatReg("Xmm13", pContext->Xmm13); + FormatFloatReg("Xmm14", pContext->Xmm14); + FormatFloatReg("Xmm15", pContext->Xmm15); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCrashHandler::FormatLoadedMods() +{ + if (g_pModManager) { - try - { - // This is to generate an actual std::exception object that we can then inspect - std::rethrow_exception(ptr); - } - catch (std::exception& e) + spdlog::error("Enabled mods:"); + for (const Mod& mod : g_pModManager->m_LoadedMods) { - storedException->runtimeInfo = e.what(); + if (!mod.m_bEnabled) + continue; + + spdlog::error("\t{}", mod.Name); } - catch (...) + + spdlog::error("Disabled mods:"); + for (const Mod& mod : g_pModManager->m_LoadedMods) { - storedException->runtimeInfo = "Unknown runtime exception type"; + if (mod.m_bEnabled) + continue; + + spdlog::error("\t{}", mod.Name); } - EXCEPTION_POINTERS test {}; - test.ContextRecord = &storedException->contextRecord; - test.ExceptionRecord = &storedException->exceptionRecord; - CreateMiniDump(&test); - GenerateExceptionLog(&test); - PrintExceptionLog(*storedException); - exit(-1); - } - else - { - spdlog::error( - "std::current_exception() returned nullptr while being handled by RuntimeExceptionHandler. This should never happen!"); - std::abort(); } } -BOOL WINAPI ConsoleHandlerRoutine(DWORD eventCode) +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCrashHandler::FormatLoadedPlugins() { - switch (eventCode) + if (g_pPluginManager) { - case CTRL_CLOSE_EVENT: - // User closed console, shut everything down - spdlog::info("Exiting due to console close..."); - RemoveCrashHandler(); - exit(EXIT_SUCCESS); - return FALSE; + spdlog::error("Loaded Plugins:"); + for (const Plugin& plugin : g_pPluginManager->m_vLoadedPlugins) + { + spdlog::error("\t{}", plugin.name); + } } - - return TRUE; } -void InitialiseCrashHandler() +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCrashHandler::FormatModules() { - hExceptionFilter = AddVectoredExceptionHandler(TRUE, ExceptionFilter); - SetConsoleCtrlHandler(ConsoleHandlerRoutine, true); - std::set_terminate(RuntimeExceptionHandler); + spdlog::error("Loaded modules:"); + HMODULE hModules[1024]; + DWORD cbNeeded; + + if (EnumProcessModules(GetCurrentProcess(), hModules, sizeof(hModules), &cbNeeded)) + { + for (DWORD i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) + { + CHAR szModulePath[MAX_PATH]; + if (GetModuleFileNameExA(GetCurrentProcess(), hModules[i], szModulePath, sizeof(szModulePath))) + { + const CHAR* pszModuleFileName = strrchr(szModulePath, '\\') + 1; + spdlog::error("\t{}", pszModuleFileName); + } + } + } } -void RemoveCrashHandler() +//----------------------------------------------------------------------------- +// Purpose: Writes minidump to disk +//----------------------------------------------------------------------------- +void CCrashHandler::WriteMinidump() { - RemoveVectoredExceptionHandler(hExceptionFilter); + time_t time = std::time(nullptr); + tm currentTime = *std::localtime(&time); + std::stringstream stream; + stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nsdump%Y-%m-%d %H-%M-%S.dmp").c_str()); + + HANDLE hMinidumpFile = CreateFileA(stream.str().c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + if (hMinidumpFile) + { + MINIDUMP_EXCEPTION_INFORMATION dumpExceptionInfo; + dumpExceptionInfo.ThreadId = GetCurrentThreadId(); + dumpExceptionInfo.ExceptionPointers = m_pExceptionInfos; + dumpExceptionInfo.ClientPointers = false; + + MiniDumpWriteDump( + GetCurrentProcess(), + GetCurrentProcessId(), + hMinidumpFile, + MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory), + &dumpExceptionInfo, + nullptr, + nullptr); + CloseHandle(hMinidumpFile); + } + else + spdlog::error("Failed to write minidump file {}!", stream.str()); } + +//----------------------------------------------------------------------------- +CCrashHandler* g_pCrashHandler = nullptr; diff --git a/NorthstarDLL/logging/crashhandler.h b/NorthstarDLL/logging/crashhandler.h index 4d8a59ced..e18de948b 100644 --- a/NorthstarDLL/logging/crashhandler.h +++ b/NorthstarDLL/logging/crashhandler.h @@ -1,26 +1,97 @@ #pragma once -void InitialiseCrashHandler(); -void RemoveCrashHandler(); +#include -struct BacktraceModule +//----------------------------------------------------------------------------- +// Purpose: Exception handling +//----------------------------------------------------------------------------- +class CCrashHandler { - std::string name; - std::string relativeAddress; - std::string address; -}; + public: + CCrashHandler(); + ~CCrashHandler(); -struct ExceptionLog -{ - std::string cause; - HMODULE crashedModule; - EXCEPTION_RECORD exceptionRecord; - CONTEXT contextRecord; - std::vector trace; - std::vector registerDump; + void Init(); + void Shutdown(); + + void Lock() + { + m_Mutex.lock(); + } + + void Unlock() + { + m_Mutex.unlock(); + } + + void SetState(bool bState) + { + m_bState = bState; + } + + bool GetState() const + { + return m_bState; + } + + void SetAllFatal(bool bState) + { + m_bAllExceptionsFatal = bState; + } + + bool GetAllFatal() const + { + return m_bAllExceptionsFatal; + } + + //----------------------------------------------------------------------------- + // Exception helpers + //----------------------------------------------------------------------------- + void SetExceptionInfos(EXCEPTION_POINTERS* pExceptionPointers); + + void SetCrashedModule(); - std::string runtimeInfo; + const CHAR* GetExceptionString() const; + const CHAR* GetExceptionString(DWORD dwExceptionCode) const; - int longestModuleNameLength; - int longestRelativeAddressLength; + bool IsExceptionFatal() const; + bool IsExceptionFatal(DWORD dwExceptionCode) const; + + //----------------------------------------------------------------------------- + // Formatting + //----------------------------------------------------------------------------- + void ShowPopUpMessage(); + + void FormatException(); + void FormatCallstack(); + void FormatFlags(const CHAR* pszRegister, DWORD nValue); + void FormatIntReg(const CHAR* pszRegister, DWORD64 nValue); + void FormatFloatReg(const CHAR* pszRegister, M128A nValue); + void FormatRegisters(); + void FormatLoadedMods(); + void FormatLoadedPlugins(); + void FormatModules(); + + //----------------------------------------------------------------------------- + // Minidump + //----------------------------------------------------------------------------- + void WriteMinidump(); + + private: + PVOID m_hExceptionFilter; + EXCEPTION_POINTERS* m_pExceptionInfos; + + bool m_bHasSetConsolehandler; + bool m_bAllExceptionsFatal; + bool m_bHasShownCrashMsg; + bool m_bState; + + std::string m_svCrashedModule; + std::string m_svCrashedOffset; + + std::string m_svError; + + std::mutex m_Mutex; }; + +extern CCrashHandler* g_pCrashHandler; From a040bff98c16b79a3e817af441a795cf4f407069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Raes?= Date: Mon, 9 Oct 2023 01:34:47 +0200 Subject: [PATCH 09/23] Create mod entry in `enabledmods.json` if it doesn't exist (#410) Currently, when you add a new mod to your `mods/` directory, when there's no associated entry in the `enabledmods.json` file, it is considered enabled by default; mod entries are only written in `enabledmods.json` when toggling them via the mods interface. In this pull request, I propose to create mod entries in `enabledmods.json` on startup when detecting they don't exist. --- NorthstarDLL/mods/modmanager.cpp | 22 ++++++++++++++++++++++ NorthstarDLL/mods/modmanager.h | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/NorthstarDLL/mods/modmanager.cpp b/NorthstarDLL/mods/modmanager.cpp index 1ca51ad7a..8ad832c1d 100644 --- a/NorthstarDLL/mods/modmanager.cpp +++ b/NorthstarDLL/mods/modmanager.cpp @@ -106,6 +106,9 @@ Mod::Mod(fs::path modDir, char* jsonBuf) ParseDependencies(modJson); ParseInitScript(modJson); + // A mod is remote if it's located in the remote mods folder + m_bIsRemote = m_ModDirectory.generic_string().find(GetRemoteModFolderPath().generic_string()) != std::string::npos; + m_bWasReadSuccessfully = true; } @@ -706,11 +709,21 @@ void ModManager::LoadMods() // sort by load prio, lowest-highest std::sort(m_LoadedMods.begin(), m_LoadedMods.end(), [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; }); + // This is used to check if some mods have a folder but no entry in enabledmods.json + bool newModsDetected = false; + for (Mod& mod : m_LoadedMods) { if (!mod.m_bEnabled) continue; + // Add mod entry to enabledmods.json if it doesn't exist + if (!mod.m_bIsRemote && !m_EnabledModsCfg.HasMember(mod.Name.c_str())) + { + m_EnabledModsCfg.AddMember(rapidjson_document::StringRefType(mod.Name.c_str()), true, m_EnabledModsCfg.GetAllocator()); + newModsDetected = true; + } + // register convars // for reloads, this is sorta barebones, when we have a good findconvar method, we could probably reset flags and stuff on // preexisting convars note: we don't delete convars if they already exist because they're used for script stuff, unfortunately this @@ -940,6 +953,15 @@ void ModManager::LoadMods() } } + // If there are new mods, we write entries accordingly in enabledmods.json + if (newModsDetected) + { + std::ofstream writeStream(GetNorthstarPrefix() + "/enabledmods.json"); + rapidjson::OStreamWrapper writeStreamWrapper(writeStream); + rapidjson::PrettyWriter writer(writeStreamWrapper); + m_EnabledModsCfg.Accept(writer); + } + // in a seperate loop because we register mod files in reverse order, since mods loaded later should have their files prioritised for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--) { diff --git a/NorthstarDLL/mods/modmanager.h b/NorthstarDLL/mods/modmanager.h index 15367e4da..813edec79 100644 --- a/NorthstarDLL/mods/modmanager.h +++ b/NorthstarDLL/mods/modmanager.h @@ -80,7 +80,7 @@ class Mod bool m_bEnabled = true; bool m_bWasReadSuccessfully = false; fs::path m_ModDirectory; - // bool m_bIsRemote; + bool m_bIsRemote; // mod.json stuff: From c1745b29ad4cae0279d905265cd3eadf79c46e12 Mon Sep 17 00:00:00 2001 From: F1F7Y <64418963+F1F7Y@users.noreply.github.com> Date: Tue, 10 Oct 2023 02:28:32 +0200 Subject: [PATCH 10/23] Add `mileslog_enable` convar (#515) Adds a convar to toggle whether we should log from the miles log func Most of these warnings are warnings about events being starved, in some cases slowing down the game due to the number of log calls. --- NorthstarDLL/client/audio.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NorthstarDLL/client/audio.cpp b/NorthstarDLL/client/audio.cpp index 9fcac9820..66d344046 100644 --- a/NorthstarDLL/client/audio.cpp +++ b/NorthstarDLL/client/audio.cpp @@ -16,6 +16,7 @@ extern "C" extern void* __fastcall Audio_GetParentEvent(); } +ConVar* Cvar_mileslog_enable; ConVar* Cvar_ns_print_played_sounds; CustomAudioManager g_CustomAudioManager; @@ -494,9 +495,17 @@ AUTOHOOK(MilesLog, client.dll + 0x57DAD0, void, __fastcall, (int level, const char* string)) // clang-format on { + if (!Cvar_mileslog_enable->GetBool()) + return; + spdlog::info("[MSS] {} - {}", level, string); } +ON_DLL_LOAD_RELIESON("engine.dll", MilesLogFuncHooks, ConVar, (CModule module)) +{ + Cvar_mileslog_enable = new ConVar("mileslog_enable", "0", FCVAR_NONE, "Enables/disables whether the mileslog func should be logged"); +} + ON_DLL_LOAD_CLIENT_RELIESON("client.dll", AudioHooks, ConVar, (CModule module)) { AUTOHOOK_DISPATCH() From 57201649e7a6b3448377b2a5d47c912ca02b5f84 Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:29:24 +0200 Subject: [PATCH 11/23] Fix incorrect help string text (#567) Help strings for `ns_server_name` and `ns_server_desc` were swapped. Co-authored-by: BobTheBob <32057864+BobTheBob9@users.noreply.github.com> --- NorthstarDLL/server/serverpresence.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NorthstarDLL/server/serverpresence.cpp b/NorthstarDLL/server/serverpresence.cpp index ed9185c10..159b9f30a 100644 --- a/NorthstarDLL/server/serverpresence.cpp +++ b/NorthstarDLL/server/serverpresence.cpp @@ -78,14 +78,14 @@ 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 description", 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, "This server's name", false, 0, false, 0, [](ConVar* cvar, const char* pOldValue, float flOldValue) { g_pServerPresence->SetName(UnescapeUnicode(g_pServerPresence->Cvar_ns_server_name->GetString())); // update engine hostname cvar 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 name", 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, "This server's description", false, 0, false, 0, [](ConVar* cvar, const char* pOldValue, float flOldValue) { g_pServerPresence->SetDescription(UnescapeUnicode(g_pServerPresence->Cvar_ns_server_desc->GetString())); }); From a9a1e0822c71486f0a9936d6aeeab78922f9198d Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Wed, 11 Oct 2023 15:41:34 +0200 Subject: [PATCH 12/23] Fixed mixed indentation in utils.cmake (#570) The file had mixed indentation (spaces and tabs) when really it should've been spaces only from the beginning --- cmake/utils.cmake | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/cmake/utils.cmake b/cmake/utils.cmake index e38b9eab3..0be097baa 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -1,19 +1,19 @@ # Check if a dependency exist before trying to init git submodules function(check_init_submodule path) - file(GLOB DIR_CONTENT path) - list(LENGTH RESULT CONTENT_COUNT) - if (CONTENT_COUNT EQUAL 0) - if (NOT EXISTS "${PROJECT_SOURCE_DIR}/.git") - message(FATAL_ERROR "Failed to find third party dependency in '${path}'") - endif() + file(GLOB DIR_CONTENT path) + list(LENGTH RESULT CONTENT_COUNT) + if (CONTENT_COUNT EQUAL 0) + if (NOT EXISTS "${PROJECT_SOURCE_DIR}/.git") + message(FATAL_ERROR "Failed to find third party dependency in '${path}'") + endif() - find_package(Git QUIET) - if (NOT Git_FOUND) - message(FATAL_ERROR "Failed to find Git, third party dependency could not be setup at `${path}") - endif() + find_package(Git QUIET) + if (NOT Git_FOUND) + message(FATAL_ERROR "Failed to find Git, third party dependency could not be setup at `${path}") + endif() - message(STATUS "Setting up dependencies as git submodules") + message(STATUS "Setting up dependencies as git submodules") execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} RESULT_VARIABLE GIT_SUBMOD_RESULT) @@ -21,5 +21,5 @@ function(check_init_submodule path) if(NOT GIT_SUBMOD_RESULT EQUAL "0") message(FATAL_ERROR "Initializing Git submodules failed with ${GIT_SUBMOD_RESULT}") endif() - endif() + endif() endfunction() From 14c01e829c5cf6fd8733d0e99822037629ee827f Mon Sep 17 00:00:00 2001 From: GeckoEidechse Date: Wed, 11 Oct 2023 15:44:55 +0200 Subject: [PATCH 13/23] Trim trailing whitespace --- cmake/utils.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/utils.cmake b/cmake/utils.cmake index 0be097baa..c331ac51b 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -5,7 +5,7 @@ function(check_init_submodule path) list(LENGTH RESULT CONTENT_COUNT) if (CONTENT_COUNT EQUAL 0) if (NOT EXISTS "${PROJECT_SOURCE_DIR}/.git") - message(FATAL_ERROR "Failed to find third party dependency in '${path}'") + message(FATAL_ERROR "Failed to find third party dependency in '${path}'") endif() find_package(Git QUIET) From 16f85420b38af8f75e6bed54cf2c79e61d3e45a2 Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:43:06 +0200 Subject: [PATCH 14/23] Make link to BUILD.md relative to README.md (#571) Make link to BUILD.md relative to README.md instead of a hardcoded link to relevant GitHub page --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 434231e6e..f789b4869 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Launcher used to modify Titanfall 2 to allow Northstar mods and custom content t ## Build -Check [BUILD.md](https://github.com/R2Northstar/NorthstarLauncher/blob/main/BUILD.md) for instructions on how to compile, you can also download [binaries built by GitHub Actions](https://github.com/R2Northstar/NorthstarLauncher/actions). +Check [BUILD.md](BUILD.md) for instructions on how to compile, you can also download [binaries built by GitHub Actions](https://github.com/R2Northstar/NorthstarLauncher/actions). ## Format From 9adf88f46d5baa93eadf0547f89f7b450b091164 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 11 Oct 2023 22:55:25 +0200 Subject: [PATCH 15/23] Correct submodule detection (#569) The contents of the submodules were incorrectly counted resulting in a git submodule update on every cmake invocation --- cmake/utils.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/utils.cmake b/cmake/utils.cmake index c331ac51b..d84505514 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -1,8 +1,8 @@ # Check if a dependency exist before trying to init git submodules function(check_init_submodule path) - file(GLOB DIR_CONTENT path) - list(LENGTH RESULT CONTENT_COUNT) + file(GLOB DIR_CONTENT "${path}/*") + list(LENGTH DIR_CONTENT CONTENT_COUNT) if (CONTENT_COUNT EQUAL 0) if (NOT EXISTS "${PROJECT_SOURCE_DIR}/.git") message(FATAL_ERROR "Failed to find third party dependency in '${path}'") From 99c5c86e188e090c93a9778da3402d62409ece8b Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 12 Oct 2023 03:01:47 +0200 Subject: [PATCH 16/23] Update libcurl to 8.4.0 (#568) Addresses: - https://curl.se/docs/CVE-2023-38545.html - https://curl.se/docs/CVE-2023-38546.html --- thirdparty/libcurl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thirdparty/libcurl b/thirdparty/libcurl index 801bd5138..d755a5f7c 160000 --- a/thirdparty/libcurl +++ b/thirdparty/libcurl @@ -1 +1 @@ -Subproject commit 801bd5138ce31aa0d906fa4e2eabfc599d74e793 +Subproject commit d755a5f7c009dd63a61b2c745180d8ba937cbfeb From 2dd19ab51f7b95f7a5cf1ac8b9e6635a918f6b3c Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Sat, 14 Oct 2023 21:55:56 +0100 Subject: [PATCH 17/23] Add workflow for adding PRs and Issues to the project board (#572) Automatically adds all opened issues and pull requests to the project board. --- .github/workflows/add-to-project.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/add-to-project.yml diff --git a/.github/workflows/add-to-project.yml b/.github/workflows/add-to-project.yml new file mode 100644 index 000000000..f0414d7cf --- /dev/null +++ b/.github/workflows/add-to-project.yml @@ -0,0 +1,20 @@ +name: add-to-project + +on: + issues: + types: + - opened + pull_request: + types: + - opened + +jobs: + add-to-project: + name: Add to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v0.5.0 + with: + project-url: "https://github.com/orgs/R2Northstar/projects/3" + github-token: "${{ secrets.PROJECT_BOARD_TOKEN }}" + From db5ef8d6b000e66304e323fda02c1b72246ff2ca Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Sun, 15 Oct 2023 02:05:58 +0200 Subject: [PATCH 18/23] Only run `add-to-project` action on issues (#577) Only run on issues PRs from forks are triggered from that fork which means that they don't have access to the necessary token --- .github/workflows/add-to-project.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/add-to-project.yml b/.github/workflows/add-to-project.yml index f0414d7cf..a51b8cece 100644 --- a/.github/workflows/add-to-project.yml +++ b/.github/workflows/add-to-project.yml @@ -4,9 +4,6 @@ on: issues: types: - opened - pull_request: - types: - - opened jobs: add-to-project: From fb7b538c2fc772fa5fcbecc20593d5b3f4622a35 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 15 Oct 2023 11:22:03 +0200 Subject: [PATCH 19/23] Revert "Update libcurl to 8.4.0" (#574) Newer releases of libcurl break the docker container This reverts commit 99c5c86e188e090c93a9778da3402d62409ece8b. --- thirdparty/libcurl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thirdparty/libcurl b/thirdparty/libcurl index d755a5f7c..801bd5138 160000 --- a/thirdparty/libcurl +++ b/thirdparty/libcurl @@ -1 +1 @@ -Subproject commit d755a5f7c009dd63a61b2c745180d8ba937cbfeb +Subproject commit 801bd5138ce31aa0d906fa4e2eabfc599d74e793 From 98af0dcbb45972d4af22ea0d26a3a2c518ea9b13 Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Sun, 15 Oct 2023 20:57:41 +0100 Subject: [PATCH 20/23] Move plugin communication handler init out of plugin manager init (#581) Plugin state requires this to be initialised even if `-noplugins` is passed, but if `-noplugins` is passed, it doesn't get initialised. Moving this to `dllmain` guarantees that it is initialised properly and prevents #580 --- NorthstarDLL/dllmain.cpp | 2 ++ NorthstarDLL/plugins/pluginbackend.cpp | 6 ------ NorthstarDLL/plugins/pluginbackend.h | 4 +--- NorthstarDLL/plugins/plugins.cpp | 2 -- 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/NorthstarDLL/dllmain.cpp b/NorthstarDLL/dllmain.cpp index cfcc1a8c3..1a80b3edb 100644 --- a/NorthstarDLL/dllmain.cpp +++ b/NorthstarDLL/dllmain.cpp @@ -5,6 +5,7 @@ #include "config/profile.h" #include "plugins/plugin_abi.h" #include "plugins/plugins.h" +#include "plugins/pluginbackend.h" #include "util/version.h" #include "squirrel/squirrel.h" #include "shared/gamepresence.h" @@ -62,6 +63,7 @@ bool InitialiseNorthstar() g_pGameStatePresence = new GameStatePresence(); g_pPluginManager = new PluginManager(); + g_pPluginCommunicationhandler = new PluginCommunicationHandler(); g_pPluginManager->LoadPlugins(); InitialiseSquirrelManagers(); diff --git a/NorthstarDLL/plugins/pluginbackend.cpp b/NorthstarDLL/plugins/pluginbackend.cpp index 091ddd2f8..f97662358 100644 --- a/NorthstarDLL/plugins/pluginbackend.cpp +++ b/NorthstarDLL/plugins/pluginbackend.cpp @@ -15,12 +15,6 @@ PluginCommunicationHandler* g_pPluginCommunicationhandler; static PluginDataRequest storedRequest {PluginDataRequestType::END, (PluginRespondDataCallable) nullptr}; -void init_plugincommunicationhandler() -{ - g_pPluginCommunicationhandler = new PluginCommunicationHandler; - g_pPluginCommunicationhandler->requestQueue = {}; -} - void PluginCommunicationHandler::RunFrame() { std::lock_guard lock(requestMutex); diff --git a/NorthstarDLL/plugins/pluginbackend.h b/NorthstarDLL/plugins/pluginbackend.h index 898b3a994..bcbccbd8e 100644 --- a/NorthstarDLL/plugins/pluginbackend.h +++ b/NorthstarDLL/plugins/pluginbackend.h @@ -33,12 +33,10 @@ class PluginCommunicationHandler void GeneratePresenceObjects(); public: - std::queue requestQueue; + std::queue requestQueue = {}; std::mutex requestMutex; PluginEngineData m_sEngineData {}; }; -void init_plugincommunicationhandler(); - extern PluginCommunicationHandler* g_pPluginCommunicationhandler; diff --git a/NorthstarDLL/plugins/plugins.cpp b/NorthstarDLL/plugins/plugins.cpp index 0c4c7fd60..5e28e12d0 100644 --- a/NorthstarDLL/plugins/plugins.cpp +++ b/NorthstarDLL/plugins/plugins.cpp @@ -226,8 +226,6 @@ bool PluginManager::LoadPlugins() funcs.relayInviteFunc = nullptr; funcs.createObject = CreateObject; - init_plugincommunicationhandler(); - data.version = ns_version.c_str(); data.northstarModule = g_NorthstarModule; From f24089e2d79443962c736ff4651d97e7a97bd952 Mon Sep 17 00:00:00 2001 From: H0L0 Date: Mon, 16 Oct 2023 06:12:02 +1000 Subject: [PATCH 21/23] Add check for r2/maps folder (#575) Adds a fix to check if `r2/maps` folder exists before trying to iterate through it. --- NorthstarDLL/util/printmaps.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/NorthstarDLL/util/printmaps.cpp b/NorthstarDLL/util/printmaps.cpp index dd825bff3..5b406c4c8 100644 --- a/NorthstarDLL/util/printmaps.cpp +++ b/NorthstarDLL/util/printmaps.cpp @@ -104,7 +104,13 @@ void RefreshMapList() } // get maps in game dir - for (fs::directory_entry file : fs::directory_iterator(fmt::format("{}/maps", R2::g_pModName))) + std::string gameDir = fmt::format("{}/maps", R2::g_pModName); + if (!std::filesystem::exists(gameDir)) + { + return; + } + + for (fs::directory_entry file : fs::directory_iterator(gameDir)) { if (file.path().extension() == ".bsp") { From f763e66a1ffcb4cd8a06e30ad9de15a4c5139f4b Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 15 Oct 2023 22:18:19 +0200 Subject: [PATCH 22/23] Fix proton detection output (#534) Fixes Proton detection logic printing incorrect basename --- NorthstarDLL/logging/logging.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/NorthstarDLL/logging/logging.cpp b/NorthstarDLL/logging/logging.cpp index a68fe7046..3416bb8cb 100644 --- a/NorthstarDLL/logging/logging.cpp +++ b/NorthstarDLL/logging/logging.cpp @@ -261,12 +261,22 @@ void StartupLog() spdlog::info("Operating System: {} (Wine)", sysname); spdlog::info("Wine build: {}", wine_get_build_id()); + // STEAM_COMPAT_TOOL_PATHS is a colon separated lists of all compat tool paths used + // The first one tends to be the Proton path itself + // We extract the basename out of it to get the name used char* compatToolPtr = std::getenv("STEAM_COMPAT_TOOL_PATHS"); if (compatToolPtr) { - std::string compatToolPath(compatToolPtr); + std::string_view compatToolPath(compatToolPtr); - spdlog::info("Proton build: {}", compatToolPath.substr(compatToolPath.rfind("/") + 1)); + auto protonBasenameEnd = compatToolPath.find(":"); + if (protonBasenameEnd == std::string_view::npos) + protonBasenameEnd = 0; + auto protonBasenameStart = compatToolPath.rfind("/", protonBasenameEnd) + 1; + if (protonBasenameStart == std::string_view::npos) + protonBasenameStart = 0; + + spdlog::info("Proton build: {}", compatToolPath.substr(protonBasenameStart, protonBasenameEnd - protonBasenameStart)); } } else From d463a434746cab295c4db57001650b623bd481b1 Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:51:24 +0100 Subject: [PATCH 23/23] Use 4th digit in version number for CI build number (#541) Sets the 4th digit of the version number to the current CI run number. This is done to more easily link a binary back to the PR or commit it came from. --- .github/workflows/ci.yml | 8 ++++++++ NorthstarDLL/util/version.cpp | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 897551749..09af4df79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,7 @@ on: [push, pull_request] env: BUILD_PROFILE: Release + NORTHSTAR_VERSION: 0.0.0.${{github.run_number}} jobs: build: @@ -16,6 +17,13 @@ jobs: uses: ilammy/msvc-dev-cmd@v1 - name: Configure cmake run: cmake -G "Ninja" -DCMAKE_BUILD_TYPE:STRING="${{ env.BUILD_PROFILE }}" + - name: Setup resource file version + shell: bash + run: | + sed -i 's/DEV/${{ env.NORTHSTAR_VERSION }}/g' NorthstarLauncher/resources.rc + sed -i 's/DEV/${{ env.NORTHSTAR_VERSION }}/g' NorthstarDLL/resources.rc + FILEVERSION=$(echo ${{ env.NORTHSTAR_VERSION }} | tr '.' ',' | sed -E 's/-rc[0-9]+//' | tr -d '[:alpha:]') + sed -i "s/0,0,0,1/${FILEVERSION}/g" NorthstarDLL/ns_version.h - name: Build run: cmake --build . - name: Extract Short Commit Hash diff --git a/NorthstarDLL/util/version.cpp b/NorthstarDLL/util/version.cpp index 1ad624384..a947cde18 100644 --- a/NorthstarDLL/util/version.cpp +++ b/NorthstarDLL/util/version.cpp @@ -11,9 +11,9 @@ void InitialiseVersion() int ua_len = 0; // We actually use the rightmost integer do determine whether or not we're a debug/dev build - // If it is set to 1, we are a dev build - // On github CI, we set this 1 to a 0 automatically as we replace the 0,0,0,1 with the real version number - if (northstar_version[3] == 1) + // If it is set to a non-zero value, we are a dev build + // On github CI, we set this to a 0 automatically as we replace the 0,0,0,1 with the real version number + if (northstar_version[3]) { sprintf(version, "%d.%d.%d.%d+dev", northstar_version[0], northstar_version[1], northstar_version[2], northstar_version[3]); ua_len += snprintf(