raze/source/platform/win32/i_crash.cpp
2020-02-02 13:33:07 +01:00

2322 lines
64 KiB
C++

/*
** i_crash.cpp
** Gathers exception information when the program crashes.
**
**---------------------------------------------------------------------------
** Copyright 2002-2006 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.s
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
// HEADER FILES ------------------------------------------------------------
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <richedit.h>
#include <tlhelp32.h>
#ifndef _M_IX86
#include <winternl.h>
#endif
#ifndef __GNUC__
#if _MSC_VER
#pragma warning(disable:4091) // this silences a warning for a bogus definition in the Windows 8.1 SDK.
#endif
#include <dbghelp.h>
#if _MSC_VER
#pragma warning(default:4091)
#endif
#endif
#include <commctrl.h>
#include <commdlg.h>
#include <uxtheme.h>
#include <shellapi.h>
#include <stdint.h>
#include <stdio.h>
#include "resource.h"
#include "version.h"
#include "m_swap.h"
#include "basics.h"
#include "zstring.h"
#include "printf.h"
#include <time.h>
#include <zlib.h>
// MACROS ------------------------------------------------------------------
// Maximum number of files that might appear in a crash report.
#define MAX_FILES 5
#define ZIP_LOCALFILE MAKE_ID('P','K',3,4)
#define ZIP_CENTRALFILE MAKE_ID('P','K',1,2)
#define ZIP_ENDOFDIR MAKE_ID('P','K',5,6)
// DBGHELP.H ---------------------------------------------------------------
// w32api does not include dbghelp.h, so if I don't include these here,
// a person using GCC will need to download the Platform SDK, grab dbghelp.h
// from it, and edit it so it works with GCC.
#ifdef __GNUC__
typedef enum _MINIDUMP_TYPE
{
MiniDumpNormal
// Other types omitted.
} MINIDUMP_TYPE;
typedef struct _MINIDUMP_EXCEPTION_INFORMATION {
DWORD ThreadId;
PEXCEPTION_POINTERS ExceptionPointers;
BOOL ClientPointers;
} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION;
typedef struct _MINIDUMP_USER_STREAM_INFORMATION {
ULONG UserStreamCount;
void *UserStreamArray; // Not really void *
} MINIDUMP_USER_STREAM_INFORMATION, *PMINIDUMP_USER_STREAM_INFORMATION;
typedef BOOL (WINAPI * MINIDUMP_CALLBACK_ROUTINE) (
IN PVOID CallbackParam,
IN CONST void *CallbackInput, // Not really void *
IN OUT void *CallbackOutput // Not really void *
);
typedef struct _MINIDUMP_CALLBACK_INFORMATION {
MINIDUMP_CALLBACK_ROUTINE CallbackRoutine;
PVOID CallbackParam;
} MINIDUMP_CALLBACK_INFORMATION, *PMINIDUMP_CALLBACK_INFORMATION;
#endif
// Dbghelp.dll is loaded at runtime so we don't create any needless
// dependencies on it.
typedef BOOL (WINAPI *THREADWALK) (HANDLE, LPTHREADENTRY32);
typedef BOOL (WINAPI *MODULEWALK) (HANDLE, LPMODULEENTRY32);
typedef HANDLE (WINAPI *CREATESNAPSHOT) (DWORD, DWORD);
typedef BOOL (WINAPI *WRITEDUMP) (HANDLE, DWORD, HANDLE, int,
PMINIDUMP_EXCEPTION_INFORMATION,
PMINIDUMP_USER_STREAM_INFORMATION,
PMINIDUMP_CALLBACK_INFORMATION);
// TYPES -------------------------------------------------------------------
#ifdef _M_X64
typedef PRUNTIME_FUNCTION (WINAPI *RTLLOOKUPFUNCTIONENTRY)
(ULONG64 ControlPc, PULONG64 ImageBase, void *HistoryTable);
#endif
// Damn Microsoft for doing Get/SetWindowLongPtr half-assed. Instead of
// giving them proper prototypes under Win32, they are just macros for
// Get/SetWindowLong, meaning they take LONGs and not LONG_PTRs.
#ifdef _WIN64
typedef LONG_PTR WLONG_PTR;
#else
typedef LONG WLONG_PTR;
#endif
namespace zip
{
#pragma pack(push,1)
struct LocalFileHeader
{
uint32_t Magic; // 0
uint8_t VersionToExtract[2]; // 4
uint16_t Flags; // 6
uint16_t Method; // 8
uint16_t ModTime; // 10
uint16_t ModDate; // 12
uint32_t CRC32; // 14
uint32_t CompressedSize; // 18
uint32_t UncompressedSize; // 22
uint16_t NameLength; // 26
uint16_t ExtraLength; // 28
};
struct CentralDirectoryEntry
{
uint32_t Magic;
uint8_t VersionMadeBy[2];
uint8_t VersionToExtract[2];
uint16_t Flags;
uint16_t Method;
uint16_t ModTime;
uint16_t ModDate;
uint32_t CRC32;
uint32_t CompressedSize;
uint32_t UncompressedSize;
uint16_t NameLength;
uint16_t ExtraLength;
uint16_t CommentLength;
uint16_t StartingDiskNumber;
uint16_t InternalAttributes;
uint32_t ExternalAttributes;
uint32_t LocalHeaderOffset;
};
struct EndOfCentralDirectory
{
uint32_t Magic;
uint16_t DiskNumber;
uint16_t FirstDisk;
uint16_t NumEntries;
uint16_t NumEntriesOnAllDisks;
uint32_t DirectorySize;
uint32_t DirectoryOffset;
uint16_t ZipCommentLength;
};
#pragma pack(pop)
}
struct TarFile
{
HANDLE File;
const char *Filename;
int ZipOffset;
uint32_t UncompressedSize;
uint32_t CompressedSize;
uint32_t CRC32;
bool Deflated;
};
struct MiniDumpThreadData
{
HANDLE File;
WRITEDUMP pMiniDumpWriteDump;
MINIDUMP_EXCEPTION_INFORMATION *Exceptor;
};
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
void I_FlushBufferedConsoleStuff();
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
static void AddFile (HANDLE file, const char *filename);
static void CloseTarFiles ();
static HANDLE MakeZip ();
static void AddZipFile (HANDLE ziphandle, TarFile *whichfile, short dosdate, short dostime);
static HANDLE CreateTempFile ();
static void DumpBytes (HANDLE file, uint8_t *address);
static void AddStackInfo (HANDLE file, void *dumpaddress, DWORD code, CONTEXT *ctxt);
static void StackWalk (HANDLE file, void *dumpaddress, DWORD *topOfStack, DWORD *jump, CONTEXT *ctxt);
static void AddToolHelp (HANDLE file);
static HANDLE WriteTextReport ();
static DWORD WINAPI WriteMiniDumpInAnotherThread (LPVOID lpParam);
static INT_PTR CALLBACK DetailsDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
static void SetEditControl (HWND control, HWND sizedisplay, int filenum);
static BOOL UploadReport (HANDLE report, bool gziped);
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
extern HINSTANCE g_hInst;
// PUBLIC DATA DEFINITIONS -------------------------------------------------
EXCEPTION_POINTERS CrashPointers;
// PRIVATE DATA DEFINITIONS ------------------------------------------------
static HRESULT (__stdcall *pEnableThemeDialogTexture) (HWND hwnd, DWORD dwFlags);
static TarFile TarFiles[MAX_FILES];
static int NumFiles;
static HANDLE DbgProcess;
static DWORD DbgProcessID;
static DWORD DbgThreadID;
static DWORD CrashCode;
static PVOID CrashAddress;
static bool NeedDbgHelp;
static char CrashSummary[256];
static WNDPROC StdStaticProc;
// CODE --------------------------------------------------------------------
//==========================================================================
//
// SafeReadMemory
//
// Returns true if the memory was succussfully read and false if not.
//
//==========================================================================
static bool SafeReadMemory (const void *base, void *buffer, size_t len)
{
#ifdef __GNUC__
// GCC version: Test the memory beforehand and hope it doesn't become
// unreadable while we read it. GCC is the only Windows compiler (that
// I know of) that can't do SEH.
if (IsBadReadPtr (base, len))
{
return false;
}
memcpy (buffer, base, len);
return true;
#else
// Non-GCC version: Use SEH to catch any bad reads immediately when
// they happen instead of trying to catch them beforehand.
bool success = false;
__try
{
memcpy (buffer, base, len);
success = true;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
}
return success;
#endif
}
//==========================================================================
//
// GetTopOfStack
//
// Given a pointer to some address on the stack, returns a pointer to
// the top of the stack.
//
//==========================================================================
DWORD *GetTopOfStack (void *top)
{
MEMORY_BASIC_INFORMATION memInfo;
if (VirtualQuery (top, &memInfo, sizeof(memInfo)))
{
return (DWORD *)((uint8_t *)memInfo.BaseAddress + memInfo.RegionSize);
}
else
{
return NULL;
}
}
//==========================================================================
//
// WriteMyMiniDump
//
// Writes a minidump if dbghelp.dll is present and adds it to the tarball.
// Otherwise, it just returns INVALID_HANDLE_VALUE.
//
//==========================================================================
static HANDLE WriteMyMiniDump (void)
{
MINIDUMP_EXCEPTION_INFORMATION exceptor = { DbgThreadID, &CrashPointers, FALSE };
WCHAR dbghelpPath[MAX_PATH+12], *bs;
WRITEDUMP pMiniDumpWriteDump;
HANDLE file;
BOOL good = FALSE;
HMODULE dbghelp = NULL;
// Make sure dbghelp.dll and MiniDumpWriteDump are available
// Try loading from the application directory first, then from the search path.
GetModuleFileNameW (NULL, dbghelpPath, MAX_PATH);
dbghelpPath[MAX_PATH] = 0;
bs = wcsrchr (dbghelpPath, '\\');
if (bs != NULL)
{
wcscpy (bs + 1, L"dbghelp.dll");
dbghelp = LoadLibraryW (dbghelpPath);
}
if (dbghelp == NULL)
{
dbghelp = LoadLibraryA ("dbghelp.dll");
if (dbghelp == NULL)
{
NeedDbgHelp = true;
return INVALID_HANDLE_VALUE;
}
}
pMiniDumpWriteDump = (WRITEDUMP)GetProcAddress (dbghelp, "MiniDumpWriteDump");
if (pMiniDumpWriteDump != NULL)
{
file = CreateTempFile ();
if (file != INVALID_HANDLE_VALUE)
{
if (CrashPointers.ExceptionRecord->ExceptionCode != EXCEPTION_STACK_OVERFLOW)
{
good = pMiniDumpWriteDump (DbgProcess, DbgProcessID, file,
#if 1
MiniDumpNormal
#else
MiniDumpWithDataSegs|MiniDumpWithIndirectlyReferencedMemory|MiniDumpWithPrivateReadWriteMemory
#endif
, &exceptor, NULL, NULL);
}
else
{
MiniDumpThreadData dumpdata = { file, pMiniDumpWriteDump, &exceptor };
DWORD id;
HANDLE thread = CreateThread (NULL, 0, WriteMiniDumpInAnotherThread,
&dumpdata, 0, &id);
WaitForSingleObject (thread, INFINITE);
if (GetExitCodeThread (thread, &id))
{
good = id;
}
}
}
}
else
{
NeedDbgHelp = true;
}
return good ? file : INVALID_HANDLE_VALUE;
}
//==========================================================================
//
// WriteMiniDumpInAnotherThread
//
// When a stack overflow occurs, there isn't enough room left on the stack
// for MiniDumpWriteDump to do its thing, so we create a new thread with
// a new stack to do the work.
//
//==========================================================================
static DWORD WINAPI WriteMiniDumpInAnotherThread (LPVOID lpParam)
{
MiniDumpThreadData *dumpdata = (MiniDumpThreadData *)lpParam;
return dumpdata->pMiniDumpWriteDump (DbgProcess, DbgProcessID,
dumpdata->File, MiniDumpNormal, dumpdata->Exceptor, NULL, NULL);
}
//==========================================================================
//
// Writef
//
// Like printf, but for Win32 file HANDLEs.
//
//==========================================================================
void Writef (HANDLE file, const char *format, ...)
{
char buffer[1024];
va_list args;
DWORD len;
va_start (args, format);
len = myvsnprintf (buffer, sizeof buffer, format, args);
va_end (args);
WriteFile (file, buffer, len, &len, NULL);
}
//==========================================================================
//
// WriteLogFileStreamer
//
// The callback function to stream a Rich Edit's contents to a file.
//
//==========================================================================
static DWORD CALLBACK WriteLogFileStreamer(DWORD_PTR cookie, LPBYTE buffer, LONG cb, LONG *pcb)
{
DWORD didwrite;
LONG p, pp;
// Replace gray foreground color with black.
static const char *badfg = "\\red223\\green223\\blue223;";
// 4321098 765432109 876543210
// 2 1 0
for (p = pp = 0; p < cb; ++p)
{
if (buffer[p] == badfg[pp])
{
++pp;
if (pp == 25)
{
buffer[p - 1] = buffer[p - 2] = buffer[p - 3] =
buffer[p - 9] = buffer[p -10] = buffer[p -11] =
buffer[p -18] = buffer[p -19] = buffer[p -20] = '0';
break;
}
}
else
{
pp = 0;
}
}
if (!WriteFile((HANDLE)cookie, buffer, cb, &didwrite, NULL))
{
return 1;
}
*pcb = didwrite;
return 0;
}
//==========================================================================
//
// WriteLogFile
//
// Writes the contents of a Rich Edit control to a file.
//
//==========================================================================
HANDLE WriteLogFile(HWND edit)
{
HANDLE file;
file = CreateTempFile();
if (file != INVALID_HANDLE_VALUE)
{
EDITSTREAM streamer = { (DWORD_PTR)file, 0, WriteLogFileStreamer };
SendMessage(edit, EM_STREAMOUT, SF_RTF, (LPARAM)&streamer);
}
return file;
}
//==========================================================================
//
// CreateCrashLog
//
// Creates all the files needed for a crash report.
//
//==========================================================================
void CreateCrashLog (const char *custominfo, DWORD customsize, HWND richlog)
{
// Do not collect information more than once.
if (NumFiles != 0)
{
return;
}
DbgThreadID = GetCurrentThreadId();
DbgProcessID = GetCurrentProcessId();
DbgProcess = GetCurrentProcess();
CrashCode = CrashPointers.ExceptionRecord->ExceptionCode;
CrashAddress = CrashPointers.ExceptionRecord->ExceptionAddress;
AddFile (WriteTextReport(), "report.txt");
AddFile (WriteMyMiniDump(), "minidump.mdmp");
// Copy app-specific information out of the crashing process's memory
// and into a new temporary file.
if (customsize != 0)
{
HANDLE file = CreateTempFile();
DWORD wrote;
if (file != INVALID_HANDLE_VALUE)
{
uint8_t buffer[512];
DWORD left;
for (;; customsize -= left, custominfo += left)
{
left = customsize > 512 ? 512 : customsize;
if (left == 0)
{
break;
}
if (SafeReadMemory (custominfo, buffer, left))
{
WriteFile (file, buffer, left, &wrote, NULL);
}
else
{
Writef (file, "Failed reading remaining %lu bytes\r\n", customsize);
break;
}
}
AddFile (file, "local.txt");
}
}
if (richlog != NULL)
{
I_FlushBufferedConsoleStuff();
AddFile (WriteLogFile(richlog), "log.rtf");
}
CloseHandle (DbgProcess);
}
//==========================================================================
//
// WriteTextReport
//
// This is the classic plain-text report ZDoom has generated since it
// added crash gathering abilities in 2002.
//
//==========================================================================
HANDLE WriteTextReport ()
{
HANDLE file;
static const struct
{
DWORD Code; const char *Text;
}
exceptions[] =
{
{ EXCEPTION_ACCESS_VIOLATION, "Access Violation" },
{ EXCEPTION_ARRAY_BOUNDS_EXCEEDED, "Array Bounds Exceeded" },
{ EXCEPTION_BREAKPOINT, "Breakpoint" },
{ EXCEPTION_DATATYPE_MISALIGNMENT, "Data Type Misalignment" },
{ EXCEPTION_FLT_DENORMAL_OPERAND, "Floating Point Denormal Operand." },
{ EXCEPTION_FLT_DIVIDE_BY_ZERO, "Floating Point Divide By Zero" },
{ EXCEPTION_FLT_INEXACT_RESULT, "Floating Point Inexact Result" },
{ EXCEPTION_FLT_INVALID_OPERATION, "Floating Point Invalid Operation" },
{ EXCEPTION_FLT_OVERFLOW, "Floating Point Overflow" },
{ EXCEPTION_FLT_STACK_CHECK, "Floating Point Stack Check" },
{ EXCEPTION_FLT_UNDERFLOW, "Floating Point Underflow" },
{ EXCEPTION_ILLEGAL_INSTRUCTION, "Illegal Instruction" },
{ EXCEPTION_IN_PAGE_ERROR, "In Page Error" },
{ EXCEPTION_INT_DIVIDE_BY_ZERO, "Integer Divide By Zero" },
{ EXCEPTION_INT_OVERFLOW, "Integer Overflow" },
{ EXCEPTION_INVALID_DISPOSITION, "Invalid Disposition" },
{ EXCEPTION_NONCONTINUABLE_EXCEPTION, "Noncontinuable Exception" },
{ EXCEPTION_PRIV_INSTRUCTION, "Priviledged Instruction" },
{ EXCEPTION_SINGLE_STEP, "Single Step" },
{ EXCEPTION_STACK_OVERFLOW, "Stack Overflow" }
};
static const char eflagsBits[][2] =
{
{ 'C','F' }, // Carry Flag
{ 'x','1' }, // Always one
{ 'P','F' }, // Parity Flag
{ 'x','0' }, // Always zero
{ 'A','F' }, // Auxilliary Carry Flag
{ 'x','0' }, // Always zero
{ 'Z','F' }, // Zero Flag
{ 'S','F' }, // Sign Flag
{ 'T','F' }, // Trap Flag
{ 'I','F' }, // Interrupt Enable Flag
{ 'D','F' }, // Direction Flag
{ 'O','F' }, // Overflow Flag
{ 'x','x' }, // IOPL low bit
{ 'x','x' }, // IOPL high bit
{ 'N','T' }, // Nested Task
{ 'x','0' }, // Always zero
{ 'R','F' }, // Resume Flag
{ 'V','M' }, // Virtual-8086 Mode
{ 'A','C' }, // Alignment Check
{ 'V','I' }, // Virtual Interrupt Flag
{ 'V','P' } // Virtual Interrupt Pending
};
int i, j;
file = CreateTempFile ();
if (file == INVALID_HANDLE_VALUE)
{
return file;
}
OSVERSIONINFO verinfo = { sizeof(verinfo) };
GetVersionEx (&verinfo);
for (i = 0; (size_t)i < sizeof(exceptions)/sizeof(exceptions[0]); ++i)
{
if (exceptions[i].Code == CrashPointers.ExceptionRecord->ExceptionCode)
{
break;
}
}
j = mysnprintf (CrashSummary, countof(CrashSummary), "Code: %08lX", CrashPointers.ExceptionRecord->ExceptionCode);
if ((size_t)i < sizeof(exceptions)/sizeof(exceptions[0]))
{
j += mysnprintf (CrashSummary + j, countof(CrashSummary) - j, " (%s", exceptions[i].Text);
if (CrashPointers.ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
// Pre-NT kernels do not seem to provide this information.
if (verinfo.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS)
{
j += mysnprintf (CrashSummary + j, countof(CrashSummary) - j,
" - tried to %s address %p",
CrashPointers.ExceptionRecord->ExceptionInformation[0] ? "write" : "read",
(void *)CrashPointers.ExceptionRecord->ExceptionInformation[1]);
}
}
CrashSummary[j++] = ')';
}
j += mysnprintf (CrashSummary + j, countof(CrashSummary) - j, "\r\nAddress: %p", CrashPointers.ExceptionRecord->ExceptionAddress);
Writef (file, "%s\r\nFlags: %08X\r\n\r\n", CrashSummary, CrashPointers.ExceptionRecord->ExceptionFlags);
Writef (file, "Windows %s %d.%d Build %d %s\r\n\r\n",
verinfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ? "9x" : "NT",
verinfo.dwMajorVersion, verinfo.dwMinorVersion, verinfo.dwBuildNumber, verinfo.szCSDVersion);
CONTEXT *ctxt = CrashPointers.ContextRecord;
if (ctxt->ContextFlags & CONTEXT_SEGMENTS)
{
Writef (file, "GS=%04x FS=%04x ES=%04x DS=%04x\r\n",
ctxt->SegGs, ctxt->SegFs, ctxt->SegEs, ctxt->SegDs);
}
if (ctxt->ContextFlags & CONTEXT_INTEGER)
{
#ifndef _M_X64
Writef (file, "EAX=%08x EBX=%08x ECX=%08x EDX=%08x\r\nESI=%08x EDI=%08x\r\n",
ctxt->Eax, ctxt->Ebx, ctxt->Ecx, ctxt->Edx, ctxt->Esi, ctxt->Edi);
#else
Writef (file, "RAX=%016I64x RBX=%016I64x RCX=%016I64x\r\n"
"RDX=%016I64x RSI=%016I64x RDI=%016I64x\r\n"
"RBP=%016I64x R8=%016I64x R9=%016I64x\r\n"
"R10=%016I64x R11=%016I64x R12=%016I64x\r\n"
"R13=%016I64x R14=%016I64x R15=%016I64x\r\n",
ctxt->Rax, ctxt->Rbx, ctxt->Rcx, ctxt->Rdx, ctxt->Rsi, ctxt->Rdi, ctxt->Rbp,
ctxt->R8, ctxt->R9, ctxt->R10, ctxt->R11, ctxt->R12, ctxt->R13, ctxt->R14, ctxt->R15);
#endif
}
if (ctxt->ContextFlags & CONTEXT_CONTROL)
{
#ifndef _M_X64
Writef (file, "EBP=%08x EIP=%08x ESP=%08x CS=%04x SS=%04x\r\nEFlags=%08x\r\n",
ctxt->Ebp, ctxt->Eip, ctxt->Esp, ctxt->SegCs, ctxt->SegSs, ctxt->EFlags);
#else
Writef (file, "RIP=%016I64x RSP=%016I64x\r\nCS=%04x SS=%04x EFlags=%08x\r\n",
ctxt->Rip, ctxt->Rsp, ctxt->SegCs, ctxt->SegSs, ctxt->EFlags);
#endif
DWORD j;
for (i = 0, j = 1; (size_t)i < sizeof(eflagsBits)/sizeof(eflagsBits[0]); j <<= 1, ++i)
{
if (eflagsBits[i][0] != 'x')
{
Writef (file, " %c%c%c", eflagsBits[i][0], eflagsBits[i][1],
ctxt->EFlags & j ? '+' : '-');
}
}
Writef (file, "\r\n");
}
if (ctxt->ContextFlags & CONTEXT_FLOATING_POINT)
{
#ifndef _M_X64
Writef (file,
"\r\nFPU State:\r\n ControlWord=%04x StatusWord=%04x TagWord=%04x\r\n"
" ErrorOffset=%08x\r\n ErrorSelector=%08x\r\n DataOffset=%08x\r\n DataSelector=%08x\r\n"
// Cr0NpxState was renamed in recent Windows headers so better skip it here. Its meaning is unknown anyway.
//" Cr0NpxState=%08x\r\n"
"\r\n"
,
(uint16_t)ctxt->FloatSave.ControlWord, (uint16_t)ctxt->FloatSave.StatusWord, (uint16_t)ctxt->FloatSave.TagWord,
ctxt->FloatSave.ErrorOffset, ctxt->FloatSave.ErrorSelector, ctxt->FloatSave.DataOffset,
ctxt->FloatSave.DataSelector
//, ctxt->FloatSave.Cr0NpxState
);
for (i = 0; i < 8; ++i)
{
DWORD d0, d1;
memcpy(&d0, &ctxt->FloatSave.RegisterArea[20*i+4], sizeof(DWORD));
memcpy(&d1, &ctxt->FloatSave.RegisterArea[20*i], sizeof(DWORD));
Writef (file, "MM%d=%08x%08x\r\n", i, d0, d1);
}
#else
for (i = 0; i < 8; ++i)
{
Writef (file, "MM%d=%016I64x\r\n", i, ctxt->Legacy[i].Low);
}
for (i = 0; i < 16; ++i)
{
Writef (file, "XMM%d=%016I64x%016I64x\r\n", i, ctxt->FltSave.XmmRegisters[i].High, ctxt->FltSave.XmmRegisters[i].Low);
}
#endif
}
AddToolHelp (file);
#ifdef _M_IX86
Writef (file, "\r\nBytes near EIP:");
#else
Writef (file, "\r\nBytes near RIP:");
#endif
DumpBytes (file, (uint8_t *)CrashPointers.ExceptionRecord->ExceptionAddress-16);
if (ctxt->ContextFlags & CONTEXT_CONTROL)
{
#ifndef _M_X64
AddStackInfo (file, (void *)(size_t)CrashPointers.ContextRecord->Esp,
CrashPointers.ExceptionRecord->ExceptionCode, ctxt);
#else
AddStackInfo (file, (void *)CrashPointers.ContextRecord->Rsp,
CrashPointers.ExceptionRecord->ExceptionCode, ctxt);
#endif
}
return file;
}
//==========================================================================
//
// AddToolHelp
//
// Adds the information supplied by the tool help functions to the text
// report. This includes the list of threads for this process and the
// loaded modules.
//
//==========================================================================
static void AddToolHelp (HANDLE file)
{
HMODULE kernel = GetModuleHandleA ("kernel32.dll");
if (kernel == NULL)
return;
CREATESNAPSHOT pCreateToolhelp32Snapshot;
THREADWALK pThread32First;
THREADWALK pThread32Next;
MODULEWALK pModule32First;
MODULEWALK pModule32Next;
pCreateToolhelp32Snapshot = (CREATESNAPSHOT)GetProcAddress (kernel, "CreateToolhelp32Snapshot");
pThread32First = (THREADWALK)GetProcAddress (kernel, "Thread32First");
pThread32Next = (THREADWALK)GetProcAddress (kernel, "Thread32Next");
pModule32First = (MODULEWALK)GetProcAddress (kernel, "Module32FirstW");
pModule32Next = (MODULEWALK)GetProcAddress (kernel, "Module32NextW");
if (!(pCreateToolhelp32Snapshot && pThread32First && pThread32Next &&
pModule32First && pModule32Next))
{
Writef (file, "\r\nTool Help unavailable\r\n");
return;
}
HANDLE snapshot = pCreateToolhelp32Snapshot (TH32CS_SNAPMODULE|TH32CS_SNAPTHREAD, 0);
if (snapshot == INVALID_HANDLE_VALUE)
{
Writef (file, "\r\nTool Help snapshot unavailable\r\n");
return;
}
THREADENTRY32 thread = { sizeof(thread) };
Writef (file, "\r\nRunning threads:\r\n");
if (pThread32First (snapshot, &thread))
{
do
{
if (thread.th32OwnerProcessID == DbgProcessID)
{
Writef (file, "%08x", thread.th32ThreadID);
if (thread.th32ThreadID == DbgThreadID)
{
Writef (file, " at %p*", CrashAddress);
}
Writef (file, "\r\n");
}
} while (pThread32Next (snapshot, &thread));
}
MODULEENTRY32 module = { sizeof(module) };
Writef (file, "\r\nLoaded modules:\r\n");
if (pModule32First (snapshot, &module))
{
do
{
auto amod = FString(module.szModule);
Writef (file, "%p - %p %c%s\r\n",
module.modBaseAddr, module.modBaseAddr + module.modBaseSize - 1,
module.modBaseAddr <= CrashPointers.ExceptionRecord->ExceptionAddress &&
module.modBaseAddr + module.modBaseSize > CrashPointers.ExceptionRecord->ExceptionAddress
? '*' : ' ',
amod.GetChars());
} while (pModule32Next (snapshot, &module));
}
CloseHandle (snapshot);
}
//==========================================================================
//
// AddStackInfo
//
// Writes a stack dump to the text report.
//
//==========================================================================
static void AddStackInfo (HANDLE file, void *dumpaddress, DWORD code, CONTEXT *ctxt)
{
DWORD *addr = (DWORD *)dumpaddress, *jump;
DWORD *topOfStack = GetTopOfStack (dumpaddress);
uint8_t peekb;
#ifdef _M_IX86
DWORD peekd;
#else
uint64_t peekq;
#endif
jump = topOfStack;
if (code == EXCEPTION_STACK_OVERFLOW)
{
// If the stack overflowed, only dump the first and last 16KB of it.
if (topOfStack - addr > 32768/4)
{
jump = addr + 16384/4;
}
}
StackWalk (file, dumpaddress, topOfStack, jump, ctxt);
Writef (file, "\r\nStack Contents:\r\n");
DWORD *scan;
for (scan = addr; scan < topOfStack; scan += 4)
{
int i;
ptrdiff_t max;
if (scan == jump)
{
scan = topOfStack - 16384/4;
Writef (file, "\r\n . . . Snip . . .\r\n\r\n");
}
Writef (file, "%p:", scan);
#ifdef _M_IX86
if (topOfStack - scan < 4)
{
max = topOfStack - scan;
}
else
{
max = 4;
}
for (i = 0; i < max; ++i)
{
if (!SafeReadMemory (&scan[i], &peekd, 4))
{
break;
}
Writef (file, " %08x", peekd);
}
for (; i < 4; ++i)
{
Writef (file, " ");
}
#else
if ((uint64_t *)topOfStack - (uint64_t *)scan < 2)
{
max = (uint64_t *)topOfStack - (uint64_t *)scan;
}
else
{
max = 2;
}
for (i = 0; i < max; ++i)
{
if (!SafeReadMemory (&scan[i], &peekq, 8))
{
break;
}
Writef (file, " %016x", peekq);
}
if (i < 2)
{
Writef (file, " ");
}
#endif
Writef (file, " ");
for (i = 0; i < int(max*sizeof(void*)); ++i)
{
if (!SafeReadMemory ((uint8_t *)scan + i, &peekb, 1))
{
break;
}
Writef (file, "%c", peekb >= 0x20 && peekb <= 0x7f ? peekb : '\xb7');
}
Writef (file, "\r\n");
}
}
#ifdef _M_IX86
//==========================================================================
//
// StackWalk
// Win32 version
//
// Lists a possible call trace for the crashing thread to the text report.
// This is not very specific and just lists any pointers into the
// main executable region, whether they are part of the real trace or not.
//
//==========================================================================
static void StackWalk (HANDLE file, void *dumpaddress, DWORD *topOfStack, DWORD *jump, CONTEXT *ctxt)
{
DWORD *addr = (DWORD *)dumpaddress;
uint8_t *pBaseOfImage = (uint8_t *)GetModuleHandle (0);
IMAGE_OPTIONAL_HEADER *pHeader = (IMAGE_OPTIONAL_HEADER *)(pBaseOfImage +
((IMAGE_DOS_HEADER*)pBaseOfImage)->e_lfanew +
sizeof(IMAGE_NT_SIGNATURE) + sizeof(IMAGE_FILE_HEADER));
DWORD_PTR codeStart = (DWORD_PTR)pBaseOfImage + pHeader->BaseOfCode;
DWORD_PTR codeEnd = codeStart + pHeader->SizeOfCode;
Writef (file, "\r\nPossible call trace:\r\n %08x BOOM", CrashAddress);
for (DWORD *scan = addr; scan < topOfStack; ++scan)
{
if (scan == jump)
{
scan = topOfStack - 16384/4;
Writef (file, "\r\n\r\n . . . . Snip . . . .\r\n");
}
DWORD_PTR code;
if (SafeReadMemory (scan, &code, sizeof(code)) &&
code >= codeStart && code < codeEnd)
{
static const char regNames[][4] =
{
"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi"
};
// Check if address is after a call statement. Print what was called if it is.
const uint8_t *bytep = (uint8_t *)code;
uint8_t peekb;
#define chkbyte(x,m,v) (SafeReadMemory(x, &peekb, 1) && ((peekb & m) == v))
if (chkbyte(bytep - 5, 0xFF, 0xE8) || chkbyte(bytep - 5, 0xFF, 0xE9))
{
DWORD peekd;
if (SafeReadMemory (bytep - 4, &peekd, 4))
{
DWORD_PTR jumpaddr = peekd + code;
Writef (file, "\r\n %p %s %p", code - 5,
peekb == 0xE9 ? "jmp " : "call", jumpaddr);
if (chkbyte((LPCVOID)jumpaddr, 0xFF, 0xE9) &&
SafeReadMemory ((LPCVOID)(jumpaddr + 1), &peekd, 4))
{
Writef (file," => jmp %p", peekd + jumpaddr + 5);
}
}
else
{
Writef (file, "\r\n %p %s ????????", code - 5,
peekb == 0xE9 ? "jmp " : "call");
}
}
else if (chkbyte(bytep - 2, 0xFF, 0xFF) && chkbyte(bytep - 1, 0xF7, 0xD0))
{
Writef (file, "\r\n %p call %s", code - 2, regNames[peekb & 7]);
}
else
{
int i;
for (i = 2; i < 7; ++i)
{
if (chkbyte(bytep - i, 0xFF, 0xFF) && chkbyte(bytep - i + 1, 070, 020))
{
break;
}
}
if (i >= 7)
{
Writef (file, "\r\n %08x", code);
}
else
{
int mod, rm, basereg = -1, indexreg = -1;
int scale = 1, offset = 0;
Writef (file, "\r\n %08x", bytep - i);
bytep -= i - 1;
SafeReadMemory (bytep, &peekb, 1);
mod = peekb >> 6;
rm = peekb & 7;
bytep++;
if (mod == 0 && rm == 5)
{
mod = 2;
}
else if (rm == 4)
{
int index, base;
SafeReadMemory (bytep, &peekb, 1);
scale = 1 << (peekb >> 6);
index = (peekb >> 3) & 7;
base = peekb & 7;
bytep++;
if (index != 4)
{
indexreg = index;
}
if (base != 5)
{
basereg = base;
}
else
{
if (mod == 0)
{
mod = 2;
}
else
{
basereg = 5;
}
}
}
else
{
basereg = rm;
}
if (mod == 1)
{
signed char miniofs;
SafeReadMemory (bytep++, &miniofs, 1);
offset = miniofs;
}
else
{
SafeReadMemory (bytep, &offset, 4);
bytep += 4;
}
if ((DWORD_PTR)bytep == code)
{
Writef (file, " call [", bytep - i + 1);
if (basereg >= 0)
{
Writef (file, "%s", regNames[basereg]);
}
if (indexreg >= 0)
{
Writef (file, "%s%s", basereg >= 0 ? "+" : "", regNames[indexreg]);
if (scale > 1)
{
Writef (file, "*%d", scale);
}
}
if (offset != 0)
{
if (indexreg < 0 && basereg < 0)
{
Writef (file, "%08x", offset);
}
else
{
char sign;
if (offset < 0)
{
sign = '-';
offset = -offset;
}
else
{
sign = '+';
}
Writef (file, "%c0x%x", sign, offset);
}
}
Writef (file, "]");
}
}
}
}
}
Writef (file, "\r\n");
}
#else
//==========================================================================
//
// StackWalk
// Win64 version
//
// Walks the stack for the crashing thread and dumps the trace to the text
// report. Unlike the Win32 version, Win64 provides facilities for
// doing a 100% exact walk.
//
// See http://www.nynaeve.net/?p=113 for more information, and
// http://www.nynaeve.net/Code/StackWalk64.cpp in particular.
//
//==========================================================================
static void StackWalk (HANDLE file, void *dumpaddress, DWORD *topOfStack, DWORD *jump, CONTEXT *ctxt)
{
RTLLOOKUPFUNCTIONENTRY RtlLookupFunctionEntry;
HMODULE kernel;
CONTEXT context;
KNONVOLATILE_CONTEXT_POINTERS nv_context;
PRUNTIME_FUNCTION function;
PVOID handler_data;
ULONG64 establisher_frame;
ULONG64 image_base;
Writef (file, "\r\nCall trace:\r\n rip=%p <- Here it dies.\r\n", CrashAddress);
kernel = GetModuleHandleA("kernel32.dll");
if (kernel == NULL || NULL == (RtlLookupFunctionEntry =
(RTLLOOKUPFUNCTIONENTRY)GetProcAddress(kernel, "RtlLookupFunctionEntry")))
{
Writef (file, " Unavailable: Could not get address of RtlLookupFunctionEntry\r\n");
return;
}
// Get the caller's context
context = *ctxt;
// This unwind loop intentionally skips the first call frame, as it
// shall correspond to the call to StackTrace64, which we aren't
// interested in.
for (ULONG frame = 0; ; ++frame)
{
// Try to look up unwind metadata for the current function.
function = RtlLookupFunctionEntry(context.Rip, &image_base, NULL);
memset(&nv_context, 0, sizeof(nv_context));
if (function == NULL)
{
// If we don't have a RUNTIME_FUNCTION, then we've encountered
// a leaf function. Adjust the stack appropriately.
context.Rip = (ULONG64)(*(PULONG64)context.Rsp);
context.Rsp += 8;
Writef(file, " Leaf function\r\n\r\n");
}
else
{
// Note that there is not a one-to-one correspondance between
// runtime functions and source functions. One source function
// may be broken into multiple runtime functions. This loop walks
// backward from the current runtime function for however many
// consecutive runtime functions precede it. There is a slight
// chance that this will walk across different source functions.
// (Or maybe not, depending on whether or not the compiler
// guarantees that there will be empty space between functions;
// it looks like VC++ might.) In practice, this seems to work
// quite well for identifying the exact address to search for in
// a map file to determine the function name.
PRUNTIME_FUNCTION function2 = function;
ULONG64 base = image_base;
while (function2 != NULL)
{
Writef(file, " Function range: %p -> %p\r\n",
(void *)(base + function2->BeginAddress),
(void *)(base + function2->EndAddress));
function2 = RtlLookupFunctionEntry(base + function2->BeginAddress - 1, &base, NULL);
}
Writef(file, "\r\n");
// Use RtlVirtualUnwind to execute the unwind for us.
RtlVirtualUnwind(0/*UNW_FLAG_NHANDLER*/, image_base, context.Rip,
function, &context, &handler_data, &establisher_frame,
&nv_context);
}
// If we reach a RIP of zero, this means we've walked off the end of
// the call stack and are done.
if (context.Rip == 0)
{
break;
}
// Display the context. Note that we don't bother showing the XMM
// context, although we have the nonvolatile portion of it.
Writef(file, " FRAME %02d:\r\n rip=%p rsp=%p rbp=%p\r\n",
frame, context.Rip, context.Rsp, context.Rbp);
Writef(file, " r12=%p r13=%p r14=%p\r\n"
" rdi=%p rsi=%p rbx=%p\r\n",
context.R12, context.R13, context.R14,
context.Rdi, context.Rsi, context.Rbx);
static const char reg_names[16][4] =
{
"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"
};
// If we have stack-based register stores, then display them here.
for (int i = 0; i < 16; ++i)
{
if (nv_context.IntegerContext[i])
{
Writef(file, " -> '%s' saved on stack at %p (=> %p)\r\n",
reg_names[i], nv_context.IntegerContext[i], *nv_context.IntegerContext[i]);
}
}
}
}
#endif
//==========================================================================
//
// DumpBytes
//
// Writes the bytes around EIP to the text report.
//
//==========================================================================
static void DumpBytes (HANDLE file, uint8_t *address)
{
char line[68*3], *line_p = line;
DWORD len;
uint8_t peek;
for (int i = 0; i < 16*3; ++i)
{
if ((i & 15) == 0)
{
line_p += mysnprintf (line_p, countof(line) - (line_p - line), "\r\n%p:", address);
}
if (SafeReadMemory (address, &peek, 1))
{
line_p += mysnprintf (line_p, countof(line) - (line_p - line), " %02x", *address);
}
else
{
line_p += mysnprintf (line_p, countof(line) - (line_p - line), " --");
}
address++;
}
*line_p++ = '\r';
*line_p++ = '\n';
WriteFile (file, line, DWORD(line_p - line), &len, NULL);
}
//==========================================================================
//
// CreateTempFile
//
// Creates a new temporary with read/write access. The file will be deleted
// automatically when it is closed.
//
//==========================================================================
static HANDLE CreateTempFile ()
{
WCHAR temppath[MAX_PATH-13];
WCHAR tempname[MAX_PATH];
if (!GetTempPathW (countof(temppath), temppath))
{
temppath[0] = '.';
temppath[1] = '\0';
}
if (!GetTempFileNameW (temppath, L"zdo", 0, tempname))
{
return INVALID_HANDLE_VALUE;
}
return CreateFileW (tempname, GENERIC_WRITE|GENERIC_READ, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE|FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
}
//==========================================================================
//
// AddFile
//
// Add another file to be written to the tar.
//
//==========================================================================
static void AddFile (HANDLE file, const char *filename)
{
if (NumFiles == MAX_FILES || file == INVALID_HANDLE_VALUE)
{
return;
}
TarFiles[NumFiles].File = file;
TarFiles[NumFiles].Filename = filename;
NumFiles++;
}
//==========================================================================
//
// CloseTarFiles
//
// Close all files that were previously registerd with AddFile(). They
// must still be open.
//
//==========================================================================
static void CloseTarFiles ()
{
for (int i = 0; i < NumFiles; ++i)
{
CloseHandle (TarFiles[i].File);
}
NumFiles = 0;
}
//==========================================================================
//
// WriteBlock
//
// This is a wrapper around WriteFile. If stream is non-NULL, then the data
// is compressed before writing it to the file. outbuf must be 1024 bytes.
//
//==========================================================================
static DWORD WriteBlock (HANDLE file, LPCVOID buffer, DWORD bytes, z_stream *stream, Bytef *outbuf)
{
if (stream == NULL)
{
WriteFile (file, buffer, bytes, &bytes, NULL);
return bytes;
}
else
{
DWORD wrote = 0;
stream->next_in = (Bytef *)buffer;
stream->avail_in = bytes;
while (stream->avail_in != 0)
{
if (stream->avail_out == 0)
{
stream->next_out = outbuf;
stream->avail_out = 1024;
wrote += 1024;
WriteFile (file, outbuf, 1024, &bytes, NULL);
}
deflate (stream, Z_NO_FLUSH);
}
return wrote;
}
}
//==========================================================================
//
// WriteZip
//
// Writes a .zip/pk3 file. A HANDLE to the file is returned. This file is
// delete-on-close.
//
// The archive contains all the files previously passed to AddFile(). They
// must still be open, because MakeZip() does not open them itself.
//
//==========================================================================
static HANDLE MakeZip ()
{
zip::CentralDirectoryEntry central = { ZIP_CENTRALFILE, { 20, 0 }, { 20, 0 }, };
zip::EndOfCentralDirectory dirend = { ZIP_ENDOFDIR, };
short dosdate, dostime;
time_t now;
struct tm *nowtm;
int i, numfiles;
HANDLE file;
DWORD len;
uint32_t dirsize;
size_t namelen;
if (NumFiles == 0)
{
return INVALID_HANDLE_VALUE;
}
// Open the zip file.
file = CreateTempFile ();
if (file == INVALID_HANDLE_VALUE)
{
return file;
}
time (&now);
nowtm = localtime (&now);
if (nowtm == NULL || nowtm->tm_year < 80)
{
dosdate = dostime = 0;
}
else
{
dosdate = (nowtm->tm_year - 80) * 512 + (nowtm->tm_mon + 1) * 32 + nowtm->tm_mday;
dostime = nowtm->tm_hour * 2048 + nowtm->tm_min * 32 + nowtm->tm_sec / 2;
dosdate = LittleShort(dosdate);
dostime = LittleShort(dostime);
}
// Write out the zip archive, one file at a time
for (i = 0; i < NumFiles; ++i)
{
AddZipFile (file, &TarFiles[i], dosdate, dostime);
}
// Write the central directory
central.ModTime = dostime;
central.ModDate = dosdate;
dirend.DirectoryOffset = LittleLong((uint32_t)SetFilePointer (file, 0, NULL, FILE_CURRENT));
for (i = 0, numfiles = 0, dirsize = 0; i < NumFiles; ++i)
{
// Skip empty files
if (TarFiles[i].UncompressedSize == 0)
{
continue;
}
numfiles++;
if (TarFiles[i].Deflated)
{
central.Flags = LittleShort((uint16_t)2);
central.Method = LittleShort((uint16_t)8);
}
else
{
central.Flags = 0;
central.Method = 0;
}
namelen = strlen(TarFiles[i].Filename);
central.InternalAttributes = 0;
if (namelen > 4 && stricmp(TarFiles[i].Filename - 4, ".txt") == 0)
{ // Bit 0 set indicates this is probably a text file. But do any tools use it?
central.InternalAttributes = LittleShort((uint16_t)1);
}
central.CRC32 = LittleLong(TarFiles[i].CRC32);
central.CompressedSize = LittleLong(TarFiles[i].CompressedSize);
central.UncompressedSize = LittleLong(TarFiles[i].UncompressedSize);
central.NameLength = LittleShort((uint16_t)namelen);
central.LocalHeaderOffset = LittleLong(TarFiles[i].ZipOffset);
WriteFile (file, &central, sizeof(central), &len, NULL);
WriteFile (file, TarFiles[i].Filename, (DWORD)namelen, &len, NULL);
dirsize += DWORD(sizeof(central) + namelen);
}
// Write the directory terminator
dirend.NumEntriesOnAllDisks = dirend.NumEntries = LittleShort((uint16_t)numfiles);
dirend.DirectorySize = LittleLong(dirsize);
WriteFile (file, &dirend, sizeof(dirend), &len, NULL);
return file;
}
//==========================================================================
//
// AddZipFile
//
// Adds a file to the opened zip.
//
//==========================================================================
static void AddZipFile (HANDLE ziphandle, TarFile *whichfile, short dosdate, short dostime)
{
zip::LocalFileHeader local = { ZIP_LOCALFILE, };
Bytef outbuf[1024], inbuf[1024];
z_stream stream;
DWORD wrote, len, k;
int err;
bool gzip;
whichfile->UncompressedSize = GetFileSize (whichfile->File, NULL);
whichfile->CompressedSize = 0;
whichfile->ZipOffset = 0;
whichfile->Deflated = false;
// Skip empty files.
if (whichfile->UncompressedSize == 0)
{
return;
}
stream.next_in = Z_NULL;
stream.avail_in = 0;
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
err = deflateInit2 (&stream, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS,
8, Z_DEFAULT_STRATEGY);
gzip = err == Z_OK;
if (gzip)
{
local.Method = LittleShort((uint16_t)8);
whichfile->Deflated = true;
stream.next_out = outbuf;
stream.avail_out = sizeof(outbuf);
}
// Write out the header and filename.
local.VersionToExtract[0] = 20;
local.Flags = gzip ? LittleShort((uint16_t)2) : 0;
local.ModTime = dostime;
local.ModDate = dosdate;
local.UncompressedSize = LittleLong(whichfile->UncompressedSize);
local.NameLength = LittleShort((uint16_t)strlen(whichfile->Filename));
whichfile->ZipOffset = SetFilePointer (ziphandle, 0, NULL, FILE_CURRENT);
WriteFile (ziphandle, &local, sizeof(local), &wrote, NULL);
WriteFile (ziphandle, whichfile->Filename, (DWORD)strlen(whichfile->Filename), &wrote, NULL);
// Write the file itself and calculate its CRC.
SetFilePointer (whichfile->File, 0, NULL, FILE_BEGIN);
for (k = 0; k < whichfile->UncompressedSize; )
{
len = whichfile->UncompressedSize - k;
if (len > 1024)
{
len = 1024;
}
k += len;
ReadFile (whichfile->File, &inbuf, len, &len, NULL);
whichfile->CRC32 = crc32 (whichfile->CRC32, inbuf, len);
whichfile->CompressedSize += WriteBlock (ziphandle, inbuf, len, gzip ? &stream : NULL, outbuf);
}
// Flush the zlib stream buffer.
if (gzip)
{
for (bool done = false;;)
{
len = sizeof(outbuf) - stream.avail_out;
if (len != 0)
{
whichfile->CompressedSize += len;
WriteFile (ziphandle, outbuf, len, &wrote, NULL);
stream.next_out = outbuf;
stream.avail_out = sizeof(outbuf);
}
if (done)
{
break;
}
err = deflate (&stream, Z_FINISH);
done = stream.avail_out != 0 || err == Z_STREAM_END;
if (err != Z_STREAM_END && err != Z_OK)
{
break;
}
}
deflateEnd (&stream);
}
// Fill in fields we didn't know when we wrote the local header.
SetFilePointer (ziphandle, whichfile->ZipOffset + 14, NULL, FILE_BEGIN);
k = LittleLong(whichfile->CRC32);
WriteFile (ziphandle, &k, 4, &wrote, NULL);
k = LittleLong(whichfile->CompressedSize);
WriteFile (ziphandle, &k, 4, &wrote, NULL);
SetFilePointer (ziphandle, 0, NULL, FILE_END);
}
//==========================================================================
//
// DrawTransparentBitmap
//
// The replacement for TransparentBlt that does not leak memory under
// Windows 98/ME. As seen in the MSKB.
//
//==========================================================================
void DrawTransparentBitmap(HDC hdc, HBITMAP hBitmap, short xStart,
short yStart, COLORREF cTransparentColor)
{
BITMAP bm;
COLORREF cColor;
HBITMAP bmAndBack, bmAndObject, bmAndMem, bmSave;
HBITMAP bmBackOld, bmObjectOld, bmMemOld, bmSaveOld;
HDC hdcMem, hdcBack, hdcObject, hdcTemp, hdcSave;
POINT ptSize;
hdcTemp = CreateCompatibleDC(hdc);
SelectObject(hdcTemp, hBitmap); // Select the bitmap
GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&bm);
ptSize.x = bm.bmWidth; // Get width of bitmap
ptSize.y = bm.bmHeight; // Get height of bitmap
DPtoLP(hdcTemp, &ptSize, 1); // Convert from device
// to logical points
// Create some DCs to hold temporary data.
hdcBack = CreateCompatibleDC(hdc);
hdcObject = CreateCompatibleDC(hdc);
hdcMem = CreateCompatibleDC(hdc);
hdcSave = CreateCompatibleDC(hdc);
// Create a bitmap for each DC. DCs are required for a number of
// GDI functions.
// Monochrome DC
bmAndBack = CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
// Monochrome DC
bmAndObject = CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
bmAndMem = CreateCompatibleBitmap(hdc, ptSize.x, ptSize.y);
bmSave = CreateCompatibleBitmap(hdc, ptSize.x, ptSize.y);
// Each DC must select a bitmap object to store pixel data.
bmBackOld = (HBITMAP)SelectObject(hdcBack, bmAndBack);
bmObjectOld = (HBITMAP)SelectObject(hdcObject, bmAndObject);
bmMemOld = (HBITMAP)SelectObject(hdcMem, bmAndMem);
bmSaveOld = (HBITMAP)SelectObject(hdcSave, bmSave);
// Set proper mapping mode.
SetMapMode(hdcTemp, GetMapMode(hdc));
// Save the bitmap sent here, because it will be overwritten.
BitBlt(hdcSave, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0, SRCCOPY);
// Set the background color of the source DC to the color.
// contained in the parts of the bitmap that should be transparent
cColor = SetBkColor(hdcTemp, cTransparentColor);
// Create the object mask for the bitmap by performing a BitBlt
// from the source bitmap to a monochrome bitmap.
BitBlt(hdcObject, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0,
SRCCOPY);
// Set the background color of the source DC back to the original
// color.
SetBkColor(hdcTemp, cColor);
// Create the inverse of the object mask.
BitBlt(hdcBack, 0, 0, ptSize.x, ptSize.y, hdcObject, 0, 0,
NOTSRCCOPY);
// Copy the background of the main DC to the destination.
BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdc, xStart, yStart,
SRCCOPY);
// Mask out the places where the bitmap will be placed.
BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdcObject, 0, 0, SRCAND);
// Mask out the transparent colored pixels on the bitmap.
BitBlt(hdcTemp, 0, 0, ptSize.x, ptSize.y, hdcBack, 0, 0, SRCAND);
// XOR the bitmap with the background on the destination DC.
BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0, SRCPAINT);
// Copy the destination to the screen.
BitBlt(hdc, xStart, yStart, ptSize.x, ptSize.y, hdcMem, 0, 0,
SRCCOPY);
// Place the original bitmap back into the bitmap sent here.
BitBlt(hdcTemp, 0, 0, ptSize.x, ptSize.y, hdcSave, 0, 0, SRCCOPY);
// Delete the memory bitmaps.
DeleteObject(SelectObject(hdcBack, bmBackOld));
DeleteObject(SelectObject(hdcObject, bmObjectOld));
DeleteObject(SelectObject(hdcMem, bmMemOld));
DeleteObject(SelectObject(hdcSave, bmSaveOld));
// Delete the memory DCs.
DeleteDC(hdcMem);
DeleteDC(hdcBack);
DeleteDC(hdcObject);
DeleteDC(hdcSave);
DeleteDC(hdcTemp);
}
//==========================================================================
//
// TransparentStaticProc
//
// A special window procedure for the static control displaying the dying
// Doom Guy. It draws the bitmap with magenta as transparent. I could have
// made it an owner-draw control instead, but then I wouldn't get any of
// the standard bitmap graphic handling (like automatic sizing).
//
//==========================================================================
static LRESULT CALLBACK TransparentStaticProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
RECT rect;
PAINTSTRUCT paint;
HDC dc;
if (uMsg == WM_PAINT)
{
if (GetUpdateRect (hWnd, &rect, FALSE))
{
dc = BeginPaint (hWnd, &paint);
if (dc != NULL)
{
HBITMAP bitmap = (HBITMAP)SendMessage (hWnd, STM_GETIMAGE, IMAGE_BITMAP, 0);
if (bitmap != NULL)
{
DrawTransparentBitmap (dc, bitmap, 0, 0, RGB(255,0,255));
}
EndPaint (hWnd, &paint);
}
}
return 0;
}
return CallWindowProc (StdStaticProc, hWnd, uMsg, wParam, lParam);
}
static HMODULE WinHlp32;
//==========================================================================
//
// OverviewDlgProc
//
// The DialogProc for the crash overview page.
//
//==========================================================================
static INT_PTR CALLBACK OverviewDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
CHARFORMAT charFormat;
HWND edit;
ENLINK *link;
switch (message)
{
case WM_INITDIALOG:
{
if (pEnableThemeDialogTexture != NULL)
{
pEnableThemeDialogTexture(hDlg, ETDT_ENABLETAB);
}
// Setup the header at the top of the page.
edit = GetDlgItem(hDlg, IDC_CRASHHEADER);
SetWindowTextW(edit, WGAMENAME" has encountered a problem and needs to close.\n"
"We are sorry for the inconvenience.");
// Setup a bold version of the standard dialog font and make the header bold.
charFormat.cbSize = sizeof(charFormat);
SendMessageW(edit, EM_GETCHARFORMAT, SCF_DEFAULT, (LPARAM)&charFormat);
charFormat.dwEffects = CFE_BOLD;
SendMessageW(edit, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&charFormat);
// Setup the drawing routine for the dying guy's bitmap.
edit = GetDlgItem(hDlg, IDC_DEADGUYVIEWER);
StdStaticProc = (WNDPROC)(LONG_PTR)SetWindowLongPtr(edit, GWLP_WNDPROC, (WLONG_PTR)(LONG_PTR)TransparentStaticProc);
// Fill in all the text just below the heading.
edit = GetDlgItem(hDlg, IDC_PLEASETELLUS);
SendMessageW(edit, EM_AUTOURLDETECT, TRUE, 0);
SetWindowTextW(edit, L"Please tell us about this problem.\n"
"The information will NOT be sent to Microsoft.\n\n"
"An error report has been created that you can submit to help improve " GAMENAME ". "
"You can either save it to disk and make a report in the bugs forum at " FORUM_URL ", "
"or you can send it directly without letting other people know about it.");
SendMessageW(edit, EM_SETSEL, 0, 81);
SendMessageW(edit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&charFormat);
SendMessageW(edit, EM_SETEVENTMASK, 0, ENM_LINK);
// Assign a default invalid file handle to the user's edit control.
edit = GetDlgItem(hDlg, IDC_CRASHINFO);
SetWindowLongPtrW(edit, GWLP_USERDATA, (LONG_PTR)INVALID_HANDLE_VALUE);
// Fill in the summary text at the bottom of the page.
edit = GetDlgItem(hDlg, IDC_CRASHSUMMARY);
auto wsum = WideString(CrashSummary);
SetWindowTextW(edit, wsum.c_str());
return TRUE;
}
case WM_NOTIFY:
// When the link in the "please tell us" edit control is clicked, open
// the bugs forum in a browser window. ShellExecute is used to do this,
// so the default browser, whatever it may be, will open it.
link = (ENLINK *)lParam;
if (link->nmhdr.idFrom == IDC_PLEASETELLUS && link->nmhdr.code == EN_LINK)
{
if (link->msg == WM_LBUTTONDOWN)
{
ShellExecuteA (NULL, "open", BUGS_FORUM_URL, NULL, NULL, 0);
SetWindowLongPtrW (hDlg, DWLP_MSGRESULT, 1);
return TRUE;
}
}
return FALSE;
break;
}
return FALSE;
}
//==========================================================================
//
// CrashDlgProc
//
// The DialogProc for the main crash dialog.
//
//==========================================================================
static INT_PTR CALLBACK CrashDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
static WCHAR overview[] = L"Overview";
static WCHAR details[] = L"Details";
HWND edit;
TCITEM tcitem;
RECT tabrect, tcrect;
LPNMHDR nmhdr;
switch (message)
{
case WM_INITDIALOG:
// Set up the tab control
tcitem.mask = TCIF_TEXT | TCIF_PARAM;
edit = GetDlgItem (hDlg, IDC_CRASHTAB);
GetWindowRect (edit, &tcrect);
ScreenToClient (hDlg, (LPPOINT)&tcrect.left);
ScreenToClient (hDlg, (LPPOINT)&tcrect.right);
// There are two tabs: Overview and Details. Each pane is created from a
// dialog template, and the resultant window is stored as the lParam for
// the corresponding tab.
tcitem.pszText = overview;
tcitem.lParam = (LPARAM)CreateDialogParamW (g_hInst, MAKEINTRESOURCE(IDD_CRASHOVERVIEW), hDlg, OverviewDlgProc, (LPARAM)edit);
TabCtrl_InsertItem (edit, 0, &tcitem);
TabCtrl_GetItemRect (edit, 0, &tabrect);
SetWindowPos ((HWND)tcitem.lParam, HWND_TOP, tcrect.left + 3, tcrect.top + tabrect.bottom + 3,
tcrect.right - tcrect.left - 8, tcrect.bottom - tcrect.top - tabrect.bottom - 8, 0);
tcitem.pszText = details;
tcitem.lParam = (LPARAM)CreateDialogParamW (g_hInst, MAKEINTRESOURCE(IDD_CRASHDETAILS), hDlg, DetailsDlgProc, (LPARAM)edit);
TabCtrl_InsertItem (edit, 1, &tcitem);
SetWindowPos ((HWND)tcitem.lParam, HWND_TOP, tcrect.left + 3, tcrect.top + tabrect.bottom + 3,
tcrect.right - tcrect.left - 8, tcrect.bottom - tcrect.top - tabrect.bottom - 8, 0);
break;
case WM_NOTIFY:
nmhdr = (LPNMHDR)lParam;
if (nmhdr->idFrom == IDC_CRASHTAB)
{
int i = TabCtrl_GetCurSel (nmhdr->hwndFrom);
tcitem.mask = TCIF_PARAM;
TabCtrl_GetItem (nmhdr->hwndFrom, i, &tcitem);
edit = (HWND)tcitem.lParam;
// TCN_SELCHANGING is sent just before the selected tab changes.
// TCN_SELCHANGE is sent just after the selected tab changes.
// So, when TCN_SELCHANGING is received hide the pane for the
// selected tab, and when TCN_SELCHANGE is received, show the pane
// for the selected tab.
if (nmhdr->code == TCN_SELCHANGING)
{
ShowWindow (edit, SW_HIDE);
SetWindowLongPtr (hDlg, DWLP_MSGRESULT, FALSE);
return TRUE;
}
else if (nmhdr->code == TCN_SELCHANGE)
{
ShowWindow (edit, SW_SHOW);
return TRUE;
}
else
{
return FALSE;
}
}
break;
case WM_COMMAND:
if (HIWORD(wParam) == BN_CLICKED)
{
EndDialog (hDlg, LOWORD(wParam));
}
break;
}
return FALSE;
}
//==========================================================================
//
// DetailsDlgProc
//
// The dialog procedure for when the user wants to examine the files
// in the report.
//
//==========================================================================
static INT_PTR CALLBACK DetailsDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
HWND ctrl;
int i, j;
switch (message)
{
case WM_INITDIALOG:
if (pEnableThemeDialogTexture != NULL)
{
pEnableThemeDialogTexture (hDlg, ETDT_ENABLETAB);
}
// Set up the file contents display: No undos. The control's
// userdata stores the index of the file currently displayed.
ctrl = GetDlgItem (hDlg, IDC_CRASHFILECONTENTS);
SendMessageW (ctrl, EM_SETUNDOLIMIT, 0, 0);
SetWindowLongPtrW (ctrl, GWLP_USERDATA, -1);
SetEditControl (ctrl, GetDlgItem(hDlg, IDC_CRASHFILESIZE), 0);
SendMessageW (ctrl, LB_SETCURSEL, 0, 0);
break;
case WM_SHOWWINDOW:
// When showing this pane, refresh the list of packaged files, and
// update the contents display as necessary.
if (wParam == TRUE)
{
ctrl = GetDlgItem (hDlg, IDC_CRASHFILES);
j = (int)SendMessageW (ctrl, LB_GETCURSEL, 0, 0);
SendMessageW (ctrl, LB_RESETCONTENT, 0, 0);
for (i = 0; i < NumFiles; ++i)
{
SendMessageA (ctrl, LB_ADDSTRING, 0, (LPARAM)TarFiles[i].Filename);
}
if (j == LB_ERR || j >= i) j = 0;
SendMessageW (ctrl, LB_SETCURSEL, j, 0);
ctrl = GetDlgItem (hDlg, IDC_CRASHFILECONTENTS);
if (j > 2) SetWindowLongPtr (ctrl, GWLP_USERDATA, -1);
SetEditControl (ctrl, GetDlgItem(hDlg, IDC_CRASHFILESIZE), j);
}
break;
case WM_COMMAND:
// Selecting a different file makes the contents display update.
if (HIWORD(wParam) == LBN_SELCHANGE)
{
i = (int)SendMessage ((HWND)lParam, LB_GETCURSEL, 0, 0);
if (i != LB_ERR)
{
// Update the contents control for this file
SetEditControl (GetDlgItem (hDlg, IDC_CRASHFILECONTENTS),
GetDlgItem (hDlg, IDC_CRASHFILESIZE), i);
}
}
break;
}
return FALSE;
}
//==========================================================================
//
// StreamEditText
//
// The callback function to stream a text file into a rich edit control.
//
//==========================================================================
static DWORD CALLBACK StreamEditText (DWORD_PTR cookie, LPBYTE buffer, LONG cb, LONG *pcb)
{
DWORD wrote;
ReadFile ((HANDLE)cookie, buffer, cb, &wrote, NULL);
*pcb = wrote;
return wrote == 0;
}
//==========================================================================
//
// StreamEditBinary
//
// The callback function to stream a binary file into a rich edit control.
// The binary file is converted to a RTF hex dump so that the different
// columns can be color-coded.
//
//==========================================================================
struct BinStreamInfo
{
int Stage;
HANDLE File;
DWORD Pointer;
};
static DWORD CALLBACK StreamEditBinary (DWORD_PTR cookie, LPBYTE buffer, LONG cb, LONG *pcb)
{
BinStreamInfo *info = (BinStreamInfo *)cookie;
uint8_t buf16[16];
DWORD read, i;
char *buff_p = (char *)buffer;
char *buff_end = (char *)buffer + cb;
repeat:
switch (info->Stage)
{
case 0: // Write prologue
buff_p += mysnprintf (buff_p, buff_end - buff_p, "{\\rtf1\\ansi\\deff0"
"{\\colortbl ;\\red0\\green0\\blue80;\\red0\\green0\\blue0;\\red80\\green0\\blue80;}"
"\\viewkind4\\pard");
info->Stage++;
break;
case 1: // Write body
while (cb - ((LPBYTE)buff_p - buffer) > 150)
{
ReadFile (info->File, buf16, 16, &read, NULL);
if (read == 0 || info->Pointer >= 65536)
{
info->Stage = read == 0 ? 2 : 3;
goto repeat;
}
char *linestart = buff_p;
buff_p += mysnprintf (buff_p, buff_end - buff_p, "\\cf1 %08lx:\\cf2 ", info->Pointer);
info->Pointer += read;
for (i = 0; i < read;)
{
if (i <= read - 4)
{
DWORD d;
memcpy(&d, &buf16[i], sizeof(d));
buff_p += mysnprintf (buff_p, buff_end - buff_p, " %08lx", d);
i += 4;
}
else
{
buff_p += mysnprintf (buff_p, buff_end - buff_p, " %02x", buf16[i]);
i += 1;
}
}
while (buff_p - linestart < 57)
{
*buff_p++ = ' ';
}
buff_p += mysnprintf (buff_p, buff_end - buff_p, "\\cf3 ");
for (i = 0; i < read; ++i)
{
uint8_t code = buf16[i];
if (code < 0x20 || code > 0x7f) code = 0xB7;
if (code == '\\' || code == '{' || code == '}') *buff_p++ = '\\';
*buff_p++ = code;
}
buff_p += mysnprintf (buff_p, buff_end - buff_p, "\\par\r\n");
}
break;
case 2: // Write epilogue
buff_p += mysnprintf (buff_p, buff_end - buff_p, "\\cf0 }");
info->Stage = 4;
break;
case 3: // Write epilogue for truncated file
buff_p += mysnprintf (buff_p, buff_end - buff_p, "--- Rest of file truncated ---\\cf0 }");
info->Stage = 4;
break;
case 4: // We're done
return TRUE;
}
*pcb = (LONG)((LPBYTE)buff_p - buffer);
return FALSE;
}
//==========================================================================
//
// SetEditControl
//
// Fills the edit control with the contents of the chosen file.
// If the filename ends with ".txt" the file is assumed to be text.
// Otherwise it is treated as binary, and you get a hex dump.
//
//==========================================================================
static void SetEditControl (HWND edit, HWND sizedisplay, int filenum)
{
char sizebuf[32];
EDITSTREAM stream;
DWORD size;
POINT pt = { 0, 0 };
const char *rtf = NULL;
HGDIOBJ font;
// Don't refresh the control if it's already showing the file we want.
if (GetWindowLongPtr (edit, GWLP_USERDATA) == filenum)
{
return;
}
size = GetFileSize (TarFiles[filenum].File, NULL);
if (size < 1024)
{
mysnprintf (sizebuf, countof(sizebuf), "(%lu bytes)", size);
}
else
{
mysnprintf (sizebuf, countof(sizebuf), "(%lu KB)", size/1024);
}
SetWindowTextA (sizedisplay, sizebuf);
SetWindowLongPtr (edit, GWLP_USERDATA, filenum);
SetFilePointer (TarFiles[filenum].File, 0, NULL, FILE_BEGIN);
SendMessage (edit, EM_SETSCROLLPOS, 0, (LPARAM)&pt);
// Set the font now, in case log.rtf was previously viewed, because
// that file changes it.
font = GetStockObject (ANSI_FIXED_FONT);
if (font != INVALID_HANDLE_VALUE)
{
SendMessage (edit, WM_SETFONT, (WPARAM)font, FALSE);
}
// Text files are streamed in as-is.
// Binary files are streamed in as color-coded hex dumps.
stream.dwError = 0;
if (strstr (TarFiles[filenum].Filename, ".txt") != NULL ||
(rtf = strstr (TarFiles[filenum].Filename, ".rtf")) != NULL)
{
CHARFORMAT beBlack;
beBlack.cbSize = sizeof(beBlack);
beBlack.dwMask = CFM_COLOR;
beBlack.dwEffects = 0;
beBlack.crTextColor = RGB(0,0,0);
SendMessage (edit, EM_SETCHARFORMAT, 0, (LPARAM)&beBlack);
stream.dwCookie = (DWORD_PTR)TarFiles[filenum].File;
stream.pfnCallback = StreamEditText;
SendMessage (edit, EM_STREAMIN, rtf ? SF_RTF : SF_TEXT | SF_USECODEPAGE | (1252 << 16), (LPARAM)&stream);
}
else
{
BinStreamInfo info = { 0, TarFiles[filenum].File, 0 };
stream.dwCookie = (DWORD_PTR)&info;
stream.pfnCallback = StreamEditBinary;
SendMessage (edit, EM_EXLIMITTEXT, 0, GetFileSize(TarFiles[filenum].File, NULL)*7);
SendMessage (edit, EM_STREAMIN, SF_RTF, (LPARAM)&stream);
}
SendMessage (edit, EM_SETSEL, (WPARAM)-1, 0);
}
//==========================================================================
//
// SaveReport
//
// Makes a permanent copy of the report tarball.
//
//==========================================================================
static void SaveReport (HANDLE file)
{
OPENFILENAME ofn = {
#ifdef OPENFILENAME_SIZE_VERSION_400
OPENFILENAME_SIZE_VERSION_400
#else
sizeof(ofn)
#endif
, };
WCHAR filename[256];
ofn.lpstrFilter = L"Zip file (*.zip)\0*.zip\0";
wcscpy (filename, L"CrashReport.zip");
ofn.lpstrFile = filename;
ofn.nMaxFile = countof(filename);
while (GetSaveFileNameW (&ofn))
{
HANDLE ofile = CreateFileW (ofn.lpstrFile, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if (ofile == INVALID_HANDLE_VALUE)
{
if (MessageBoxA (NULL, "Could not open the crash report file",
"Save As failed", MB_RETRYCANCEL) == IDRETRY)
{
continue;
}
return;
}
else
{
DWORD fileLen = GetFileSize (file, NULL), fileLeft;
char xferbuf[1024];
SetFilePointer (file, 0, NULL, FILE_BEGIN);
fileLeft = fileLen;
while (fileLeft != 0)
{
DWORD grab = fileLeft > sizeof(xferbuf) ? sizeof(xferbuf) : fileLeft;
DWORD didread;
ReadFile (file, xferbuf, grab, &didread, NULL);
WriteFile (ofile, xferbuf, didread, &grab, NULL);
fileLeft -= didread;
}
CloseHandle (ofile);
return;
}
}
}
//==========================================================================
//
// DisplayCrashLog
//
// Displays the crash information and possibly submits it. CreateCrashLog()
// must have been called previously.
//
//==========================================================================
void DisplayCrashLog ()
{
HANDLE file;
if (NumFiles == 0)
{
char ohPoo[] =
GAMENAME" crashed but was unable to produce\n"
"detailed information about the crash.\n"
"\nThis is all that is available:\n\nCode=XXXXXXXX\nAddr=XXXXXXXX";
mysnprintf (ohPoo + countof(ohPoo) - 23, 23, "%08lX\nAddr=%p", CrashCode, CrashAddress);
MessageBoxA (NULL, ohPoo, GAMENAME" Very Fatal Error", MB_OK|MB_ICONSTOP);
if (WinHlp32 != NULL)
{
FreeLibrary (WinHlp32);
}
}
else
{
HMODULE uxtheme = LoadLibraryA ("uxtheme.dll");
if (uxtheme != NULL)
{
pEnableThemeDialogTexture = (HRESULT (__stdcall *)(HWND,DWORD))GetProcAddress (uxtheme, "EnableThemeDialogTexture");
}
INT_PTR result = DialogBox (g_hInst, MAKEINTRESOURCE(IDD_CRASHDIALOG), NULL, (DLGPROC)CrashDlgProc);
if (result == IDC_SAVEREPORT)
{
file = MakeZip ();
SaveReport (file);
CloseHandle (file);
}
if (uxtheme != NULL)
{
FreeLibrary (uxtheme);
}
}
CloseTarFiles ();
}