diff --git a/.github/scripts/prepare_JREs.sh b/.github/scripts/prepare_JREs.sh deleted file mode 100755 index ee713f81f..000000000 --- a/.github/scripts/prepare_JREs.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -URL_JDK8="https://api.adoptium.net/v3/binary/version/jdk8u312-b07/linux/x64/jre/hotspot/normal/eclipse" -URL_JDK17="https://api.adoptium.net/v3/binary/latest/17/ga/linux/x64/jre/hotspot/normal/eclipse" - -mkdir -p JREs -pushd JREs - -wget --content-disposition "$URL_JDK8" -wget --content-disposition "$URL_JDK17" - -for file in *; -do - mkdir temp - - re='(OpenJDK([[:digit:]]+)U-jre_x64_linux_hotspot_([[:digit:]]+)(.*).tar.gz)' - if [[ $file =~ $re ]]; - then - version_major=${BASH_REMATCH[2]} - version_trailing=${BASH_REMATCH[4]} - - if [ $version_major = 17 ]; - then - hyphen='-' - else - hyphen='' - fi - - version_edit=$(echo $version_trailing | sed -e 's/_/+/g' | sed -e 's/b/-b/g') - dir_name=jdk$hyphen$version_major$version_edit-jre - mkdir jre$version_major - tar -xzf $file -C temp - pushd temp/$dir_name - cp -r . ../../jre$version_major - popd - fi - - rm -rf temp -done - -popd diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 9b51b201e..60bd86eec 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -16,6 +16,7 @@ jobs: permissions: contents: write # for korthout/backport-action to create branch pull-requests: write # for korthout/backport-action to create PR to backport + actions: write # for korthout/backport-action to create PR with workflow changes name: Backport Pull Request if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name)) runs-on: ubuntu-latest @@ -24,7 +25,7 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - name: Create backport PRs - uses: korthout/backport-action@v2.4.1 + uses: korthout/backport-action@v2.5.0 with: # Config README: https://github.com/korthout/backport-action#backport-action pull_description: |- diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a0a8d6db9..f3896550f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,7 +76,7 @@ jobs: qt_ver: 6 qt_host: windows qt_arch: '' - qt_version: '6.6.1' + qt_version: '6.7.0' qt_modules: 'qt5compat qtimageformats' qt_tools: '' @@ -88,7 +88,7 @@ jobs: qt_ver: 6 qt_host: windows qt_arch: 'win64_msvc2019_arm64' - qt_version: '6.6.1' + qt_version: '6.7.0' qt_modules: 'qt5compat qtimageformats' qt_tools: '' @@ -98,7 +98,7 @@ jobs: qt_ver: 6 qt_host: mac qt_arch: '' - qt_version: '6.6.1' + qt_version: '6.7.0' qt_modules: 'qt5compat qtimageformats' qt_tools: '' @@ -160,13 +160,13 @@ jobs: - name: Setup ccache if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' - uses: hendrikmuhs/ccache-action@v1.2.11 + uses: hendrikmuhs/ccache-action@v1.2.12 with: key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} - name: Retrieve ccache cache (Windows MinGW-w64) if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.2 with: path: '${{ github.workspace }}\.ccache' key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} @@ -259,7 +259,6 @@ jobs: wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage" - ${{ github.workspace }}/.github/scripts/prepare_JREs.sh sudo apt install libopengl0 - name: Add QT_HOST_PATH var (Windows MSVC arm64) @@ -407,7 +406,7 @@ jobs: if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then brew install openssl@3 echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem - signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.tar.gz -inkey ed25519-priv.pem | openssl base64 | tr -d \\n) + signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n) rm ed25519-priv.pem cat >> $GITHUB_STEP_SUMMARY << EOF ### Artifact Information :information_source: @@ -496,7 +495,6 @@ jobs: run: | cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }} for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_DIR }}/manifest.txt - cd ${{ env.INSTALL_DIR }} tar --owner root --group root -czf ../PrismLauncher.tar.gz * @@ -505,9 +503,12 @@ jobs: run: | cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable + + # workaround to make portable installs to work on fedora + mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib + cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib + for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt - - cd ${{ env.INSTALL_PORTABLE_DIR }} tar -czf ../PrismLauncher-portable.tar.gz * @@ -526,13 +527,9 @@ jobs: chmod +x linuxdeploy-*.AppImage - mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-{8,17}-openjdk + mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines - cp -r ${{ github.workspace }}/JREs/jre8/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk - - cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk - cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ @@ -540,10 +537,6 @@ jobs: cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib/server" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib" export LD_LIBRARY_PATH chmod +x AppImageUpdate-x86_64.AppImage diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 2afbaeb61..fa22c96d5 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -84,7 +84,7 @@ jobs: - name: Create release id: create_release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: token: ${{ secrets.GITHUB_TOKEN }} tag_name: ${{ github.ref }} diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 2d78997ed..855b105ea 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -17,9 +17,9 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@6004951b182f8860210c8d6f0d808ec5b1a33d28 # v25 + - uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26 - - uses: DeterminateSystems/update-flake-lock@v20 + - uses: DeterminateSystems/update-flake-lock@v21 with: commit-msg: "chore(nix): update lockfile" pr-title: "chore(nix): update lockfile" diff --git a/CMakeLists.txt b/CMakeLists.txt index e42186cb5..2e46bb605 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -178,7 +178,7 @@ set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL th set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help") ######## Set version numbers ######## -set(Launcher_VERSION_MAJOR 8) +set(Launcher_VERSION_MAJOR 9) set(Launcher_VERSION_MINOR 0) set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}") @@ -381,8 +381,8 @@ if(UNIX AND APPLE) set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed") set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed") - set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.1.0/Sparkle-2.1.0.tar.xz" CACHE STRING "URL to Sparkle release archive") - set(MACOSX_SPARKLE_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive") + set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.5.2/Sparkle-2.5.2.tar.xz" CACHE STRING "URL to Sparkle release archive") + set(MACOSX_SPARKLE_SHA256 "572dd67ae398a466f19f343a449e1890bac1ef74885b4739f68f979a8a89884b" CACHE STRING "SHA256 checksum for Sparkle release archive") set(MACOSX_SPARKLE_DIR "${CMAKE_BINARY_DIR}/frameworks/Sparkle") # directories to look for dependencies @@ -504,11 +504,10 @@ else() endif() if(NOT cmark_FOUND) message(STATUS "Using bundled cmark") - set(CMARK_STATIC ON CACHE BOOL "Build static libcmark library" FORCE) - set(CMARK_SHARED OFF CACHE BOOL "Build shared libcmark library" FORCE) - set(CMARK_TESTS OFF CACHE BOOL "Build cmark tests and enable testing" FORCE) + set(BUILD_TESTING 0) + set(BUILD_SHARED_LIBS 0) add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser - add_library(cmark::cmark ALIAS cmark_static) + add_library(cmark::cmark ALIAS cmark) else() message(STATUS "Using system cmark") endif() diff --git a/flake.lock b/flake.lock index 5b0619292..0fbfca9cc 100644 --- a/flake.lock +++ b/flake.lock @@ -23,11 +23,11 @@ ] }, "locked": { - "lastModified": 1704982712, - "narHash": "sha256-2Ptt+9h8dczgle2Oo6z5ni5rt/uLMG47UFTR1ry/wgg=", + "lastModified": 1712014858, + "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "07f6395285469419cf9d078f59b5b49993198c00", + "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", "type": "github" }, "original": { @@ -41,11 +41,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -62,11 +62,11 @@ ] }, "locked": { - "lastModified": 1703887061, - "narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=", + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", "owner": "hercules-ci", "repo": "gitignore.nix", - "rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", "type": "github" }, "original": { @@ -91,28 +91,13 @@ "type": "github" } }, - "nix-filter": { - "locked": { - "lastModified": 1705332318, - "narHash": "sha256-kcw1yFeJe9N4PjQji9ZeX47jg0p9A0DuU4djKvg1a7I=", - "owner": "numtide", - "repo": "nix-filter", - "rev": "3449dc925982ad46246cfc36469baf66e1b64f17", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "nix-filter", - "type": "github" - } - }, "nixpkgs": { "locked": { - "lastModified": 1705697961, - "narHash": "sha256-XepT3WS516evSFYkme3GrcI3+7uwXHqtHbip+t24J7E=", + "lastModified": 1713596654, + "narHash": "sha256-LJbHQQ5aX1LVth2ST+Kkse/DRzgxlVhTL1rxthvyhZc=", "owner": "nixos", "repo": "nixpkgs", - "rev": "e5d1c87f5813afde2dda384ac807c57a105721cc", + "rev": "fd16bb6d3bcca96039b11aa52038fafeb6e4f4be", "type": "github" }, "original": { @@ -137,11 +122,11 @@ ] }, "locked": { - "lastModified": 1705757126, - "narHash": "sha256-Eksr+n4Q8EYZKAN0Scef5JK4H6FcHc+TKNHb95CWm+c=", + "lastModified": 1712897695, + "narHash": "sha256-nMirxrGteNAl9sWiOhoN5tIHyjBbVi5e2tgZUgZlK3Y=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "f56597d53fd174f796b5a7d3ee0b494f9e2285cc", + "rev": "40e6053ecb65fcbf12863338a6dcefb3f55f1bf8", "type": "github" }, "original": { @@ -155,7 +140,6 @@ "flake-compat": "flake-compat", "flake-parts": "flake-parts", "libnbtplusplus": "libnbtplusplus", - "nix-filter": "nix-filter", "nixpkgs": "nixpkgs", "pre-commit-hooks": "pre-commit-hooks" } diff --git a/flake.nix b/flake.nix index 1582400f1..e16c76699 100644 --- a/flake.nix +++ b/flake.nix @@ -12,7 +12,6 @@ url = "github:hercules-ci/flake-parts"; inputs.nixpkgs-lib.follows = "nixpkgs"; }; - nix-filter.url = "github:numtide/nix-filter"; pre-commit-hooks = { url = "github:cachix/pre-commit-hooks.nix"; inputs = { diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml index c3ac132b1..b4c6e8143 100644 --- a/flatpak/org.prismlauncher.PrismLauncher.yml +++ b/flatpak/org.prismlauncher.PrismLauncher.yml @@ -3,6 +3,7 @@ runtime: org.kde.Platform runtime-version: 5.15-23.08 sdk: org.kde.Sdk sdk-extensions: + - org.freedesktop.Sdk.Extension.openjdk21 - org.freedesktop.Sdk.Extension.openjdk17 - org.freedesktop.Sdk.Extension.openjdk8 @@ -50,6 +51,8 @@ modules: buildsystem: simple build-commands: - mkdir -p /app/jdk/ + - /usr/lib/sdk/openjdk21/install.sh + - mv /app/jre /app/jdk/21 - /usr/lib/sdk/openjdk17/install.sh - mv /app/jre /app/jdk/17 - /usr/lib/sdk/openjdk8/install.sh diff --git a/flatpak/shared-modules b/flatpak/shared-modules index 55a8e460c..f2b0c16a2 160000 --- a/flatpak/shared-modules +++ b/flatpak/shared-modules @@ -1 +1 @@ -Subproject commit 55a8e460c6343229597a13e973ba4855c27a1c4c +Subproject commit f2b0c16a2a217a1822ce5a6538ba8f755ed1dd32 diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 42343ff8f..bb8751ccc 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -225,6 +225,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // Don't quit on hiding the last window this->setQuitOnLastWindowClosed(false); + this->setQuitLockEnabled(false); // Commandline parsing QCommandLineParser parser; @@ -308,7 +309,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) adjustedBy = "Persistent data path"; #ifndef Q_OS_MACOS - if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) { + if (auto portableUserData = FS::PathCombine(m_rootPath, "UserData"); QDir(portableUserData).exists()) { + dataPath = portableUserData; + adjustedBy = "Portable user data path"; + m_portable = true; + } else if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) { dataPath = m_rootPath; adjustedBy = "Portable data path"; m_portable = true; @@ -639,10 +644,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("UseNativeGLFW", false); m_settings->registerSetting("CustomGLFWPath", ""); - // Peformance related options + // Performance related options m_settings->registerSetting("EnableFeralGamemode", false); m_settings->registerSetting("EnableMangoHud", false); m_settings->registerSetting("UseDiscreteGpu", false); + m_settings->registerSetting("UseZink", false); // Game time m_settings->registerSetting("ShowGameTime", true); diff --git a/launcher/Exception.h b/launcher/Exception.h index ef1e4e0d8..55b40fdc8 100644 --- a/launcher/Exception.h +++ b/launcher/Exception.h @@ -1,4 +1,37 @@ -// Licensed under the Apache-2.0 license. See README.md for details. +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2024 TheKodeToad + * + * 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 . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * 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 @@ -8,12 +41,12 @@ class Exception : public std::exception { public: - Exception(const QString& message) : std::exception(), m_message(message) { qCritical() << "Exception:" << message; } - Exception(const Exception& other) : std::exception(), m_message(other.cause()) {} + Exception(const QString& message) : std::exception(), m_message(message.toUtf8()) { qCritical() << "Exception:" << message; } + Exception(const Exception& other) : std::exception(), m_message(other.m_message) {} virtual ~Exception() noexcept {} - const char* what() const noexcept { return m_message.toLatin1().constData(); } - QString cause() const { return m_message; } + const char* what() const noexcept { return m_message.constData(); } + QString cause() const { return QString::fromUtf8(m_message); } private: - QString m_message; + QByteArray m_message; }; diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index f9be91a2a..70704e1d3 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -801,15 +801,24 @@ QString NormalizePath(QString path) } } -QString badFilenameChars = "\"\\/?<>:;*|!+\r\n"; +static const QString BAD_PATH_CHARS = "\"?<>:;*|!+\r\n"; +static const QString BAD_FILENAME_CHARS = BAD_PATH_CHARS + "\\/"; QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) { - for (int i = 0; i < string.length(); i++) { - if (badFilenameChars.contains(string[i])) { + for (int i = 0; i < string.length(); i++) + if (string.at(i) < ' ' || BAD_FILENAME_CHARS.contains(string.at(i))) string[i] = replaceWith; - } - } + + return string; +} + +QString RemoveInvalidPathChars(QString string, QChar replaceWith) +{ + for (int i = 0; i < string.length(); i++) + if (string.at(i) < ' ' || BAD_PATH_CHARS.contains(string.at(i))) + string[i] = replaceWith; + return string; } @@ -1585,4 +1594,44 @@ uintmax_t hardLinkCount(const QString& path) return count; } +#ifdef Q_OS_WIN +// returns 8.3 file format from long path +QString shortPathName(const QString& file) +{ + auto input = file.toStdWString(); + std::wstring output; + long length = GetShortPathNameW(input.c_str(), NULL, 0); + if (length == 0) + return {}; + // NOTE: this resizing might seem weird... + // when GetShortPathNameW fails, it returns length including null character + // when it succeeds, it returns length excluding null character + // See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx + output.resize(length); + if (GetShortPathNameW(input.c_str(), (LPWSTR)output.c_str(), length) == 0) + return {}; + output.resize(length - 1); + QString ret = QString::fromStdWString(output); + return ret; +} + +// if the string survives roundtrip through local 8bit encoding... +bool fitsInLocal8bit(const QString& string) +{ + return string == QString::fromLocal8Bit(string.toLocal8Bit()); +} + +QString getPathNameInLocal8bit(const QString& file) +{ + if (!fitsInLocal8bit(file)) { + auto path = shortPathName(file); + if (!path.isEmpty()) { + return path; + } + // in case shortPathName fails just return the path as is + } + return file; +} +#endif + } // namespace FS diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index f13fb9f28..5496c3795 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -342,6 +342,8 @@ QString NormalizePath(QString path); QString RemoveInvalidFilenameChars(QString string, QChar replaceWith = '-'); +QString RemoveInvalidPathChars(QString string, QChar replaceWith = '-'); + QString DirNameFromString(QString string, QString inDir = "."); /// Checks if the a given Path contains "!" @@ -551,4 +553,8 @@ bool canLink(const QString& src, const QString& dst); uintmax_t hardLinkCount(const QString& path); +#ifdef Q_OS_WIN +QString getPathNameInLocal8bit(const QString& file); +#endif + } // namespace FS diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index cdcd61ba6..52eb7d879 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -43,10 +43,10 @@ void InstanceCopyTask::executeTask() QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft")); QString staging_mc_dir; - if (mcDir.exists() && !dotMCDir.exists()) - staging_mc_dir = mcDir.filePath(); - else + if (dotMCDir.exists() && !mcDir.exists()) staging_mc_dir = dotMCDir.filePath(); + else + staging_mc_dir = mcDir.filePath(); FS::copy savesCopy(FS::PathCombine(m_origInstance->gameRoot(), "saves"), FS::PathCombine(staging_mc_dir, "saves")); savesCopy.followSymlinks(true); diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 9f0cf7460..d4676f358 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -164,8 +164,8 @@ void InstanceImportTask::processZipPack() } else if (technicFound) { // process as Technic pack qDebug() << "Technic:" << technicFound; - extractDir.mkpath(".minecraft"); - extractDir.cd(".minecraft"); + extractDir.mkpath("minecraft"); + extractDir.cd("minecraft"); m_modpackType = ModpackType::Technic; } else { QStringList paths_to_ignore{ "overrides/" }; diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index a1cf2560b..28efd7ec5 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -47,9 +47,6 @@ #include class QuaZip; -namespace Flame { -class FileResolvingTask; -} class InstanceImportTask : public InstanceTask { Q_OBJECT @@ -79,7 +76,6 @@ class InstanceImportTask : public InstanceTask { private: /* data */ NetJob::Ptr m_filesNetJob; - shared_qobject_ptr m_modIdResolver; QUrl m_sourceUrl; QString m_archivePath; bool m_downloadRequired = false; diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index c884a4f12..5e4abf020 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -847,14 +848,16 @@ class InstanceStaging : public Task { const unsigned maxBackoff = 16; public: - InstanceStaging(InstanceList* parent, InstanceTask* child, QString stagingPath, InstanceName const& instanceName, QString groupName) - : m_parent(parent) - , backoff(minBackoff, maxBackoff) - , m_stagingPath(std::move(stagingPath)) - , m_instance_name(std::move(instanceName)) - , m_groupName(std::move(groupName)) + InstanceStaging(InstanceList* parent, InstanceTask* child, SettingsObjectPtr settings) + : m_parent(parent), backoff(minBackoff, maxBackoff) { + m_stagingPath = parent->getStagedInstancePath(); + m_child.reset(child); + + m_child->setStagingPath(m_stagingPath); + m_child->setParentSettings(std::move(settings)); + connect(child, &Task::succeeded, this, &InstanceStaging::childSucceeded); connect(child, &Task::failed, this, &InstanceStaging::childFailed); connect(child, &Task::aborted, this, &InstanceStaging::childAborted); @@ -866,7 +869,7 @@ class InstanceStaging : public Task { connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceeded); } - virtual ~InstanceStaging(){}; + virtual ~InstanceStaging() {} // FIXME/TODO: add ability to abort during instance commit retries bool abort() override @@ -881,14 +884,22 @@ class InstanceStaging : public Task { bool canAbort() const override { return (m_child && m_child->canAbort()); } protected: - virtual void executeTask() override { m_child->start(); } + virtual void executeTask() override + { + if (m_stagingPath.isNull()) { + emitFailed(tr("Could not create staging folder")); + return; + } + + m_child->start(); + } QStringList warnings() const override { return m_child->warnings(); } private slots: void childSucceeded() { unsigned sleepTime = backoff(); - if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, *m_child.get())) { + if (m_parent->commitStagedInstance(m_stagingPath, *m_child.get(), m_child->group(), *m_child.get())) { emitSucceeded(); return; } @@ -897,7 +908,7 @@ class InstanceStaging : public Task { emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something.")); return; } - qDebug() << "Failed to commit instance" << m_instance_name.name() << "Initiating backoff:" << sleepTime; + qDebug() << "Failed to commit instance" << m_child->name() << "Initiating backoff:" << sleepTime; m_backoffTimer.start(sleepTime * 500); } void childFailed(const QString& reason) @@ -906,7 +917,11 @@ class InstanceStaging : public Task { emitFailed(reason); } - void childAborted() { emitAborted(); } + void childAborted() + { + m_parent->destroyStagingPath(m_stagingPath); + emitAborted(); + } private: InstanceList* m_parent; @@ -918,34 +933,35 @@ class InstanceStaging : public Task { ExponentialSeries backoff; QString m_stagingPath; unique_qobject_ptr m_child; - InstanceName m_instance_name; - QString m_groupName; QTimer m_backoffTimer; }; Task* InstanceList::wrapInstanceTask(InstanceTask* task) { - auto stagingPath = getStagedInstancePath(); - task->setStagingPath(stagingPath); - task->setParentSettings(m_globalSettings); - return new InstanceStaging(this, task, stagingPath, *task, task->group()); + return new InstanceStaging(this, task, m_globalSettings); } QString InstanceList::getStagedInstancePath() { - QString key = QUuid::createUuid().toString(QUuid::WithoutBraces); - QString tempDir = ".LAUNCHER_TEMP/"; - QString relPath = FS::PathCombine(tempDir, key); - QDir rootPath(m_instDir); - auto path = FS::PathCombine(m_instDir, relPath); - if (!rootPath.mkpath(relPath)) { - return QString(); - } + const QString tempRoot = FS::PathCombine(m_instDir, ".tmp"); + + QString result; + int tries = 0; + + do { + if (++tries > 256) + return {}; + + const QString key = QUuid::createUuid().toString(QUuid::Id128).left(6); + result = FS::PathCombine(tempRoot, key); + } while (QFileInfo::exists(result)); + + if (!QDir::current().mkpath(result)) + return {}; #ifdef Q_OS_WIN32 - auto tempPath = FS::PathCombine(m_instDir, tempDir); - SetFileAttributesA(tempPath.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED); + SetFileAttributesA(tempRoot.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED); #endif - return path; + return result; } bool InstanceList::commitStagedInstance(const QString& path, diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index ce2573329..9a5ae7a9d 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -119,6 +119,7 @@ bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool follow bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks) { QuaZip zip(fileCompressed); + zip.setUtf8Enabled(true); QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); if (!zip.open(QuaZip::mdCreate)) { QFile::remove(fileCompressed); @@ -141,6 +142,7 @@ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods) { QuaZip zipOut(targetJarPath); + zipOut.setUtf8Enabled(true); if (!zipOut.open(QuaZip::mdCreate)) { QFile::remove(targetJarPath); qCritical() << "Failed to open the minecraft.jar for modding"; @@ -286,10 +288,13 @@ std::optional extractSubDir(QuaZip* zip, const QString& subdir, con do { QString file_name = zip->getCurrentFileName(); +#ifdef Q_OS_WIN + file_name = FS::RemoveInvalidPathChars(file_name); +#endif if (!file_name.startsWith(subdir)) continue; - auto relative_file_name = QDir::fromNativeSeparators(file_name.remove(0, subdir.size())); + auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(subdir.size())); auto original_name = relative_file_name; // Fix subdirs/files ending with a / getting transformed into absolute paths @@ -463,7 +468,7 @@ auto ExportToZipTask::exportZip() -> ZipResult auto absolute = file.absoluteFilePath(); auto relative = m_dir.relativeFilePath(absolute); - setStatus("Compresing: " + relative); + setStatus("Compressing: " + relative); setProgress(m_progress + 1, m_progressTotal); if (m_follow_symlinks) { if (file.isSymLink()) diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index 93692d0d2..43b4ab933 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -163,6 +163,7 @@ class ExportToZipTask : public Task { , m_follow_symlinks(followSymlinks) { setAbortable(true); + m_output.setUtf8Enabled(true); }; ExportToZipTask(QString outputPath, QString dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false) : ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks){}; diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 20caba189..fc8da55c2 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -55,6 +55,9 @@ void JavaChecker::performCheck() qDebug() << "Java checker library could not be found. Please check your installation."; return; } +#ifdef Q_OS_WIN + checkerJar = FS::getPathNameInLocal8bit(checkerJar); +#endif QStringList args; diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 074bf54df..3627cec39 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -413,6 +413,8 @@ QList JavaUtils::FindJavaPaths() scanJavaDirs(FS::PathCombine(home, ".jdks")); // javas downloaded by sdkman scanJavaDirs(FS::PathCombine(home, ".sdkman/candidates/java")); + // javas downloaded by gradle (toolchains) + scanJavaDirs(FS::PathCombine(home, ".gradle/jdks")); javas.append(getMinecraftJavaBundle()); javas = addJavasFromEnv(javas); @@ -439,26 +441,25 @@ QString JavaUtils::getJavaCheckPath() QStringList getMinecraftJavaBundle() { - QString partialPath; QString executable = "java"; QStringList processpaths; #if defined(Q_OS_OSX) - partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support"); + processpaths << FS::PathCombine(QDir::homePath(), FS::PathCombine("Library", "Application Support", "minecraft", "runtime")); #elif defined(Q_OS_WIN32) - partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", ""); executable += "w.exe"; + auto appDataPath = QProcessEnvironment::systemEnvironment().value("APPDATA", ""); + processpaths << FS::PathCombine(QFileInfo(appDataPath).absoluteFilePath(), ".minecraft", "runtime"); + // add the microsoft store version of the launcher to the search. the current path is: // C:\Users\USERNAME\AppData\Local\Packages\Microsoft.4297127D64EC6_8wekyb3d8bbwe\LocalCache\Local\runtime + auto localAppDataPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", ""); auto minecraftMSStorePath = - FS::PathCombine(QFileInfo(partialPath).absolutePath(), "Local", "Packages", "Microsoft.4297127D64EC6_8wekyb3d8bbwe"); - minecraftMSStorePath = FS::PathCombine(minecraftMSStorePath, "LocalCache", "Local", "runtime"); - processpaths << minecraftMSStorePath; + FS::PathCombine(QFileInfo(localAppDataPath).absoluteFilePath(), "Packages", "Microsoft.4297127D64EC6_8wekyb3d8bbwe"); + processpaths << FS::PathCombine(minecraftMSStorePath, "LocalCache", "Local", "runtime"); #else - partialPath = QDir::homePath(); + processpaths << FS::PathCombine(QDir::homePath(), ".minecraft", "runtime"); #endif - auto minecraftDataPath = FS::PathCombine(partialPath, ".minecraft", "runtime"); - processpaths << minecraftDataPath; QStringList javas; while (!processpaths.isEmpty()) { diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index d08cb2f3d..cb3166166 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -173,11 +173,12 @@ void MinecraftInstance::loadSpecificSettings() m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride); m_settings->registerOverride(global_settings->getSetting("CustomGLFWPath"), nativeLibraryWorkaroundsOverride); - // Peformance related options + // Performance related options auto performanceOverride = m_settings->registerSetting("OverridePerformance", false); m_settings->registerOverride(global_settings->getSetting("EnableFeralGamemode"), performanceOverride); m_settings->registerOverride(global_settings->getSetting("EnableMangoHud"), performanceOverride); m_settings->registerOverride(global_settings->getSetting("UseDiscreteGpu"), performanceOverride); + m_settings->registerOverride(global_settings->getSetting("UseZink"), performanceOverride); // Miscellaneous auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false); @@ -292,10 +293,10 @@ QString MinecraftInstance::gameRoot() const QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); - if (mcDir.exists() && !dotMCDir.exists()) - return mcDir.filePath(); - else + if (dotMCDir.exists() && !mcDir.exists()) return dotMCDir.filePath(); + else + return mcDir.filePath(); } QString MinecraftInstance::binRoot() const @@ -594,9 +595,6 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment() QStringList preloadList; if (auto value = env.value("LD_PRELOAD"); !value.isEmpty()) preloadList = value.split(QLatin1String(":")); - QStringList libPaths; - if (auto value = env.value("LD_LIBRARY_PATH"); !value.isEmpty()) - libPaths = value.split(QLatin1String(":")); auto mangoHudLibString = MangoHud::getLibraryString(); if (!mangoHudLibString.isEmpty()) { @@ -604,18 +602,16 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment() QString libPath = mangoHudLib.absolutePath(); auto appendLib = [libPath, &preloadList](QString fileName) { if (QFileInfo(FS::PathCombine(libPath, fileName)).exists()) - preloadList << fileName; + preloadList << FS::PathCombine(libPath, fileName); }; // dlsym variant is only needed for OpenGL and not included in the vulkan layer appendLib("libMangoHud_dlsym.so"); appendLib("libMangoHud_opengl.so"); appendLib(mangoHudLib.fileName()); - libPaths << libPath; } env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":"))); - env.insert("LD_LIBRARY_PATH", libPaths.join(QLatin1String(":"))); env.insert("MANGOHUD", "1"); } @@ -627,6 +623,13 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment() env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only"); env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia"); } + + if (settings()->get("UseZink").toBool()) { + // taken from https://wiki.archlinux.org/title/OpenGL#OpenGL_over_Vulkan_(Zink) + env.insert("__GLX_VENDOR_LIBRARY_NAME", "mesa"); + env.insert("MESA_LOADER_DRIVER_OVERRIDE", "zink"); + env.insert("GALLIUM_DRIVER", "zink"); + } #endif return env; } @@ -662,8 +665,12 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine } if (serverToJoin && !serverToJoin->address.isEmpty()) { - args_pattern += " --server " + serverToJoin->address; - args_pattern += " --port " + QString::number(serverToJoin->port); + if (profile->hasTrait("feature:is_quick_play_multiplayer")) { + args_pattern += " --quickPlayMultiplayer " + serverToJoin->address + ':' + QString::number(serverToJoin->port); + } else { + args_pattern += " --server " + serverToJoin->address; + args_pattern += " --port " + QString::number(serverToJoin->port); + } } QMap token_mapping; diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp index c33d7e629..84c52c386 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -126,7 +126,35 @@ bool XboxAuthorizationStep::processSTSError(QNetworkReply::NetworkError error, Q emit finished( AccountTaskState::STATE_FAILED_SOFT, tr("This Microsoft account is underaged and is not linked to a family.\n\nPlease set up your account according to %1.") - .arg("help.minecraft.net")); + .arg("help.minecraft.net")); + return true; + } + // the following codes where copied from: https://github.com/PrismarineJS/prismarine-auth/pull/44 + case 2148916236: { + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("This Microsoft account requires proof of age to play. Please login to %1 to provide proof of age.") + .arg("login.live.com")); + return true; + } + case 2148916237: + emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("This Microsoft account has reached its limit for playtime. This " + "Microsoft account has been blocked from logging in.")); + return true; + case 2148916227: { + emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("This Microsoft account was banned by Xbox for violating one or more " + "Community Standards for Xbox and is unable to be used.")); + return true; + } + case 2148916229: { + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("This Microsoft account is currently restricted and your guardian has not given you permission to play " + "online. Login to %1 and have your guardian change your permissions.") + .arg("account.microsoft.com")); + return true; + } + case 2148916234: { + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("This Microsoft account has not accepted Xbox's Terms of Service. Please login and accept them.")); return true; } default: { diff --git a/launcher/minecraft/launch/ExtractNatives.cpp b/launcher/minecraft/launch/ExtractNatives.cpp index 8f3cac4d1..405008f40 100644 --- a/launcher/minecraft/launch/ExtractNatives.cpp +++ b/launcher/minecraft/launch/ExtractNatives.cpp @@ -79,6 +79,7 @@ void ExtractNatives::executeTask() auto settings = minecraftInstance->settings(); auto outputPath = minecraftInstance->getNativePath(); + FS::ensureFolderPathExists(outputPath); auto javaVersion = minecraftInstance->getJavaVersion(); bool jniHackEnabled = javaVersion.major() >= 8; for (const auto& source : toExtract) { diff --git a/launcher/minecraft/launch/ExtractNatives.h b/launcher/minecraft/launch/ExtractNatives.h index 2ab8816bd..4837a9dbb 100644 --- a/launcher/minecraft/launch/ExtractNatives.h +++ b/launcher/minecraft/launch/ExtractNatives.h @@ -16,8 +16,6 @@ #pragma once #include -#include -#include "minecraft/auth/AuthSession.h" // FIXME: temporary wrapper for existing task. class ExtractNatives : public LaunchStep { diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index aa94edb5d..4e021c4a8 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -66,32 +66,6 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent) : LaunchStep(parent) connect(&m_process, &LoggedProcess::stateChanged, this, &LauncherPartLaunch::on_state); } -#ifdef Q_OS_WIN -// returns 8.3 file format from long path -#include -QString shortPathName(const QString& file) -{ - auto input = file.toStdWString(); - std::wstring output; - long length = GetShortPathNameW(input.c_str(), NULL, 0); - // NOTE: this resizing might seem weird... - // when GetShortPathNameW fails, it returns length including null character - // when it succeeds, it returns length excluding null character - // See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx - output.resize(length); - GetShortPathNameW(input.c_str(), (LPWSTR)output.c_str(), length); - output.resize(length - 1); - QString ret = QString::fromStdWString(output); - return ret; -} -#endif - -// if the string survives roundtrip through local 8bit encoding... -bool fitsInLocal8bit(const QString& string) -{ - return string == QString::fromLocal8Bit(string.toLocal8Bit()); -} - void LauncherPartLaunch::executeTask() { QString jarPath = APPLICATION->getJarPath("NewLaunch.jar"); @@ -136,24 +110,15 @@ void LauncherPartLaunch::executeTask() auto natPath = minecraftInstance->getNativePath(); #ifdef Q_OS_WIN - if (!fitsInLocal8bit(natPath)) { - args << "-Djava.library.path=" + shortPathName(natPath); - } else { - args << "-Djava.library.path=" + natPath; - } -#else - args << "-Djava.library.path=" + natPath; + natPath = FS::getPathNameInLocal8bit(natPath); #endif + args << "-Djava.library.path=" + natPath; args << "-cp"; #ifdef Q_OS_WIN QStringList processed; for (auto& item : classPath) { - if (!fitsInLocal8bit(item)) { - processed << shortPathName(item); - } else { - processed << item; - } + processed << FS::getPathNameInLocal8bit(item); } args << processed.join(';'); #else diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp index 5aef4e585..238032532 100644 --- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp +++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp @@ -57,9 +57,11 @@ GetModDependenciesTask::GetModDependenciesTask(QObject* parent, , m_version(mcVersion(instance)) , m_loaderType(mcLoaders(instance)) { - for (auto mod : folder->allMods()) + for (auto mod : folder->allMods()) { + m_mods_file_names << mod->fileinfo().fileName(); if (auto meta = mod->metadata(); meta) m_mods.append(meta); + } prepare(); } @@ -231,8 +233,13 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen if (dep_.addonId != pDep->version.addonId) { removePack(pDep->version.addonId); addTask(prepareDependencyTask(dep_, provider.name, level)); - } else + } else { addTask(getProjectInfoTask(pDep)); + } + } + if (isLocalyInstalled(pDep)) { + removePack(pDep->version.addonId); + return; } for (auto dep_ : getDependenciesForVersion(pDep->version, provider.name)) { addTask(prepareDependencyTask(dep_, provider.name, level - 1)); @@ -258,9 +265,9 @@ void GetModDependenciesTask::removePack(const QVariant& addonId) #endif } -QHash GetModDependenciesTask::getRequiredBy() +auto GetModDependenciesTask::getExtraInfo() -> QHash { - QHash rby; + QHash rby; auto fullList = m_selected + m_pack_dependencies; for (auto& mod : fullList) { auto addonId = mod->pack->addonId; @@ -282,7 +289,61 @@ QHash GetModDependenciesTask::getRequiredBy() req.append(smod->pack->name); } } - rby[addonId.toString()] = req; + rby[addonId.toString()] = { maybeInstalled(mod), req }; } return rby; } + +// super lax compare (but not fuzzy) +// convert to lowercase +// convert all speratores to whitespace +// simplify sequence of internal whitespace to a single space +// efectivly compare two strings ignoring all separators and case +auto laxCompare = [](QString fsfilename, QString metadataFilename, bool excludeDigits = false) { + // allowed character seperators + QList allowedSeperators = { '-', '+', '.', '_' }; + if (excludeDigits) + allowedSeperators.append({ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }); + + // copy in lowercase + auto fsName = fsfilename.toLower(); + auto metaName = metadataFilename.toLower(); + + // replace all potential allowed seperatores with whitespace + for (auto sep : allowedSeperators) { + fsName = fsName.replace(sep, ' '); + metaName = metaName.replace(sep, ' '); + } + + // remove extraneous whitespace + fsName = fsName.simplified(); + metaName = metaName.simplified(); + + return fsName.compare(metaName) == 0; +}; + +bool GetModDependenciesTask::isLocalyInstalled(std::shared_ptr pDep) +{ + return pDep->version.fileName.isEmpty() || + + std::find_if(m_selected.begin(), m_selected.end(), + [pDep](std::shared_ptr i) { + return !i->version.fileName.isEmpty() && laxCompare(i->version.fileName, pDep->version.fileName); + }) != m_selected.end() || // check the selected versions + + std::find_if(m_mods_file_names.begin(), m_mods_file_names.end(), + [pDep](QString i) { return !i.isEmpty() && laxCompare(i, pDep->version.fileName); }) != + m_mods_file_names.end() || // check the existing mods + + std::find_if(m_pack_dependencies.begin(), m_pack_dependencies.end(), [pDep](std::shared_ptr i) { + return pDep->pack->addonId != i->pack->addonId && !i->version.fileName.isEmpty() && + laxCompare(pDep->version.fileName, i->version.fileName); + }) != m_pack_dependencies.end(); // check loaded dependencies +} + +bool GetModDependenciesTask::maybeInstalled(std::shared_ptr pDep) +{ + return std::find_if(m_mods_file_names.begin(), m_mods_file_names.end(), [pDep](QString i) { + return !i.isEmpty() && laxCompare(i, pDep->version.fileName, true); + }) != m_mods_file_names.end(); // check the existing mods +} diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h index e88204bdc..7202b01e0 100644 --- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h +++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h @@ -50,6 +50,11 @@ class GetModDependenciesTask : public SequentialTask { } }; + struct PackDependencyExtraInfo { + bool maybe_installed; + QStringList required_by; + }; + struct Provider { ModPlatform::ResourceProvider name; std::shared_ptr mod; @@ -62,7 +67,7 @@ class GetModDependenciesTask : public SequentialTask { QList> selected); auto getDependecies() const -> QList> { return m_pack_dependencies; } - QHash getRequiredBy(); + QHash getExtraInfo(); protected slots: Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, ModPlatform::ResourceProvider, int); @@ -73,10 +78,14 @@ class GetModDependenciesTask : public SequentialTask { ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, ModPlatform::ResourceProvider providerName); void removePack(const QVariant& addonId); + bool isLocalyInstalled(std::shared_ptr pDep); + bool maybeInstalled(std::shared_ptr pDep); + private: QList> m_pack_dependencies; QList> m_mods; QList> m_selected; + QStringList m_mods_file_names; Provider m_flame_provider; Provider m_modrinth_provider; diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index e9e12d86a..3982f3338 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -469,7 +469,7 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level) QuaZipFile file(&zip); - if (zip.setCurrentFile("META-INF/mods.toml")) { + if (zip.setCurrentFile("META-INF/mods.toml") || zip.setCurrentFile("META-INF/neoforge.mods.toml")) { if (!file.open(QIODevice::ReadOnly)) { zip.close(); return false; diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index aaa997c16..04481d0e3 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -28,6 +28,7 @@ class CheckUpdateTask : public Task { QString changelog; ModPlatform::ResourceProvider provider; shared_qobject_ptr download; + bool enabled = true; public: Update(QString name, @@ -37,7 +38,8 @@ class CheckUpdateTask : public Task { std::optional new_v_type, QString changelog, ModPlatform::ResourceProvider p, - shared_qobject_ptr t) + shared_qobject_ptr t, + bool enabled = true) : name(name) , old_hash(old_h) , old_version(old_v) @@ -46,6 +48,7 @@ class CheckUpdateTask : public Task { , changelog(changelog) , provider(p) , download(t) + , enabled(enabled) {} }; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 7c3dfe6d4..ef552c3c2 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -354,6 +354,8 @@ bool FlameCreationTask::createInstance() auto id = loader.id; if (id.startsWith("neoforge-")) { id.remove("neoforge-"); + if (id.startsWith("1.20.1-")) + id.remove("1.20.1-"); // this is a mess for curseforge loaderType = "neoforge"; loaderUid = "net.neoforged"; } else if (id.startsWith("forge-")) { diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp index 3827170b3..3a2028fd1 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.cpp +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -393,13 +393,17 @@ QByteArray FlamePackExportTask::generateIndex() version["version"] = minecraft->m_version; QString id; if (quilt != nullptr) - id = "quilt-" + quilt->getVersion(); + id = "quilt-" + quilt->m_version; else if (fabric != nullptr) - id = "fabric-" + fabric->getVersion(); + id = "fabric-" + fabric->m_version; else if (forge != nullptr) - id = "forge-" + forge->getVersion(); - else if (neoforge != nullptr) - id = "neoforge-" + neoforge->getVersion(); + id = "forge-" + forge->m_version; + else if (neoforge != nullptr) { + id = "neoforge-"; + if (minecraft->m_version == "1.20.1") + id += "1.20.1-"; + id += neoforge->m_version; + } version["modLoaders"] = QJsonArray(); if (!id.isEmpty()) { QJsonObject loader; diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp index 16ec90ec2..225583764 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -43,7 +43,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks& callbacks.on_succeed(doc); }); - QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) { + QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) { int network_error_code = -1; if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply) network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -102,7 +102,7 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi callbacks.on_succeed(doc, args.pack); }); - QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) { + QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) { int network_error_code = -1; if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply) network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -153,7 +153,7 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args, callbacks.on_succeed(doc, args.dependency); }); - QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) { + QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) { int network_error_code = -1; if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply) network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); diff --git a/launcher/modplatform/import_ftb/PackHelpers.cpp b/launcher/modplatform/import_ftb/PackHelpers.cpp index ecf973452..e523b9d20 100644 --- a/launcher/modplatform/import_ftb/PackHelpers.cpp +++ b/launcher/modplatform/import_ftb/PackHelpers.cpp @@ -43,6 +43,7 @@ Modpack parseDirectory(QString path) modpack.version = Json::requireString(root, "version", "version"); modpack.mcVersion = Json::requireString(root, "mcVersion", "mcVersion"); modpack.jvmArgs = Json::ensureVariant(root, "jvmArgs", {}, "jvmArgs"); + modpack.totalPlayTime = Json::requireInteger(root, "totalPlayTime", "totalPlayTime"); } catch (const Exception& e) { qDebug() << "Couldn't load ftb instance json: " << e.cause(); return {}; diff --git a/launcher/modplatform/import_ftb/PackHelpers.h b/launcher/modplatform/import_ftb/PackHelpers.h index 221eb5bf6..449ed2546 100644 --- a/launcher/modplatform/import_ftb/PackHelpers.h +++ b/launcher/modplatform/import_ftb/PackHelpers.h @@ -36,6 +36,7 @@ struct Modpack { QString name; QString version; QString mcVersion; + int totalPlayTime; // not needed for instance creation QVariant jvmArgs; diff --git a/launcher/modplatform/import_ftb/PackInstallTask.cpp b/launcher/modplatform/import_ftb/PackInstallTask.cpp index 9a3b2595b..8046300e1 100644 --- a/launcher/modplatform/import_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/import_ftb/PackInstallTask.cpp @@ -37,7 +37,7 @@ void PackInstallTask::executeTask() progress(1, 2); m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] { - FS::copy folderCopy(m_pack.path, FS::PathCombine(m_stagingPath, ".minecraft")); + FS::copy folderCopy(m_pack.path, FS::PathCombine(m_stagingPath, "minecraft")); folderCopy.followSymlinks(true); return folderCopy(); }); @@ -55,6 +55,7 @@ void PackInstallTask::copySettings() instanceSettings->suspendSave(); MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); instance.settings()->set("InstanceType", "OneSix"); + instance.settings()->set("totalTimePlayed", m_pack.totalPlayTime / 1000); if (m_pack.jvmArgs.isValid() && !m_pack.jvmArgs.toString().isEmpty()) { instance.settings()->set("OverrideJavaArgs", true); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 091296751..0048c7fac 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -137,7 +137,7 @@ void PackInstallTask::install() QDir unzipMcDir(m_stagingPath + "/unzip/minecraft"); if (unzipMcDir.exists()) { // ok, found minecraft dir, move contents to instance dir - if (!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) { + if (!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/minecraft")) { emitFailed(tr("Failed to move unzipped Minecraft!")); return; } @@ -155,7 +155,7 @@ void PackInstallTask::install() bool fallback = true; // handle different versions - QFile packJson(m_stagingPath + "/.minecraft/pack.json"); + QFile packJson(m_stagingPath + "/minecraft/pack.json"); QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods"); if (packJson.exists()) { packJson.open(QIODevice::ReadOnly | QIODevice::Text); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 96d9c84d2..a688cea87 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -20,6 +20,7 @@ #include "ui/pages/modplatform/OptionalModDialog.h" #include +#include #include bool ModrinthCreationTask::abort() @@ -58,6 +59,7 @@ bool ModrinthCreationTask::updateInstance() return false; auto version_name = inst->getManagedPackVersionName(); + m_root_path = QFileInfo(inst->gameRoot()).fileName(); auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : ""; if (shouldConfirmUpdate()) { @@ -173,7 +175,7 @@ bool ModrinthCreationTask::createInstance() FS::ensureFilePathExists(new_index_place); QFile::rename(index_path, new_index_place); - auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); + auto mcPath = FS::PathCombine(m_stagingPath, m_root_path); auto override_path = FS::PathCombine(m_stagingPath, "overrides"); if (QFile::exists(override_path)) { @@ -234,7 +236,7 @@ bool ModrinthCreationTask::createInstance() m_files_job.reset(new NetJob(tr("Mod Download Modrinth"), APPLICATION->network())); - auto root_modpack_path = FS::PathCombine(m_stagingPath, ".minecraft"); + auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path); auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path); for (auto file : m_files) { diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index 1bd5b7de9..f07734a58 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -46,4 +46,6 @@ class ModrinthCreationTask final : public InstanceCreationTask { NetJob::Ptr m_files_job; std::optional m_instance; + + QString m_root_path = "minecraft"; }; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index c2cdb0b5b..a07b0d5b4 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -287,16 +287,12 @@ QByteArray ModrinthPackExportTask::generateIndex() env["client"] = "required"; env["server"] = "required"; } - switch (iterator->side) { - case Metadata::ModSide::ClientSide: - env["server"] = "unsupported"; - break; - case Metadata::ModSide::ServerSide: - env["client"] = "unsupported"; - break; - case Metadata::ModSide::UniversalSide: - break; - } + + // a server side mod does not imply that the mod does not work on the client + // however, if a mrpack mod is marked as server-only it will not install on the client + if (iterator->side == Metadata::ModSide::ClientSide) + env["server"] = "unsupported"; + fileOut["env"] = env; fileOut["path"] = path; diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp index ad08d72d4..cc9ced10b 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp @@ -62,7 +62,7 @@ void Technic::SingleZipPackInstallTask::downloadSucceeded() m_abortable = false; setStatus(tr("Extracting modpack")); - QDir extractDir(FS::PathCombine(m_stagingPath, ".minecraft")); + QDir extractDir(FS::PathCombine(m_stagingPath, "minecraft")); qDebug() << "Attempting to create instance from" << m_archivePath; // open the zip and find relevant files in it diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index c162d6253..ed8b0a8a4 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -140,7 +140,7 @@ void Technic::SolderPackInstallTask::downloadSucceeded() m_filesNetJob.reset(); m_extractFuture = QtConcurrent::run([this]() { int i = 0; - QString extractDir = FS::PathCombine(m_stagingPath, ".minecraft"); + QString extractDir = FS::PathCombine(m_stagingPath, "minecraft"); FS::ensureFolderPathExists(extractDir); while (m_modCount > i) { diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp index 3b9424bf8..90f59ce54 100644 --- a/launcher/modplatform/technic/TechnicPackProcessor.cpp +++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp @@ -33,7 +33,7 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const QString& minecraftVersion, [[maybe_unused]] const bool isSolder) { - QString minecraftPath = FS::PathCombine(stagingPath, ".minecraft"); + QString minecraftPath = FS::PathCombine(stagingPath, "minecraft"); QString configPath = FS::PathCombine(stagingPath, "instance.cfg"); auto instanceSettings = std::make_shared(configPath); MinecraftInstance instance(globalSettings, instanceSettings, stagingPath); diff --git a/launcher/ui/dialogs/ExportPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp index 5af24b1b7..73e44efb1 100644 --- a/launcher/ui/dialogs/ExportPackDialog.cpp +++ b/launcher/ui/dialogs/ExportPackDialog.cpp @@ -47,11 +47,18 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla if (m_provider == ModPlatform::ResourceProvider::MODRINTH) { setWindowTitle(tr("Export Modrinth Pack")); - ui->summary->setText(instance->settings()->get("ExportSummary").toString()); + + ui->authorLabel->hide(); + ui->author->hide(); + + ui->summary->setPlainText(instance->settings()->get("ExportSummary").toString()); } else { setWindowTitle(tr("Export CurseForge Pack")); - ui->summaryLabel->setText(tr("&Author")); - ui->summary->setText(instance->settings()->get("ExportAuthor").toString()); + + ui->summaryLabel->hide(); + ui->summary->hide(); + + ui->author->setText(instance->settings()->get("ExportAuthor").toString()); } // ensure a valid pack is generated @@ -108,9 +115,13 @@ void ExportPackDialog::done(int result) auto settings = instance->settings(); settings->set("ExportName", ui->name->text()); settings->set("ExportVersion", ui->version->text()); - settings->set(m_provider == ModPlatform::ResourceProvider::FLAME ? "ExportAuthor" : "ExportSummary", ui->summary->text()); settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked()); + if (m_provider == ModPlatform::ResourceProvider::MODRINTH) + settings->set("ExportSummary", ui->summary->toPlainText()); + else + settings->set("ExportAuthor", ui->author->text()); + if (result == Accepted) { const QString name = ui->name->text().isEmpty() ? instance->name() : ui->name->text(); const QString filename = FS::RemoveInvalidFilenameChars(name); @@ -134,10 +145,10 @@ void ExportPackDialog::done(int result) Task* task; if (m_provider == ModPlatform::ResourceProvider::MODRINTH) { - task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance, - output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); + task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->toPlainText(), ui->optionalFiles->isChecked(), + instance, output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); } else { - task = new FlamePackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance, output, + task = new FlamePackExportTask(name, ui->version->text(), ui->author->text(), ui->optionalFiles->isChecked(), instance, output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); } diff --git a/launcher/ui/dialogs/ExportPackDialog.ui b/launcher/ui/dialogs/ExportPackDialog.ui index 09dea72a8..a4a174212 100644 --- a/launcher/ui/dialogs/ExportPackDialog.ui +++ b/launcher/ui/dialogs/ExportPackDialog.ui @@ -7,7 +7,7 @@ 0 0 650 - 510 + 532 @@ -19,21 +19,8 @@ &Description - - - - - &Summary - - - summary - - - - - - - + + &Name @@ -43,7 +30,10 @@ - + + + + &Version @@ -53,16 +43,43 @@ - - - - + 1.0.0 + + + + &Summary + + + summary + + + + + + + true + + + + + + + &Author + + + author + + + + + + @@ -124,6 +141,7 @@ name version summary + author files optionalFiles diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index 6f3f7f7ec..3524d43f8 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -97,6 +97,9 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, ui->verticalLayout->insertWidget(2, m_container); m_container->addButtons(m_buttons); + connect(m_container, &PageContainer::selectedPageChanged, this, [this](BasePage* previous, BasePage* selected) { + m_buttons->button(QDialogButtonBox::Ok)->setEnabled(creationTask && !instName().isEmpty()); + }); // Bonk Qt over its stupid head and make sure it understands which button is the default one... // See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 3f2be760a..1b7b0ac3b 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -130,7 +130,7 @@ void ResourceDownloadDialog::confirm() auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); confirm_dialog->retranslateUi(resourcesString()); - QHash getRequiredBy; + QHash dependencyExtraInfo; if (auto task = getModDependenciesTask(); task) { connect(task.get(), &Task::failed, this, [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); @@ -155,7 +155,7 @@ void ResourceDownloadDialog::confirm() } else { for (auto dep : task->getDependecies()) addResource(dep->pack, dep->version); - getRequiredBy = task->getRequiredBy(); + dependencyExtraInfo = task->getExtraInfo(); } } @@ -164,9 +164,10 @@ void ResourceDownloadDialog::confirm() return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0; }); for (auto& task : selected) { + auto extraInfo = dependencyExtraInfo.value(task->getPack()->addonId.toString()); confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(), - ModPlatform::ProviderCapabilities::name(task->getProvider()), getRequiredBy.value(task->getPack()->addonId.toString()), - task->getVersion().version_type.toString() }); + ModPlatform::ProviderCapabilities::name(task->getProvider()), extraInfo.required_by, + task->getVersion().version_type.toString(), !extraInfo.maybe_installed }); } if (confirm_dialog->exec()) { diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index 6426c2bfd..378191378 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -219,19 +219,20 @@ void ResourceUpdateDialog::checkCandidates() } static FlameAPI api; - auto getRequiredBy = depTask->getRequiredBy(); + auto dependencyExtraInfo = depTask->getExtraInfo(); for (const auto& dep : depTask->getDependecies()) { auto changelog = dep->version.changelog; if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); auto download_task = makeShared(dep->pack, dep->version, m_resource_model); - CheckUpdateTask::Update updatable = { dep->pack->name, dep->version.hash, - tr("Not installed"), dep->version.version, - dep->version.version_type, changelog, - dep->pack->provider, download_task }; + auto extraInfo = dependencyExtraInfo.value(dep->version.addonId.toString()); + CheckUpdateTask::Update updatable = { + dep->pack->name, dep->version.hash, tr("Not installed"), dep->version.version, dep->version.version_type, + changelog, dep->pack->provider, download_task, !extraInfo.maybe_installed + }; - appendResource(updatable, getRequiredBy.value(dep->version.addonId.toString())); + appendResource(updatable, extraInfo.required_by); m_tasks.insert(updatable.name, updatable.download); } } @@ -418,7 +419,10 @@ void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others, void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, QStringList requiredBy) { auto item_top = new QTreeWidgetItem(ui->modTreeWidget); - item_top->setCheckState(0, Qt::CheckState::Checked); + item_top->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + if (!info.enabled) { + item_top->setToolTip(0, tr("Mod was disabled as it may be already instaled.")); + } item_top->setText(0, info.name); item_top->setExpanded(true); diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp index 41b832e03..66c36d400 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.cpp +++ b/launcher/ui/dialogs/ReviewMessageBox.cpp @@ -35,8 +35,11 @@ auto ReviewMessageBox::create(QWidget* parent, QString&& title, QString&& icon) void ReviewMessageBox::appendResource(ResourceInformation&& info) { auto itemTop = new QTreeWidgetItem(ui->modTreeWidget); - itemTop->setCheckState(0, Qt::CheckState::Checked); + itemTop->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); itemTop->setText(0, info.name); + if (!info.enabled) { + itemTop->setToolTip(0, tr("Mod was disabled as it may be already instaled.")); + } auto filenameItem = new QTreeWidgetItem(itemTop); filenameItem->setText(0, tr("Filename: %1").arg(info.filename)); diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h index 7dd2732a0..82a43bc11 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.h +++ b/launcher/ui/dialogs/ReviewMessageBox.h @@ -20,6 +20,7 @@ class ReviewMessageBox : public QDialog { QString provider; QStringList required_by; QString version_type; + bool enabled = true; }; void appendResource(ResourceInformation&& info); diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index 6f3812a62..ed97de17a 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -465,7 +465,7 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event) widWidth = m_catPixmap.width(); if (m_catPixmap.height() < widHeight) widHeight = m_catPixmap.height(); - auto pixmap = m_catPixmap.scaled(widWidth, widHeight, Qt::KeepAspectRatio); + auto pixmap = m_catPixmap.scaled(widWidth, widHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); QRect rectOfPixmap = pixmap.rect(); rectOfPixmap.moveBottomRight(this->viewport()->rect().bottomRight()); painter.drawPixmap(rectOfPixmap.topLeft(), pixmap); @@ -482,32 +482,42 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event) if (model()->rowCount() == 0) { painter.save(); - const QString line1 = tr("Welcome!"); - const QString line2 = tr("Click \"Add Instance\" to get started."); - auto rect = this->viewport()->rect(); - auto font = option.font; - font.setPointSize(37); - painter.setFont(font); - auto fm = painter.fontMetrics(); + QString emptyString = tr("Welcome!") + "\n" + tr("Click \"Add Instance\" to get started."); - if (rect.height() <= (fm.height() * 5) || rect.width() <= fm.horizontalAdvance(line2)) { - auto s = rect.height() / (5. * fm.height()); - auto sx = rect.width() * 1. / fm.horizontalAdvance(line2); - if (s >= sx) - s = sx; - auto ps = font.pointSize() * s; - if (ps <= 0) - ps = 1; - font.setPointSize(ps); - painter.setFont(font); - fm = painter.fontMetrics(); + // calculate the rect for the overlay + painter.setRenderHint(QPainter::Antialiasing, true); + QFont font("sans", 20); + font.setBold(true); + + QRect bounds = viewport()->geometry(); + bounds.moveTop(0); + auto innerBounds = bounds; + innerBounds.adjust(10, 10, -10, -10); + + QColor background = QApplication::palette().color(QPalette::WindowText); + QColor foreground = QApplication::palette().color(QPalette::Base); + foreground.setAlpha(190); + painter.setFont(font); + auto fontMetrics = painter.fontMetrics(); + auto textRect = fontMetrics.boundingRect(innerBounds, Qt::AlignHCenter | Qt::TextWordWrap, emptyString); + textRect.moveCenter(bounds.center()); + + auto wrapRect = textRect; + wrapRect.adjust(-10, -10, 10, 10); + + // check if we are allowed to draw in our area + if (!event->rect().intersects(wrapRect)) { + return; } - // text - rect.setTop(rect.top() + fm.height() * 1.5); - painter.drawText(rect, Qt::AlignHCenter, line1); - rect.setTop(rect.top() + fm.height()); - painter.drawText(rect, Qt::AlignHCenter, line2); + painter.setBrush(QBrush(background)); + painter.setPen(foreground); + painter.drawRoundedRect(wrapRect, 5.0, 5.0); + + painter.setPen(foreground); + painter.setFont(font); + painter.drawText(textRect, Qt::AlignHCenter | Qt::TextWordWrap, emptyString); + painter.restore(); return; } diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp index a9530effc..3431dcb9c 100644 --- a/launcher/ui/pages/global/MinecraftPage.cpp +++ b/launcher/ui/pages/global/MinecraftPage.cpp @@ -109,6 +109,7 @@ void MinecraftPage::applySettings() s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked()); s->set("EnableMangoHud", ui->enableMangoHud->isChecked()); s->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked()); + s->set("UseZink", ui->useZink->isChecked()); // Game time s->set("ShowGameTime", ui->showGameTime->isChecked()); @@ -151,6 +152,7 @@ void MinecraftPage::loadSettings() ui->enableFeralGamemodeCheck->setChecked(s->get("EnableFeralGamemode").toBool()); ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool()); ui->useDiscreteGpuCheck->setChecked(s->get("UseDiscreteGpu").toBool()); + ui->useZink->setChecked(s->get("UseZink").toBool()); #if !defined(Q_OS_LINUX) ui->perfomanceGroupBox->setVisible(false); diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui index 3008099e3..7d2741250 100644 --- a/launcher/ui/pages/global/MinecraftPage.ui +++ b/launcher/ui/pages/global/MinecraftPage.ui @@ -309,6 +309,16 @@ + + + + <html><head/><body><p>Use Zink, a Mesa OpenGL driver that implements OpenGL on top of Vulkan. Performance may vary depending on the situation. Note: If no suitable Vulkan driver is found, software rendering will be used.</p></body></html> + + + Use Zink + + + diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index d4fd0ec5b..76add9402 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -232,10 +232,13 @@ void InstanceSettingsPage::applySettings() m_settings->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked()); m_settings->set("EnableMangoHud", ui->enableMangoHud->isChecked()); m_settings->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked()); + m_settings->set("UseZink", ui->useZink->isChecked()); + } else { m_settings->reset("EnableFeralGamemode"); m_settings->reset("EnableMangoHud"); m_settings->reset("UseDiscreteGpu"); + m_settings->reset("UseZink"); } // Game time @@ -354,6 +357,7 @@ void InstanceSettingsPage::loadSettings() ui->enableFeralGamemodeCheck->setChecked(m_settings->get("EnableFeralGamemode").toBool()); ui->enableMangoHud->setChecked(m_settings->get("EnableMangoHud").toBool()); ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool()); + ui->useZink->setChecked(m_settings->get("UseZink").toBool()); #if !defined(Q_OS_LINUX) ui->settingsTabs->setTabVisible(ui->settingsTabs->indexOf(ui->performancePage), false); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index 632569e0c..9490860ae 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -567,6 +567,16 @@ + + + + Use Zink, a Mesa OpenGL driver that implements OpenGL on top of Vulkan. Performance may vary depending on the situation. Note: If no suitable Vulkan driver is found, software rendering will be used. + + + Use Zink + + + diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 3e3c36b7b..ed7ebfad9 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -123,6 +123,10 @@ void ImportPage::updateState() // need to find the download link for the modpack // format of url curseforge://install?addonId=IDHERE&fileId=IDHERE QUrlQuery query(url); + if (query.allQueryItemValues("addonId").isEmpty() || query.allQueryItemValues("fileId").isEmpty()) { + qDebug() << "Invalid curseforge link:" << url; + return; + } auto addonId = query.allQueryItemValues("addonId")[0]; auto fileId = query.allQueryItemValues("fileId")[0]; auto array = std::make_shared(); @@ -200,7 +204,9 @@ void ImportPage::setExtraInfo(const QMap& extra_info) void ImportPage::on_modpackBtn_clicked() { - auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString(); + const QMimeType zip = QMimeDatabase().mimeTypeForName("application/zip"); + auto filter = tr("Supported files") + QString(" (%1 *.mrpack)").arg(zip.globPatterns().join(" ")); + filter += ";;" + zip.filterString(); //: Option for filtering for *.mrpack files when importing filter += ";;" + tr("Modrinth pack") + " (*.mrpack)"; const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter); diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index d6cc1fdcc..851c1c9e5 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -90,6 +90,7 @@ void ModPage::filterMods() void ModPage::triggerSearch() { m_filter = m_filter_widget->getFilter(); + m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect); m_ui->packView->clearSelection(); m_ui->packDescription->clear(); m_ui->versionSelectionBox->clear(); diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 8a70eb4de..f3c7ff60b 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -209,7 +209,8 @@ void ResourceModel::loadEntry(QModelIndex& entry) }; if (!callbacks.on_fail) callbacks.on_fail = [](QString reason, int) { - QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project versions:%1").arg(reason)); + QMessageBox::critical(nullptr, tr("Error"), + tr("A network error occurred. Could not load project versions: %1").arg(reason)); }; if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) @@ -232,7 +233,7 @@ void ResourceModel::loadEntry(QModelIndex& entry) callbacks.on_fail = [this](QString reason) { if (!s_running_models.constFind(this).value()) return; - QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason)); + QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info: %1").arg(reason)); }; if (!callbacks.on_abort) callbacks.on_abort = [this] { diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.cpp b/launcher/ui/pages/modplatform/ResourcePackPage.cpp index 2a4d02815..fc2dc15f3 100644 --- a/launcher/ui/pages/modplatform/ResourcePackPage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePackPage.cpp @@ -23,6 +23,7 @@ ResourcePackResourcePage::ResourcePackResourcePage(ResourceDownloadDialog* dialo void ResourcePackResourcePage::triggerSearch() { + m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect); m_ui->packView->clearSelection(); m_ui->packDescription->clear(); m_ui->versionSelectionBox->clear(); diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp index 6f4120289..0a6aa6d01 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp @@ -25,6 +25,7 @@ ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, void ShaderPackResourcePage::triggerSearch() { + m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect); m_ui->packView->clearSelection(); m_ui->packDescription->clear(); m_ui->versionSelectionBox->clear(); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index fffa21940..da5fe1e7b 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -104,6 +104,7 @@ void ModrinthPage::retranslate() void ModrinthPage::openedImpl() { BasePage::openedImpl(); + suggestCurrent(); triggerSearch(); } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui index 78a25feae..68b1d4e24 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui @@ -11,43 +11,7 @@ - - - - - true - - - - Note: Modrinth modpacks are still in alpha phase. Some things may be rough on the edges, or not working at all! Use it with caution. - - - Qt::AlignCenter - - - true - - - - - - - - - Search and filter ... - - - - - - - Search - - - - - - + @@ -77,7 +41,7 @@ - + @@ -97,6 +61,24 @@ + + + + + + Search and filter ... + + + + + + + Search + + + + + diff --git a/launcher/ui/themes/CatPack.cpp b/launcher/ui/themes/CatPack.cpp index 50c10189e..85eb85a18 100644 --- a/launcher/ui/themes/CatPack.cpp +++ b/launcher/ui/themes/CatPack.cpp @@ -36,7 +36,10 @@ #include "ui/themes/CatPack.h" #include #include +#include #include +#include +#include #include "FileSystem.h" #include "Json.h" @@ -79,7 +82,7 @@ JsonCatPack::JsonCatPack(QFileInfo& manifestInfo) : BasicCatPack(manifestInfo.di auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "CatPack JSON file"); const auto root = doc.object(); m_name = Json::requireString(root, "name", "Catpack name"); - m_defaultPath = FS::PathCombine(path, Json::requireString(root, "default", "Default Cat")); + m_default_path = FS::PathCombine(path, Json::requireString(root, "default", "Default Cat")); auto variants = Json::ensureArray(root, "variants", QJsonArray(), "Catpack Variants"); for (auto v : variants) { auto variant = Json::ensureObject(v, QJsonObject(), "Cat variant"); @@ -117,5 +120,21 @@ QString JsonCatPack::path(QDate now) if (startDate <= now && now <= endDate) return var.path; } - return m_defaultPath; + auto dInfo = QFileInfo(m_default_path); + if (!dInfo.isDir()) + return m_default_path; + + QStringList supportedImageFormats; + for (auto format : QImageReader::supportedImageFormats()) { + supportedImageFormats.append("*." + format); + } + + auto files = QDir(m_default_path).entryInfoList(supportedImageFormats, QDir::Files, QDir::Name); + if (files.length() == 0) + return ""; + auto idx = (now.dayOfYear() - 1) % files.length(); + auto isRandom = dInfo.fileName().compare("random", Qt::CaseInsensitive) == 0; + if (isRandom) + idx = QRandomGenerator::global()->bounded(0, files.length()); + return files[idx].absoluteFilePath(); } diff --git a/launcher/ui/themes/CatPack.h b/launcher/ui/themes/CatPack.h index 1d310e796..5a13d0cef 100644 --- a/launcher/ui/themes/CatPack.h +++ b/launcher/ui/themes/CatPack.h @@ -87,6 +87,6 @@ class JsonCatPack : public BasicCatPack { QString path(QDate now); private: - QString m_defaultPath; + QString m_default_path; QList m_variants; }; diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index 0bcac100c..a128fc3f5 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -178,8 +178,8 @@ QList ThemeManager::getValidApplicationThemes() QList ThemeManager::getValidCatPacks() { QList ret; - ret.reserve(m_catPacks.size()); - for (auto&& [id, theme] : m_catPacks) { + ret.reserve(m_cat_packs.size()); + for (auto&& [id, theme] : m_cat_packs) { ret.append(theme.get()); } return ret; @@ -244,8 +244,8 @@ void ThemeManager::applyCurrentlySelectedTheme(bool initial) QString ThemeManager::getCatPack(QString catName) { - auto catIter = m_catPacks.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString()); - if (catIter != m_catPacks.end()) { + auto catIter = m_cat_packs.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString()); + if (catIter != m_cat_packs.end()) { auto& catPack = catIter->second; themeDebugLog() << "applying catpack" << catPack->id(); return catPack->path(); @@ -253,14 +253,14 @@ QString ThemeManager::getCatPack(QString catName) themeWarningLog() << "Tried to get invalid catPack:" << catName; } - return m_catPacks.begin()->second->path(); + return m_cat_packs.begin()->second->path(); } QString ThemeManager::addCatPack(std::unique_ptr catPack) { QString id = catPack->id(); - if (m_catPacks.find(id) == m_catPacks.end()) - m_catPacks.emplace(id, std::move(catPack)); + if (m_cat_packs.find(id) == m_cat_packs.end()) + m_cat_packs.emplace(id, std::move(catPack)); else themeWarningLog() << "CatPack(" << id << ") not added to prevent id duplication"; return id; diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index b5c66677b..b77b5947a 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -61,7 +61,7 @@ class ThemeManager { QDir m_iconThemeFolder{ "iconthemes" }; QDir m_applicationThemeFolder{ "themes" }; QDir m_catPacksFolder{ "catpacks" }; - std::map> m_catPacks; + std::map> m_cat_packs; void initializeThemes(); void initializeCatPacks(); diff --git a/launcher/ui/widgets/LogView.cpp b/launcher/ui/widgets/LogView.cpp index 4096889d3..6578b1f12 100644 --- a/launcher/ui/widgets/LogView.cpp +++ b/launcher/ui/widgets/LogView.cpp @@ -36,6 +36,7 @@ #include "LogView.h" #include #include +#include LogView::LogView(QWidget* parent) : QPlainTextEdit(parent) { @@ -117,6 +118,9 @@ void LogView::rowsAboutToBeInserted(const QModelIndex& parent, int first, int la void LogView::rowsInserted(const QModelIndex& parent, int first, int last) { + QTextDocument document; + QTextCursor cursor(&document); + for (int i = first; i <= last; i++) { auto idx = m_model->index(i, 0, parent); auto text = m_model->data(idx, Qt::DisplayRole).toString(); @@ -133,11 +137,16 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last) if (bg.isValid()) { format.setBackground(bg.value()); } - auto workCursor = textCursor(); - workCursor.movePosition(QTextCursor::End); - workCursor.insertText(text, format); - workCursor.insertBlock(); + cursor.movePosition(QTextCursor::End); + cursor.insertText(text, format); + cursor.insertBlock(); } + + QTextDocumentFragment fragment(&document); + QTextCursor workCursor = textCursor(); + workCursor.movePosition(QTextCursor::End); + workCursor.insertFragment(fragment); + if (m_scroll && !m_scrolling) { m_scrolling = true; QMetaObject::invokeMethod(this, "scrollToBottom", Qt::QueuedConnection); diff --git a/libraries/cmark b/libraries/cmark index 5ba25ff40..8fbf02968 160000 --- a/libraries/cmark +++ b/libraries/cmark @@ -1 +1 @@ -Subproject commit 5ba25ff40eba44c811f79ab6a792baf945b8307c +Subproject commit 8fbf029685482827828b5858444157052f1b0a5f diff --git a/libraries/filesystem b/libraries/filesystem index 8a2edd6d9..2fc4b4637 160000 --- a/libraries/filesystem +++ b/libraries/filesystem @@ -1 +1 @@ -Subproject commit 8a2edd6d92ed820521d42c94d179462bf06b5ed3 +Subproject commit 2fc4b463759e043476fc0036da094e5877e3dd50 diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java index 186909123..49e5d518f 100644 --- a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java +++ b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java @@ -58,10 +58,17 @@ import org.prismlauncher.utils.Parameters; import org.prismlauncher.utils.ReflectionUtils; import java.lang.invoke.MethodHandle; +import java.util.Collections; +import java.util.List; public final class StandardLauncher extends AbstractLauncher { + private final boolean quickPlaySupported; + public StandardLauncher(Parameters params) { super(params); + + List traits = params.getList("traits", Collections.emptyList()); + quickPlaySupported = traits.contains("feature:is_quick_play_multiplayer"); } @Override @@ -76,10 +83,16 @@ public final class StandardLauncher extends AbstractLauncher { } if (serverAddress != null) { - gameArgs.add("--server"); - gameArgs.add(serverAddress); - gameArgs.add("--port"); - gameArgs.add(serverPort); + if (quickPlaySupported) { + // as of 23w14a + gameArgs.add("--quickPlayMultiplayer"); + gameArgs.add(serverAddress + ':' + serverPort); + } else { + gameArgs.add("--server"); + gameArgs.add(serverAddress); + gameArgs.add("--port"); + gameArgs.add(serverPort); + } } // find and invoke the main method diff --git a/libraries/libnbtplusplus b/libraries/libnbtplusplus index a5e8fd52b..23b955121 160000 --- a/libraries/libnbtplusplus +++ b/libraries/libnbtplusplus @@ -1 +1 @@ -Subproject commit a5e8fd52b8bf4ab5d5bcc042b2a247867589985f +Subproject commit 23b955121b8217c1c348a9ed2483167a6f3ff4ad diff --git a/libraries/quazip b/libraries/quazip index 6117161af..9d3aa3ee9 160000 --- a/libraries/quazip +++ b/libraries/quazip @@ -1 +1 @@ -Subproject commit 6117161af08e366c37499895b00ef62f93adc345 +Subproject commit 9d3aa3ee948c1cde5a9f873ecbc3bb229c1182ee diff --git a/nix/distribution.nix b/nix/distribution.nix index ca9999dcb..01c90f783 100644 --- a/nix/distribution.nix +++ b/nix/distribution.nix @@ -26,24 +26,8 @@ overlays.default = final: prev: let version = builtins.substring 0 8 self.lastModifiedDate or "dirty"; - filteredSelf = inputs.nix-filter.lib.filter { - root = ../.; - include = [ - "buildconfig" - "cmake" - "launcher" - "libraries" - "program_info" - "tests" - ../COPYING.md - ../CMakeLists.txt - ]; - }; - # common args for prismlauncher evaluations unwrappedArgs = { - self = filteredSelf; - inherit (inputs) libnbtplusplus; inherit ((final.darwin or prev.darwin).apple_sdk.frameworks) Cocoa; inherit version; diff --git a/nix/pkg/default.nix b/nix/pkg/default.nix index fd19a0b3d..0078def8c 100644 --- a/nix/pkg/default.nix +++ b/nix/pkg/default.nix @@ -16,7 +16,6 @@ gamemode, msaClientID ? null, gamemodeSupport ? stdenv.isLinux, - self, version, libnbtplusplus, }: @@ -25,7 +24,19 @@ assert lib.assertMsg (stdenv.isLinux || !gamemodeSupport) "gamemodeSupport is on pname = "prismlauncher-unwrapped"; inherit version; - src = lib.cleanSource self; + src = lib.fileset.toSource { + root = ../../.; + fileset = lib.fileset.unions (map (fileName: ../../${fileName}) [ + "buildconfig" + "cmake" + "launcher" + "libraries" + "program_info" + "tests" + "COPYING.md" + "CMakeLists.txt" + ]); + }; nativeBuildInputs = [extra-cmake-modules cmake jdk17 ninja canonicalize-jars-hook]; buildInputs = diff --git a/nix/pkg/wrapper.nix b/nix/pkg/wrapper.nix index cd356c8d7..1bcff1f9b 100644 --- a/nix/pkg/wrapper.nix +++ b/nix/pkg/wrapper.nix @@ -15,6 +15,7 @@ openal, jdk8, jdk17, + jdk21, gamemode, flite, mesa-demos, @@ -24,7 +25,7 @@ gamemodeSupport ? stdenv.isLinux, textToSpeechSupport ? stdenv.isLinux, controllerSupport ? stdenv.isLinux, - jdks ? [jdk17 jdk8], + jdks ? [jdk21 jdk17 jdk8], additionalLibs ? [], additionalPrograms ? [], }: let diff --git a/program_info/prismlauncher.manifest.in b/program_info/prismlauncher.manifest.in index fb28afc17..71378134c 100644 --- a/program_info/prismlauncher.manifest.in +++ b/program_info/prismlauncher.manifest.in @@ -1,5 +1,10 @@ + + + true + +