From dc7d0c21a08589712018974e001b8e40206476cc Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Thu, 5 Nov 2026 04:38:51 +0100 Subject: [PATCH] Remove the updaters UpdaterCommon, WinUpdater, MacUpdater and UICommon's AutoUpdate only served the standalone Dolphin frontends; RetroArch updates the core. Also drop the AutoUpdate settings, the update-track build string and the u32 WindowsRegistry::ReadValue instantiation only WinUpdater used. --- Makefile.common | 3 +- Source/Core/Common/Version.cpp | 6 - Source/Core/Common/Version.h | 1 - Source/Core/Common/WindowsRegistry.cpp | 5 +- Source/Core/Common/build_info.txt.in | 23 - Source/Core/Common/scmrev.h.in | 1 - Source/Core/Core/Config/MainSettings.cpp | 7 - Source/Core/Core/Config/MainSettings.h | 5 - Source/Core/Core/DolphinAnalytics.cpp | 3 - Source/Core/MacUpdater/AppDelegate.h | 8 - Source/Core/MacUpdater/AppDelegate.mm | 40 - Source/Core/MacUpdater/Info.plist.in | 30 - Source/Core/MacUpdater/MacUI.mm | 197 ----- Source/Core/MacUpdater/Main.storyboard | 109 --- Source/Core/MacUpdater/ViewController.h | 20 - Source/Core/MacUpdater/ViewController.m | 51 -- Source/Core/MacUpdater/main.m | 22 - Source/Core/UICommon/AutoUpdate.cpp | 340 --------- Source/Core/UICommon/AutoUpdate.h | 52 -- Source/Core/UpdaterCommon/Platform.h | 19 - Source/Core/UpdaterCommon/UI.h | 34 - Source/Core/UpdaterCommon/UpdaterCommon.cpp | 794 -------------------- Source/Core/UpdaterCommon/UpdaterCommon.h | 54 -- Source/Core/WinUpdater/Main.cpp | 67 -- Source/Core/WinUpdater/Platform.cpp | 330 -------- Source/Core/WinUpdater/Updater.exe.manifest | 33 - Source/Core/WinUpdater/WinUI.cpp | 308 -------- Tools/test-updater.py | 201 ----- 28 files changed, 3 insertions(+), 2760 deletions(-) delete mode 100644 Source/Core/Common/build_info.txt.in delete mode 100644 Source/Core/MacUpdater/AppDelegate.h delete mode 100644 Source/Core/MacUpdater/AppDelegate.mm delete mode 100644 Source/Core/MacUpdater/Info.plist.in delete mode 100644 Source/Core/MacUpdater/MacUI.mm delete mode 100644 Source/Core/MacUpdater/Main.storyboard delete mode 100644 Source/Core/MacUpdater/ViewController.h delete mode 100644 Source/Core/MacUpdater/ViewController.m delete mode 100644 Source/Core/MacUpdater/main.m delete mode 100644 Source/Core/UICommon/AutoUpdate.cpp delete mode 100644 Source/Core/UICommon/AutoUpdate.h delete mode 100644 Source/Core/UpdaterCommon/Platform.h delete mode 100644 Source/Core/UpdaterCommon/UI.h delete mode 100644 Source/Core/UpdaterCommon/UpdaterCommon.cpp delete mode 100644 Source/Core/UpdaterCommon/UpdaterCommon.h delete mode 100644 Source/Core/WinUpdater/Main.cpp delete mode 100644 Source/Core/WinUpdater/Platform.cpp delete mode 100644 Source/Core/WinUpdater/Updater.exe.manifest delete mode 100644 Source/Core/WinUpdater/WinUI.cpp delete mode 100644 Tools/test-updater.py diff --git a/Makefile.common b/Makefile.common index e7f34866af..725f70dcce 100644 --- a/Makefile.common +++ b/Makefile.common @@ -83,8 +83,7 @@ SCMREV_STATUS := $(shell \ '$(HASH)define SCM_DESC_STR "$(SCM_DESC)"' \ '$(HASH)define SCM_BRANCH_STR "$(SCM_BRANCH)"' \ '$(HASH)define SCM_COMMITS_AHEAD_MASTER $(SCM_AHEAD)' \ - '$(HASH)define SCM_DISTRIBUTOR_STR "None"' \ - '$(HASH)define SCM_UPDATE_TRACK_STR ""' > $(SCMREV_H).tmp; \ + '$(HASH)define SCM_DISTRIBUTOR_STR "None"' > $(SCMREV_H).tmp; \ cmp -s $(SCMREV_H).tmp $(SCMREV_H) || mv -f $(SCMREV_H).tmp $(SCMREV_H); \ rm -f $(SCMREV_H).tmp) diff --git a/Source/Core/Common/Version.cpp b/Source/Core/Common/Version.cpp index dcac646e3a..7c203436f3 100644 --- a/Source/Core/Common/Version.cpp +++ b/Source/Core/Common/Version.cpp @@ -71,12 +71,6 @@ const std::string& GetScmDistributorStr() return scm_distributor_str; } -const std::string& GetScmUpdateTrackStr() -{ - static const std::string scm_update_track_str = SCM_UPDATE_TRACK_STR; - return scm_update_track_str; -} - const std::string& GetNetplayDolphinVer() { #ifdef _WIN32 diff --git a/Source/Core/Common/Version.h b/Source/Core/Common/Version.h index 6a8f29923c..38a7442610 100644 --- a/Source/Core/Common/Version.h +++ b/Source/Core/Common/Version.h @@ -14,7 +14,6 @@ const std::string& GetScmRevStr(); const std::string& GetScmRevGitStr(); const std::string& GetUserAgentStr(); const std::string& GetScmDistributorStr(); -const std::string& GetScmUpdateTrackStr(); const std::string& GetNetplayDolphinVer(); int GetScmCommitsAheadMaster(); } // namespace Common diff --git a/Source/Core/Common/WindowsRegistry.cpp b/Source/Core/Common/WindowsRegistry.cpp index e95bd2da0c..11eb81ef1f 100644 --- a/Source/Core/Common/WindowsRegistry.cpp +++ b/Source/Core/Common/WindowsRegistry.cpp @@ -46,9 +46,8 @@ bool ReadValue(std::string* value, const std::string& subkey, const std::string& return true; } -// These explicit instantiations are needed to prevent linker errors when calling -// ReadValue in WinUpdater/Platform.cpp (for u32) and ArmCPUDetect.cpp (for u64) -template bool ReadValue(u32* value, const std::string& subkey, const std::string& name); +// This explicit instantiation is needed to prevent linker errors when calling +// ReadValue in ArmCPUDetect.cpp (for u64) template bool ReadValue(u64* value, const std::string& subkey, const std::string& name); OSVERSIONINFOW GetOSVersion() diff --git a/Source/Core/Common/build_info.txt.in b/Source/Core/Common/build_info.txt.in deleted file mode 100644 index f19c120dc6..0000000000 --- a/Source/Core/Common/build_info.txt.in +++ /dev/null @@ -1,23 +0,0 @@ -// Indicate the minimum OS version required for the binary to run properly. -// Updater will fail the update if the user does not meet this requirement. -OSMinimumVersionWin10=10.0.18362.0 -OSMinimumVersionWin11=10.0.22000.0 - -// This is the runtime which was compiled against - providing a way for Updater to detect if update -// is needed before executing this binary. Note that, annoyingly, the version in environment -// variables does not match the "real" version. Consider: -// VersionInfo : File: C:\Program Files\Microsoft Visual Studio\18\Community\VC\Redist\MSVC\14.51.36231\vc_redist.x64.exe -// InternalName: setup -// OriginalFilename: VC_redist.x64.exe -// FileVersion: 14.51.36247.0 -// FileDescription: Microsoft Visual C++ v14 Redistributable (x64) - 14.51.36247 -// Product: Microsoft Visual C++ v14 Redistributable (x64) - 14.51.36247 -// ProductVersion: 14.51.36247.0 -// Whereas the environment variables look like: -// VCToolsInstallDir=C:\Program Files\Microsoft Visual Studio\18\Community\VC\Tools\MSVC\14.51.36231\ -// VCToolsRedistDir=C:\Program Files\Microsoft Visual Studio\18\Community\VC\Redist\MSVC\14.51.36231\ -// VCToolsVersion=14.51.36231 -// We're really looking for "14.51.36247.00" (because that's what will appear in the registry once -// installed), NOT the other values! -VCToolsVersion=${VC_TOOLS_VERSION} -VCToolsUpdateURL=https://aka.ms/vc14/vc_redist.x64.exe diff --git a/Source/Core/Common/scmrev.h.in b/Source/Core/Common/scmrev.h.in index 42aed93c41..db4f37b7e9 100644 --- a/Source/Core/Common/scmrev.h.in +++ b/Source/Core/Common/scmrev.h.in @@ -3,4 +3,3 @@ #define SCM_BRANCH_STR "${DOLPHIN_WC_BRANCH}" #define SCM_COMMITS_AHEAD_MASTER ${DOLPHIN_WC_COMMITS_AHEAD_MASTER} #define SCM_DISTRIBUTOR_STR "${DISTRIBUTOR}" -#define SCM_UPDATE_TRACK_STR "${DOLPHIN_DEFAULT_UPDATE_TRACK}" diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 424db24e76..6ed63682c8 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -509,13 +509,6 @@ const Info MAIN_FIFOPLAYER_LOOP_REPLAY{{System::Main, "FifoPlayer", "LoopR const Info MAIN_FIFOPLAYER_EARLY_MEMORY_UPDATES{ {System::Main, "FifoPlayer", "EarlyMemoryUpdates"}, false}; -// Main.AutoUpdate - -const Info MAIN_AUTOUPDATE_UPDATE_TRACK{{System::Main, "AutoUpdate", "UpdateTrack"}, - Common::GetScmUpdateTrackStr()}; -const Info MAIN_AUTOUPDATE_HASH_OVERRIDE{{System::Main, "AutoUpdate", "HashOverride"}, - ""}; - // Main.Movie const Info MAIN_MOVIE_PAUSE_MOVIE{{System::Main, "Movie", "PauseMovie"}, false}; diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 25a15ee178..3926d3895b 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -323,11 +323,6 @@ extern const Info MAIN_GAMELIST_COLUMN_TAGS; extern const Info MAIN_FIFOPLAYER_LOOP_REPLAY; extern const Info MAIN_FIFOPLAYER_EARLY_MEMORY_UPDATES; -// Main.AutoUpdate - -extern const Info MAIN_AUTOUPDATE_UPDATE_TRACK; -extern const Info MAIN_AUTOUPDATE_HASH_OVERRIDE; - // Main.Movie extern const Info MAIN_MOVIE_PAUSE_MOVIE; diff --git a/Source/Core/Core/DolphinAnalytics.cpp b/Source/Core/Core/DolphinAnalytics.cpp index 72134f3da3..4e122ea497 100644 --- a/Source/Core/Core/DolphinAnalytics.cpp +++ b/Source/Core/Core/DolphinAnalytics.cpp @@ -290,9 +290,6 @@ void DolphinAnalytics::MakeBaseBuilder() builder.AddData("version-branch", Common::GetScmBranchStr()); builder.AddData("version-dist", Common::GetScmDistributorStr()); - // Auto-Update information. - builder.AddData("update-track", Config::Get(Config::MAIN_AUTOUPDATE_UPDATE_TRACK)); - // CPU information. builder.AddData("cpu-summary", cpu_info.Summarize()); diff --git a/Source/Core/MacUpdater/AppDelegate.h b/Source/Core/MacUpdater/AppDelegate.h deleted file mode 100644 index e30f812d8f..0000000000 --- a/Source/Core/MacUpdater/AppDelegate.h +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2019 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#import - -@interface AppDelegate : NSObject - -@end diff --git a/Source/Core/MacUpdater/AppDelegate.mm b/Source/Core/MacUpdater/AppDelegate.mm deleted file mode 100644 index 03880ab7cb..0000000000 --- a/Source/Core/MacUpdater/AppDelegate.mm +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2019 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#import "AppDelegate.h" - -#include "UpdaterCommon/UpdaterCommon.h" - -#include -#include -#include - - -@interface AppDelegate () - -@end - -@implementation AppDelegate - -- (void)applicationDidFinishLaunching:(NSNotification*)aNotification -{ - NSArray* arguments = [[NSProcessInfo processInfo] arguments]; - - __block std::vector args; - [arguments - enumerateObjectsUsingBlock:^(NSString* _Nonnull obj, NSUInteger idx, BOOL* _Nonnull stop) { - args.push_back(std::string([obj UTF8String])); - }]; - - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul); - dispatch_async(queue, ^{ - RunUpdater(args); - [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; - }); -} - -- (void)applicationWillTerminate:(NSNotification*)aNotification -{ -} - -@end diff --git a/Source/Core/MacUpdater/Info.plist.in b/Source/Core/MacUpdater/Info.plist.in deleted file mode 100644 index 22872c858f..0000000000 --- a/Source/Core/MacUpdater/Info.plist.in +++ /dev/null @@ -1,30 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - Dolphin Updater - CFBundleIconFile - Dolphin.icns - CFBundleIdentifier - org.dolphin-emu.updater - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Dolphin Updater - CFBundlePackageType - APPL - LSMinimumSystemVersion - ${CMAKE_OSX_DEPLOYMENT_TARGET} - NSHumanReadableCopyright - Licensed under GPL version 2 or later (GPLv2+) - NSMainStoryboardFile - Main - NSPrincipalClass - NSApplication - LSUIElement - - - diff --git a/Source/Core/MacUpdater/MacUI.mm b/Source/Core/MacUpdater/MacUI.mm deleted file mode 100644 index b52da3182f..0000000000 --- a/Source/Core/MacUpdater/MacUI.mm +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2019 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "MacUpdater/ViewController.h" - -#include "UpdaterCommon/Platform.h" -#include "UpdaterCommon/UI.h" -#include "UpdaterCommon/UpdaterCommon.h" - -#include -#include - -#include - -// When we call from the main thread, we are not allowed to use -// dispatch_sync(dispatch_get_main_queue() as it will cause crashes) To prevent this check if we're -// already on the main thread first -void run_on_main(std::function fnc) -{ - if (![NSThread isMainThread]) - { - dispatch_sync(dispatch_get_main_queue(), ^{ - fnc(); - }); - } - else - { - fnc(); - } -} - -NSWindow* GetWindow() -{ - return [[[NSApplication sharedApplication] windows] objectAtIndex:0]; -} - -ViewController* GetView() -{ - return (ViewController*)GetWindow().contentViewController; -} - -void UI::Error(const std::string& text) -{ - run_on_main([&] { - NSAlert* alert = [[[NSAlert alloc] init] autorelease]; - - [alert setMessageText:@"Fatal error"]; - [alert setInformativeText:[NSString stringWithCString:text.c_str() - encoding:NSUTF8StringEncoding]]; - [alert setAlertStyle:NSAlertStyleCritical]; - - [alert beginSheetModalForWindow:GetWindow() - completionHandler:^(NSModalResponse) { - [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; - }]; - }); -} - -void UI::SetVisible(bool visible) -{ - run_on_main([&] { - if (visible) - { - [NSApp unhide:nil]; - [NSApp activateIgnoringOtherApps:YES]; - } - else - { - [NSApp hide:nil]; - } - }); -} - -void UI::SetDescription(const std::string& text) -{ - run_on_main([&] { - [GetView() SetDescription:[NSString stringWithCString:text.c_str() - encoding:NSUTF8StringEncoding]]; - }); -} - -void UI::SetTotalMarquee(bool marquee) -{ - run_on_main([marquee] { [GetView() SetTotalMarquee:marquee]; }); -} - -void UI::SetCurrentMarquee(bool marquee) -{ - run_on_main([&] { [GetView() SetCurrentMarquee:marquee]; }); -} - -void UI::ResetTotalProgress() -{ - run_on_main([] { SetTotalProgress(0, 1); }); -} - -void UI::ResetCurrentProgress() -{ - run_on_main([] { SetCurrentProgress(0, 1); }); -} - -void UI::SetCurrentProgress(int current, int total) -{ - run_on_main([&] { [GetView() SetCurrentProgress:(double)current total:(double)total]; }); -} - -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(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]]]; -} - -void UI::Stop() -{ - run_on_main([] { [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; }); -} - -// Stub. Only needed on Windows -void UI::Init() -{ -} - -// test-updater.py only works on Windows. -bool UI::IsTestMode() -{ - return false; -} - -bool Platform::VersionCheck(const std::vector& to_update, - const std::string& install_base_path, const std::string& temp_dir) -{ - const auto op_it = std::ranges::find(to_update, "Dolphin.app/Contents/Info.plist", - &TodoList::UpdateOp::filename); - if (op_it == to_update.cend()) - return true; - - const auto op = *op_it; - std::string plist_path = temp_dir + "/" + HexEncode(op.new_hash.data(), op.new_hash.size()); - - NSData* data = [NSData dataWithContentsOfFile:[NSString stringWithCString:plist_path.c_str()]]; - if (!data) - { - LogToFile("Failed to read %s, skipping platform version check.\n", plist_path.c_str()); - return true; - } - - NSError* error = nil; - NSDictionary* info_dict = - [NSPropertyListSerialization propertyListWithData:data - options:NSPropertyListImmutable - format:nil - error:&error]; - if (error) - { - LogToFile("Failed to parse %s, skipping platform version check.\n", plist_path.c_str()); - return true; - } - NSString* min_version_str = info_dict[@"LSMinimumSystemVersion"]; - if (!min_version_str) - { - LogToFile("LSMinimumSystemVersion key missing, skipping platform version check.\n"); - return true; - } - - NSArray* components = [min_version_str componentsSeparatedByString:@"."]; - NSOperatingSystemVersion next_version{ - [components[0] integerValue], [components[1] integerValue], [components[2] integerValue]}; - - LogToFile("Platform version check: next_version=%ld.%ld.%ld\n", (long)next_version.majorVersion, - (long)next_version.minorVersion, (long)next_version.patchVersion); - - if (![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:next_version]) - { - UI::Error("Please update macOS in order to update Dolphin."); - return false; - } - return true; -} diff --git a/Source/Core/MacUpdater/Main.storyboard b/Source/Core/MacUpdater/Main.storyboard deleted file mode 100644 index 2e19147ad7..0000000000 --- a/Source/Core/MacUpdater/Main.storyboard +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Source/Core/MacUpdater/ViewController.h b/Source/Core/MacUpdater/ViewController.h deleted file mode 100644 index 7508182865..0000000000 --- a/Source/Core/MacUpdater/ViewController.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2019 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#import - -@interface ViewController : NSViewController - -@property(assign) IBOutlet NSProgressIndicator* progressCurrent; -@property(assign) IBOutlet NSProgressIndicator* progressTotal; -@property(assign) IBOutlet NSTextField* labelProgress; - -- (void)SetDescription:(NSString*)string; - -- (void)SetTotalMarquee:(bool)marquee; -- (void)SetCurrentMarquee:(bool)marquee; - -- (void)SetTotalProgress:(double)current total:(double)total; -- (void)SetCurrentProgress:(double)current total:(double)total; - -@end diff --git a/Source/Core/MacUpdater/ViewController.m b/Source/Core/MacUpdater/ViewController.m deleted file mode 100644 index 82a0cedf56..0000000000 --- a/Source/Core/MacUpdater/ViewController.m +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2019 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#import "ViewController.h" - -@implementation ViewController - -- (void)viewDidLoad -{ - [super viewDidLoad]; - NSWindow* window = [[self view] window]; - - [window setLevel:kCGMainMenuWindowLevel - 1]; - [window setCollectionBehavior:NSWindowCollectionBehaviorStationary | - NSWindowCollectionBehaviorCanJoinAllSpaces | - NSWindowCollectionBehaviorFullScreenAuxiliary]; -} - -- (void)SetDescription:(NSString*)string -{ - [_labelProgress setStringValue:string]; -} - -- (void)SetTotalMarquee:(bool)marquee -{ - [_progressTotal setIndeterminate:marquee]; -} - -- (void)SetCurrentMarquee:(bool)marquee -{ - [_progressCurrent setIndeterminate:marquee]; -} - -- (void)SetTotalProgress:(double)current total:(double)total -{ - [_progressTotal setMaxValue:total]; - [_progressTotal setDoubleValue:current]; -} - -- (void)SetCurrentProgress:(double)current total:(double)total -{ - [_progressCurrent setMaxValue:total]; - [_progressCurrent setDoubleValue:current]; -} - -- (void)setRepresentedObject:(id)representedObject -{ - [super setRepresentedObject:representedObject]; -} - -@end diff --git a/Source/Core/MacUpdater/main.m b/Source/Core/MacUpdater/main.m deleted file mode 100644 index 62661800b4..0000000000 --- a/Source/Core/MacUpdater/main.m +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2019 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - - -int main(int argc, const char** argv) -{ - if (argc == 1) - { - NSAlert* alert = [[[NSAlert alloc] init] autorelease]; - - [alert setMessageText:@"This updater is not meant to be launched directly."]; - [alert setAlertStyle:NSAlertStyleWarning]; - [alert setInformativeText:@"Configure Auto-Update in Dolphin's settings instead."]; - [alert runModal]; - - return 1; - } - - return NSApplicationMain(argc, argv); -} diff --git a/Source/Core/UICommon/AutoUpdate.cpp b/Source/Core/UICommon/AutoUpdate.cpp deleted file mode 100644 index 494a7e0200..0000000000 --- a/Source/Core/UICommon/AutoUpdate.cpp +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright 2018 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "UICommon/AutoUpdate.h" - -#include -#include -#include - -#include -#include - -#include "Common/HttpRequest.h" -#include "Common/Logging/Log.h" -#include "Common/MsgHandler.h" -#include "Common/ScopeGuard.h" -#include "Common/StringUtil.h" -#include "Common/Version.h" - -#ifdef _WIN32 -#include -#else -#include -#endif - -#ifdef __APPLE__ -#include -#endif - -#if defined(_WIN32) || defined(__APPLE__) -#define OS_SUPPORTS_UPDATER -#include "Common/CommonFuncs.h" -#include "Common/CommonPaths.h" -#include "Common/FileUtil.h" -#endif - - -namespace -{ -std::atomic_bool s_check_in_progress = false; -bool s_update_triggered = false; - -#ifdef __APPLE__ -const char UPDATER_CONTENT_PATH[] = "/Contents/MacOS/Dolphin Updater"; -#endif - -#ifdef OS_SUPPORTS_UPDATER - -const char UPDATER_LOG_FILE[] = "Updater.log"; - -std::string UpdaterPath(bool relocated = false) -{ -#ifdef __APPLE__ - if (relocated) - return File::GetExeDirectory() + DIR_SEP + ".Dolphin Updater.2.app"; - else - return File::GetBundleDirectory() + DIR_SEP + "Contents/Helpers/Dolphin Updater.app"; -#else - return File::GetExeDirectory() + DIR_SEP + "Updater.exe"; -#endif -} - -std::string MakeUpdaterCommandLine(const std::map& flags) -{ -#ifdef __APPLE__ - std::string cmdline = "\"" + UpdaterPath(true) + UPDATER_CONTENT_PATH + "\""; -#else - std::string cmdline = UpdaterPath(); -#endif - - cmdline += " "; - - for (const auto& pair : flags) - { - std::string value = "--" + pair.first + "=" + pair.second; - value = ReplaceAll(value, "\"", "\\\""); // Escape double quotes. - value = "\"" + value + "\" "; - cmdline += value; - } - return cmdline; -} - -void CleanupFromPreviousUpdate() -{ -#ifdef __APPLE__ - // Remove the relocated updater file. - File::DeleteDirRecursively(UpdaterPath(true)); - - // Remove the old (non-embedded) updater app bundle. - // While the update process will delete the files within the old bundle after updating to a - // version with an embedded updater, it won't delete the folder structure of the bundle, so - // we should clean those leftovers up. - File::DeleteDirRecursively(File::GetExeDirectory() + DIR_SEP + "Dolphin Updater.app"); -#endif - - // Updater.log was moved from GetExeDirectory() to GetUserPath(D_LOGS_IDX) in 5.0-14529. - File::Delete(File::GetExeDirectory() + DIR_SEP + "Updater.log", - File::IfAbsentBehavior::NoConsoleWarning); -} -#endif - -// This ignores i18n because most of the text in there (change descriptions) is only going to be -// written in english anyway. -std::string GenerateChangelog(const picojson::array& versions) -{ - std::string changelog; - for (const auto& ver : versions) - { - if (!ver.is()) - continue; - picojson::object ver_obj = ver.get(); - - if (ver_obj["changelog_html"].is()) - { - if (!changelog.empty()) - changelog += "
"; // Vertical spacing. - - // Try to link to the PR if we have this info. Otherwise just show shortrev. - if (ver_obj["pr_url"].is()) - { - changelog += "() + "\">" + - ver_obj["shortrev"].get() + ""; - } - else - { - changelog += ver_obj["shortrev"].get(); - } - const std::string escaped_description = - Common::GetEscapedHtml(ver_obj["short_descr"].get()); - changelog += " by () + "\">" + - ver_obj["author"].get() + " — " + escaped_description; - } - else - { - if (!changelog.empty()) - changelog += "
"; - changelog += "Dolphin " + ver_obj["shortrev"].get() + ""; - changelog += "

" + ver_obj["changelog_html"].get() + "

"; - } - } - return changelog; -} -} // namespace - -bool AutoUpdateChecker::SystemSupportsAutoUpdates() -{ -#if defined(AUTOUPDATE) && defined(OS_SUPPORTS_UPDATER) - return true; -#else - return false; -#endif -} - -static std::string GetPlatformID() -{ -#if defined(_WIN32) -#if defined(_M_ARM_64) - return "win-arm64"; -#else - return "win"; -#endif -#elif defined(__APPLE__) -#if defined(MACOS_UNIVERSAL_BUILD) - return "macos-universal"; -#else - return "macos"; -#endif -#else - return "unknown"; -#endif -} - -static std::string GetUpdateServerUrl() -{ - auto server_url = std::getenv("DOLPHIN_UPDATE_SERVER_URL"); - if (server_url) - return server_url; - return "https://dolphin-emu.org"; -} - -static u32 GetOwnProcessId() -{ -#ifdef _WIN32 - return GetCurrentProcessId(); -#else - return getpid(); -#endif -} - -void AutoUpdateChecker::CheckForUpdate(std::string_view update_track, - std::string_view hash_override, const CheckType check_type) -{ - bool expected_check_in_progress = false; - if (!s_check_in_progress.compare_exchange_strong(expected_check_in_progress, true)) - return; - - Common::ScopeGuard guard([]() { s_check_in_progress.store(false); }); - - if (s_update_triggered) - { - if (check_type == CheckType::Manual) - SuccessAlertFmtT("A Dolphin update is already scheduled for the next time it closes."); - - return; - } - - // Don't bother checking if updates are not supported or not enabled. - if (!SystemSupportsAutoUpdates() || update_track.empty()) - return; - -#ifdef OS_SUPPORTS_UPDATER - CleanupFromPreviousUpdate(); -#endif - - std::string_view version_hash = hash_override.empty() ? Common::GetScmRevGitStr() : hash_override; - std::string url = fmt::format("{}/update/check/v1/{}/{}/{}", GetUpdateServerUrl(), update_track, - version_hash, GetPlatformID()); - - const bool is_manual_check = check_type == CheckType::Manual; - - Common::HttpRequest req{std::chrono::seconds{10}}; - auto resp = req.Get(url); - if (!resp) - { - if (is_manual_check) - CriticalAlertFmtT("Unable to contact update server."); - return; - } - const std::string contents(reinterpret_cast(resp->data()), resp->size()); - INFO_LOG_FMT(COMMON, "Auto-update JSON response: {}", contents); - - picojson::value json; - const std::string err = picojson::parse(json, contents); - if (!err.empty()) - { - CriticalAlertFmtT("Invalid JSON received from auto-update service : {0}", err); - return; - } - picojson::object obj = json.get(); - - if (obj["status"].get() != "outdated") - { - if (is_manual_check) - SuccessAlertFmtT("You are running the latest version available on this update track."); - INFO_LOG_FMT(COMMON, "Auto-update status: we are up to date."); - return; - } - - NewVersionInformation nvi; - nvi.this_manifest_url = obj["old"].get()["manifest"].get(); - nvi.next_manifest_url = obj["new"].get()["manifest"].get(); - nvi.content_store_url = obj["content-store"].get(); - nvi.new_shortrev = obj["new"].get()["name"].get(); - nvi.new_hash = obj["new"].get()["hash"].get(); - - // TODO: generate the HTML changelog from the JSON information. - nvi.changelog_html = GenerateChangelog(obj["changelog"].get()); - - if (std::getenv("DOLPHIN_UPDATE_TEST_DONE")) - { - // We are at end of updater test flow, send a message to server, which will kill us. - req.Get(fmt::format("{}/update-test-done/{}", GetUpdateServerUrl(), GetOwnProcessId())); - } - else - { - OnUpdateAvailable(nvi); - } -} - -void AutoUpdateChecker::TriggerUpdate(const AutoUpdateChecker::NewVersionInformation& info, - const AutoUpdateChecker::RestartMode restart_mode) -{ - // Check to make sure we don't already have an update triggered - if (s_update_triggered) - { - WARN_LOG_FMT(COMMON, "Auto-update: received a redundant trigger request, ignoring"); - return; - } - -#ifdef OS_SUPPORTS_UPDATER - std::map updater_flags; - updater_flags["this-manifest-url"] = info.this_manifest_url; - updater_flags["next-manifest-url"] = info.next_manifest_url; - updater_flags["content-store-url"] = info.content_store_url; - updater_flags["parent-pid"] = std::to_string(GetOwnProcessId()); - updater_flags["install-base-path"] = File::GetExeDirectory(); - updater_flags["log-file"] = File::GetUserPath(D_LOGS_IDX) + UPDATER_LOG_FILE; - - if (restart_mode == RestartMode::RESTART_AFTER_UPDATE) - updater_flags["binary-to-restart"] = File::GetExePath(); - -#ifdef __APPLE__ - // Copy the updater so it can update itself if needed. - const std::string reloc_updater_path = UpdaterPath(true); - if (!File::Copy(UpdaterPath(), reloc_updater_path)) - { - CriticalAlertFmtT("Unable to create updater copy."); - return; - } - if (chmod((reloc_updater_path + UPDATER_CONTENT_PATH).c_str(), 0700) != 0) - { - CriticalAlertFmtT("Unable to set permissions on updater copy."); - return; - } -#endif - - // Run the updater! - std::string command_line = MakeUpdaterCommandLine(updater_flags); - INFO_LOG_FMT(COMMON, "Updater command line: {}", command_line); - -#ifdef _WIN32 - STARTUPINFO sinfo{.cb = sizeof(sinfo)}; - sinfo.dwFlags = STARTF_FORCEOFFFEEDBACK; // No hourglass cursor after starting the process. - PROCESS_INFORMATION pinfo; - if (CreateProcessW(UTF8ToWString(UpdaterPath()).c_str(), UTF8ToWString(command_line).data(), - nullptr, nullptr, FALSE, 0, nullptr, nullptr, &sinfo, &pinfo)) - { - CloseHandle(pinfo.hThread); - CloseHandle(pinfo.hProcess); - s_update_triggered = true; - } - else - { - const std::string error = Common::GetLastErrorString(); - CriticalAlertFmtT("Could not start updater process: {0}", error); - } -#else - if (popen(command_line.c_str(), "r") == nullptr) - { - const std::string error = Common::LastStrerrorString(); - CriticalAlertFmtT("Could not start updater process: {0}", error); - } - else - { - s_update_triggered = true; - } -#endif - -#endif -} diff --git a/Source/Core/UICommon/AutoUpdate.h b/Source/Core/UICommon/AutoUpdate.h deleted file mode 100644 index 997835034e..0000000000 --- a/Source/Core/UICommon/AutoUpdate.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2018 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - - -// This class defines all the logic for Dolphin auto-update checking. UI-specific elements have to -// be defined in a backend specific subclass. -class AutoUpdateChecker -{ -public: - enum class CheckType - { - Automatic, - Manual, - }; - // Initiates a check for updates in the background. Calls the OnUpdateAvailable callback if an - // update is available, does "nothing" otherwise. - void CheckForUpdate(std::string_view update_track, std::string_view hash_override, - CheckType check_type); - - static bool SystemSupportsAutoUpdates(); - - struct NewVersionInformation - { - // Name (5.0-1234) and revision hash of the new version. - std::string new_shortrev; - std::string new_hash; - - // The full changelog in HTML format. - std::string changelog_html; - - // Internals, to be passed to the updater binary. - std::string this_manifest_url; - std::string next_manifest_url; - std::string content_store_url; - }; - - // Starts the updater process, which will wait in the background until the current process exits. - enum class RestartMode - { - NO_RESTART_AFTER_UPDATE = 0, - RESTART_AFTER_UPDATE, - }; - void TriggerUpdate(const NewVersionInformation& info, RestartMode restart_mode); - -protected: - virtual void OnUpdateAvailable(const NewVersionInformation& info) = 0; -}; diff --git a/Source/Core/UpdaterCommon/Platform.h b/Source/Core/UpdaterCommon/Platform.h deleted file mode 100644 index e2580ec694..0000000000 --- a/Source/Core/UpdaterCommon/Platform.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2018 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include - -#include "Common/CommonTypes.h" -#include "Common/StringUtil.h" - -#include "UpdaterCommon/UpdaterCommon.h" - -namespace Platform -{ -bool VersionCheck(const std::vector& to_update, - const std::string& install_base_path, const std::string& temp_dir); -} // namespace Platform diff --git a/Source/Core/UpdaterCommon/UI.h b/Source/Core/UpdaterCommon/UI.h deleted file mode 100644 index 3b441ea568..0000000000 --- a/Source/Core/UpdaterCommon/UI.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include "Common/CommonTypes.h" - -namespace UI -{ -void Error(const std::string& text); - -void SetDescription(const std::string& text); - -void SetTotalMarquee(bool marquee); -void ResetTotalProgress(); -void SetTotalProgress(int current, int total); - -void SetCurrentMarquee(bool marquee); -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); - -bool IsTestMode(); -} // namespace UI diff --git a/Source/Core/UpdaterCommon/UpdaterCommon.cpp b/Source/Core/UpdaterCommon/UpdaterCommon.cpp deleted file mode 100644 index 9f71199918..0000000000 --- a/Source/Core/UpdaterCommon/UpdaterCommon.cpp +++ /dev/null @@ -1,794 +0,0 @@ -// Copyright 2019 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "UpdaterCommon/UpdaterCommon.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include "Common/CommonFuncs.h" -#endif -#include "Common/CommonPaths.h" -#include "Common/FileUtil.h" -#include "Common/HttpRequest.h" -#include "Common/IOFile.h" -#include "Common/ScopeGuard.h" -#include "Common/StringUtil.h" -#include "UpdaterCommon/Platform.h" -#include "UpdaterCommon/UI.h" - -#ifndef _WIN32 -#include -#include -#endif - -#ifdef _WIN32 -#include -#include -#endif - - -// Public key used to verify update manifests. -const std::array UPDATE_PUB_KEY = { - 0x2a, 0xb3, 0xd1, 0xdc, 0x6e, 0xf5, 0x07, 0xf6, 0xa0, 0x6c, 0x7c, 0x54, 0xdf, 0x54, 0xf4, 0x42, - 0x80, 0xa6, 0x28, 0x8b, 0x6d, 0x70, 0x14, 0xb5, 0x4c, 0x34, 0x95, 0x20, 0x4d, 0xd4, 0xd3, 0x5d}; -// The private key for UPDATE_PUB_KEY_TEST is in Tools/test-updater.py -const std::array UPDATE_PUB_KEY_TEST = { - 0x0c, 0x5f, 0xdc, 0xd1, 0x15, 0x71, 0xfb, 0x86, 0x4f, 0x9e, 0x6d, 0xe6, 0x65, 0x39, 0x43, 0xe1, - 0x9e, 0xe0, 0x9b, 0x28, 0xc9, 0x1a, 0x60, 0xb7, 0x67, 0x1c, 0xf3, 0xf6, 0xca, 0x1b, 0xdd, 0x1a}; - -// Where to log updater output. -static File::IOFile log_file; - -void LogToFile(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - - log_file.WriteString(StringFromFormatV(fmt, args)); - log_file.Flush(); - - va_end(args); -} - -static bool ProgressCallback(s64 total, s64 now, s64, s64) -{ - UI::SetCurrentProgress(static_cast(now), static_cast(total)); - 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; -} - -static bool HexDecode(const std::string& hex, u8* buffer, size_t size) -{ - if (hex.size() != size * 2) - return false; - - auto DecodeNibble = [](char c) -> std::optional { - if (c >= '0' && c <= '9') - return static_cast(c - '0'); - else if (c >= 'a' && c <= 'f') - return static_cast(c - 'a' + 10); - else if (c >= 'A' && c <= 'F') - return static_cast(c - 'A' + 10); - else - return {}; - }; - for (size_t i = 0; i < size; ++i) - { - std::optional high = DecodeNibble(hex[2 * i]); - std::optional low = DecodeNibble(hex[2 * i + 1]); - - if (!high || !low) - return false; - - buffer[i] = (*high << 4) | *low; - } - - return true; -} - -static std::optional GzipInflate(const std::string& data) -{ - z_stream zstrm; - zstrm.zalloc = nullptr; - zstrm.zfree = nullptr; - zstrm.opaque = nullptr; - zstrm.avail_in = static_cast(data.size()); - zstrm.next_in = reinterpret_cast(const_cast(data.data())); - - // 16 + MAX_WBITS means gzip. Don't ask me. - inflateInit2(&zstrm, 16 + MAX_WBITS); - - std::string out; - const size_t buf_len = 20 * 1024 * 1024; - auto buffer = std::make_unique(buf_len); - int ret; - - do - { - zstrm.avail_out = buf_len; - zstrm.next_out = reinterpret_cast(buffer.get()); - - ret = inflate(&zstrm, 0); - out.append(buffer.get(), buf_len - zstrm.avail_out); - } while (ret == Z_OK); - - inflateEnd(&zstrm); - - if (ret != Z_STREAM_END) - { - LogToFile("Could not read the data as gzip: error %d.\n", ret); - return {}; - } - - return out; -} - -Manifest::Hash ComputeHash(const std::string& contents) -{ - std::array full; - mbedtls_sha256_ret(reinterpret_cast(contents.data()), contents.size(), full.data(), - false); - - Manifest::Hash out; - std::copy_n(full.begin(), 16, out.begin()); - return out; -} - -static 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(b64_signature.data()), - b64_signature.size()) || - sig_size != sizeof(signature)) - { - LogToFile("Invalid base64: %s\n", b64_signature.c_str()); - return false; - } - - const auto& pub_key = UI::IsTestMode() ? UPDATE_PUB_KEY_TEST : UPDATE_PUB_KEY; - return ed25519_verify(signature, reinterpret_cast(data.data()), data.size(), - pub_key.data()); -} - -static void FlushLog() -{ - log_file.Flush(); - log_file.Close(); -} - -void TodoList::Log() const -{ - if (to_update.size()) - { - LogToFile("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)"; - LogToFile(" - %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()) - { - LogToFile("Deleting:\n"); - for (const auto& op : to_delete) - { - LogToFile(" - %s (%s)\n", op.filename.c_str(), - HexEncode(op.old_hash.data(), op.old_hash.size()).c_str()); - } - } -} - -static bool DownloadContent(std::span to_download, - const std::string& content_base_url, const std::string& temp_path) -{ - Common::HttpRequest req(std::chrono::seconds(30), ProgressCallback); - - UI::SetTotalMarquee(false); - - for (size_t i = 0; i < to_download.size(); i++) - { - UI::SetTotalProgress(static_cast(i + 1), static_cast(to_download.size())); - - auto& download = to_download[i]; - - std::string hash_filename = HexEncode(download.hash.data(), download.hash.size()); - - // File already exists, skipping - if (File::Exists(temp_path + DIR_SEP + hash_filename)) - continue; - - UI::SetDescription(fmt::format("Downloading {}... (File {} of {})", download.filename, i + 1, - to_download.size())); - UI::SetCurrentMarquee(false); - - // 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; - LogToFile("Downloading %s ...\n", url.c_str()); - - auto resp = req.Get(url); - if (!resp) - return false; - - UI::SetCurrentMarquee(true); - UI::SetDescription("Verifying " + download.filename + "..."); - - std::string contents(reinterpret_cast(resp->data()), resp->size()); - std::optional maybe_decompressed = GzipInflate(contents); - if (!maybe_decompressed) - return false; - const std::string decompressed = std::move(*maybe_decompressed); - - // Check that the downloaded contents have the right hash. - Manifest::Hash contents_hash = ComputeHash(decompressed); - if (contents_hash != download.hash) - { - LogToFile("Wrong hash on downloaded content %s.\n", url.c_str()); - return false; - } - - const std::string out = temp_path + DIR_SEP + hash_filename; - if (!File::WriteStringToFile(out, decompressed)) - { - LogToFile("Could not write cache file %s.\n", out.c_str()); - return false; - } - } - return true; -} - -static bool PlatformVersionCheck(const std::vector& to_update, - const std::string& install_base_path, const std::string& temp_dir) -{ - UI::SetDescription("Checking platform..."); - return Platform::VersionCheck(to_update, install_base_path, temp_dir); -} - -static 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.contains(entry.first)) - { - 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 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) - { - TodoList::DownloadOp download; - download.filename = entry.first; - download.hash = entry.second; - - todo.to_download.push_back(std::move(download)); - - 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; -} - -static void CleanUpTempDir(const std::string& temp_dir, const TodoList& todo) -{ - // This is best-effort cleanup, we ignore most errors. - for (const auto& download : todo.to_download) - File::Delete(temp_dir + DIR_SEP + HexEncode(download.hash.data(), download.hash.size())); - File::DeleteDir(temp_dir); -} - -static bool BackupFile(const std::string& path) -{ - std::string backup_path = path + ".bak"; - LogToFile("Backing up existing %s to .bak.\n", path.c_str()); - if (!File::Rename(path, backup_path)) - { - LogToFile("Could not rename %s to %s for backup.\n", path.c_str(), backup_path.c_str()); - return false; - } - return true; -} - -static bool DeleteObsoleteFiles(std::span 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)) - { - LogToFile("File %s is already missing.\n", op.filename.c_str()); - continue; - } - else - { - std::string contents; - if (!File::ReadFileToString(path, contents)) - { - LogToFile("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; -} - -static bool UpdateFiles(std::span to_update, - const std::string& install_base_path, const std::string& temp_path) -{ -#ifdef _WIN32 - const auto self_path = std::filesystem::path(Common::GetModuleName(nullptr).value()); - const auto self_filename = self_path.filename(); -#endif - - for (const auto& op : to_update) - { - std::string path = install_base_path + DIR_SEP + op.filename; - if (!File::CreateFullPath(path)) - { - LogToFile("Could not create directory structure for %s.\n", op.filename.c_str()); - return false; - } - -#ifndef _WIN32 - // TODO: A new updater protocol version is required to properly mark executable files. For - // now, copy executable bits from existing files. This will break for newly added executables. - std::optional permission; -#endif - - if (File::Exists(path)) - { -#ifndef _WIN32 - struct stat file_stats; - - if (lstat(path.c_str(), &file_stats) != 0) - continue; - - if (S_ISLNK(file_stats.st_mode)) - { - LogToFile("%s is symlink, skipping\n", path.c_str()); - continue; - } - - permission = file_stats.st_mode; -#endif - -#ifdef _WIN32 - // If incoming file would overwrite the currently executing file, rename ourself to allow the - // overwrite to complete. Renaming ourself while executing is fine, but deleting ourself is - // rather tricky. The best way to handle that would be to execute the newly-placed Updater.exe - // after entire update has completed, and have it delete our relocated executable. For now we - // just let the relocated file hang around. - // It is enough to match based on filename, don't need File/VolumeId etc. - const bool is_self = op.filename == self_filename; -#else - // On other platforms, the renaming is handled by Dolphin before running the Updater. - const bool is_self = false; -#endif - - std::string contents; - if (!File::ReadFileToString(path, contents)) - { - LogToFile("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) - { - LogToFile("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 || is_self) - { - 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()); - LogToFile("Updating file %s from content %s...\n", op.filename.c_str(), - content_filename.c_str()); -#ifdef __APPLE__ - // macOS caches the code signature of Mach-O executables when they're first loaded. - // Unfortunately, there is a quirk in the kernel with how it handles the cache: if the file is - // simply overwritten, the cache isn't invalidated and the old code signature is used to verify - // the new file. This causes macOS to kill the process with a code signing error. To workaround - // this, we use File::Rename() instead of File::CopyRegularFile(). However, this also means that - // if two files have the same hash, the first file will succeed, but the second file will fail - // because the source file no longer exists. To deal with this, we copy the content file to a - // temporary file and then rename the temporary file to the destination path. - const std::string temporary_file = temp_path + DIR_SEP + "temporary_file"; - if (!File::CopyRegularFile(temp_path + DIR_SEP + content_filename, temporary_file)) - { - LogToFile("Could not copy %s to %s.\n", content_filename.c_str(), temporary_file.c_str()); - return false; - } - - if (!File::Rename(temporary_file, path)) -#else - if (!File::CopyRegularFile(temp_path + DIR_SEP + content_filename, path)) -#endif - { - LogToFile("Could not update file %s.\n", op.filename.c_str()); - return false; - } - -#ifndef _WIN32 - if (permission.has_value() && chmod(path.c_str(), permission.value()) != 0) - return false; -#endif - } - return true; -} - -static bool PerformUpdate(const TodoList& todo, const std::string& install_base_path, - const std::string& content_base_url, const std::string& temp_path) -{ - LogToFile("Starting download step...\n"); - if (!DownloadContent(todo.to_download, content_base_url, temp_path)) - return false; - LogToFile("Download step completed.\n"); - - LogToFile("Starting platform version check step...\n"); - if (!PlatformVersionCheck(todo.to_update, install_base_path, temp_path)) - return false; - LogToFile("Platform version check step completed.\n"); - - LogToFile("Starting update step...\n"); - if (!UpdateFiles(todo.to_update, install_base_path, temp_path)) - return false; - LogToFile("Update step completed.\n"); - - LogToFile("Starting deletion step...\n"); - if (!DeleteObsoleteFiles(todo.to_delete, install_base_path)) - return false; - LogToFile("Deletion step completed.\n"); - - return true; -} - -static void FatalError(const std::string& message) -{ - LogToFile("%s\n", message.c_str()); - - UI::SetVisible(true); - UI::Error(message); -} - -static std::optional 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) - { - LogToFile("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) - { - LogToFile("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) - { - LogToFile("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())) - { - LogToFile("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; -} - -// Not showing a progress bar here because this part is just too quick -static std::optional FetchAndParseManifest(const std::string& url) -{ - Common::HttpRequest http; - - Common::HttpRequest::Response resp = http.Get(url); - if (!resp) - { - LogToFile("Manifest download failed.\n"); - return {}; - } - - std::string contents(reinterpret_cast(resp->data()), resp->size()); - std::optional 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) - { - LogToFile("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 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) - { - LogToFile("Could not verify signature of the manifest.\n"); - return {}; - } - - 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 binary_to_restart; - std::optional parent_pid; - std::optional log_file; -}; - -static std::optional ParseCommandLine(std::vector& 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 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(options.get("parent-pid")); - if (options.is_set("log-file")) - opts.log_file = options["log-file"]; - - return opts; -} - -bool RunUpdater(std::vector args) -{ - std::optional maybe_opts = ParseCommandLine(args); - - if (!maybe_opts) - { - return false; - } - - UI::Init(); - UI::SetVisible(false); - - Common::ScopeGuard ui_guard{[] { UI::Stop(); }}; - Options opts = std::move(*maybe_opts); - - if (opts.log_file) - { - if (!log_file.Open(opts.log_file.value(), "w")) - log_file.SetHandle(stderr); - else - atexit(FlushLog); - } - - LogToFile("Updating from: %s\n", opts.this_manifest_url.c_str()); - LogToFile("Updating to: %s\n", opts.next_manifest_url.c_str()); - LogToFile("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) - { - LogToFile("Waiting for parent PID %d to complete...\n", *opts.parent_pid); - - auto pid = opts.parent_pid.value(); - - UI::WaitForPID(static_cast(pid)); - - LogToFile("Completed! Proceeding with update.\n"); - } - - UI::SetVisible(true); - - 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 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::string temp_dir = File::CreateTempDir(); - if (temp_dir.empty()) - { - FatalError("Could not create temporary directory. Aborting."); - return false; - } - - UI::SetDescription("Performing Update..."); - - bool ok = PerformUpdate(todo, opts.install_base_path, opts.content_store_url, temp_dir); - CleanUpTempDir(temp_dir, todo); - if (!ok) - { - FatalError("Failed to apply the update."); - 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()); - } - - return true; -} diff --git a/Source/Core/UpdaterCommon/UpdaterCommon.h b/Source/Core/UpdaterCommon/UpdaterCommon.h deleted file mode 100644 index 556d4ae3fd..0000000000 --- a/Source/Core/UpdaterCommon/UpdaterCommon.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2019 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "Common/CommonTypes.h" - - -struct Manifest -{ - using Filename = std::string; - using Hash = std::array; - std::map entries; -}; - -// Represent the operations to be performed by the updater. -struct TodoList -{ - struct DownloadOp - { - Manifest::Filename filename; - Manifest::Hash hash{}; - }; - std::vector to_download; - - struct UpdateOp - { - Manifest::Filename filename; - std::optional old_hash; - Manifest::Hash new_hash{}; - }; - std::vector to_update; - - struct DeleteOp - { - Manifest::Filename filename; - Manifest::Hash old_hash{}; - }; - std::vector to_delete; - - void Log() const; -}; - -void LogToFile(const char* fmt, ...); -std::string HexEncode(const u8* buffer, size_t size); -Manifest::Hash ComputeHash(const std::string& contents); -bool RunUpdater(std::vector args); diff --git a/Source/Core/WinUpdater/Main.cpp b/Source/Core/WinUpdater/Main.cpp deleted file mode 100644 index 79bca325b4..0000000000 --- a/Source/Core/WinUpdater/Main.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2018 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include - -#include -#include -#include - -#include "Common/CommonFuncs.h" -#include "Common/StringUtil.h" - -#include "UpdaterCommon/UI.h" -#include "UpdaterCommon/UpdaterCommon.h" - - -int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) -{ - if (lstrlenW(pCmdLine) == 0) - { - MessageBoxW(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; - } - - // Test for write permissions - bool need_admin = false; - - auto path = Common::GetModuleName(hInstance); - if (!path) - { - UI::Error("Failed to get updater filename."); - return 1; - } - - const auto test_fh = ::CreateFileW( - (std::filesystem::path(*path).parent_path() / "directory_writable_check.tmp").c_str(), - GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, - FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, nullptr); - - if (test_fh == INVALID_HANDLE_VALUE) - need_admin = true; - else - CloseHandle(test_fh); - - if (need_admin) - { - if (IsUserAnAdmin()) - { - MessageBox(nullptr, L"Failed to write to directory despite administrator privileges.", - L"Error", MB_ICONERROR); - return 1; - } - - // Relaunch the updater as administrator - ShellExecuteW(nullptr, L"runas", path->c_str(), pCmdLine, nullptr, SW_SHOW); - return 0; - } - - std::vector args = Common::CommandLineToUtf8Argv(pCmdLine); - - return RunUpdater(args) ? 0 : 1; -} diff --git a/Source/Core/WinUpdater/Platform.cpp b/Source/Core/WinUpdater/Platform.cpp deleted file mode 100644 index 69889c6fa7..0000000000 --- a/Source/Core/WinUpdater/Platform.cpp +++ /dev/null @@ -1,330 +0,0 @@ -#include - -#include -#include -#include - -#include "Common/CommonPaths.h" -#include "Common/FileUtil.h" -#include "Common/HttpRequest.h" -#include "Common/IOFile.h" -#include "Common/ScopeGuard.h" -#include "Common/StringUtil.h" -#include "Common/WindowsRegistry.h" - -#include "UpdaterCommon/Platform.h" -#include "UpdaterCommon/UI.h" -#include "UpdaterCommon/UpdaterCommon.h" - -namespace Platform -{ -struct BuildVersion -{ - u32 major{}; - u32 minor{}; - u32 build{}; - auto operator<=>(BuildVersion const& rhs) const = default; - static std::optional from_string(const std::string& str) - { - auto components = SplitString(str, '.'); - // Allow variable number of components (truncating after "build"), but not - // empty. - if (components.size() == 0) - return {}; - BuildVersion version; - if (!TryParse(components[0], &version.major, 10)) - return {}; - if (components.size() > 1 && !TryParse(components[1], &version.minor, 10)) - return {}; - if (components.size() > 2 && !TryParse(components[2], &version.build, 10)) - return {}; - return version; - } -}; - -enum class VersionCheckStatus -{ - NothingToDo, - UpdateOptional, - UpdateRequired, -}; - -struct VersionCheckResult -{ - VersionCheckStatus status{VersionCheckStatus::NothingToDo}; - std::optional current_version{}; - std::optional target_version{}; -}; - -class BuildInfo -{ - using Map = std::map; - -public: - BuildInfo() = default; - BuildInfo(const std::string& content) - { - map = {{"OSMinimumVersionWin10", ""}, - {"OSMinimumVersionWin11", ""}, - {"VCToolsVersion", ""}, - {"VCToolsUpdateURL", ""}}; - Parse(content); - } - - std::optional GetString(const std::string& name) const - { - auto it = map.find(name); - if (it == map.end() || it->second.size() == 0) - return {}; - return it->second; - } - - std::optional GetVersion(const std::string& name) const - { - auto str = GetString(name); - if (!str.has_value()) - return {}; - return BuildVersion::from_string(str.value()); - } - -private: - void Parse(const std::string& content) - { - std::stringstream content_stream(content); - std::string line; - while (std::getline(content_stream, line)) - { - if (line.starts_with("//")) - continue; - const size_t equals_index = line.find('='); - if (equals_index == line.npos) - continue; - auto key = line.substr(0, equals_index); - auto key_it = map.find(key); - if (key_it == map.end()) - continue; - auto val_start = equals_index + 1; - auto eol = line.find('\r', val_start); - auto val_size = (eol == line.npos) ? line.npos : eol - val_start; - key_it->second = line.substr(val_start, val_size); - } - } - Map map; -}; - -struct BuildInfos -{ - BuildInfo current; - BuildInfo next; -}; - -// This default value should be kept in sync with the value of VCToolsUpdateURL in -// build_info.txt.in -static const char* VCToolsUpdateURLDefault = "https://aka.ms/vc14/vc_redist.x64.exe"; -#define VC_RUNTIME_REGKEY R"(SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\)" - -static const char* VCRuntimeRegistrySubkey() -{ - return VC_RUNTIME_REGKEY -#ifdef _M_X86_64 - "x64"; -#elif _M_ARM_64 - "arm64"; -#else -#error unsupported architecture -#endif -} - -static bool ReadVCRuntimeVersionField(u32* value, const char* name) -{ - return WindowsRegistry::ReadValue(value, VCRuntimeRegistrySubkey(), name); -} - -static std::optional GetInstalledVCRuntimeVersion() -{ - u32 installed; - if (!ReadVCRuntimeVersionField(&installed, "Installed") || !installed) - return {}; - BuildVersion version; - if (!ReadVCRuntimeVersionField(&version.major, "Major") || - !ReadVCRuntimeVersionField(&version.minor, "Minor") || - !ReadVCRuntimeVersionField(&version.build, "Bld")) - { - return {}; - } - return version; -} - -static VersionCheckResult VCRuntimeVersionCheck(const BuildInfos& build_infos) -{ - VersionCheckResult result; - result.current_version = GetInstalledVCRuntimeVersion(); - result.target_version = build_infos.next.GetVersion("VCToolsVersion"); - - auto existing_version = build_infos.current.GetVersion("VCToolsVersion"); - - if (!result.target_version.has_value()) - result.status = VersionCheckStatus::UpdateOptional; - else if (!result.current_version.has_value() || result.current_version < result.target_version) - result.status = VersionCheckStatus::UpdateRequired; - - // See if the current build was already running on acceptable version of the runtime. This could - // happen if the user manually copied the redist DLLs and got Dolphin running that way. - if (existing_version.has_value() && existing_version >= result.target_version) - result.status = VersionCheckStatus::NothingToDo; - - return result; -} - -static bool VCRuntimeUpdate(const BuildInfo& build_info) -{ - UI::SetDescription("Updating VC++ Redist, please wait..."); - - Common::HttpRequest req; - req.FollowRedirects(10); - auto resp = req.Get(build_info.GetString("VCToolsUpdateURL").value_or(VCToolsUpdateURLDefault)); - if (!resp) - return false; - - // Write it to current working directory. - auto redist_path = std::filesystem::current_path() / L"vc_redist.x64.exe"; - auto redist_path_u8 = WStringToUTF8(redist_path.wstring()); - File::IOFile redist_file; - redist_file.Open(redist_path_u8, "wb"); - if (!redist_file.WriteBytes(resp->data(), resp->size())) - return false; - redist_file.Close(); - - Common::ScopeGuard redist_deleter([&] { File::Delete(redist_path_u8); }); - - // The installer also supports /passive and /quiet. We normally pass neither (the - // exception being test automation) to allow the user to see and interact with the installer. - std::wstring cmdline = redist_path.filename().wstring() + L" /install /norestart"; - if (UI::IsTestMode()) - cmdline += L" /passive /quiet"; - - STARTUPINFOW startup_info{.cb = sizeof(startup_info)}; - PROCESS_INFORMATION process_info; - if (!CreateProcessW(redist_path.c_str(), cmdline.data(), nullptr, nullptr, TRUE, 0, nullptr, - nullptr, &startup_info, &process_info)) - { - return false; - } - CloseHandle(process_info.hThread); - - // Wait for it to run - WaitForSingleObject(process_info.hProcess, INFINITE); - DWORD exit_code; - bool has_exit_code = GetExitCodeProcess(process_info.hProcess, &exit_code); - CloseHandle(process_info.hProcess); - // NOTE: Some nonzero exit codes can still be considered success (e.g. if installation was - // bypassed because the same version already installed). - return has_exit_code && - (exit_code == ERROR_SUCCESS || exit_code == ERROR_SUCCESS_REBOOT_REQUIRED); -} - -static BuildVersion CurrentOSVersion() -{ - OSVERSIONINFOW info = WindowsRegistry::GetOSVersion(); - return {.major = info.dwMajorVersion, .minor = info.dwMinorVersion, .build = info.dwBuildNumber}; -} - -static VersionCheckResult OSVersionCheck(const BuildInfo& build_info) -{ - VersionCheckResult result; - result.current_version = CurrentOSVersion(); - - constexpr BuildVersion WIN11_BASE{10, 0, 22000}; - const char* version_name = - (result.current_version >= WIN11_BASE) ? "OSMinimumVersionWin11" : "OSMinimumVersionWin10"; - result.target_version = build_info.GetVersion(version_name); - - if (!result.target_version.has_value() || result.current_version >= result.target_version) - result.status = VersionCheckStatus::NothingToDo; - else - result.status = VersionCheckStatus::UpdateRequired; - return result; -} - -static std::optional InitBuildInfos(const std::vector& to_update, - const std::string& install_base_path, - const std::string& temp_dir) -{ - const auto op_it = std::ranges::find(to_update, "build_info.txt", &TodoList::UpdateOp::filename); - if (op_it == to_update.cend()) - return {}; - - const auto op = *op_it; - std::string build_info_path = - temp_dir + DIR_SEP + HexEncode(op.new_hash.data(), op.new_hash.size()); - std::string build_info_content; - if (!File::ReadFileToString(build_info_path, build_info_content) || - op.new_hash != ComputeHash(build_info_content)) - { - LogToFile("Failed to read %s\n.", build_info_path.c_str()); - return {}; - } - BuildInfos build_infos; - build_infos.next = Platform::BuildInfo(build_info_content); - - build_info_path = install_base_path + DIR_SEP + "build_info.txt"; - build_infos.current = Platform::BuildInfo(); - if (File::ReadFileToString(build_info_path, build_info_content)) - { - if (op.old_hash != ComputeHash(build_info_content)) - LogToFile("Using modified existing BuildInfo %s.\n", build_info_path.c_str()); - build_infos.current = Platform::BuildInfo(build_info_content); - } - return build_infos; -} - -static bool CheckBuildInfo(const BuildInfos& build_infos) -{ - // The existing BuildInfo may have been modified. Be careful not to overly trust its contents! - - // If the binary requires more recent OS, inform the user. - auto os_check = OSVersionCheck(build_infos.next); - if (os_check.status == VersionCheckStatus::UpdateRequired) - { - UI::Error("Please update Windows in order to update Dolphin."); - return false; - } - - // Check if application being launched needs more recent version of VC Redist. If so, download - // latest updater and execute it. - auto vc_check = VCRuntimeVersionCheck(build_infos); - const auto is_test_mode = UI::IsTestMode(); - if (vc_check.status != VersionCheckStatus::NothingToDo || is_test_mode) - { - auto update_ok = VCRuntimeUpdate(build_infos.next); - if (!update_ok && is_test_mode) - { - // For now, only check return value when test automation is running. - // The vc_redist exe may return other non-zero status that we don't check for, yet. - return false; - } - vc_check = VCRuntimeVersionCheck(build_infos); - if (vc_check.status == VersionCheckStatus::UpdateRequired) - { - // The update is required and the install failed for some reason. - UI::Error("Please update VC++ Runtime in order to update Dolphin."); - return false; - } - } - - return true; -} - -bool VersionCheck(const std::vector& to_update, - const std::string& install_base_path, const std::string& temp_dir) -{ - auto build_infos = InitBuildInfos(to_update, install_base_path, temp_dir); - // If there's no build info, it means the check should be skipped. - if (!build_infos.has_value()) - { - return true; - } - return CheckBuildInfo(build_infos.value()); -} - -} // namespace Platform diff --git a/Source/Core/WinUpdater/Updater.exe.manifest b/Source/Core/WinUpdater/Updater.exe.manifest deleted file mode 100644 index 567743a5e6..0000000000 --- a/Source/Core/WinUpdater/Updater.exe.manifest +++ /dev/null @@ -1,33 +0,0 @@ - - - -Dolphin updater - - - - - - - - - - - - - true - true - - - \ No newline at end of file diff --git a/Source/Core/WinUpdater/WinUI.cpp b/Source/Core/WinUpdater/WinUI.cpp deleted file mode 100644 index 0a92172911..0000000000 --- a/Source/Core/WinUpdater/WinUI.cpp +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright 2018 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "UpdaterCommon/UI.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "Common/Event.h" -#include "Common/ScopeGuard.h" -#include "Common/StringUtil.h" - -namespace -{ -HWND window_handle = nullptr; -HWND label_handle = nullptr; -HWND total_progressbar_handle = nullptr; -HWND current_progressbar_handle = nullptr; -Microsoft::WRL::ComPtr taskbar_list; - -std::thread ui_thread; -Common::Event window_created_event; - -int GetWindowHeight(HWND hwnd) -{ - RECT rect; - GetWindowRect(hwnd, &rect); - - return rect.bottom - rect.top; -} - -LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_DESTROY: - PostQuitMessage(0); - return 0; - } - return DefWindowProc(hwnd, uMsg, wParam, lParam); -} -} // namespace - -constexpr int PROGRESSBAR_FLAGS = WS_VISIBLE | WS_CHILD | PBS_SMOOTH | PBS_SMOOTHREVERSE; -constexpr int WINDOW_FLAGS = WS_CLIPCHILDREN; -constexpr int PADDING_HEIGHT = 5; - -namespace UI -{ -static bool InitWindow() -{ - InitCommonControls(); - - // Notify main thread we're done creating the window when we return - Common::ScopeGuard ui_guard{[] { window_created_event.Set(); }}; - - WNDCLASS wndcl = {}; - wndcl.lpfnWndProc = WindowProc; - wndcl.hbrBackground = GetSysColorBrush(COLOR_MENU); - wndcl.lpszClassName = L"UPDATER"; - - if (!RegisterClass(&wndcl)) - return false; - - window_handle = - CreateWindow(L"UPDATER", L"Dolphin Updater", WINDOW_FLAGS, CW_USEDEFAULT, CW_USEDEFAULT, 500, - 100, nullptr, nullptr, GetModuleHandle(nullptr), 0); - - if (!window_handle) - return false; - - if (SUCCEEDED(CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&taskbar_list)))) - { - if (FAILED(taskbar_list->HrInit())) - { - taskbar_list.Reset(); - } - } - - int y = PADDING_HEIGHT; - - label_handle = CreateWindow(L"STATIC", nullptr, WS_VISIBLE | WS_CHILD, 5, y, 500, 25, - window_handle, nullptr, nullptr, 0); - - if (!label_handle) - return false; - - // Get the default system font - NONCLIENTMETRICS metrics = {}; - metrics.cbSize = sizeof(NONCLIENTMETRICS); - - if (!SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(metrics), &metrics, 0)) - return false; - - SendMessage(label_handle, WM_SETFONT, - reinterpret_cast(CreateFontIndirect(&metrics.lfMessageFont)), 0); - - y += GetWindowHeight(label_handle) + PADDING_HEIGHT; - - total_progressbar_handle = CreateWindow(PROGRESS_CLASS, nullptr, PROGRESSBAR_FLAGS, 5, y, 470, 25, - window_handle, nullptr, nullptr, 0); - - y += GetWindowHeight(total_progressbar_handle) + PADDING_HEIGHT; - - if (!total_progressbar_handle) - return false; - - current_progressbar_handle = CreateWindow(PROGRESS_CLASS, nullptr, PROGRESSBAR_FLAGS, 5, y, 470, - 25, window_handle, nullptr, nullptr, 0); - - y += GetWindowHeight(current_progressbar_handle) + PADDING_HEIGHT; - - if (!current_progressbar_handle) - return false; - - RECT rect; - GetWindowRect(window_handle, &rect); - - // Account for the title bar - y += GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYCAPTION) + - GetSystemMetrics(SM_CXPADDEDBORDER); - // ...and window border - y += GetSystemMetrics(SM_CYBORDER); - - // Add some padding for good measure - y += PADDING_HEIGHT * 3; - - SetWindowPos(window_handle, HWND_TOPMOST, 0, 0, rect.right - rect.left, y, SWP_NOMOVE); - - return true; -} - -void SetTotalMarquee(bool marquee) -{ - SetWindowLong(total_progressbar_handle, GWL_STYLE, - PROGRESSBAR_FLAGS | (marquee ? PBS_MARQUEE : 0)); - SendMessage(total_progressbar_handle, PBM_SETMARQUEE, marquee, 0); - if (taskbar_list) - { - taskbar_list->SetProgressState(window_handle, marquee ? TBPF_INDETERMINATE : TBPF_NORMAL); - } -} - -void ResetTotalProgress() -{ - SendMessage(total_progressbar_handle, PBM_SETPOS, 0, 0); - SetCurrentMarquee(true); -} - -void SetTotalProgress(int current, int total) -{ - SendMessage(total_progressbar_handle, PBM_SETRANGE32, 0, total); - SendMessage(total_progressbar_handle, PBM_SETPOS, current, 0); - if (taskbar_list) - { - taskbar_list->SetProgressValue(window_handle, current, total); - } -} - -void SetCurrentMarquee(bool marquee) -{ - SetWindowLong(current_progressbar_handle, GWL_STYLE, - PROGRESSBAR_FLAGS | (marquee ? PBS_MARQUEE : 0)); - SendMessage(current_progressbar_handle, PBM_SETMARQUEE, marquee, 0); -} - -void ResetCurrentProgress() -{ - SendMessage(current_progressbar_handle, PBM_SETPOS, 0, 0); - SetCurrentMarquee(true); -} - -void Error(const std::string& text) -{ - auto message = L"A fatal error occurred and the updater cannot continue:\n " + - UTF8ToWString(text) + L"\n" + - L"If the issue persists, please manually download the latest version from " - L"dolphin-emu.org/download and extract it overtop your existing installation.\n" + - L"Also consider filing a bug at bugs.dolphin-emu.org/projects/emulator"; - - MessageBox(nullptr, message.c_str(), L"Error", MB_ICONERROR); - - if (taskbar_list) - { - taskbar_list->SetProgressState(window_handle, TBPF_ERROR); - } -} - -void SetCurrentProgress(int current, int total) -{ - SendMessage(current_progressbar_handle, PBM_SETRANGE32, 0, total); - SendMessage(current_progressbar_handle, PBM_SETPOS, current, 0); -} - -void SetDescription(const std::string& text) -{ - SetWindowText(label_handle, UTF8ToWString(text).c_str()); -} - -static void MessageLoop() -{ - HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - - Common::ScopeGuard ui_guard{[result] { - taskbar_list.Reset(); - if (SUCCEEDED(result)) - CoUninitialize(); - }}; - - if (!InitWindow()) - { - MessageBox(nullptr, L"Window init failed!", L"", MB_ICONERROR); - // Destroying the parent (if exists) destroys all children windows - if (window_handle) - { - DestroyWindow(window_handle); - window_handle = nullptr; - } - return; - } - - SetTotalMarquee(true); - SetCurrentMarquee(true); - - MSG msg; - while (GetMessage(&msg, nullptr, 0, 0)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } -} - -void Init() -{ - ui_thread = std::thread(MessageLoop); - - // Wait for UI thread to finish creating the window (or at least attempting to) - window_created_event.Wait(); -} - -void Stop() -{ - if (window_handle) - PostMessage(window_handle, WM_CLOSE, 0, 0); - - ui_thread.join(); -} - -bool IsTestMode() -{ - return std::getenv("DOLPHIN_UPDATE_SERVER_URL") != nullptr; -} - -void LaunchApplication(std::string path) -{ - const auto wpath = UTF8ToWString(path); - if (IsUserAnAdmin()) - { - // Indirectly start the application via explorer. This effectively drops admin privileges - // because explorer is running as current user. - ShellExecuteW(nullptr, nullptr, L"explorer.exe", wpath.c_str(), nullptr, SW_SHOW); - } - else - { - std::wstring cmdline = L"\"" + wpath + L"\""; - STARTUPINFOW startup_info{.cb = sizeof(startup_info)}; - PROCESS_INFORMATION process_info; - if (IsTestMode()) - SetEnvironmentVariableA("DOLPHIN_UPDATE_TEST_DONE", "1"); - if (CreateProcessW(wpath.c_str(), cmdline.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, - &startup_info, &process_info)) - { - CloseHandle(process_info.hThread); - CloseHandle(process_info.hProcess); - } - } -} - -void Sleep(int sleep) -{ - ::Sleep(sleep * 1000); -} - -void WaitForPID(u32 pid) -{ - HANDLE parent_handle = OpenProcess(SYNCHRONIZE, FALSE, static_cast(pid)); - if (parent_handle) - { - WaitForSingleObject(parent_handle, INFINITE); - CloseHandle(parent_handle); - } -} - -void SetVisible(bool visible) -{ - ShowWindow(window_handle, visible ? SW_SHOW : SW_HIDE); -} - -} // namespace UI diff --git a/Tools/test-updater.py b/Tools/test-updater.py deleted file mode 100644 index 638bd4fac7..0000000000 --- a/Tools/test-updater.py +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/bin/env python3 -# requirements: pycryptodome -from Crypto.PublicKey import ECC -from Crypto.Signature import eddsa -from hashlib import sha256 -from pathlib import Path -import base64 -import configparser -import gzip -import http.server -import json -import os -import shutil -import socketserver -import subprocess -import sys -import tempfile -import threading -import time - -UPDATE_KEY_TEST = ECC.construct( - curve="Ed25519", - seed=bytes.fromhex( - "543a581db60008bbb978a464e136d686dbc9d594119e928b5276bece3d583d81" - ), -) - -HTTP_SERVER_ADDR = ("localhost", 8042) -DOLPHIN_UPDATE_SERVER_URL = f"http://{HTTP_SERVER_ADDR[0]}:{HTTP_SERVER_ADDR[1]}" - - -class Manifest: - def __init__(self, path: Path): - self.path = path - self.entries = {} - for p in self.path.glob("**/*.*"): - if not p.is_file(): - continue - digest = sha256(p.read_bytes()).digest()[:0x10].hex() - self.entries[digest] = p.relative_to(self.path).as_posix() - - def get_signed(self): - manifest = "".join( - f"{name}\t{digest}\n" for digest, name in self.entries.items() - ) - manifest = manifest.encode("utf-8") - sig = eddsa.new(UPDATE_KEY_TEST, "rfc8032").sign(manifest) - manifest += b"\n" + base64.b64encode(sig) + b"\n" - return gzip.compress(manifest) - - def get_path(self, digest): - return self.path.joinpath(self.entries.get(digest)) - - -class HTTPRequestHandler(http.server.BaseHTTPRequestHandler): - def do_GET(self): - if self.path.startswith("/update/check/v1/updater-test"): - self.send_response(200) - self.end_headers() - self.wfile.write( - bytes( - json.dumps( - { - "status": "outdated", - "content-store": DOLPHIN_UPDATE_SERVER_URL + "/content/", - "changelog": [], - "old": {"manifest": DOLPHIN_UPDATE_SERVER_URL + "/old"}, - "new": { - "manifest": DOLPHIN_UPDATE_SERVER_URL + "/new", - "name": "updater-test", - "hash": bytes(range(32)).hex(), - }, - } - ), - "utf-8", - ) - ) - elif self.path == "/old": - self.send_response(200) - self.end_headers() - self.wfile.write(self.current.get_signed()) - elif self.path == "/new": - self.send_response(200) - self.end_headers() - self.wfile.write(self.next.get_signed()) - elif self.path.startswith("/content/"): - self.send_response(200) - self.end_headers() - digest = "".join(self.path[len("/content/") :].split("/")) - path = self.next.get_path(digest) - self.wfile.write(gzip.compress(path.read_bytes())) - elif self.path.startswith("/update-test-done/"): - self.send_response(200) - self.end_headers() - HTTPRequestHandler.dolphin_pid = int(self.path[len("/update-test-done/") :]) - self.done.set() - - -def http_server(): - with socketserver.TCPServer(HTTP_SERVER_ADDR, HTTPRequestHandler) as httpd: - httpd.serve_forever() - - -def create_entries_in_ini(ini_path: Path, entries: dict): - config = configparser.ConfigParser() - if ini_path.exists(): - config.read(ini_path) - else: - ini_path.parent.mkdir(parents=True, exist_ok=True) - - for section, options in entries.items(): - if not config.has_section(section): - config.add_section(section) - for option, value in options.items(): - config.set(section, option, value) - - with ini_path.open("w") as f: - config.write(f) - - -if __name__ == "__main__": - dolphin_bin_path = Path(sys.argv[1]) - - threading.Thread(target=http_server, daemon=True).start() - - with tempfile.TemporaryDirectory(suffix=" ¿ 🐬") as tmp_dir: - tmp_dir = Path(tmp_dir) - - tmp_dolphin = tmp_dir.joinpath("dolphin") - print(f"install to {tmp_dolphin}") - shutil.copytree(dolphin_bin_path.parent, tmp_dolphin) - tmp_dolphin.joinpath("portable.txt").touch() - create_entries_in_ini( - tmp_dolphin.joinpath("User/Config/Dolphin.ini"), - { - "Analytics": {"Enabled": "False", "PermissionAsked": "True"}, - "AutoUpdate": {"UpdateTrack": "updater-test"}, - }, - ) - - tmp_dolphin_next = tmp_dir.joinpath("dolphin_next") - print(f"install next to {tmp_dolphin_next}") - # XXX copies from just-created dir so Dolphin.ini is kept - shutil.copytree(tmp_dolphin, tmp_dolphin_next) - tmp_dolphin_next.joinpath("updater-test-file").write_text("test") - tmp_dolphin_next.joinpath("updater-test-filἑ").write_text("test") - with tmp_dolphin_next.joinpath("build_info.txt").open("a") as f: - print("test", file=f) - for ext in ("exe", "dll"): - for path in tmp_dolphin_next.glob("**/*." + ext): - data = bytearray(path.read_bytes()) - richpos = data[:0x200].find(b"Rich") - if richpos < 0: - continue - data[richpos : richpos + 4] = b"DOLP" - path.write_bytes(data) - - HTTPRequestHandler.current = Manifest(tmp_dolphin) - HTTPRequestHandler.next = Manifest(tmp_dolphin_next) - HTTPRequestHandler.done = threading.Event() - - tmp_env = os.environ - tmp_env.update({"DOLPHIN_UPDATE_SERVER_URL": DOLPHIN_UPDATE_SERVER_URL}) - tmp_dolphin_bin = tmp_dolphin.joinpath(dolphin_bin_path.name) - result = subprocess.run(tmp_dolphin_bin, env=tmp_env) - assert result.returncode == 0 - - assert HTTPRequestHandler.done.wait(60 * 2) - # works fine but raises exceptions... - try: - os.kill(HTTPRequestHandler.dolphin_pid, 0) - except: - pass - try: - os.waitpid(HTTPRequestHandler.dolphin_pid, 0) - except: - pass - - failed = False - for path in tmp_dolphin_next.glob("**/*.*"): - if not path.is_file(): - continue - path_rel = path.relative_to(tmp_dolphin_next) - if path_rel.parts[0] == "User": - continue - new_path = tmp_dolphin.joinpath(path_rel) - if not new_path.exists(): - print(f"missing: {new_path}") - failed = True - continue - if ( - sha256(new_path.read_bytes()).digest() - != sha256(path.read_bytes()).digest() - ): - print(f"bad digest: {new_path} {path}") - failed = True - continue - assert not failed - - print(tmp_dolphin.joinpath("User/Logs/Updater.log").read_text()) - # while True: time.sleep(1)