Qt: Invoke Updater as Administrator for Program Files installs

This commit is contained in:
Connor McLaughlin 2024-01-14 17:09:49 +10:00
parent d634088282
commit 5fb6e22bed
4 changed files with 76 additions and 50 deletions

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team // SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+ // SPDX-License-Identifier: LGPL-3.0+
#include "AutoUpdaterDialog.h" #include "AutoUpdaterDialog.h"
@ -16,8 +16,10 @@
#include "common/Assertions.h" #include "common/Assertions.h"
#include "common/CocoaTools.h" #include "common/CocoaTools.h"
#include "common/Console.h" #include "common/Console.h"
#include "common/Error.h"
#include "common/FileSystem.h" #include "common/FileSystem.h"
#include "common/HTTPDownloader.h" #include "common/HTTPDownloader.h"
#include "common/Path.h"
#include "common/StringUtil.h" #include "common/StringUtil.h"
#include "cpuinfo.h" #include "cpuinfo.h"
@ -38,6 +40,11 @@
#include <QtWidgets/QMessageBox> #include <QtWidgets/QMessageBox>
#include <QtWidgets/QProgressDialog> #include <QtWidgets/QProgressDialog>
#ifdef _WIN32
#include "common/RedtapeWindows.h"
#include <shellapi.h>
#endif
// Interval at which HTTP requests are polled. // Interval at which HTTP requests are polled.
static constexpr u32 HTTP_POLL_INTERVAL = 10; static constexpr u32 HTTP_POLL_INTERVAL = 10;
@ -566,70 +573,74 @@ void AutoUpdaterDialog::remindMeLaterClicked()
#if defined(_WIN32) #if defined(_WIN32)
bool AutoUpdaterDialog::doesUpdaterNeedElevation(const std::string& application_dir) const
{
// Try to create a dummy text file in the PCSX2 updater directory. If it fails, we probably won't have write permission.
const std::string dummy_path = Path::Combine(application_dir, "update.txt");
auto fp = FileSystem::OpenManagedCFile(dummy_path.c_str(), "wb");
if (!fp)
return true;
fp.reset();
FileSystem::DeleteFilePath(dummy_path.c_str());
return false;
}
bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& data, QProgressDialog&) bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& data, QProgressDialog&)
{ {
const QString update_directory = QCoreApplication::applicationDirPath(); const std::string& application_dir = EmuFolders::AppRoot;
const QString update_zip_path = QStringLiteral("%1" FS_OSPATH_SEPARATOR_STR "%2").arg(update_directory).arg(UPDATER_ARCHIVE_NAME); const std::string update_zip_path = Path::Combine(EmuFolders::DataRoot, UPDATER_ARCHIVE_NAME);
const QString updater_path = QStringLiteral("%1" FS_OSPATH_SEPARATOR_STR "%2").arg(update_directory).arg(UPDATER_EXECUTABLE); const std::string updater_path = Path::Combine(EmuFolders::DataRoot, UPDATER_EXECUTABLE);
Q_ASSERT(!update_zip_path.isEmpty() && !updater_path.isEmpty() && !update_directory.isEmpty()); if ((FileSystem::FileExists(update_zip_path.c_str()) && !FileSystem::DeleteFilePath(update_zip_path.c_str())))
if ((QFile::exists(update_zip_path) && !QFile::remove(update_zip_path)) ||
(QFile::exists(updater_path) && !QFile::remove(updater_path)))
{ {
reportError("Removing existing update zip/updater failed"); reportError("Removing existing update zip failed");
return false; return false;
} }
if (!FileSystem::WriteBinaryFile(update_zip_path.c_str(), data.data(), data.size()))
{ {
QFile update_zip_file(update_zip_path); reportError("Writing update zip to '%s' failed", update_zip_path.c_str());
if (!update_zip_file.open(QIODevice::WriteOnly) || return false;
update_zip_file.write(reinterpret_cast<const char*>(data.data()), static_cast<qint64>(data.size())) != static_cast<qint64>(data.size()))
{
reportError("Writing update zip to '%s' failed", update_zip_path.toUtf8().constData());
return false;
}
update_zip_file.close();
} }
std::string updater_extract_error; std::string updater_extract_error;
if (!ExtractUpdater(update_zip_path.toUtf8().constData(), updater_path.toUtf8().constData(), &updater_extract_error)) if (!ExtractUpdater(update_zip_path.c_str(), updater_path.c_str(), &updater_extract_error))
{ {
reportError("Extracting updater failed: %s", updater_extract_error.c_str()); reportError("Extracting updater failed: %s", updater_extract_error.c_str());
return false; return false;
} }
if (!doUpdate(update_zip_path, updater_path, update_directory)) return doUpdate(application_dir, update_zip_path, updater_path);
{
reportError("Launching updater failed");
return false;
}
return true;
} }
bool AutoUpdaterDialog::doUpdate(const QString& zip_path, const QString& updater_path, const QString& destination_path) bool AutoUpdaterDialog::doUpdate(const std::string& application_dir, const std::string& zip_path, const std::string& updater_path)
{ {
const QString program_path = QCoreApplication::applicationFilePath(); const std::string program_path = QDir::toNativeSeparators(QCoreApplication::applicationFilePath()).toStdString();
if (program_path.isEmpty()) if (program_path.empty())
{ {
reportError("Failed to get current application path"); reportError("Failed to get current application path");
return false; return false;
} }
QStringList arguments; const std::wstring wupdater_path = StringUtil::UTF8StringToWideString(updater_path);
arguments << QString::number(QCoreApplication::applicationPid()); const std::wstring wapplication_dir = StringUtil::UTF8StringToWideString(application_dir);
arguments << destination_path; const std::wstring arguments = StringUtil::UTF8StringToWideString(fmt::format("{} \"{}\" \"{}\" \"{}\"",
arguments << zip_path; QCoreApplication::applicationPid(), application_dir, zip_path, program_path));
arguments << program_path;
// this will leak, but not sure how else to handle it... const bool needs_elevation = doesUpdaterNeedElevation(application_dir);
QProcess* updater_process = new QProcess();
updater_process->setProgram(updater_path); SHELLEXECUTEINFOW sei = {};
updater_process->setArguments(arguments); sei.cbSize = sizeof(sei);
updater_process->start(QIODevice::NotOpen); sei.lpVerb = needs_elevation ? L"runas" : nullptr; // needed to trigger elevation
if (!updater_process->waitForStarted()) sei.lpFile = wupdater_path.c_str();
sei.lpParameters = arguments.c_str();
sei.lpDirectory = wapplication_dir.c_str();
sei.nShow = SW_SHOWNORMAL;
if (!ShellExecuteExW(&sei))
{ {
reportError("Failed to start updater"); reportError("Failed to start %s: %s", needs_elevation ? "elevated updater" : "updater",
Error::CreateWin32(GetLastError()).GetDescription().c_str());
return false; return false;
} }
@ -638,7 +649,21 @@ bool AutoUpdaterDialog::doUpdate(const QString& zip_path, const QString& updater
void AutoUpdaterDialog::cleanupAfterUpdate() void AutoUpdaterDialog::cleanupAfterUpdate()
{ {
// Nothing to do on Windows for now, the updater stub cleans everything up. QMessageBox::critical(nullptr, "CLEANUP", "CLEANUP");
// If we weren't portable, then updater executable gets left in the application directory.
if (EmuFolders::AppRoot == EmuFolders::DataRoot)
return;
const std::string updater_path = Path::Combine(EmuFolders::DataRoot, UPDATER_EXECUTABLE);
if (!FileSystem::FileExists(updater_path.c_str()))
return;
if (!FileSystem::DeleteFilePath(updater_path.c_str()))
{
QMessageBox::critical(nullptr, tr("Updater Error"), tr("Failed to remove updater exe after update."));
return;
}
} }
#elif defined(__linux__) #elif defined(__linux__)

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team // SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+ // SPDX-License-Identifier: LGPL-3.0+
#pragma once #pragma once
@ -60,7 +60,8 @@ private:
bool processUpdate(const std::vector<u8>& data, QProgressDialog& progress); bool processUpdate(const std::vector<u8>& data, QProgressDialog& progress);
#if defined(_WIN32) #if defined(_WIN32)
bool doUpdate(const QString& zip_path, const QString& updater_path, const QString& destination_path); bool doesUpdaterNeedElevation(const std::string& application_dir) const;
bool doUpdate(const std::string& application_dir, const std::string& zip_path, const std::string& updater_path);
#endif #endif
Ui::AutoUpdaterDialog m_ui; Ui::AutoUpdaterDialog m_ui;

View File

@ -88,6 +88,7 @@ static bool s_start_fullscreen_ui = false;
static bool s_start_fullscreen_ui_fullscreen = false; static bool s_start_fullscreen_ui_fullscreen = false;
static bool s_test_config_and_exit = false; static bool s_test_config_and_exit = false;
static bool s_run_setup_wizard = false; static bool s_run_setup_wizard = false;
static bool s_cleanup_after_update = false;
static bool s_boot_and_debug = false; static bool s_boot_and_debug = false;
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -1778,9 +1779,7 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
} }
else if (CHECK_ARG(QStringLiteral("-updatecleanup"))) else if (CHECK_ARG(QStringLiteral("-updatecleanup")))
{ {
if (AutoUpdaterDialog::isSupported()) s_cleanup_after_update = AutoUpdaterDialog::isSupported();
AutoUpdaterDialog::cleanupAfterUpdate();
continue; continue;
} }
#ifdef ENABLE_RAINTEGRATION #ifdef ENABLE_RAINTEGRATION
@ -1911,6 +1910,10 @@ int main(int argc, char* argv[])
if (s_test_config_and_exit) if (s_test_config_and_exit)
return EXIT_SUCCESS; return EXIT_SUCCESS;
// Remove any previous-version remanants.
if (s_cleanup_after_update)
AutoUpdaterDialog::cleanupAfterUpdate();
// Set theme before creating any windows. // Set theme before creating any windows.
QtHost::UpdateApplicationTheme(); QtHost::UpdateApplicationTheme();

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team // SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+ // SPDX-License-Identifier: LGPL-3.0+
#include "Updater.h" #include "Updater.h"
@ -406,7 +406,6 @@ void Win32ProgressCallback::ModalInformation(const char* message)
MessageBoxW(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Information", MB_ICONINFORMATION | MB_OK); MessageBoxW(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Information", MB_ICONINFORMATION | MB_OK);
} }
static void WaitForProcessToExit(int process_id) static void WaitForProcessToExit(int process_id)
{ {
HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, process_id); HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, process_id);
@ -417,8 +416,6 @@ static void WaitForProcessToExit(int process_id)
CloseHandle(hProcess); CloseHandle(hProcess);
} }
#include "UpdaterExtractor.h"
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
{ {
Win32ProgressCallback progress; Win32ProgressCallback progress;
@ -512,6 +509,6 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLi
progress.DisplayFormattedInformation("Launching '%s'...", progress.DisplayFormattedInformation("Launching '%s'...",
StringUtil::WideStringToUTF8String(program_to_launch).c_str()); StringUtil::WideStringToUTF8String(program_to_launch).c_str());
ShellExecuteW(nullptr, L"open", program_to_launch.c_str(), nullptr, nullptr, SW_SHOWNORMAL); ShellExecuteW(nullptr, L"open", program_to_launch.c_str(), L"-updatecleanup", nullptr, SW_SHOWNORMAL);
return 0; return 0;
} }