Update: Translations from eints
[openttd-github.git] / src / os / windows / crashlog_win.cpp
blob111e6a53b76fca0102b1352fb38a854b5d69aa34
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD 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, version 2.
4 * OpenTTD 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.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file crashlog_win.cpp Implementation of a crashlogger for Windows */
10 #include "../../stdafx.h"
11 #include "../../crashlog.h"
12 #include "win32.h"
13 #include "../../core/math_func.hpp"
14 #include "../../string_func.h"
15 #include "../../fileio_func.h"
16 #include "../../strings_func.h"
17 #include "../../gamelog.h"
18 #include "../../saveload/saveload.h"
19 #include "../../video/video_driver.hpp"
20 #include "../../library_loader.h"
22 #include <windows.h>
23 #include <mmsystem.h>
24 #include <signal.h>
25 #include <psapi.h>
27 #if defined(_MSC_VER)
28 # include <dbghelp.h>
29 #else
30 # include <setjmp.h>
31 #endif
33 #ifdef WITH_UNOFFICIAL_BREAKPAD
34 # include <client/windows/handler/exception_handler.h>
35 #endif
37 #include "../../safeguards.h"
39 /** Exception code used for custom abort. */
40 static constexpr DWORD CUSTOM_ABORT_EXCEPTION = 0xE1212012;
42 /** A map between exception code and its name. */
43 static const std::map<DWORD, std::string> exception_code_to_name{
44 {EXCEPTION_ACCESS_VIOLATION, "EXCEPTION_ACCESS_VIOLATION"},
45 {EXCEPTION_ARRAY_BOUNDS_EXCEEDED, "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"},
46 {EXCEPTION_BREAKPOINT, "EXCEPTION_BREAKPOINT"},
47 {EXCEPTION_DATATYPE_MISALIGNMENT, "EXCEPTION_DATATYPE_MISALIGNMENT"},
48 {EXCEPTION_FLT_DENORMAL_OPERAND, "EXCEPTION_FLT_DENORMAL_OPERAND"},
49 {EXCEPTION_FLT_DIVIDE_BY_ZERO, "EXCEPTION_FLT_DIVIDE_BY_ZERO"},
50 {EXCEPTION_FLT_INEXACT_RESULT, "EXCEPTION_FLT_INEXACT_RESULT"},
51 {EXCEPTION_FLT_INVALID_OPERATION, "EXCEPTION_FLT_INVALID_OPERATION"},
52 {EXCEPTION_FLT_OVERFLOW, "EXCEPTION_FLT_OVERFLOW"},
53 {EXCEPTION_FLT_STACK_CHECK, "EXCEPTION_FLT_STACK_CHECK"},
54 {EXCEPTION_FLT_UNDERFLOW, "EXCEPTION_FLT_UNDERFLOW"},
55 {EXCEPTION_GUARD_PAGE, "EXCEPTION_GUARD_PAGE"},
56 {EXCEPTION_ILLEGAL_INSTRUCTION, "EXCEPTION_ILLEGAL_INSTRUCTION"},
57 {EXCEPTION_IN_PAGE_ERROR, "EXCEPTION_IN_PAGE_ERROR"},
58 {EXCEPTION_INT_DIVIDE_BY_ZERO, "EXCEPTION_INT_DIVIDE_BY_ZERO"},
59 {EXCEPTION_INT_OVERFLOW, "EXCEPTION_INT_OVERFLOW"},
60 {EXCEPTION_INVALID_DISPOSITION, "EXCEPTION_INVALID_DISPOSITION"},
61 {EXCEPTION_INVALID_HANDLE, "EXCEPTION_INVALID_HANDLE"},
62 {EXCEPTION_NONCONTINUABLE_EXCEPTION, "EXCEPTION_NONCONTINUABLE_EXCEPTION"},
63 {EXCEPTION_PRIV_INSTRUCTION, "EXCEPTION_PRIV_INSTRUCTION"},
64 {EXCEPTION_SINGLE_STEP, "EXCEPTION_SINGLE_STEP"},
65 {EXCEPTION_STACK_OVERFLOW, "EXCEPTION_STACK_OVERFLOW"},
66 {STATUS_UNWIND_CONSOLIDATE, "STATUS_UNWIND_CONSOLIDATE"},
69 /**
70 * Forcefully try to terminate the application.
72 * @param exit_code The exit code to return.
74 [[noreturn]] static void ImmediateExitProcess(uint exit_code)
76 /* TerminateProcess may fail in some special edge cases; fall back to ExitProcess in this case. */
77 TerminateProcess(GetCurrentProcess(), exit_code);
78 ExitProcess(exit_code);
81 /**
82 * Windows implementation for the crash logger.
84 class CrashLogWindows : public CrashLog {
85 /** Information about the encountered exception */
86 EXCEPTION_POINTERS *ep;
88 void SurveyCrash(nlohmann::json &survey) const override
90 survey["id"] = ep->ExceptionRecord->ExceptionCode;
91 if (exception_code_to_name.count(ep->ExceptionRecord->ExceptionCode) > 0) {
92 survey["reason"] = exception_code_to_name.at(ep->ExceptionRecord->ExceptionCode);
93 } else {
94 survey["reason"] = "Unknown exception code";
98 void SurveyStacktrace(nlohmann::json &survey) const override;
99 public:
101 #ifdef WITH_UNOFFICIAL_BREAKPAD
102 static bool MinidumpCallback(const wchar_t *dump_dir, const wchar_t *minidump_id, void *context, EXCEPTION_POINTERS *, MDRawAssertionInfo *, bool succeeded)
104 CrashLogWindows *crashlog = reinterpret_cast<CrashLogWindows *>(context);
106 crashlog->crashdump_filename = crashlog->CreateFileName(".dmp");
107 std::rename(fmt::format("{}/{}.dmp", FS2OTTD(dump_dir), FS2OTTD(minidump_id)).c_str(), crashlog->crashdump_filename.c_str());
108 return succeeded;
111 bool WriteCrashDump() override
113 return google_breakpad::ExceptionHandler::WriteMinidump(OTTD2FS(_personal_dir), MinidumpCallback, this);
115 #endif
117 #if defined(_MSC_VER)
118 /* virtual */ bool TryExecute(std::string_view section_name, std::function<bool()> &&func) override
120 this->try_execute_active = true;
121 bool res;
123 __try {
124 res = func();
125 } __except (EXCEPTION_EXECUTE_HANDLER) {
126 fmt::print("Something went wrong when attempting to fill {} section of the crash log.\n", section_name);
127 res = false;
130 this->try_execute_active = false;
131 return res;
133 #else
134 /* virtual */ bool TryExecute(std::string_view section_name, std::function<bool()> &&func) override
136 this->try_execute_active = true;
138 /* Setup a longjump in case a crash happens. */
139 if (setjmp(this->internal_fault_jmp_buf) != 0) {
140 fmt::print("Something went wrong when attempting to fill {} section of the crash log.\n", section_name);
142 this->try_execute_active = false;
143 return false;
146 bool res = func();
147 this->try_execute_active = false;
148 return res;
150 #endif /* _MSC_VER */
153 * A crash log is always generated when it's generated.
154 * @param ep the data related to the exception.
156 CrashLogWindows(EXCEPTION_POINTERS *ep = nullptr) :
157 ep(ep)
161 #if !defined(_MSC_VER)
162 /** Buffer to track the long jump set setup. */
163 jmp_buf internal_fault_jmp_buf;
164 #endif
166 /** Whether we are in a TryExecute block. */
167 bool try_execute_active = false;
169 /** Points to the current crash log. */
170 static CrashLogWindows *current;
173 /* static */ CrashLogWindows *CrashLogWindows::current = nullptr;
175 #if defined(_MSC_VER)
176 static const uint MAX_SYMBOL_LEN = 512;
177 static const uint MAX_FRAMES = 64;
179 /* virtual */ void CrashLogWindows::SurveyStacktrace(nlohmann::json &survey) const
181 LibraryLoader dbghelp("dbghelp.dll");
182 struct ProcPtrs {
183 BOOL (WINAPI * pSymInitialize)(HANDLE, PCSTR, BOOL);
184 BOOL (WINAPI * pSymSetOptions)(DWORD);
185 BOOL (WINAPI * pSymCleanup)(HANDLE);
186 BOOL (WINAPI * pStackWalk64)(DWORD, HANDLE, HANDLE, LPSTACKFRAME64, PVOID, PREAD_PROCESS_MEMORY_ROUTINE64, PFUNCTION_TABLE_ACCESS_ROUTINE64, PGET_MODULE_BASE_ROUTINE64, PTRANSLATE_ADDRESS_ROUTINE64);
187 PVOID (WINAPI * pSymFunctionTableAccess64)(HANDLE, DWORD64);
188 DWORD64 (WINAPI * pSymGetModuleBase64)(HANDLE, DWORD64);
189 BOOL (WINAPI * pSymGetModuleInfo64)(HANDLE, DWORD64, PIMAGEHLP_MODULE64);
190 BOOL (WINAPI * pSymGetSymFromAddr64)(HANDLE, DWORD64, PDWORD64, PIMAGEHLP_SYMBOL64);
191 BOOL (WINAPI * pSymGetLineFromAddr64)(HANDLE, DWORD64, PDWORD, PIMAGEHLP_LINE64);
192 } proc = {
193 dbghelp.GetFunction("SymInitialize"),
194 dbghelp.GetFunction("SymSetOptions"),
195 dbghelp.GetFunction("SymCleanup"),
196 dbghelp.GetFunction("StackWalk64"),
197 dbghelp.GetFunction("SymFunctionTableAccess64"),
198 dbghelp.GetFunction("SymGetModuleBase64"),
199 dbghelp.GetFunction("SymGetModuleInfo64"),
200 dbghelp.GetFunction("SymGetSymFromAddr64"),
201 dbghelp.GetFunction("SymGetLineFromAddr64"),
204 survey = nlohmann::json::array();
206 /* Try to load the functions from the DLL, if that fails because of a too old dbghelp.dll, just skip it. */
207 if (!dbghelp.HasError()) {
208 /* Initialize symbol handler. */
209 HANDLE hCur = GetCurrentProcess();
210 proc.pSymInitialize(hCur, nullptr, TRUE);
211 /* Load symbols only when needed, fail silently on errors, demangle symbol names. */
212 proc.pSymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_UNDNAME);
214 /* Initialize starting stack frame from context record. */
215 STACKFRAME64 frame;
216 memset(&frame, 0, sizeof(frame));
217 #ifdef _M_AMD64
218 frame.AddrPC.Offset = ep->ContextRecord->Rip;
219 frame.AddrFrame.Offset = ep->ContextRecord->Rbp;
220 frame.AddrStack.Offset = ep->ContextRecord->Rsp;
221 #elif defined(_M_IX86)
222 frame.AddrPC.Offset = ep->ContextRecord->Eip;
223 frame.AddrFrame.Offset = ep->ContextRecord->Ebp;
224 frame.AddrStack.Offset = ep->ContextRecord->Esp;
225 #elif defined(_M_ARM64)
226 frame.AddrPC.Offset = ep->ContextRecord->Pc;
227 frame.AddrFrame.Offset = ep->ContextRecord->Fp;
228 frame.AddrStack.Offset = ep->ContextRecord->Sp;
229 #endif
230 frame.AddrPC.Mode = AddrModeFlat;
231 frame.AddrFrame.Mode = AddrModeFlat;
232 frame.AddrStack.Mode = AddrModeFlat;
234 /* Copy context record as StackWalk64 may modify it. */
235 CONTEXT ctx;
236 memcpy(&ctx, ep->ContextRecord, sizeof(ctx));
238 /* Allocate space for symbol info. */
239 char sym_info_raw[sizeof(IMAGEHLP_SYMBOL64) + MAX_SYMBOL_LEN - 1];
240 IMAGEHLP_SYMBOL64 *sym_info = (IMAGEHLP_SYMBOL64*)sym_info_raw;
241 sym_info->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
242 sym_info->MaxNameLength = MAX_SYMBOL_LEN;
244 /* Walk stack at most MAX_FRAMES deep in case the stack is corrupt. */
245 for (uint num = 0; num < MAX_FRAMES; num++) {
246 if (!proc.pStackWalk64(
247 #ifdef _M_AMD64
248 IMAGE_FILE_MACHINE_AMD64,
249 #else
250 IMAGE_FILE_MACHINE_I386,
251 #endif
252 hCur, GetCurrentThread(), &frame, &ctx, nullptr, proc.pSymFunctionTableAccess64, proc.pSymGetModuleBase64, nullptr)) break;
254 if (frame.AddrPC.Offset == frame.AddrReturn.Offset) {
255 survey.push_back("<infinite loop>");
256 break;
259 /* Get module name. */
260 const char *mod_name = "???";
262 IMAGEHLP_MODULE64 module;
263 module.SizeOfStruct = sizeof(module);
264 if (proc.pSymGetModuleInfo64(hCur, frame.AddrPC.Offset, &module)) {
265 mod_name = module.ModuleName;
268 /* Print module and instruction pointer. */
269 std::string message = fmt::format("{:20s} {:X}", mod_name, frame.AddrPC.Offset);
271 /* Get symbol name and line info if possible. */
272 DWORD64 offset;
273 if (proc.pSymGetSymFromAddr64(hCur, frame.AddrPC.Offset, &offset, sym_info)) {
274 message += fmt::format(" {} + {}", sym_info->Name, offset);
276 DWORD line_offs;
277 IMAGEHLP_LINE64 line;
278 line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
279 if (proc.pSymGetLineFromAddr64(hCur, frame.AddrPC.Offset, &line_offs, &line)) {
280 message += fmt::format(" ({}:{})", line.FileName, line.LineNumber);
284 survey.push_back(message);
287 proc.pSymCleanup(hCur);
290 #else
291 /* virtual */ void CrashLogWindows::SurveyStacktrace(nlohmann::json &) const
293 /* Not supported. */
295 #endif /* _MSC_VER */
297 extern bool CloseConsoleLogIfActive();
298 static void ShowCrashlogWindow();
301 * Stack pointer for use when 'starting' the crash handler.
302 * Not static as gcc's inline assembly needs it that way.
304 thread_local void *_safe_esp = nullptr;
306 static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
308 /* Restore system timer resolution. */
309 timeEndPeriod(1);
311 /* Disable our event loop. */
312 SetWindowLongPtr(GetActiveWindow(), GWLP_WNDPROC, (LONG_PTR)&DefWindowProc);
314 if (CrashLogWindows::current != nullptr) {
315 CrashLog::AfterCrashLogCleanup();
316 ImmediateExitProcess(2);
319 if (_gamelog.TestEmergency()) {
320 static const wchar_t _emergency_crash[] =
321 L"A serious fault condition occurred in the game. The game will shut down.\n"
322 L"As you loaded an emergency savegame no crash information will be generated.\n";
323 MessageBox(nullptr, _emergency_crash, L"Fatal Application Failure", MB_ICONERROR);
324 ImmediateExitProcess(3);
327 if (SaveloadCrashWithMissingNewGRFs()) {
328 static const wchar_t _saveload_crash[] =
329 L"A serious fault condition occurred in the game. The game will shut down.\n"
330 L"As you loaded an savegame for which you do not have the required NewGRFs\n"
331 L"no crash information will be generated.\n";
332 MessageBox(nullptr, _saveload_crash, L"Fatal Application Failure", MB_ICONERROR);
333 ImmediateExitProcess(3);
336 CrashLogWindows *log = new CrashLogWindows(ep);
337 CrashLogWindows::current = log;
338 log->MakeCrashLog();
340 /* Close any possible log files */
341 CloseConsoleLogIfActive();
343 if ((VideoDriver::GetInstance() == nullptr || VideoDriver::GetInstance()->HasGUI()) && _safe_esp != nullptr) {
344 #ifdef _M_AMD64
345 ep->ContextRecord->Rip = (DWORD64)ShowCrashlogWindow;
346 ep->ContextRecord->Rsp = (DWORD64)_safe_esp;
347 #elif defined(_M_IX86)
348 ep->ContextRecord->Eip = (DWORD)ShowCrashlogWindow;
349 ep->ContextRecord->Esp = (DWORD)_safe_esp;
350 #elif defined(_M_ARM64)
351 ep->ContextRecord->Pc = (DWORD64)ShowCrashlogWindow;
352 ep->ContextRecord->Sp = (DWORD64)_safe_esp;
353 #endif
354 return EXCEPTION_CONTINUE_EXECUTION;
357 CrashLog::AfterCrashLogCleanup();
358 return EXCEPTION_EXECUTE_HANDLER;
361 static LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS *ep)
363 if (CrashLogWindows::current != nullptr && CrashLogWindows::current->try_execute_active) {
364 #if defined(_MSC_VER)
365 return EXCEPTION_CONTINUE_SEARCH;
366 #else
367 longjmp(CrashLogWindows::current->internal_fault_jmp_buf, 1);
368 #endif
371 if (ep->ExceptionRecord->ExceptionCode == 0xC0000374 /* heap corruption */) {
372 return ExceptionHandler(ep);
374 if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) {
375 return ExceptionHandler(ep);
377 if (ep->ExceptionRecord->ExceptionCode == CUSTOM_ABORT_EXCEPTION) {
378 return ExceptionHandler(ep);
381 return EXCEPTION_CONTINUE_SEARCH;
384 static void CDECL CustomAbort(int)
386 RaiseException(CUSTOM_ABORT_EXCEPTION, 0, 0, nullptr);
389 /* static */ void CrashLog::InitialiseCrashLog()
391 CrashLog::InitThread();
393 /* SIGABRT is not an unhandled exception, so we need to intercept it. */
394 signal(SIGABRT, CustomAbort);
395 #if defined(_MSC_VER)
396 /* Don't show abort message as we will get the crashlog window anyway. */
397 _set_abort_behavior(0, _WRITE_ABORT_MSG);
398 #endif
399 SetUnhandledExceptionFilter(ExceptionHandler);
400 AddVectoredExceptionHandler(1, VectoredExceptionHandler);
403 /* static */ void CrashLog::InitThread()
405 #if defined(_M_AMD64) || defined(_M_ARM64)
406 CONTEXT ctx;
407 RtlCaptureContext(&ctx);
409 /* The stack pointer for AMD64 must always be 16-byte aligned inside a
410 * function. As we are simulating a function call with the safe ESP value,
411 * we need to subtract 8 for the imaginary return address otherwise stack
412 * alignment would be wrong in the called function. */
413 # if defined(_M_ARM64)
414 _safe_esp = (void *)(ctx.Sp - 8);
415 # else
416 _safe_esp = (void *)(ctx.Rsp - 8);
417 # endif
418 #else
419 void *safe_esp;
420 # if defined(_MSC_VER)
421 _asm {
422 mov safe_esp, esp
424 # else
425 asm("movl %%esp, %0" : "=rm" (safe_esp));
426 # endif
427 _safe_esp = safe_esp;
428 #endif
431 /* The crash log GUI */
433 static bool _expanded;
435 static const wchar_t _crash_desc[] =
436 L"A serious fault condition occurred in the game. The game will shut down.\n"
437 L"Please send crash.json.log, crash.dmp, and crash.sav to the developers.\n"
438 L"This will greatly help debugging.\n\n"
439 L"https://github.com/OpenTTD/OpenTTD/issues\n\n"
440 L"%s\n%s\n%s\n%s\n";
442 static const wchar_t * const _expand_texts[] = {L"S&how report >>", L"&Hide report <<" };
444 static void SetWndSize(HWND wnd, int mode)
446 RECT r, r2;
448 GetWindowRect(wnd, &r);
449 SetDlgItemText(wnd, 15, _expand_texts[mode == 1]);
451 if (mode >= 0) {
452 GetWindowRect(GetDlgItem(wnd, 11), &r2);
453 int offs = r2.bottom - r2.top + 10;
454 if (mode == 0) offs = -offs;
455 SetWindowPos(wnd, HWND_TOPMOST, 0, 0,
456 r.right - r.left, r.bottom - r.top + offs, SWP_NOMOVE | SWP_NOZORDER);
457 } else {
458 SetWindowPos(wnd, HWND_TOPMOST,
459 (GetSystemMetrics(SM_CXSCREEN) - (r.right - r.left)) / 2,
460 (GetSystemMetrics(SM_CYSCREEN) - (r.bottom - r.top)) / 2,
461 0, 0, SWP_NOSIZE);
465 static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM)
467 switch (msg) {
468 case WM_INITDIALOG: {
469 std::string crashlog = CrashLogWindows::current->survey.dump(4);
470 size_t crashlog_length = crashlog.size() + 1;
471 /* Reserve extra space for LF to CRLF conversion. */
472 crashlog_length += std::count(crashlog.begin(), crashlog.end(), '\n');
474 const size_t filename_count = 4;
475 const size_t filename_buf_length = MAX_PATH + 1;
476 const size_t crash_desc_buf_length = lengthof(_crash_desc) + filename_buf_length * filename_count + 1;
478 /* We need to put the crash-log in a separate buffer because the default
479 * buffer in MB_TO_WIDE is not large enough (512 chars).
480 * Use VirtualAlloc to allocate pages for the buffer to avoid overflowing the stack.
481 * Avoid the heap in case the crash is because the heap became corrupted. */
482 const size_t total_length = crash_desc_buf_length * sizeof(wchar_t) +
483 crashlog_length * sizeof(wchar_t) +
484 filename_buf_length * sizeof(wchar_t) * filename_count +
485 crashlog_length;
486 void *raw_buffer = VirtualAlloc(nullptr, total_length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
488 wchar_t *crash_desc_buf = reinterpret_cast<wchar_t *>(raw_buffer);
489 wchar_t *crashlog_buf = crash_desc_buf + crash_desc_buf_length;
490 wchar_t *filename_buf = crashlog_buf + crashlog_length;
491 char *crashlog_dos_nl = reinterpret_cast<char *>(filename_buf + filename_buf_length * filename_count);
493 /* Convert unix -> dos newlines because the edit box only supports that properly. */
494 const char *crashlog_unix_nl = crashlog.data();
495 char *p = crashlog_dos_nl;
496 char32_t c;
497 while ((c = Utf8Consume(&crashlog_unix_nl))) {
498 if (c == '\n') p += Utf8Encode(p, '\r');
499 p += Utf8Encode(p, c);
501 *p = '\0';
503 _snwprintf(
504 crash_desc_buf,
505 crash_desc_buf_length,
506 _crash_desc,
507 convert_to_fs(CrashLogWindows::current->crashlog_filename, {filename_buf + filename_buf_length * 0, filename_buf_length}),
508 convert_to_fs(CrashLogWindows::current->crashdump_filename, {filename_buf + filename_buf_length * 1, filename_buf_length}),
509 convert_to_fs(CrashLogWindows::current->savegame_filename, {filename_buf + filename_buf_length * 2, filename_buf_length}),
510 convert_to_fs(CrashLogWindows::current->screenshot_filename, {filename_buf + filename_buf_length * 3, filename_buf_length})
513 SetDlgItemText(wnd, 10, crash_desc_buf);
514 SetDlgItemText(wnd, 11, convert_to_fs(crashlog_dos_nl, {crashlog_buf, crashlog_length}));
515 SendDlgItemMessage(wnd, 11, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), FALSE);
516 SetWndSize(wnd, -1);
517 } return TRUE;
518 case WM_COMMAND:
519 switch (wParam) {
520 case 12: // Close
521 CrashLog::AfterCrashLogCleanup();
522 ImmediateExitProcess(2);
523 case 15: // Expand window to show crash-message
524 _expanded = !_expanded;
525 SetWndSize(wnd, _expanded);
526 break;
528 return TRUE;
529 case WM_CLOSE:
530 CrashLog::AfterCrashLogCleanup();
531 ImmediateExitProcess(2);
534 return FALSE;
537 static void ShowCrashlogWindow()
539 ShowCursor(TRUE);
540 ShowWindow(GetActiveWindow(), FALSE);
541 DialogBox(GetModuleHandle(nullptr), MAKEINTRESOURCE(100), nullptr, CrashDialogFunc);