@@ -4,6 +4,7 @@
#include < map>
#include < optional>
#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<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)
{
map = {{" OSMinimumVersionWin10" , " " },
{" OSMinimumVersionWin11" , " " },
{" VCToolsVersion" , " " },
{" VCToolsUpdateURL" , " " }};
Parse (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;
};
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<BuildVersion> 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<BuildInfos> InitBuildInfos (const std::vector<TodoList::UpdateOp>& 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<TodoList::UpdateOp>& 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