| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  | // Copyright 2018 Dolphin Emulator Project
 | 
					
						
							|  |  |  | // Licensed under GPLv2+
 | 
					
						
							|  |  |  | // Refer to the license.txt file included.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <windows.h>
 | 
					
						
							| 
									
										
										
										
											2018-04-25 22:38:10 +02:00
										 |  |  | #include <ShlObj.h>
 | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include <OptionParser.h>
 | 
					
						
							|  |  |  | #include <algorithm>
 | 
					
						
							|  |  |  | #include <array>
 | 
					
						
							| 
									
										
										
										
											2018-03-23 22:24:45 +01:00
										 |  |  | #include <chrono>
 | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  | #include <cstdio>
 | 
					
						
							|  |  |  | #include <ed25519/ed25519.h>
 | 
					
						
							|  |  |  | #include <mbedtls/base64.h>
 | 
					
						
							|  |  |  | #include <mbedtls/sha256.h>
 | 
					
						
							|  |  |  | #include <optional>
 | 
					
						
							|  |  |  | #include <shellapi.h>
 | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  | #include <thread>
 | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  | #include <vector>
 | 
					
						
							|  |  |  | #include <zlib.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "Common/CommonPaths.h"
 | 
					
						
							|  |  |  | #include "Common/CommonTypes.h"
 | 
					
						
							|  |  |  | #include "Common/FileUtil.h"
 | 
					
						
							|  |  |  | #include "Common/HttpRequest.h"
 | 
					
						
							|  |  |  | #include "Common/StringUtil.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  | #include "Updater/UI.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  | namespace | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | // Public key used to verify update manifests.
 | 
					
						
							|  |  |  | const u8 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}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const char UPDATE_TEMP_DIR[] = "TempUpdate"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Where to log updater output.
 | 
					
						
							|  |  |  | FILE* log_fp = stderr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void FlushLog() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   fflush(log_fp); | 
					
						
							|  |  |  |   fclose(log_fp); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Internal representation of options passed on the command-line.
 | 
					
						
							|  |  |  | struct Options | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   std::string this_manifest_url; | 
					
						
							|  |  |  |   std::string next_manifest_url; | 
					
						
							|  |  |  |   std::string content_store_url; | 
					
						
							|  |  |  |   std::string install_base_path; | 
					
						
							| 
									
										
										
										
											2018-03-23 00:29:03 +01:00
										 |  |  |   std::optional<std::string> binary_to_restart; | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |   std::optional<DWORD> parent_pid; | 
					
						
							|  |  |  |   std::optional<std::string> log_file; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | std::vector<std::string> CommandLineToUtf8Argv(PCWSTR command_line) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   int nargs; | 
					
						
							|  |  |  |   LPWSTR* tokenized = CommandLineToArgvW(command_line, &nargs); | 
					
						
							|  |  |  |   if (!tokenized) | 
					
						
							|  |  |  |     return {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   std::vector<std::string> argv(nargs); | 
					
						
							|  |  |  |   for (int i = 0; i < nargs; ++i) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     argv[i] = UTF16ToUTF8(tokenized[i]); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   LocalFree(tokenized); | 
					
						
							|  |  |  |   return argv; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | std::optional<Options> ParseCommandLine(PCWSTR command_line) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   using optparse::OptionParser; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   OptionParser parser = OptionParser().prog("updater.exe").description("Dolphin Updater binary"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   parser.add_option("--this-manifest-url") | 
					
						
							|  |  |  |       .dest("this-manifest-url") | 
					
						
							|  |  |  |       .help("URL to the update manifest for the currently installed version.") | 
					
						
							|  |  |  |       .metavar("URL"); | 
					
						
							|  |  |  |   parser.add_option("--next-manifest-url") | 
					
						
							|  |  |  |       .dest("next-manifest-url") | 
					
						
							|  |  |  |       .help("URL to the update manifest for the to-be-installed version.") | 
					
						
							|  |  |  |       .metavar("URL"); | 
					
						
							|  |  |  |   parser.add_option("--content-store-url") | 
					
						
							|  |  |  |       .dest("content-store-url") | 
					
						
							|  |  |  |       .help("Base URL of the content store where files to download are stored.") | 
					
						
							|  |  |  |       .metavar("URL"); | 
					
						
							|  |  |  |   parser.add_option("--install-base-path") | 
					
						
							|  |  |  |       .dest("install-base-path") | 
					
						
							|  |  |  |       .help("Base path of the Dolphin install to be updated.") | 
					
						
							|  |  |  |       .metavar("PATH"); | 
					
						
							| 
									
										
										
										
											2018-03-23 00:29:03 +01:00
										 |  |  |   parser.add_option("--binary-to-restart") | 
					
						
							|  |  |  |       .dest("binary-to-restart") | 
					
						
							|  |  |  |       .help("Binary to restart after the update is over.") | 
					
						
							|  |  |  |       .metavar("PATH"); | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |   parser.add_option("--log-file") | 
					
						
							|  |  |  |       .dest("log-file") | 
					
						
							|  |  |  |       .help("File where to log updater debug output.") | 
					
						
							|  |  |  |       .metavar("PATH"); | 
					
						
							|  |  |  |   parser.add_option("--parent-pid") | 
					
						
							|  |  |  |       .dest("parent-pid") | 
					
						
							|  |  |  |       .type("int") | 
					
						
							|  |  |  |       .help("(optional) PID of the parent process. The updater will wait for this process to " | 
					
						
							|  |  |  |             "complete before proceeding.") | 
					
						
							|  |  |  |       .metavar("PID"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   std::vector<std::string> argv = CommandLineToUtf8Argv(command_line); | 
					
						
							|  |  |  |   optparse::Values options = parser.parse_args(argv); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Options opts; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Required arguments.
 | 
					
						
							|  |  |  |   std::vector<std::string> required{"this-manifest-url", "next-manifest-url", "content-store-url", | 
					
						
							|  |  |  |                                     "install-base-path"}; | 
					
						
							|  |  |  |   for (const auto& req : required) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (!options.is_set(req)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       parser.print_help(); | 
					
						
							|  |  |  |       return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   opts.this_manifest_url = options["this-manifest-url"]; | 
					
						
							|  |  |  |   opts.next_manifest_url = options["next-manifest-url"]; | 
					
						
							|  |  |  |   opts.content_store_url = options["content-store-url"]; | 
					
						
							|  |  |  |   opts.install_base_path = options["install-base-path"]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Optional arguments.
 | 
					
						
							| 
									
										
										
										
											2018-03-23 00:29:03 +01:00
										 |  |  |   if (options.is_set("binary-to-restart")) | 
					
						
							|  |  |  |     opts.binary_to_restart = options["binary-to-restart"]; | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |   if (options.is_set("parent-pid")) | 
					
						
							|  |  |  |     opts.parent_pid = (DWORD)options.get("parent-pid"); | 
					
						
							|  |  |  |   if (options.is_set("log-file")) | 
					
						
							|  |  |  |     opts.log_file = options["log-file"]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return opts; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | std::optional<std::string> GzipInflate(const std::string& data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   z_stream zstrm; | 
					
						
							|  |  |  |   zstrm.zalloc = nullptr; | 
					
						
							|  |  |  |   zstrm.zfree = nullptr; | 
					
						
							|  |  |  |   zstrm.opaque = nullptr; | 
					
						
							|  |  |  |   zstrm.avail_in = static_cast<u32>(data.size()); | 
					
						
							|  |  |  |   zstrm.next_in = reinterpret_cast<u8*>(const_cast<char*>(data.data())); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // 16 + MAX_WBITS means gzip. Don't ask me.
 | 
					
						
							|  |  |  |   inflateInit2(&zstrm, 16 + MAX_WBITS); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   std::string out; | 
					
						
							|  |  |  |   char buffer[4096]; | 
					
						
							|  |  |  |   int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   do | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     zstrm.avail_out = sizeof(buffer); | 
					
						
							|  |  |  |     zstrm.next_out = reinterpret_cast<u8*>(buffer); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ret = inflate(&zstrm, 0); | 
					
						
							|  |  |  |     out.append(buffer, sizeof(buffer) - zstrm.avail_out); | 
					
						
							|  |  |  |   } while (ret == Z_OK); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   inflateEnd(&zstrm); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (ret != Z_STREAM_END) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     fprintf(log_fp, "Could not read the data as gzip: error %d.\n", ret); | 
					
						
							|  |  |  |     return {}; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return out; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool VerifySignature(const std::string& data, const std::string& b64_signature) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   u8 signature[64];  // ed25519 sig size.
 | 
					
						
							|  |  |  |   size_t sig_size; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (mbedtls_base64_decode(signature, sizeof(signature), &sig_size, | 
					
						
							|  |  |  |                             reinterpret_cast<const u8*>(b64_signature.data()), | 
					
						
							|  |  |  |                             b64_signature.size()) || | 
					
						
							|  |  |  |       sig_size != sizeof(signature)) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     fprintf(log_fp, "Invalid base64: %s\n", b64_signature.c_str()); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return ed25519_verify(signature, reinterpret_cast<const u8*>(data.data()), data.size(), | 
					
						
							|  |  |  |                         UPDATE_PUB_KEY); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct Manifest | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   using Filename = std::string; | 
					
						
							|  |  |  |   using Hash = std::array<u8, 16>; | 
					
						
							|  |  |  |   std::map<Filename, Hash> entries; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool HexDecode(const std::string& hex, u8* buffer, size_t size) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   if (hex.size() != size * 2) | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   auto DecodeNibble = [](char c) -> std::optional<u8> { | 
					
						
							|  |  |  |     if (c >= '0' && c <= '9') | 
					
						
							|  |  |  |       return static_cast<u8>(c - '0'); | 
					
						
							|  |  |  |     else if (c >= 'a' && c <= 'f') | 
					
						
							|  |  |  |       return static_cast<u8>(c - 'a' + 10); | 
					
						
							|  |  |  |     else if (c >= 'A' && c <= 'F') | 
					
						
							|  |  |  |       return static_cast<u8>(c - 'A' + 10); | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       return {}; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   for (size_t i = 0; i < size; ++i) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     std::optional<u8> high = DecodeNibble(hex[2 * i]); | 
					
						
							|  |  |  |     std::optional<u8> low = DecodeNibble(hex[2 * i + 1]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!high || !low) | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     buffer[i] = (*high << 4) | *low; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | std::string HexEncode(const u8* buffer, size_t size) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   std::string out(size * 2, '\0'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (size_t i = 0; i < size; ++i) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     out[2 * i] = "0123456789abcdef"[buffer[i] >> 4]; | 
					
						
							|  |  |  |     out[2 * i + 1] = "0123456789abcdef"[buffer[i] & 0xF]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return out; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | std::optional<Manifest> ParseManifest(const std::string& manifest) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   Manifest parsed; | 
					
						
							|  |  |  |   size_t pos = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while (pos < manifest.size()) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     size_t filename_end_pos = manifest.find('\t', pos); | 
					
						
							|  |  |  |     if (filename_end_pos == std::string::npos) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       fprintf(log_fp, "Manifest entry %zu: could not find filename end.\n", parsed.entries.size()); | 
					
						
							|  |  |  |       return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     size_t hash_end_pos = manifest.find('\n', filename_end_pos); | 
					
						
							|  |  |  |     if (hash_end_pos == std::string::npos) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       fprintf(log_fp, "Manifest entry %zu: could not find hash end.\n", parsed.entries.size()); | 
					
						
							|  |  |  |       return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     std::string filename = manifest.substr(pos, filename_end_pos - pos); | 
					
						
							|  |  |  |     std::string hash = manifest.substr(filename_end_pos + 1, hash_end_pos - filename_end_pos - 1); | 
					
						
							|  |  |  |     if (hash.size() != 32) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       fprintf(log_fp, "Manifest entry %zu: invalid hash: \"%s\".\n", parsed.entries.size(), | 
					
						
							|  |  |  |               hash.c_str()); | 
					
						
							|  |  |  |       return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Manifest::Hash decoded_hash; | 
					
						
							|  |  |  |     if (!HexDecode(hash, decoded_hash.data(), decoded_hash.size())) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       fprintf(log_fp, "Manifest entry %zu: invalid hash: \"%s\".\n", parsed.entries.size(), | 
					
						
							|  |  |  |               hash.c_str()); | 
					
						
							|  |  |  |       return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     parsed.entries[filename] = decoded_hash; | 
					
						
							|  |  |  |     pos = hash_end_pos + 1; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return parsed; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  | // Not showing a progress bar here because this part is just too quick
 | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  | std::optional<Manifest> FetchAndParseManifest(const std::string& url) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   Common::HttpRequest http; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Common::HttpRequest::Response resp = http.Get(url); | 
					
						
							|  |  |  |   if (!resp) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     fprintf(log_fp, "Manifest download failed.\n"); | 
					
						
							|  |  |  |     return {}; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   std::string contents(reinterpret_cast<char*>(resp->data()), resp->size()); | 
					
						
							|  |  |  |   std::optional<std::string> maybe_decompressed = GzipInflate(contents); | 
					
						
							|  |  |  |   if (!maybe_decompressed) | 
					
						
							|  |  |  |     return {}; | 
					
						
							|  |  |  |   std::string decompressed = std::move(*maybe_decompressed); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Split into manifest and signature.
 | 
					
						
							|  |  |  |   size_t boundary = decompressed.rfind("\n\n"); | 
					
						
							|  |  |  |   if (boundary == std::string::npos) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     fprintf(log_fp, "No signature was found in manifest.\n"); | 
					
						
							|  |  |  |     return {}; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   std::string signature_block = decompressed.substr(boundary + 2);  // 2 for "\n\n".
 | 
					
						
							|  |  |  |   decompressed.resize(boundary + 1);                                // 1 to keep the final "\n".
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   std::vector<std::string> signatures = SplitString(signature_block, '\n'); | 
					
						
							|  |  |  |   bool found_valid_signature = false; | 
					
						
							|  |  |  |   for (const auto& signature : signatures) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (VerifySignature(decompressed, signature)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       found_valid_signature = true; | 
					
						
							|  |  |  |       break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (!found_valid_signature) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     fprintf(log_fp, "Could not verify signature of the manifest.\n"); | 
					
						
							|  |  |  |     return {}; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return ParseManifest(decompressed); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Represent the operations to be performed by the updater.
 | 
					
						
							|  |  |  | struct TodoList | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  |   struct DownloadOp | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     Manifest::Filename filename; | 
					
						
							|  |  |  |     Manifest::Hash hash; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   std::vector<DownloadOp> to_download; | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   struct UpdateOp | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     Manifest::Filename filename; | 
					
						
							|  |  |  |     std::optional<Manifest::Hash> old_hash; | 
					
						
							|  |  |  |     Manifest::Hash new_hash; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   std::vector<UpdateOp> to_update; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   struct DeleteOp | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     Manifest::Filename filename; | 
					
						
							|  |  |  |     Manifest::Hash old_hash; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   std::vector<DeleteOp> to_delete; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void Log() const | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (to_update.size()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       fprintf(log_fp, "Updating:\n"); | 
					
						
							|  |  |  |       for (const auto& op : to_update) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         std::string old_desc = | 
					
						
							|  |  |  |             op.old_hash ? HexEncode(op.old_hash->data(), op.old_hash->size()) : "(new)"; | 
					
						
							|  |  |  |         fprintf(log_fp, "  - %s: %s -> %s\n", op.filename.c_str(), old_desc.c_str(), | 
					
						
							|  |  |  |                 HexEncode(op.new_hash.data(), op.new_hash.size()).c_str()); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (to_delete.size()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       fprintf(log_fp, "Deleting:\n"); | 
					
						
							|  |  |  |       for (const auto& op : to_delete) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         fprintf(log_fp, "  - %s (%s)\n", op.filename.c_str(), | 
					
						
							|  |  |  |                 HexEncode(op.old_hash.data(), op.old_hash.size()).c_str()); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | TodoList ComputeActionsToDo(Manifest this_manifest, Manifest next_manifest) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   TodoList todo; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Delete if present in this manifest but not in next manifest.
 | 
					
						
							|  |  |  |   for (const auto& entry : this_manifest.entries) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (next_manifest.entries.find(entry.first) == next_manifest.entries.end()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       TodoList::DeleteOp del; | 
					
						
							|  |  |  |       del.filename = entry.first; | 
					
						
							|  |  |  |       del.old_hash = entry.second; | 
					
						
							|  |  |  |       todo.to_delete.push_back(std::move(del)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Download and update if present in next manifest with different hash from this manifest.
 | 
					
						
							|  |  |  |   for (const auto& entry : next_manifest.entries) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     std::optional<Manifest::Hash> old_hash; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const auto& old_entry = this_manifest.entries.find(entry.first); | 
					
						
							|  |  |  |     if (old_entry != this_manifest.entries.end()) | 
					
						
							|  |  |  |       old_hash = old_entry->second; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!old_hash || *old_hash != entry.second) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  |       TodoList::DownloadOp download; | 
					
						
							|  |  |  |       download.filename = entry.first; | 
					
						
							|  |  |  |       download.hash = entry.second; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       todo.to_download.push_back(std::move(download)); | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       TodoList::UpdateOp update; | 
					
						
							|  |  |  |       update.filename = entry.first; | 
					
						
							|  |  |  |       update.old_hash = old_hash; | 
					
						
							|  |  |  |       update.new_hash = entry.second; | 
					
						
							|  |  |  |       todo.to_update.push_back(std::move(update)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return todo; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | std::optional<std::string> FindOrCreateTempDir(const std::string& base_path) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   std::string temp_path = base_path + DIR_SEP + UPDATE_TEMP_DIR; | 
					
						
							|  |  |  |   int counter = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   do | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (!File::Exists(temp_path)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       if (File::CreateDir(temp_path)) | 
					
						
							|  |  |  |         return temp_path; | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         fprintf(log_fp, "Couldn't create temp directory.\n"); | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (File::IsDirectory(temp_path)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       return temp_path; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       // Try again with a counter appended to the path.
 | 
					
						
							|  |  |  |       std::string suffix = UPDATE_TEMP_DIR + std::to_string(counter); | 
					
						
							|  |  |  |       temp_path = base_path + DIR_SEP + suffix; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } while (counter++ < 10); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   fprintf(log_fp, "Could not find an appropriate temp directory name. Giving up.\n"); | 
					
						
							|  |  |  |   return {}; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void CleanUpTempDir(const std::string& temp_dir, const TodoList& todo) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   // This is best-effort cleanup, we ignore most errors.
 | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  |   for (const auto& download : todo.to_download) | 
					
						
							|  |  |  |     File::Delete(temp_dir + DIR_SEP + HexEncode(download.hash.data(), download.hash.size())); | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |   File::DeleteDir(temp_dir); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Manifest::Hash ComputeHash(const std::string& contents) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   std::array<u8, 32> full; | 
					
						
							|  |  |  |   mbedtls_sha256(reinterpret_cast<const u8*>(contents.data()), contents.size(), full.data(), false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Manifest::Hash out; | 
					
						
							|  |  |  |   std::copy(full.begin(), full.begin() + 16, out.begin()); | 
					
						
							|  |  |  |   return out; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  | bool ProgressCallback(double total, double now, double, double) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   UI::SetProgress(static_cast<int>(now), static_cast<int>(total)); | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool DownloadContent(const std::vector<TodoList::DownloadOp>& to_download, | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |                      const std::string& content_base_url, const std::string& temp_path) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  |   Common::HttpRequest req(std::chrono::seconds(30), ProgressCallback); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (size_t i = 0; i < to_download.size(); i++) | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |   { | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  |     auto& download = to_download[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     std::string hash_filename = HexEncode(download.hash.data(), download.hash.size()); | 
					
						
							|  |  |  |     UI::SetDescription("Downloading " + download.filename + "... (File " + std::to_string(i + 1) + | 
					
						
							|  |  |  |                        " of " + std::to_string(to_download.size()) + ")"); | 
					
						
							|  |  |  |     UI::SetMarquee(false); | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Add slashes where needed.
 | 
					
						
							|  |  |  |     std::string content_store_path = hash_filename; | 
					
						
							|  |  |  |     content_store_path.insert(4, "/"); | 
					
						
							|  |  |  |     content_store_path.insert(2, "/"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     std::string url = content_base_url + content_store_path; | 
					
						
							|  |  |  |     fprintf(log_fp, "Downloading %s ...\n", url.c_str()); | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |     auto resp = req.Get(url); | 
					
						
							|  |  |  |     if (!resp) | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  |     UI::SetMarquee(true); | 
					
						
							|  |  |  |     UI::SetDescription("Verifying " + download.filename + "..."); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |     std::string contents(reinterpret_cast<char*>(resp->data()), resp->size()); | 
					
						
							|  |  |  |     std::optional<std::string> maybe_decompressed = GzipInflate(contents); | 
					
						
							|  |  |  |     if (!maybe_decompressed) | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     std::string decompressed = std::move(*maybe_decompressed); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Check that the downloaded contents have the right hash.
 | 
					
						
							|  |  |  |     Manifest::Hash contents_hash = ComputeHash(decompressed); | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  |     if (contents_hash != download.hash) | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |     { | 
					
						
							|  |  |  |       fprintf(log_fp, "Wrong hash on downloaded content %s.\n", url.c_str()); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     std::string out = temp_path + DIR_SEP + hash_filename; | 
					
						
							|  |  |  |     if (!File::WriteStringToFile(decompressed, out)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       fprintf(log_fp, "Could not write cache file %s.\n", out.c_str()); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool BackupFile(const std::string& path) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   std::string backup_path = path + ".bak"; | 
					
						
							|  |  |  |   fprintf(log_fp, "Backing up unknown pre-existing %s to .bak.\n", path.c_str()); | 
					
						
							|  |  |  |   if (!File::Rename(path, backup_path)) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     fprintf(log_fp, "Cound not rename %s to %s for backup.\n", path.c_str(), backup_path.c_str()); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool UpdateFiles(const std::vector<TodoList::UpdateOp>& to_update, | 
					
						
							|  |  |  |                  const std::string& install_base_path, const std::string& temp_path) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   for (const auto& op : to_update) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     std::string path = install_base_path + DIR_SEP + op.filename; | 
					
						
							|  |  |  |     if (!File::CreateFullPath(path)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       fprintf(log_fp, "Could not create directory structure for %s.\n", op.filename.c_str()); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (File::Exists(path)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       std::string contents; | 
					
						
							|  |  |  |       if (!File::ReadFileToString(path, contents)) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         fprintf(log_fp, "Could not read existing file %s.\n", op.filename.c_str()); | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       Manifest::Hash contents_hash = ComputeHash(contents); | 
					
						
							|  |  |  |       if (contents_hash == op.new_hash) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         fprintf(log_fp, "File %s was already up to date. Partial update?\n", op.filename.c_str()); | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       else if (!op.old_hash || contents_hash != *op.old_hash) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         if (!BackupFile(path)) | 
					
						
							|  |  |  |           return false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Now we can safely move the new contents to the location.
 | 
					
						
							|  |  |  |     std::string content_filename = HexEncode(op.new_hash.data(), op.new_hash.size()); | 
					
						
							|  |  |  |     fprintf(log_fp, "Updating file %s from content %s...\n", op.filename.c_str(), | 
					
						
							|  |  |  |             content_filename.c_str()); | 
					
						
							| 
									
										
										
										
											2018-04-26 11:00:03 +02:00
										 |  |  |     if (!File::Copy(temp_path + DIR_SEP + content_filename, path)) | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |     { | 
					
						
							|  |  |  |       fprintf(log_fp, "Could not update file %s.\n", op.filename.c_str()); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool DeleteObsoleteFiles(const std::vector<TodoList::DeleteOp>& to_delete, | 
					
						
							|  |  |  |                          const std::string& install_base_path) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   for (const auto& op : to_delete) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     std::string path = install_base_path + DIR_SEP + op.filename; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!File::Exists(path)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       fprintf(log_fp, "File %s is already missing.\n", op.filename.c_str()); | 
					
						
							|  |  |  |       continue; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       std::string contents; | 
					
						
							|  |  |  |       if (!File::ReadFileToString(path, contents)) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         fprintf(log_fp, "Could not read file planned for deletion: %s.\n", op.filename.c_str()); | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       Manifest::Hash contents_hash = ComputeHash(contents); | 
					
						
							|  |  |  |       if (contents_hash != op.old_hash) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         if (!BackupFile(path)) | 
					
						
							|  |  |  |           return false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       File::Delete(path); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool PerformUpdate(const TodoList& todo, const std::string& install_base_path, | 
					
						
							|  |  |  |                    const std::string& content_base_url, const std::string& temp_path) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   fprintf(log_fp, "Starting download step...\n"); | 
					
						
							|  |  |  |   if (!DownloadContent(todo.to_download, content_base_url, temp_path)) | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   fprintf(log_fp, "Download step completed.\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   fprintf(log_fp, "Starting update step...\n"); | 
					
						
							|  |  |  |   if (!UpdateFiles(todo.to_update, install_base_path, temp_path)) | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   fprintf(log_fp, "Update step completed.\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   fprintf(log_fp, "Starting deletion step...\n"); | 
					
						
							|  |  |  |   if (!DeleteObsoleteFiles(todo.to_delete, install_base_path)) | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   fprintf(log_fp, "Deletion step completed.\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-27 17:49:36 +02:00
										 |  |  | void FatalError(const std::string& message) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   auto wide_message = UTF8ToUTF16(message); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   MessageBox(nullptr, | 
					
						
							|  |  |  |              (L"A fatal error occured and the updater cannot continue:\n " + wide_message).c_str(), | 
					
						
							|  |  |  |              L"Error", MB_ICONERROR); | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-27 17:49:36 +02:00
										 |  |  |   fprintf(log_fp, "%s\n", message.c_str()); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  |   UI::Stop(); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  | }  // namespace
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   std::optional<Options> maybe_opts = ParseCommandLine(pCmdLine); | 
					
						
							|  |  |  |   if (!maybe_opts) | 
					
						
							|  |  |  |     return 1; | 
					
						
							|  |  |  |   Options opts = std::move(*maybe_opts); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 22:38:10 +02:00
										 |  |  |   bool need_admin = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |   if (opts.log_file) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     log_fp = _wfopen(UTF8ToUTF16(*opts.log_file).c_str(), L"w"); | 
					
						
							|  |  |  |     if (!log_fp) | 
					
						
							| 
									
										
										
										
											2018-04-25 22:38:10 +02:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |       log_fp = stderr; | 
					
						
							| 
									
										
										
										
											2018-04-25 22:38:10 +02:00
										 |  |  |       // Failing to create the logfile for writing is a good indicator that we need administrator
 | 
					
						
							|  |  |  |       // priviliges
 | 
					
						
							|  |  |  |       need_admin = true; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |     else | 
					
						
							|  |  |  |       atexit(FlushLog); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   fprintf(log_fp, "Updating from: %s\n", opts.this_manifest_url.c_str()); | 
					
						
							|  |  |  |   fprintf(log_fp, "Updating to:   %s\n", opts.next_manifest_url.c_str()); | 
					
						
							|  |  |  |   fprintf(log_fp, "Install path:  %s\n", opts.install_base_path.c_str()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!File::IsDirectory(opts.install_base_path)) | 
					
						
							|  |  |  |   { | 
					
						
							| 
									
										
										
										
											2018-03-27 17:49:36 +02:00
										 |  |  |     FatalError("Cannot find install base path, or not a directory."); | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |     return 1; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (opts.parent_pid) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     fprintf(log_fp, "Waiting for parent PID %d to complete...\n", *opts.parent_pid); | 
					
						
							|  |  |  |     HANDLE parent_handle = OpenProcess(SYNCHRONIZE, FALSE, *opts.parent_pid); | 
					
						
							|  |  |  |     WaitForSingleObject(parent_handle, INFINITE); | 
					
						
							|  |  |  |     CloseHandle(parent_handle); | 
					
						
							|  |  |  |     fprintf(log_fp, "Completed! Proceeding with update.\n"); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 22:38:10 +02:00
										 |  |  |   if (need_admin) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (IsUserAnAdmin()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       FatalError("Failed to write to directory despite administrator priviliges."); | 
					
						
							|  |  |  |       return 1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     wchar_t path[MAX_PATH]; | 
					
						
							|  |  |  |     if (GetModuleFileName(hInstance, path, sizeof(path)) == 0) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       FatalError("Failed to get updater filename."); | 
					
						
							|  |  |  |       return 1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Relaunch the updater as administrator
 | 
					
						
							|  |  |  |     ShellExecuteW(nullptr, L"runas", path, pCmdLine, NULL, SW_SHOW); | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   UI::SetDescription("Fetching and parsing manifests..."); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |   Manifest this_manifest, next_manifest; | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     std::optional<Manifest> maybe_manifest = FetchAndParseManifest(opts.this_manifest_url); | 
					
						
							|  |  |  |     if (!maybe_manifest) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2018-03-27 17:49:36 +02:00
										 |  |  |       FatalError("Could not fetch current manifest. Aborting."); | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |       return 1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this_manifest = std::move(*maybe_manifest); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     maybe_manifest = FetchAndParseManifest(opts.next_manifest_url); | 
					
						
							|  |  |  |     if (!maybe_manifest) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2018-03-27 17:49:36 +02:00
										 |  |  |       FatalError("Could not fetch next manifest. Aborting."); | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |       return 1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     next_manifest = std::move(*maybe_manifest); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  |   UI::SetDescription("Computing what to do..."); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |   TodoList todo = ComputeActionsToDo(this_manifest, next_manifest); | 
					
						
							|  |  |  |   todo.Log(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   std::optional<std::string> maybe_temp_dir = FindOrCreateTempDir(opts.install_base_path); | 
					
						
							|  |  |  |   if (!maybe_temp_dir) | 
					
						
							|  |  |  |     return 1; | 
					
						
							|  |  |  |   std::string temp_dir = std::move(*maybe_temp_dir); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  |   UI::SetDescription("Performing Update..."); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |   bool ok = PerformUpdate(todo, opts.install_base_path, opts.content_store_url, temp_dir); | 
					
						
							|  |  |  |   if (!ok) | 
					
						
							| 
									
										
										
										
											2018-03-27 17:49:36 +02:00
										 |  |  |     FatalError("Failed to apply the update."); | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   CleanUpTempDir(temp_dir, todo); | 
					
						
							| 
									
										
										
										
											2018-03-23 00:29:03 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  |   UI::ResetProgress(); | 
					
						
							|  |  |  |   UI::SetMarquee(false); | 
					
						
							|  |  |  |   UI::SetProgress(100, 100); | 
					
						
							|  |  |  |   UI::SetDescription("Done!"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Let the user process that we are done.
 | 
					
						
							|  |  |  |   Sleep(1000); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-23 00:29:03 +01:00
										 |  |  |   if (opts.binary_to_restart) | 
					
						
							|  |  |  |   { | 
					
						
							| 
									
										
										
										
											2018-04-25 22:38:10 +02:00
										 |  |  |     // Hack: Launching the updater over the explorer ensures that admin priviliges are dropped. Why?
 | 
					
						
							|  |  |  |     // Ask Microsoft.
 | 
					
						
							|  |  |  |     ShellExecuteW(nullptr, nullptr, L"explorer.exe", UTF8ToUTF16(*opts.binary_to_restart).c_str(), | 
					
						
							|  |  |  |                   nullptr, SW_SHOW); | 
					
						
							| 
									
										
										
										
											2018-03-23 00:29:03 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-03-28 00:28:40 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   UI::Stop(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-18 00:45:45 +01:00
										 |  |  |   return !ok; | 
					
						
							|  |  |  | } |