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.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-05 04:47:53 +01:00
1 parent 55857660c6
commit dc7d0c21a0
28 files changed
+3 -2760

No files matched your search

+1 -2
View File
@@ -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)
-6
View File
@@ -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
-1
View File
@@ -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
+2 -3
View File
@@ -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()
-23
View File
@@ -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
-1
View File
@@ -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}"
-7
View File
@@ -509,13 +509,6 @@ const Info<bool> MAIN_FIFOPLAYER_LOOP_REPLAY{{System::Main, "FifoPlayer", "LoopR
const Info<bool> MAIN_FIFOPLAYER_EARLY_MEMORY_UPDATES{
{System::Main, "FifoPlayer", "EarlyMemoryUpdates"}, false};
// Main.AutoUpdate
const Info<std::string> MAIN_AUTOUPDATE_UPDATE_TRACK{{System::Main, "AutoUpdate", "UpdateTrack"},
Common::GetScmUpdateTrackStr()};
const Info<std::string> MAIN_AUTOUPDATE_HASH_OVERRIDE{{System::Main, "AutoUpdate", "HashOverride"},
""};
// Main.Movie
const Info<bool> MAIN_MOVIE_PAUSE_MOVIE{{System::Main, "Movie", "PauseMovie"}, false};
-5
View File
@@ -323,11 +323,6 @@ extern const Info<bool> MAIN_GAMELIST_COLUMN_TAGS;
extern const Info<bool> MAIN_FIFOPLAYER_LOOP_REPLAY;
extern const Info<bool> MAIN_FIFOPLAYER_EARLY_MEMORY_UPDATES;
// Main.AutoUpdate
extern const Info<std::string> MAIN_AUTOUPDATE_UPDATE_TRACK;
extern const Info<std::string> MAIN_AUTOUPDATE_HASH_OVERRIDE;
// Main.Movie
extern const Info<bool> MAIN_MOVIE_PAUSE_MOVIE;
-3
View File
@@ -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());
-8
View File
@@ -1,8 +0,0 @@
// Copyright 2019 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@end
-40
View File
@@ -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 <Cocoa/Cocoa.h>
#include <string>
#include <vector>
@interface AppDelegate ()
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification
{
NSArray* arguments = [[NSProcessInfo processInfo] arguments];
__block std::vector<std::string> 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
-30
View File
@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>Dolphin Updater</string>
<key>CFBundleIconFile</key>
<string>Dolphin.icns</string>
<key>CFBundleIdentifier</key>
<string>org.dolphin-emu.updater</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Dolphin Updater</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>LSMinimumSystemVersion</key>
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
<key>NSHumanReadableCopyright</key>
<string>Licensed under GPL version 2 or later (GPLv2+)</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>LSUIElement</key>
<true/>
</dict>
</plist>
-197
View File
@@ -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 <Cocoa/Cocoa.h>
#include <unistd.h>
#include <functional>
// 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<void()> 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<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]]];
}
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<TodoList::UpdateOp>& 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;
}
-109
View File
@@ -1,109 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies>
<deployment version="101000" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Application-->
<scene sceneID="JPo-4y-FX3">
<objects>
<application id="hnw-xV-0zn" sceneMemberID="viewController">
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="Updater" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Updater" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About Updater" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Quit Updater" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
</connections>
</application>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="0.0"/>
</scene>
<!--Window Controller-->
<scene sceneID="R2V-B0-nI4">
<objects>
<windowController id="B8D-0N-5wS" sceneMemberID="viewController">
<window key="window" title="Dolphin Updater" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" titlebarAppearsTransparent="YES" id="IQv-IB-iLA">
<windowStyleMask key="styleMask" titled="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
<connections>
<outlet property="delegate" destination="B8D-0N-5wS" id="98r-iN-zZc"/>
</connections>
</window>
<connections>
<segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
</connections>
</windowController>
<customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="250"/>
</scene>
<!--View Controller-->
<scene sceneID="hIz-AP-VOD">
<objects>
<viewController id="XfG-lQ-9wD" customClass="ViewController" sceneMemberID="viewController">
<view key="view" wantsLayer="YES" id="m2S-Jp-Qdl">
<rect key="frame" x="0.0" y="0.0" width="480" height="133"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<progressIndicator identifier="progressTotal" wantsLayer="YES" fixedFrame="YES" maxValue="100" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="oLS-AW-V72">
<rect key="frame" x="20" y="94" width="440" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</progressIndicator>
<progressIndicator identifier="progressCurrent" wantsLayer="YES" fixedFrame="YES" maxValue="100" indeterminate="YES" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="mni-rL-166">
<rect key="frame" x="20" y="52" width="440" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</progressIndicator>
<textField identifier="labelProgress" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="r87-pn-Exw" userLabel="Label">
<rect key="frame" x="18" y="7" width="444" height="38"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" truncatesLastVisibleLine="YES" title="Initializing..." id="FBK-Tz-5cA">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<action selector="progressLabel:" target="XfG-lQ-9wD" id="PDs-H5-idS"/>
</connections>
</textField>
</subviews>
</view>
<connections>
<outlet property="foo" destination="r87-pn-Exw" id="i76-Sg-K3o"/>
<outlet property="labelProgress" destination="r87-pn-Exw" id="v6b-r7-3lR"/>
<outlet property="progressCurrent" destination="mni-rL-166" id="A3C-wp-83j"/>
<outlet property="progressTotal" destination="oLS-AW-V72" id="pwo-CZ-XyN"/>
</connections>
</viewController>
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="653.5"/>
</scene>
</scenes>
</document>
-20
View File
@@ -1,20 +0,0 @@
// Copyright 2019 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#import <Cocoa/Cocoa.h>
@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
-51
View File
@@ -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
-22
View File
@@ -1,22 +0,0 @@
// Copyright 2019 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <Cocoa/Cocoa.h>
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);
}
-340
View File
@@ -1,340 +0,0 @@
// Copyright 2018 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "UICommon/AutoUpdate.h"
#include <atomic>
#include <cstdlib>
#include <string>
#include <fmt/format.h>
#include <picojson.h>
#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 <windows.h>
#else
#include <unistd.h>
#endif
#ifdef __APPLE__
#include <sys/stat.h>
#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<std::string, std::string>& 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<picojson::object>())
continue;
picojson::object ver_obj = ver.get<picojson::object>();
if (ver_obj["changelog_html"].is<picojson::null>())
{
if (!changelog.empty())
changelog += "<div style=\"margin-top: 0.4em;\"></div>"; // Vertical spacing.
// Try to link to the PR if we have this info. Otherwise just show shortrev.
if (ver_obj["pr_url"].is<std::string>())
{
changelog += "<a href=\"" + ver_obj["pr_url"].get<std::string>() + "\">" +
ver_obj["shortrev"].get<std::string>() + "</a>";
}
else
{
changelog += ver_obj["shortrev"].get<std::string>();
}
const std::string escaped_description =
Common::GetEscapedHtml(ver_obj["short_descr"].get<std::string>());
changelog += " by <a href = \"" + ver_obj["author_url"].get<std::string>() + "\">" +
ver_obj["author"].get<std::string>() + "</a> &mdash; " + escaped_description;
}
else
{
if (!changelog.empty())
changelog += "<hr>";
changelog += "<b>Dolphin " + ver_obj["shortrev"].get<std::string>() + "</b>";
changelog += "<p>" + ver_obj["changelog_html"].get<std::string>() + "</p>";
}
}
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<char*>(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<picojson::object>();
if (obj["status"].get<std::string>() != "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<picojson::object>()["manifest"].get<std::string>();
nvi.next_manifest_url = obj["new"].get<picojson::object>()["manifest"].get<std::string>();
nvi.content_store_url = obj["content-store"].get<std::string>();
nvi.new_shortrev = obj["new"].get<picojson::object>()["name"].get<std::string>();
nvi.new_hash = obj["new"].get<picojson::object>()["hash"].get<std::string>();
// TODO: generate the HTML changelog from the JSON information.
nvi.changelog_html = GenerateChangelog(obj["changelog"].get<picojson::array>());
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<std::string, std::string> 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
}
-52
View File
@@ -1,52 +0,0 @@
// Copyright 2018 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <string_view>
// 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;
};
-19
View File
@@ -1,19 +0,0 @@
// Copyright 2018 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <map>
#include <optional>
#include <sstream>
#include "Common/CommonTypes.h"
#include "Common/StringUtil.h"
#include "UpdaterCommon/UpdaterCommon.h"
namespace Platform
{
bool VersionCheck(const std::vector<TodoList::UpdateOp>& to_update,
const std::string& install_base_path, const std::string& temp_dir);
} // namespace Platform
-34
View File
@@ -1,34 +0,0 @@
// Copyright 2018 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#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
-794
View File
@@ -1,794 +0,0 @@
// Copyright 2019 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "UpdaterCommon/UpdaterCommon.h"
#include <algorithm>
#include <array>
#include <memory>
#include <optional>
#include <OptionParser.h>
#include <ed25519.h>
#include <fmt/format.h>
#include <mbedtls/base64.h>
#include <mbedtls/sha256.h>
#include <zlib.h>
#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 <sys/stat.h>
#include <sys/types.h>
#endif
#ifdef _WIN32
#include <windows.h>
#include <filesystem>
#endif
// Public key used to verify update manifests.
const std::array<u8, 32> 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<u8, 32> 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<int>(now), static_cast<int>(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<u8> {
if (c >= '0' && c <= '9')
return static_cast<u8>(c - '0');
else if (c >= 'a' && c <= 'f')
return static_cast<u8>(c - 'a' + 10);
else if (c >= 'A' && c <= 'F')
return static_cast<u8>(c - 'A' + 10);
else
return {};
};
for (size_t i = 0; i < size; ++i)
{
std::optional<u8> high = DecodeNibble(hex[2 * i]);
std::optional<u8> low = DecodeNibble(hex[2 * i + 1]);
if (!high || !low)
return false;
buffer[i] = (*high << 4) | *low;
}
return true;
}
static std::optional<std::string> GzipInflate(const std::string& data)
{
z_stream zstrm;
zstrm.zalloc = nullptr;
zstrm.zfree = nullptr;
zstrm.opaque = nullptr;
zstrm.avail_in = static_cast<u32>(data.size());
zstrm.next_in = reinterpret_cast<u8*>(const_cast<char*>(data.data()));
// 16 + MAX_WBITS means gzip. Don't ask me.
inflateInit2(&zstrm, 16 + MAX_WBITS);
std::string out;
const size_t buf_len = 20 * 1024 * 1024;
auto buffer = std::make_unique<char[]>(buf_len);
int ret;
do
{
zstrm.avail_out = buf_len;
zstrm.next_out = reinterpret_cast<u8*>(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<u8, 32> full;
mbedtls_sha256_ret(reinterpret_cast<const u8*>(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<const u8*>(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<const u8*>(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<const TodoList::DownloadOp> 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<int>(i + 1), static_cast<int>(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<char*>(resp->data()), resp->size());
std::optional<std::string> 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<TodoList::UpdateOp>& 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<Manifest::Hash> old_hash;
const auto& old_entry = this_manifest.entries.find(entry.first);
if (old_entry != this_manifest.entries.end())
old_hash = old_entry->second;
if (!old_hash || *old_hash != entry.second)
{
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<const TodoList::DeleteOp> to_delete,
const std::string& install_base_path)
{
for (const auto& op : to_delete)
{
std::string path = install_base_path + DIR_SEP + op.filename;
if (!File::Exists(path))
{
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<const TodoList::UpdateOp> 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<mode_t> 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<Manifest> ParseManifest(const std::string& manifest)
{
Manifest parsed;
size_t pos = 0;
while (pos < manifest.size())
{
size_t filename_end_pos = manifest.find('\t', pos);
if (filename_end_pos == std::string::npos)
{
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<Manifest> 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<char*>(resp->data()), resp->size());
std::optional<std::string> maybe_decompressed = GzipInflate(contents);
if (!maybe_decompressed)
return {};
std::string decompressed = std::move(*maybe_decompressed);
// Split into manifest and signature.
size_t boundary = decompressed.rfind("\n\n");
if (boundary == std::string::npos)
{
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<std::string> signatures = SplitString(signature_block, '\n');
bool found_valid_signature = false;
for (const auto& signature : signatures)
{
if (VerifySignature(decompressed, signature))
{
found_valid_signature = true;
break;
}
}
if (!found_valid_signature)
{
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<std::string> binary_to_restart;
std::optional<u32> parent_pid;
std::optional<std::string> log_file;
};
static 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;
}
bool RunUpdater(std::vector<std::string> args)
{
std::optional<Options> 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<u32>(pid));
LogToFile("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::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;
}
-54
View File
@@ -1,54 +0,0 @@
// Copyright 2019 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstdio>
#include <map>
#include <optional>
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
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 LogToFile(const char* fmt, ...);
std::string HexEncode(const u8* buffer, size_t size);
Manifest::Hash ComputeHash(const std::string& contents);
bool RunUpdater(std::vector<std::string> args);
-67
View File
@@ -1,67 +0,0 @@
// Copyright 2018 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <windows.h>
#include <ShlObj.h>
#include <shellapi.h>
#include <optional>
#include <string>
#include <vector>
#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<std::string> args = Common::CommandLineToUtf8Argv(pCmdLine);
return RunUpdater(args) ? 0 : 1;
}
-330
View File
@@ -1,330 +0,0 @@
#include <windows.h>
#include <filesystem>
#include <map>
#include <optional>
#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<BuildVersion> from_string(const std::string& str)
{
auto components = SplitString(str, '.');
// Allow variable number of components (truncating after "build"), but not
// empty.
if (components.size() == 0)
return {};
BuildVersion version;
if (!TryParse(components[0], &version.major, 10))
return {};
if (components.size() > 1 && !TryParse(components[1], &version.minor, 10))
return {};
if (components.size() > 2 && !TryParse(components[2], &version.build, 10))
return {};
return version;
}
};
enum class VersionCheckStatus
{
NothingToDo,
UpdateOptional,
UpdateRequired,
};
struct VersionCheckResult
{
VersionCheckStatus status{VersionCheckStatus::NothingToDo};
std::optional<BuildVersion> current_version{};
std::optional<BuildVersion> target_version{};
};
class BuildInfo
{
using Map = std::map<std::string, std::string>;
public:
BuildInfo() = default;
BuildInfo(const std::string& content)
{
map = {{"OSMinimumVersionWin10", ""},
{"OSMinimumVersionWin11", ""},
{"VCToolsVersion", ""},
{"VCToolsUpdateURL", ""}};
Parse(content);
}
std::optional<std::string> GetString(const std::string& name) const
{
auto it = map.find(name);
if (it == map.end() || it->second.size() == 0)
return {};
return it->second;
}
std::optional<BuildVersion> GetVersion(const std::string& name) const
{
auto str = GetString(name);
if (!str.has_value())
return {};
return BuildVersion::from_string(str.value());
}
private:
void Parse(const std::string& content)
{
std::stringstream content_stream(content);
std::string line;
while (std::getline(content_stream, line))
{
if (line.starts_with("//"))
continue;
const size_t equals_index = line.find('=');
if (equals_index == line.npos)
continue;
auto key = line.substr(0, equals_index);
auto key_it = map.find(key);
if (key_it == map.end())
continue;
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<BuildVersion> 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<BuildInfos> InitBuildInfos(const std::vector<TodoList::UpdateOp>& 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<TodoList::UpdateOp>& 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
@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="amd64"
name="DolphinTeam.DolphinEmuUpdater"
type="win32"
/>
<description>Dolphin updater</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
-308
View File
@@ -1,308 +0,0 @@
// Copyright 2018 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "UpdaterCommon/UI.h"
#include <cstdlib>
#include <string>
#include <thread>
#include <windows.h>
#include <commctrl.h>
#include <shellapi.h>
#include <shlobj.h>
#include <shobjidl.h>
#include <wrl/client.h>
#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<ITaskbarList3> 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<WPARAM>(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<DWORD>(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
-201
View File
@@ -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)