diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index e5443439d..5df88fe86 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.1.1 + uses: korthout/backport-action@v2.4.1 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 4fb2d6794..14c725b63 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,8 +21,23 @@ on: WINDOWS_CODESIGN_PASSWORD: description: Password for signing Windows builds required: false - CACHIX_AUTH_TOKEN: - description: Private token for authenticating against Cachix cache + APPLE_CODESIGN_CERT: + description: Certificate for signing macOS builds + required: false + APPLE_CODESIGN_PASSWORD: + description: Password for signing macOS builds + required: false + APPLE_CODESIGN_ID: + description: Certificate ID for signing macOS builds + required: false + APPLE_NOTARIZE_APPLE_ID: + description: Apple ID used for notarizing macOS builds + required: false + APPLE_NOTARIZE_TEAM_ID: + description: Team ID used for notarizing macOS builds + required: false + APPLE_NOTARIZE_PASSWORD: + description: Password used for notarizing macOS builds required: false GPG_PRIVATE_KEY: description: Private key for AppImage signing @@ -61,7 +76,7 @@ jobs: qt_ver: 6 qt_host: windows qt_arch: '' - qt_version: '6.6.0' + qt_version: '6.6.2' qt_modules: 'qt5compat qtimageformats' qt_tools: '' @@ -73,7 +88,7 @@ jobs: qt_ver: 6 qt_host: windows qt_arch: 'win64_msvc2019_arm64' - qt_version: '6.6.0' + qt_version: '6.6.2' qt_modules: 'qt5compat qtimageformats' qt_tools: '' @@ -83,7 +98,7 @@ jobs: qt_ver: 6 qt_host: mac qt_arch: '' - qt_version: '6.6.0' + qt_version: '6.6.2' qt_modules: 'qt5compat qtimageformats' qt_tools: '' @@ -145,13 +160,13 @@ jobs: - name: Setup ccache if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' - uses: hendrikmuhs/ccache-action@v1.2.10 + 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@v3.3.2 + uses: actions/cache@v4.0.2 with: path: '${{ github.workspace }}\.ccache' key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} @@ -336,6 +351,20 @@ jobs: # PACKAGE BUILDS ## + - name: Fetch codesign certificate (macOS) + if: runner.os == 'macOS' + run: | + echo '${{ secrets.APPLE_CODESIGN_CERT }}' | base64 --decode > codesign.p12 + if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then + security create-keychain -p '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain + security import codesign.p12 -k build.keychain -P '${{ secrets.APPLE_CODESIGN_PASSWORD }}' -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain + else + echo ":warning: Using ad-hoc code signing for macOS, as certificate was not present." >> $GITHUB_STEP_SUMMARY + fi + - name: Package (macOS) if: runner.os == 'macOS' run: | @@ -343,9 +372,34 @@ jobs: cd ${{ env.INSTALL_DIR }} chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher" - sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher" + + if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then + APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}' + else + APPLE_CODESIGN_ID='-' + fi + + sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher" mv "PrismLauncher.app" "Prism Launcher.app" - tar -czf ../PrismLauncher.tar.gz * + + - name: Notarize (macOS) + if: runner.os == 'macOS' + run: | + cd ${{ env.INSTALL_DIR }} + + if [ -n '${{ secrets.APPLE_NOTARIZE_PASSWORD }}' ]; then + ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip + xcrun notarytool submit ../PrismLauncher.zip \ + --wait --progress \ + --apple-id '${{ secrets.APPLE_NOTARIZE_APPLE_ID }}' \ + --team-id '${{ secrets.APPLE_NOTARIZE_TEAM_ID }}' \ + --password '${{ secrets.APPLE_NOTARIZE_PASSWORD }}' + + xcrun stapler staple "Prism Launcher.app" + else + echo ":warning: Skipping notarization as credentials are not present." >> $GITHUB_STEP_SUMMARY + fi + ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip - name: Make Sparkle signature (macOS) if: matrix.name == 'macOS' @@ -353,7 +407,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: @@ -442,7 +496,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 * @@ -451,9 +504,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 * @@ -517,70 +573,70 @@ jobs: - name: Upload binary tarball (macOS) if: runner.os == 'macOS' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }} - path: PrismLauncher.tar.gz + path: PrismLauncher.zip - name: Upload binary zip (Windows) if: runner.os == 'Windows' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }} path: ${{ env.INSTALL_DIR }}/** - name: Upload binary zip (Windows, portable) if: runner.os == 'Windows' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }} path: ${{ env.INSTALL_PORTABLE_DIR }}/** - name: Upload installer (Windows) if: runner.os == 'Windows' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }} path: PrismLauncher-Setup.exe - name: Upload binary tarball (Linux, Qt 5) if: runner.os == 'Linux' && matrix.qt_ver != 6 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ runner.os }}-Qt5-${{ env.VERSION }}-${{ inputs.build_type }} path: PrismLauncher.tar.gz - name: Upload binary tarball (Linux, portable, Qt 5) if: runner.os == 'Linux' && matrix.qt_ver != 6 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }} path: PrismLauncher-portable.tar.gz - name: Upload binary tarball (Linux, Qt 6) if: runner.os == 'Linux' && matrix.qt_ver !=5 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ runner.os }}-Qt6-${{ env.VERSION }}-${{ inputs.build_type }} path: PrismLauncher.tar.gz - name: Upload binary tarball (Linux, portable, Qt 6) if: runner.os == 'Linux' && matrix.qt_ver != 5 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }} path: PrismLauncher-portable.tar.gz - name: Upload AppImage (Linux) if: runner.os == 'Linux' && matrix.qt_ver != 5 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage - name: Upload AppImage Zsync (Linux) if: runner.os == 'Linux' && matrix.qt_ver != 5 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage.zsync path: PrismLauncher-Linux-x86_64.AppImage.zsync diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a77b4ae1e..d40d7eb68 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -13,7 +13,7 @@ jobs: submodules: 'true' - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: config-file: ./.github/codeql/codeql-config.yml queries: security-and-quality @@ -32,4 +32,4 @@ jobs: cmake --build build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml index 70fda60ed..9efafc8cc 100644 --- a/.github/workflows/trigger_builds.yml +++ b/.github/workflows/trigger_builds.yml @@ -32,6 +32,11 @@ jobs: SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }} WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }} WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }} - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} + APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }} + APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }} + APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }} + APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }} + APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }} + APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }} diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 28578165f..fa22c96d5 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -16,7 +16,12 @@ jobs: SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }} WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }} WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }} - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} + APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }} + APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }} + APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }} + APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }} + APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }} + APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }} @@ -32,7 +37,7 @@ jobs: submodules: "true" path: "PrismLauncher-source" - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 - name: Grab and store version run: | tag_name=$(echo ${{ github.ref }} | grep -oE "[^/]+$") @@ -46,8 +51,8 @@ jobs: mv PrismLauncher-Linux-Qt5*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync - mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz - mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz + mv PrismLauncher-macOS-Legacy*/PrismLauncher.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip + mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }} @@ -79,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 }} @@ -102,6 +107,6 @@ jobs: PrismLauncher-Windows-MSVC-${{ env.VERSION }}.zip PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe - PrismLauncher-macOS-${{ env.VERSION }}.tar.gz - PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz + PrismLauncher-macOS-${{ env.VERSION }}.zip + PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip PrismLauncher-${{ env.VERSION }}.tar.gz diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 6a16b0369..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@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23 + - 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 faeb1c44e..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}") @@ -377,12 +377,12 @@ if(UNIX AND APPLE) set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns) - set(MACOSX_BUNDLE_COPYRIGHT "© 2022-2023 ${Launcher_Copyright_Mac}") + set(MACOSX_BUNDLE_COPYRIGHT "${Launcher_Copyright_Mac}") 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/COPYING.md b/COPYING.md index 0221d1b08..f14e2958e 100644 --- a/COPYING.md +++ b/COPYING.md @@ -1,7 +1,7 @@ ## Prism Launcher Prism Launcher - Minecraft Launcher - Copyright (C) 2022-2023 Prism Launcher Contributors + Copyright (C) 2022-2024 Prism Launcher Contributors 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 @@ -436,7 +436,7 @@ Copyright (C) 2007 Johann Ollivier Lapeyre Copyright (C) 2007 Kenneth Wimer Copyright (C) 2007 Riccardo Iaconelli - + and others This library is free software; you can redistribute it and/or diff --git a/flake.lock b/flake.lock index 3422af0ac..492f7855e 100644 --- a/flake.lock +++ b/flake.lock @@ -18,14 +18,16 @@ }, "flake-parts": { "inputs": { - "nixpkgs-lib": "nixpkgs-lib" + "nixpkgs-lib": [ + "nixpkgs" + ] }, "locked": { - "lastModified": 1698882062, - "narHash": "sha256-HkhafUayIqxXyHH1X8d9RDl1M2CkFgZLjKD3MzabiEo=", + "lastModified": 1709336216, + "narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "8c9fa2545007b49a5db5f650ae91f227672c3877", + "rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2", "type": "github" }, "original": { @@ -39,11 +41,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1685518550, - "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -60,11 +62,11 @@ ] }, "locked": { - "lastModified": 1660459072, - "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", "owner": "hercules-ci", "repo": "gitignore.nix", - "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", "type": "github" }, "original": { @@ -89,28 +91,13 @@ "type": "github" } }, - "nix-filter": { - "locked": { - "lastModified": 1694857738, - "narHash": "sha256-bxxNyLHjhu0N8T3REINXQ2ZkJco0ABFPn6PIe2QUfqo=", - "owner": "numtide", - "repo": "nix-filter", - "rev": "41fd48e00c22b4ced525af521ead8792402de0ea", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "nix-filter", - "type": "github" - } - }, "nixpkgs": { "locked": { - "lastModified": 1700108881, - "narHash": "sha256-+Lqybl8kj0+nD/IlAWPPG/RDTa47gff9nbei0u7BntE=", + "lastModified": 1711200738, + "narHash": "sha256-dkJmk/ET/tRV4007O6kU101UEg1svUwiyk/zEEX9Tdg=", "owner": "nixos", "repo": "nixpkgs", - "rev": "7414e9ee0b3e9903c24d3379f577a417f0aae5f1", + "rev": "20bc93ca7b2158ebc99b8cef987a2173a81cde35", "type": "github" }, "original": { @@ -120,24 +107,6 @@ "type": "github" } }, - "nixpkgs-lib": { - "locked": { - "dir": "lib", - "lastModified": 1698611440, - "narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735", - "type": "github" - }, - "original": { - "dir": "lib", - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, "pre-commit-hooks": { "inputs": { "flake-compat": [ @@ -153,11 +122,11 @@ ] }, "locked": { - "lastModified": 1700064067, - "narHash": "sha256-1ZWNDzhu8UlVCK7+DUN9dVQfiHX1bv6OQP9VxstY/gs=", + "lastModified": 1710923068, + "narHash": "sha256-6hOpUiuxuwpXXc/xfJsBUJeqqgGI+JMJuLo45aG3cKc=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "e558068cba67b23b4fbc5537173dbb43748a17e8", + "rev": "e611897ddfdde3ed3eaac4758635d7177ff78673", "type": "github" }, "original": { @@ -171,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 afb0ec63a..e16c76699 100644 --- a/flake.nix +++ b/flake.nix @@ -1,15 +1,24 @@ { description = "A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once (Fork of MultiMC)"; + nixConfig = { + extra-substituters = ["https://cache.garnix.io"]; + extra-trusted-public-keys = ["cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g="]; + }; + inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; - flake-parts.url = "github:hercules-ci/flake-parts"; - nix-filter.url = "github:numtide/nix-filter"; + flake-parts = { + url = "github:hercules-ci/flake-parts"; + inputs.nixpkgs-lib.follows = "nixpkgs"; + }; pre-commit-hooks = { url = "github:cachix/pre-commit-hooks.nix"; - inputs.nixpkgs.follows = "nixpkgs"; - inputs.nixpkgs-stable.follows = "nixpkgs"; - inputs.flake-compat.follows = "flake-compat"; + inputs = { + nixpkgs.follows = "nixpkgs"; + nixpkgs-stable.follows = "nixpkgs"; + flake-compat.follows = "flake-compat"; + }; }; flake-compat = { url = "github:edolstra/flake-compat"; diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml index 89727751e..c3ac132b1 100644 --- a/flatpak/org.prismlauncher.PrismLauncher.yml +++ b/flatpak/org.prismlauncher.PrismLauncher.yml @@ -1,6 +1,6 @@ id: org.prismlauncher.PrismLauncher runtime: org.kde.Platform -runtime-version: "5.15-23.08" +runtime-version: 5.15-23.08 sdk: org.kde.Sdk sdk-extensions: - org.freedesktop.Sdk.Extension.openjdk17 @@ -104,18 +104,15 @@ modules: - install -Dm755 ../data/gamemoderun -t /app/bin sources: - type: archive - archive-type: tar-gzip - url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.7 - sha256: 57ce73ba605d1cf12f8d13725006a895182308d93eba0f69f285648449641803 + dest-filename: gamemode.tar.gz + url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.1 + sha256: 969cf85b5ca3944f3e315cd73a0ee9bea4f9c968cd7d485e9f4745bc1e679c4e x-checker-data: type: json url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest version-query: .tag_name url-query: .tarball_url timestamp-query: .published_at - # from https://github.com/flathub/net.gaijin.WarThunder/blob/7ea6f7a9f84b9c77150c003a7059dc03f8dcbc7f/gamemode.patch - - type: patch - path: patches/gamemode.patch cleanup: - /include - /lib/pkgconfig diff --git a/flatpak/patches/gamemode.patch b/flatpak/patches/gamemode.patch deleted file mode 100644 index 3cc0d7412..000000000 --- a/flatpak/patches/gamemode.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff -ruN a/common/common-pidfds.c b/common/common-pidfds.c ---- a/common/common-pidfds.c 2021-02-18 20:00:12.000000000 +0100 -+++ b/common/common-pidfds.c 2023-09-07 08:57:42.954362763 +0200 -@@ -58,6 +58,8 @@ - { - return (int)syscall(__NR_pidfd_open, pid, flags); - } -+#else -+#include - #endif - - /* pidfd functions */ diff --git a/flatpak/shared-modules b/flatpak/shared-modules index 45094ca57..f2b0c16a2 160000 --- a/flatpak/shared-modules +++ b/flatpak/shared-modules @@ -1 +1 @@ -Subproject commit 45094ca570be383d06df729b6972830ec63bd3df +Subproject commit f2b0c16a2a217a1822ce5a6538ba8f755ed1dd32 diff --git a/launcher/Application.cpp b/launcher/Application.cpp index be252f1c5..db4b0d9cd 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -132,6 +132,15 @@ #include "gamemode_client.h" #endif +#if defined(Q_OS_LINUX) +#include +#endif + +#if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) +#include +#include +#endif + #if defined(Q_OS_MAC) #if defined(SPARKLE_ENABLED) #include "updater/MacSparkleUpdater.h" @@ -485,8 +494,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } { - qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME) << ", (c) 2022-2023 " - << qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", ")); + qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", ")); qDebug() << "Version : " << BuildConfig.printableVersionString(); qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM; qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; @@ -631,10 +639,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); @@ -658,6 +667,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // The cat m_settings->registerSetting("TheCat", false); + m_settings->registerSetting("CatOpacity", 100); + + m_settings->registerSetting("StatusBarVisible", true); m_settings->registerSetting("ToolbarsLocked", false); @@ -740,6 +752,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("ModrinthToken", ""); m_settings->registerSetting("UserAgentOverride", ""); + // FTBApp instances + m_settings->registerSetting("FTBAppInstancesPath", ""); + // Init page provider { m_globalSettingsProvider = std::make_shared(tr("Settings")); @@ -988,6 +1003,37 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } } + // notify user if /tmp is mounted with `noexec` (#1693) + { + bool is_tmp_noexec = false; + +#if defined(Q_OS_LINUX) + + struct statvfs tmp_stat; + statvfs("/tmp", &tmp_stat); + is_tmp_noexec = tmp_stat.f_flag & ST_NOEXEC; + +#elif defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + + struct statfs tmp_stat; + statfs("/tmp", &tmp_stat); + is_tmp_noexec = tmp_stat.f_flags & MNT_NOEXEC; + +#endif + + if (is_tmp_noexec) { + auto infoMsg = + tr("Your /tmp directory is currently mounted with the 'noexec' flag enabled.\n" + "Some versions of Minecraft may not launch.\n"); + auto msgBox = new QMessageBox(QMessageBox::Information, tr("Incompatible system configuration"), infoMsg, QMessageBox::Ok); + msgBox->setDefaultButton(QMessageBox::Ok); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->setMinimumWidth(460); + msgBox->adjustSize(); + msgBox->open(); + } + } + if (createSetupWizard()) { return; } @@ -1471,6 +1517,17 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa auto& window = extras.window; if (window) { +// If the window is minimized on macOS or Windows, activate and bring it up +#ifdef Q_OS_MACOS + if (window->isMinimized()) { + window->setWindowState(window->windowState() & ~Qt::WindowMinimized); + } +#elif defined(Q_OS_WIN) + if (window->isMinimized()) { + window->showNormal(); + } +#endif + window->raise(); window->activateWindow(); } else { @@ -1478,6 +1535,7 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa m_openWindows++; connect(window, &InstanceWindow::isClosing, this, &Application::on_windowClose); } + if (!page.isEmpty()) { window->selectPage(page); } diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 33dc3f741..cda44b454 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -64,6 +64,8 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s m_settings->registerSetting("lastLaunchTime", 0); m_settings->registerSetting("totalTimePlayed", 0); + if (m_settings->get("totalTimePlayed").toLongLong() < 0) + m_settings->reset("totalTimePlayed"); m_settings->registerSetting("lastTimePlayed", 0); m_settings->registerSetting("linkedInstances", "[]"); diff --git a/launcher/DesktopServices.cpp b/launcher/DesktopServices.cpp index 17eb7c2df..841c1399c 100644 --- a/launcher/DesktopServices.cpp +++ b/launcher/DesktopServices.cpp @@ -37,140 +37,33 @@ #include #include #include - -/** - * This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing. - */ -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - -#include -#include -#include -#include - -template -bool IndirectOpen(T callable, qint64* pid_forked = nullptr) -{ - auto pid = fork(); - if (pid_forked) { - if (pid > 0) - *pid_forked = pid; - else - *pid_forked = 0; - } - if (pid == -1) { - qWarning() << "IndirectOpen failed to fork: " << errno; - return false; - } - // child - do the stuff - if (pid == 0) { - // unset all this garbage so it doesn't get passed to the child process - qunsetenv("LD_PRELOAD"); - qunsetenv("LD_LIBRARY_PATH"); - qunsetenv("LD_DEBUG"); - qunsetenv("QT_PLUGIN_PATH"); - qunsetenv("QT_FONTPATH"); - - // open the URL - auto status = callable(); - - // detach from the parent process group. - setsid(); - - // die. now. do not clean up anything, it would just hang forever. - _exit(status ? 0 : 1); - } else { - // parent - assume it worked. - int status; - while (waitpid(pid, &status, 0)) { - if (WIFEXITED(status)) { - return WEXITSTATUS(status) == 0; - } - if (WIFSIGNALED(status)) { - return false; - } - } - return true; - } -} -#endif +#include "FileSystem.h" namespace DesktopServices { -bool openDirectory(const QString& path, [[maybe_unused]] bool ensureExists) +bool openPath(const QFileInfo& path, bool ensureFolderPathExists) { - qDebug() << "Opening directory" << path; - QDir parentPath; - QDir dir(path); - if (ensureExists && !dir.exists()) { - parentPath.mkpath(dir.absolutePath()); + qDebug() << "Opening path" << path; + if (ensureFolderPathExists) { + FS::ensureFolderPathExists(path); } - auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); }; -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - if (!isSandbox()) { - return IndirectOpen(f); - } -#endif - return f(); + return openUrl(QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath())); } -bool openFile(const QString& path) +bool openPath(const QString& path, bool ensureFolderPathExists) { - qDebug() << "Opening file" << path; - auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); }; -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - if (!isSandbox()) { - return IndirectOpen(f); - } else { - return f(); - } -#else - return f(); -#endif -} - -bool openFile(const QString& application, const QString& path, const QString& workingDirectory, qint64* pid) -{ - qDebug() << "Opening file" << path << "using" << application; -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave - if (!isSandbox()) { - return IndirectOpen([&]() { return QProcess::startDetached(application, QStringList() << path, workingDirectory); }, pid); - } else { - return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid); - } -#else - return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid); -#endif + return openPath(QFileInfo(path), ensureFolderPathExists); } bool run(const QString& application, const QStringList& args, const QString& workingDirectory, qint64* pid) { qDebug() << "Running" << application << "with args" << args.join(' '); -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - if (!isSandbox()) { - // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave - return IndirectOpen([&]() { return QProcess::startDetached(application, args, workingDirectory); }, pid); - } else { - return QProcess::startDetached(application, args, workingDirectory, pid); - } -#else return QProcess::startDetached(application, args, workingDirectory, pid); -#endif } bool openUrl(const QUrl& url) { qDebug() << "Opening URL" << url.toString(); - auto f = [&]() { return QDesktopServices::openUrl(url); }; -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - if (!isSandbox()) { - return IndirectOpen(f); - } else { - return f(); - } -#else - return f(); -#endif + return QDesktopServices::openUrl(url); } bool isFlatpak() @@ -191,9 +84,4 @@ bool isSnap() #endif } -bool isSandbox() -{ - return isSnap() || isFlatpak(); -} - } // namespace DesktopServices diff --git a/launcher/DesktopServices.h b/launcher/DesktopServices.h index 151db5557..6c6208e82 100644 --- a/launcher/DesktopServices.h +++ b/launcher/DesktopServices.h @@ -3,31 +3,30 @@ #include #include +class QFileInfo; + /** * This wraps around QDesktopServices and adds workarounds where needed * Use this instead of QDesktopServices! */ namespace DesktopServices { /** - * Open a file in whatever application is applicable + * Open a path in whatever application is applicable. + * @param ensureFolderPathExists Make sure the path exists */ -bool openFile(const QString& path); +bool openPath(const QFileInfo& path, bool ensureFolderPathExists = false); /** - * Open a file in the specified application + * Open a path in whatever application is applicable. + * @param ensureFolderPathExists Make sure the path exists */ -bool openFile(const QString& application, const QString& path, const QString& workingDirectory = QString(), qint64* pid = 0); +bool openPath(const QString& path, bool ensureFolderPathExists = false); /** * Run an application */ bool run(const QString& application, const QStringList& args, const QString& workingDirectory = QString(), qint64* pid = 0); -/** - * Open a directory - */ -bool openDirectory(const QString& path, bool ensureExists = false); - /** * Open the URL, most likely in a browser. Maybe. */ @@ -42,9 +41,4 @@ bool isFlatpak(); * Determine whether the launcher is running in a Snap environment */ bool isSnap(); - -/** - * Determine whether the launcher is running in a sandboxed (Flatpak or Snap) environment - */ -bool isSandbox(); } // namespace DesktopServices 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 a30d0ae0b..f9be91a2a 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -272,15 +272,19 @@ bool ensureFilePathExists(QString filenamepath) return success; } -bool ensureFolderPathExists(QString foldernamepath) +bool ensureFolderPathExists(const QFileInfo folderPath) { - QFileInfo a(foldernamepath); QDir dir; - QString ensuredPath = a.filePath(); + QString ensuredPath = folderPath.filePath(); bool success = dir.mkpath(ensuredPath); return success; } +bool ensureFolderPathExists(const QString folderPathName) +{ + return ensureFolderPathExists(QFileInfo(folderPathName)); +} + bool copyFileAttributes(QString src, QString dst) { #ifdef Q_OS_WIN32 diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 861cfa267..f13fb9f28 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -91,7 +91,13 @@ bool ensureFilePathExists(QString filenamepath); * Creates all the folders in a path for the specified path * last segment of the path is treated as a folder name and is created! */ -bool ensureFolderPathExists(QString filenamepath); +bool ensureFolderPathExists(const QFileInfo folderPath); + +/** + * Creates all the folders in a path for the specified path + * last segment of the path is treated as a folder name and is created! + */ +bool ensureFolderPathExists(const QString folderPathName); /** * @brief Copies a directory and it's contents from src to dest diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 8abf30640..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); @@ -142,9 +142,8 @@ void InstanceCopyTask::copyFinished() if (!m_keepPlaytime) { inst->resetTimePlayed(); } - if (m_useLinks) - inst->addLinkedInstanceId(m_origInstance->id()); if (m_useLinks) { + inst->addLinkedInstanceId(m_origInstance->id()); auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt")); QByteArray allowed_symlinks; 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/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/InstanceTask.cpp b/launcher/InstanceTask.cpp index 5b2398268..53476897c 100644 --- a/launcher/InstanceTask.cpp +++ b/launcher/InstanceTask.cpp @@ -2,6 +2,8 @@ #include "ui/dialogs/CustomMessageBox.h" +#include + InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name) { auto dialog = @@ -27,16 +29,15 @@ ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name) "separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before " "updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).") .arg(original_version_name), - QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort); - info->setButtonText(QMessageBox::Ok, QObject::tr("Update existing instance")); - info->setButtonText(QMessageBox::Abort, QObject::tr("Create new instance")); - info->setButtonText(QMessageBox::Reset, QObject::tr("Cancel")); + QMessageBox::Information, QMessageBox::Cancel); + QAbstractButton* update = info->addButton(QObject::tr("Update existing instance"), QMessageBox::AcceptRole); + QAbstractButton* skip = info->addButton(QObject::tr("Create new instance"), QMessageBox::ResetRole); info->exec(); - if (info->clickedButton() == info->button(QMessageBox::Ok)) + if (info->clickedButton() == update) return ShouldUpdate::Update; - if (info->clickedButton() == info->button(QMessageBox::Abort)) + if (info->clickedButton() == skip) return ShouldUpdate::SkipUpdating; return ShouldUpdate::Cancel; } diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 8a8fa00a8..a30f99439 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -42,7 +42,6 @@ #include "ui/InstanceWindow.h" #include "ui/MainWindow.h" #include "ui/dialogs/CustomMessageBox.h" -#include "ui/dialogs/EditAccountDialog.h" #include "ui/dialogs/ProfileSelectDialog.h" #include "ui/dialogs/ProfileSetupDialog.h" #include "ui/dialogs/ProgressDialog.h" @@ -144,6 +143,12 @@ void LaunchController::login() bool tryagain = true; unsigned int tries = 0; + if (m_accountToUse->accountType() != AccountType::Offline && m_accountToUse->accountState() == AccountState::Offline) { + // Force account refresh on the account used to launch the instance updating the AccountState + // only on first try and if it is not meant to be offline + auto accounts = APPLICATION->accounts(); + accounts->requestRefresh(m_accountToUse->internalId()); + } while (tryagain) { if (tries > 0 && tries % 3 == 0) { auto result = @@ -250,12 +255,6 @@ void LaunchController::login() progDialog.execWithTask(task.get()); continue; } - // FIXME: this is missing - the meaning is that the account is queued for refresh and we should wait for that - /* - case AccountState::Queued: { - return; - } - */ case AccountState::Expired: { auto errorString = tr("The account has expired and needs to be logged into manually again."); QMessageBox::warning(m_parentWidget, tr("Account refresh failed"), errorString, QMessageBox::StandardButton::Ok, diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index ce2573329..033aa0755 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -463,7 +463,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/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 4229f73eb..e0b677b69 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; } diff --git a/launcher/minecraft/MojangVersionFormat.cpp b/launcher/minecraft/MojangVersionFormat.cpp index 6ee10ec15..bb782e47f 100644 --- a/launcher/minecraft/MojangVersionFormat.cpp +++ b/launcher/minecraft/MojangVersionFormat.cpp @@ -157,20 +157,6 @@ void MojangVersionFormat::readVersionProperties(const QJsonObject& in, VersionFi Bits::readString(in, "id", out->minecraftVersion); Bits::readString(in, "mainClass", out->mainClass); Bits::readString(in, "minecraftArguments", out->minecraftArguments); - if (out->minecraftArguments.isEmpty()) { - QString processArguments; - Bits::readString(in, "processArguments", processArguments); - QString toCompare = processArguments.toLower(); - if (toCompare == "legacy") { - out->minecraftArguments = " ${auth_player_name} ${auth_session}"; - } else if (toCompare == "username_session") { - out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; - } else if (toCompare == "username_session_version") { - out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; - } else if (!toCompare.isEmpty()) { - out->addProblem(ProblemSeverity::Error, QObject::tr("processArguments is set to unknown value '%1'").arg(processArguments)); - } - } Bits::readString(in, "type", out->type); Bits::readString(in, "assets", out->assets); diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index c3c09003c..68ebe3626 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -52,8 +52,6 @@ #include #include -#include - enum AccountListVersion { MojangMSA = 3 }; AccountList::AccountList(QObject* parent) : QAbstractListModel(parent) 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/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 6d0f3b48c..942caede5 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -303,7 +303,6 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet auto removed_it = m_resources.begin() + removed_index; Q_ASSERT(removed_it != m_resources.end()); - Q_ASSERT(removed_set.contains(removed_it->get()->internal_id())); if ((*removed_it)->isResolving()) { auto ticket = (*removed_it)->resolutionTicket(); diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp index efc17298e..5aef4e585 100644 --- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp +++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp @@ -182,7 +182,9 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType }; ResourceAPI::DependencySearchCallbacks callbacks; - + callbacks.on_fail = [](QString reason, int) { + qCritical() << tr("A network error occurred. Could not load project dependencies:%1").arg(reason); + }; callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, [[maybe_unused]] auto& pack) { try { QJsonArray arr; @@ -283,4 +285,4 @@ QHash GetModDependenciesTask::getRequiredBy() rby[addonId.toString()] = req; } return rby; -} \ No newline at end of file +} diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index a9ad22581..ce53ee62d 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -149,6 +149,7 @@ void EnsureMetadataTask::executeTask() if (m_current_task) m_current_task.reset(); }); + connect(project_task.get(), &Task::failed, this, &EnsureMetadataTask::emitFailed); m_current_task = project_task; project_task->start(); diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 72294c399..eff7e7f9f 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -122,6 +122,8 @@ struct ExtraPackData { QString wikiUrl; QString discordUrl; + QString status; + QString body; }; diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index 3b1959384..2c7bec5d4 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -96,6 +96,7 @@ class ResourceAPI { }; struct VersionSearchCallbacks { std::function on_succeed; + std::function on_fail; }; struct ProjectInfoArgs { @@ -118,6 +119,7 @@ class ResourceAPI { struct DependencySearchCallbacks { std::function on_succeed; + std::function on_fail; }; public: diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 5865bee91..8d23896d9 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -119,7 +119,6 @@ void Flame::FileResolvingTask::netJobFinished() connect(m_checkJob.get(), &NetJob::failed, this, [this, step_progress](QString reason) { step_progress->state = TaskStepState::Failed; stepProgress(*step_progress); - emitFailed(reason); }); connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress); connect(m_checkJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) { diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index c014863a3..b4eb304f0 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -24,7 +24,7 @@ bool FlameCheckUpdate::abort() return true; } -ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info) +ModPlatform::IndexedPack FlameCheckUpdate::getProjectInfo(ModPlatform::IndexedVersion& ver_info) { ModPlatform::IndexedPack pack; @@ -57,6 +57,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info) } }); + connect(get_project_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed); QObject::connect(get_project_job, &NetJob::finished, [&loop, get_project_job] { get_project_job->deleteLater(); loop.quit(); @@ -68,7 +69,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info) return pack; } -ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId) +ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileId) { ModPlatform::IndexedVersion ver; @@ -100,7 +101,7 @@ ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId) qDebug() << doc; } }); - + connect(get_file_info_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed); QObject::connect(get_file_info_job, &NetJob::finished, [&loop, get_file_info_job] { get_file_info_job->deleteLater(); loop.quit(); diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h index 05c619a70..f5bb1653d 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.h +++ b/launcher/modplatform/flame/FlameCheckUpdate.h @@ -22,6 +22,9 @@ class FlameCheckUpdate : public CheckUpdateTask { void executeTask() override; private: + ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info); + ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId); + NetJob* m_net_job = nullptr; bool m_was_aborted = false; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 2a26ce944..ef552c3c2 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -227,6 +227,7 @@ bool FlameCreationTask::updateInstance() m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path)); } }); + connect(job.get(), &Task::failed, this, [](QString reason) { qCritical() << "Failed to get files: " << reason; }); connect(job.get(), &Task::finished, &loop, &QEventLoop::quit); m_process_update_file_info_job = job; @@ -353,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-")) { @@ -427,6 +430,9 @@ bool FlameCreationTask::createInstance() // Don't add managed info to packs without an ID (most likely imported from ZIP) if (!m_managed_id.isEmpty()) instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version); + else + instance.setManagedPack("flame", "", name(), "", ""); + instance.setName(name()); m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack)); diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp index b5ab7bc73..e28c82b4c 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.cpp +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -323,6 +323,7 @@ void FlamePackExportTask::getProjectsInfo() } buildZip(); }); + connect(projTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed); task.reset(projTask); task->start(); } @@ -397,8 +398,12 @@ QByteArray FlamePackExportTask::generateIndex() id = "fabric-" + fabric->getVersion(); else if (forge != nullptr) id = "forge-" + forge->getVersion(); - else if (neoforge != nullptr) - id = "neoforge-" + neoforge->getVersion(); + else if (neoforge != nullptr) { + id = "neoforge-"; + if (minecraft->m_version == "1.20.1") + id += "1.20.1-"; + id += neoforge->getVersion(); + } version["modLoaders"] = QJsonArray(); if (!id.isEmpty()) { QJsonObject loader; diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp index dccccdc21..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,6 +102,13 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi callbacks.on_succeed(doc, args.pack); }); + 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(); + + callbacks.on_fail(reason, network_error_code); + }); return netJob; } @@ -146,6 +153,12 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args, callbacks.on_succeed(doc, args.dependency); }); + 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(); + callbacks.on_fail(reason, network_error_code); + }); return netJob; } 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/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index 9b7c53854..e78061f27 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -72,9 +72,7 @@ void ModrinthCheckUpdate::executeTask() auto response = std::make_shared(); auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response); - QEventLoop lock; - - connect(job.get(), &Task::succeeded, this, [this, response, &mappings, best_hash_type, job] { + connect(job.get(), &Task::succeeded, this, [this, response, mappings, best_hash_type, job] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -82,7 +80,7 @@ void ModrinthCheckUpdate::executeTask() << " reason: " << parse_error.errorString(); qWarning() << *response; - failed(parse_error.errorString()); + emitFailed(parse_error.errorString()); return; } @@ -167,19 +165,17 @@ void ModrinthCheckUpdate::executeTask() m_deps.append(std::make_shared(pack, project_ver)); } } catch (Json::JsonException& e) { - failed(e.cause() + " : " + e.what()); + emitFailed(e.cause() + " : " + e.what()); + return; } + emitSucceeded(); }); - connect(job.get(), &Task::finished, &lock, &QEventLoop::quit); + connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::emitFailed); setStatus(tr("Waiting for the API response from Modrinth...")); setProgress(1, 3); m_net_job = qSharedPointerObjectCast(job); job->start(); - - lock.exec(); - - emitSucceeded(); } diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index e732ad39c..824fdce7e 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -173,7 +173,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, "minecraft"); auto override_path = FS::PathCombine(m_stagingPath, "overrides"); if (QFile::exists(override_path)) { @@ -226,12 +226,15 @@ bool ModrinthCreationTask::createInstance() // Don't add managed info to packs without an ID (most likely imported from ZIP) if (!m_managed_id.isEmpty()) instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version()); + else + instance.setManagedPack("modrinth", "", name(), "", ""); + instance.setName(name()); instance.saveNow(); 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, "minecraft"); auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path); for (auto file : m_files) { @@ -289,7 +292,7 @@ bool ModrinthCreationTask::createInstance() // Only change the name if it didn't use a custom name, so that the previous custom name // is preserved, but if we're using the original one, we update the version string. // NOTE: This needs to come before the copyManagedPack call! - if (inst->name().contains(inst->getManagedPackVersionName())) { + if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != instance.name()) { if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange) inst->setName(instance.name()); } diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index e9e8a3b75..c704708ad 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/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 7d0893261..c1c30ab5f 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -104,6 +104,8 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob pack.extraData.donate.append(donate); } + pack.extraData.status = Json::ensureString(obj, "status"); + pack.extraData.body = Json::ensureString(obj, "body").remove("
"); pack.extraDataLoaded = true; diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index d86f1c0e5..7846e966d 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -95,6 +95,8 @@ void loadIndexedInfo(Modpack& pack, QJsonObject& obj) pack.extra.donate.append(donate); } + pack.extra.status = Json::ensureString(obj, "status"); + pack.extraInfoLoaded = true; } diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h index 6fc55a043..1ffd31d83 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.h +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h @@ -77,6 +77,8 @@ struct ModpackExtra { QString discordUrl; QList donate; + + QString status; }; struct ModpackVersion { 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/net/NetJob.cpp b/launcher/net/NetJob.cpp index 784d81c37..d027e31c9 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -36,6 +36,7 @@ */ #include "NetJob.h" +#include "tasks/ConcurrentTask.h" #if defined(LAUNCHER_APPLICATION) #include "Application.h" #endif @@ -56,18 +57,15 @@ auto NetJob::addNetAction(NetAction::Ptr action) -> bool return true; } -void NetJob::startNext() +void NetJob::executeNextSubTask() { - if (m_queue.isEmpty() && m_doing.isEmpty()) { - // We're finished, check for failures and retry if we can (up to 3 times) - if (!m_failed.isEmpty() && m_try < 3) { - m_try += 1; - while (!m_failed.isEmpty()) - m_queue.enqueue(m_failed.take(*m_failed.keyBegin())); - } + // We're finished, check for failures and retry if we can (up to 3 times) + if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) { + m_try += 1; + while (!m_failed.isEmpty()) + m_queue.enqueue(m_failed.take(*m_failed.keyBegin())); } - - ConcurrentTask::startNext(); + ConcurrentTask::executeNextSubTask(); } auto NetJob::size() const -> int diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h index 1c4337ec6..f6c005809 100644 --- a/launcher/net/NetJob.h +++ b/launcher/net/NetJob.h @@ -55,8 +55,6 @@ class NetJob : public ConcurrentTask { explicit NetJob(QString job_name, shared_qobject_ptr network); ~NetJob() override = default; - void startNext() override; - auto size() const -> int; auto canAbort() const -> bool override; @@ -69,6 +67,9 @@ class NetJob : public ConcurrentTask { // Qt can't handle auto at the start for some reason? bool abort() override; + protected slots: + void executeNextSubTask() override; + protected: void updateState() override; diff --git a/launcher/qtlogging.ini b/launcher/qtlogging.ini index c12d1e109..5266de59b 100644 --- a/launcher/qtlogging.ini +++ b/launcher/qtlogging.ini @@ -5,6 +5,7 @@ qt.*.debug=false # don't log credentials by default launcher.auth.credentials.debug=false +katabasis.*.debug=false # remove the debug lines, other log levels still get through launcher.task.net.download.debug=false # enable or disable whole catageries diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index 7ed672eb7..15fb043e4 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -58,14 +58,14 @@ void ImgurUpload::init() QNetworkReply* ImgurUpload::getReply(QNetworkRequest& request) { - auto file = new QFile(m_fileInfo.absoluteFilePath()); + auto file = new QFile(m_fileInfo.absoluteFilePath(), this); if (!file->open(QFile::ReadOnly)) { emitFailed(); return nullptr; } - QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this); file->setParent(multipart); QHttpPart filePart; filePart.setBodyDevice(file); diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp index 4fb11ed35..e97741f20 100644 --- a/launcher/settings/INIFile.cpp +++ b/launcher/settings/INIFile.cpp @@ -54,6 +54,7 @@ bool INIFile::saveFile(QString fileName) insert("ConfigVersion", "1.2"); QSettings _settings_obj{ fileName, QSettings::Format::IniFormat }; _settings_obj.setFallbacksEnabled(false); + _settings_obj.clear(); for (Iterator iter = begin(); iter != end(); iter++) _settings_obj.setValue(iter.key(), iter.value()); diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 6cfd864cc..6f4a94e7f 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -35,7 +35,6 @@ */ #include "ConcurrentTask.h" -#include #include #include "tasks/Task.h" @@ -47,9 +46,9 @@ ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concu ConcurrentTask::~ConcurrentTask() { - for (auto task : m_queue) { + for (auto task : m_doing) { if (task) - task->deleteLater(); + task->disconnect(this); } } @@ -65,15 +64,13 @@ void ConcurrentTask::addTask(Task::Ptr task) void ConcurrentTask::executeTask() { - // Start one task, startNext handles starting the up to the m_total_max_size - // while tracking the number currently being done - QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); + for (auto i = 0; i < m_total_max_size; i++) + QMetaObject::invokeMethod(this, &ConcurrentTask::executeNextSubTask, Qt::QueuedConnection); } bool ConcurrentTask::abort() { m_queue.clear(); - m_aborted = true; if (m_doing.isEmpty()) { // Don't call emitAborted() here, we want to bypass the 'is the task running' check @@ -108,29 +105,36 @@ void ConcurrentTask::clear() m_failed.clear(); m_queue.clear(); - m_aborted = false; - m_progress = 0; m_stepProgress = 0; } -void ConcurrentTask::startNext() +void ConcurrentTask::executeNextSubTask() { - if (m_aborted || m_doing.count() > m_total_max_size) + if (!isRunning()) { return; - - if (m_queue.isEmpty() && m_doing.isEmpty() && !wasSuccessful()) { - emitSucceeded(); + } + if (m_doing.count() >= m_total_max_size) { + return; + } + if (m_queue.isEmpty()) { + if (m_doing.isEmpty()) { + if (m_failed.isEmpty()) + emitSucceeded(); + else + emitFailed(tr("One or more subtasks failed")); + } return; } - if (m_queue.isEmpty()) - return; - - Task::Ptr next = m_queue.dequeue(); + startSubTask(m_queue.dequeue()); +} +void ConcurrentTask::startSubTask(Task::Ptr next) +{ connect(next.get(), &Task::succeeded, this, [this, next]() { subTaskSucceeded(next); }); connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); }); + // this should never happen but if it does, it's better to fail the task than get stuck connect(next.get(), &Task::aborted, this, [this, next] { subTaskFailed(next, "Aborted"); }); connect(next.get(), &Task::status, this, [this, next](QString msg) { subTaskStatus(next, msg); }); @@ -140,55 +144,42 @@ void ConcurrentTask::startNext() connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total) { subTaskProgress(next, current, total); }); m_doing.insert(next.get(), next); - qsizetype num_starts = qMin(m_queue.size(), m_total_max_size - m_doing.size()); + auto task_progress = std::make_shared(next->getUid()); m_task_progress.insert(next->getUid(), task_progress); updateState(); updateStepProgress(*task_progress.get(), Operation::ADDED); - QCoreApplication::processEvents(); - QMetaObject::invokeMethod(next.get(), &Task::start, Qt::QueuedConnection); +} - // Allow going up the number of concurrent tasks in case of tasks being added in the middle of a running task. - for (int i = 0; i < num_starts; i++) - QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); +void ConcurrentTask::subTaskFinished(Task::Ptr task, TaskStepState state) +{ + m_done.insert(task.get(), task); + (state == TaskStepState::Succeeded ? m_succeeded : m_failed).insert(task.get(), task); + + m_doing.remove(task.get()); + + auto task_progress = m_task_progress.value(task->getUid()); + task_progress->state = state; + + disconnect(task.get(), 0, this, 0); + + emit stepProgress(*task_progress); + updateState(); + updateStepProgress(*task_progress, Operation::REMOVED); + QMetaObject::invokeMethod(this, &ConcurrentTask::executeNextSubTask, Qt::QueuedConnection); } void ConcurrentTask::subTaskSucceeded(Task::Ptr task) { - m_done.insert(task.get(), task); - m_succeeded.insert(task.get(), task); - - m_doing.remove(task.get()); - auto task_progress = m_task_progress.value(task->getUid()); - task_progress->state = TaskStepState::Succeeded; - - disconnect(task.get(), 0, this, 0); - - emit stepProgress(*task_progress); - updateState(); - updateStepProgress(*task_progress, Operation::REMOVED); - startNext(); + subTaskFinished(task, TaskStepState::Succeeded); } void ConcurrentTask::subTaskFailed(Task::Ptr task, [[maybe_unused]] const QString& msg) { - m_done.insert(task.get(), task); - m_failed.insert(task.get(), task); - - m_doing.remove(task.get()); - - auto task_progress = m_task_progress.value(task->getUid()); - task_progress->state = TaskStepState::Failed; - - disconnect(task.get(), 0, this, 0); - - emit stepProgress(*task_progress); - updateState(); - updateStepProgress(*task_progress, Operation::REMOVED); - startNext(); + subTaskFinished(task, TaskStepState::Failed); } void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index 00b1d48d6..07ea58575 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -72,10 +72,11 @@ class ConcurrentTask : public Task { protected slots: void executeTask() override; - virtual void startNext(); + virtual void executeNextSubTask(); void subTaskSucceeded(Task::Ptr); - void subTaskFailed(Task::Ptr, const QString& msg); + virtual void subTaskFailed(Task::Ptr, const QString& msg); + void subTaskFinished(Task::Ptr, TaskStepState); void subTaskStatus(Task::Ptr task, const QString& msg); void subTaskDetails(Task::Ptr task, const QString& msg); void subTaskProgress(Task::Ptr task, qint64 current, qint64 total); @@ -90,6 +91,8 @@ class ConcurrentTask : public Task { virtual void updateState(); + void startSubTask(Task::Ptr task); + protected: QString m_name; QString m_step_status; @@ -107,6 +110,4 @@ class ConcurrentTask : public Task { qint64 m_stepProgress = 0; qint64 m_stepTotalProgress = 100; - - bool m_aborted = false; }; diff --git a/launcher/tasks/MultipleOptionsTask.cpp b/launcher/tasks/MultipleOptionsTask.cpp index 89187a26d..5afe03964 100644 --- a/launcher/tasks/MultipleOptionsTask.cpp +++ b/launcher/tasks/MultipleOptionsTask.cpp @@ -36,9 +36,9 @@ #include -MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : SequentialTask(parent, task_name) {} +MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : ConcurrentTask(parent, task_name, 1) {} -void MultipleOptionsTask::startNext() +void MultipleOptionsTask::executeNextSubTask() { if (m_done.size() != m_failed.size()) { emitSucceeded(); @@ -51,7 +51,7 @@ void MultipleOptionsTask::startNext() return; } - ConcurrentTask::startNext(); + ConcurrentTask::executeNextSubTask(); } void MultipleOptionsTask::updateState() diff --git a/launcher/tasks/MultipleOptionsTask.h b/launcher/tasks/MultipleOptionsTask.h index a344343ef..9a88a9999 100644 --- a/launcher/tasks/MultipleOptionsTask.h +++ b/launcher/tasks/MultipleOptionsTask.h @@ -34,18 +34,18 @@ */ #pragma once -#include "SequentialTask.h" +#include "ConcurrentTask.h" /* This task type will attempt to do run each of it's subtasks in sequence, * until one of them succeeds. When that happens, the remaining tasks will not run. * */ -class MultipleOptionsTask : public SequentialTask { +class MultipleOptionsTask : public ConcurrentTask { Q_OBJECT public: explicit MultipleOptionsTask(QObject* parent = nullptr, const QString& task_name = ""); ~MultipleOptionsTask() override = default; private slots: - void startNext() override; + void executeNextSubTask() override; void updateState() override; }; diff --git a/launcher/tasks/SequentialTask.cpp b/launcher/tasks/SequentialTask.cpp index abf7536b9..509d91cf7 100644 --- a/launcher/tasks/SequentialTask.cpp +++ b/launcher/tasks/SequentialTask.cpp @@ -36,18 +36,15 @@ #include "SequentialTask.h" #include +#include "tasks/ConcurrentTask.h" SequentialTask::SequentialTask(QObject* parent, QString task_name) : ConcurrentTask(parent, task_name, 1) {} -void SequentialTask::startNext() +void SequentialTask::subTaskFailed(Task::Ptr task, const QString& msg) { - if (m_failed.size() > 0) { - emitFailed(tr("One of the tasks failed!")); - qWarning() << m_failed.constBegin()->get()->failReason(); - return; - } - - ConcurrentTask::startNext(); + emitFailed(msg); + qWarning() << msg; + ConcurrentTask::subTaskFailed(task, msg); } void SequentialTask::updateState() diff --git a/launcher/tasks/SequentialTask.h b/launcher/tasks/SequentialTask.h index cec3b2be8..a7c101ab4 100644 --- a/launcher/tasks/SequentialTask.h +++ b/launcher/tasks/SequentialTask.h @@ -50,7 +50,9 @@ class SequentialTask : public ConcurrentTask { explicit SequentialTask(QObject* parent = nullptr, QString task_name = ""); ~SequentialTask() override = default; + protected slots: + virtual void subTaskFailed(Task::Ptr, const QString& msg) override; + protected: - void startNext() override; void updateState() override; }; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 7b4d1c8a5..85573314d 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -186,6 +186,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi ui->instanceToolBar->addContextMenuAction(ui->newsToolBar->toggleViewAction()); ui->instanceToolBar->addContextMenuAction(ui->instanceToolBar->toggleViewAction()); + ui->instanceToolBar->addContextMenuAction(ui->actionToggleStatusBar); ui->instanceToolBar->addContextMenuAction(ui->actionLockToolbars); } @@ -319,6 +320,14 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi setCatBackground(cat_enable); } + // Togglable status bar + { + bool statusBarVisible = APPLICATION->settings()->get("StatusBarVisible").toBool(); + ui->actionToggleStatusBar->setChecked(statusBarVisible); + connect(ui->actionToggleStatusBar, &QAction::toggled, this, &MainWindow::setStatusBarVisibility); + setStatusBarVisibility(statusBarVisible); + } + // Lock toolbars { bool toolbarsLocked = APPLICATION->settings()->get("ToolbarsLocked").toBool(); @@ -451,10 +460,16 @@ QMenu* MainWindow::createPopupMenu() QMenu* filteredMenu = QMainWindow::createPopupMenu(); filteredMenu->removeAction(ui->mainToolBar->toggleViewAction()); + filteredMenu->addAction(ui->actionToggleStatusBar); filteredMenu->addAction(ui->actionLockToolbars); return filteredMenu; } +void MainWindow::setStatusBarVisibility(bool state) +{ + statusBar()->setVisible(state); + APPLICATION->settings()->set("StatusBarVisible", state); +} void MainWindow::lockToolbars(bool state) { ui->mainToolBar->setMovable(!state); @@ -1182,43 +1197,43 @@ void MainWindow::undoTrashInstance() void MainWindow::on_actionViewLauncherRootFolder_triggered() { - DesktopServices::openDirectory("."); + DesktopServices::openPath("."); } void MainWindow::on_actionViewInstanceFolder_triggered() { QString str = APPLICATION->settings()->get("InstanceDir").toString(); - DesktopServices::openDirectory(str); + DesktopServices::openPath(str); } void MainWindow::on_actionViewCentralModsFolder_triggered() { - DesktopServices::openDirectory(APPLICATION->settings()->get("CentralModsDir").toString(), true); + DesktopServices::openPath(APPLICATION->settings()->get("CentralModsDir").toString(), true); } void MainWindow::on_actionViewIconThemeFolder_triggered() { - DesktopServices::openDirectory(APPLICATION->themeManager()->getIconThemesFolder().path(), true); + DesktopServices::openPath(APPLICATION->themeManager()->getIconThemesFolder().path(), true); } void MainWindow::on_actionViewWidgetThemeFolder_triggered() { - DesktopServices::openDirectory(APPLICATION->themeManager()->getApplicationThemesFolder().path(), true); + DesktopServices::openPath(APPLICATION->themeManager()->getApplicationThemesFolder().path(), true); } void MainWindow::on_actionViewCatPackFolder_triggered() { - DesktopServices::openDirectory(APPLICATION->themeManager()->getCatPacksFolder().path(), true); + DesktopServices::openPath(APPLICATION->themeManager()->getCatPacksFolder().path(), true); } void MainWindow::on_actionViewIconsFolder_triggered() { - DesktopServices::openDirectory(APPLICATION->icons()->getDirectory(), true); + DesktopServices::openPath(APPLICATION->icons()->getDirectory(), true); } void MainWindow::on_actionViewLogsFolder_triggered() { - DesktopServices::openDirectory("logs", true); + DesktopServices::openPath("logs", true); } void MainWindow::refreshInstances() @@ -1437,7 +1452,7 @@ void MainWindow::on_actionViewSelectedInstFolder_triggered() { if (m_selectedInstance) { QString str = m_selectedInstance->instanceRoot(); - DesktopServices::openDirectory(QDir(str).absolutePath()); + DesktopServices::openPath(QFileInfo(str)); } } diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index d2e154643..07a6e1eba 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -205,6 +205,8 @@ class MainWindow : public QMainWindow { void globalSettingsClosed(); + void setStatusBarVisibility(bool); + void lockToolbars(bool); #ifndef Q_OS_MAC diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 1ee3a5632..889012105 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -176,6 +176,7 @@ + @@ -257,6 +258,14 @@ It's a fluffy kitty :3 + + + true + + + Status Bar + + true diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index cee01e821..17b79ecaa 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -174,8 +174,7 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia QString urlText("

%1

"); ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT)); - QString copyText("© 2022-2023 %1"); - ui->copyLabel->setText(copyText.arg(BuildConfig.LAUNCHER_COPYRIGHT)); + ui->copyLabel->setText(BuildConfig.LAUNCHER_COPYRIGHT); connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); diff --git a/launcher/ui/dialogs/AboutDialog.h b/launcher/ui/dialogs/AboutDialog.h index 356f005e0..5da686b5f 100644 --- a/launcher/ui/dialogs/AboutDialog.h +++ b/launcher/ui/dialogs/AboutDialog.h @@ -15,7 +15,6 @@ #pragma once -#include #include namespace Ui { @@ -31,7 +30,4 @@ class AboutDialog : public QDialog { private: Ui::AboutDialog* ui; - - NetJob::Ptr netJob; - QByteArray dataSink; }; 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/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index faad3ce75..a196fd587 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -159,5 +159,5 @@ IconPickerDialog::~IconPickerDialog() void IconPickerDialog::openFolder() { - DesktopServices::openDirectory(APPLICATION->icons()->getDirectory(), true); + DesktopServices::openPath(APPLICATION->icons()->getDirectory(), true); } diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index cbaaf88b9..190638487 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -328,6 +328,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); }); + connect(modrinth_task.get(), &EnsureMetadataTask::failed, + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); if (modrinth_task->getHashingTask()) seq.addTask(modrinth_task->getHashingTask()); @@ -341,6 +343,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); }); + connect(flame_task.get(), &EnsureMetadataTask::failed, + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); if (flame_task->getHashingTask()) seq.addTask(flame_task->getHashingTask()); @@ -394,6 +398,8 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::R auto task = makeShared(mod, index_dir, next(first_choice)); connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Mod* candidate) { onMetadataFailed(candidate, false); }); + connect(task.get(), &EnsureMetadataTask::failed, + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); m_second_try_metadata->addTask(task); } else { @@ -437,6 +443,9 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri reqItem->insertChildren(i++, { reqItem }); } } + + ui->toggleDepsButton->show(); + m_deps << item_top; } auto changelog_item = new QTreeWidgetItem(item_top); 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/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp index aa668f8c2..41b832e03 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.cpp +++ b/launcher/ui/dialogs/ReviewMessageBox.cpp @@ -13,6 +13,7 @@ ReviewMessageBox::ReviewMessageBox(QWidget* parent, [[maybe_unused]] QString con auto back_button = ui->buttonBox->button(QDialogButtonBox::Cancel); back_button->setText(tr("Back")); + ui->toggleDepsButton->hide(); ui->modTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch); ui->modTreeWidget->header()->setStretchLastSection(false); ui->modTreeWidget->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); @@ -75,6 +76,8 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info) } itemTop->insertChildren(childIndx++, { requiredByItem }); + ui->toggleDepsButton->show(); + m_deps << itemTop; } auto versionTypeItem = new QTreeWidgetItem(itemTop); @@ -108,3 +111,10 @@ void ReviewMessageBox::retranslateUi(QString resources_name) ui->explainLabel->setText(tr("You're about to download the following %1:").arg(resources_name)); ui->onlyCheckedLabel->setText(tr("Only %1 with a check will be downloaded!").arg(resources_name)); } +void ReviewMessageBox::on_toggleDepsButton_clicked() +{ + m_deps_checked = !m_deps_checked; + auto state = m_deps_checked ? Qt::Checked : Qt::Unchecked; + for (auto dep : m_deps) + dep->setCheckState(0, state); +}; \ No newline at end of file diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h index 596f39c8e..7dd2732a0 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.h +++ b/launcher/ui/dialogs/ReviewMessageBox.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace Ui { class ReviewMessageBox; @@ -28,8 +29,14 @@ class ReviewMessageBox : public QDialog { ~ReviewMessageBox() override; + protected slots: + void on_toggleDepsButton_clicked(); + protected: ReviewMessageBox(QWidget* parent, const QString& title, const QString& icon); Ui::ReviewMessageBox* ui; + + QList m_deps; + bool m_deps_checked = true; }; diff --git a/launcher/ui/dialogs/ReviewMessageBox.ui b/launcher/ui/dialogs/ReviewMessageBox.ui index bf53ae80b..dbe351019 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.ui +++ b/launcher/ui/dialogs/ReviewMessageBox.ui @@ -44,15 +44,20 @@ - - + - + + + Toggle Dependencies + + + + diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index e665097f4..0723b5add 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -458,6 +458,7 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event) QPainter painter(this->viewport()); if (m_catVisible) { + painter.setOpacity(APPLICATION->settings()->get("CatOpacity").toFloat() / 100); int widWidth = this->viewport()->width(); int widHeight = this->viewport()->height(); if (m_catPixmap.width() < widWidth) @@ -468,6 +469,7 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event) QRect rectOfPixmap = pixmap.rect(); rectOfPixmap.moveBottomRight(this->viewport()->rect().bottomRight()); painter.drawPixmap(rectOfPixmap.topLeft(), pixmap); + painter.setOpacity(1.0); } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) @@ -480,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/instanceview/VisualGroup.cpp b/launcher/ui/instanceview/VisualGroup.cpp index aaf31941d..7bff727fe 100644 --- a/launcher/ui/instanceview/VisualGroup.cpp +++ b/launcher/ui/instanceview/VisualGroup.cpp @@ -158,13 +158,14 @@ void VisualGroup::drawHeader(QPainter* painter, const QStyleOptionViewItem& opti painter->setRenderHint(QPainter::Antialiasing); // sizes and offsets, to keep things consistent below - int arrowOffsetLeft = fontMetrics.height() / 2 + 7; - int textOffsetLeft = arrowOffsetLeft * 2; - int arrowSize = 6; - int centerHeight = optRect.top() + fontMetrics.height() / 2; + const int arrowOffsetLeft = fontMetrics.height() / 2 + 7; + const int textOffsetLeft = arrowOffsetLeft * 2; + const int centerHeight = optRect.top() + fontMetrics.height() / 2; + const QString& textToDraw = text.isEmpty() ? QObject::tr("Ungrouped") : text; // BEGIN: arrow { + constexpr int arrowSize = 6; QPolygon arrowPolygon; if (collapsed) { arrowPolygon << QPoint(arrowOffsetLeft - arrowSize / 2, centerHeight - arrowSize) @@ -188,9 +189,26 @@ void VisualGroup::drawHeader(QPainter* painter, const QStyleOptionViewItem& opti textRect.setHeight(fontMetrics.height()); textRect.setRight(textRect.right() - 7); - painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, !text.isEmpty() ? text : QObject::tr("Ungrouped")); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, textToDraw); } // END: text + + // BEGIN: horizontal line + { + penColor.setAlphaF(0.05); + pen.setColor(penColor); + painter->setPen(pen); + // startPoint is left + arrow + text + space + const int startPoint = + optRect.left() + fontMetrics.height() + fontMetrics.size(Qt::AlignLeft | Qt::AlignVCenter, textToDraw).width() + 20; + painter->setRenderHint(QPainter::Antialiasing, false); + QPolygon polygon; + // for some reason the height (yPos) doesn't look centered, so we are adding 1 to the center height + const int lineHeight = centerHeight + 1; + polygon << QPoint(startPoint, lineHeight) << QPoint(optRect.right() - 3, lineHeight); + painter->drawPolyline(polygon); + } + // END: horizontal line } int VisualGroup::totalHeight() const diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index d15883db5..78c44380a 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -221,6 +221,9 @@ void LauncherPage::applySettings() break; } + // Cat + s->set("CatOpacity", ui->catOpacitySpinBox->value()); + // Mods s->set("ModMetadataDisabled", ui->metadataDisableBtn->isChecked()); s->set("ModDependenciesDisabled", ui->dependenciesDisableBtn->isChecked()); @@ -276,6 +279,9 @@ void LauncherPage::loadSettings() ui->sortByNameBtn->setChecked(true); } + // Cat + ui->catOpacitySpinBox->setValue(s->get("CatOpacity").toInt()); + // Mods ui->metadataDisableBtn->setChecked(s->get("ModMetadataDisabled").toBool()); ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 83897f7a7..18b52e1b8 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -186,7 +186,7 @@ - + Disable the automatic detection, installation, and updating of mod dependencies. @@ -300,6 +300,54 @@ + + + + Cat + + + + + + Set the cat's opacity. 0% is fully transparent and 100% is fully opaque. + + + Opacity + + + + + + + + + + % + + + 100 + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + 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 2a3c0d96d..7d2741250 100644 --- a/launcher/ui/pages/global/MinecraftPage.ui +++ b/launcher/ui/pages/global/MinecraftPage.ui @@ -206,7 +206,7 @@ - <html><head/><body><p>Emulates usages of old online services which are no longer operating.</p><p>This currently allows modern skins to be used.</p></body></html> + <html><head/><body><p>Emulates usages of old online services which are no longer operating.</p><p>Current fixes include: skin and online mode support.</p></body></html> Enable online fixes (experimental) @@ -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/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index 663a2806f..8f8dab46d 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -291,12 +291,12 @@ void ExternalResourcesPage::disableItem() void ExternalResourcesPage::viewConfigs() { - DesktopServices::openDirectory(m_instance->instanceConfigFolder(), true); + DesktopServices::openPath(m_instance->instanceConfigFolder(), true); } void ExternalResourcesPage::viewFolder() { - DesktopServices::openDirectory(m_model->dir().absolutePath(), true); + DesktopServices::openPath(m_model->dir().absolutePath(), true); } bool ExternalResourcesPage::current(const QModelIndex& current, const QModelIndex& previous) diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 74785f97e..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 @@ -346,7 +349,7 @@ void InstanceSettingsPage::loadSettings() #ifdef Q_OS_LINUX ui->lineEditOpenALPath->setPlaceholderText(APPLICATION->m_detectedOpenALPath); #else - ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME)); + ui->lineEditOpenALPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME)); #endif // Performance @@ -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 3b06ac865..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 + + + @@ -605,7 +615,7 @@ - <html><head/><body><p>Emulates usages of old online services which are no longer operating.</p><p>This currently allows modern skins to be used.</p></body></html> + <html><head/><body><p>Emulates usages of old online services which are no longer operating.</p><p>Current fixes include: skin and online mode support.</p></body></html> Enable online fixes (experimental) diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index 26a8302db..2210d0263 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -131,6 +131,22 @@ ManagedPackPage::~ManagedPackPage() void ManagedPackPage::openedImpl() { + if (m_inst->getManagedPackID().isEmpty()) { + ui->packVersion->hide(); + ui->packVersionLabel->hide(); + ui->packOrigin->hide(); + ui->packOriginLabel->hide(); + ui->versionsComboBox->hide(); + ui->updateButton->hide(); + ui->updateToVersionLabel->hide(); + ui->updateFromFileButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + ui->packName->setText(m_inst->name()); + ui->changelogTextBrowser->setText(tr("This is a local modpack.\n" + "This can be updated only using a file in %1 format\n") + .arg(displayName())); + return; + } ui->packName->setText(m_inst->getManagedPackName()); ui->packVersion->setText(m_inst->getManagedPackVersionName()); ui->packOrigin->setText(tr("Website: %2 | Pack ID: %3 | Version ID: %4") @@ -355,6 +371,8 @@ void ModrinthManagedPackPage::update() void ModrinthManagedPackPage::updateFromFile() { auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), "Modrinth pack (*.mrpack *.zip)"); + if (output.isEmpty()) + return; QMap extra_info; extra_info.insert("pack_id", m_inst->getManagedPackID()); extra_info.insert("pack_version_id", QString()); @@ -472,7 +490,7 @@ void FlameManagedPackPage::parseManagedPack() QString FlameManagedPackPage::url() const { // FIXME: We should display the websiteUrl field, but this requires doing the API request first :( - return {}; + return "https://www.curseforge.com/projects/" + m_inst->getManagedPackID(); } void FlameManagedPackPage::suggestVersion() @@ -519,6 +537,8 @@ void FlameManagedPackPage::update() void FlameManagedPackPage::updateFromFile() { auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), "CurseForge pack (*.zip)"); + if (output.isEmpty()) + return; QMap extra_info; extra_info.insert("pack_id", m_inst->getManagedPackID()); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index ce1cc3a90..313fef2b6 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -242,7 +242,7 @@ void ModFolderPage::updateMods(bool includeDeps) if (m_instance != nullptr && m_instance->isRunning()) { auto response = CustomMessageBox::selectable(this, tr("Confirm Update"), - tr("If you update mods while the game is running may cause mod duplication and game crashes.\n" + tr("Updating mods while the game is running may cause mod duplication and game crashes.\n" "The old files may not be deleted as they are in use.\n" "Are you sure you want to do this?"), QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index 83fb0d5ff..c3f955733 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -324,8 +324,7 @@ void ScreenshotsPage::onItemActivated(QModelIndex index) if (!index.isValid()) return; auto info = m_model->fileInfo(index); - QString fileName = info.absoluteFilePath(); - DesktopServices::openFile(info.absoluteFilePath()); + DesktopServices::openPath(info); } void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected) @@ -352,7 +351,7 @@ void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected) void ScreenshotsPage::on_actionView_Folder_triggered() { - DesktopServices::openDirectory(m_folder, true); + DesktopServices::openPath(m_folder, true); } void ScreenshotsPage::on_actionUpload_triggered() diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index 2918261d2..487433230 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -295,13 +295,6 @@ void VersionPage::on_actionRemove_triggered() m_container->refreshContainer(); } -void VersionPage::on_actionInstall_mods_triggered() -{ - if (m_container) { - m_container->selectPage("mods"); - } -} - void VersionPage::on_actionAdd_to_Minecraft_jar_triggered() { auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), @@ -454,12 +447,12 @@ void VersionPage::on_actionAdd_Empty_triggered() void VersionPage::on_actionLibrariesFolder_triggered() { - DesktopServices::openDirectory(m_inst->getLocalLibraryPath(), true); + DesktopServices::openPath(m_inst->getLocalLibraryPath(), true); } void VersionPage::on_actionMinecraftFolder_triggered() { - DesktopServices::openDirectory(m_inst->gameRoot(), true); + DesktopServices::openPath(m_inst->gameRoot(), true); } void VersionPage::versionCurrent(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) diff --git a/launcher/ui/pages/instance/VersionPage.h b/launcher/ui/pages/instance/VersionPage.h index 6915be883..951643743 100644 --- a/launcher/ui/pages/instance/VersionPage.h +++ b/launcher/ui/pages/instance/VersionPage.h @@ -80,7 +80,6 @@ class VersionPage : public QMainWindow, public BasePage { void on_actionAdd_Agents_triggered(); void on_actionRevert_triggered(); void on_actionEdit_triggered(); - void on_actionInstall_mods_triggered(); void on_actionCustomize_triggered(); void on_actionDownload_All_triggered(); diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 587bb6ce6..692db7ad7 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -207,7 +207,7 @@ void WorldListPage::on_actionRemove_triggered() void WorldListPage::on_actionView_Folder_triggered() { - DesktopServices::openDirectory(m_worlds->dir().absolutePath(), true); + DesktopServices::openPath(m_worlds->dir().absolutePath(), true); } void WorldListPage::on_actionDatapacks_triggered() @@ -223,7 +223,7 @@ void WorldListPage::on_actionDatapacks_triggered() auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); - DesktopServices::openDirectory(FS::PathCombine(fullPath, "datapacks"), true); + DesktopServices::openPath(FS::PathCombine(fullPath, "datapacks"), true); } void WorldListPage::on_actionReset_Icon_triggered() diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 3e3c36b7b..ae4bd49d7 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -200,7 +200,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/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 48e66efca..f3c7ff60b 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -207,6 +207,11 @@ void ResourceModel::loadEntry(QModelIndex& entry) return; versionRequestSucceeded(doc, pack, 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)); + }; if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) runInfoJob(job); @@ -228,7 +233,13 @@ 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] { + if (!s_running_models.constFind(this).value()) + return; + qCritical() << tr("The request was aborted for an unknown reason"); }; if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 44a91003d..ae48e5523 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -200,6 +200,11 @@ void ResourcePage::updateUi() } if (current_pack->extraDataLoaded) { + if (current_pack->extraData.status == "archived") { + text += "

" + tr("This project has been archived. It will not receive any further updates unless the author decides " + "to unarchive the project."); + } + if (!current_pack->extraData.donate.isEmpty()) { text += "

" + tr("Donate information: "); auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { @@ -404,9 +409,9 @@ void ResourcePage::openUrl(const QUrl& url) auto jump = [url, slug, model, view] { for (int row = 0; row < model->rowCount({}); row++) { const QModelIndex index = model->index(row); - const auto pack = model->data(index, Qt::UserRole).value(); + const auto pack = model->data(index, Qt::UserRole).value(); - if (pack.slug == slug) { + if (pack->slug == slug) { view->setCurrentIndex(index); return; } diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index 8875a9452..3b266bcef 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -170,6 +170,10 @@ void ListModel::performPaginatedSearch() callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); }; callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); }; + callbacks.on_abort = [this] { + qCritical() << "Search task aborted by an unknown reason!"; + searchRequestFailed("Abborted"); + }; static const FlameAPI api; if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) { jobPtr = job; diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index 16055c4bb..f1fd9b5d8 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -34,6 +34,7 @@ */ #include "FlamePage.h" +#include "ui/dialogs/CustomMessageBox.h" #include "ui_FlamePage.h" #include @@ -193,6 +194,8 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde suggestCurrent(); }); QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); }); + connect(netJob, &NetJob::failed, + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); netJob->start(); } else { for (auto version : current.versions) { diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp index d46f16b4d..ac06f4cdd 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp @@ -20,6 +20,7 @@ #include "ui/widgets/ProjectItem.h" #include "ui_ImportFTBPage.h" +#include #include #include "FileSystem.h" #include "ListModel.h" @@ -56,6 +57,13 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch); + connect(ui->browseButton, &QPushButton::clicked, this, [this] { + auto path = listModel->getPath(); + QString dir = QFileDialog::getExistingDirectory(this, tr("Select FTBApp instances directory"), path, QFileDialog::ShowDirsOnly); + if (!dir.isEmpty()) + listModel->setPath(dir); + }); + ui->modpackList->setItemDelegate(new ProjectItemDelegate(this)); ui->modpackList->selectionModel()->reset(); } diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui index 5e09fb6d1..6613a5939 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui +++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui @@ -11,7 +11,7 @@
- + @@ -21,28 +21,7 @@ - - - - - - Search and filter... - - - true - - - - - - - Search - - - - - - + @@ -69,6 +48,54 @@ + + + + + + Search and filter... + + + true + + + + + + + Search + + + + + + + Select FTBApp instances directory + + + + + + + .. + + + true + + + + + + + + + Note: If your FTB instances are not in the default location, select it using the button next to search. + + + Qt::AlignCenter + + +
diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index 134bdc0c3..e058937a6 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -17,11 +17,13 @@ */ #include "ListModel.h" +#include #include #include #include #include #include +#include "Application.h" #include "FileSystem.h" #include "StringUtils.h" #include "modplatform/import_ftb/PackHelpers.h" @@ -29,7 +31,7 @@ namespace FTBImportAPP { -QString getPath() +QString getStaticPath() { QString partialPath; #if defined(Q_OS_OSX) @@ -42,14 +44,14 @@ QString getPath() return FS::PathCombine(partialPath, ".ftba"); } -const QString ListModel::FTB_APP_PATH = getPath(); +static const QString FTB_APP_PATH = FS::PathCombine(getStaticPath(), "instances"); void ListModel::update() { beginResetModel(); modpacks.clear(); - QString instancesPath = FS::PathCombine(FTB_APP_PATH, "instances"); + QString instancesPath = getPath(); if (auto instancesInfo = QFileInfo(instancesPath); instancesInfo.exists() && instancesInfo.isDir()) { QDirIterator directoryIterator(instancesPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks); @@ -168,4 +170,17 @@ FilterModel::Sorting FilterModel::getCurrentSorting() { return currentSorting; } +void ListModel::setPath(QString path) +{ + APPLICATION->settings()->set("FTBAppInstancesPath", path); + update(); +} + +QString ListModel::getPath() +{ + auto path = APPLICATION->settings()->get("FTBAppInstancesPath").toString(); + if (path.isEmpty() || !QFileInfo(path).exists()) + path = FTB_APP_PATH; + return path; +} } // namespace FTBImportAPP \ No newline at end of file diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.h b/launcher/ui/pages/modplatform/import_ftb/ListModel.h index 111928276..ed33a88f3 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.h +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.h @@ -60,7 +60,8 @@ class ListModel : public QAbstractListModel { void update(); - static const QString FTB_APP_PATH; + QString getPath(); + void setPath(QString path); private: ModpackList modpacks; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index f691a185d..bac294b60 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -140,6 +140,10 @@ void ModpackListModel::performPaginatedSearch() callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); }; callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); }; + callbacks.on_abort = [this] { + qCritical() << "Search task aborted by an unknown reason!"; + searchRequestFailed("Aborted"); + }; static const ModrinthAPI api; if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) { jobPtr = job; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 8e2b9a902..da5fe1e7b 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -35,6 +35,7 @@ */ #include "ModrinthPage.h" +#include "ui/dialogs/CustomMessageBox.h" #include "ui_ModrinthPage.h" #include "ModrinthModel.h" @@ -103,6 +104,7 @@ void ModrinthPage::retranslate() void ModrinthPage::openedImpl() { BasePage::openedImpl(); + suggestCurrent(); triggerSearch(); } @@ -182,6 +184,8 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI suggestCurrent(); }); QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); }); + connect(netJob, &NetJob::failed, + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); netJob->start(); } else updateUI(); @@ -235,6 +239,8 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI suggestCurrent(); }); QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); }); + connect(netJob, &NetJob::failed, + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); netJob->start(); } else { @@ -262,6 +268,11 @@ void ModrinthPage::updateUI() text += "
" + tr(" by ") + QString("%2").arg(std::get<1>(current.author).toString(), std::get<0>(current.author)); if (current.extraInfoLoaded) { + if (current.extra.status == "archived") { + text += "

" + tr("This project has been archived. It will not receive any further updates unless the author decides " + "to unarchive the project."); + } + if (!current.extra.donate.isEmpty()) { text += "

" + tr("Donate information: "); auto donateToStr = [](Modrinth::DonationData& donate) -> QString { 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/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index 190b7c68f..6b1ec8cb5 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -34,6 +34,7 @@ */ #include "TechnicPage.h" +#include "ui/dialogs/CustomMessageBox.h" #include "ui/widgets/ProjectItem.h" #include "ui_TechnicPage.h" @@ -208,6 +209,8 @@ void TechnicPage::suggestCurrent() metadataLoaded(); }); + connect(jobPtr.get(), &NetJob::failed, + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); jobPtr = netJob; jobPtr->start(); @@ -258,6 +261,8 @@ void TechnicPage::metadataLoaded() netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response)); QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded); + connect(jobPtr.get(), &NetJob::failed, + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); jobPtr = netJob; jobPtr->start(); 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/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 60b92b28b..6946df41f 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -88,7 +88,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o } { // Description painting - auto description = index.data(UserDataTypes::DESCRIPTION).toString(); + auto description = index.data(UserDataTypes::DESCRIPTION).toString().simplified(); QTextLayout text_layout(description, opt.font); diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.cpp b/launcher/ui/widgets/ThemeCustomizationWidget.cpp index 0de97441f..25b91857c 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.cpp +++ b/launcher/ui/widgets/ThemeCustomizationWidget.cpp @@ -34,11 +34,11 @@ ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget* parent) : QWidget(pa connect(ui->backgroundCatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyCatTheme); connect(ui->iconsFolder, &QPushButton::clicked, this, - [] { DesktopServices::openDirectory(APPLICATION->themeManager()->getIconThemesFolder().path()); }); + [] { DesktopServices::openPath(APPLICATION->themeManager()->getIconThemesFolder().path()); }); connect(ui->widgetStyleFolder, &QPushButton::clicked, this, - [] { DesktopServices::openDirectory(APPLICATION->themeManager()->getApplicationThemesFolder().path()); }); + [] { DesktopServices::openPath(APPLICATION->themeManager()->getApplicationThemesFolder().path()); }); connect(ui->catPackFolder, &QPushButton::clicked, this, - [] { DesktopServices::openDirectory(APPLICATION->themeManager()->getCatPacksFolder().path()); }); + [] { DesktopServices::openPath(APPLICATION->themeManager()->getCatPacksFolder().path()); }); } ThemeCustomizationWidget::~ThemeCustomizationWidget() diff --git a/launcher/ui/widgets/VariableSizedImageObject.cpp b/launcher/ui/widgets/VariableSizedImageObject.cpp index f655fc38d..cebf2a5f1 100644 --- a/launcher/ui/widgets/VariableSizedImageObject.cpp +++ b/launcher/ui/widgets/VariableSizedImageObject.cpp @@ -102,14 +102,7 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source, auto full_entry_path = entry->getFullPath(); auto source_url = source; - connect(job, &NetJob::succeeded, this, [this, doc, full_entry_path, source_url, posInDocument] { - qDebug() << "Loaded resource at" << full_entry_path; - - // If we flushed, don't proceed. - if (!m_fetching_images.contains(source_url)) - return; - - QImage image(full_entry_path); + auto loadImage = [this, doc, full_entry_path, source_url, posInDocument](const QImage& image) { doc->addResource(QTextDocument::ImageResource, source_url, image); parseImage(doc, image, posInDocument); @@ -121,6 +114,23 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source, doc->setPageSize(size); m_fetching_images.remove(source_url); + }; + connect(job, &NetJob::succeeded, this, [this, full_entry_path, source_url, loadImage] { + qDebug() << "Loaded resource at:" << full_entry_path; + // If we flushed, don't proceed. + if (!m_fetching_images.contains(source_url)) + return; + + QImage image(full_entry_path); + loadImage(image); + }); + connect(job, &NetJob::failed, this, [this, full_entry_path, source_url, loadImage](QString reason) { + qWarning() << "Failed resource at:" << full_entry_path << " because:" << reason; + // If we flushed, don't proceed. + if (!m_fetching_images.contains(source_url)) + return; + + loadImage(QImage()); }); connect(job, &NetJob::finished, job, &NetJob::deleteLater); diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 1c5aaf1e8..5fe22bdd0 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -436,8 +436,8 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar } { // log debug program info - qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME) << "Updater" - << ", (c) 2022-2023 " << qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", ")); + qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + " Updater, " + + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", ")); qDebug() << "Version : " << BuildConfig.printableVersionString(); qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC; 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/CMakeLists.txt b/libraries/launcher/CMakeLists.txt index 7bf160760..4cd1ba58b 100644 --- a/libraries/launcher/CMakeLists.txt +++ b/libraries/launcher/CMakeLists.txt @@ -25,13 +25,14 @@ set(LEGACY_SRC legacy/org/prismlauncher/legacy/LegacyLauncher.java legacy/org/prismlauncher/legacy/fix/online/Handler.java legacy/org/prismlauncher/legacy/fix/online/OnlineFixes.java + legacy/org/prismlauncher/legacy/fix/online/OnlineModeFix.java legacy/org/prismlauncher/legacy/fix/online/SkinFix.java legacy/org/prismlauncher/legacy/utils/Base64.java legacy/org/prismlauncher/legacy/utils/api/MojangApi.java legacy/org/prismlauncher/legacy/utils/api/Texture.java legacy/org/prismlauncher/legacy/utils/json/JsonParseException.java legacy/org/prismlauncher/legacy/utils/json/JsonParser.java - legacy/org/prismlauncher/legacy/utils/url/CustomUrlConnection.java + legacy/org/prismlauncher/legacy/utils/url/ByteArrayUrlConnection.java legacy/org/prismlauncher/legacy/utils/url/UrlUtils.java legacy/net/minecraft/Launcher.java legacy/org/prismlauncher/legacy/LegacyProxy.java diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/Handler.java b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/Handler.java index f85a8bc79..5ef3d7ac2 100644 --- a/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/Handler.java +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/Handler.java @@ -53,11 +53,16 @@ final class Handler extends URLStreamHandler { protected URLConnection openConnection(URL address, Proxy proxy) throws IOException { URLConnection result; - // try skin fix + // try various fixes... result = SkinFix.openConnection(address, proxy); if (result != null) return result; + result = OnlineModeFix.openConnection(address, proxy); + if (result != null) + return result; + + // ...then give up and make the request directly return UrlUtils.openConnection(address, proxy); } } diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/OnlineFixes.java b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/OnlineFixes.java index 88facff69..9ba57ff71 100644 --- a/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/OnlineFixes.java +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/OnlineFixes.java @@ -46,6 +46,8 @@ import java.net.URLStreamHandlerFactory; /** * Fixes skins by redirecting to other URLs. + * Thanks to MineOnline for the implementation from which this was inspired! + * See https://github.com/ahnewark/MineOnline/tree/main/src/main/java/gg/codie/mineonline/protocol. * * @see {@link Handler} * @see {@link UrlUtils} diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/OnlineModeFix.java b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/OnlineModeFix.java new file mode 100644 index 000000000..1bab76d53 --- /dev/null +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/OnlineModeFix.java @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 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. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.prismlauncher.legacy.fix.online; + +import org.prismlauncher.legacy.utils.url.UrlUtils; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; + +public final class OnlineModeFix { + public static URLConnection openConnection(URL address, Proxy proxy) throws IOException { + // we start with "http://www.minecraft.net/game/joinserver.jsp?user=..." + if (!(address.getHost().equals("www.minecraft.net") && address.getPath().equals("/game/joinserver.jsp"))) + return null; + + // change it to "https://session.minecraft.net/game/joinserver.jsp?user=..." + // this seems to be the modern version of the same endpoint... + // maybe Mojang planned to patch old versions of the game to use it + // if it ever disappears this should be changed to use sessionserver.mojang.com/session/minecraft/join + // which of course has a different usage requiring JSON serialisation... + URL url; + try { + url = new URL("https", "session.minecraft.net", address.getPort(), address.getFile()); + } catch (MalformedURLException e) { + throw new AssertionError("url should be valid", e); + } + + return UrlUtils.openConnection(url, proxy); + } +} diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/SkinFix.java b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/SkinFix.java index e734bdbc7..d5b185450 100644 --- a/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/SkinFix.java +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/SkinFix.java @@ -54,7 +54,7 @@ package org.prismlauncher.legacy.fix.online; import org.prismlauncher.legacy.utils.api.MojangApi; import org.prismlauncher.legacy.utils.api.Texture; -import org.prismlauncher.legacy.utils.url.CustomUrlConnection; +import org.prismlauncher.legacy.utils.url.ByteArrayUrlConnection; import org.prismlauncher.legacy.utils.url.UrlUtils; import java.awt.AlphaComposite; @@ -97,9 +97,9 @@ final class SkinFix { URLConnection connection = UrlUtils.openConnection(texture.getUrl(), proxy); try (InputStream in = connection.getInputStream()) { - // thank you craftycodie! + // thank you ahnewark! // this is heavily based on - // https://github.com/craftycodie/MineOnline/blob/4f4f86f9d051e0a6fd7ff0b95b2a05f7437683d7/src/main/java/gg/codie/mineonline/gui/textures/TextureHelper.java#L17 + // https://github.com/ahnewark/MineOnline/blob/4f4f86f9d051e0a6fd7ff0b95b2a05f7437683d7/src/main/java/gg/codie/mineonline/gui/textures/TextureHelper.java#L17 BufferedImage image = ImageIO.read(in); Graphics2D graphics = image.createGraphics(); graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); @@ -131,7 +131,7 @@ final class SkinFix { image = image.getSubimage(0, 0, 64, 32); ImageIO.write(image, "png", out); - return new CustomUrlConnection(out.toByteArray()); + return new ByteArrayUrlConnection(out.toByteArray()); } } diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/CustomUrlConnection.java b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/ByteArrayUrlConnection.java similarity index 86% rename from libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/CustomUrlConnection.java rename to libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/ByteArrayUrlConnection.java index 71b0e68f2..bc9cf2cc2 100644 --- a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/CustomUrlConnection.java +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/ByteArrayUrlConnection.java @@ -40,16 +40,12 @@ import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; -public final class CustomUrlConnection extends HttpURLConnection { +public final class ByteArrayUrlConnection extends HttpURLConnection { private final InputStream in; - public CustomUrlConnection(byte[] data) { - this(new ByteArrayInputStream(data)); - } - - public CustomUrlConnection(InputStream in) { + public ByteArrayUrlConnection(byte[] data) { super(null); - this.in = in; + this.in = new ByteArrayInputStream(data); } @Override @@ -58,12 +54,7 @@ public final class CustomUrlConnection extends HttpURLConnection { } @Override - public void disconnect() { - try { - in.close(); - } catch (IOException e) { - } - } + public void disconnect() {} @Override public InputStream getInputStream() throws IOException { diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/UrlUtils.java b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/UrlUtils.java index b0072485e..ae91b683c 100644 --- a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/UrlUtils.java +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/UrlUtils.java @@ -101,7 +101,7 @@ public final class UrlUtils { } catch (IOException | Error | RuntimeException e) { throw e; // rethrow if possible } catch (Throwable e) { - throw new AssertionError(e); // oh dear! this isn't meant to happen + throw new AssertionError("openConnection should not throw", e); // oh dear! this isn't meant to happen } } } diff --git a/libraries/launcher/org/prismlauncher/EntryPoint.java b/libraries/launcher/org/prismlauncher/EntryPoint.java index 699adfe30..8b9046f0c 100644 --- a/libraries/launcher/org/prismlauncher/EntryPoint.java +++ b/libraries/launcher/org/prismlauncher/EntryPoint.java @@ -81,10 +81,9 @@ public final class EntryPoint { PreLaunchAction action = PreLaunchAction.PROCEED; try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))) { - String line; - while (action == PreLaunchAction.PROCEED) { - if ((line = reader.readLine()) != null) + String line = reader.readLine(); + if (line != null) action = parseLine(line, params); else action = PreLaunchAction.ABORT; @@ -105,7 +104,7 @@ public final class EntryPoint { return ExitCode.ABORT; } - setProperties(params); + SystemProperties.apply(params); String launcherType = params.getString("launcher"); @@ -141,43 +140,10 @@ public final class EntryPoint { } } - private static void setProperties(Parameters params) { - String launcherBrand = params.getString("launcherBrand", null); - String launcherVersion = params.getString("launcherVersion", null); - String name = params.getString("instanceName", null); - String iconId = params.getString("instanceIconKey", null); - String iconPath = params.getString("instanceIconPath", null); - String windowTitle = params.getString("windowTitle", null); - String windowDimensions = params.getString("windowParams", null); - - if (launcherBrand != null) - System.setProperty("minecraft.launcher.brand", launcherBrand); - if (launcherVersion != null) - System.setProperty("minecraft.launcher.version", launcherVersion); - - // set useful properties for mods - if (name != null) - System.setProperty("org.prismlauncher.instance.name", name); - if (iconId != null) - System.setProperty("org.prismlauncher.instance.icon.id", iconId); - if (iconPath != null) - System.setProperty("org.prismlauncher.instance.icon.path", iconPath); - if (windowTitle != null) - System.setProperty("org.prismlauncher.window.title", windowTitle); - if (windowDimensions != null) - System.setProperty("org.prismlauncher.window.dimensions", windowDimensions); - - // set multimc properties for compatibility - if (name != null) - System.setProperty("multimc.instance.title", name); - if (iconId != null) - System.setProperty("multimc.instance.icon", iconId); - } - private static PreLaunchAction parseLine(String input, Parameters params) throws ParseException { switch (input) { case "": - break; + return PreLaunchAction.PROCEED; case "launch": return PreLaunchAction.LAUNCH; @@ -192,9 +158,9 @@ public final class EntryPoint { throw new ParseException(input, "[key] [value]"); params.add(pair[0], pair[1]); - } - return PreLaunchAction.PROCEED; + return PreLaunchAction.PROCEED; + } } private enum PreLaunchAction { PROCEED, LAUNCH, ABORT } diff --git a/libraries/launcher/org/prismlauncher/SystemProperties.java b/libraries/launcher/org/prismlauncher/SystemProperties.java new file mode 100644 index 000000000..5120e930d --- /dev/null +++ b/libraries/launcher/org/prismlauncher/SystemProperties.java @@ -0,0 +1,38 @@ +package org.prismlauncher; + +import org.prismlauncher.utils.Parameters; + +public final class SystemProperties { + public static void apply(Parameters params) { + String launcherBrand = params.getString("launcherBrand", null); + String launcherVersion = params.getString("launcherVersion", null); + String name = params.getString("instanceName", null); + String iconId = params.getString("instanceIconKey", null); + String iconPath = params.getString("instanceIconPath", null); + String windowTitle = params.getString("windowTitle", null); + String windowDimensions = params.getString("windowParams", null); + + if (launcherBrand != null) + System.setProperty("minecraft.launcher.brand", launcherBrand); + if (launcherVersion != null) + System.setProperty("minecraft.launcher.version", launcherVersion); + + // set useful properties for mods + if (name != null) + System.setProperty("org.prismlauncher.instance.name", name); + if (iconId != null) + System.setProperty("org.prismlauncher.instance.icon.id", iconId); + if (iconPath != null) + System.setProperty("org.prismlauncher.instance.icon.path", iconPath); + if (windowTitle != null) + System.setProperty("org.prismlauncher.window.title", windowTitle); + if (windowDimensions != null) + System.setProperty("org.prismlauncher.window.dimensions", windowDimensions); + + // set multimc properties for compatibility + if (name != null) + System.setProperty("multimc.instance.title", name); + if (iconId != null) + System.setProperty("multimc.instance.icon", iconId); + } +} diff --git a/libraries/launcher/org/prismlauncher/utils/ReflectionUtils.java b/libraries/launcher/org/prismlauncher/utils/ReflectionUtils.java index 3b2bfd9bb..9d03a90a6 100644 --- a/libraries/launcher/org/prismlauncher/utils/ReflectionUtils.java +++ b/libraries/launcher/org/prismlauncher/utils/ReflectionUtils.java @@ -54,15 +54,9 @@ package org.prismlauncher.utils; -import org.prismlauncher.utils.logging.Log; - -import java.applet.Applet; -import java.io.File; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; public final class ReflectionUtils { private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 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/README.md b/nix/README.md index 7a422fc5b..f7923577f 100644 --- a/nix/README.md +++ b/nix/README.md @@ -9,7 +9,8 @@ See [Package variants](#package-variants) for a list of available packages. ## Installing a development release (flake) We use [garnix](https://garnix.io/) to build and cache our development builds. -If you want to avoid rebuilds you may add the garnix cache to your substitutors. +If you want to avoid rebuilds you may add the garnix cache to your substitutors, or use `--accept-flake-config` +to temporarily enable it when using `nix` commands. Example (NixOS): 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/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index 8c6827119..91b213274 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -14,8 +14,8 @@ set(Launcher_DisplayName "Prism Launcher") set(Launcher_Name "${Launcher_CommonName}" PARENT_SCOPE) set(Launcher_DisplayName "${Launcher_DisplayName}" PARENT_SCOPE) -set(Launcher_Copyright "Prism Launcher Contributors\\n© 2021-2022 PolyMC Contributors \\n© 2012-2021 MultiMC Contributors") -set(Launcher_Copyright_Mac "Prism Launcher Contributors, © 2021-2022 PolyMC Contributors and © 2012-2021 MultiMC Contributors" PARENT_SCOPE) +set(Launcher_Copyright "© 2022-2024 Prism Launcher Contributors\\n© 2021-2022 PolyMC Contributors\\n© 2012-2021 MultiMC Contributors") +set(Launcher_Copyright_Mac "© 2022-2024 Prism Launcher Contributors, © 2021-2022 PolyMC Contributors and © 2012-2021 MultiMC Contributors" PARENT_SCOPE) set(Launcher_Copyright "${Launcher_Copyright}" PARENT_SCOPE) set(Launcher_Domain "prismlauncher.org" PARENT_SCOPE) set(Launcher_UserAgent "${Launcher_CommonName}/${Launcher_VERSION_NAME}" PARENT_SCOPE) 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 + + diff --git a/tests/Task_test.cpp b/tests/Task_test.cpp index abc9be905..0740ba0a3 100644 --- a/tests/Task_test.cpp +++ b/tests/Task_test.cpp @@ -37,13 +37,13 @@ class BasicTask_MultiStep : public Task { class BigConcurrentTask : public ConcurrentTask { Q_OBJECT - void startNext() override + void executeNextSubTask() override { // This is here only to help fill the stack a bit more quickly (if there's an issue, of course :^)) // Each tasks thus adds 1024 * 4 bytes to the stack, at the very least. [[maybe_unused]] volatile std::array some_data_on_the_stack{}; - ConcurrentTask::startNext(); + ConcurrentTask::executeNextSubTask(); } }; @@ -71,11 +71,14 @@ class BigConcurrentTaskThread : public QThread { quit(); }); - m_deadline.start(); + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, &BigConcurrentTaskThread::start_timer, Qt::QueuedConnection); + } big_task.run(); exec(); } + void start_timer() { m_deadline.start(); } public: bool passed_the_deadline = false;