From c305ed45063f7897cc77cdbfc67b28db97c95229 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Thu, 4 Dec 2025 09:57:01 -0500 Subject: [PATCH] fix(appimage): launch external processes with bundled linker This ensures that external processes (including our updater and Minecraft itself) maintain the same compatibility guarantees as the main binary Signed-off-by: Seth Flynn --- launcher/DesktopServices.cpp | 10 ++++ launcher/DesktopServices.h | 5 ++ launcher/FileSystem.cpp | 29 ++++++++++ launcher/FileSystem.h | 11 ++++ launcher/updater/PrismExternalUpdater.cpp | 57 ++++++++++--------- .../updater/prismupdater/PrismUpdater.cpp | 25 ++++++-- 6 files changed, 105 insertions(+), 32 deletions(-) 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..dac69616d 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -188,7 +188,19 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar 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 @@ -809,13 +821,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; }