Files
test2/source/blender/blenlib/intern/system_win32.cc
Hans Goudey a68d39e9d9 Cleanup: Formatting
Run `make format` after the library update in the previous commit.
2025-10-02 12:55:42 -04:00

697 lines
23 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#include <Windows.h>
#include <commctrl.h>
#include <sstream>
#include <dbghelp.h>
#include <shlwapi.h>
#include <tlhelp32.h>
#include "MEM_guardedalloc.h"
#include "uri_convert.hh"
#include "utfconv.hh"
#include "BLI_string.h"
#include "BLI_system.h" /* Own include. */
static const char *bli_windows_get_exception_description(const DWORD exceptioncode)
{
switch (exceptioncode) {
case EXCEPTION_ACCESS_VIOLATION:
return "EXCEPTION_ACCESS_VIOLATION";
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
case EXCEPTION_BREAKPOINT:
return "EXCEPTION_BREAKPOINT";
case EXCEPTION_DATATYPE_MISALIGNMENT:
return "EXCEPTION_DATATYPE_MISALIGNMENT";
case EXCEPTION_FLT_DENORMAL_OPERAND:
return "EXCEPTION_FLT_DENORMAL_OPERAND";
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
case EXCEPTION_FLT_INEXACT_RESULT:
return "EXCEPTION_FLT_INEXACT_RESULT";
case EXCEPTION_FLT_INVALID_OPERATION:
return "EXCEPTION_FLT_INVALID_OPERATION";
case EXCEPTION_FLT_OVERFLOW:
return "EXCEPTION_FLT_OVERFLOW";
case EXCEPTION_FLT_STACK_CHECK:
return "EXCEPTION_FLT_STACK_CHECK";
case EXCEPTION_FLT_UNDERFLOW:
return "EXCEPTION_FLT_UNDERFLOW";
case EXCEPTION_ILLEGAL_INSTRUCTION:
return "EXCEPTION_ILLEGAL_INSTRUCTION";
case EXCEPTION_IN_PAGE_ERROR:
return "EXCEPTION_IN_PAGE_ERROR";
case EXCEPTION_INT_DIVIDE_BY_ZERO:
return "EXCEPTION_INT_DIVIDE_BY_ZERO";
case EXCEPTION_INT_OVERFLOW:
return "EXCEPTION_INT_OVERFLOW";
case EXCEPTION_INVALID_DISPOSITION:
return "EXCEPTION_INVALID_DISPOSITION";
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
case EXCEPTION_PRIV_INSTRUCTION:
return "EXCEPTION_PRIV_INSTRUCTION";
case EXCEPTION_SINGLE_STEP:
return "EXCEPTION_SINGLE_STEP";
case EXCEPTION_STACK_OVERFLOW:
return "EXCEPTION_STACK_OVERFLOW";
/* This one does not have a known define, but the MSVC runtime raises this for uncaught C++
* exceptions. See https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273 for
* details. */
case 0xe06d7363:
return "Microsoft C++ Exception";
default:
return "UNKNOWN EXCEPTION";
}
}
static void bli_windows_get_module_name(LPVOID address, PCHAR buffer, size_t size)
{
HMODULE mod;
buffer[0] = 0;
if (GetModuleHandleEx(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast<LPCSTR>(address), &mod))
{
if (GetModuleFileName(mod, buffer, size)) {
PathStripPath(buffer);
}
}
}
static void bli_windows_get_module_version(const char *file, char *buffer, size_t buffersize)
{
buffer[0] = 0;
DWORD verHandle = 0;
UINT size = 0;
LPBYTE lpBuffer = nullptr;
DWORD verSize = GetFileVersionInfoSize(file, &verHandle);
if (verSize != 0) {
LPSTR verData = (LPSTR)MEM_callocN(verSize, "crash module version");
if (GetFileVersionInfo(file, verHandle, verSize, verData)) {
if (VerQueryValue(verData, "\\", (VOID FAR * FAR *)&lpBuffer, &size)) {
if (size) {
VS_FIXEDFILEINFO *verInfo = (VS_FIXEDFILEINFO *)lpBuffer;
/* Magic value from
* https://docs.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
*/
if (verInfo->dwSignature == 0xfeef04bd) {
BLI_snprintf(buffer,
buffersize,
"%d.%d.%d.%d",
(verInfo->dwFileVersionMS >> 16) & 0xffff,
(verInfo->dwFileVersionMS >> 0) & 0xffff,
(verInfo->dwFileVersionLS >> 16) & 0xffff,
(verInfo->dwFileVersionLS >> 0) & 0xffff);
}
}
}
}
MEM_freeN(verData);
}
}
static void bli_windows_system_backtrace_exception_record(FILE *fp, PEXCEPTION_RECORD record)
{
char module[MAX_PATH];
fprintf(fp, "Exception Record:\n\n");
fprintf(fp,
"ExceptionCode : %s (0x%.8x)\n",
bli_windows_get_exception_description(record->ExceptionCode),
record->ExceptionCode);
fprintf(fp, "Exception Address : 0x%p\n", record->ExceptionAddress);
bli_windows_get_module_name(record->ExceptionAddress, module, sizeof(module));
fprintf(fp, "Exception Module : %s\n", module);
fprintf(fp, "Exception Flags : 0x%.8x\n", record->ExceptionFlags);
fprintf(fp, "Exception Parameters : 0x%x\n", record->NumberParameters);
/* Special handling for access violations to make them a little easier to read. */
if (record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && record->NumberParameters == 2) {
const char *action;
switch (record->ExceptionInformation[0]) {
case 0:
action = "read";
break;
case 1:
action = "write";
break;
case 8:
action = "execute";
break;
default:
action = "unknown";
break;
}
fprintf(fp,
"\tParameters[0] (action) : 0x%p (%s)\n",
(LPVOID *)record->ExceptionInformation[0],
action);
fprintf(fp, "\tParameters[1] (address) : 0x%p\n", (LPVOID *)record->ExceptionInformation[1]);
}
else {
for (DWORD idx = 0; idx < record->NumberParameters; idx++) {
fprintf(fp, "\tParameters[%d] : 0x%p\n", idx, (LPVOID *)record->ExceptionInformation[idx]);
}
}
if (record->ExceptionRecord) {
fprintf(fp, "Nested ");
bli_windows_system_backtrace_exception_record(fp, record->ExceptionRecord);
}
fprintf(fp, "\n\n");
}
static bool BLI_windows_system_backtrace_run_trace(FILE *fp, HANDLE hThread, PCONTEXT context)
{
const int max_symbol_length = 100;
bool result = true;
PSYMBOL_INFO symbolinfo = static_cast<PSYMBOL_INFO>(
MEM_callocN(sizeof(SYMBOL_INFO) + max_symbol_length * sizeof(char), "crash Symbol table"));
symbolinfo->MaxNameLen = max_symbol_length - 1;
symbolinfo->SizeOfStruct = sizeof(SYMBOL_INFO);
STACKFRAME frame = {0};
DWORD machineType = 0;
#if defined(_M_AMD64)
frame.AddrPC.Offset = context->Rip;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context->Rsp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Offset = context->Rsp;
frame.AddrStack.Mode = AddrModeFlat;
machineType = IMAGE_FILE_MACHINE_AMD64;
#elif defined(_M_ARM64)
frame.AddrPC.Offset = context->Pc;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context->Fp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Offset = context->Sp;
frame.AddrStack.Mode = AddrModeFlat;
machineType = IMAGE_FILE_MACHINE_ARM64;
#endif
while (true) {
if (StackWalk64(machineType,
GetCurrentProcess(),
hThread,
&frame,
context,
nullptr,
SymFunctionTableAccess64,
SymGetModuleBase64,
0))
{
if (frame.AddrPC.Offset) {
char module[MAX_PATH];
bli_windows_get_module_name((LPVOID)frame.AddrPC.Offset, module, sizeof(module));
if (SymFromAddr(GetCurrentProcess(), (DWORD64)(frame.AddrPC.Offset), 0, symbolinfo)) {
fprintf(fp, "%-20s:0x%p %s", module, (LPVOID)symbolinfo->Address, symbolinfo->Name);
IMAGEHLP_LINE lineinfo;
lineinfo.SizeOfStruct = sizeof(lineinfo);
DWORD displacement = 0;
if (SymGetLineFromAddr(
GetCurrentProcess(), (DWORD64)(frame.AddrPC.Offset), &displacement, &lineinfo))
{
fprintf(fp, " %s:%d", lineinfo.FileName, lineinfo.LineNumber);
}
fprintf(fp, "\n");
}
else {
fprintf(fp,
"%-20s:0x%p %s\n",
module,
(LPVOID)frame.AddrPC.Offset,
"Symbols not available");
result = false;
break;
}
}
else {
break;
}
}
else {
break;
}
}
MEM_freeN(symbolinfo);
fprintf(fp, "\n\n");
return result;
}
static bool bli_windows_system_backtrace_stack_thread(FILE *fp, HANDLE hThread)
{
CONTEXT context = {0};
context.ContextFlags = CONTEXT_ALL;
/* GetThreadContext requires the thread to be in a suspended state, which is problematic for the
* currently running thread, RtlCaptureContext is used as an alternative to sidestep this */
if (hThread != GetCurrentThread()) {
SuspendThread(hThread);
bool success = GetThreadContext(hThread, &context);
ResumeThread(hThread);
if (!success) {
fprintf(fp, "Cannot get thread context : 0x0%.8x\n", GetLastError());
return false;
}
}
else {
RtlCaptureContext(&context);
}
return BLI_windows_system_backtrace_run_trace(fp, hThread, &context);
}
static void bli_windows_system_backtrace_modules(FILE *fp)
{
fprintf(fp, "Loaded Modules :\n");
HANDLE hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
if (hModuleSnap == INVALID_HANDLE_VALUE) {
return;
}
MODULEENTRY32 me32;
me32.dwSize = sizeof(MODULEENTRY32);
if (!Module32First(hModuleSnap, &me32)) {
CloseHandle(hModuleSnap); /* Must clean up the snapshot object! */
fprintf(fp, " Error getting module list.\n");
return;
}
do {
if (me32.th32ProcessID == GetCurrentProcessId()) {
char version[MAX_PATH];
bli_windows_get_module_version(me32.szExePath, version, sizeof(version));
IMAGEHLP_MODULE64 m64;
m64.SizeOfStruct = sizeof(m64);
if (SymGetModuleInfo64(GetCurrentProcess(), (DWORD64)me32.modBaseAddr, &m64)) {
fprintf(fp,
"0x%p %-20s %s %s %s\n",
me32.modBaseAddr,
version,
me32.szModule,
m64.LoadedPdbName,
m64.PdbUnmatched ? "[unmatched]" : "");
}
else {
fprintf(fp, "0x%p %-20s %s\n", me32.modBaseAddr, version, me32.szModule);
}
}
} while (Module32Next(hModuleSnap, &me32));
}
static void bli_windows_system_backtrace_threads(FILE *fp)
{
fprintf(fp, "Threads:\n");
HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
THREADENTRY32 te32;
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hThreadSnap == INVALID_HANDLE_VALUE) {
fprintf(fp, "Unable to retrieve threads list.\n");
return;
}
te32.dwSize = sizeof(THREADENTRY32);
if (!Thread32First(hThreadSnap, &te32)) {
CloseHandle(hThreadSnap);
return;
}
do {
if (te32.th32OwnerProcessID == GetCurrentProcessId()) {
if (GetCurrentThreadId() != te32.th32ThreadID) {
fprintf(fp, "Thread : %.8x\n", te32.th32ThreadID);
HANDLE ht = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
bli_windows_system_backtrace_stack_thread(fp, ht);
CloseHandle(ht);
}
}
} while (Thread32Next(hThreadSnap, &te32));
CloseHandle(hThreadSnap);
}
static bool bli_windows_system_backtrace_stack(FILE *fp, const EXCEPTION_POINTERS *exception_info)
{
fprintf(fp, "Stack trace:\n");
/* If we are handling an exception use the context record from that. */
if (exception_info && exception_info->ExceptionRecord->ExceptionAddress) {
/* The back trace code will write to the context record, to protect the original record from
* modifications give the backtrace a copy to work on. */
CONTEXT TempContext = *exception_info->ContextRecord;
return BLI_windows_system_backtrace_run_trace(fp, GetCurrentThread(), &TempContext);
}
else {
/* If there is no current exception or the address is not set, walk the current stack. */
return bli_windows_system_backtrace_stack_thread(fp, GetCurrentThread());
}
}
static bool bli_private_symbols_loaded()
{
IMAGEHLP_MODULE64 m64;
m64.SizeOfStruct = sizeof(m64);
if (SymGetModuleInfo64(GetCurrentProcess(), (DWORD64)GetModuleHandle(nullptr), &m64)) {
return m64.GlobalSymbols;
}
return false;
}
static void bli_load_symbols()
{
/* If this is a developer station and the private pdb is already loaded leave it be. */
if (bli_private_symbols_loaded()) {
return;
}
char pdb_file[MAX_PATH] = {0};
/* get the currently executing image */
if (GetModuleFileNameA(nullptr, pdb_file, sizeof(pdb_file))) {
/* remove the filename */
PathRemoveFileSpecA(pdb_file);
/* append blender.pdb */
PathAppendA(pdb_file, "blender.pdb");
if (PathFileExistsA(pdb_file)) {
HMODULE mod = GetModuleHandle(nullptr);
if (mod) {
WIN32_FILE_ATTRIBUTE_DATA file_data;
if (GetFileAttributesExA(pdb_file, GetFileExInfoStandard, &file_data)) {
/* SymInitialize will try to load symbols on its own, so we first must unload whatever it
* did trying to help */
SymUnloadModule64(GetCurrentProcess(), (DWORD64)mod);
DWORD64 module_base = SymLoadModule(GetCurrentProcess(),
nullptr,
pdb_file,
nullptr,
(DWORD64)mod,
(DWORD)file_data.nFileSizeLow);
if (module_base == 0) {
fprintf(stderr,
"Error loading symbols %s\n\terror:0x%.8x\n\tsize = %d\n\tbase=0x%p\n",
pdb_file,
GetLastError(),
file_data.nFileSizeLow,
(LPVOID)mod);
}
}
}
}
}
}
void BLI_system_backtrace_with_os_info(FILE *fp, const void *os_info)
{
const EXCEPTION_POINTERS *exception_info = static_cast<const EXCEPTION_POINTERS *>(os_info);
SymInitialize(GetCurrentProcess(), nullptr, TRUE);
bli_load_symbols();
if (exception_info) {
bli_windows_system_backtrace_exception_record(fp, exception_info->ExceptionRecord);
}
if (bli_windows_system_backtrace_stack(fp, exception_info)) {
/* When the blender symbols are missing the stack traces will be unreliable
* so only run if the previous step completed successfully. */
bli_windows_system_backtrace_threads(fp);
}
bli_windows_system_backtrace_modules(fp);
}
void BLI_windows_exception_print_message(const void *os_info)
{
if (!os_info) {
return;
}
const EXCEPTION_POINTERS *exception = static_cast<const EXCEPTION_POINTERS *>(os_info);
const char *exception_name = bli_windows_get_exception_description(
exception->ExceptionRecord->ExceptionCode);
LPVOID address = exception->ExceptionRecord->ExceptionAddress;
CHAR modulename[MAX_PATH];
bli_windows_get_module_name(address, modulename, sizeof(modulename));
DWORD threadId = GetCurrentThreadId();
char message[512];
BLI_snprintf(message,
512,
"Error : %s\n"
"Address : 0x%p\n"
"Module : %s\n"
"Thread : %.8x\n",
exception_name,
address,
modulename,
threadId);
fprintf(stderr, message);
fflush(stderr);
}
/* -------------------------------------------------------------------- */
/** \name bli_show_message_box
* \{ */
static std::string get_os_info()
{
OSVERSIONINFOEX osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
if (!GetVersionEx((OSVERSIONINFO *)&osvi)) {
return "Unknown System";
}
std::string version = std::to_string(osvi.dwMajorVersion) + "-" +
std::to_string(osvi.dwMajorVersion) + "." +
std::to_string(osvi.dwMinorVersion) + "." +
std::to_string(osvi.dwBuildNumber) + "-SP" +
std::to_string(osvi.wServicePackMajor);
SYSTEM_INFO si;
GetSystemInfo(&si);
std::string architecture;
switch (si.wProcessorArchitecture) {
case PROCESSOR_ARCHITECTURE_AMD64:
architecture = "64 Bits";
break;
case PROCESSOR_ARCHITECTURE_INTEL:
architecture = "32 Bits";
break;
case PROCESSOR_ARCHITECTURE_ARM:
architecture = "ARM Architecture";
break;
case PROCESSOR_ARCHITECTURE_ARM64:
architecture = "ARM64 Architecture";
break;
case PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64:
architecture = "ARM32 on Windows 64-bit";
break;
case PROCESSOR_ARCHITECTURE_IA32_ON_ARM64:
architecture = "IA32 on ARM64";
break;
default:
architecture = "Unknown Architecture";
}
return "Windows-" + version + " " + architecture;
}
/**
* Retrieve the path to "blender-launcher.exe" if it exists; otherwise, return the current
* executable path.
*/
static bool bli_executable_path_get(LPWSTR path, DWORD size)
{
wchar_t executable_path[MAX_PATH];
DWORD nSize = GetModuleFileNameW(nullptr, executable_path, MAX_PATH);
if (nSize == 0 || nSize == MAX_PATH) {
return false;
}
if (size <= nSize) {
return false;
}
/* Copy the path to the output buffer. */
if (wcscpy_s(path, size, executable_path) != 0) {
return false;
}
/* Replace the filename "blender.exe" with "blender-launcher.exe". */
if (!PathRemoveFileSpecW(executable_path)) {
/* Failed to remove the file spec. Use the original path. */
return true;
}
if (!PathAppendW(executable_path, L"blender-launcher.exe")) {
/* Failed to append the new filename. Use the original path. */
return true;
}
/* Check if "blender-launcher.exe" exists at this path. */
DWORD attributes = GetFileAttributesW(executable_path);
if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
/* "blender-launcher.exe" does not exist. Use the original executable path. */
return true;
}
if (wcslen(executable_path) + 1 > size) {
/* The output buffer is not large enough for the new path. Use the original path. */
return true;
}
/* The file exists. Copy the path to the output buffer. */
if (wcscpy_s(path, size, executable_path) != 0) {
/* Error: It's not supposed to happen. Return false since the buffer has been modified. */
return false;
}
return true;
}
/* Wrapper function for url_encode. */
static std::wstring url_encode_wstring(const std::string &str)
{
size_t len = str.length();
/* Maximum encoded length is 3 times the original length +1 for null terminator. */
size_t encoded_len_max = len * 3 + 1;
char *encoded_str = new char[encoded_len_max];
url_encode(str.c_str(), encoded_str, encoded_len_max);
/* Convert the encoded char *to a std::wstring (assuming the encoded string is ASCII). */
std::wstring result(encoded_str, encoded_str + strlen(encoded_str));
delete[] encoded_str;
return result;
}
void BLI_windows_exception_show_dialog(const char *filepath_crashlog,
const char *filepath_relaunch,
const char *gpu_name,
const char *build_version)
{
/* Redundant: #InitCommonControls is already called during GHOST System initialization. */
// InitCommonControls();
/* Convert file paths to UTF16 to handle non-ASCII characters. */
wchar_t *filepath_crashlog_utf16 = alloc_utf16_from_8(filepath_crashlog, 0);
wchar_t *filepath_relaunch_utf16 = filepath_relaunch[0] ?
alloc_utf16_from_8(filepath_relaunch, 0) :
nullptr;
std::wstring full_message_16 =
L"A problem has caused the program to stop functioning correctly. If you know the steps to "
L"reproduce this issue, please submit a bug report.\n"
"\n"
L"The crash log can be found at:\n" +
std::wstring(filepath_crashlog_utf16);
TASKDIALOGCONFIG config = {0};
const TASKDIALOG_BUTTON buttons[] = {{IDRETRY, L"Restart"},
#if 0
/* This lead to a large influx of low quality reports on the tracker,
* and has been disabled for that reason, we can re-enable this when
* a better workflow has been established. */
{IDOK, L"Report a Bug"},
#endif
{IDHELP, L"View Crash Log"},
{IDCLOSE, L"Close"}};
config.cbSize = sizeof(config);
config.hwndParent = GetActiveWindow();
config.hInstance = 0;
config.dwCommonButtons = 0;
config.pszMainIcon = TD_ERROR_ICON;
config.pszWindowTitle = L"Blender";
config.pszMainInstruction = L"Blender has stopped working";
config.pszContent = full_message_16.c_str();
config.pButtons = buttons;
config.cButtons = ARRAY_SIZE(buttons);
/* Data passed to the callback function for handling button events. */
const struct Data {
const wchar_t *filepath_crashlog_utf16;
const wchar_t *filepath_relaunch_utf16;
const char *gpu_name;
const char *build_version;
} data = {filepath_crashlog_utf16, filepath_relaunch_utf16, gpu_name, build_version};
config.lpCallbackData = reinterpret_cast<LONG_PTR>(&data);
/* Callback for handling button events. */
config.pfCallback = [](HWND /*hwnd*/,
UINT uNotification,
WPARAM wParam,
LPARAM /*lParam*/,
LONG_PTR dwRefData) -> HRESULT {
const Data *data_ptr = reinterpret_cast<const Data *>(dwRefData);
if (uNotification != TDN_BUTTON_CLICKED) {
return S_OK;
}
int pnButton = static_cast<int>(wParam);
switch (pnButton) {
case IDCLOSE:
return S_OK;
case IDRETRY: {
/* Relaunch the application. */
wchar_t executable_path[MAX_PATH];
if (bli_executable_path_get(executable_path, ARRAYSIZE(executable_path))) {
std::wstring parameters;
if (data_ptr->filepath_relaunch_utf16) {
/* Properly quote the argument to handle spaces and special characters. */
parameters = L"\"" + std::wstring(data_ptr->filepath_relaunch_utf16) + L"\"";
}
else {
/* Proceeding without parameters. */
parameters = L"";
}
ShellExecuteW(
nullptr, L"open", executable_path, parameters.c_str(), nullptr, SW_SHOWNORMAL);
}
return S_OK;
}
case IDHELP:
/* Open the crash log. */
ShellExecuteW(
nullptr, L"open", data_ptr->filepath_crashlog_utf16, nullptr, nullptr, SW_SHOWNORMAL);
return S_FALSE;
case IDOK: {
/* Open the bug report form with pre-filled data. */
/* clang-format off */
std::wstring link =
L"https://redirect.blender.org/"
L"?type=bug_report"
L"&project=blender"
L"&os=" + url_encode_wstring(get_os_info()) +
L"&gpu=" + url_encode_wstring(data_ptr->gpu_name) +
L"&broken_version=" + url_encode_wstring(data_ptr->build_version);
/* clang-format on */
ShellExecuteW(nullptr, L"open", link.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
return S_FALSE;
}
default:
return S_FALSE;
}
};
TaskDialogIndirect(&config, nullptr, nullptr, nullptr);
free((void *)filepath_crashlog_utf16);
free((void *)filepath_relaunch_utf16);
}
/** \} */