diff options
39 files changed, 624 insertions, 280 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1108fed6..0599c1d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,10 +23,9 @@ jobs: qt_ver: 5 - os: ubuntu-20.04 - appimage: true qt_ver: 6 qt_host: linux - qt_version: '6.3.1' + qt_version: '6.2.4' qt_modules: 'qt5compat qtimageformats' qt_path: /home/runner/work/PolyMC/Qt @@ -92,7 +91,7 @@ jobs: if: runner.os != 'Windows' && inputs.build_type == 'Debug' uses: hendrikmuhs/ccache-action@v1.2.1 with: - key: ${{ matrix.os }}-${{ matrix.appimage }} + key: ${{ matrix.os }}-qt${{ matrix.qt_ver }} - name: Setup ccache (Windows) if: runner.os == 'Windows' && inputs.build_type == 'Debug' @@ -115,9 +114,9 @@ jobs: uses: actions/cache@v3.0.2 with: path: '${{ github.workspace }}\.ccache' - key: ${{ matrix.os }}--qt${{ matrix.qt_ver }} + key: ${{ matrix.os }}-qt${{ matrix.qt_ver }} restore-keys: | - ${{ matrix.os }}--qt${{ matrix.qt_ver }} + ${{ matrix.os }}-qt${{ matrix.qt_ver }} - name: Set short version shell: bash @@ -138,7 +137,7 @@ jobs: brew install ninja extra-cmake-modules - name: Install Qt (Linux) - if: runner.os == 'Linux' && matrix.appimage != true + if: runner.os == 'Linux' && matrix.qt_ver != 6 run: | sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 @@ -162,7 +161,7 @@ jobs: aqtversion: ==2.1.* - name: Prepare AppImage (Linux) - if: runner.os == 'Linux' && matrix.appimage == true + if: runner.os == 'Linux' && matrix.qt_ver != 5 run: | wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage" @@ -281,7 +280,7 @@ jobs: makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi" - name: Package (Linux) - if: runner.os == 'Linux' && matrix.appimage != true + if: runner.os == 'Linux' run: | cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }} @@ -289,7 +288,7 @@ jobs: tar --owner root --group root -czf ../PolyMC.tar.gz * - name: Package (Linux, portable) - if: runner.os == 'Linux' && matrix.appimage != true + if: runner.os == 'Linux' run: | cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable @@ -298,7 +297,7 @@ jobs: tar -czf ../PolyMC-portable.tar.gz * - name: Package AppImage (Linux) - if: runner.os == 'Linux' && matrix.appimage == true + if: runner.os == 'Linux' && matrix.qt_ver != 5 shell: bash run: | cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr @@ -357,22 +356,36 @@ jobs: name: PolyMC-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }} path: PolyMC-Setup.exe - - name: Upload binary tarball (Linux) - if: runner.os == 'Linux' && matrix.appimage != true + - name: Upload binary tarball (Linux, Qt 5) + if: runner.os == 'Linux' && matrix.qt_ver != 6 uses: actions/upload-artifact@v3 with: name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }} path: PolyMC.tar.gz - - name: Upload binary tarball (Linux, portable) - if: runner.os == 'Linux' && matrix.appimage != true + - name: Upload binary tarball (Linux, portable, Qt 5) + if: runner.os == 'Linux' && matrix.qt_ver != 6 uses: actions/upload-artifact@v3 with: name: PolyMC-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }} path: PolyMC-portable.tar.gz + - name: Upload binary tarball (Linux, Qt 6) + if: runner.os == 'Linux' && matrix.qt_ver !=5 + uses: actions/upload-artifact@v3 + with: + name: PolyMC-${{ runner.os }}-Qt6-${{ env.VERSION }}-${{ inputs.build_type }} + path: PolyMC.tar.gz + + - name: Upload binary tarball (Linux, portable, Qt 6) + if: runner.os == 'Linux' && matrix.qt_ver != 5 + uses: actions/upload-artifact@v3 + with: + name: PolyMC-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }} + path: PolyMC-portable.tar.gz + - name: Upload AppImage (Linux) - if: runner.os == 'Linux' && matrix.appimage == true + if: runner.os == 'Linux' && matrix.qt_ver != 5 uses: actions/upload-artifact@v3 with: name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index af4a5f03..45ef7281 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -35,6 +35,8 @@ jobs: - name: Package artifacts properly run: | mv ${{ github.workspace }}/PolyMC-source PolyMC-${{ env.VERSION }} + mv PolyMC-Linux-Qt6-Portable*/PolyMC-portable.tar.gz PolyMC-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz + mv PolyMC-Linux-Qt6*/PolyMC.tar.gz PolyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz mv PolyMC-Linux-Portable*/PolyMC-portable.tar.gz PolyMC-Linux-Portable-${{ env.VERSION }}.tar.gz mv PolyMC-Linux*/PolyMC.tar.gz PolyMC-Linux-${{ env.VERSION }}.tar.gz mv PolyMC-*.AppImage/PolyMC-*.AppImage PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage @@ -70,6 +72,8 @@ jobs: PolyMC-Linux-Portable-${{ env.VERSION }}.tar.gz PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage PolyMC-Windows-Legacy-${{ env.VERSION }}.zip + PolyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz + PolyMC-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz PolyMC-Windows-Legacy-Portable-${{ env.VERSION }}.zip PolyMC-Windows-Legacy-Setup-${{ env.VERSION }}.exe PolyMC-Windows-${{ env.VERSION }}.zip @@ -64,7 +64,7 @@ The translation effort for PolyMC is hosted on [Weblate](https://hosted.weblate. ## Download information -To modify download information or change packaging information send a pull request or issue to the website [Here](https://github.com/PolyMC/polymc.github.io/blob/master/src/download.md) +To modify download information or change packaging information send a pull request or issue to the website [here](https://github.com/PolyMC/polymc.github.io/tree/master/src/download). ## Forking/Redistributing/Custom builds policy @@ -19,26 +19,26 @@ "libnbtplusplus": { "flake": false, "locked": { - "lastModified": 1591558203, - "narHash": "sha256-QgvNvaoFflCXEPCCFBCeZvYTpuiwScBG7EosUgFwFNQ=", - "owner": "multimc", + "lastModified": 1650031308, + "narHash": "sha256-TvVOjkUobYJD9itQYueELJX3wmecvEdCbJ0FinW2mL4=", + "owner": "PolyMC", "repo": "libnbtplusplus", - "rev": "dc72a20b7efd304d12af2025223fad07b4b78464", + "rev": "2203af7eeb48c45398139b583615134efd8d407f", "type": "github" }, "original": { - "owner": "multimc", + "owner": "PolyMC", "repo": "libnbtplusplus", "type": "github" } }, "nixpkgs": { "locked": { - "lastModified": 1654665288, - "narHash": "sha256-7blJpfoZEu7GKb84uh3io/5eSJNdaagXD9d15P9iQMs=", + "lastModified": 1658119717, + "narHash": "sha256-4upOZIQQ7Bc4CprqnHsKnqYfw+arJeAuU+QcpjYBXW0=", "owner": "nixos", "repo": "nixpkgs", - "rev": "43ecbe7840d155fa933ee8a500fb00dbbc651fc8", + "rev": "9eb60f25aff0d2218c848dd4574a0ab5e296cabe", "type": "github" }, "original": { @@ -4,7 +4,7 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; flake-compat = { url = "github:edolstra/flake-compat"; flake = false; }; - libnbtplusplus = { url = "github:multimc/libnbtplusplus"; flake = false; }; + libnbtplusplus = { url = "github:PolyMC/libnbtplusplus"; flake = false; }; }; outputs = { self, nixpkgs, libnbtplusplus, ... }: diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b540dcab..9c5c2ea8 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -117,6 +117,7 @@ set(NET_SOURCES net/NetAction.h net/NetJob.cpp net/NetJob.h + net/NetUtils.h net/PasteUpload.cpp net/PasteUpload.h net/Sink.h diff --git a/launcher/Launcher.in b/launcher/Launcher.in index 528e360e..68fac26a 100755 --- a/launcher/Launcher.in +++ b/launcher/Launcher.in @@ -18,13 +18,17 @@ LAUNCHER_NAME=@Launcher_APP_BINARY_NAME@ LAUNCHER_DIR="$(dirname "$(readlink -f "$0")")" echo "Launcher Dir: ${LAUNCHER_DIR}" -# Set up env - filter out input LD_ variables but pass them in under different names -export GAME_LIBRARY_PATH=${GAME_LIBRARY_PATH-${LD_LIBRARY_PATH}} -export GAME_PRELOAD=${GAME_PRELOAD-${LD_PRELOAD}} -export LD_LIBRARY_PATH="${LAUNCHER_DIR}/lib@LIB_SUFFIX@":$LAUNCHER_LIBRARY_PATH -export LD_PRELOAD=$LAUNCHER_PRELOAD -export QT_PLUGIN_PATH="${LAUNCHER_DIR}/plugins" -export QT_FONTPATH="${LAUNCHER_DIR}/fonts" +# Set up env. +# Pass our custom variables separately so that the launcher can remove them for child processes +export LAUNCHER_LD_LIBRARY_PATH="${LAUNCHER_DIR}/lib@LIB_SUFFIX@" +export LAUNCHER_LD_PRELOAD="" +export LAUNCHER_QT_PLUGIN_PATH="${LAUNCHER_DIR}/plugins" +export LAUNCHER_QT_FONTPATH="${LAUNCHER_DIR}/fonts" + +export LD_LIBRARY_PATH="$LAUNCHER_LD_LIBRARY_PATH:$LD_LIBRARY_PATH" +export LD_PRELOAD="$LAUNCHER_LD_PRELOAD:$LD_PRELOAD" +export QT_PLUGIN_PATH="$LAUNCHER_QT_PLUGIN_PATH:$QT_PLUGIN_PATH" +export QT_FONTPATH="$LAUNCHER_QT_FONTPATH:$QT_FONTPATH" # Detect missing dependencies... DEPS_LIST=`ldd "${LAUNCHER_DIR}"/plugins/*/*.so 2>/dev/null | grep "not found" | sort -u | awk -vORS=", " '{ print $1 }'` diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 71cca03b..04ca5094 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -141,9 +141,10 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QSet<QString> addedFiles; // Modify the jar - for (auto i = mods.constEnd(); i != mods.constBegin(); --i) + // This needs to be done in reverse-order to ensure we respect the loading order of components + for (auto i = mods.crbegin(); i != mods.crend(); i++) { - const Mod* mod = *i; + const auto* mod = *i; // do not merge disabled mods. if (!mod->enabled()) continue; diff --git a/launcher/QObjectPtr.h b/launcher/QObjectPtr.h index 57974939..173dc5e7 100644 --- a/launcher/QObjectPtr.h +++ b/launcher/QObjectPtr.h @@ -77,10 +77,12 @@ public: { return m_ptr; } - bool operator==(const shared_qobject_ptr<T>& other) { + template<typename U> + bool operator==(const shared_qobject_ptr<U>& other) const { return m_ptr == other.m_ptr; } - bool operator!=(const shared_qobject_ptr<T>& other) { + template<typename U> + bool operator!=(const shared_qobject_ptr<U>& other) const { return m_ptr != other.m_ptr; } diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index e7013142..2b19fca0 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -52,25 +52,24 @@ JavaUtils::JavaUtils() { } -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) -static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH) +QString stripVariableEntries(QString name, QString target, QString remove) { - QDir mmcBin(QCoreApplication::applicationDirPath()); - auto items = LD_LIBRARY_PATH.split(':'); - QStringList final; - for(auto & item: items) - { - QDir test(item); - if(test == mmcBin) - { - qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item; - continue; - } - final.append(item); + char delimiter = ':'; +#ifdef Q_OS_WIN32 + delimiter = ';'; +#endif + + auto targetItems = target.split(delimiter); + auto toRemove = remove.split(delimiter); + + for (QString item : toRemove) { + bool removed = targetItems.removeOne(item); + if (!removed) + qWarning() << "Entry" << item + << "could not be stripped from variable" << name; } - return final.join(':'); + return targetItems.join(delimiter); } -#endif QProcessEnvironment CleanEnviroment() { @@ -89,6 +88,16 @@ QProcessEnvironment CleanEnviroment() "JAVA_OPTIONS", "JAVA_TOOL_OPTIONS" }; + + QStringList stripped = + { +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + "LD_LIBRARY_PATH", + "LD_PRELOAD", +#endif + "QT_PLUGIN_PATH", + "QT_FONTPATH" + }; for(auto key: rawenv.keys()) { auto value = rawenv.value(key); @@ -98,19 +107,22 @@ QProcessEnvironment CleanEnviroment() qDebug() << "Env: ignoring" << key << value; continue; } - // filter PolyMC-related things - if(key.startsWith("QT_")) + + // These are used to strip the original variables + // If there is "LD_LIBRARY_PATH" and "LAUNCHER_LD_LIBRARY_PATH", we want to + // remove all values in "LAUNCHER_LD_LIBRARY_PATH" from "LD_LIBRARY_PATH" + if(key.startsWith("LAUNCHER_")) { qDebug() << "Env: ignoring" << key << value; continue; } -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - // Do not pass LD_* variables to java. They were intended for PolyMC - if(key.startsWith("LD_")) + if(stripped.contains(key)) { - qDebug() << "Env: ignoring" << key << value; - continue; + QString newValue = stripVariableEntries(key, value, rawenv.value("LAUNCHER_" + key)); + + qDebug() << "Env: stripped" << key << value << "to" << newValue; } +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) // Strip IBus // IBus is a Linux IME framework. For some reason, it breaks MC? if (key == "XMODIFIERS" && value.contains(IBUS)) @@ -119,22 +131,12 @@ QProcessEnvironment CleanEnviroment() value.replace(IBUS, ""); qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value; } - if(key == "GAME_PRELOAD") - { - env.insert("LD_PRELOAD", value); - continue; - } - if(key == "GAME_LIBRARY_PATH") - { - env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value)); - continue; - } #endif // qDebug() << "Env: " << key << value; env.insert(key, value); } #ifdef Q_OS_LINUX - // HACK: Workaround for QTBUG42500 + // HACK: Workaround for QTBUG-42500 if(!env.contains("LD_LIBRARY_PATH")) { env.insert("LD_LIBRARY_PATH", ""); diff --git a/launcher/java/JavaUtils.h b/launcher/java/JavaUtils.h index 26d8003b..9b69b516 100644 --- a/launcher/java/JavaUtils.h +++ b/launcher/java/JavaUtils.h @@ -24,6 +24,7 @@ #include <windows.h> #endif +QString stripVariableEntries(QString name, QString target, QString remove); QProcessEnvironment CleanEnviroment(); class JavaUtils : public QObject diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp index 3aa95052..28fcc4f4 100644 --- a/launcher/launch/LaunchTask.cpp +++ b/launcher/launch/LaunchTask.cpp @@ -282,35 +282,22 @@ void LaunchTask::emitFailed(QString reason) Task::emitFailed(reason); } -void LaunchTask::substituteVariables(const QStringList &args) const +void LaunchTask::substituteVariables(QStringList &args) const { - auto variables = m_instance->getVariables(); - auto envVariables = QProcessEnvironment::systemEnvironment(); + auto env = m_instance->createEnvironment(); - for (auto arg : args) { - for (auto key : variables) - { - arg.replace("$" + key, variables.value(key)); - } - for (auto env : envVariables.keys()) - { - arg.replace("$" + env, envVariables.value(env)); - } + for (auto key : env.keys()) + { + args.replaceInStrings("$" + key, env.value(key)); } } -QString LaunchTask::substituteVariables(const QString &cmd) const +void LaunchTask::substituteVariables(QString &cmd) const { - QString out = cmd; - auto variables = m_instance->getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) - { - out.replace("$" + it.key(), it.value()); - } - auto env = QProcessEnvironment::systemEnvironment(); - for (auto var : env.keys()) + auto env = m_instance->createEnvironment(); + + for (auto key : env.keys()) { - out.replace("$" + var, env.value(var)); + cmd.replace("$" + key, env.value(key)); } - return out; } diff --git a/launcher/launch/LaunchTask.h b/launcher/launch/LaunchTask.h index 2efe1fa2..9c72b23f 100644 --- a/launcher/launch/LaunchTask.h +++ b/launcher/launch/LaunchTask.h @@ -105,8 +105,8 @@ public: /* methods */ shared_qobject_ptr<LogModel> getLogModel(); public: - void substituteVariables(const QStringList &args) const; - QString substituteVariables(const QString &cmd) const; + void substituteVariables(QStringList &args) const; + void substituteVariables(QString &cmd) const; QString censorPrivateInfo(QString in); protected: /* methods */ diff --git a/launcher/launch/steps/PostLaunchCommand.cpp b/launcher/launch/steps/PostLaunchCommand.cpp index cf765bc0..ccf07f22 100644 --- a/launcher/launch/steps/PostLaunchCommand.cpp +++ b/launcher/launch/steps/PostLaunchCommand.cpp @@ -56,9 +56,10 @@ void PostLaunchCommand::executeTask() const QString program = args.takeFirst(); m_process.start(program, args); #else - QString postlaunch_cmd = m_parent->substituteVariables(m_command); - emit logLine(tr("Running Post-Launch command: %1").arg(postlaunch_cmd), MessageLevel::Launcher); - m_process.start(postlaunch_cmd); + m_parent->substituteVariables(m_command); + + emit logLine(tr("Running Post-Launch command: %1").arg(m_command), MessageLevel::Launcher); + m_process.start(m_command); #endif } diff --git a/launcher/launch/steps/PreLaunchCommand.cpp b/launcher/launch/steps/PreLaunchCommand.cpp index bf7d27eb..0b0392cb 100644 --- a/launcher/launch/steps/PreLaunchCommand.cpp +++ b/launcher/launch/steps/PreLaunchCommand.cpp @@ -56,9 +56,10 @@ void PreLaunchCommand::executeTask() const QString program = args.takeFirst(); m_process.start(program, args); #else - QString prelaunch_cmd = m_parent->substituteVariables(m_command); - emit logLine(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd), MessageLevel::Launcher); - m_process.start(prelaunch_cmd); + m_parent->substituteVariables(m_command); + + emit logLine(tr("Running Pre-Launch command: %1").arg(m_command), MessageLevel::Launcher); + m_process.start(m_command); #endif } diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 360e754d..5a6f8de0 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -709,15 +709,15 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr { if(mod->type() == Mod::MOD_FOLDER) { - out << u8" [📁] " + mod->fileinfo().completeBaseName() + " (folder)"; + out << u8" [🖿] " + mod->fileinfo().completeBaseName() + " (folder)"; continue; } if(mod->enabled()) { - out << u8" [✔️]" + mod->fileinfo().completeBaseName(); + out << u8" [✔] " + mod->fileinfo().completeBaseName(); } else { - out << u8" [❌] " + mod->fileinfo().completeBaseName() + " (disabled)"; + out << u8" [✘] " + mod->fileinfo().completeBaseName() + " (disabled)"; } } diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index 2b851e18..b3b57c74 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -109,8 +109,10 @@ QStringList AccountList::profileNames() const { void AccountList::addAccount(const MinecraftAccountPtr account) { - // NOTE: Do not allow adding something that's already there - if(m_accounts.contains(account)) { + // NOTE: Do not allow adding something that's already there. We shouldn't let it continue + // because of the signal / slot connections after this. + if (m_accounts.contains(account)) { + qDebug() << "Tried to add account that's already on the accounts list!"; return; } @@ -123,6 +125,8 @@ void AccountList::addAccount(const MinecraftAccountPtr account) if(profileId.size()) { auto existingAccount = findAccountByProfileId(profileId); if(existingAccount != -1) { + qDebug() << "Replacing old account with a new one with the same profile ID!"; + MinecraftAccountPtr existingAccountPtr = m_accounts[existingAccount]; m_accounts[existingAccount] = account; if(m_defaultAccount == existingAccountPtr) { @@ -138,9 +142,12 @@ void AccountList::addAccount(const MinecraftAccountPtr account) // if we don't have this profileId yet, add the account to the end int row = m_accounts.count(); + qDebug() << "Inserting account at index" << row; + beginInsertRows(QModelIndex(), row, row); m_accounts.append(account); endInsertRows(); + onListChanged(); } diff --git a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp index f5697223..8c53f037 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp @@ -5,6 +5,7 @@ #include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" #include "minecraft/auth/AccountTask.h" +#include "net/NetUtils.h" LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) { @@ -58,10 +59,18 @@ void LauncherLoginStep::onRequestDone( #ifndef NDEBUG qDebug() << data; #endif - emit finished( - AccountTaskState::STATE_FAILED_SOFT, - tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_) - ); + if (Net::isApplicationError(error)) { + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_) + ); + } + else { + emit finished( + AccountTaskState::STATE_OFFLINE, + tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_) + ); + } return; } diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index add91659..b39b9326 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -4,6 +4,7 @@ #include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" +#include "net/NetUtils.h" MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) { @@ -64,10 +65,18 @@ void MinecraftProfileStep::onRequestDone( qWarning() << " Response:"; qWarning() << QString::fromUtf8(data); - emit finished( - AccountTaskState::STATE_FAILED_SOFT, - tr("Minecraft Java profile acquisition failed.") - ); + if (Net::isApplicationError(error)) { + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_) + ); + } + else { + emit finished( + AccountTaskState::STATE_OFFLINE, + tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_) + ); + } return; } if(!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) { diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp index d3035272..6a1eb7a0 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp @@ -4,6 +4,7 @@ #include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" +#include "net/NetUtils.h" MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) { @@ -67,10 +68,18 @@ void MinecraftProfileStepMojang::onRequestDone( qWarning() << " Response:"; qWarning() << QString::fromUtf8(data); - emit finished( - AccountTaskState::STATE_FAILED_SOFT, - tr("Minecraft Java profile acquisition failed.") - ); + if (Net::isApplicationError(error)) { + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_) + ); + } + else { + emit finished( + AccountTaskState::STATE_OFFLINE, + tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_) + ); + } return; } if(!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) { diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp index 589768e3..14bde47e 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -6,6 +6,7 @@ #include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" +#include "net/NetUtils.h" XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token *token, QString relyingParty, QString authorizationKind): AuthStep(data), @@ -62,10 +63,24 @@ void XboxAuthorizationStep::onRequestDone( #endif if (error != QNetworkReply::NoError) { qWarning() << "Reply error:" << error; - if(!processSTSError(error, data, headers)) { + if (Net::isApplicationError(error)) { + if(!processSTSError(error, data, headers)) { + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, error) + ); + } + else { + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, requestor->errorString_) + ); + } + } + else { emit finished( - AccountTaskState::STATE_FAILED_SOFT, - tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, error) + AccountTaskState::STATE_OFFLINE, + tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, requestor->errorString_) ); } return; diff --git a/launcher/minecraft/auth/steps/XboxProfileStep.cpp b/launcher/minecraft/auth/steps/XboxProfileStep.cpp index 9f50138e..738fe1db 100644 --- a/launcher/minecraft/auth/steps/XboxProfileStep.cpp +++ b/launcher/minecraft/auth/steps/XboxProfileStep.cpp @@ -6,6 +6,7 @@ #include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" +#include "net/NetUtils.h" XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) { @@ -58,10 +59,18 @@ void XboxProfileStep::onRequestDone( #ifndef NDEBUG qDebug() << data; #endif - finished( - AccountTaskState::STATE_FAILED_SOFT, - tr("Failed to retrieve the Xbox profile.") - ); + if (Net::isApplicationError(error)) { + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_) + ); + } + else { + emit finished( + AccountTaskState::STATE_OFFLINE, + tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_) + ); + } return; } diff --git a/launcher/minecraft/auth/steps/XboxUserStep.cpp b/launcher/minecraft/auth/steps/XboxUserStep.cpp index a38a28e4..53069597 100644 --- a/launcher/minecraft/auth/steps/XboxUserStep.cpp +++ b/launcher/minecraft/auth/steps/XboxUserStep.cpp @@ -4,6 +4,7 @@ #include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" +#include "net/NetUtils.h" XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) { @@ -53,7 +54,17 @@ void XboxUserStep::onRequestDone( if (error != QNetworkReply::NoError) { qWarning() << "Reply error:" << error; - emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed.")); + if (Net::isApplicationError(error)) { + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("XBox user authentication failed: %1").arg(requestor->errorString_) + ); + } + else { + emit finished( + AccountTaskState::STATE_OFFLINE, + tr("XBox user authentication failed: %1").arg(requestor->errorString_) + ); + } return; } diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index c1f56658..058d2471 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -7,6 +7,13 @@ Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAcc : m_network(network), m_toProcess(toProcess) {} +bool Flame::FileResolvingTask::abort() +{ + if (m_dljob) + return m_dljob->abort(); + return true; +} + void Flame::FileResolvingTask::executeTask() { setStatus(tr("Resolving mod IDs...")); diff --git a/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h index 87981f0a..f71b87ce 100644 --- a/launcher/modplatform/flame/FileResolvingTask.h +++ b/launcher/modplatform/flame/FileResolvingTask.h @@ -13,6 +13,9 @@ public: explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest &toProcess); virtual ~FileResolvingTask() {}; + bool canAbort() const override { return true; } + bool abort() override; + const Flame::Manifest &getResults() const { return m_toProcess; diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 68a4589b..8dd3a846 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -123,7 +123,7 @@ void FlameCheckUpdate::executeTask() continue; } - setStatus(tr("Getting API response from CurseForge for '%1'").arg(mod->name())); + setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name())); setProgress(i++, m_mods.size()); auto latest_ver = api.getLatestVersion({ mod->metadata()->project_id.toString(), m_game_versions, m_loaders }); @@ -145,7 +145,7 @@ void FlameCheckUpdate::executeTask() if (latest_ver.downloadUrl.isEmpty() && latest_ver.fileId != mod->metadata()->file_id) { auto pack = getProjectInfo(latest_ver); auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver.fileId.toString()); - emit checkFailed(mod, tr("Mod has a new update available, but is opted-out on CurseForge"), recover_url); + emit checkFailed(mod, tr("Mod has a new update available, but is not downloadable using CurseForge."), recover_url); continue; } diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index cac432cd..16013070 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher + * Copyright (C) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * * 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 @@ -40,103 +42,190 @@ #include "Json.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "modplatform/flame/PackManifest.h" #include "net/ChecksumValidator.h" #include "settings/INISettingsObject.h" -#include "BuildConfig.h" #include "Application.h" +#include "BuildConfig.h" +#include "ui/dialogs/ScrollMessageBox.h" namespace ModpacksCH { -PackInstallTask::PackInstallTask(Modpack pack, QString version) -{ - m_pack = pack; - m_version_name = version; -} +PackInstallTask::PackInstallTask(Modpack pack, QString version, QWidget* parent) + : m_pack(std::move(pack)), m_version_name(std::move(version)), m_parent(parent) +{} bool PackInstallTask::abort() { - if(abortable) - { - return jobPtr->abort(); - } - return false; + bool aborted = true; + + if (m_net_job) + aborted &= m_net_job->abort(); + if (m_mod_id_resolver_task) + aborted &= m_mod_id_resolver_task->abort(); + + // FIXME: This should be 'emitAborted()', but InstanceStaging doesn't connect to the abort signal yet... + if (aborted) + emitFailed(tr("Aborted")); + + return aborted; } void PackInstallTask::executeTask() { - // Find pack version - bool found = false; - VersionInfo version; + setStatus(tr("Getting the manifest...")); - for(auto vInfo : m_pack.versions) { - if (vInfo.name == m_version_name) { - found = true; - version = vInfo; - break; - } - } + // Find pack version + auto version_it = std::find_if(m_pack.versions.constBegin(), m_pack.versions.constEnd(), + [this](ModpacksCH::VersionInfo const& a) { return a.name == m_version_name; }); - if(!found) { + if (version_it == m_pack.versions.constEnd()) { emitFailed(tr("Failed to find pack version %1").arg(m_version_name)); return; } - auto *netJob = new NetJob("ModpacksCH::VersionFetch", APPLICATION->network()); + auto version = *version_it; + + auto* netJob = new NetJob("ModpacksCH::VersionFetch", APPLICATION->network()); + auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2").arg(m_pack.id).arg(version.id); - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); - jobPtr = netJob; - jobPtr->start(); + netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &m_response)); + + QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onManifestDownloadSucceeded); + QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onManifestDownloadFailed); + QObject::connect(netJob, &NetJob::progress, this, &PackInstallTask::setProgress); - QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); - QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed); + m_net_job = netJob; + + netJob->start(); } -void PackInstallTask::onDownloadSucceeded() +void PackInstallTask::onManifestDownloadSucceeded() { - jobPtr.reset(); - - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); - if(parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << response; + m_net_job.reset(); + + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << m_response; return; } - auto obj = doc.object(); - ModpacksCH::Version version; - try - { + try { + auto obj = Json::requireObject(doc); ModpacksCH::loadVersion(version, obj); - } - catch (const JSONValidationError &e) - { + } catch (const JSONValidationError& e) { emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); return; } + m_version = version; - downloadPack(); + resolveMods(); } -void PackInstallTask::onDownloadFailed(QString reason) +void PackInstallTask::resolveMods() { - jobPtr.reset(); - emitFailed(reason); + setStatus(tr("Resolving mods...")); + setProgress(0, 100); + + m_file_id_map.clear(); + + Flame::Manifest manifest; + int index = 0; + + for (auto const& file : m_version.files) { + if (!file.serverOnly && file.url.isEmpty()) { + if (file.curseforge.file_id <= 0) { + emitFailed(tr("Invalid manifest: There's no information available to download the file '%1'!").arg(file.name)); + return; + } + + Flame::File flame_file; + flame_file.projectId = file.curseforge.project_id; + flame_file.fileId = file.curseforge.file_id; + flame_file.hash = file.sha1; + + manifest.files.insert(flame_file.fileId, flame_file); + m_file_id_map.append(flame_file.fileId); + } else { + m_file_id_map.append(-1); + } + + index++; + } + + m_mod_id_resolver_task = new Flame::FileResolvingTask(APPLICATION->network(), manifest); + + connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::succeeded, this, &PackInstallTask::onResolveModsSucceeded); + connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onResolveModsFailed); + connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::progress, this, &PackInstallTask::setProgress); + + m_mod_id_resolver_task->start(); +} + +void PackInstallTask::onResolveModsSucceeded() +{ + m_abortable = false; + + QString text; + auto anyBlocked = false; + + Flame::Manifest results = m_mod_id_resolver_task->getResults(); + for (int index = 0; index < m_file_id_map.size(); index++) { + auto const file_id = m_file_id_map.at(index); + if (file_id < 0) + continue; + + Flame::File results_file = results.files[file_id]; + VersionFile& local_file = m_version.files[index]; + + // First check for blocked mods + if (!results_file.resolved || results_file.url.isEmpty()) { + QString type(local_file.type); + + type[0] = type[0].toUpper(); + text += QString("%1: %2 - <a href='%3'>%3</a><br/>").arg(type, local_file.name, results_file.websiteUrl); + anyBlocked = true; + } else { + local_file.url = results_file.url.toString(); + } + } + + m_mod_id_resolver_task.reset(); + + if (anyBlocked) { + qDebug() << "Blocked files found, displaying file list"; + + auto message_dialog = new ScrollMessageBox(m_parent, tr("Blocked files found"), + tr("The following files are not available for download in third party launchers.<br/>" + "You will need to manually download them and add them to the instance."), + text); + + if (message_dialog->exec() == QDialog::Accepted) + downloadPack(); + else + abort(); + } else { + downloadPack(); + } } void PackInstallTask::downloadPack() { setStatus(tr("Downloading mods...")); - jobPtr = new NetJob(tr("Mod download"), APPLICATION->network()); - for(auto file : m_version.files) { - if(file.serverOnly) continue; + auto* jobPtr = new NetJob(tr("Mod download"), APPLICATION->network()); + for (auto const& file : m_version.files) { + if (file.serverOnly || file.url.isEmpty()) + continue; - QFileInfo fileName(file.name); - auto cacheName = fileName.completeBaseName() + "-" + file.sha1 + "." + fileName.suffix(); + QFileInfo file_info(file.name); + auto cacheName = file_info.completeBaseName() + "-" + file.sha1 + "." + file_info.suffix(); auto entry = APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", cacheName); entry->setStale(true); @@ -144,58 +233,64 @@ void PackInstallTask::downloadPack() auto relpath = FS::PathCombine("minecraft", file.path, file.name); auto path = FS::PathCombine(m_stagingPath, relpath); - if (filesToCopy.contains(path)) { + if (m_files_to_copy.contains(path)) { qWarning() << "Ignoring" << file.url << "as a file of that path is already downloading."; continue; } + qDebug() << "Will download" << file.url << "to" << path; - filesToCopy[path] = entry->getFullPath(); + m_files_to_copy[path] = entry->getFullPath(); auto dl = Net::Download::makeCached(file.url, entry); if (!file.sha1.isEmpty()) { auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1()); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); } + jobPtr->addNetAction(dl); } - connect(jobPtr.get(), &NetJob::succeeded, this, [&]() - { - abortable = false; - jobPtr.reset(); - install(); - }); - connect(jobPtr.get(), &NetJob::failed, [&](QString reason) - { - abortable = false; - jobPtr.reset(); - emitFailed(reason); - }); - connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total) - { - abortable = true; - setProgress(current, total); - }); + connect(jobPtr, &NetJob::succeeded, this, &PackInstallTask::onModDownloadSucceeded); + connect(jobPtr, &NetJob::failed, this, &PackInstallTask::onModDownloadFailed); + connect(jobPtr, &NetJob::progress, this, &PackInstallTask::setProgress); + m_net_job = jobPtr; jobPtr->start(); + + m_abortable = true; +} + +void PackInstallTask::onModDownloadSucceeded() +{ + m_net_job.reset(); + install(); } void PackInstallTask::install() { - setStatus(tr("Copying modpack files")); + setStatus(tr("Copying modpack files...")); + setProgress(0, m_files_to_copy.size()); + QCoreApplication::processEvents(); + + m_abortable = false; - for (auto iter = filesToCopy.begin(); iter != filesToCopy.end(); iter++) { - auto &to = iter.key(); - auto &from = iter.value(); + int i = 0; + for (auto iter = m_files_to_copy.constBegin(); iter != m_files_to_copy.constEnd(); iter++) { + auto& to = iter.key(); + auto& from = iter.value(); FS::copy fileCopyOperation(from, to); - if(!fileCopyOperation()) { + if (!fileCopyOperation()) { qWarning() << "Failed to copy" << from << "to" << to; emitFailed(tr("Failed to copy files")); return; } + + setProgress(i++, m_files_to_copy.size()); + QCoreApplication::processEvents(); } - setStatus(tr("Installing modpack")); + setStatus(tr("Installing modpack...")); + QCoreApplication::processEvents(); auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath); @@ -205,20 +300,20 @@ void PackInstallTask::install() auto components = instance.getPackProfile(); components->buildingFromScratch(); - for(auto target : m_version.targets) { - if(target.type == "game" && target.name == "minecraft") { + for (auto target : m_version.targets) { + if (target.type == "game" && target.name == "minecraft") { components->setComponentVersion("net.minecraft", target.version, true); break; } } - for(auto target : m_version.targets) { - if(target.type != "modloader") continue; + for (auto target : m_version.targets) { + if (target.type != "modloader") + continue; - if(target.name == "forge") { + if (target.name == "forge") { components->setComponentVersion("net.minecraftforge", target.version); - } - else if(target.name == "fabric") { + } else if (target.name == "fabric") { components->setComponentVersion("net.fabricmc.fabric-loader", target.version); } } @@ -245,4 +340,20 @@ void PackInstallTask::install() emitSucceeded(); } +void PackInstallTask::onManifestDownloadFailed(QString reason) +{ + m_net_job.reset(); + emitFailed(reason); +} +void PackInstallTask::onResolveModsFailed(QString reason) +{ + m_net_job.reset(); + emitFailed(reason); } +void PackInstallTask::onModDownloadFailed(QString reason) +{ + m_net_job.reset(); + emitFailed(reason); +} + +} // namespace ModpacksCH diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.h b/launcher/modplatform/modpacksch/FTBPackInstallTask.h index ff59b695..e63ca0df 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.h +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.h @@ -1,18 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only /* - * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> - * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com> + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 flowln <flowlnlnln@gmail.com> + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * 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 + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> + * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #pragma once @@ -20,44 +40,60 @@ #include "FTBPackManifest.h" #include "InstanceTask.h" +#include "QObjectPtr.h" +#include "modplatform/flame/FileResolvingTask.h" #include "net/NetJob.h" +#include <QWidget> + namespace ModpacksCH { -class PackInstallTask : public InstanceTask +class PackInstallTask final : public InstanceTask { Q_OBJECT public: - explicit PackInstallTask(Modpack pack, QString version); - virtual ~PackInstallTask(){} + explicit PackInstallTask(Modpack pack, QString version, QWidget* parent = nullptr); + ~PackInstallTask() override = default; - bool canAbort() const override { return true; } + bool canAbort() const override { return m_abortable; } bool abort() override; protected: - virtual void executeTask() override; + void executeTask() override; private slots: - void onDownloadSucceeded(); - void onDownloadFailed(QString reason); + void onManifestDownloadSucceeded(); + void onResolveModsSucceeded(); + void onModDownloadSucceeded(); + + void onManifestDownloadFailed(QString reason); + void onResolveModsFailed(QString reason); + void onModDownloadFailed(QString reason); private: + void resolveMods(); void downloadPack(); void install(); private: - bool abortable = false; + bool m_abortable = true; + + NetJob::Ptr m_net_job = nullptr; + shared_qobject_ptr<Flame::FileResolvingTask> m_mod_id_resolver_task = nullptr; + + QList<int> m_file_id_map; - NetJob::Ptr jobPtr; - QByteArray response; + QByteArray m_response; Modpack m_pack; QString m_version_name; Version m_version; - QMap<QString, QString> filesToCopy; + QMap<QString, QString> m_files_to_copy; + //FIXME: nuke + QWidget* m_parent; }; } diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.cpp b/launcher/modplatform/modpacksch/FTBPackManifest.cpp index e2d47a5b..421527ae 100644 --- a/launcher/modplatform/modpacksch/FTBPackManifest.cpp +++ b/launcher/modplatform/modpacksch/FTBPackManifest.cpp @@ -1,18 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only /* - * Copyright 2020 Jamie Mansfield <jmansfield@cadixdev.org> - * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com> + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * 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 + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020 Jamie Mansfield <jmansfield@cadixdev.org> + * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include "FTBPackManifest.h" @@ -127,13 +146,16 @@ static void loadVersionFile(ModpacksCH::VersionFile & a, QJsonObject & obj) a.path = Json::requireString(obj, "path"); a.name = Json::requireString(obj, "name"); a.version = Json::requireString(obj, "version"); - a.url = Json::requireString(obj, "url"); + a.url = Json::ensureString(obj, "url"); // optional a.sha1 = Json::requireString(obj, "sha1"); a.size = Json::requireInteger(obj, "size"); a.clientOnly = Json::requireBoolean(obj, "clientonly"); a.serverOnly = Json::requireBoolean(obj, "serveronly"); a.optional = Json::requireBoolean(obj, "optional"); a.updated = Json::requireInteger(obj, "updated"); + auto curseforgeObj = Json::ensureObject(obj, "curseforge"); // optional + a.curseforge.project_id = Json::ensureInteger(curseforgeObj, "project"); + a.curseforge.file_id = Json::ensureInteger(curseforgeObj, "file"); } void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj) diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.h b/launcher/modplatform/modpacksch/FTBPackManifest.h index da45d8ac..a8b6f35e 100644 --- a/launcher/modplatform/modpacksch/FTBPackManifest.h +++ b/launcher/modplatform/modpacksch/FTBPackManifest.h @@ -1,18 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only /* - * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> - * Copyright 2020 Petr Mrazek <peterix@gmail.com> + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * 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 + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> + * Copyright 2020 Petr Mrazek <peterix@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #pragma once @@ -97,6 +116,12 @@ struct VersionTarget int64_t updated; }; +struct VersionFileCurseForge +{ + int project_id; + int file_id; +}; + struct VersionFile { int id; @@ -111,6 +136,7 @@ struct VersionFile bool serverOnly; bool optional; int64_t updated; + VersionFileCurseForge curseforge; }; struct Version diff --git a/launcher/net/NetUtils.h b/launcher/net/NetUtils.h new file mode 100644 index 00000000..fa3bd8c0 --- /dev/null +++ b/launcher/net/NetUtils.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + * 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 + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <QNetworkReply> +#include <QSet> + +namespace Net { + inline bool isApplicationError(QNetworkReply::NetworkError x) { + // Mainly taken from https://github.com/qt/qtbase/blob/dev/src/network/access/qhttpthreaddelegate.cpp + static QSet<QNetworkReply::NetworkError> errors = { + QNetworkReply::ProtocolInvalidOperationError, + QNetworkReply::AuthenticationRequiredError, + QNetworkReply::ContentAccessDenied, + QNetworkReply::ContentNotFoundError, + QNetworkReply::ContentOperationNotPermittedError, + QNetworkReply::ProxyAuthenticationRequiredError, + QNetworkReply::ContentConflictError, + QNetworkReply::ContentGoneError, + QNetworkReply::InternalServerError, + QNetworkReply::OperationNotImplementedError, + QNetworkReply::ServiceUnavailableError, + QNetworkReply::UnknownServerError, + QNetworkReply::UnknownContentError + }; + return errors.contains(x); + } +} // namespace Net diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index b6e76ff1..d73c8ebb 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -158,8 +158,9 @@ void ModUpdateDialog::checkCandidates() if (!reason.isEmpty()) text += tr("Reason: %1").arg(reason) + "<br>"; if (!recover_url.isEmpty()) - text += tr("Possible solution: ") + tr("Getting the latest version manually:") + "<br>" + - QString("<a href='%1'>").arg(recover_url.toString()) + recover_url.toString() + "</a><br>"; + //: %1 is the link to download it manually + text += tr("Possible solution: Getting the latest version manually:<br>%1<br>") + .arg(QString("<a href='%1'>%1</a>").arg(recover_url.toString())); text += "<br>"; } @@ -241,9 +242,9 @@ auto ModUpdateDialog::ensureMetadata() -> bool } ChooseProviderDialog chooser(this); - chooser.setDescription(tr("This mod (%1) does not have a metadata yet. We need to create one in order to keep relevant " - "information on how to update this " - "mod. To do this, please select a mod provider from which we can search for updates for %1.") + chooser.setDescription(tr("The mod '%1' does not have a metadata yet. We need to generate it in order to track relevant " + "information on how to update this mod. " + "To do this, please select a mod provider which we can use to check for updates for this mod.") .arg(candidate->name())); auto confirmed = chooser.exec() == QDialog::DialogCode::Accepted; @@ -330,7 +331,7 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::P m_second_try_metadata->addTask(task); } else { - QString reason{ tr("Didn't find a valid version on the selected mod provider(s)") }; + QString reason{ tr("Couldn't find a valid version on the selected mod provider(s)") }; m_failed_metadata.append({mod, reason}); } diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index a79bc837..3c7f53d3 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -43,8 +43,8 @@ void ProgressDialog::setSkipButton(bool present, QString label) void ProgressDialog::on_skipButton_clicked(bool checked) { Q_UNUSED(checked); - task->abort(); - QDialog::reject(); + if (task->abort()) + QDialog::reject(); } ProgressDialog::~ProgressDialog() diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index d06f412b..69c20309 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -32,13 +32,13 @@ class SortProxy : public QSortFilterProxyModel { const auto& mod = model->at(source_row); - if (mod.name().contains(filterRegularExpression())) + if (filterRegularExpression().match(mod.name()).hasMatch()) return true; - if (mod.description().contains(filterRegularExpression())) + if (filterRegularExpression().match(mod.description()).hasMatch()) return true; for (auto& author : mod.authors()) { - if (author.contains(filterRegularExpression())) { + if (filterRegularExpression().match(author).hasMatch()) { return true; } } @@ -182,7 +182,7 @@ void ExternalResourcesPage::retranslate() void ExternalResourcesPage::filterTextChanged(const QString& newContents) { m_viewFilter = newContents; - m_filterModel->setFilterFixedString(m_viewFilter); + m_filterModel->setFilterRegularExpression(m_viewFilter); } void ExternalResourcesPage::runningStateChanged(bool running) diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui index 8edcfd64..a13666b2 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.ui +++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui @@ -155,7 +155,7 @@ <string>Check for &Updates</string> </property> <property name="toolTip"> - <string>"Tries to find / update all selected resources (all resources if none is selected)"</string> + <string>Try to check or update all selected resources (all resources if none are selected)</string> </property> </action> </widget> diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index fcc110de..f11cf992 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -349,7 +349,7 @@ void InstanceSettingsPage::loadSettings() ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool()); #if !defined(Q_OS_LINUX) - ui->perfomanceGroupBox->setVisible(false); + ui->settingsTabs->setTabVisible(ui->settingsTabs->indexOf(ui->performancePage), false); #endif // Miscellanous diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index b190e51a..14e1f1e5 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -80,7 +80,7 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::installMods); - ui->actionUpdateItem->setToolTip(tr("Tries to find / update all selected mods (all mods if none is selected)")); + ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)")); ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem); connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); @@ -190,10 +190,15 @@ void ModFolderPage::updateMods() return; } if (update_dialog.noUpdates()) { - CustomMessageBox::selectable(this, tr("Update checker"), - (mods_list.size() == 1) - ? tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) - : tr("All %1mods are up-to-date! :)").arg(use_all ? "" : (tr("selected") + " "))) + QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; + if (mods_list.size() > 1) { + if (use_all) { + message = tr("All mods are up-to-date! :)"); + } else { + message = tr("All selected mods are up-to-date! :)"); + } + } + CustomMessageBox::selectable(this, tr("Update checker"), message) ->exec(); return; } diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp index 8a93bc2e..504d7f7b 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp +++ b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * * 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 @@ -126,7 +127,7 @@ void FtbPage::suggestCurrent() return; } - dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion)); + dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion, this)); for(auto art : selected.art) { if(art.type == "square") { QString editedLogoName; diff --git a/nix/default.nix b/nix/default.nix index 1a6b9e44..42ddda18 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -71,14 +71,16 @@ stdenv.mkDerivation rec { postInstall = '' # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 wrapQtApp $out/bin/polymc \ - --run '[ -f /etc/NIXOS ] && export GAME_LIBRARY_PATH="${stdenv.cc.cc.lib}/lib"' \ - --prefix GAME_LIBRARY_PATH : ${gameLibraryPath} \ + --run '[ -f /etc/NIXOS ] && export LD_LIBRARY_PATH="${stdenv.cc.cc.lib}/lib:$LD_LIBRARY_PATH"' \ + --prefix LD_LIBRARY_PATH : ${gameLibraryPath} \ --prefix POLYMC_JAVA_PATHS : ${javaPaths} \ --prefix PATH : ${lib.makeBinPath [ xorg.xrandr ]} ''; meta = with lib; { homepage = "https://polymc.org/"; + downloadPage = "https://polymc.org/download/"; + changelog = "https://github.com/PolyMC/PolyMC/releases"; description = "A free, open source launcher for Minecraft"; longDescription = '' Allows you to have multiple, separate instances of Minecraft (each with @@ -86,7 +88,7 @@ stdenv.mkDerivation rec { their associated options with a simple interface. ''; platforms = platforms.unix; - license = licenses.gpl3Plus; + license = licenses.gpl3Only; maintainers = with maintainers; [ starcraft66 kloenk ]; }; } |