// Copyright 2018 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include #include #include #include #include #include #include #include #include #include "Common/CommonPaths.h" #include "Common/CommonTypes.h" #include "Common/FileUtil.h" #include "UpdaterCommon/UI.h" #include "UpdaterCommon/UpdaterCommon.h" namespace { // 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; std::optional binary_to_restart; std::optional parent_pid; std::optional log_file; }; std::vector CommandLineToUtf8Argv(PCWSTR command_line) { int nargs; LPWSTR* tokenized = CommandLineToArgvW(command_line, &nargs); if (!tokenized) return {}; std::vector argv(nargs); for (int i = 0; i < nargs; ++i) { argv[i] = UTF16ToUTF8(tokenized[i]); } LocalFree(tokenized); return argv; } std::optional 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"); parser.add_option("--binary-to-restart") .dest("binary-to-restart") .help("Binary to restart after the update is over.") .metavar("PATH"); 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 argv = CommandLineToUtf8Argv(command_line); optparse::Values options = parser.parse_args(argv); Options opts; // Required arguments. std::vector 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. if (options.is_set("binary-to-restart")) opts.binary_to_restart = options["binary-to-restart"]; 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; } }; // namespace int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) { 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); return 1; } std::optional maybe_opts = ParseCommandLine(pCmdLine); if (!maybe_opts) return 1; Options opts = std::move(*maybe_opts); bool need_admin = false; if (opts.log_file) { log_fp = _wfopen(UTF8ToUTF16(*opts.log_file).c_str(), L"w"); if (!log_fp) { log_fp = stderr; // Failing to create the logfile for writing is a good indicator that we need administrator // priviliges need_admin = true; } 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)) { FatalError("Cannot find install base path, or not a directory."); 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"); } 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; } std::thread thread(UI::MessageLoop); thread.detach(); UI::SetDescription("Fetching and parsing manifests..."); Manifest this_manifest, next_manifest; { std::optional maybe_manifest = FetchAndParseManifest(opts.this_manifest_url); if (!maybe_manifest) { FatalError("Could not fetch current manifest. Aborting."); return 1; } this_manifest = std::move(*maybe_manifest); maybe_manifest = FetchAndParseManifest(opts.next_manifest_url); if (!maybe_manifest) { FatalError("Could not fetch next manifest. Aborting."); return 1; } next_manifest = std::move(*maybe_manifest); } UI::SetDescription("Computing what to do..."); TodoList todo = ComputeActionsToDo(this_manifest, next_manifest); todo.Log(); std::optional maybe_temp_dir = FindOrCreateTempDir(opts.install_base_path); if (!maybe_temp_dir) return 1; std::string temp_dir = std::move(*maybe_temp_dir); UI::SetDescription("Performing Update..."); bool ok = PerformUpdate(todo, opts.install_base_path, opts.content_store_url, temp_dir); if (!ok) FatalError("Failed to apply the update."); CleanUpTempDir(temp_dir, todo); UI::ResetCurrentProgress(); UI::ResetTotalProgress(); UI::SetCurrentMarquee(false); UI::SetTotalMarquee(false); UI::SetCurrentProgress(0, 1); UI::SetTotalProgress(1, 1); UI::SetDescription("Done!"); // Let the user process that we are done. Sleep(1000); if (opts.binary_to_restart) { // 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); } UI::Stop(); return !ok; }