diff --git a/launcher/DesktopServices.cpp b/launcher/DesktopServices.cpp index 841c1399c..b926dbca5 100644 --- a/launcher/DesktopServices.cpp +++ b/launcher/DesktopServices.cpp @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 dada513 + * Copyright (C) 2025 Seth Flynn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -75,6 +76,15 @@ bool isFlatpak() #endif } +bool isSelfContained() +{ +#ifdef Q_OS_LINUX + return QFileInfo(QCoreApplication::applicationFilePath()).fileName().startsWith("ld-linux"); +#else + return false; +#endif +} + bool isSnap() { #ifdef Q_OS_LINUX diff --git a/launcher/DesktopServices.h b/launcher/DesktopServices.h index 6c6208e82..5deb25872 100644 --- a/launcher/DesktopServices.h +++ b/launcher/DesktopServices.h @@ -37,6 +37,11 @@ bool openUrl(const QUrl& url); */ bool isFlatpak(); +/** + * Determine whether the launcher is running in a self-contained Linux bundle + */ +bool isSelfContained(); + /** * Determine whether the launcher is running in a Snap environment */ diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 30d0a9c4c..ab7c73493 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -4,6 +4,7 @@ * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2022 TheKodeToad * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> + * Copyright (C) 2025 Seth Flynn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -772,6 +773,34 @@ QString ResolveExecutable(QString path) return pathInfo.absoluteFilePath(); } +std::unique_ptr createProcess(const QString& program, const QStringList& arguments) +{ + qDebug() << "Creating process for" << program; + auto proc = std::unique_ptr(new QProcess()); + +#if defined(Q_OS_LINUX) + if (DesktopServices::isSelfContained()) { + const auto linkerPath = QCoreApplication::applicationFilePath(); + qDebug() << "Wrapping" << program << "with self-contained linker at" << linkerPath; + + QStringList wrappedArguments; + wrappedArguments << "--inhibit-cache" << program; + wrappedArguments += arguments; + + proc->setProgram(linkerPath); + proc->setArguments(wrappedArguments); + } else { + proc->setProgram(program); + proc->setArguments(arguments); + } +#else + proc->setProgram(program); + proc->setArguments(arguments); +#endif + + return proc; +} + /** * Normalize path * diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index f2676b147..db8545fd2 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -4,6 +4,7 @@ * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2022 TheKodeToad * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> + * Copyright (C) 2025 Seth Flynn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,11 +43,13 @@ #include +#include #include #include #include #include #include +#include #include namespace FS { @@ -333,6 +336,14 @@ QString pathTruncate(const QString& path, int depth); */ QString ResolveExecutable(QString path); +/** + * Create a QProcess instance + * + * This wrapper is currently only required for wrapping binaries called in + * self-contained AppImages (like those created by `go-appimage`) + */ +std::unique_ptr createProcess(const QString& program, const QStringList& arguments); + /** * Normalize path * diff --git a/launcher/updater/PrismExternalUpdater.cpp b/launcher/updater/PrismExternalUpdater.cpp index 69774dc04..6b237d947 100644 --- a/launcher/updater/PrismExternalUpdater.cpp +++ b/launcher/updater/PrismExternalUpdater.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -35,6 +34,7 @@ #include "StringUtils.h" #include "BuildConfig.h" +#include "FileSystem.h" #include "ui/dialogs/UpdateAvailableDialog.h" @@ -97,14 +97,9 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) progress.show(); QCoreApplication::processEvents(); - QProcess proc; auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); #if defined Q_OS_WIN32 exe_name.append(".exe"); - - auto env = QProcessEnvironment::systemEnvironment(); - env.insert("__COMPAT_LAYER", "RUNASINVOKER"); - proc.setProcessEnvironment(env); #else exe_name = QString("bin/%1").arg(exe_name); #endif @@ -113,15 +108,21 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) if (priv->allowBeta) args.append("--pre-release"); - proc.start(priv->appDir.absoluteFilePath(exe_name), args); - auto result_start = proc.waitForStarted(5000); + auto proc = FS::createProcess(priv->appDir.absoluteFilePath(exe_name), args); +#if defined Q_OS_WIN32 + auto env = QProcessEnvironment::systemEnvironment(); + env.insert("__COMPAT_LAYER", "RUNASINVOKER"); + proc->setProcessEnvironment(env); +#endif + proc->start(proc->program(), proc->arguments()); + auto result_start = proc->waitForStarted(5000); if (!result_start) { - auto err = proc.error(); + auto err = proc->error(); qDebug() << "Failed to start updater after 5 seconds." - << "reason:" << err << proc.errorString(); + << "reason:" << err << proc->errorString(); auto msgBox = QMessageBox(QMessageBox::Information, tr("Update Check Failed"), - tr("Failed to start after 5 seconds\nReason: %1.").arg(proc.errorString()), QMessageBox::Ok, priv->parent); + tr("Failed to start after 5 seconds\nReason: %1.").arg(proc->errorString()), QMessageBox::Ok, priv->parent); msgBox.setMinimumWidth(460); msgBox.adjustSize(); msgBox.exec(); @@ -133,16 +134,16 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) } QCoreApplication::processEvents(); - auto result_finished = proc.waitForFinished(60000); + auto result_finished = proc->waitForFinished(60000); if (!result_finished) { - proc.kill(); - auto err = proc.error(); - auto output = proc.readAll(); + proc->kill(); + auto err = proc->error(); + auto output = proc->readAll(); qDebug() << "Updater failed to close after 60 seconds." - << "reason:" << err << proc.errorString(); + << "reason:" << err << proc->errorString(); auto msgBox = QMessageBox(QMessageBox::Information, tr("Update Check Failed"), - tr("Updater failed to close 60 seconds\nReason: %1.").arg(proc.errorString()), QMessageBox::Ok, priv->parent); + tr("Updater failed to close 60 seconds\nReason: %1.").arg(proc->errorString()), QMessageBox::Ok, priv->parent); msgBox.setDetailedText(output); msgBox.setMinimumWidth(460); msgBox.adjustSize(); @@ -154,10 +155,10 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) return; } - auto exit_code = proc.exitCode(); + auto exit_code = proc->exitCode(); - auto std_output = proc.readAllStandardOutput(); - auto std_error = proc.readAllStandardError(); + auto std_output = proc->readAllStandardOutput(); + auto std_error = proc->readAllStandardError(); progress.hide(); QCoreApplication::processEvents(); @@ -335,14 +336,9 @@ void PrismExternalUpdater::offerUpdate(const QString& version_name, const QStrin void PrismExternalUpdater::performUpdate(const QString& version_tag) { - QProcess proc; auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); #if defined Q_OS_WIN32 exe_name.append(".exe"); - - auto env = QProcessEnvironment::systemEnvironment(); - env.insert("__COMPAT_LAYER", "RUNASINVOKER"); - proc.setProcessEnvironment(env); #else exe_name = QString("bin/%1").arg(exe_name); #endif @@ -351,9 +347,16 @@ void PrismExternalUpdater::performUpdate(const QString& version_tag) if (priv->allowBeta) args.append("--pre-release"); - auto result = proc.startDetached(priv->appDir.absoluteFilePath(exe_name), args); + auto proc = FS::createProcess(exe_name, args); +#if defined Q_OS_WIN32 + auto env = QProcessEnvironment::systemEnvironment(); + env.insert("__COMPAT_LAYER", "RUNASINVOKER"); + proc->setProcessEnvironment(env); +#endif + + auto result = proc->startDetached(priv->appDir.absoluteFilePath(exe_name), args); if (!result) { - qDebug() << "Failed to start updater:" << proc.error() << proc.errorString(); + qDebug() << "Failed to start updater:" << proc->error() << proc->errorString(); } QCoreApplication::exit(); } diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 90c3abe6f..cc28bfc1d 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -123,72 +123,20 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar logToConsole = parser.isSet("debug"); - auto updater_executable = QCoreApplication::applicationFilePath(); - -#ifdef Q_OS_MACOS - showFatalErrorMessage(tr("MacOS Not Supported"), tr("The updater does not support installations on MacOS")); -#endif - - if (updater_executable.startsWith("/tmp/.mount_")) { - m_isAppimage = true; - m_appimagePath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); - if (m_appimagePath.isEmpty()) { - showFatalErrorMessage(tr("Unsupported Installation"), - tr("Updater is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); - } - } - - m_isFlatpak = DesktopServices::isFlatpak(); - - QString prism_executable = FS::PathCombine(applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME); -#if defined Q_OS_WIN32 - prism_executable.append(".exe"); -#endif - - if (!QFileInfo(prism_executable).isFile()) { - showFatalErrorMessage(tr("Unsupported Installation"), tr("The updater can not find the main executable.")); - } - - m_prismExecutable = prism_executable; - - auto prism_update_url = parser.value("update-url"); - if (prism_update_url.isEmpty()) - prism_update_url = BuildConfig.UPDATER_GITHUB_REPO; - - m_prismRepoUrl = QUrl::fromUserInput(prism_update_url); - - m_checkOnly = parser.isSet("check-only"); - m_forceUpdate = parser.isSet("force"); - m_printOnly = parser.isSet("list"); - auto user_version = parser.value("install-version"); - if (!user_version.isEmpty()) { - m_userSelectedVersion = Version(user_version); - } - m_selectUI = parser.isSet("select-ui"); - m_allowDowngrade = parser.isSet("allow-downgrade"); - - auto version = parser.value("prism-version"); - if (!version.isEmpty()) { - if (version.contains('-')) { - auto index = version.indexOf('-'); - m_prsimVersionChannel = version.mid(index + 1); - version = version.left(index); - } else { - m_prsimVersionChannel = "stable"; - } - auto version_parts = version.split('.'); - m_prismVersionMajor = version_parts.takeFirst().toInt(); - m_prismVersionMinor = version_parts.takeFirst().toInt(); - if (!version_parts.isEmpty()) - m_prismVersionPatch = version_parts.takeFirst().toInt(); - else - m_prismVersionPatch = 0; - } - - m_allowPreRelease = parser.isSet("pre-release"); - QString origCwdPath = QDir::currentPath(); +#if defined(Q_OS_LINUX) + // NOTE(@getchoo): In order for `go-appimage` to generate self-contained AppImages, it executes apps from a bundled linker at + // /lib64 + // This is not the path to our actual binary, which we want + QString binPath; + if (DesktopServices::isSelfContained()) { + binPath = FS::PathCombine(applicationDirPath(), "../usr/bin"); + } else { + binPath = applicationDirPath(); + } +#else QString binPath = applicationDirPath(); +#endif { // find data director // Root path is used for updates and portable data @@ -363,6 +311,68 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar m_network->setProxy(proxy); } +#ifdef Q_OS_MACOS + showFatalErrorMessage(tr("MacOS Not Supported"), tr("The updater does not support installations on MacOS")); +#endif + + if (binPath.startsWith("/tmp/.mount_")) { + m_isAppimage = true; + m_appimagePath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); + if (m_appimagePath.isEmpty()) { + showFatalErrorMessage(tr("Unsupported Installation"), + tr("Updater is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); + } + } + + m_isFlatpak = DesktopServices::isFlatpak(); + + QString prism_executable = FS::PathCombine(binPath, BuildConfig.LAUNCHER_APP_BINARY_NAME); +#if defined Q_OS_WIN32 + prism_executable.append(".exe"); +#endif + + if (!QFileInfo(prism_executable).isFile()) { + showFatalErrorMessage(tr("Unsupported Installation"), tr("The updater can not find the main executable.")); + } + + m_prismExecutable = prism_executable; + + auto prism_update_url = parser.value("update-url"); + if (prism_update_url.isEmpty()) + prism_update_url = BuildConfig.UPDATER_GITHUB_REPO; + + m_prismRepoUrl = QUrl::fromUserInput(prism_update_url); + + m_checkOnly = parser.isSet("check-only"); + m_forceUpdate = parser.isSet("force"); + m_printOnly = parser.isSet("list"); + auto user_version = parser.value("install-version"); + if (!user_version.isEmpty()) { + m_userSelectedVersion = Version(user_version); + } + m_selectUI = parser.isSet("select-ui"); + m_allowDowngrade = parser.isSet("allow-downgrade"); + + auto version = parser.value("prism-version"); + if (!version.isEmpty()) { + if (version.contains('-')) { + auto index = version.indexOf('-'); + m_prsimVersionChannel = version.mid(index + 1); + version = version.left(index); + } else { + m_prsimVersionChannel = "stable"; + } + auto version_parts = version.split('.'); + m_prismVersionMajor = version_parts.takeFirst().toInt(); + m_prismVersionMinor = version_parts.takeFirst().toInt(); + if (!version_parts.isEmpty()) + m_prismVersionPatch = version_parts.takeFirst().toInt(); + else + m_prismVersionPatch = 0; + } + + m_allowPreRelease = parser.isSet("pre-release"); + auto marker_file_path = QDir(m_rootPath).absoluteFilePath(".prism_launcher_updater_unpack.marker"); auto marker_file = QFileInfo(marker_file_path); if (marker_file.exists()) { @@ -809,13 +819,16 @@ QFileInfo PrismUpdaterApp::downloadAsset(const GitHubReleaseAsset& asset) bool PrismUpdaterApp::callAppImageUpdate() { auto appimage_path = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); - QProcess proc = QProcess(); qDebug() << "Calling: AppImageUpdate" << appimage_path; - proc.setProgram(FS::PathCombine(m_rootPath, "bin", "AppImageUpdate.AppImage")); - proc.setArguments({ appimage_path }); - auto result = proc.startDetached(); + const auto program = FS::PathCombine(m_rootPath, "bin", "AppImageUpdate.AppImage"); + auto proc = FS::createProcess(program, { appimage_path }); + if (!proc) { + qCritical() << "Unable to create process:" << program; + return false; + } + auto result = proc->startDetached(); if (!result) - qDebug() << "Failed to start AppImageUpdate reason:" << proc.errorString(); + qDebug() << "Failed to start AppImageUpdate reason:" << proc->errorString(); return result; }