diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt
index faebd73f6cc..95748f544ad 100644
--- a/Core/GameEngine/CMakeLists.txt
+++ b/Core/GameEngine/CMakeLists.txt
@@ -72,6 +72,7 @@ set(GAMEENGINE_SRC
# Include/Common/MapObject.h
# Include/Common/MapReaderWriterInfo.h
# Include/Common/MessageStream.h
+ Include/Common/MiniDumper.h
# Include/Common/MiniLog.h
Include/Common/MiscAudio.h
# Include/Common/MissionStats.h
@@ -660,6 +661,7 @@ set(GAMEENGINE_SRC
# Source/Common/System/List.cpp
Source/Common/System/LocalFile.cpp
Source/Common/System/LocalFileSystem.cpp
+ Source/Common/System/MiniDumper.cpp
Source/Common/System/ObjectStatusTypes.cpp
# Source/Common/System/QuotedPrintable.cpp
Source/Common/System/Radar.cpp
diff --git a/Core/GameEngine/Include/Common/MiniDumper.h b/Core/GameEngine/Include/Common/MiniDumper.h
new file mode 100644
index 00000000000..44c03e96ef1
--- /dev/null
+++ b/Core/GameEngine/Include/Common/MiniDumper.h
@@ -0,0 +1,97 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2025 TheSuperHackers
+**
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program. If not, see .
+*/
+
+#pragma once
+
+#ifdef RTS_ENABLE_CRASHDUMP
+#include "DbgHelpLoader.h"
+
+enum DumpType CPP_11(: Char)
+{
+ // Smallest dump type with call stacks and some supporting variables
+ DumpType_Minimal = 'M',
+ // Largest dump size including complete memory contents of the process
+ DumpType_Full = 'F',
+};
+
+class MiniDumper
+{
+ enum MiniDumperExitCode CPP_11(: Int)
+ {
+ MiniDumperExitCode_Success = 0x0,
+ MiniDumperExitCode_FailureWait = 0x37DA1040,
+ MiniDumperExitCode_FailureParam = 0x4EA527BB,
+ MiniDumperExitCode_ForcedTerminate = 0x158B1154,
+ };
+
+public:
+ MiniDumper();
+ Bool IsInitialized() const;
+ void TriggerMiniDump(DumpType dumpType);
+ void TriggerMiniDumpForException(_EXCEPTION_POINTERS* e_info, DumpType dumpType);
+ static void initMiniDumper(const AsciiString& userDirPath);
+ static void shutdownMiniDumper();
+ static LONG WINAPI DumpingExceptionFilter(_EXCEPTION_POINTERS* e_info);
+
+private:
+ void Initialize(const AsciiString& userDirPath);
+ void ShutDown();
+ void CreateMiniDump(DumpType dumpType);
+ void CleanupResources();
+ Bool IsDumpThreadStillRunning() const;
+ void ShutdownDumpThread();
+
+ // Thread procs
+ static DWORD WINAPI MiniDumpThreadProc(LPVOID lpParam);
+ DWORD ThreadProcInternal();
+
+ // Dump file directory bookkeeping
+ Bool InitializeDumpDirectory(const AsciiString& userDirPath);
+ static void KeepNewestFiles(const std::string& directory, const DumpType dumpType, const Int keepCount);
+
+ // Struct to hold file information
+ struct FileInfo
+ {
+ std::string name;
+ FILETIME lastWriteTime;
+ };
+
+ static bool CompareByLastWriteTime(const FileInfo& a, const FileInfo& b);
+
+private:
+ Bool m_miniDumpInitialized;
+ Bool m_loadedDbgHelp;
+ DumpType m_requestedDumpType;
+
+ // Path buffers
+ Char m_dumpDir[MAX_PATH];
+ Char m_dumpFile[MAX_PATH];
+ WideChar m_executablePath[MAX_PATH];
+
+ // Event handles
+ HANDLE m_dumpRequested;
+ HANDLE m_dumpComplete;
+ HANDLE m_quitting;
+
+ // Thread handles
+ HANDLE m_dumpThread;
+ DWORD m_dumpThreadId;
+};
+
+extern MiniDumper* TheMiniDumper;
+#endif
diff --git a/Core/GameEngine/Source/Common/System/Debug.cpp b/Core/GameEngine/Source/Common/System/Debug.cpp
index 385dd856161..33ddbff4606 100644
--- a/Core/GameEngine/Source/Common/System/Debug.cpp
+++ b/Core/GameEngine/Source/Common/System/Debug.cpp
@@ -70,6 +70,9 @@
#if defined(DEBUG_STACKTRACE) || defined(IG_DEBUG_STACKTRACE)
#include "Common/StackDump.h"
#endif
+#ifdef RTS_ENABLE_CRASHDUMP
+#include "Common/MiniDumper.h"
+#endif
// Horrible reference, but we really, really need to know if we are windowed.
extern bool DX8Wrapper_IsWindowed;
@@ -727,6 +730,22 @@ double SimpleProfiler::getAverageTime()
}
}
+
+static void TriggerMiniDump()
+{
+#ifdef RTS_ENABLE_CRASHDUMP
+ if (TheMiniDumper && TheMiniDumper->IsInitialized())
+ {
+ // Create both minimal and full memory dumps
+ TheMiniDumper->TriggerMiniDump(DumpType_Minimal);
+ TheMiniDumper->TriggerMiniDump(DumpType_Full);
+ }
+
+ MiniDumper::shutdownMiniDumper();
+#endif
+}
+
+
void ReleaseCrash(const char *reason)
{
/// do additional reporting on the crash, if possible
@@ -737,6 +756,8 @@ void ReleaseCrash(const char *reason)
}
}
+ TriggerMiniDump();
+
char prevbuf[ _MAX_PATH ];
char curbuf[ _MAX_PATH ];
@@ -813,6 +834,8 @@ void ReleaseCrashLocalized(const AsciiString& p, const AsciiString& m)
return;
}
+ TriggerMiniDump();
+
UnicodeString prompt = TheGameText->fetch(p);
UnicodeString mesg = TheGameText->fetch(m);
diff --git a/Core/GameEngine/Source/Common/System/MiniDumper.cpp b/Core/GameEngine/Source/Common/System/MiniDumper.cpp
new file mode 100644
index 00000000000..21ab80e578a
--- /dev/null
+++ b/Core/GameEngine/Source/Common/System/MiniDumper.cpp
@@ -0,0 +1,461 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2025 TheSuperHackers
+**
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program. If not, see .
+*/
+
+#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
+
+#ifdef RTS_ENABLE_CRASHDUMP
+#include "Common/MiniDumper.h"
+#include
+#include "gitinfo.h"
+
+// Globals for storing the pointer to the exception
+_EXCEPTION_POINTERS* g_dumpException = NULL;
+DWORD g_dumpExceptionThreadId = 0;
+
+MiniDumper* TheMiniDumper = NULL;
+
+// Globals containing state about the current exception that's used for context in the mini dump.
+// These are populated by MiniDumper::DumpingExceptionFilter to store a copy of the exception in case it goes out of scope
+_EXCEPTION_POINTERS g_exceptionPointers = { 0 };
+EXCEPTION_RECORD g_exceptionRecord = { 0 };
+CONTEXT g_exceptionContext = { 0 };
+
+constexpr const char* DumpFileNamePrefix = "Crash";
+
+void MiniDumper::initMiniDumper(const AsciiString& userDirPath)
+{
+ DEBUG_ASSERTCRASH(TheMiniDumper == NULL, ("MiniDumper::initMiniDumper called on already created instance"));
+
+ // Use placement new on the process heap so TheMiniDumper is placed outside the MemoryPoolFactory managed area.
+ // If the crash is due to corrupted MemoryPoolFactory structures, try to mitigate the chances of MiniDumper memory also being corrupted
+ TheMiniDumper = new (::HeapAlloc(::GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, sizeof(MiniDumper))) MiniDumper;
+ TheMiniDumper->Initialize(userDirPath);
+}
+
+void MiniDumper::shutdownMiniDumper()
+{
+ if (TheMiniDumper)
+ {
+ TheMiniDumper->ShutDown();
+ TheMiniDumper->~MiniDumper();
+ ::HeapFree(::GetProcessHeap(), NULL, TheMiniDumper);
+ TheMiniDumper = NULL;
+ }
+}
+
+MiniDumper::MiniDumper()
+{
+ m_miniDumpInitialized = false;
+ m_loadedDbgHelp = false;
+ m_requestedDumpType = DumpType_Minimal;
+ m_dumpRequested = NULL;
+ m_dumpComplete = NULL;
+ m_quitting = NULL;
+ m_dumpThread = NULL;
+ m_dumpThreadId = 0;
+ m_dumpDir[0] = 0;
+ m_dumpFile[0] = 0;
+ m_executablePath[0] = 0;
+};
+
+LONG WINAPI MiniDumper::DumpingExceptionFilter(_EXCEPTION_POINTERS* e_info)
+{
+ // Store the exception info in the global variables for later use by the dumping thread
+ g_exceptionRecord = *(e_info->ExceptionRecord);
+ g_exceptionContext = *(e_info->ContextRecord);
+ g_exceptionPointers.ContextRecord = &g_exceptionContext;
+ g_exceptionPointers.ExceptionRecord = &g_exceptionRecord;
+ g_dumpException = &g_exceptionPointers;
+
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+void MiniDumper::TriggerMiniDump(DumpType dumpType)
+{
+ if (!m_miniDumpInitialized)
+ {
+ DEBUG_LOG(("MiniDumper::TriggerMiniDump: Attempted to use an uninitialized instance."));
+ return;
+ }
+
+ __try
+ {
+ // Use DebugBreak to raise an exception that can be caught in the __except block
+ ::DebugBreak();
+ }
+ __except (DumpingExceptionFilter(GetExceptionInformation()))
+ {
+ TriggerMiniDumpForException(g_dumpException, dumpType);
+ }
+}
+
+void MiniDumper::TriggerMiniDumpForException(_EXCEPTION_POINTERS* e_info, DumpType dumpType)
+{
+ if (!m_miniDumpInitialized)
+ {
+ DEBUG_LOG(("MiniDumper::TriggerMiniDumpForException: Attempted to use an uninitialized instance."));
+ return;
+ }
+
+ g_dumpException = e_info;
+ g_dumpExceptionThreadId = ::GetCurrentThreadId();
+ m_requestedDumpType = dumpType;
+
+ DEBUG_ASSERTCRASH(IsDumpThreadStillRunning(), ("MiniDumper::TriggerMiniDumpForException: Dumping thread has exited."));
+ ::SetEvent(m_dumpRequested);
+ DWORD wait = ::WaitForSingleObject(m_dumpComplete, INFINITE);
+ if (wait != WAIT_OBJECT_0)
+ {
+ if (wait == WAIT_FAILED)
+ {
+ DEBUG_LOG(("MiniDumper::TriggerMiniDumpForException: Waiting for minidump triggering failed: status=%u, error=%u", wait, ::GetLastError()));
+ }
+ else
+ {
+ DEBUG_LOG(("MiniDumper::TriggerMiniDumpForException: Waiting for minidump triggering failed: status=%u", wait));
+ }
+ }
+
+ ::ResetEvent(m_dumpComplete);
+}
+
+void MiniDumper::Initialize(const AsciiString& userDirPath)
+{
+ m_loadedDbgHelp = DbgHelpLoader::load();
+
+ // We want to only use the dbghelp.dll from the OS installation, as the one bundled with the game does not support MiniDump functionality
+ if (!(m_loadedDbgHelp && DbgHelpLoader::isLoadedFromSystem()))
+ {
+ DEBUG_LOG(("MiniDumper::Initialize: Unable to load system-provided dbghelp.dll, minidump functionality disabled."));
+ return;
+ }
+
+ DWORD executableSize = ::GetModuleFileNameW(NULL, m_executablePath, ARRAY_SIZE(m_executablePath));
+ if (executableSize == 0 || executableSize == ARRAY_SIZE(m_executablePath))
+ {
+ DEBUG_LOG(("MiniDumper::Initialize: Could not get executable file name. Returned value=%u", executableSize));
+ return;
+ }
+
+ // Create & store dump folder
+ if (!InitializeDumpDirectory(userDirPath))
+ {
+ return;
+ }
+
+ m_dumpRequested = CreateEvent(NULL, TRUE, FALSE, NULL);
+ m_dumpComplete = CreateEvent(NULL, TRUE, FALSE, NULL);
+ m_quitting = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (m_dumpRequested == NULL || m_dumpComplete == NULL || m_quitting == NULL)
+ {
+ // Something went wrong with the creation of the events..
+ DEBUG_LOG(("MiniDumper::Initialize: Unable to create events: error=%u", ::GetLastError()));
+ return;
+ }
+
+ m_dumpThread = ::CreateThread(NULL, 0, MiniDumpThreadProc, this, CREATE_SUSPENDED, &m_dumpThreadId);
+ if (!m_dumpThread)
+ {
+ DEBUG_LOG(("MiniDumper::Initialize: Unable to create thread: error=%u", ::GetLastError()));
+ return;
+ }
+
+ if (::ResumeThread(m_dumpThread) != 1)
+ {
+ DEBUG_LOG(("MiniDumper::Initialize: Unable to resume thread: error=%u", ::GetLastError()));
+ return;
+ }
+
+ DEBUG_LOG(("MiniDumper::Initialize: Configured to store crash dumps in '%s'", m_dumpDir));
+ m_miniDumpInitialized = true;
+}
+
+Bool MiniDumper::IsInitialized() const
+{
+ return m_miniDumpInitialized;
+}
+
+Bool MiniDumper::IsDumpThreadStillRunning() const
+{
+ DWORD exitCode;
+ if (m_dumpThread != NULL && ::GetExitCodeThread(m_dumpThread, &exitCode) && exitCode == STILL_ACTIVE)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+Bool MiniDumper::InitializeDumpDirectory(const AsciiString& userDirPath)
+{
+ constexpr const Int MaxFullFileCount = 2;
+ constexpr const Int MaxMiniFileCount = 10;
+
+ strlcpy(m_dumpDir, userDirPath.str(), ARRAY_SIZE(m_dumpDir));
+ strlcat(m_dumpDir, "CrashDumps\\", ARRAY_SIZE(m_dumpDir));
+ if (::_access(m_dumpDir, 0) != 0)
+ {
+ if (!::CreateDirectory(m_dumpDir, NULL))
+ {
+ DEBUG_LOG(("MiniDumper::Initialize: Unable to create path for crash dumps at '%s': error=%u", m_dumpDir, ::GetLastError()));
+ return false;
+ }
+ }
+
+ // Clean up old files (we keep a maximum of 10 small, 2 full)
+ KeepNewestFiles(m_dumpDir, DumpType_Full, MaxFullFileCount);
+ KeepNewestFiles(m_dumpDir, DumpType_Minimal, MaxMiniFileCount);
+
+ return true;
+}
+
+void MiniDumper::ShutdownDumpThread()
+{
+ if (IsDumpThreadStillRunning())
+ {
+ DEBUG_ASSERTCRASH(m_quitting != NULL, ("MiniDumper::ShutdownDumpThread: Dump thread still running despite m_quitting being NULL"));
+ ::SetEvent(m_quitting);
+
+ DWORD waitRet = ::WaitForSingleObject(m_dumpThread, 3000);
+ switch (waitRet)
+ {
+ case WAIT_OBJECT_0:
+ // Wait for thread exit was successful
+ break;
+ case WAIT_TIMEOUT:
+ DEBUG_LOG(("MiniDumper::ShutdownDumpThread: Waiting for dumping thread to exit timed out, killing thread", waitRet));
+ ::TerminateThread(m_dumpThread, MiniDumperExitCode_ForcedTerminate);
+ break;
+ case WAIT_FAILED:
+ DEBUG_LOG(("MiniDumper::ShutdownDumpThread: Waiting for minidump triggering failed: status=%u, error=%u", waitRet, ::GetLastError()));
+ break;
+ default:
+ DEBUG_LOG(("MiniDumper::ShutdownDumpThread: Waiting for minidump triggering failed: status=%u", waitRet));
+ break;
+ }
+ }
+}
+
+void MiniDumper::ShutDown()
+{
+ ShutdownDumpThread();
+
+ if (m_dumpThread != NULL)
+ {
+ DEBUG_ASSERTCRASH(!IsDumpThreadStillRunning(), ("MiniDumper::ShutDown: ShutdownDumpThread() was unable to stop Dump thread"));
+ ::CloseHandle(m_dumpThread);
+ m_dumpThread = NULL;
+ }
+
+ if (m_quitting != NULL)
+ {
+ ::CloseHandle(m_quitting);
+ m_quitting = NULL;
+ }
+
+ if (m_dumpComplete != NULL)
+ {
+ ::CloseHandle(m_dumpComplete);
+ m_dumpComplete = NULL;
+ }
+
+ if (m_dumpRequested != NULL)
+ {
+ ::CloseHandle(m_dumpRequested);
+ m_dumpRequested = NULL;
+ }
+
+ if (m_loadedDbgHelp)
+ {
+ DbgHelpLoader::unload();
+ m_loadedDbgHelp = false;
+ }
+
+ m_miniDumpInitialized = false;
+}
+
+DWORD MiniDumper::ThreadProcInternal()
+{
+ while (true)
+ {
+ HANDLE waitEvents[2] = { m_dumpRequested, m_quitting };
+ DWORD event = ::WaitForMultipleObjects(ARRAY_SIZE(waitEvents), waitEvents, FALSE, INFINITE);
+ switch (event)
+ {
+ case WAIT_OBJECT_0 + 0:
+ // A dump is requested (m_dumpRequested)
+ ::ResetEvent(m_dumpComplete);
+ CreateMiniDump(m_requestedDumpType);
+ ::ResetEvent(m_dumpRequested);
+ ::SetEvent(m_dumpComplete);
+ break;
+ case WAIT_OBJECT_0 + 1:
+ // Quit (m_quitting)
+ return MiniDumperExitCode_Success;
+ case WAIT_FAILED:
+ DEBUG_LOG(("MiniDumper::ThreadProcInternal: Waiting for events failed: status=%u, error=%u", event, ::GetLastError()));
+ return MiniDumperExitCode_FailureWait;
+ default:
+ DEBUG_LOG(("MiniDumper::ThreadProcInternal: Waiting for events failed: status=%u", event));
+ return MiniDumperExitCode_FailureWait;
+ }
+ }
+}
+
+DWORD WINAPI MiniDumper::MiniDumpThreadProc(LPVOID lpParam)
+{
+ if (lpParam == NULL)
+ {
+ DEBUG_LOG(("MiniDumper::MiniDumpThreadProc: The provided parameter was NULL, exiting thread."));
+ return MiniDumperExitCode_FailureParam;
+ }
+
+ MiniDumper* dumper = static_cast(lpParam);
+ return dumper->ThreadProcInternal();
+}
+
+
+void MiniDumper::CreateMiniDump(DumpType dumpType)
+{
+ // Create a unique dump file name, using the path from m_dumpDir, in m_dumpFile
+ SYSTEMTIME sysTime;
+ ::GetLocalTime(&sysTime);
+#if RTS_GENERALS
+ const Char product = 'G';
+#elif RTS_ZEROHOUR
+ const Char product = 'Z';
+#endif
+ Char dumpTypeSpecifier = static_cast(dumpType);
+ DWORD currentProcessId = ::GetCurrentProcessId();
+
+ // m_dumpDir is stored with trailing backslash in Initialize
+ snprintf(m_dumpFile, ARRAY_SIZE(m_dumpFile), "%s%s%c%c-%04d%02d%02d-%02d%02d%02d-%s-pid%ld.dmp",
+ m_dumpDir, DumpFileNamePrefix, dumpTypeSpecifier, product, sysTime.wYear, sysTime.wMonth,
+ sysTime.wDay, sysTime.wHour, sysTime.wMinute, sysTime.wSecond,
+ GitShortSHA1, currentProcessId);
+
+ HANDLE dumpFile = ::CreateFile(m_dumpFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (dumpFile == NULL || dumpFile == INVALID_HANDLE_VALUE)
+ {
+ DEBUG_LOG(("MiniDumper::CreateMiniDump: Unable to create dump file '%s': error=%u", m_dumpFile, ::GetLastError()));
+ return;
+ }
+
+ PMINIDUMP_EXCEPTION_INFORMATION exceptionInfoPtr = NULL;
+ MINIDUMP_EXCEPTION_INFORMATION exceptionInfo = { 0 };
+ if (g_dumpException != NULL)
+ {
+ exceptionInfo.ExceptionPointers = g_dumpException;
+ exceptionInfo.ThreadId = g_dumpExceptionThreadId;
+ exceptionInfo.ClientPointers = FALSE;
+ exceptionInfoPtr = &exceptionInfo;
+ }
+
+ int dumpTypeFlags = MiniDumpNormal;
+ switch (dumpType)
+ {
+ case DumpType_Full:
+ dumpTypeFlags |= MiniDumpWithFullMemory | MiniDumpWithDataSegs | MiniDumpWithHandleData |
+ MiniDumpWithThreadInfo | MiniDumpWithFullMemoryInfo | MiniDumpWithPrivateReadWriteMemory;
+ FALLTHROUGH;
+ case DumpType_Minimal:
+ dumpTypeFlags |= MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory;
+ break;
+ }
+
+ MINIDUMP_TYPE miniDumpType = static_cast(dumpTypeFlags);
+ BOOL success = DbgHelpLoader::miniDumpWriteDump(
+ ::GetCurrentProcess(),
+ currentProcessId,
+ dumpFile,
+ miniDumpType,
+ exceptionInfoPtr,
+ NULL,
+ NULL);
+
+ if (!success)
+ {
+ DEBUG_LOG(("MiniDumper::CreateMiniDump: Unable to write minidump file '%s': error=%u", m_dumpFile, ::GetLastError()));
+ }
+ else
+ {
+ DEBUG_LOG(("MiniDumper::CreateMiniDump: Successfully wrote minidump file to '%s'", m_dumpFile));
+ }
+
+ ::CloseHandle(dumpFile);
+}
+
+// Comparator for sorting files by last modified time (newest first)
+bool MiniDumper::CompareByLastWriteTime(const FileInfo& a, const FileInfo& b)
+{
+ return ::CompareFileTime(&a.lastWriteTime, &b.lastWriteTime) > 0;
+}
+
+void MiniDumper::KeepNewestFiles(const std::string& directory, const DumpType dumpType, const Int keepCount)
+{
+ // directory already contains trailing backslash
+ std::string searchPath = directory + DumpFileNamePrefix + static_cast(dumpType) + "*";
+ WIN32_FIND_DATA findData;
+ HANDLE hFind = ::FindFirstFile(searchPath.c_str(), &findData);
+
+ if (hFind == INVALID_HANDLE_VALUE)
+ {
+ if (::GetLastError() != ERROR_FILE_NOT_FOUND)
+ {
+ DEBUG_LOG(("MiniDumper::KeepNewestFiles: Unable to find files in directory '%s': error=%u", searchPath.c_str(), ::GetLastError()));
+ }
+
+ return;
+ }
+
+ std::vector files;
+ do
+ {
+ if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ continue;
+ }
+
+ // Store file info
+ FileInfo fileInfo;
+ fileInfo.name = directory + findData.cFileName;
+ fileInfo.lastWriteTime = findData.ftLastWriteTime;
+ files.push_back(fileInfo);
+
+ } while (::FindNextFile(hFind, &findData));
+
+ ::FindClose(hFind);
+
+ // Sort files by last modified time in descending order
+ std::sort(files.begin(), files.end(), CompareByLastWriteTime);
+
+ // Delete files beyond the newest keepCount
+ for (size_t i = keepCount; i < files.size(); ++i)
+ {
+ if (::DeleteFile(files[i].name.c_str()))
+ {
+ DEBUG_LOG(("MiniDumper::KeepNewestFiles: Deleted old dump file '%s'.", files[i].name.c_str()));
+ }
+ else
+ {
+ DEBUG_LOG(("MiniDumper::KeepNewestFiles: Failed to delete file '%s': error=%u", files[i].name.c_str(), ::GetLastError()));
+ }
+ }
+}
+#endif
diff --git a/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt
index 3eec2f42f8e..997a6c02420 100644
--- a/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt
+++ b/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt
@@ -36,6 +36,7 @@ set(WWLIB_SRC
DbgHelpGuard.h
DbgHelpLoader.cpp
DbgHelpLoader.h
+ DbgHelpLoader_minidump.h
Except.cpp
Except.h
FastAllocator.cpp
diff --git a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp
index 96898d5097f..368bcd03ca7 100644
--- a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp
+++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp
@@ -33,6 +33,9 @@ DbgHelpLoader::DbgHelpLoader()
, m_symSetOptions(NULL)
, m_symFunctionTableAccess(NULL)
, m_stackWalk(NULL)
+#ifdef RTS_ENABLE_CRASHDUMP
+ , m_miniDumpWriteDump(NULL)
+#endif
, m_dllModule(HMODULE(0))
, m_referenceCount(0)
, m_failed(false)
@@ -118,6 +121,9 @@ bool DbgHelpLoader::load()
Inst->m_symSetOptions = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymSetOptions"));
Inst->m_symFunctionTableAccess = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymFunctionTableAccess"));
Inst->m_stackWalk = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "StackWalk"));
+#ifdef RTS_ENABLE_CRASHDUMP
+ Inst->m_miniDumpWriteDump = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "MiniDumpWriteDump"));
+#endif
if (Inst->m_symInitialize == NULL || Inst->m_symCleanup == NULL)
{
@@ -171,6 +177,9 @@ void DbgHelpLoader::freeResources()
Inst->m_symSetOptions = NULL;
Inst->m_symFunctionTableAccess = NULL;
Inst->m_stackWalk = NULL;
+#ifdef RTS_ENABLE_CRASHDUMP
+ Inst->m_miniDumpWriteDump = NULL;
+#endif
Inst->m_loadedFromSystem = false;
}
@@ -332,3 +341,22 @@ BOOL DbgHelpLoader::stackWalk(
return FALSE;
}
+
+#ifdef RTS_ENABLE_CRASHDUMP
+BOOL DbgHelpLoader::miniDumpWriteDump(
+ HANDLE hProcess,
+ DWORD ProcessId,
+ HANDLE hFile,
+ MINIDUMP_TYPE DumpType,
+ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
+ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
+ PMINIDUMP_CALLBACK_INFORMATION CallbackParam)
+{
+ CriticalSectionClass::LockClass lock(CriticalSection);
+
+ if (Inst != NULL && Inst->m_miniDumpWriteDump)
+ return Inst->m_miniDumpWriteDump(hProcess, ProcessId, hFile, DumpType, ExceptionParam, UserStreamParam, CallbackParam);
+
+ return FALSE;
+}
+#endif
diff --git a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h
index c2167db075e..3dda58f2068 100644
--- a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h
+++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h
@@ -23,6 +23,9 @@
#include
#include // Must be included after Windows.h
#include
+#ifdef RTS_ENABLE_CRASHDUMP
+#include
+#endif
#include "mutex.h"
#include "SystemAllocator.h"
@@ -109,6 +112,17 @@ class DbgHelpLoader
PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine,
PTRANSLATE_ADDRESS_ROUTINE TranslateAddress);
+#ifdef RTS_ENABLE_CRASHDUMP
+ static BOOL WINAPI miniDumpWriteDump(
+ HANDLE hProcess,
+ DWORD ProcessId,
+ HANDLE hFile,
+ MINIDUMP_TYPE DumpType,
+ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
+ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
+ PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
+#endif
+
private:
static void freeResources();
@@ -167,6 +181,17 @@ class DbgHelpLoader
PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine,
PTRANSLATE_ADDRESS_ROUTINE TranslateAddress);
+#ifdef RTS_ENABLE_CRASHDUMP
+ typedef BOOL(WINAPI* MiniDumpWriteDump_t)(
+ HANDLE hProcess,
+ DWORD ProcessId,
+ HANDLE hFile,
+ MINIDUMP_TYPE DumpType,
+ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
+ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
+ PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
+#endif
+
SymInitialize_t m_symInitialize;
SymCleanup_t m_symCleanup;
SymLoadModule_t m_symLoadModule;
@@ -177,13 +202,15 @@ class DbgHelpLoader
SymSetOptions_t m_symSetOptions;
SymFunctionTableAccess_t m_symFunctionTableAccess;
StackWalk_t m_stackWalk;
+#ifdef RTS_ENABLE_CRASHDUMP
+ MiniDumpWriteDump_t m_miniDumpWriteDump;
+#endif
typedef std::set, stl::system_allocator > Processes;
Processes m_initializedProcesses;
HMODULE m_dllModule;
int m_referenceCount;
- CriticalSectionClass m_criticalSection;
bool m_failed;
bool m_loadedFromSystem;
};
diff --git a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h
new file mode 100644
index 00000000000..272fa832f52
--- /dev/null
+++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader_minidump.h
@@ -0,0 +1,265 @@
+#pragma once
+
+#ifdef RTS_ENABLE_CRASHDUMP
+
+// Backported defines from minidumpapiset.h for VC6.
+// minidumpapiset.h is Copyright (C) Microsoft Corporation. All rights reserved.
+
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#include
+
+typedef enum _MINIDUMP_CALLBACK_TYPE {
+ ModuleCallback,
+ ThreadCallback,
+ ThreadExCallback,
+ IncludeThreadCallback,
+ IncludeModuleCallback,
+ MemoryCallback,
+ CancelCallback,
+ WriteKernelMinidumpCallback,
+ KernelMinidumpStatusCallback,
+ RemoveMemoryCallback,
+ IncludeVmRegionCallback,
+ IoStartCallback,
+ IoWriteAllCallback,
+ IoFinishCallback,
+ ReadMemoryFailureCallback,
+ SecondaryFlagsCallback,
+ IsProcessSnapshotCallback,
+ VmStartCallback,
+ VmQueryCallback,
+ VmPreReadCallback,
+ VmPostReadCallback
+} MINIDUMP_CALLBACK_TYPE;
+
+typedef struct _MINIDUMP_THREAD_CALLBACK {
+ ULONG ThreadId;
+ HANDLE ThreadHandle;
+#if defined(_ARM64_)
+ ULONG Pad;
+#endif
+ CONTEXT Context;
+ ULONG SizeOfContext;
+ ULONG64 StackBase;
+ ULONG64 StackEnd;
+} MINIDUMP_THREAD_CALLBACK, * PMINIDUMP_THREAD_CALLBACK;
+
+typedef struct _MINIDUMP_THREAD_EX_CALLBACK {
+ ULONG ThreadId;
+ HANDLE ThreadHandle;
+#if defined(_ARM64_)
+ ULONG Pad;
+#endif
+ CONTEXT Context;
+ ULONG SizeOfContext;
+ ULONG64 StackBase;
+ ULONG64 StackEnd;
+ ULONG64 BackingStoreBase;
+ ULONG64 BackingStoreEnd;
+} MINIDUMP_THREAD_EX_CALLBACK, * PMINIDUMP_THREAD_EX_CALLBACK;
+
+typedef struct _MINIDUMP_MODULE_CALLBACK {
+ PWCHAR FullPath;
+ ULONG64 BaseOfImage;
+ ULONG SizeOfImage;
+ ULONG CheckSum;
+ ULONG TimeDateStamp;
+ VS_FIXEDFILEINFO VersionInfo;
+ PVOID CvRecord;
+ ULONG SizeOfCvRecord;
+ PVOID MiscRecord;
+ ULONG SizeOfMiscRecord;
+} MINIDUMP_MODULE_CALLBACK, * PMINIDUMP_MODULE_CALLBACK;
+
+typedef struct _MINIDUMP_INCLUDE_THREAD_CALLBACK {
+ ULONG ThreadId;
+} MINIDUMP_INCLUDE_THREAD_CALLBACK, * PMINIDUMP_INCLUDE_THREAD_CALLBACK;
+
+typedef enum _THREAD_WRITE_FLAGS {
+ ThreadWriteThread = 0x0001,
+ ThreadWriteStack = 0x0002,
+ ThreadWriteContext = 0x0004,
+ ThreadWriteBackingStore = 0x0008,
+ ThreadWriteInstructionWindow = 0x0010,
+ ThreadWriteThreadData = 0x0020,
+ ThreadWriteThreadInfo = 0x0040,
+} THREAD_WRITE_FLAGS;
+
+typedef struct _MINIDUMP_INCLUDE_MODULE_CALLBACK {
+ ULONG64 BaseOfImage;
+} MINIDUMP_INCLUDE_MODULE_CALLBACK, * PMINIDUMP_INCLUDE_MODULE_CALLBACK;
+
+typedef struct _MINIDUMP_IO_CALLBACK {
+ HANDLE Handle;
+ ULONG64 Offset;
+ PVOID Buffer;
+ ULONG BufferBytes;
+} MINIDUMP_IO_CALLBACK, * PMINIDUMP_IO_CALLBACK;
+
+typedef struct _MINIDUMP_READ_MEMORY_FAILURE_CALLBACK
+{
+ ULONG64 Offset;
+ ULONG Bytes;
+ HRESULT FailureStatus;
+} MINIDUMP_READ_MEMORY_FAILURE_CALLBACK,
+* PMINIDUMP_READ_MEMORY_FAILURE_CALLBACK;
+
+typedef struct _MINIDUMP_VM_QUERY_CALLBACK
+{
+ ULONG64 Offset;
+} MINIDUMP_VM_QUERY_CALLBACK, * PMINIDUMP_VM_QUERY_CALLBACK;
+
+typedef struct _MINIDUMP_VM_PRE_READ_CALLBACK
+{
+ ULONG64 Offset;
+ PVOID Buffer;
+ ULONG Size;
+} MINIDUMP_VM_PRE_READ_CALLBACK, * PMINIDUMP_VM_PRE_READ_CALLBACK;
+
+typedef struct _MINIDUMP_VM_POST_READ_CALLBACK
+{
+ ULONG64 Offset;
+ PVOID Buffer;
+ ULONG Size;
+ ULONG Completed;
+ HRESULT Status;
+} MINIDUMP_VM_POST_READ_CALLBACK, * PMINIDUMP_VM_POST_READ_CALLBACK;
+
+typedef struct _MINIDUMP_MEMORY_INFO {
+ ULONG64 BaseAddress;
+ ULONG64 AllocationBase;
+ ULONG32 AllocationProtect;
+ ULONG32 __alignment1;
+ ULONG64 RegionSize;
+ ULONG32 State;
+ ULONG32 Protect;
+ ULONG32 Type;
+ ULONG32 __alignment2;
+} MINIDUMP_MEMORY_INFO, * PMINIDUMP_MEMORY_INFO;
+
+typedef struct _MINIDUMP_CALLBACK_INPUT {
+ ULONG ProcessId;
+ HANDLE ProcessHandle;
+ ULONG CallbackType;
+ union {
+ HRESULT Status;
+ MINIDUMP_THREAD_CALLBACK Thread;
+ MINIDUMP_THREAD_EX_CALLBACK ThreadEx;
+ MINIDUMP_MODULE_CALLBACK Module;
+ MINIDUMP_INCLUDE_THREAD_CALLBACK IncludeThread;
+ MINIDUMP_INCLUDE_MODULE_CALLBACK IncludeModule;
+ MINIDUMP_IO_CALLBACK Io;
+ MINIDUMP_READ_MEMORY_FAILURE_CALLBACK ReadMemoryFailure;
+ ULONG SecondaryFlags;
+ MINIDUMP_VM_QUERY_CALLBACK VmQuery;
+ MINIDUMP_VM_PRE_READ_CALLBACK VmPreRead;
+ MINIDUMP_VM_POST_READ_CALLBACK VmPostRead;
+ };
+} MINIDUMP_CALLBACK_INPUT, * PMINIDUMP_CALLBACK_INPUT;
+
+typedef struct _MINIDUMP_CALLBACK_OUTPUT {
+ union {
+ ULONG ModuleWriteFlags;
+ ULONG ThreadWriteFlags;
+ ULONG SecondaryFlags;
+ struct {
+ ULONG64 MemoryBase;
+ ULONG MemorySize;
+ };
+ struct {
+ BOOL CheckCancel;
+ BOOL Cancel;
+ };
+ HANDLE Handle;
+ struct {
+ MINIDUMP_MEMORY_INFO VmRegion;
+ BOOL Continue;
+ };
+ struct {
+ HRESULT VmQueryStatus;
+ MINIDUMP_MEMORY_INFO VmQueryResult;
+ };
+ struct {
+ HRESULT VmReadStatus;
+ ULONG VmReadBytesCompleted;
+ };
+ HRESULT Status;
+ };
+} MINIDUMP_CALLBACK_OUTPUT, * PMINIDUMP_CALLBACK_OUTPUT;
+
+typedef struct _MINIDUMP_EXCEPTION_INFORMATION {
+ DWORD ThreadId;
+ PEXCEPTION_POINTERS ExceptionPointers;
+ BOOL ClientPointers;
+} MINIDUMP_EXCEPTION_INFORMATION, * PMINIDUMP_EXCEPTION_INFORMATION;
+
+typedef struct _MINIDUMP_USER_STREAM {
+ ULONG32 Type;
+ ULONG BufferSize;
+ PVOID Buffer;
+
+} MINIDUMP_USER_STREAM, * PMINIDUMP_USER_STREAM;
+
+typedef struct _MINIDUMP_USER_STREAM_INFORMATION {
+ ULONG UserStreamCount;
+ PMINIDUMP_USER_STREAM UserStreamArray;
+} MINIDUMP_USER_STREAM_INFORMATION, * PMINIDUMP_USER_STREAM_INFORMATION;
+
+typedef
+BOOL
+(WINAPI* MINIDUMP_CALLBACK_ROUTINE) (
+ PVOID CallbackParam,
+ PMINIDUMP_CALLBACK_INPUT CallbackInput,
+ PMINIDUMP_CALLBACK_OUTPUT CallbackOutput
+ );
+
+typedef struct _MINIDUMP_CALLBACK_INFORMATION {
+ MINIDUMP_CALLBACK_ROUTINE CallbackRoutine;
+ PVOID CallbackParam;
+} MINIDUMP_CALLBACK_INFORMATION, * PMINIDUMP_CALLBACK_INFORMATION;
+
+typedef enum _MINIDUMP_TYPE {
+ MiniDumpNormal = 0x00000000,
+ MiniDumpWithDataSegs = 0x00000001,
+ MiniDumpWithFullMemory = 0x00000002,
+ MiniDumpWithHandleData = 0x00000004,
+ MiniDumpFilterMemory = 0x00000008,
+ MiniDumpScanMemory = 0x00000010,
+ MiniDumpWithUnloadedModules = 0x00000020,
+ MiniDumpWithIndirectlyReferencedMemory = 0x00000040,
+ MiniDumpFilterModulePaths = 0x00000080,
+ MiniDumpWithProcessThreadData = 0x00000100,
+ MiniDumpWithPrivateReadWriteMemory = 0x00000200,
+ MiniDumpWithoutOptionalData = 0x00000400,
+ MiniDumpWithFullMemoryInfo = 0x00000800,
+ MiniDumpWithThreadInfo = 0x00001000,
+ MiniDumpWithCodeSegs = 0x00002000,
+ MiniDumpWithoutAuxiliaryState = 0x00004000,
+ MiniDumpWithFullAuxiliaryState = 0x00008000,
+ MiniDumpWithPrivateWriteCopyMemory = 0x00010000,
+ MiniDumpIgnoreInaccessibleMemory = 0x00020000,
+ MiniDumpWithTokenInformation = 0x00040000,
+ MiniDumpWithModuleHeaders = 0x00080000,
+ MiniDumpFilterTriage = 0x00100000,
+ MiniDumpWithAvxXStateContext = 0x00200000,
+ MiniDumpWithIptTrace = 0x00400000,
+ MiniDumpScanInaccessiblePartialPages = 0x00800000,
+ MiniDumpFilterWriteCombinedMemory = 0x01000000,
+ MiniDumpValidTypeFlags = 0x01ffffff,
+ MiniDumpNoIgnoreInaccessibleMemory = 0x02000000,
+ MiniDumpValidTypeFlagsEx = 0x03ffffff,
+} MINIDUMP_TYPE;
+
+typedef enum _MODULE_WRITE_FLAGS {
+ ModuleWriteModule = 0x0001,
+ ModuleWriteDataSeg = 0x0002,
+ ModuleWriteMiscRecord = 0x0004,
+ ModuleWriteCvRecord = 0x0008,
+ ModuleReferencedByMemory = 0x0010,
+ ModuleWriteTlsData = 0x0020,
+ ModuleWriteCodeSegs = 0x0040,
+} MODULE_WRITE_FLAGS;
+
+#include
+#endif
+#endif
diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp
index 303d6da1cec..47412511028 100644
--- a/Generals/Code/Main/WinMain.cpp
+++ b/Generals/Code/Main/WinMain.cpp
@@ -64,6 +64,9 @@
#include "BuildVersion.h"
#include "GeneratedVersion.h"
#include "resource.h"
+#ifdef RTS_ENABLE_CRASHDUMP
+#include "Common/MiniDumper.h"
+#endif
// GLOBALS ////////////////////////////////////////////////////////////////////
@@ -741,6 +744,16 @@ static CriticalSection critSec1, critSec2, critSec3, critSec4, critSec5;
static LONG WINAPI UnHandledExceptionFilter( struct _EXCEPTION_POINTERS* e_info )
{
DumpExceptionInfo( e_info->ExceptionRecord->ExceptionCode, e_info );
+#ifdef RTS_ENABLE_CRASHDUMP
+ if (TheMiniDumper && TheMiniDumper->IsInitialized())
+ {
+ // Create both minimal and full memory dumps
+ TheMiniDumper->TriggerMiniDumpForException(e_info, DumpType_Minimal);
+ TheMiniDumper->TriggerMiniDumpForException(e_info, DumpType_Full);
+ }
+
+ MiniDumper::shutdownMiniDumper();
+#endif
return EXCEPTION_EXECUTE_HANDLER;
}
@@ -801,6 +814,10 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
CommandLine::parseCommandLineForStartup();
+#ifdef RTS_ENABLE_CRASHDUMP
+ // Initialize minidump facilities - requires TheGlobalData so performed after parseCommandLineForStartup
+ MiniDumper::initMiniDumper(TheGlobalData->getPath_UserData());
+#endif
// register windows class and create application window
if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false)
return exitcode;
@@ -868,6 +885,9 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
}
+#ifdef RTS_ENABLE_CRASHDUMP
+ MiniDumper::shutdownMiniDumper();
+#endif
TheAsciiStringCriticalSection = NULL;
TheUnicodeStringCriticalSection = NULL;
TheDmaCriticalSection = NULL;
diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp
index 6017276941f..a92a799932f 100644
--- a/GeneralsMD/Code/Main/WinMain.cpp
+++ b/GeneralsMD/Code/Main/WinMain.cpp
@@ -67,6 +67,9 @@
#include "resource.h"
#include
+#ifdef RTS_ENABLE_CRASHDUMP
+#include "Common/MiniDumper.h"
+#endif
// GLOBALS ////////////////////////////////////////////////////////////////////
@@ -763,6 +766,16 @@ static CriticalSection critSec1, critSec2, critSec3, critSec4, critSec5;
static LONG WINAPI UnHandledExceptionFilter( struct _EXCEPTION_POINTERS* e_info )
{
DumpExceptionInfo( e_info->ExceptionRecord->ExceptionCode, e_info );
+#ifdef RTS_ENABLE_CRASHDUMP
+ if (TheMiniDumper && TheMiniDumper->IsInitialized())
+ {
+ // Create both minimal and full memory dumps
+ TheMiniDumper->TriggerMiniDumpForException(e_info, DumpType_Minimal);
+ TheMiniDumper->TriggerMiniDumpForException(e_info, DumpType_Full);
+ }
+
+ MiniDumper::shutdownMiniDumper();
+#endif
return EXCEPTION_EXECUTE_HANDLER;
}
@@ -847,6 +860,10 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
#endif
CommandLine::parseCommandLineForStartup();
+#ifdef RTS_ENABLE_CRASHDUMP
+ // Initialize minidump facilities - requires TheGlobalData so performed after parseCommandLineForStartup
+ MiniDumper::initMiniDumper(TheGlobalData->getPath_UserData());
+#endif
// register windows class and create application window
if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false)
@@ -916,6 +933,9 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
}
+#ifdef RTS_ENABLE_CRASHDUMP
+ MiniDumper::shutdownMiniDumper();
+#endif
TheUnicodeStringCriticalSection = NULL;
TheDmaCriticalSection = NULL;
TheMemoryPoolCriticalSection = NULL;
diff --git a/cmake/config-memory.cmake b/cmake/config-memory.cmake
index 72a28d1c94e..5daa6f110a4 100644
--- a/cmake/config-memory.cmake
+++ b/cmake/config-memory.cmake
@@ -20,6 +20,9 @@ option(RTS_MEMORYPOOL_DEBUG_INTENSE_VERIFY "Enables intensive verifications afte
option(RTS_MEMORYPOOL_DEBUG_CHECK_BLOCK_OWNERSHIP "Enables debug to verify that a block actually belongs to the pool it is called with. This is great for debugging, but can be realllly slow, so is OFF by default." OFF)
option(RTS_MEMORYPOOL_DEBUG_INTENSE_DMA_BOOKKEEPING "Prints statistics for memory usage of Memory Pools." OFF)
+# Memory dump options
+option(RTS_CRASHDUMP_ENABLE "Enables writing crash dumps on unhandled exceptions or release crash failures." ON)
+
# Game Memory features
add_feature_info(GameMemoryEnable RTS_GAMEMEMORY_ENABLE "Build with the original game memory implementation")
@@ -37,6 +40,8 @@ add_feature_info(MemoryPoolDebugIntenseVerify RTS_MEMORYPOOL_DEBUG_INTENSE_VERIF
add_feature_info(MemoryPoolDebugCheckBlockOwnership RTS_MEMORYPOOL_DEBUG_CHECK_BLOCK_OWNERSHIP "Build with Memory Pool block ownership checks")
add_feature_info(MemoryPoolDebugIntenseDmaBookkeeping RTS_MEMORYPOOL_DEBUG_INTENSE_DMA_BOOKKEEPING "Build with Memory Pool intense DMA bookkeeping")
+# Memory dump features
+add_feature_info(CrashDumpEnable RTS_CRASHDUMP_ENABLE "Build with Crash Dumps")
# Game Memory features
if(NOT RTS_GAMEMEMORY_ENABLE)
@@ -87,3 +92,10 @@ else()
target_compile_definitions(core_config INTERFACE INTENSE_DMA_BOOKKEEPING=1)
endif()
endif()
+
+if(RTS_CRASHDUMP_ENABLE)
+ target_compile_definitions(core_config INTERFACE RTS_ENABLE_CRASHDUMP=1)
+ if (IS_VS6_BUILD AND NOT RTS_BUILD_OPTION_VC6_FULL_DEBUG)
+ message(STATUS "Crash Dumps will be significantly less useful in VC6 builds without full debug info enabled")
+ endif()
+endif()