diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1adef225593..97e26b3060a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2338,6 +2338,8 @@ if(CLIENT)
checksum.h
client.cpp
client.h
+ crash_handler.cpp
+ crash_handler.h
demoedit.cpp
demoedit.h
discord.cpp
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
index 733f74c6d7b..b1ccd627ccb 100644
--- a/src/engine/client/client.cpp
+++ b/src/engine/client/client.cpp
@@ -57,6 +57,7 @@
#include "friends.h"
#include "notifications.h"
#include "serverbrowser.h"
+#include "crash_handler.h"
#if defined(CONF_VIDEORECORDER)
#include "video.h"
@@ -4932,6 +4933,12 @@ int main(int argc, const char **argv)
}
g_Config.m_ClConfigVersion = 1;
+#if defined(CONF_FAMILY_WINDOWS)
+ if (g_Config.m_ClWriteCrashDump > 0) {
+ InitCrashHandler(g_Config.m_ClWriteCrashDump == 2);
+ }
+#endif
+
// parse the command line arguments
pConsole->SetUnknownCommandCallback(UnknownArgumentCallback, pClient);
pConsole->ParseArguments(argc - 1, &argv[1]);
diff --git a/src/engine/client/crash_handler.cpp b/src/engine/client/crash_handler.cpp
new file mode 100644
index 00000000000..b265ced9fe4
--- /dev/null
+++ b/src/engine/client/crash_handler.cpp
@@ -0,0 +1,99 @@
+#include "crash_handler.h"
+
+#include
+
+#if defined(CONF_FAMILY_WINDOWS)
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#pragma comment(lib, "dbghelp.lib")
+#endif
+
+namespace
+{
+bool g_useFullMemoryDump = false;
+}
+
+#if defined(CONF_FAMILY_WINDOWS)
+static LPTOP_LEVEL_EXCEPTION_FILTER g_prevExceptionFilter = nullptr;
+
+std::wstring getExecutableName()
+{
+ wchar_t path[MAX_PATH];
+ GetModuleFileNameW(NULL, path, MAX_PATH);
+ std::wstring fullPath(path);
+ size_t pos = fullPath.find_last_of(L"\\/");
+ return (pos != std::wstring::npos) ? fullPath.substr(pos + 1) : fullPath;
+}
+
+std::wstring GenerateDumpFilename()
+{
+ SYSTEMTIME st;
+ GetLocalTime(&st);
+
+ std::wostringstream oss;
+ oss << getExecutableName()
+ << L"_"
+ << st.wYear << L"-"
+ << std::setw(2) << std::setfill(L'0') << st.wMonth << L"-"
+ << std::setw(2) << std::setfill(L'0') << st.wDay << L"_"
+ << std::setw(2) << std::setfill(L'0') << st.wHour << L"-"
+ << std::setw(2) << std::setfill(L'0') << st.wMinute << L"-"
+ << std::setw(2) << std::setfill(L'0') << st.wSecond;
+
+ if (g_useFullMemoryDump)
+ oss << L"_full";
+
+ oss << L".dmp";
+
+ return oss.str();
+}
+
+LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* pExceptionPointers)
+{
+ std::wstring dumpFilename = GenerateDumpFilename();
+
+ HANDLE hFile = CreateFileW(dumpFilename.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
+
+ if (hFile != INVALID_HANDLE_VALUE) {
+ MINIDUMP_EXCEPTION_INFORMATION mdei;
+ mdei.ThreadId = GetCurrentThreadId();
+ mdei.ExceptionPointers = pExceptionPointers;
+ mdei.ClientPointers = FALSE;
+
+ MINIDUMP_TYPE dumpType = g_useFullMemoryDump
+ ? MiniDumpWithFullMemory
+ : MiniDumpNormal;
+
+ BOOL result = MiniDumpWriteDump(
+ GetCurrentProcess(),
+ GetCurrentProcessId(),
+ hFile,
+ dumpType,
+ &mdei,
+ nullptr,
+ nullptr
+ );
+
+ CloseHandle(hFile);
+ }
+
+ if (g_prevExceptionFilter)
+ return g_prevExceptionFilter(pExceptionPointers);
+
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+void InitCrashHandler(bool full)
+{
+ g_useFullMemoryDump = full;
+ g_prevExceptionFilter = SetUnhandledExceptionFilter(UnhandledExceptionHandler);
+}
+#else
+void InitCrashHandler(bool full) {}
+#endif
\ No newline at end of file
diff --git a/src/engine/client/crash_handler.h b/src/engine/client/crash_handler.h
new file mode 100644
index 00000000000..2f883c7bdac
--- /dev/null
+++ b/src/engine/client/crash_handler.h
@@ -0,0 +1,6 @@
+#ifndef ENGINE_CLIENT_CRASH_HANDLER_H
+#define ENGINE_CLIENT_CRASH_HANDLER_H
+
+void InitCrashHandler(bool full);
+
+#endif
\ No newline at end of file
diff --git a/src/engine/shared/config_variables_tclient.h b/src/engine/shared/config_variables_tclient.h
index 042ee791660..c8159a9d105 100644
--- a/src/engine/shared/config_variables_tclient.h
+++ b/src/engine/shared/config_variables_tclient.h
@@ -238,3 +238,7 @@ MACRO_CONFIG_INT(ClTClientSettingsTabs, tc_tclient_settings_tabs, 0, 0, 65536, C
// Mod
MACRO_CONFIG_INT(ClShowPlayerHitBoxes, tc_show_player_hit_boxes, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show player hit boxes (1 = predicted, 2 = predicted and unpredicted)")
MACRO_CONFIG_INT(ClHideChatBubbles, tc_hide_chat_bubbles, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Hide your own chat bubbles, only works when authed in remote console")
+
+#if defined(CONF_FAMILY_WINDOWS)
+MACRO_CONFIG_INT(ClWriteCrashDump, tc_write_crash_dump, 0, 0, 2, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Whether to write a crash dump on crash (0 = off, 1 = on/normal, 2 = full")
+#endif
\ No newline at end of file