Skip to content

Commit f4608df

Browse files
authored
feat(crashdump): Add crash dump functionality for fatal errors (#1594)
Crash dumps are stored in %USERPROFILE%\Documents\Command and Conquer Generals Zero Hour Data\CrashDumps
1 parent 68d832f commit f4608df

File tree

11 files changed

+957
-1
lines changed

11 files changed

+957
-1
lines changed

Core/GameEngine/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ set(GAMEENGINE_SRC
7272
# Include/Common/MapObject.h
7373
# Include/Common/MapReaderWriterInfo.h
7474
# Include/Common/MessageStream.h
75+
Include/Common/MiniDumper.h
7576
# Include/Common/MiniLog.h
7677
Include/Common/MiscAudio.h
7778
# Include/Common/MissionStats.h
@@ -660,6 +661,7 @@ set(GAMEENGINE_SRC
660661
# Source/Common/System/List.cpp
661662
Source/Common/System/LocalFile.cpp
662663
Source/Common/System/LocalFileSystem.cpp
664+
Source/Common/System/MiniDumper.cpp
663665
Source/Common/System/ObjectStatusTypes.cpp
664666
# Source/Common/System/QuotedPrintable.cpp
665667
Source/Common/System/Radar.cpp
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 TheSuperHackers
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#pragma once
20+
21+
#ifdef RTS_ENABLE_CRASHDUMP
22+
#include "DbgHelpLoader.h"
23+
24+
enum DumpType CPP_11(: Char)
25+
{
26+
// Smallest dump type with call stacks and some supporting variables
27+
DumpType_Minimal = 'M',
28+
// Largest dump size including complete memory contents of the process
29+
DumpType_Full = 'F',
30+
};
31+
32+
class MiniDumper
33+
{
34+
enum MiniDumperExitCode CPP_11(: Int)
35+
{
36+
MiniDumperExitCode_Success = 0x0,
37+
MiniDumperExitCode_FailureWait = 0x37DA1040,
38+
MiniDumperExitCode_FailureParam = 0x4EA527BB,
39+
MiniDumperExitCode_ForcedTerminate = 0x158B1154,
40+
};
41+
42+
public:
43+
MiniDumper();
44+
Bool IsInitialized() const;
45+
void TriggerMiniDump(DumpType dumpType);
46+
void TriggerMiniDumpForException(_EXCEPTION_POINTERS* e_info, DumpType dumpType);
47+
static void initMiniDumper(const AsciiString& userDirPath);
48+
static void shutdownMiniDumper();
49+
static LONG WINAPI DumpingExceptionFilter(_EXCEPTION_POINTERS* e_info);
50+
51+
private:
52+
void Initialize(const AsciiString& userDirPath);
53+
void ShutDown();
54+
void CreateMiniDump(DumpType dumpType);
55+
void CleanupResources();
56+
Bool IsDumpThreadStillRunning() const;
57+
void ShutdownDumpThread();
58+
59+
// Thread procs
60+
static DWORD WINAPI MiniDumpThreadProc(LPVOID lpParam);
61+
DWORD ThreadProcInternal();
62+
63+
// Dump file directory bookkeeping
64+
Bool InitializeDumpDirectory(const AsciiString& userDirPath);
65+
static void KeepNewestFiles(const std::string& directory, const DumpType dumpType, const Int keepCount);
66+
67+
// Struct to hold file information
68+
struct FileInfo
69+
{
70+
std::string name;
71+
FILETIME lastWriteTime;
72+
};
73+
74+
static bool CompareByLastWriteTime(const FileInfo& a, const FileInfo& b);
75+
76+
private:
77+
Bool m_miniDumpInitialized;
78+
Bool m_loadedDbgHelp;
79+
DumpType m_requestedDumpType;
80+
81+
// Path buffers
82+
Char m_dumpDir[MAX_PATH];
83+
Char m_dumpFile[MAX_PATH];
84+
WideChar m_executablePath[MAX_PATH];
85+
86+
// Event handles
87+
HANDLE m_dumpRequested;
88+
HANDLE m_dumpComplete;
89+
HANDLE m_quitting;
90+
91+
// Thread handles
92+
HANDLE m_dumpThread;
93+
DWORD m_dumpThreadId;
94+
};
95+
96+
extern MiniDumper* TheMiniDumper;
97+
#endif

Core/GameEngine/Source/Common/System/Debug.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@
7070
#if defined(DEBUG_STACKTRACE) || defined(IG_DEBUG_STACKTRACE)
7171
#include "Common/StackDump.h"
7272
#endif
73+
#ifdef RTS_ENABLE_CRASHDUMP
74+
#include "Common/MiniDumper.h"
75+
#endif
7376

7477
// Horrible reference, but we really, really need to know if we are windowed.
7578
extern bool DX8Wrapper_IsWindowed;
@@ -727,6 +730,22 @@ double SimpleProfiler::getAverageTime()
727730
}
728731
}
729732

733+
734+
static void TriggerMiniDump()
735+
{
736+
#ifdef RTS_ENABLE_CRASHDUMP
737+
if (TheMiniDumper && TheMiniDumper->IsInitialized())
738+
{
739+
// Create both minimal and full memory dumps
740+
TheMiniDumper->TriggerMiniDump(DumpType_Minimal);
741+
TheMiniDumper->TriggerMiniDump(DumpType_Full);
742+
}
743+
744+
MiniDumper::shutdownMiniDumper();
745+
#endif
746+
}
747+
748+
730749
void ReleaseCrash(const char *reason)
731750
{
732751
/// do additional reporting on the crash, if possible
@@ -737,6 +756,8 @@ void ReleaseCrash(const char *reason)
737756
}
738757
}
739758

759+
TriggerMiniDump();
760+
740761
char prevbuf[ _MAX_PATH ];
741762
char curbuf[ _MAX_PATH ];
742763

@@ -813,6 +834,8 @@ void ReleaseCrashLocalized(const AsciiString& p, const AsciiString& m)
813834
return;
814835
}
815836

837+
TriggerMiniDump();
838+
816839
UnicodeString prompt = TheGameText->fetch(p);
817840
UnicodeString mesg = TheGameText->fetch(m);
818841

0 commit comments

Comments
 (0)