diff options
| author | JMC47 <JMC4789@gmail.com> | 2022-10-21 13:55:51 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-10-21 13:55:51 -0400 |
| commit | 00e23da6074b5b25a2ff4e4ff040986c07753e94 (patch) | |
| tree | 2831c77c6a2aabb04c1d53c408df0b20dd6d0efe /Source/Core/WinUpdater/Platform.cpp | |
| parent | 9222956acdfb6a0526b6fdada4aeaa936de9fcd1 (diff) | |
| parent | 717c36bc43c2a568b3fff04bd6d3ea2a81c0582d (diff) | |
Merge pull request #11051 from shuffle2/update-vcredist
WinUpdater: Check OS and VC++ Redist versions
Diffstat (limited to 'Source/Core/WinUpdater/Platform.cpp')
| -rw-r--r-- | Source/Core/WinUpdater/Platform.cpp | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/Source/Core/WinUpdater/Platform.cpp b/Source/Core/WinUpdater/Platform.cpp new file mode 100644 index 0000000000..8c79479560 --- /dev/null +++ b/Source/Core/WinUpdater/Platform.cpp @@ -0,0 +1,187 @@ +#include <Windows.h> + +#include <filesystem> +#include <map> +#include <optional> + +#include "Common/FileUtil.h" +#include "Common/HttpRequest.h" +#include "Common/IOFile.h" +#include "Common/ScopeGuard.h" +#include "Common/StringUtil.h" + +#include "UpdaterCommon/Platform.h" +#include "UpdaterCommon/UI.h" + +namespace Platform +{ +BuildInfo::BuildInfo(const std::string& content) +{ + map = {{"OSMinimumVersionWin10", ""}, + {"OSMinimumVersionWin11", ""}, + {"VCToolsVersion", ""}, + {"VCToolsUpdateURL", ""}}; + Parse(content); +} + +// This default value should be kept in sync with the value of VCToolsUpdateURL in +// build_info.txt.in +static const char* VCToolsUpdateURLDefault = "https://aka.ms/vs/17/release/vc_redist.x64.exe"; +#define VC_RUNTIME_REGKEY R"(SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\)" + +static const char* VCRuntimeRegistrySubkey() +{ + return VC_RUNTIME_REGKEY +#ifdef _M_X86_64 + "x64"; +#elif _M_ARM_64 + "arm64"; +#else +#error unsupported architecture +#endif +} + +static bool ReadVCRuntimeVersionField(u32* value, const char* name) +{ + DWORD value_len = sizeof(*value); + return RegGetValueA(HKEY_LOCAL_MACHINE, VCRuntimeRegistrySubkey(), name, RRF_RT_REG_DWORD, + nullptr, value, &value_len) == ERROR_SUCCESS; +} + +static std::optional<BuildVersion> GetInstalledVCRuntimeVersion() +{ + u32 installed; + if (!ReadVCRuntimeVersionField(&installed, "Installed") || !installed) + return {}; + BuildVersion version; + if (!ReadVCRuntimeVersionField(&version.major, "Major") || + !ReadVCRuntimeVersionField(&version.minor, "Minor") || + !ReadVCRuntimeVersionField(&version.build, "Bld")) + { + return {}; + } + return version; +} + +static VersionCheckResult VCRuntimeVersionCheck(const BuildInfo& this_build_info, + const BuildInfo& next_build_info) +{ + VersionCheckResult result; + result.current_version = GetInstalledVCRuntimeVersion(); + result.target_version = next_build_info.GetVersion("VCToolsVersion"); + + auto existing_version = this_build_info.GetVersion("VCToolsVersion"); + + if (!result.target_version.has_value()) + result.status = VersionCheckStatus::UpdateOptional; + else if (!result.current_version.has_value() || result.current_version < result.target_version) + result.status = VersionCheckStatus::UpdateRequired; + + // See if the current build was already running on acceptable version of the runtime. This could + // happen if the user manually copied the redist DLLs and got Dolphin running that way. + if (existing_version.has_value() && existing_version >= result.target_version) + result.status = VersionCheckStatus::NothingToDo; + + return result; +} + +static bool VCRuntimeUpdate(const BuildInfo& build_info) +{ + UI::SetDescription("Updating VC++ Redist, please wait..."); + + Common::HttpRequest req; + req.FollowRedirects(10); + auto resp = req.Get(build_info.GetString("VCToolsUpdateURL").value_or(VCToolsUpdateURLDefault)); + if (!resp) + return false; + + // Write it to current working directory. + auto redist_path = std::filesystem::current_path() / L"vc_redist.x64.exe"; + auto redist_path_u8 = WStringToUTF8(redist_path.wstring()); + File::IOFile redist_file; + redist_file.Open(redist_path_u8, "wb"); + if (!redist_file.WriteBytes(resp->data(), resp->size())) + return false; + redist_file.Close(); + + Common::ScopeGuard redist_deleter([&] { File::Delete(redist_path_u8); }); + + // The installer also supports /passive and /quiet. We pass neither to allow the user to see and + // interact with the installer. + std::wstring cmdline = redist_path.filename().wstring() + L" /install /norestart"; + STARTUPINFOW startup_info{.cb = sizeof(startup_info)}; + PROCESS_INFORMATION process_info; + if (!CreateProcessW(redist_path.c_str(), cmdline.data(), nullptr, nullptr, TRUE, 0, nullptr, + nullptr, &startup_info, &process_info)) + { + return false; + } + CloseHandle(process_info.hThread); + + // Wait for it to run + WaitForSingleObject(process_info.hProcess, INFINITE); + DWORD exit_code; + bool has_exit_code = GetExitCodeProcess(process_info.hProcess, &exit_code); + CloseHandle(process_info.hProcess); + // NOTE: Some nonzero exit codes can still be considered success (e.g. if installation was + // bypassed because the same version already installed). + return has_exit_code && exit_code == EXIT_SUCCESS; +} + +static BuildVersion CurrentOSVersion() +{ + typedef DWORD(WINAPI * RtlGetVersion_t)(PRTL_OSVERSIONINFOW); + auto RtlGetVersion = + (RtlGetVersion_t)GetProcAddress(GetModuleHandle(TEXT("ntdll")), "RtlGetVersion"); + RTL_OSVERSIONINFOW info{.dwOSVersionInfoSize = sizeof(info)}; + RtlGetVersion(&info); + return {.major = info.dwMajorVersion, .minor = info.dwMinorVersion, .build = info.dwBuildNumber}; +} + +static VersionCheckResult OSVersionCheck(const BuildInfo& build_info) +{ + VersionCheckResult result; + result.current_version = CurrentOSVersion(); + + constexpr BuildVersion WIN11_BASE{10, 0, 22000}; + const char* version_name = + (result.current_version >= WIN11_BASE) ? "OSMinimumVersionWin11" : "OSMinimumVersionWin10"; + result.target_version = build_info.GetVersion(version_name); + + if (!result.target_version.has_value() || result.current_version >= result.target_version) + result.status = VersionCheckStatus::NothingToDo; + else + result.status = VersionCheckStatus::UpdateRequired; + return result; +} + +bool VersionCheck(const BuildInfo& this_build_info, const BuildInfo& next_build_info) +{ + // If the binary requires more recent OS, inform the user. + auto os_check = OSVersionCheck(next_build_info); + if (os_check.status == VersionCheckStatus::UpdateRequired) + { + UI::Error("Please update Windows in order to update Dolphin."); + return false; + } + + // Check if application being launched needs more recent version of VC Redist. If so, download + // latest updater and execute it. + auto vc_check = VCRuntimeVersionCheck(this_build_info, next_build_info); + if (vc_check.status != VersionCheckStatus::NothingToDo) + { + // Don't bother checking status of the install itself, just check if we actually see the new + // version. + VCRuntimeUpdate(next_build_info); + vc_check = VCRuntimeVersionCheck(this_build_info, next_build_info); + if (vc_check.status == VersionCheckStatus::UpdateRequired) + { + // The update is required and the install failed for some reason. + UI::Error("Please update VC++ Runtime in order to update Dolphin."); + return false; + } + } + + return true; +} +} // namespace Platform |
