Skip to content
131 changes: 119 additions & 12 deletions Core/GameEngine/Source/Common/System/Debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,14 @@ void DebugInit(int flags)

theMainThreadID = GetCurrentThreadId();

#if defined(DEBUG_STACKTRACE) || defined(IG_DEBUG_STACKTRACE)
// TheSuperHackers @bugfix JohnsterID 12/01/2026 Initialize g_LastErrorDump at startup.
// A crash can occur before static initialization completes. Without explicit
// initialization here, different STL implementations (VC6/STLPort vs Win32) may
// leave garbage/uninitialized data in the global AsciiString.
g_LastErrorDump.clear();
#endif

#ifdef DEBUG_LOGGING

// TheSuperHackers @info Debug initialization can happen very early.
Expand Down Expand Up @@ -746,6 +754,55 @@ static void TriggerMiniDump()
}


// TheSuperHackers @bugfix JohnsterID 06/01/2025 Helper function to extract crash location from stack trace.
// Extracts the first few lines of the stack dump for display in the crash dialog.
static void extractCrashLocation(char* outBuffer, size_t bufferSize)
{
if (bufferSize == 0) {
return;
}

// Always initialize output buffer first to prevent garbage on early return.
outBuffer[0] = '\0';

if (g_LastErrorDump.isEmpty()) {
return;
}

const char* stackStr = g_LastErrorDump.str();
if (!stackStr || !*stackStr) {
return;
}

// Extract first 5 lines from stack trace for context.
const int maxLines = 5;
int lineCount = 0;
size_t written = 0;
const size_t maxWrite = bufferSize - 1;

while (*stackStr && lineCount < maxLines && written < maxWrite) {
// Copy characters until newline or buffer full
while (*stackStr && *stackStr != '\n' && *stackStr != '\r' && written < maxWrite) {
outBuffer[written++] = *stackStr++;
}

// Skip newline characters
while (*stackStr && (*stackStr == '\n' || *stackStr == '\r')) {
stackStr++;
}

lineCount++;

// Add newline between lines if more content follows
if (*stackStr && lineCount < maxLines && written < maxWrite) {
outBuffer[written++] = '\n';
}
}

outBuffer[written] = '\0';
}


void ReleaseCrash(const char *reason)
{
/// do additional reporting on the crash, if possible
Expand Down Expand Up @@ -806,23 +863,50 @@ void ReleaseCrash(const char *reason)
}
}

// TheSuperHackers @bugfix JohnsterID 06/01/2025 Update crash message to show crash report locations
// and point users to repo. Removes outdated EA forum references.
// Also shows crash location from stack trace when debug symbols are available.
char crashInfoPath[_MAX_PATH];
strlcpy(crashInfoPath, TheGlobalData->getPath_UserData().str(), ARRAY_SIZE(crashInfoPath));
strlcat(crashInfoPath, RELEASECRASH_FILE_NAME, ARRAY_SIZE(crashInfoPath));

#ifdef RTS_ENABLE_CRASHDUMP
char crashDumpDir[_MAX_PATH];
strlcpy(crashDumpDir, TheGlobalData->getPath_UserData().str(), ARRAY_SIZE(crashDumpDir));
strlcat(crashDumpDir, "CrashDumps\\", ARRAY_SIZE(crashDumpDir));
#endif

char crashLocation[512];
extractCrashLocation(crashLocation, ARRAY_SIZE(crashLocation));

char buff[2560];
char* p = buff;
char* end = buff + sizeof(buff);

p += snprintf(p, end - p, "The game encountered a critical error and needs to close.\n\n");

#if defined(RTS_DEBUG)
/* static */ char buff[8192]; // not so static so we can be threadsafe
snprintf(buff, 8192, "Sorry, a serious error occurred. (%s)", reason);
::MessageBox(nullptr, buff, "Technical Difficulties...", MB_OK|MB_SYSTEMMODAL|MB_ICONERROR);
#else
// crash error messaged changed 3/6/03 BGC
// ::MessageBox(nullptr, "Sorry, a serious error occurred.", "Technical Difficulties...", MB_OK|MB_TASKMODAL|MB_ICONERROR);
// ::MessageBox(nullptr, "You have encountered a serious error. Serious errors can be caused by many things including viruses, overheated hardware and hardware that does not meet the minimum specifications for the game. Please visit the forums at www.generals.ea.com for suggested courses of action or consult your manual for Technical Support contact information.", "Technical Difficulties...", MB_OK|MB_TASKMODAL|MB_ICONERROR);
if (reason && *reason) {
p += snprintf(p, end - p, "Error: %s\n", reason);
}
#endif

// crash error message changed again 8/22/03 M Lorenzen... made this message box modal to the system so it will appear on top of any task-modal windows, splash-screen, etc.
::MessageBox(nullptr, "You have encountered a serious error. Serious errors can be caused by many things including viruses, overheated hardware and hardware that does not meet the minimum specifications for the game. Please visit the forums at www.generals.ea.com for suggested courses of action or consult your manual for Technical Support contact information.",
"Technical Difficulties...",
MB_OK|MB_SYSTEMMODAL|MB_ICONERROR);
if (crashLocation[0] != '\0') {
p += snprintf(p, end - p, "Location:\n%s\n\n", crashLocation);
} else if (p > buff && *(p - 1) != '\n') {
p += snprintf(p, end - p, "\n");
}

p += snprintf(p, end - p, "Crash report saved to:\n%s\n", crashInfoPath);

#ifdef RTS_ENABLE_CRASHDUMP
p += snprintf(p, end - p, "\nMinidump files saved to:\n%s\n", crashDumpDir);
#endif

snprintf(p, end - p, "\nPlease report the issue:\nhttps://github.com/TheSuperHackers/GeneralsCrashReports/issues");

::MessageBox(NULL, buff, "Game Crash", MB_OK|MB_SYSTEMMODAL|MB_ICONERROR);

_exit(1);
}

Expand All @@ -848,9 +932,31 @@ void ReleaseCrashLocalized(const AsciiString& p, const AsciiString& m)
}
}

// TheSuperHackers @bugfix JohnsterID 06/01/2025 Append crash file locations to localized error message
char crashInfoPath[_MAX_PATH];
strlcpy(crashInfoPath, TheGlobalData->getPath_UserData().str(), ARRAY_SIZE(crashInfoPath));
strlcat(crashInfoPath, RELEASECRASH_FILE_NAME, ARRAY_SIZE(crashInfoPath));

char crashInfoAppendix[1024];
snprintf(crashInfoAppendix, sizeof(crashInfoAppendix),
"\n\nCrash report: %s"
#ifdef RTS_ENABLE_CRASHDUMP
"\nMinidump files: %sCrashDumps\\"
#endif
"\n\nReport issue: https://github.com/TheSuperHackers/GeneralsCrashReports/issues",
crashInfoPath
#ifdef RTS_ENABLE_CRASHDUMP
, TheGlobalData->getPath_UserData().str()
#endif
);

if (TheSystemIsUnicode)
{
::MessageBoxW(nullptr, mesg.str(), prompt.str(), MB_OK|MB_SYSTEMMODAL|MB_ICONERROR);
UnicodeString appendix;
appendix.translate(crashInfoAppendix);
UnicodeString fullMessage = mesg;
fullMessage.concat(appendix);
::MessageBoxW(NULL, fullMessage.str(), prompt.str(), MB_OK|MB_SYSTEMMODAL|MB_ICONERROR);
}
else
{
Expand All @@ -859,6 +965,7 @@ void ReleaseCrashLocalized(const AsciiString& p, const AsciiString& m)
AsciiString promptA, mesgA;
promptA.translate(prompt);
mesgA.translate(mesg);
mesgA.concat(crashInfoAppendix);
//Make sure main window is not TOP_MOST
::SetWindowPos(ApplicationHWnd, HWND_NOTOPMOST, 0, 0, 0, 0,SWP_NOSIZE |SWP_NOMOVE);
::MessageBoxA(nullptr, mesgA.str(), promptA.str(), MB_OK|MB_TASKMODAL|MB_ICONERROR);
Expand Down
6 changes: 6 additions & 0 deletions Generals/Code/Main/WinMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,12 @@ static LONG WINAPI UnHandledExceptionFilter( struct _EXCEPTION_POINTERS* e_info

MiniDumper::shutdownMiniDumper();
#endif

// TheSuperHackers @bugfix JohnsterID 20/01/2026 Show crash dialog for unhandled exceptions.
// This ensures users see crash information and GitHub link for all crashes,
// not just explicit DEBUG_CRASH calls.
ReleaseCrash("Unhandled exception");

return EXCEPTION_EXECUTE_HANDLER;
}

Expand Down
6 changes: 6 additions & 0 deletions GeneralsMD/Code/Main/WinMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,12 @@ static LONG WINAPI UnHandledExceptionFilter( struct _EXCEPTION_POINTERS* e_info

MiniDumper::shutdownMiniDumper();
#endif

// TheSuperHackers @bugfix JohnsterID 20/01/2026 Show crash dialog for unhandled exceptions.
// This ensures users see crash information and GitHub link for all crashes,
// not just explicit DEBUG_CRASH calls.
ReleaseCrash("Unhandled exception");

return EXCEPTION_EXECUTE_HANDLER;
}

Expand Down
Loading