//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #if defined( TORQUE_MINIDUMP ) && defined( TORQUE_RELEASE ) #include "platformWin32/platformWin32.h" #include "platformWin32/minidump/winStackWalker.h" #include "core/fileio.h" #include "core/strings/stringFunctions.h" #include "console/console.h" #include "app/net/serverQuery.h" #pragma pack(push,8) #include #include #pragma pack(pop) #pragma comment(lib, "dbghelp.lib") extern Win32PlatState winState; //Forward declarations for the dialog functions BOOL CALLBACK MiniDumpDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam); LRESULT DisplayMiniDumpDialog(HINSTANCE hinst, HWND hwndOwner); LPWORD lpwAlign(LPWORD lpIn); char gUserInput[4096]; //Console variables extern StringTableEntry gMiniDumpDir; extern StringTableEntry gMiniDumpUser; extern StringTableEntry gMiniDumpExec; extern StringTableEntry gMiniDumpParams; extern StringTableEntry gMiniDumpExecDir; char* dStrrstr(char* dst, const char* src, const char* findStr, char* replaceStr) { //see if str contains findStr, if not then return const char* findpos = strstr(src, findStr); if(!findpos) { strcpy(dst, src); } else { //copy the new string to the buffer dst[0]='\0'; strncat(dst, src, findpos-src); strcat(dst, replaceStr); const char* cur = findpos + strlen(findStr); strcat(dst, cur); } return dst; } //----------------------------------------------------------------------------------------------------------------------------------------- // CreateMiniDump() //----------------------------------------------------------------------------------------------------------------------------------------- INT CreateMiniDump( LPEXCEPTION_POINTERS ExceptionInfo) { //Get any information we can from the user and store it in gUserInput try { while(ShowCursor(TRUE) < 0); DisplayMiniDumpDialog(winState.appInstance, winState.appWindow); } catch(...) { //dSprintf(gUserInput, 4096, "The user could not enter a description of what was occurring.\n\n\n"); } //Build a Game, Date and Time stamped MiniDump folder time_t theTime; time(&theTime); tm* pLocalTime = localtime(&theTime); char crashFolder[2048]; dSprintf(crashFolder, 2048, "%s_%02d.%02d_%02d.%02d.%02d", Platform::getExecutableName(), pLocalTime->tm_mon+1, pLocalTime->tm_mday, pLocalTime->tm_hour, pLocalTime->tm_min, pLocalTime->tm_sec); //Builed the fully qualified MiniDump path char crashPath[2048]; char fileName[2048]; if(gMiniDumpDir==NULL) { dSprintf(crashPath, 2048, "%s/MiniDump/%s", Platform::getCurrentDirectory(), crashFolder); } else { dSprintf(crashPath, 2048, "%s/%s", gMiniDumpDir, crashFolder); } dSprintf(fileName, 2048, "%s/Minidump.dmp",crashPath); if (!Platform::createPath (fileName))return false; //create the directory //Save the minidump File fileObject; if(fileObject.open(fileName, File::Write) == File::Ok) { MINIDUMP_EXCEPTION_INFORMATION DumpExceptionInfo; DumpExceptionInfo.ThreadId = GetCurrentThreadId(); DumpExceptionInfo.ExceptionPointers = ExceptionInfo; DumpExceptionInfo.ClientPointers = true; MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), (HANDLE)fileObject.getHandle(), MiniDumpNormal, &DumpExceptionInfo, NULL, NULL ); fileObject.close(); } //copy over the log file char fromFile[2048]; dSprintf(fromFile, 2048, "%s/%s", Platform::getCurrentDirectory(), "console.log" ); dSprintf(fileName, 2048, "%s/console.log", crashPath); Con::setLogMode(3); //ensure that the log file is closed (so it can be copied) dPathCopy(fromFile, fileName, true); //copy over the exe file char exeName[1024]; dSprintf(exeName, 1024, Platform::getExecutableName()); exeName[dStrlen(exeName)-4]=0; dSprintf(fromFile, 2048, "%s/%s.dll", Platform::getCurrentDirectory(), exeName ); dSprintf(fileName, 2048, "%s/%s.dll", crashPath, exeName ); dPathCopy(fromFile, fileName, true); //copy over the pdb file char pdbName[1024]; dStrcpy(pdbName, exeName, 1024); dStrncat(pdbName, ".pdb", 4); dSprintf(fromFile, 2048, "%s/%s", Platform::getCurrentDirectory(), pdbName ); dSprintf(fileName, 2048, "%s/%s", crashPath, pdbName ); dPathCopy(fromFile, fileName, true); //save the call stack char traceBuffer[65536]; traceBuffer[0] = 0; dGetStackTrace( traceBuffer, *static_cast(ExceptionInfo->ContextRecord) ); //save the user input and the call stack to a file char crashlogFile[2048]; dSprintf(crashlogFile, 2048, "%s/crash.log", crashPath); if(fileObject.open(crashlogFile, File::Write) == File::Ok) { fileObject.write(strlen(gUserInput), gUserInput); fileObject.write(strlen(traceBuffer), traceBuffer); fileObject.close(); } //call the external program indicated in script if(gMiniDumpExec!= NULL) { //replace special variables in gMiniDumpParams if(gMiniDumpParams) { char updateParams[4096]; char finalParams[4096]; dStrrstr(finalParams, gMiniDumpParams, "%crashpath%", crashPath); dStrrstr(updateParams, finalParams, "%crashfolder%", crashFolder); dStrrstr(finalParams, updateParams, "%crashlog%", crashlogFile); ShellExecuteA(NULL, "", gMiniDumpExec, finalParams, gMiniDumpExecDir ? gMiniDumpExecDir : "", SW_SHOWNORMAL); } else { ShellExecuteA(NULL, "", gMiniDumpExec, "", gMiniDumpExecDir ? gMiniDumpExecDir : "", SW_SHOWNORMAL); } } return EXCEPTION_EXECUTE_HANDLER; } //----------------------------------------------------------------------------------------------------------------------------------------- // MiniDumpDialogProc - Used By DisplayMiniDumpDialog //----------------------------------------------------------------------------------------------------------------------------------------- const S32 ID_TEXT=200; const S32 ID_USERTEXT=300; const S32 ID_DONE=400; BOOL CALLBACK MiniDumpDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) { char text[128]= ""; switch (message) { case WM_INITDIALOG : SetDlgItemTextA ( hwndDlg, ID_USERTEXT, text ); return TRUE ; case WM_COMMAND: switch (LOWORD(wParam)) { case ID_DONE: if( !GetDlgItemTextA(hwndDlg, ID_USERTEXT, gUserInput, 4096) ) gUserInput[0]='\0'; strcat(gUserInput, "\n\n\n"); EndDialog(hwndDlg, wParam); return TRUE; default: return TRUE; } } return FALSE; } //----------------------------------------------------------------------------------------------------------------------------------------- // Helper function to DWORD align the Dialog Box components (Used in DisplayMiniDumpDialog() //----------------------------------------------------------------------------------------------------------------------------------------- LPWORD lpwAlign(LPWORD lpIn) { ULONG ul; ul = (ULONG)lpIn; ul ++; ul >>=1; ul <<=1; return (LPWORD)ul; } //----------------------------------------------------------------------------------------------------------------------------------------- // Create the Dialog Box to get input from the user //----------------------------------------------------------------------------------------------------------------------------------------- LRESULT DisplayMiniDumpDialog(HINSTANCE hinst, HWND hwndOwner) { HGLOBAL hgbl = GlobalAlloc(GMEM_ZEROINIT, 1024); if (!hgbl) return -1; //----------------------------------------------------------------- // Define the dialog box //----------------------------------------------------------------- LPDLGTEMPLATE lpdt = (LPDLGTEMPLATE)GlobalLock(hgbl); lpdt->style = WS_POPUP | WS_BORDER | DS_MODALFRAME | WS_CAPTION; lpdt->cdit = 3; // Number of controls lpdt->x = 100; lpdt->y = 100; lpdt->cx = 300; lpdt->cy = 90; LPWORD lpw = (LPWORD)(lpdt + 1); *lpw++ = 0; // No menu *lpw++ = 0; // Predefined dialog box class (by default) LPWSTR lpwsz = (LPWSTR)lpw; S32 nchar = 1 + MultiByteToWideChar(CP_ACP, 0, "MiniDump Crash Report", -1, lpwsz, 50); lpw += nchar; //----------------------------------------------------------------- // Define a static text message //----------------------------------------------------------------- lpw = lpwAlign(lpw); // Align DLGITEMTEMPLATE on DWORD boundary LPDLGITEMTEMPLATE lpdit = (LPDLGITEMTEMPLATE)lpw; lpdit->x = 10; lpdit->y = 10; lpdit->cx = 290; lpdit->cy = 10; lpdit->id = ID_TEXT; // Text identifier lpdit->style = WS_CHILD | WS_VISIBLE | SS_LEFT; lpw = (LPWORD)(lpdit + 1); *lpw++ = 0xFFFF; *lpw++ = 0x0082; // Static class LPSTR msg = "The program has crashed. Please describe what was happening:"; for (lpwsz = (LPWSTR)lpw; *lpwsz++ = (WCHAR)*msg++;); lpw = (LPWORD)lpwsz; *lpw++ = 0; // No creation data //----------------------------------------------------------------- // Define a DONE button //----------------------------------------------------------------- lpw = lpwAlign(lpw); // Align DLGITEMTEMPLATE on DWORD boundary lpdit = (LPDLGITEMTEMPLATE)lpw; lpdit->x = 265; lpdit->y = 75; lpdit->cx = 25; lpdit->cy = 12; lpdit->id = ID_DONE; // OK button identifier lpdit->style = WS_CHILD | WS_VISIBLE | WS_TABSTOP;// | BS_DEFPUSHBUTTON; lpw = (LPWORD)(lpdit + 1); *lpw++ = 0xFFFF; *lpw++ = 0x0080; // Button class lpwsz = (LPWSTR)lpw; nchar = 1 + MultiByteToWideChar(CP_ACP, 0, "Done", -1, lpwsz, 50); lpw += nchar; *lpw++ = 0; // No creation data //----------------------------------------------------------------- // Define a text entry message //----------------------------------------------------------------- lpw = lpwAlign(lpw); // Align DLGITEMTEMPLATE on DWORD boundary lpdit = (LPDLGITEMTEMPLATE)lpw; lpdit->x = 10; lpdit->y = 22; lpdit->cx = 280; lpdit->cy = 50; lpdit->id = ID_USERTEXT; // Text identifier lpdit->style = ES_LEFT | WS_BORDER | WS_TABSTOP | WS_CHILD | WS_VISIBLE; lpw = (LPWORD)(lpdit + 1); *lpw++ = 0xFFFF; *lpw++ = 0x0081; // Text edit class *lpw++ = 0; // No creation data GlobalUnlock(hgbl); LRESULT ret = DialogBoxIndirect( hinst, (LPDLGTEMPLATE)hgbl, hwndOwner, (DLGPROC)MiniDumpDialogProc); GlobalFree(hgbl); return ret; } #endif