summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorspycrab <spycrab@users.noreply.github.com>2019-03-07 19:09:35 +0100
committerGitHub <noreply@github.com>2019-03-07 19:09:35 +0100
commitdb2542886a76a3d346fbdb019dec6b0afd53bd84 (patch)
treea3dce03c9b5f5c4c98818cfd11b5083dbda67f51 /Source/Core
parent7d17163ce85f518e3951a39ab226a446c32a8e54 (diff)
parent19bf2c166d9bc1647a3d055dbdeca87e8cdf5a06 (diff)
Merge pull request #7844 from spycrab/updatecommon_electric_boogaloo
UpdaterCommon: Move most of the programs here
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/MacUpdater/AppDelegate.mm204
-rw-r--r--Source/Core/MacUpdater/MacUI.mm27
-rw-r--r--Source/Core/Updater/Main.cpp203
-rw-r--r--Source/Core/Updater/Updater.vcxproj3
-rw-r--r--Source/Core/Updater/WinUI.cpp37
-rw-r--r--Source/Core/UpdaterCommon/CMakeLists.txt3
-rw-r--r--Source/Core/UpdaterCommon/UI.h11
-rw-r--r--Source/Core/UpdaterCommon/UpdaterCommon.cpp258
-rw-r--r--Source/Core/UpdaterCommon/UpdaterCommon.h46
-rw-r--r--Source/Core/UpdaterCommon/UpdaterCommon.vcxproj3
10 files changed, 337 insertions, 458 deletions
diff --git a/Source/Core/MacUpdater/AppDelegate.mm b/Source/Core/MacUpdater/AppDelegate.mm
index e8bfc5c016..d260a2122a 100644
--- a/Source/Core/MacUpdater/AppDelegate.mm
+++ b/Source/Core/MacUpdater/AppDelegate.mm
@@ -4,100 +4,12 @@
#import "AppDelegate.h"
-#include "Common/FileUtil.h"
-
-#include "UpdaterCommon/UI.h"
#include "UpdaterCommon/UpdaterCommon.h"
#include <Cocoa/Cocoa.h>
-#include <OptionParser.h>
-#include <optional>
-#include <unistd.h>
+#include <string>
#include <vector>
-namespace
-{
-struct Options
-{
- std::string this_manifest_url;
- std::string next_manifest_url;
- std::string content_store_url;
- std::string install_base_path;
- std::optional<std::string> binary_to_restart;
- std::optional<pid_t> parent_pid;
- std::optional<std::string> log_file;
-};
-
-std::optional<Options> ParseCommandLine(std::vector<std::string>& args)
-{
- using optparse::OptionParser;
-
- OptionParser parser =
- OptionParser().prog("Dolphin Updater").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");
-
- optparse::Values options = parser.parse_args(args);
-
- 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.
- if (options.is_set("binary-to-restart"))
- opts.binary_to_restart = options["binary-to-restart"];
- if (options.is_set("parent-pid"))
- opts.parent_pid = (pid_t)options.get("parent-pid");
- if (options.is_set("log-file"))
- opts.log_file = options["log-file"];
-
- return opts;
-}
-} // namespace
-
@interface AppDelegate ()
@end
@@ -106,8 +18,6 @@ std::optional<Options> ParseCommandLine(std::vector<std::string>& args)
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification
{
- UI::SetVisible(false);
-
NSArray* arguments = [[NSProcessInfo processInfo] arguments];
__block std::vector<std::string> args;
@@ -116,118 +26,10 @@ std::optional<Options> ParseCommandLine(std::vector<std::string>& args)
args.push_back(std::string([obj UTF8String]));
}];
- std::optional<Options> maybe_opts = ParseCommandLine(args);
-
- if (!maybe_opts)
- {
- [NSApp terminate:nil];
- return;
- }
-
- Options opts = std::move(*maybe_opts);
-
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
- if (opts.log_file)
- {
- log_fp = fopen(opts.log_file.value().c_str(), "w");
- if (!log_fp)
- log_fp = stderr;
- 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.");
-
- [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
- return;
- }
-
- if (opts.parent_pid)
- {
- UI::SetDescription("Waiting for Dolphin to quit...");
-
- fprintf(log_fp, "Waiting for parent PID %d to complete...\n", *opts.parent_pid);
-
- auto pid = opts.parent_pid.value();
-
- for (int res = kill(pid, 0); res == 0 || (res < 0 && errno == EPERM); res = kill(pid, 0))
- {
- sleep(1);
- }
-
- fprintf(log_fp, "Completed! Proceeding with update.\n");
- }
-
- UI::SetVisible(true);
-
- UI::SetDescription("Fetching and parsing manifests...");
-
- Manifest this_manifest, next_manifest;
- {
- std::optional<Manifest> maybe_manifest = FetchAndParseManifest(opts.this_manifest_url);
- if (!maybe_manifest)
- {
- FatalError("Could not fetch current manifest. Aborting.");
- return;
- }
- this_manifest = std::move(*maybe_manifest);
-
- maybe_manifest = FetchAndParseManifest(opts.next_manifest_url);
- if (!maybe_manifest)
- {
- FatalError("Could not fetch next manifest. Aborting.");
- [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
- return;
- }
- next_manifest = std::move(*maybe_manifest);
- }
-
- UI::SetDescription("Computing what to do...");
-
- 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;
- 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(1, 1);
- UI::SetTotalProgress(1, 1);
- UI::SetDescription("Done!");
-
- // Let the user process that we are done.
- [NSThread sleepForTimeInterval:1.0f];
-
- if (opts.binary_to_restart)
- {
- [[NSWorkspace sharedWorkspace]
- launchApplication:[NSString stringWithCString:opts.binary_to_restart.value().c_str()
- encoding:[NSString defaultCStringEncoding]]];
- }
-
- dispatch_sync(dispatch_get_main_queue(), ^{
- [NSApp terminate:nil];
- });
+ RunUpdater(args);
+ [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
});
}
diff --git a/Source/Core/MacUpdater/MacUI.mm b/Source/Core/MacUpdater/MacUI.mm
index aab655749d..d36a6a111e 100644
--- a/Source/Core/MacUpdater/MacUI.mm
+++ b/Source/Core/MacUpdater/MacUI.mm
@@ -7,6 +7,7 @@
#include "UpdaterCommon/UI.h"
#include <Cocoa/Cocoa.h>
+#include <unistd.h>
#include <functional>
@@ -107,6 +108,32 @@ void UI::SetTotalProgress(int current, int total)
run_on_main([&] { [GetView() SetTotalProgress:(double)current total:(double)total]; });
}
+void UI::Sleep(int seconds)
+{
+ [NSThread sleepForTimeInterval:static_cast<float>(seconds)];
+}
+
+void UI::WaitForPID(u32 pid)
+{
+ for (int res = kill(pid, 0); res == 0 || (res < 0 && errno == EPERM); res = kill(pid, 0))
+ {
+ UI::Sleep(1);
+ }
+}
+
+void UI::LaunchApplication(std::string path)
+{
+ [[NSWorkspace sharedWorkspace]
+ launchApplication:[NSString stringWithCString:path.c_str()
+ encoding:[NSString defaultCStringEncoding]]];
+}
+
+// Stubs. These are only needed on Windows
+
+void UI::Init()
+{
+}
+
void UI::Stop()
{
}
diff --git a/Source/Core/Updater/Main.cpp b/Source/Core/Updater/Main.cpp
index 35e868a7f1..0cb09cb966 100644
--- a/Source/Core/Updater/Main.cpp
+++ b/Source/Core/Updater/Main.cpp
@@ -4,36 +4,18 @@
#include <windows.h>
#include <ShlObj.h>
-
-#include <OptionParser.h>
-#include <array>
-#include <optional>
#include <shellapi.h>
+
#include <string>
-#include <thread>
#include <vector>
-#include "Common/CommonPaths.h"
-#include "Common/CommonTypes.h"
-#include "Common/FileUtil.h"
+#include "Common/StringUtil.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<std::string> binary_to_restart;
- std::optional<DWORD> parent_pid;
- std::optional<std::string> log_file;
-};
-
std::vector<std::string> CommandLineToUtf8Argv(PCWSTR command_line)
{
int nargs;
@@ -50,75 +32,6 @@ std::vector<std::string> CommandLineToUtf8Argv(PCWSTR command_line)
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");
- 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<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.
- 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)
@@ -132,58 +45,29 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine
return 1;
}
- std::optional<Options> maybe_opts = ParseCommandLine(pCmdLine);
- if (!maybe_opts)
- return 1;
- Options opts = std::move(*maybe_opts);
-
+ // Test for write permissions
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());
+ FILE* test_fh = fopen("Updater.log", "w");
- 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 (test_fh == nullptr)
+ need_admin = true;
+ else
+ fclose(test_fh);
if (need_admin)
{
if (IsUserAnAdmin())
{
- FatalError("Failed to write to directory despite administrator priviliges.");
+ MessageBox(nullptr, L"Failed to write to directory despite administrator priviliges.",
+ L"Error", MB_ICONERROR);
return 1;
}
wchar_t path[MAX_PATH];
if (GetModuleFileName(hInstance, path, sizeof(path)) == 0)
{
- FatalError("Failed to get updater filename.");
+ MessageBox(nullptr, L"Failed to get updater filename.", L"Error", MB_ICONERROR);
return 1;
}
@@ -192,68 +76,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine
return 0;
}
- std::thread thread(UI::MessageLoop);
- thread.detach();
-
- UI::SetDescription("Fetching and parsing manifests...");
-
- Manifest this_manifest, next_manifest;
- {
- std::optional<Manifest> 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<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);
-
- 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();
+ std::vector<std::string> args = CommandLineToUtf8Argv(pCmdLine);
- return !ok;
+ return RunUpdater(args) ? 0 : 1;
}
diff --git a/Source/Core/Updater/Updater.vcxproj b/Source/Core/Updater/Updater.vcxproj
index cb6085f967..93e1c7ea72 100644
--- a/Source/Core/Updater/Updater.vcxproj
+++ b/Source/Core/Updater/Updater.vcxproj
@@ -42,9 +42,6 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
- <ProjectReference Include="..\..\..\Externals\cpp-optparse\cpp-optparse.vcxproj">
- <Project>{c636d9d1-82fe-42b5-9987-63b7d4836341}</Project>
- </ProjectReference>
<ProjectReference Include="..\Common\Common.vcxproj">
<Project>{2e6c348c-c75c-4d94-8d1e-9c1fcbf3efe4}</Project>
</ProjectReference>
diff --git a/Source/Core/Updater/WinUI.cpp b/Source/Core/Updater/WinUI.cpp
index b66a8fc92c..7f9a951bef 100644
--- a/Source/Core/Updater/WinUI.cpp
+++ b/Source/Core/Updater/WinUI.cpp
@@ -5,10 +5,12 @@
#include "UpdaterCommon/UI.h"
#include <string>
+#include <thread>
#include <Windows.h>
#include <CommCtrl.h>
#include <ShObjIdl.h>
+#include <shellapi.h>
#include "Common/Flag.h"
#include "Common/StringUtil.h"
@@ -39,7 +41,7 @@ constexpr int PADDING_HEIGHT = 5;
namespace UI
{
-bool Init()
+bool InitWindow()
{
InitCommonControls();
@@ -200,7 +202,7 @@ void MessageLoop()
request_stop.Clear();
running.Set();
- if (!Init())
+ if (!InitWindow())
{
running.Clear();
MessageBox(nullptr, L"Window init failed!", L"", MB_ICONERROR);
@@ -224,6 +226,12 @@ void MessageLoop()
Destroy();
}
+void Init()
+{
+ std::thread thread(MessageLoop);
+ thread.detach();
+}
+
void Stop()
{
if (!running.IsSet())
@@ -235,4 +243,29 @@ void Stop()
{
}
}
+
+void LaunchApplication(std::string path)
+{
+ // Hack: Launching the updater over the explorer ensures that admin priviliges are dropped. Why?
+ // Ask Microsoft.
+ ShellExecuteW(nullptr, nullptr, L"explorer.exe", UTF8ToUTF16(path).c_str(), nullptr, SW_SHOW);
+}
+
+void Sleep(int sleep)
+{
+ ::Sleep(sleep * 1000);
+}
+
+void WaitForPID(u32 pid)
+{
+ HANDLE parent_handle = OpenProcess(SYNCHRONIZE, FALSE, static_cast<DWORD>(pid));
+ WaitForSingleObject(parent_handle, INFINITE);
+ CloseHandle(parent_handle);
+}
+
+void SetVisible(bool visible)
+{
+ ShowWindow(window_handle, visible ? SW_SHOW : SW_HIDE);
+}
+
}; // namespace UI
diff --git a/Source/Core/UpdaterCommon/CMakeLists.txt b/Source/Core/UpdaterCommon/CMakeLists.txt
index c789681ef4..8a98814e4d 100644
--- a/Source/Core/UpdaterCommon/CMakeLists.txt
+++ b/Source/Core/UpdaterCommon/CMakeLists.txt
@@ -5,4 +5,5 @@ target_link_libraries(updatercommon PRIVATE
uicommon
mbedtls
z
- ed25519) \ No newline at end of file
+ ed25519
+ cpp-optparse) \ No newline at end of file
diff --git a/Source/Core/UpdaterCommon/UI.h b/Source/Core/UpdaterCommon/UI.h
index f542d02da0..0d4d606f37 100644
--- a/Source/Core/UpdaterCommon/UI.h
+++ b/Source/Core/UpdaterCommon/UI.h
@@ -6,11 +6,11 @@
#include <string>
+#include "Common/CommonTypes.h"
+
namespace UI
{
-void MessageLoop();
void Error(const std::string& text);
-void Stop();
void SetDescription(const std::string& text);
@@ -23,4 +23,11 @@ void ResetCurrentProgress();
void SetCurrentProgress(int current, int total);
void SetVisible(bool visible);
+
+void Stop();
+
+void Init();
+void Sleep(int seconds);
+void WaitForPID(u32 pid);
+void LaunchApplication(std::string path);
} // namespace UI
diff --git a/Source/Core/UpdaterCommon/UpdaterCommon.cpp b/Source/Core/UpdaterCommon/UpdaterCommon.cpp
index 456576d5e7..77fb317040 100644
--- a/Source/Core/UpdaterCommon/UpdaterCommon.cpp
+++ b/Source/Core/UpdaterCommon/UpdaterCommon.cpp
@@ -5,6 +5,9 @@
#include "UpdaterCommon/UpdaterCommon.h"
#include <array>
+#include <optional>
+
+#include <OptionParser.h>
#include <ed25519/ed25519.h>
#include <mbedtls/base64.h>
#include <mbedtls/sha256.h>
@@ -21,6 +24,8 @@
#include <sys/types.h>
#endif
+namespace
+{
// Where to log updater output.
FILE* log_fp = stderr;
@@ -31,13 +36,48 @@ const std::array<u8, 32> UPDATE_PUB_KEY = {
const char UPDATE_TEMP_DIR[] = "TempUpdate";
-static bool ProgressCallback(double total, double now, double, double)
+struct Manifest
+{
+ using Filename = std::string;
+ using Hash = std::array<u8, 16>;
+ std::map<Filename, Hash> entries;
+};
+
+// Represent the operations to be performed by the updater.
+struct TodoList
+{
+ struct DownloadOp
+ {
+ Manifest::Filename filename;
+ Manifest::Hash hash;
+ };
+ std::vector<DownloadOp> to_download;
+
+ 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;
+};
+
+bool ProgressCallback(double total, double now, double, double)
{
UI::SetCurrentProgress(static_cast<int>(now), static_cast<int>(total));
return true;
}
-static std::string HexEncode(const u8* buffer, size_t size)
+std::string HexEncode(const u8* buffer, size_t size)
{
std::string out(size * 2, '\0');
@@ -50,7 +90,7 @@ static std::string HexEncode(const u8* buffer, size_t size)
return out;
}
-static bool HexDecode(const std::string& hex, u8* buffer, size_t size)
+bool HexDecode(const std::string& hex, u8* buffer, size_t size)
{
if (hex.size() != size * 2)
return false;
@@ -79,7 +119,7 @@ static bool HexDecode(const std::string& hex, u8* buffer, size_t size)
return true;
}
-static std::optional<std::string> GzipInflate(const std::string& data)
+std::optional<std::string> GzipInflate(const std::string& data)
{
z_stream zstrm;
zstrm.zalloc = nullptr;
@@ -115,7 +155,7 @@ static std::optional<std::string> GzipInflate(const std::string& data)
return out;
}
-static Manifest::Hash ComputeHash(const std::string& contents)
+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);
@@ -125,7 +165,7 @@ static Manifest::Hash ComputeHash(const std::string& contents)
return out;
}
-static bool VerifySignature(const std::string& data, const std::string& b64_signature)
+bool VerifySignature(const std::string& data, const std::string& b64_signature)
{
u8 signature[64]; // ed25519 sig size.
size_t sig_size;
@@ -173,8 +213,8 @@ void TodoList::Log() const
}
}
-static bool DownloadContent(const std::vector<TodoList::DownloadOp>& to_download,
- const std::string& content_base_url, const std::string& temp_path)
+bool DownloadContent(const std::vector<TodoList::DownloadOp>& to_download,
+ const std::string& content_base_url, const std::string& temp_path)
{
Common::HttpRequest req(std::chrono::seconds(30), ProgressCallback);
@@ -314,7 +354,7 @@ void CleanUpTempDir(const std::string& temp_dir, const TodoList& todo)
File::DeleteDir(temp_dir);
}
-static bool BackupFile(const std::string& path)
+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());
@@ -326,8 +366,8 @@ static bool BackupFile(const std::string& path)
return true;
}
-static bool DeleteObsoleteFiles(const std::vector<TodoList::DeleteOp>& to_delete,
- const std::string& install_base_path)
+bool DeleteObsoleteFiles(const std::vector<TodoList::DeleteOp>& to_delete,
+ const std::string& install_base_path)
{
for (const auto& op : to_delete)
{
@@ -359,8 +399,8 @@ static bool DeleteObsoleteFiles(const std::vector<TodoList::DeleteOp>& to_delete
return true;
}
-static bool UpdateFiles(const std::vector<TodoList::UpdateOp>& to_update,
- const std::string& install_base_path, const std::string& temp_path)
+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)
{
@@ -459,7 +499,7 @@ void FatalError(const std::string& message)
UI::Stop();
}
-static std::optional<Manifest> ParseManifest(const std::string& manifest)
+std::optional<Manifest> ParseManifest(const std::string& manifest)
{
Manifest parsed;
size_t pos = 0;
@@ -550,3 +590,193 @@ std::optional<Manifest> FetchAndParseManifest(const std::string& url)
return ParseManifest(decompressed);
}
+
+struct Options
+{
+ std::string this_manifest_url;
+ std::string next_manifest_url;
+ std::string content_store_url;
+ std::string install_base_path;
+ std::optional<std::string> binary_to_restart;
+ std::optional<u32> parent_pid;
+ std::optional<std::string> log_file;
+};
+
+std::optional<Options> ParseCommandLine(std::vector<std::string>& args)
+{
+ using optparse::OptionParser;
+
+ OptionParser parser =
+ OptionParser().prog("Dolphin Updater").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");
+
+ optparse::Values options = parser.parse_args(args);
+
+ 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.
+ if (options.is_set("binary-to-restart"))
+ opts.binary_to_restart = options["binary-to-restart"];
+ if (options.is_set("parent-pid"))
+ opts.parent_pid = static_cast<u32>(options.get("parent-pid"));
+ if (options.is_set("log-file"))
+ opts.log_file = options["log-file"];
+
+ return opts;
+}
+}; // namespace
+
+bool RunUpdater(std::vector<std::string> args)
+{
+ std::optional<Options> maybe_opts = ParseCommandLine(args);
+
+ if (!maybe_opts)
+ {
+ return false;
+ }
+
+ UI::Init();
+
+ Options opts = std::move(*maybe_opts);
+
+ if (opts.log_file)
+ {
+ log_fp = fopen(opts.log_file.value().c_str(), "w");
+ if (!log_fp)
+ log_fp = stderr;
+ 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 false;
+ }
+
+ if (opts.parent_pid)
+ {
+ UI::SetDescription("Waiting for Dolphin to quit...");
+
+ fprintf(log_fp, "Waiting for parent PID %d to complete...\n", *opts.parent_pid);
+
+ auto pid = opts.parent_pid.value();
+
+ UI::WaitForPID(static_cast<u32>(pid));
+
+ fprintf(log_fp, "Completed! Proceeding with update.\n");
+ }
+
+ UI::SetVisible(true);
+
+ UI::SetDescription("Fetching and parsing manifests...");
+
+ Manifest this_manifest, next_manifest;
+ {
+ std::optional<Manifest> maybe_manifest = FetchAndParseManifest(opts.this_manifest_url);
+ if (!maybe_manifest)
+ {
+ FatalError("Could not fetch current manifest. Aborting.");
+ return false;
+ }
+ this_manifest = std::move(*maybe_manifest);
+
+ maybe_manifest = FetchAndParseManifest(opts.next_manifest_url);
+ if (!maybe_manifest)
+ {
+ FatalError("Could not fetch next manifest. Aborting.");
+ return false;
+ }
+ next_manifest = std::move(*maybe_manifest);
+ }
+
+ UI::SetDescription("Computing what to do...");
+
+ 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 false;
+ 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);
+ return false;
+ }
+
+ UI::ResetCurrentProgress();
+ UI::ResetTotalProgress();
+ UI::SetCurrentMarquee(false);
+ UI::SetTotalMarquee(false);
+ UI::SetCurrentProgress(1, 1);
+ UI::SetTotalProgress(1, 1);
+ UI::SetDescription("Done!");
+
+ // Let the user process that we are done.
+ UI::Sleep(1);
+
+ if (opts.binary_to_restart)
+ {
+ UI::LaunchApplication(opts.binary_to_restart.value());
+ }
+
+ UI::Stop();
+
+ return true;
+}
diff --git a/Source/Core/UpdaterCommon/UpdaterCommon.h b/Source/Core/UpdaterCommon/UpdaterCommon.h
index fb63994d80..53f37c2424 100644
--- a/Source/Core/UpdaterCommon/UpdaterCommon.h
+++ b/Source/Core/UpdaterCommon/UpdaterCommon.h
@@ -13,48 +13,4 @@
#include "Common/CommonTypes.h"
-extern FILE* log_fp;
-
-struct Manifest
-{
- using Filename = std::string;
- using Hash = std::array<u8, 16>;
- std::map<Filename, Hash> entries;
-};
-
-// Represent the operations to be performed by the updater.
-struct TodoList
-{
- struct DownloadOp
- {
- Manifest::Filename filename;
- Manifest::Hash hash;
- };
- std::vector<DownloadOp> to_download;
-
- 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;
-};
-
-void FatalError(const std::string& message);
-std::optional<Manifest> FetchAndParseManifest(const std::string& url);
-TodoList ComputeActionsToDo(Manifest this_manifest, Manifest next_manifest);
-std::optional<std::string> FindOrCreateTempDir(const std::string& base_path);
-void CleanUpTempDir(const std::string& temp_dir, const TodoList& todo);
-bool PerformUpdate(const TodoList& todo, const std::string& install_base_path,
- const std::string& content_base_url, const std::string& temp_path);
-void FlushLog();
+bool RunUpdater(std::vector<std::string> args);
diff --git a/Source/Core/UpdaterCommon/UpdaterCommon.vcxproj b/Source/Core/UpdaterCommon/UpdaterCommon.vcxproj
index 191098f15f..77fbaf069c 100644
--- a/Source/Core/UpdaterCommon/UpdaterCommon.vcxproj
+++ b/Source/Core/UpdaterCommon/UpdaterCommon.vcxproj
@@ -55,6 +55,9 @@
<ProjectReference Include="..\..\..\Externals\zlib\zlib.vcxproj">
<Project>{ff213b23-2c26-4214-9f88-85271e557e87}</Project>
</ProjectReference>
+ <ProjectReference Include="..\..\..\Externals\cpp-optparse\cpp-optparse.vcxproj">
+ <Project>{c636d9d1-82fe-42b5-9987-63b7d4836341}</Project>
+ </ProjectReference>
</ItemGroup>
<ItemGroup>
<ClCompile Include="UpdaterCommon.cpp" />