mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-21 13:20:27 -06:00
WinUpdater: Check OS and VC++ Redist versions.
This commit is contained in:
187
Source/Core/WinUpdater/Platform.cpp
Normal file
187
Source/Core/WinUpdater/Platform.cpp
Normal file
@ -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
|
Reference in New Issue
Block a user