Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #11051 from shuffle2/update-vcredist
WinUpdater: Check OS and VC++ Redist versions
  • Loading branch information
JMC47 committed Oct 21, 2022
2 parents 9222956 + 717c36b commit 00e23da
Show file tree
Hide file tree
Showing 13 changed files with 435 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CMake/FindPowerShell.cmake
@@ -0,0 +1,4 @@
find_program(POWERSHELL_EXE NAMES powershell)

INCLUDE(FindPackageHandleStandardArgs)
find_package_handle_standard_args(PowerShell DEFAULT_MSG POWERSHELL_EXE)
11 changes: 11 additions & 0 deletions Source/Core/Common/CMakeLists.txt
Expand Up @@ -324,6 +324,17 @@ if(UNIX)
target_link_libraries(traversal_server PRIVATE ${SYSTEMD_LIBRARIES})
endif()
elseif(WIN32)
find_package(PowerShell REQUIRED)
execute_process(
COMMAND ${POWERSHELL_EXE} -Command "[System.Diagnostics.FileVersionInfo]::GetVersionInfo('$ENV{VCToolsRedistDir}vc_redist.x64.exe').ProductVersion"
OUTPUT_VARIABLE VC_TOOLS_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/build_info.txt.in"
"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/build_info.txt"
)

target_link_libraries(common PRIVATE "-INCLUDE:enableCompatPatches")
endif()

Expand Down
24 changes: 24 additions & 0 deletions Source/Core/Common/build_info.txt.in
@@ -0,0 +1,24 @@
// Indicate the minimum OS version required for the binary to run properly.
// Updater will fail the update if the user does not meet this requirement.
OSMinimumVersionWin10=10.0.15063.0
OSMinimumVersionWin11=10.0.22000.0
OSMinimumVersionMacOS=10.14

// This is the runtime which was compiled against - providing a way for Updater to detect if update
// is needed before executing this binary. Note that, annoyingly, the version in environment
// variables does not match the "real" version. Consider:
// VersionInfo : File: C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Redist\MSVC\14.32.31326\vc_redist.x64.exe
// InternalName: setup
// OriginalFilename: VC_redist.x64.exe
// FileVersion: 14.32.31332.0
// FileDescription: Microsoft Visual C++ 2015-2022 Redistributable (x64) - 14.32.31332
// Product: Microsoft Visual C++ 2015-2022 Redistributable (x64) - 14.32.31332
// ProductVersion: 14.32.31332.0
// Whereas the environment variables look like:
// VCToolsInstallDir=C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.33.31629\
// VCToolsRedistDir=C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Redist\MSVC\14.32.31326\
// VCToolsVersion=14.33.31629
// We're really looking for "14.32.31332.0" (because that's what will appear in the registry once
// installed), NOT the other values!
VCToolsVersion=${VC_TOOLS_VERSION}
VCToolsUpdateURL=https://aka.ms/vs/17/release/vc_redist.x64.exe
34 changes: 34 additions & 0 deletions Source/Core/DolphinLib.vcxproj
Expand Up @@ -60,4 +60,38 @@
<Import Project="$(ExternalsDir)zstd\exports.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
<PropertyGroup>
<BuildInfoTemplate>Common\build_info.txt.in</BuildInfoTemplate>
<BuildInfoOutput>$(BinaryOutputDir)build_info.txt</BuildInfoOutput>
</PropertyGroup>
<ItemGroup>
<Text Include="$(BuildInfoTemplate)" />
</ItemGroup>
<UsingTask TaskName="GetProductVersion"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Path ParameterType="System.String" Required="true" />
<ProductVersion ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System.Diagnostics"/>
<Code Type="Fragment" Language="cs">
<![CDATA[
ProductVersion = FileVersionInfo.GetVersionInfo(Path).ProductVersion;
]]>
</Code>
</Task>
</UsingTask>
<Target Name="WriteBuildInfo" AfterTargets="Build" Inputs="$(BuildInfoTemplate)" Outputs="$(BuildInfoOutput)">
<GetProductVersion Path="$(VCToolsRedistInstallDir)vc_redist.x64.exe">
<Output PropertyName="VCToolsProductVersion" TaskParameter="ProductVersion" />
</GetProductVersion>
<Message Text="VCToolsProductVersion $(VCToolsProductVersion)" Importance="High" />
<WriteLinesToFile
File="$(BuildInfoOutput)"
Lines="$([System.IO.File]::ReadAllText($(BuildInfoTemplate)).Replace('${VC_TOOLS_VERSION}', $(VCToolsProductVersion)))"
Overwrite="true"
/>
</Target>
</Project>
19 changes: 19 additions & 0 deletions Source/Core/MacUpdater/MacUI.mm
Expand Up @@ -3,6 +3,7 @@

#include "MacUpdater/ViewController.h"

#include "UpdaterCommon/Platform.h"
#include "UpdaterCommon/UI.h"

#include <Cocoa/Cocoa.h>
Expand Down Expand Up @@ -136,3 +137,21 @@ void run_on_main(std::function<void()> fnc)
void UI::Init()
{
}

Platform::BuildInfo::BuildInfo(const std::string& content)
{
map = {{"OSMinimumVersionMacOS", ""}};
Parse(content);
}

bool Platform::VersionCheck(const BuildInfo& this_build_info, const BuildInfo& next_build_info)
{
// TODO implement OS Minimum Version check
// It should go something like this:
// auto target_version = next_build_info.GetVersion("OSMinimumVersionMacOS");
// if (!target_version.has_value() || current_version >= target_version)
// return true;
// show error
// return false;
return true;
}
4 changes: 2 additions & 2 deletions Source/Core/UICommon/AutoUpdate.cpp
Expand Up @@ -246,11 +246,11 @@ void AutoUpdateChecker::TriggerUpdate(const AutoUpdateChecker::NewVersionInforma
#endif

// Run the updater!
const std::string command_line = MakeUpdaterCommandLine(updater_flags);
std::string command_line = MakeUpdaterCommandLine(updater_flags);
INFO_LOG_FMT(COMMON, "Updater command line: {}", command_line);

#ifdef _WIN32
STARTUPINFO sinfo = {sizeof(sinfo)};
STARTUPINFO sinfo{.cb = sizeof(sinfo)};
sinfo.dwFlags = STARTF_FORCEOFFFEEDBACK; // No hourglass cursor after starting the process.
PROCESS_INFORMATION pinfo;
if (CreateProcessW(UTF8ToWString(reloc_updater_path).c_str(), UTF8ToWString(command_line).data(),
Expand Down
100 changes: 100 additions & 0 deletions Source/Core/UpdaterCommon/Platform.h
@@ -0,0 +1,100 @@
// Copyright 2018 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <map>
#include <optional>
#include <sstream>

#include "Common/CommonTypes.h"
#include "Common/StringUtil.h"

namespace Platform
{
struct BuildVersion
{
u32 major{};
u32 minor{};
u32 build{};
auto operator<=>(BuildVersion const& rhs) const = default;
static std::optional<BuildVersion> from_string(const std::string& str)
{
auto components = SplitString(str, '.');
// Allow variable number of components (truncating after "build"), but not
// empty.
if (components.size() == 0)
return {};
BuildVersion version;
if (!TryParse(components[0], &version.major, 10))
return {};
if (components.size() > 1 && !TryParse(components[1], &version.minor, 10))
return {};
if (components.size() > 2 && !TryParse(components[2], &version.build, 10))
return {};
return version;
}
};

enum class VersionCheckStatus
{
NothingToDo,
UpdateOptional,
UpdateRequired,
};

struct VersionCheckResult
{
VersionCheckStatus status{VersionCheckStatus::NothingToDo};
std::optional<BuildVersion> current_version{};
std::optional<BuildVersion> target_version{};
};

class BuildInfo
{
using Map = std::map<std::string, std::string>;

public:
BuildInfo() = default;
BuildInfo(const std::string& content);

std::optional<std::string> GetString(const std::string& name) const
{
auto it = map.find(name);
if (it == map.end() || it->second.size() == 0)
return {};
return it->second;
}

std::optional<BuildVersion> GetVersion(const std::string& name) const
{
auto str = GetString(name);
if (!str.has_value())
return {};
return BuildVersion::from_string(str.value());
}

private:
void Parse(const std::string& content)
{
std::stringstream content_stream(content);
std::string line;
while (std::getline(content_stream, line))
{
if (line.starts_with("//"))
continue;
const size_t equals_index = line.find('=');
if (equals_index == line.npos)
continue;
auto key = line.substr(0, equals_index);
auto key_it = map.find(key);
if (key_it == map.end())
continue;
key_it->second = line.substr(equals_index + 1);
}
}
Map map;
};

bool VersionCheck(const BuildInfo& this_build_info, const BuildInfo& next_build_info);
} // namespace Platform
41 changes: 41 additions & 0 deletions Source/Core/UpdaterCommon/UpdaterCommon.cpp
Expand Up @@ -18,6 +18,7 @@
#include "Common/HttpRequest.h"
#include "Common/ScopeGuard.h"
#include "Common/StringUtil.h"
#include "UpdaterCommon/Platform.h"
#include "UpdaterCommon/UI.h"

#ifndef _WIN32
Expand Down Expand Up @@ -278,6 +279,41 @@ bool DownloadContent(const std::vector<TodoList::DownloadOp>& to_download,
return true;
}

bool PlatformVersionCheck(const std::vector<TodoList::UpdateOp>& to_update,
const std::string& install_base_path, const std::string& temp_dir)
{
UI::SetDescription("Checking platform...");

const auto op_it = std::find_if(to_update.cbegin(), to_update.cend(),
[&](const auto& op) { return op.filename == "build_info.txt"; });
if (op_it == to_update.cend())
return true;

const auto op = *op_it;
std::string build_info_path =
temp_dir + DIR_SEP + HexEncode(op.new_hash.data(), op.new_hash.size());
std::string build_info_content;
if (!File::ReadFileToString(build_info_path, build_info_content) ||
op.new_hash != ComputeHash(build_info_content))
{
fprintf(log_fp, "Failed to read %s\n.", build_info_path.c_str());
return false;
}
auto next_build_info = Platform::BuildInfo(build_info_content);

build_info_path = install_base_path + DIR_SEP + "build_info.txt";
auto this_build_info = Platform::BuildInfo();
if (File::ReadFileToString(build_info_path, build_info_content))
{
if (op.old_hash != ComputeHash(build_info_content))
fprintf(log_fp, "Using modified existing BuildInfo %s.\n", build_info_path.c_str());
this_build_info = Platform::BuildInfo(build_info_content);
}

// The existing BuildInfo may have been modified. Be careful not to overly trust its contents!
return Platform::VersionCheck(this_build_info, next_build_info);
}

TodoList ComputeActionsToDo(Manifest this_manifest, Manifest next_manifest)
{
TodoList todo;
Expand Down Expand Up @@ -474,6 +510,11 @@ bool PerformUpdate(const TodoList& todo, const std::string& install_base_path,
return false;
fprintf(log_fp, "Download step completed.\n");

fprintf(log_fp, "Starting platform version check step...\n");
if (!PlatformVersionCheck(todo.to_update, install_base_path, temp_path))
return false;
fprintf(log_fp, "Platform version check step completed.\n");

fprintf(log_fp, "Starting update step...\n");
if (!UpdateFiles(todo.to_update, install_base_path, temp_path))
return false;
Expand Down
1 change: 1 addition & 0 deletions Source/Core/WinUpdater/CMakeLists.txt
Expand Up @@ -2,6 +2,7 @@ set (MANIFEST_FILE Updater.exe.manifest)

add_executable(winupdater WIN32
Main.cpp
Platform.cpp
WinUI.cpp
${MANIFEST_FILE})

Expand Down
8 changes: 4 additions & 4 deletions Source/Core/WinUpdater/Main.cpp
Expand Up @@ -21,10 +21,10 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine
{
if (lstrlenW(pCmdLine) == 0)
{
MessageBox(nullptr,
L"This updater is not meant to be launched directly. Configure Auto-Update in "
"Dolphin's settings instead.",
L"Error", MB_ICONERROR);
MessageBoxW(nullptr,
L"This updater is not meant to be launched directly. Configure Auto-Update in "
"Dolphin's settings instead.",
L"Error", MB_ICONERROR);
return 1;
}

Expand Down

0 comments on commit 00e23da

Please sign in to comment.