vcruntimecheck.cpp (4396B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "common/windows_headers.h" 5 #include <shellapi.h> 6 7 #include "fmt/format.h" 8 9 #define MAKE_VERSION64(v0, v1, v2, v3) \ 10 (static_cast<DWORD64>(v3) | (static_cast<DWORD64>(v2) << 16) | (static_cast<DWORD64>(v1) << 32) | \ 11 (static_cast<DWORD64>(v0) << 48)) 12 #define VERSION64_PART(v, p) (static_cast<WORD>(((v) >> (48 - ((p) * 16))) & 0xFFFFu)) 13 14 // Minimum version is 14.38.33135.0. 15 static constexpr DWORD64 MIN_VERSION = MAKE_VERSION64(14, 38, 33135, 0); 16 static constexpr const char* DOWNLOAD_URL = "https://aka.ms/vs/17/release/vc_redist.x64.exe"; 17 18 struct VCRuntimeCheckObject 19 { 20 VCRuntimeCheckObject() 21 { 22 const HMODULE crt_handle = GetModuleHandleW(L"msvcp140.dll"); 23 if (!crt_handle) 24 return; 25 26 const HANDLE heap = GetProcessHeap(); 27 DWORD filename_length = MAX_PATH; 28 LPWSTR filename = static_cast<LPWSTR>(HeapAlloc(heap, 0, filename_length)); 29 if (!filename) 30 return; 31 32 for (;;) 33 { 34 DWORD len = GetModuleFileNameW(crt_handle, filename, filename_length); 35 if (len == filename_length && GetLastError() == ERROR_INSUFFICIENT_BUFFER) 36 { 37 filename_length *= 2; 38 if (filename_length >= 4 * 1024) 39 return; 40 LPWSTR new_filename = static_cast<LPWSTR>(HeapReAlloc(heap, 0, filename, filename_length)); 41 if (!new_filename) 42 { 43 HeapFree(heap, 0, filename); 44 return; 45 } 46 filename = new_filename; 47 continue; 48 } 49 50 break; 51 } 52 53 const DWORD version_size = GetFileVersionInfoSizeExW(0, filename, nullptr); 54 LPVOID version_block; 55 if (version_size == 0 || !(version_block = HeapAlloc(heap, 0, version_size))) 56 { 57 HeapFree(heap, 0, filename); 58 return; 59 } 60 61 VS_FIXEDFILEINFO* fi; 62 UINT fi_size; 63 if (!GetFileVersionInfoExW(0, filename, 0, version_size, version_block) || 64 !VerQueryValueW(version_block, L"\\", reinterpret_cast<LPVOID*>(&fi), &fi_size)) 65 { 66 HeapFree(heap, 0, version_block); 67 HeapFree(heap, 0, filename); 68 return; 69 } 70 71 const DWORD64 version = MAKE_VERSION64((fi->dwFileVersionMS >> 16) & 0xFFFFu, fi->dwFileVersionMS & 0xFFFFu, 72 (fi->dwFileVersionLS >> 16) & 0xFFFFu, fi->dwFileVersionLS & 0xFFFFu); 73 74 HeapFree(heap, 0, version_block); 75 HeapFree(heap, 0, filename); 76 77 if (version >= MIN_VERSION) 78 return; 79 80 // fmt is self-contained, hopefully it'll be okay. 81 char message[512]; 82 const auto fmt_result = 83 fmt::format_to_n(message, sizeof(message), 84 "Your Microsoft Visual C++ Runtime appears to be too old for this build of DuckStation.\n\n" 85 "Your version: {}.{}.{}.{}\n" 86 "Required version: {}.{}.{}.{}\n\n" 87 "You can download the latest version from {}.\n\n" 88 "Do you want to exit and download this version now?\n" 89 "If you select No, DuckStation will likely crash.", 90 VERSION64_PART(version, 0), VERSION64_PART(version, 1), VERSION64_PART(version, 2), 91 VERSION64_PART(version, 3), VERSION64_PART(MIN_VERSION, 0), VERSION64_PART(MIN_VERSION, 1), 92 VERSION64_PART(MIN_VERSION, 2), VERSION64_PART(MIN_VERSION, 3), DOWNLOAD_URL); 93 message[(fmt_result.size > (sizeof(message) - 1)) ? (sizeof(message) - 1) : fmt_result.size] = 0; 94 95 if (MessageBoxA(NULL, message, "Old Visual C++ Runtime Detected", MB_ICONERROR | MB_YESNO) == IDNO) 96 return; 97 98 if (!ShellExecuteA(NULL, "open", DOWNLOAD_URL, nullptr, nullptr, SW_SHOWNORMAL)) 99 MessageBoxA(NULL, "ShellExecuteA() failed, you may need to manually open the URL.", "Error", MB_OK); 100 101 TerminateProcess(GetCurrentProcess(), 0xFFFFFFFF); 102 } 103 }; 104 105 // We have to use a special object which gets initialized before all other global objects, because those might use the 106 // CRT and go kaboom. Yucky, but gets the job done. 107 #pragma optimize("", off) 108 #pragma warning(disable : 4075) // warning C4075: initializers put in unrecognized initialization area 109 #pragma init_seg(".CRT$XCT") 110 VCRuntimeCheckObject s_vcruntime_checker;