summaryrefslogtreecommitdiff
path: root/Source/Core/WinUpdater/Platform.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Core/WinUpdater/Platform.cpp')
-rw-r--r--Source/Core/WinUpdater/Platform.cpp187
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