Skip to content

Commit

Permalink
util: Add very-early VC++ runtime version check
Browse files Browse the repository at this point in the history
  • Loading branch information
Macdu committed Jun 9, 2024
1 parent 1b97d05 commit 1b3d0a0
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 1 deletion.
7 changes: 6 additions & 1 deletion vita3k/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,12 @@ add_subdirectory(vkutil)

add_executable(vita3k MACOSX_BUNDLE main.cpp interface.cpp interface.h performance.cpp)

target_link_libraries(vita3k PRIVATE app config cppcommon ctrl display gdbstub gui gxm io miniz modules packages renderer shader touch)
if(WIN32)
# This file is a bit special, it is run before anything else and must be added here
target_sources(vita3k PRIVATE util/src/vc_runtime_checker.cpp)
endif()

target_link_libraries(vita3k PRIVATE app config cppcommon ctrl display gdbstub gui gxm io miniz modules packages renderer shader touch util)
if(USE_DISCORD_RICH_PRESENCE)
target_link_libraries(vita3k PRIVATE discord-rpc)
endif()
Expand Down
2 changes: 2 additions & 0 deletions vita3k/util/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ add_library(
src/tracy.cpp
)

# vc_runtime_checker.cpp is directly added from the main CMakeList (for some reason adding it here doesn't work)

target_include_directories(util PUBLIC include)
target_link_libraries(util PUBLIC ${Boost_LIBRARIES} config fmt spdlog http mem)
target_link_libraries(util PRIVATE libcurl crypto)
Expand Down
106 changes: 106 additions & 0 deletions vita3k/util/src/vc_runtime_checker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+

#pragma comment(lib, "Version.lib")

#include <cstdint>

#include <Windows.h>
#include <winver.h>
#include <shellapi.h>

#include <fmt/format.h>

// Minimum version is 14.38.33135.0.
static constexpr uint64_t MIN_VERSION_V0 = 14;
static constexpr uint64_t MIN_VERSION_V1 = 38;
static constexpr uint64_t MIN_VERSION_V2 = 33135;
static constexpr uint64_t MIN_VERSION_V3 = 0;
static constexpr uint64_t MIN_VERSION = (MIN_VERSION_V0 << 48) | (MIN_VERSION_V1 << 32) | (MIN_VERSION_V2 << 16) | MIN_VERSION_V3;
static constexpr const char *DOWNLOAD_URL = "https://aka.ms/vs/17/release/vc_redist.x64.exe";

struct VCRuntimeCheckObject {
VCRuntimeCheckObject() {
const HMODULE crt_handle = GetModuleHandleW(L"msvcp140.dll");
if (!crt_handle)
return;

const HANDLE heap = GetProcessHeap();
DWORD filename_length = MAX_PATH;
LPWSTR filename = static_cast<LPWSTR>(HeapAlloc(heap, 0, filename_length));
if (!filename)
return;

for (;;) {
DWORD len = GetModuleFileNameW(crt_handle, filename, filename_length);
if (len == filename_length && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
filename_length *= 2;
if (filename_length >= 4 * 1024)
return;
LPWSTR new_filename = static_cast<LPWSTR>(HeapReAlloc(heap, 0, filename, filename_length));
if (!new_filename) {
HeapFree(heap, 0, filename);
return;
}
filename = new_filename;
continue;
}

break;
}

const DWORD version_size = GetFileVersionInfoSizeExW(0, filename, nullptr);
LPVOID version_block;
if (version_size == 0 || !(version_block = HeapAlloc(heap, 0, version_size))) {
HeapFree(heap, 0, filename);
return;
}

VS_FIXEDFILEINFO *fi;
UINT fi_size;
if (!GetFileVersionInfoExW(0, filename, 0, version_size, version_block) || !VerQueryValueW(version_block, L"\\", reinterpret_cast<LPVOID *>(&fi), &fi_size)) {
HeapFree(heap, 0, version_block);
HeapFree(heap, 0, filename);
return;
}

const DWORD v0 = (fi->dwFileVersionMS >> 16) & 0xFFFFu;
const DWORD v1 = fi->dwFileVersionMS & 0xFFFFu;
const DWORD v2 = (fi->dwFileVersionLS >> 16) & 0xFFFFu;
const DWORD v3 = fi->dwFileVersionLS & 0xFFFFu;
const uint64_t version = (static_cast<uint64_t>(fi->dwFileVersionMS) << 32) | fi->dwFileVersionLS;

HeapFree(heap, 0, version_block);
HeapFree(heap, 0, filename);

if (version >= MIN_VERSION)
return;

// fmt is self-contained, hopefully it'll be okay.
char message[512];
const auto fmt_result = fmt::format_to_n(message, sizeof(message),
"Your Microsoft Visual C++ Runtime appears to be too old for this build of PCSX2.\n\n"
"Your version: {}.{}.{}.{}\n"
"Required version: {}.{}.{}.{}\n\n"
"You can download the latest version from {}.\n\n"
"Do you want to exit and download this version now?\n"
"If you select No, PCSX2 will likely crash.",
v0, v1, v2, v3, MIN_VERSION_V0, MIN_VERSION_V1, MIN_VERSION_V2, MIN_VERSION_V3, DOWNLOAD_URL);
message[(fmt_result.size > (sizeof(message) - 1)) ? (sizeof(message) - 1) : fmt_result.size] = 0;

if (MessageBoxA(NULL, message, "Old Visual C++ Runtime Detected", MB_ICONERROR | MB_YESNO) == IDNO)
return;

if (!ShellExecuteA(NULL, "open", DOWNLOAD_URL, nullptr, nullptr, SW_SHOWNORMAL))
MessageBoxA(NULL, "ShellExecuteA() failed, you may need to manually open the URL.", "Error", MB_OK);

TerminateProcess(GetCurrentProcess(), 0xFFFFFFFF);
}
};

// We have to use a special object which gets initialized before all other global objects, because those might use the
// CRT and go kaboom. Yucky, but gets the job done.
#pragma optimize("", off)
#pragma warning(disable : 4075) // warning C4075: initializers put in unrecognized initialization area
#pragma init_seg(".CRT$XCT")
VCRuntimeCheckObject s_vcruntime_checker;

0 comments on commit 1b3d0a0

Please sign in to comment.