Skip to content
3 changes: 3 additions & 0 deletions Core/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,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
Expand Down Expand Up @@ -658,6 +659,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
Expand Down Expand Up @@ -1171,6 +1173,7 @@ target_include_directories(corei_gameengine_private INTERFACE

target_link_libraries(corei_gameengine_private INTERFACE
corei_always
$<$<AND:$<BOOL:${IS_VS6_BUILD}>,$<BOOL:${RTS_CRASHDUMP_ENABLE}>>:shlwapi.lib>
)

target_compile_definitions(corei_gameengine_private INTERFACE
Expand Down
96 changes: 96 additions & 0 deletions Core/GameEngine/Include/Common/GameMemory.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ class MemoryPool;
class MemoryPoolFactory;
class DynamicMemoryAllocator;
class BlockCheckpointInfo;
#ifdef RTS_ENABLE_CRASHDUMP
class AllocationRangeIterator;
#endif

// TYPE DEFINES ///////////////////////////////////////////////////////////////

Expand Down Expand Up @@ -279,6 +282,14 @@ class Checkpointable
};
#endif

#ifdef RTS_ENABLE_CRASHDUMP
struct MemoryPoolAllocatedRange
{
char* allocationAddr;
size_t allocationSize;
};
#endif

// ----------------------------------------------------------------------------
/**
A MemoryPool provides a way to efficiently allocate objects of the same (or similar)
Expand Down Expand Up @@ -384,6 +395,9 @@ class MemoryPool
/// return true iff this block was allocated by this pool.
Bool debugIsBlockInPool(void *pBlock);
#endif
#ifdef RTS_ENABLE_CRASHDUMP
friend class AllocationRangeIterator;
#endif
};

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -474,13 +488,80 @@ class DynamicMemoryAllocator
Bool debugIsPoolInDma(MemoryPool *pool);

#endif // MEMORYPOOL_DEBUG
#ifdef RTS_ENABLE_CRASHDUMP
Int getRawBlockCount() const;
void fillAllocationRangeForRawBlockN(const Int n, MemoryPoolAllocatedRange& allocationRange) const;
#endif
};

// ----------------------------------------------------------------------------
#ifdef MEMORYPOOL_DEBUG
enum { MAX_SPECIAL_USED = 256 };
#endif

#ifdef RTS_ENABLE_CRASHDUMP
class AllocationRangeIterator
{
typedef const MemoryPoolAllocatedRange value_type;
typedef const MemoryPoolAllocatedRange* pointer;
typedef const MemoryPoolAllocatedRange& reference;

public:

AllocationRangeIterator(const MemoryPoolFactory* factory);
AllocationRangeIterator(MemoryPool& pool, MemoryPoolBlob& blob)
{
m_currentPool = &pool;
m_currentBlobInPool = &blob;
m_factory = NULL;
m_range = MemoryPoolAllocatedRange();
};

AllocationRangeIterator(MemoryPool* pool, MemoryPoolBlob* blob)
{
m_currentPool = pool;
m_currentBlobInPool = blob;
m_factory = NULL;
m_range = MemoryPoolAllocatedRange();
};

AllocationRangeIterator()
{
m_currentPool = NULL;
m_currentBlobInPool = NULL;
m_factory = NULL;
m_range = MemoryPoolAllocatedRange();
};

reference operator*() { UpdateRange(); return m_range; }
pointer operator->() { UpdateRange(); return &m_range; }

// Prefix increment
AllocationRangeIterator& operator++() { MoveToNextBlob(); return *this; }

// Postfix increment
AllocationRangeIterator operator++(int) { AllocationRangeIterator tmp = *this; ++(*this); return tmp; }

friend const bool operator== (const AllocationRangeIterator& a, const AllocationRangeIterator& b)
{
return a.m_currentBlobInPool == b.m_currentBlobInPool;
};

friend const bool operator!= (const AllocationRangeIterator& a, const AllocationRangeIterator& b)
{
return a.m_currentBlobInPool != b.m_currentBlobInPool;
};

private:
const MemoryPoolFactory* m_factory;
MemoryPool* m_currentPool;
MemoryPoolBlob* m_currentBlobInPool;
MemoryPoolAllocatedRange m_range;
void UpdateRange();
void MoveToNextBlob();
};
#endif

// ----------------------------------------------------------------------------
/**
The class that manages all the MemoryPools and DynamicMemoryAllocators.
Expand Down Expand Up @@ -573,6 +654,21 @@ class MemoryPoolFactory
void debugResetCheckpoints();

#endif
#ifdef RTS_ENABLE_CRASHDUMP
AllocationRangeIterator cbegin() const
{
return AllocationRangeIterator(this);
}

AllocationRangeIterator cend() const
{
return AllocationRangeIterator(NULL, NULL);
}

Int getMemoryPoolCount() const;
MemoryPool* getMemoryPoolN(const Int n) const;
friend class AllocationRangeIterator;
#endif
};

// how many bytes are we allowed to 'waste' per pool allocation before the debug code starts yelling at us...
Expand Down
113 changes: 113 additions & 0 deletions Core/GameEngine/Include/Common/MiniDumper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/

#pragma once

#ifdef RTS_ENABLE_CRASHDUMP
#include "DbgHelpLoader.h"

enum DumpType CPP_11(: Int)
{
// Smallest dump type with call stacks and some supporting variables
DUMP_TYPE_MINIMAL,
// Large dump including all memory regions allocated by the GameMemory implementaion
DUMP_TYPE_GAMEMEMORY,
// Largest dump size including complete memory contents of the process
DUMP_TYPE_FULL,
};

enum MiniDumperExitCode CPP_11(: Int)
{
DUMPER_EXIT_SUCCESS = 0x0,
DUMPER_EXIT_FAILURE_WAIT = 0x37DA1040,
DUMPER_EXIT_FAILURE_PARAM = 0x4EA527BB,
DUMPER_EXIT_FORCED_TERMINATE = 0x158B1154,
};

class MiniDumper
{
public:
MiniDumper();
Bool IsInitialized() const;
void TriggerMiniDump(DumpType dumpType);
void TriggerMiniDumpForException(struct _EXCEPTION_POINTERS* e_info, DumpType dumpType);
static void initMiniDumper(const AsciiString& userDirPath);
static void shutdownMiniDumper();
static LONG WINAPI DumpingExceptionFilter(struct _EXCEPTION_POINTERS* e_info);

private:
void Initialize(const AsciiString& userDirPath);
void ShutDown();
void CreateMiniDump(DumpType dumpType);
BOOL DumpMemoryObjects(ULONG64& memoryBase, ULONG& memorySize);
void CleanupResources();
Bool IsDumpThreadStillRunning() const;
void ShutdownDumpThread();

// Callbacks from dbghelp
static BOOL CALLBACK MiniDumpCallback(PVOID CallbackParam, PMINIDUMP_CALLBACK_INPUT CallbackInput, PMINIDUMP_CALLBACK_OUTPUT CallbackOutput);
BOOL CallbackInternal(const MINIDUMP_CALLBACK_INPUT& input, MINIDUMP_CALLBACK_OUTPUT& output);

// 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 std::string& fileWildcard, 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;

#ifndef DISABLE_GAMEMEMORY
// Internal memory dumping progress state
int m_dumpObjectsState;
int m_dumpObjectsSubState;
int m_dmaRawBlockIndex;

AllocationRangeIterator m_rangeIter;
#endif
};

extern MiniDumper* TheMiniDumper;
#endif
25 changes: 25 additions & 0 deletions Core/GameEngine/Source/Common/System/Debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -725,6 +728,17 @@ void ReleaseCrash(const char *reason)
}
}

#ifdef RTS_ENABLE_CRASHDUMP
if (TheMiniDumper && TheMiniDumper->IsInitialized())
{
// Do dumps both with and without extended info
TheMiniDumper->TriggerMiniDump(DUMP_TYPE_MINIMAL);
TheMiniDumper->TriggerMiniDump(DUMP_TYPE_GAMEMEMORY);
}

MiniDumper::shutdownMiniDumper();
#endif

char prevbuf[ _MAX_PATH ];
char curbuf[ _MAX_PATH ];

Expand Down Expand Up @@ -790,6 +804,17 @@ void ReleaseCrashLocalized(const AsciiString& p, const AsciiString& m)
return;
}

#ifdef RTS_ENABLE_CRASHDUMP
if (TheMiniDumper && TheMiniDumper->IsInitialized())
{
// Do dumps both with and without extended info
TheMiniDumper->TriggerMiniDump(DUMP_TYPE_MINIMAL);
TheMiniDumper->TriggerMiniDump(DUMP_TYPE_GAMEMEMORY);
}

MiniDumper::shutdownMiniDumper();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code block exists twice in this file. Perhaps consolidate?

#endif

UnicodeString prompt = TheGameText->fetch(p);
UnicodeString mesg = TheGameText->fetch(m);

Expand Down
Loading
Loading