From 68875dc06b8c9aa72cd2bc87d3b2d4eda73e020c Mon Sep 17 00:00:00 2001 From: Shawn Hoffman Date: Thu, 27 Oct 2022 11:55:44 -0700 Subject: [PATCH 1/2] MacUpdater: add version info to Updater.app too --- Source/Core/MacUpdater/Info.plist.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Core/MacUpdater/Info.plist.in b/Source/Core/MacUpdater/Info.plist.in index 7248dc35b3..f9c9263682 100644 --- a/Source/Core/MacUpdater/Info.plist.in +++ b/Source/Core/MacUpdater/Info.plist.in @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + ${DOLPHIN_WC_DESCRIBE} CFBundleVersion - 1 + ${DOLPHIN_VERSION_MAJOR}.${DOLPHIN_VERSION_MINOR} LSMinimumSystemVersion - 10.9 + ${CMAKE_OSX_DEPLOYMENT_TARGET} NSHumanReadableCopyright Licensed under GPL version 2 or later (GPLv2+) NSMainStoryboardFile From 089886a6f869992f7566ee9543e136e136b168aa Mon Sep 17 00:00:00 2001 From: Shawn Hoffman Date: Thu, 27 Oct 2022 13:24:36 -0700 Subject: [PATCH 2/2] MacUpdater: check os version --- Source/Core/Common/build_info.txt.in | 1 - Source/Core/MacUpdater/MacUI.mm | 63 ++++++-- Source/Core/UpdaterCommon/Platform.h | 89 +---------- Source/Core/UpdaterCommon/UpdaterCommon.cpp | 70 +------- Source/Core/UpdaterCommon/UpdaterCommon.h | 37 +++++ Source/Core/WinUpdater/Platform.cpp | 169 ++++++++++++++++++-- 6 files changed, 246 insertions(+), 183 deletions(-) diff --git a/Source/Core/Common/build_info.txt.in b/Source/Core/Common/build_info.txt.in index 3ae938cfc5..5d658d95e6 100644 --- a/Source/Core/Common/build_info.txt.in +++ b/Source/Core/Common/build_info.txt.in @@ -2,7 +2,6 @@ // 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 diff --git a/Source/Core/MacUpdater/MacUI.mm b/Source/Core/MacUpdater/MacUI.mm index 0b2699b1dd..388f5a1da4 100644 --- a/Source/Core/MacUpdater/MacUI.mm +++ b/Source/Core/MacUpdater/MacUI.mm @@ -5,6 +5,7 @@ #include "UpdaterCommon/Platform.h" #include "UpdaterCommon/UI.h" +#include "UpdaterCommon/UpdaterCommon.h" #include #include @@ -138,20 +139,56 @@ void UI::Init() { } -Platform::BuildInfo::BuildInfo(const std::string& content) +bool Platform::VersionCheck(const std::vector& to_update, + const std::string& install_base_path, const std::string& temp_dir, + FILE* log_fp) { - map = {{"OSMinimumVersionMacOS", ""}}; - Parse(content); -} + const auto op_it = std::find_if(to_update.cbegin(), to_update.cend(), [&](const auto& op) { + return op.filename == "Dolphin.app/Contents/Info.plist"; + }); + if (op_it == to_update.cend()) + return true; -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; + const auto op = *op_it; + std::string plist_path = temp_dir + "/" + HexEncode(op.new_hash.data(), op.new_hash.size()); + + NSData* data = [NSData dataWithContentsOfFile:[NSString stringWithCString:plist_path.c_str()]]; + if (!data) + { + fprintf(log_fp, "Failed to read %s, skipping platform version check.\n", plist_path.c_str()); + return true; + } + + NSError* error = nil; + NSDictionary* info_dict = + [NSPropertyListSerialization propertyListWithData:data + options:NSPropertyListImmutable + format:nil + error:&error]; + if (error) + { + fprintf(log_fp, "Failed to parse %s, skipping platform version check.\n", plist_path.c_str()); + return true; + } + NSString* min_version_str = info_dict[@"LSMinimumSystemVersion"]; + if (!min_version_str) + { + fprintf(log_fp, "LSMinimumSystemVersion key missing, skipping platform version check.\n"); + return true; + } + + NSArray* components = [min_version_str componentsSeparatedByString:@"."]; + NSOperatingSystemVersion next_version{ + [components[0] integerValue], [components[1] integerValue], [components[2] integerValue]}; + + fprintf(log_fp, "Platform version check: next_version=%ld.%ld.%ld\n", + (long)next_version.majorVersion, (long)next_version.minorVersion, + (long)next_version.patchVersion); + + if (![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:next_version]) + { + UI::Error("Please update macOS in order to update Dolphin."); + return false; + } return true; } diff --git a/Source/Core/UpdaterCommon/Platform.h b/Source/Core/UpdaterCommon/Platform.h index 9489ba9947..546ceff98b 100644 --- a/Source/Core/UpdaterCommon/Platform.h +++ b/Source/Core/UpdaterCommon/Platform.h @@ -10,91 +10,10 @@ #include "Common/CommonTypes.h" #include "Common/StringUtil.h" +#include "UpdaterCommon/UpdaterCommon.h" + namespace Platform { -struct BuildVersion -{ - u32 major{}; - u32 minor{}; - u32 build{}; - auto operator<=>(BuildVersion const& rhs) const = default; - static std::optional 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 current_version{}; - std::optional target_version{}; -}; - -class BuildInfo -{ - using Map = std::map; - -public: - BuildInfo() = default; - BuildInfo(const std::string& content); - - std::optional 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 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); +bool VersionCheck(const std::vector& to_update, + const std::string& install_base_path, const std::string& temp_dir, FILE* log_fp); } // namespace Platform diff --git a/Source/Core/UpdaterCommon/UpdaterCommon.cpp b/Source/Core/UpdaterCommon/UpdaterCommon.cpp index e307b0610e..6fdb9630a0 100644 --- a/Source/Core/UpdaterCommon/UpdaterCommon.cpp +++ b/Source/Core/UpdaterCommon/UpdaterCommon.cpp @@ -34,51 +34,14 @@ // Refer to docs/autoupdate_overview.md for a detailed overview of the autoupdate process -namespace -{ // Where to log updater output. -FILE* log_fp = stderr; +static FILE* log_fp = stderr; // Public key used to verify update manifests. const std::array UPDATE_PUB_KEY = { 0x2a, 0xb3, 0xd1, 0xdc, 0x6e, 0xf5, 0x07, 0xf6, 0xa0, 0x6c, 0x7c, 0x54, 0xdf, 0x54, 0xf4, 0x42, 0x80, 0xa6, 0x28, 0x8b, 0x6d, 0x70, 0x14, 0xb5, 0x4c, 0x34, 0x95, 0x20, 0x4d, 0xd4, 0xd3, 0x5d}; -struct Manifest -{ - using Filename = std::string; - using Hash = std::array; - std::map entries; -}; - -// Represent the operations to be performed by the updater. -struct TodoList -{ - struct DownloadOp - { - Manifest::Filename filename; - Manifest::Hash hash{}; - }; - std::vector to_download; - - struct UpdateOp - { - Manifest::Filename filename; - std::optional old_hash; - Manifest::Hash new_hash{}; - }; - std::vector to_update; - - struct DeleteOp - { - Manifest::Filename filename; - Manifest::Hash old_hash{}; - }; - std::vector to_delete; - - void Log() const; -}; - bool ProgressCallback(double total, double now, double, double) { UI::SetCurrentProgress(static_cast(now), static_cast(total)); @@ -289,35 +252,7 @@ bool PlatformVersionCheck(const std::vector& 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); + return Platform::VersionCheck(to_update, install_base_path, temp_dir, log_fp); } TodoList ComputeActionsToDo(Manifest this_manifest, Manifest next_manifest) @@ -732,7 +667,6 @@ std::optional ParseCommandLine(std::vector& args) return opts; } -}; // namespace bool RunUpdater(std::vector args) { diff --git a/Source/Core/UpdaterCommon/UpdaterCommon.h b/Source/Core/UpdaterCommon/UpdaterCommon.h index 178ceffe28..679a339f90 100644 --- a/Source/Core/UpdaterCommon/UpdaterCommon.h +++ b/Source/Core/UpdaterCommon/UpdaterCommon.h @@ -14,4 +14,41 @@ // Refer to docs/autoupdate_overview.md for a detailed overview of the autoupdate process +struct Manifest +{ + using Filename = std::string; + using Hash = std::array; + std::map entries; +}; + +// Represent the operations to be performed by the updater. +struct TodoList +{ + struct DownloadOp + { + Manifest::Filename filename; + Manifest::Hash hash{}; + }; + std::vector to_download; + + struct UpdateOp + { + Manifest::Filename filename; + std::optional old_hash; + Manifest::Hash new_hash{}; + }; + std::vector to_update; + + struct DeleteOp + { + Manifest::Filename filename; + Manifest::Hash old_hash{}; + }; + std::vector to_delete; + + void Log() const; +}; + +std::string HexEncode(const u8* buffer, size_t size); +Manifest::Hash ComputeHash(const std::string& contents); bool RunUpdater(std::vector args); diff --git a/Source/Core/WinUpdater/Platform.cpp b/Source/Core/WinUpdater/Platform.cpp index 8c79479560..565d53c413 100644 --- a/Source/Core/WinUpdater/Platform.cpp +++ b/Source/Core/WinUpdater/Platform.cpp @@ -4,6 +4,7 @@ #include #include +#include "Common/CommonPaths.h" #include "Common/FileUtil.h" #include "Common/HttpRequest.h" #include "Common/IOFile.h" @@ -12,17 +13,106 @@ #include "UpdaterCommon/Platform.h" #include "UpdaterCommon/UI.h" +#include "UpdaterCommon/UpdaterCommon.h" namespace Platform { -BuildInfo::BuildInfo(const std::string& content) +struct BuildVersion { - map = {{"OSMinimumVersionWin10", ""}, - {"OSMinimumVersionWin11", ""}, - {"VCToolsVersion", ""}, - {"VCToolsUpdateURL", ""}}; - Parse(content); -} + u32 major{}; + u32 minor{}; + u32 build{}; + auto operator<=>(BuildVersion const& rhs) const = default; + static std::optional 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 current_version{}; + std::optional target_version{}; +}; + +class BuildInfo +{ + using Map = std::map; + +public: + BuildInfo() = default; + BuildInfo(const std::string& content) + { + map = {{"OSMinimumVersionWin10", ""}, + {"OSMinimumVersionWin11", ""}, + {"VCToolsVersion", ""}, + {"VCToolsUpdateURL", ""}}; + Parse(content); + } + + std::optional 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 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; +}; + +struct BuildInfos +{ + BuildInfo current; + BuildInfo next; +}; // This default value should be kept in sync with the value of VCToolsUpdateURL in // build_info.txt.in @@ -63,14 +153,13 @@ static std::optional GetInstalledVCRuntimeVersion() return version; } -static VersionCheckResult VCRuntimeVersionCheck(const BuildInfo& this_build_info, - const BuildInfo& next_build_info) +static VersionCheckResult VCRuntimeVersionCheck(const BuildInfos& build_infos) { VersionCheckResult result; result.current_version = GetInstalledVCRuntimeVersion(); - result.target_version = next_build_info.GetVersion("VCToolsVersion"); + result.target_version = build_infos.next.GetVersion("VCToolsVersion"); - auto existing_version = this_build_info.GetVersion("VCToolsVersion"); + auto existing_version = build_infos.current.GetVersion("VCToolsVersion"); if (!result.target_version.has_value()) result.status = VersionCheckStatus::UpdateOptional; @@ -155,10 +244,45 @@ static VersionCheckResult OSVersionCheck(const BuildInfo& build_info) return result; } -bool VersionCheck(const BuildInfo& this_build_info, const BuildInfo& next_build_info) +std::optional InitBuildInfos(const std::vector& to_update, + const std::string& install_base_path, + const std::string& temp_dir, FILE* log_fp) { + 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 {}; + + 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 {}; + } + BuildInfos build_infos; + build_infos.next = Platform::BuildInfo(build_info_content); + + build_info_path = install_base_path + DIR_SEP + "build_info.txt"; + build_infos.current = 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()); + build_infos.current = Platform::BuildInfo(build_info_content); + } + return build_infos; +} + +bool CheckBuildInfo(const BuildInfos& build_infos) +{ + // The existing BuildInfo may have been modified. Be careful not to overly trust its contents! + // If the binary requires more recent OS, inform the user. - auto os_check = OSVersionCheck(next_build_info); + auto os_check = OSVersionCheck(build_infos.next); if (os_check.status == VersionCheckStatus::UpdateRequired) { UI::Error("Please update Windows in order to update Dolphin."); @@ -167,13 +291,13 @@ bool VersionCheck(const BuildInfo& this_build_info, const BuildInfo& next_build_ // 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); + auto vc_check = VCRuntimeVersionCheck(build_infos); 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); + VCRuntimeUpdate(build_infos.next); + vc_check = VCRuntimeVersionCheck(build_infos); if (vc_check.status == VersionCheckStatus::UpdateRequired) { // The update is required and the install failed for some reason. @@ -184,4 +308,17 @@ bool VersionCheck(const BuildInfo& this_build_info, const BuildInfo& next_build_ return true; } + +bool VersionCheck(const std::vector& to_update, + const std::string& install_base_path, const std::string& temp_dir, FILE* log_fp) +{ + auto build_infos = InitBuildInfos(to_update, install_base_path, temp_dir, log_fp); + // If there's no build info, it means the check should be skipped. + if (!build_infos.has_value()) + { + return true; + } + return CheckBuildInfo(build_infos.value()); +} + } // namespace Platform